feat(cli): persist build output URL + QR via --output-record#2290
Conversation
Previously the artifact download URL and QR code arrived as a transient
custom message and were only printed to stdout, forcing CI users to
scrape the log output with brittle regex pipelines.
Add an opt-in `--output-record <path>` flag to `build request`:
- On a successful build, writes a JSON record to <path> containing
jobId, appId, platform, buildMode, status, outputUrl, qrCodeAscii,
qrCodePngPath, and finishedAt (with a schemaVersion for forward
compatibility).
- A PNG QR code is rendered alongside at <path>.qr.png — handy for
inline posting on PR/issue comments and Slack.
- PNG generation failure is non-fatal; the JSON is always written.
- When --output-upload is not set, the record is still written but
outputUrl is null (callers can branch on it).
Add a sibling `build last-output` command for ergonomic reads:
- `--path <file>` (required): record to read.
- default: prints the JSON (for `jq` piping).
- `--field <name>`: prints a single field's value, newline-terminated
(drop-in for `URL=$(... --field outputUrl)`).
- `--qr`: shortcut for --field qrCodeAscii.
- Unknown fields and unsupported schema versions exit non-zero with a
clear error.
The URL is captured by wrapping the existing BuildLogger.customMsg
handler rather than refactoring stream-handling internals — keeps the
existing print behaviour intact.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughAdds on-disk build output records with optional QR generation, integrates capture/write after successful builds, and introduces ChangesBuild Output Recording and Retrieval System
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~22 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
Merging this PR will degrade performance by 50.41%
|
| Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|
| ❌ | /updates manifest response with metadata |
112.3 µs | 226.6 µs | -50.41% |
Tip
Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.
Comparing feat/build-output-record (154ecec) with main (107e6ed)
Footnotes
-
2 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@cli/src/build/output-record.ts`:
- Around line 42-56: The PNG generation can fail with ENOENT because the target
directory for pngPath (derived from absoluteRecordPath/recordPath) isn't created
until later; before calling QRCode.toFile (and before setting pngPath), ensure
the directory exists by deriving the directory from pngPath (or
absoluteRecordPath) and calling fs.promises.mkdir(dir, { recursive: true }) (or
equivalent sync) and only then call QRCode.toFile; keep the existing try/catch
around QRCode.toFile so qrCodePngPath remains null on other errors.
In `@cli/src/build/request.ts`:
- Around line 1255-1264: The URL-capture wrapper (customMsg that sets
capturedOutputUrl) is only applied when a logger is provided, so when silent
mode passes undefined into streamBuildLogs the wrapper is skipped and output URL
is lost; change the logic so the wrapped BuildLogger is created whenever
options.outputRecord is true (even if baseLogger is undefined) and pass that
wrapped logger into streamBuildLogs (instead of possibly passing undefined).
Implement the wrapper to call through to baseLogger methods only if baseLogger
exists (or use no-op fallbacks) and ensure capturedOutputUrl is set inside the
wrapper's customMsg; reference symbols: log/BuildLogger, baseLogger, customMsg,
capturedOutputUrl, streamBuildLogs, and options.outputRecord/silent.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 89b61d38-52c0-4c1d-9049-1f977e792092
📒 Files selected for processing (5)
cli/src/build/last-output-command.tscli/src/build/output-record.tscli/src/build/request.tscli/src/index.tscli/src/schemas/build.ts
1. output-record.ts: parent directory was created after `QRCode.toFile`,
so a `--output-record /not/yet/here/build.json` invocation lost the
PNG to ENOENT (caught and downgraded to a warning, but the file was
missing). Hoist the `mkdir(..., { recursive: true })` above the PNG
render so both PNG and JSON land in a freshly-created directory.
2. request.ts: the `customMsg` wrapper that captures the artifact URL
only fires when `streamBuildLogs` receives a logger. In silent mode
with no user-supplied logger, the existing condition passed
`undefined`, bypassing the wrapper and dropping the URL on the floor.
Skip the `undefined` shortcut whenever `--output-record` is set so
the wrapped logger always runs.
…pture Replaces the regex-and-tee scraping pattern with the clean opt-in record flow shipped in Cap-go/capgo#2290: --output-record /tmp/build.json → npx @capgo/cli build last-output --field outputUrl Also adds two troubleshooting rows for empty URLs (build had no --output-upload or failed early) and schemaVersion mismatches (producer and reader on different CLI versions).
|
* docs(cloud-build): add GitHub Actions guide using combined .env export
New page walks users through the full GitHub Actions setup:
- one `gh secret set CAPGO_TOKEN` for the API key
- `build credentials manage` → Export to .env → single
`.env.capgo.<appId>` file containing both platforms' secrets
- `gh secret set -f .env.capgo.<appId>` to push every CI secret in one shot
Includes three workflow templates (manual dispatch, tag-based release
with iOS+Android matrix, debug build on push to main), shared-key
conflict guidance, and CI-specific troubleshooting.
Replaces the inline GitHub Actions YAML snippet in getting-started.mdx
with a link to the new page so the canonical workflow examples live in
one place.
* docs(github-actions): use --output-record + last-output for URL/QR capture
Replaces the regex-and-tee scraping pattern with the clean opt-in record
flow shipped in Cap-go/capgo#2290:
--output-record /tmp/build.json → npx @capgo/cli build last-output --field outputUrl
Also adds two troubleshooting rows for empty URLs (build had no
--output-upload or failed early) and schemaVersion mismatches (producer
and reader on different CLI versions).
* docs(github-actions): switch CI examples from npm/npx to bun/bunx
The page's CI examples used `actions/setup-node` + `npm ci` + `npx`,
which (a) violates the website's documented tooling convention
(AGENTS.md: "Always use bun instead of npm. Always use bunx instead of
npx.") and (b) breaks bun-only projects out of the box because `npm ci`
requires a `package-lock.json` that doesn't exist when the lockfile is
`bun.lock`.
Match the rest of cloud-build/*.mdx and the linked `getting-started.mdx`:
- `actions/setup-node@v4 + cache: 'npm'` → `oven-sh/setup-bun@v2`
- `npm ci` → `bun install --frozen-lockfile`
- `npm run build` → `bun run build`
- `npx cap sync …` → `bunx cap sync …`
- `npx @capgo/cli@latest …` → `bunx @capgo/cli@latest …`
- `npm version` → `bun pm version`
- Cache section: rewritten around `~/.bun/install/cache` + `bun.lock`,
with an honest note that bun installs are fast enough that the JS
cache is rarely the bottleneck.
Troubleshooting row updated: "you forgot npm ci before cap sync" → "you
forgot bun install before cap sync".
* docs(github-actions): recommend an explicit @capgo/cli version, not @latest
The schemaVersion-mismatch troubleshooting cell previously said 'pin
both producer and reader to the same @capgo/cli@latest version', which
contradicts itself — @latest is a floating tag that can change between
the producer job and a later reader job, so pinning to it pins nothing.
Recommend an explicit semver pin (e.g. @capgo/cli@7.104.0) on both
sides instead.
* docs(github-actions): capitalize Markdown



Summary
Previously the artifact download URL and QR code arrived as a transient WebSocket message and were only printed to stdout, forcing CI users to scrape build logs with brittle regex pipelines (e.g.
tee /dev/stderr | grep -oE 'https://[^ ]+\.apk'). This PR adds first-class persistence and retrieval.New flags / commands
build request --output-record <path>After a successful build, writes:
<path>containing the full build context and artifact info.<path>.qr.png(handy for inline posting on PR/issue comments and Slack).PNG generation failures are non-fatal — the JSON is always written, with
qrCodePngPath: nullto signal the miss. When--output-uploadis not set, the record is still written butoutputUrlisnull(callers can branch on it).build last-output --path <file>Ergonomic reader. No new lookup magic — just JSON + a small printer.
jqif you want).--field <name>: prints a single field's value, newline-terminated. Drop-in forURL=$(... --field outputUrl).--qr: shortcut for--field qrCodeAscii, ready to paste inside a markdown code fence.Unknown fields and unsupported
schemaVersionexit non-zero with a clear error.Record shape
{ "schemaVersion": 1, "jobId": "job-abc", "appId": "com.example.app", "platform": "android", "buildMode": "debug", "status": "succeeded", "outputUrl": "https://capgo.app/d/abc", "qrCodeAscii": "███ ▄▄▄ ███\n...", "qrCodePngPath": "/tmp/build.json.qr.png", "finishedAt": "2026-05-18T22:14:03.121Z" }schemaVersionis a literal1so future breaking changes can be detected by readers and fail loudly instead of misbehaving.CI example
Implementation notes
BuildLogger.customMsghandler inrequestBuildInternalrather than refactoring stream-handling internals. Existing print behaviour stays intact; the wrapper is only installed when--output-recordis set.src/build/output-record.ts) so the writer is reusable and easy to test in isolation later.src/build/last-output-command.ts) keeps a printable-field allow-list so typos surface asUnknown field "..."instead of mysteriously empty output.Test plan
bun run typecheckcleanbun run lintcleanbun run buildsucceedsbun run test:credentials— 17/17 passbun run test:credentials-validation— 13/13 passbun run test:build-platform-selection— passnode dist/index.js build last-output --helpshows the new flags + examples--field outputUrlprints just the URL--qrprints the ASCII QR--field nonexistenterrors with the allow-list listed--output-upload --output-record /tmp/r.json, verify JSON + PNG land andlast-outputreads them backFollow-up
Docs page at
apps/docs/.../cli/cloud-build/github-actions.mdx(currently in Cap-go/website#704) still references a regex-based "capture build URL" pattern. Once this ships, the docs PR can be updated to use--output-record+build last-outputinstead.Summary by CodeRabbit
New Features
--output-recordto save build metadata and QR codes (ASCII + PNG) after successful buildsbuild last-outputto read saved records and print full JSON, an allowed single field, or an ASCII QR (--qr)Bug Fixes / Reliability
Documentation