Skip to content

feat(cli): persist build output URL + QR via --output-record#2290

Merged
riderx merged 3 commits into
mainfrom
feat/build-output-record
May 18, 2026
Merged

feat(cli): persist build output URL + QR via --output-record#2290
riderx merged 3 commits into
mainfrom
feat/build-output-record

Conversation

@WcaleNieWolny
Copy link
Copy Markdown
Contributor

@WcaleNieWolny WcaleNieWolny commented May 18, 2026

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:

  • A JSON record to <path> containing the full build context and artifact info.
  • A PNG QR code alongside at <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: null to signal the miss. When --output-upload is not set, the record is still written but outputUrl is null (callers can branch on it).

build last-output --path <file>

Ergonomic reader. No new lookup magic — just JSON + a small printer.

  • Default: prints the full JSON (pipe to jq if you want).
  • --field <name>: prints a single field's value, newline-terminated. Drop-in for URL=$(... --field outputUrl).
  • --qr: shortcut for --field qrCodeAscii, ready to paste inside a markdown code fence.

Unknown fields and unsupported schemaVersion exit 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"
}

schemaVersion is a literal 1 so future breaking changes can be detected by readers and fail loudly instead of misbehaving.

CI example

- name: Build
  env:
    CAPGO_TOKEN: ${{ secrets.CAPGO_TOKEN }}
    # …credentials…
  run: |
    npx @capgo/cli@latest build com.example.app \
      --platform android --build-mode debug \
      --output-upload --output-record /tmp/build.json

- name: Comment on PR with build URL
  env:
    GH_TOKEN: ${{ github.token }}
  run: |
    URL=$(npx @capgo/cli build last-output --path /tmp/build.json --field outputUrl)
    if [ -n "$URL" ]; then
      gh pr comment ${{ github.event.pull_request.number }} --body "Debug build ready: $URL"
    fi

Implementation notes

  • The URL is captured by wrapping the existing BuildLogger.customMsg handler in requestBuildInternal rather than refactoring stream-handling internals. Existing print behaviour stays intact; the wrapper is only installed when --output-record is set.
  • JSON + PNG live in their own module (src/build/output-record.ts) so the writer is reusable and easy to test in isolation later.
  • The reader (src/build/last-output-command.ts) keeps a printable-field allow-list so typos surface as Unknown field "..." instead of mysteriously empty output.

Test plan

  • bun run typecheck clean
  • bun run lint clean
  • bun run build succeeds
  • bun run test:credentials — 17/17 pass
  • bun run test:credentials-validation — 13/13 pass
  • bun run test:build-platform-selection — pass
  • node dist/index.js build last-output --help shows the new flags + examples
  • Smoke test — --field outputUrl prints just the URL
  • Smoke test — --qr prints the ASCII QR
  • Smoke test — --field nonexistent errors with the allow-list listed
  • Smoke test — missing file errors with a clean ENOENT message
  • Manual end-to-end: real build with --output-upload --output-record /tmp/r.json, verify JSON + PNG land and last-output reads them back

Follow-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-output instead.

Summary by CodeRabbit

  • New Features

    • Added --output-record to save build metadata and QR codes (ASCII + PNG) after successful builds
    • Added build last-output to read saved records and print full JSON, an allowed single field, or an ASCII QR (--qr)
  • Bug Fixes / Reliability

    • Improved option validation, error/warning messages, and resiliency when reading/writing records or generating QR PNGs
  • Documentation

    • Updated build command docs with end-to-end usage examples

Review Change Stack

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.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 18, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8fd845f7-7b49-4367-80d3-e97090b037a6

📥 Commits

Reviewing files that changed from the base of the PR and between f384b89 and 154ecec.

📒 Files selected for processing (2)
  • cli/src/build/output-record.ts
  • cli/src/build/request.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • cli/src/build/request.ts
  • cli/src/build/output-record.ts

📝 Walkthrough

Walkthrough

Adds on-disk build output records with optional QR generation, integrates capture/write after successful builds, and introduces build last-output to read/display the saved JSON, a single allow-listed field, or an ASCII QR.

Changes

Build Output Recording and Retrieval System

Layer / File(s) Summary
Output record data contracts and schema
cli/src/build/last-output-command.ts, cli/src/build/output-record.ts, cli/src/schemas/build.ts
Defines LastOutputOptions and BuildOutputRecord, and adds outputRecord to buildRequestOptionsSchema.
Output record persistence with QR generation
cli/src/build/output-record.ts
writeBuildOutputRecord writes a stable JSON record, attempts ASCII and PNG QR generation from outputUrl, and reports QR rendering warnings without blocking the JSON write.
Last output CLI command
cli/src/build/last-output-command.ts
lastOutputCommand reads the saved record, enforces schemaVersion === 1, validates --field against an allow-list, and prints full JSON, a single field, or ASCII QR; fatal errors log and exit(1).
Build request output record integration
cli/src/build/request.ts
Wraps the logger to capture qr_download_link, ensures capture runs in silent mode when needed, and writes the output record after successful builds; write failures are logged as warnings.
CLI command registration
cli/src/index.ts
Imports and registers build last-output and adds the --output-record <path> option to build request, updating documentation/examples accordingly.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 I saved the build, a JSON delight,
A QR to shimmer in the terminal light,
Captured the link, wrote it with care,
Last-output reads it, ready to share. ✨📦

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main feature: persisting build output URL and QR code via a new --output-record flag.
Description check ✅ Passed The description is comprehensive, covering summary, implementation details, record shape, CI examples, test plan, and follow-up notes. All required template sections are present and well-documented.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/build-output-record

Comment @coderabbitai help to get the list of available commands and usage tips.

@codspeed-hq
Copy link
Copy Markdown
Contributor

codspeed-hq Bot commented May 18, 2026

Merging this PR will degrade performance by 50.41%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

❌ 1 regressed benchmark
✅ 42 untouched benchmarks
⏩ 2 skipped benchmarks1

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

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)

Open in CodSpeed

Footnotes

  1. 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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 107e6ed and ba9a3e0.

📒 Files selected for processing (5)
  • cli/src/build/last-output-command.ts
  • cli/src/build/output-record.ts
  • cli/src/build/request.ts
  • cli/src/index.ts
  • cli/src/schemas/build.ts

Comment thread cli/src/build/output-record.ts
Comment thread cli/src/build/request.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.
WcaleNieWolny added a commit to Cap-go/website that referenced this pull request May 18, 2026
…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).
@sonarqubecloud
Copy link
Copy Markdown

@riderx riderx merged commit 1d8f303 into main May 18, 2026
41 of 42 checks passed
@riderx riderx deleted the feat/build-output-record branch May 18, 2026 15:19
riderx pushed a commit to Cap-go/website that referenced this pull request May 18, 2026
* 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants