Pear Docs
ReferenceCI & release

pear-ci GitHub Action

Reference for the holepunchto/actions/pear-ci GitHub Action—inputs, outputs, validation, and the underlying pear-ci CLI for staging a Pear app from CI.

The pear-ci GitHub Action stages a directory from your repository into a Hyperdrive on every run, producing a stable pear:// link your app can use as its upgrade field. For the task-oriented setup, see Publish with GitHub Actions.

It is a composite action that installs the pear-ci npm tool, fetches the committed snapshot from main, stages, and commits the updated snapshot back. Each run needs a peer that holds the drive, as Peers and timeouts describes.

Usage

- uses: holepunchto/actions/pear-ci@v1
  with:
    primary-key: ${{ secrets.PEAR_PRIMARY_KEY }}
    namespace: my-app
    target: dist
    dry-run: false

Inputs

InputRequiredDefaultDescription
primary-keyYes—32-byte (64 hex character) key for the Corestore identity. Determines the drive's pear:// link across runs. Store it as a secret.
namespaceYes—Drive namespace name. Combined with primary-key to derive the drive. Only letters, numbers, dots, hyphens, and underscores.
targetYes—Repo-relative path of the directory to stage. Must not be absolute or contain ...
dry-runNofalseWhen true, computes and logs the diff without writing to the drive or committing the snapshot. Must be true or false.
github-tokenNo${{ github.token }}Token used to fetch and commit ci/snapshot.json.
app-slugNo—When set, the snapshot commit is attributed to the matching GitHub App bot identity instead of github-actions[bot].

Outputs

OutputDescription
lengthThe drive's length after staging, the block count of its metadata core, which pear info reports as length. Not set when dry-run is true.
pr-urlURL of the PR created if a direct push of the snapshot to main failed; empty otherwise.

Input validation

The action rejects malformed inputs before staging:

  • primary-key must match ^[a-fA-F0-9]{64}$.
  • namespace must match ^[a-zA-Z0-9._-]+$.
  • target must contain only [a-zA-Z0-9_./-], must not be an absolute path, and must not contain .. (no directory traversal).
  • dry-run must be exactly true or false.

Snapshot commits

The action reads ci/snapshot.json from a branch named main before staging and writes it back to main afterward. The action hard-codes the branch name. The snapshot records each core's length so subsequent runs sync only what changed. When dry-run is true, no commit is made. If pushing the snapshot directly to main fails, the action pushes a pear-ci/snapshot-<run-id> branch and opens a PR, exposing its URL via the pr-url output.

The file must exist before the first run. Commit it to main containing an empty list, []. Without it, the Fetch snapshot from main step fails with gh: Not Found (HTTP 404).

Grant the workflow contents: write (and pull-requests: write if you want the PR fallback) so the snapshot commit can land.

Never reset ci/snapshot.json to [] after the first release. A run with an empty snapshot against a drive that already has releases writes a second history under the same key: pear-ci 0.1.2 logs [hypercore] conflict detected, still exits with code 0, and the seeder's view of the drive drops to length 0. pear-ci 0.1.2 can't recover from this state.

Peers and timeouts

A runner starts with no copy of the drive, so each run relies on a peer that holds it, such as an always-on machine running pear seed on the drive's link. pear-ci waits on that peer twice, with no time limit:

  • Before staging, until it has synced each core up to its length in the snapshot. It logs syncing <key> to length <n> for each core, dry runs included. If a peer holds more of a core than the snapshot records, the run fails with Core <key> length (<n>) is higher than length in snapshot.json.
  • After staging, until a connected peer has downloaded every new block. It logs waiting synced remote peers for key <drive-key>, then all remote peers synced.

With no peer online, a run waits at one of these lines until the job's timeout-minutes ends it, which defaults to 360 minutes. Set timeout-minutes on the job, well over the time your largest release takes to upload. If a timeout or a cancel stops a run partway through its upload, the seeder records the release's new length but misses blocks that only the stopped runner had, and pear-ci 0.1.2 can't recover from this state: later runs fail with the same length error, and a snapshot edited to the new length leaves the run waiting at waiting synced remote peers.

Underlying CLI

Outside of Actions, the same staging runs through the pear-ci CLI:

npm install -g pear-ci
pear-ci \
  --primary-key <64-hex-key> \
  --name my-app \
  --target ./dist \
  --snapshot ci/snapshot.json \
  --storage /tmp/store
FlagDescription
--primary-key32-byte hex key for the Corestore.
--nameNamespace name used to derive the drive.
--targetLocal directory to mirror into the drive.
--snapshotPath to read/write the JSON snapshot.
--storageCorestore storage path.
--dry-runCompute the diff without writing to the drive.
--outputPath to write the staged drive's key (z32) and length to, as JSON: { "key": "<drive-key>", "length": <n> }. Added in pear-ci 0.1.2.

The CLI logs the drive key it stages to (starting stage for key <drive-key>)—that key is your pear:// link. With --output, it also writes the key and length to a file, so a script does not have to parse the log. The action uses it to set its length output. See the pear-ci README for the programmatic API and snapshot format.

See also

Last updated on

Was this helpful?

On this page