Pear Docs

Preflight a release before staging

Check a deployment directory and your write access before you stage: confirm every architecture landed with pear build --json, check pear info's writable field, then dry-run the stage. Includes a CI version.

Run these checks between pear build and pear stage. They catch the two release problems that otherwise show up late: an architecture missing from the deployment directory, and a machine that cannot write to the link you are about to stage to.

Both checks need Pear 3.6.0 or later. Before 3.6.0, pear build printed nothing when it succeeded, and pear info had no writable field. Run pear versions to see which you have.

Need the pear CLI? Install it from install.pears.com, or prefix any command below with npx. See Install & upgrade for details.

Before you begin

You need the per-OS makes collected on one machine, a stage link from pear touch, and jq for the scripted checks. If you have not built a deployment directory before, follow Deploy your application through step 3 first. Step 1 below does what its step 4 does, and adds a report.

1. Build, and list what landed

Run pear build with --json so every artifact it places is reported as its own event, and keep the output:

pear build --json \
  --package=./pear-chat/package.json \
  --darwin-arm64-app ./pear-chat/out/PearChat-darwin-arm64/PearChat.app \
  --darwin-x64-app   ./pear-chat/out/PearChat-darwin-x64/PearChat.app \
  --linux-arm64-app  ./pear-chat/out/PearChat-linux-arm64/PearChat.AppImage \
  --linux-x64-app    ./pear-chat/out/PearChat-linux-x64/PearChat.AppImage \
  --win32-x64-app    ./pear-chat/out/PearChat-win32-x64/PearChat.msix \
  --target pear-chat-1.0.1 > build.ndjson

Without --json, the same run prints the app name, version, and target, then one + <file> (origin: <source>) line for each artifact, and ends with Build complete!. That is enough to read by eye. The --json form is the one to script against. It emits one object per line, tagged building (once, with name, version, and target), executable (once per artifact, with file and origin), and final. The pear build reference describes all three:

{"cmd":"build","tag":"building","data":{"name":"PearChat","version":"1.0.1","target":"pear-chat-1.0.1"}}
{"cmd":"build","tag":"executable","data":{"file":"/work/pear-chat-1.0.1/by-arch/linux-arm64/app/PearChat.AppImage","origin":"/work/pear-chat/out/PearChat-linux-arm64/PearChat.AppImage"}}
{"cmd":"build","tag":"executable","data":{"file":"/work/pear-chat-1.0.1/by-arch/darwin-arm64/app/PearChat.app","origin":"/work/pear-chat/out/PearChat-darwin-arm64/PearChat.app"}}
{"cmd":"build","tag":"executable","data":{"file":"/work/pear-chat-1.0.1/by-arch/linux-x64/app/PearChat.AppImage","origin":"/work/pear-chat/out/PearChat-linux-x64/PearChat.AppImage"}}
{"cmd":"build","tag":"executable","data":{"file":"/work/pear-chat-1.0.1/by-arch/darwin-x64/app/PearChat.app","origin":"/work/pear-chat/out/PearChat-darwin-x64/PearChat.app"}}
{"cmd":"build","tag":"executable","data":{"file":"/work/pear-chat-1.0.1/by-arch/win32-x64/app/PearChat.msix","origin":"/work/pear-chat/out/PearChat-win32-x64/PearChat.msix"}}
{"cmd":"build","tag":"final","data":{"success":true}}

An executable event is reported as each copy finishes, so the lines can come in a different order from your flags, as they do above. A failed build writes one error object to the file instead of a final, and exits non-zero, so nothing shows on the terminal. Check the exit status, or open the file, before you trust the counts below.

An executable event carries no architecture field. The architecture is the folder name after by-arch/ in file, so count events per architecture by reading it from the path:

jq -r 'select(.tag == "executable") | .data.file
       | capture(".*[/\\\\]by-arch[/\\\\](?<arch>[^/\\\\]+)[/\\\\]app[/\\\\]").arch' build.ndjson | sort | uniq -c

Each architecture you passed should appear. The count per architecture is the number of artifacts you passed for it, which is usually 1. If you pass a flag more than once to ship a standalone binary beside the desktop app, expect that many.

2. Check that this machine can write

pear info reports whether this machine holds the key pair for a drive. Run it against the stage link before you stage:

pear info --json pear://qxenz5wmspmryjc13m9yzsqj1conqotn8fb4ocbufwtz9mtbqq5o \
  | jq -e 'select(.tag == "info") | .data
           | if has("writable") then .writable == true
             else error("this Pear does not report writable") end'

jq -e exits non-zero when the answer is false, when writable is missing (a Pear older than 3.6.0), or when no info object came back at all, so the command works as a gate in a script. In a script, run set -o pipefail first so a failing pear info fails the gate too, and keep stdout clean, because a stray non-JSON line makes jq fail. In the human-readable output the same answer is the writable row.

Run it against a link you have staged to at least once. A link you have only just created with pear touch has no data on this machine yet, so pear info prints [ Empty ] and sends an empty object instead of info. The gate then fails even though you hold the key. On a first release, skip this step and let the first real stage be the test.

writable: false means this machine's corestore does not have the key pair for that link. It is the first thing to check when a stage fails because this machine cannot write to the drive, since staged and provisioned drives are machine-bound. See Recovering from lost write access.

Two limits to keep in mind:

  • It describes the stage (or provision) link. A multisig production link is written by a quorum of signers, so no machine holds a key pair for it, and writable reads false there even when signing works. Do not use it to gate a production release. See Sign with multisig.
  • It tells you whether this machine holds the key, not whether the stage will succeed. It does not check that the link is seeded or reachable.

3. Dry-run the stage

With the deployment directory complete and write access confirmed, dry-run the stage and read the file-by-file diff before running the real one. A dry run does not write, so it cannot detect missing write access. That is what step 2 is for.

pear stage --dry-run pear://qxenz5wmspmryjc13m9yzsqj1conqotn8fb4ocbufwtz9mtbqq5o ./pear-chat-1.0.1

Run the same checks in CI

Fail the job before it stages anything. This step reuses build.ndjson from step 1. It fails when the build did not finish cleanly or when any architecture you expect is missing:

- name: Fail on a missing architecture
  shell: bash
  env:
    EXPECTED: darwin-arm64 darwin-x64 linux-arm64 linux-x64 win32-x64
  run: |
    : "${EXPECTED:?set EXPECTED to the architectures you build}"
    [ -s build.ndjson ] || { echo "build.ndjson is missing or empty (needs Pear 3.6.0 and pear build --json)"; exit 1; }
    jq -e -s 'any(.[]; .tag == "final" and .data.success == true) and all(.[]; .tag != "error")' \
      build.ndjson > /dev/null || { echo "pear build did not finish cleanly"; exit 1; }
    found=$(jq -r 'select(.tag == "executable") | .data.file
                   | capture(".*[/\\\\]by-arch[/\\\\](?<arch>[^/\\\\]+)[/\\\\]app[/\\\\]").arch' build.ndjson | sort -u)
    status=0
    for arch in $EXPECTED; do
      grep -qxF "$arch" <<< "$found" || { echo "missing: $arch"; status=1; }
    done
    exit $status

shell: bash makes the step stop on a failed command, and it also works on a Windows runner through Git Bash.

Add the writable check from step 2 as a second step only when the job runs pear stage itself on a machine that holds the stage link's key pair, such as a persistent self-hosted runner. A fresh hosted runner has no key pair, and the pear-ci staging path does not use Pear's local corestore, so the check does not apply there. To produce the signed per-OS builds this assumes, see Build and sign desktop apps with GitHub Actions.

Where to go next

Last updated on

Was this helpful?

On this page