Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
e28991b
feat(cli): guided App Store Connect API key via precompiled macOS helper
WcaleNieWolny Jun 12, 2026
7341f59
feat(cli): move ASC key helper into repo + wire it into iOS onboarding
WcaleNieWolny Jun 12, 2026
ef606c9
test(cli): journey tests for the ASC key helper integration
WcaleNieWolny Jun 12, 2026
2748e28
test(cli): move ASC key helper journey tests to the private suite
WcaleNieWolny Jun 12, 2026
c1e2899
fix(cli): register new p8 fork steps in STEP_PROGRESS + getPhaseLabel
WcaleNieWolny Jun 12, 2026
a4d576f
feat(cli): expand ASC helper protocol with diagnostic log lines → int…
WcaleNieWolny Jun 13, 2026
9f88156
refactor(cli): don't render ASC helper log lines — route them, redact…
WcaleNieWolny Jun 13, 2026
2e7713f
feat(cli): only show the .p8 source fork when the guided helper is av…
WcaleNieWolny Jun 13, 2026
9593a08
fix(helper): harden highlight overlay loop + probe the generate-butto…
WcaleNieWolny Jun 13, 2026
0186a3b
fix(cli): resume the automated p8 path on the helper, not the manual …
WcaleNieWolny Jun 13, 2026
e640c34
fix(helper): attach the '+' highlight directly to the button, not an …
WcaleNieWolny Jun 13, 2026
607c3e2
fix(cli): kill the guided helper on quit so the CLI doesn't hang
WcaleNieWolny Jun 13, 2026
762f6fc
fix(cli): scope helper-kill to unmount + surface crash signal (NO_RES…
WcaleNieWolny Jun 13, 2026
801e104
fix(helper): bigger, rounder ring on the '+' highlight
WcaleNieWolny Jun 13, 2026
d12bc4a
feat(helper): persist Apple sign-in across runs (no re-login each time)
WcaleNieWolny Jun 13, 2026
2e985c5
fix(helper): persist Apple sign-in via native cookies, not forIdentif…
WcaleNieWolny Jun 13, 2026
14795cf
feat(helper): persist Apple sign-in via WKWebsiteDataStore(forIdentif…
WcaleNieWolny Jun 13, 2026
0b6742e
chore(helper): dev script to locally sign the helper as a .app for pe…
WcaleNieWolny Jun 13, 2026
98c1829
feat(helper): auto-detect persistence via bundle id, drop CAPGO_ASC_K…
WcaleNieWolny Jun 13, 2026
0fe1f87
fix(helper): use a fixed store identifier — file-backed UUID never pe…
WcaleNieWolny Jun 13, 2026
e97d7e1
fix(helper): stop wiping the persisted session on transient auth-redi…
WcaleNieWolny Jun 13, 2026
e7aac52
fix(helper): detect sign-in on any ASC page so a restored session adv…
WcaleNieWolny Jun 13, 2026
35c451d
fix(cli): address hostile-review findings for the ASC key helper
WcaleNieWolny Jun 13, 2026
cdf760a
fix(helper): cleaner '+' highlight — framed ring with a gap, not a so…
WcaleNieWolny Jun 13, 2026
443bdde
fix(helper): force the '+' element round so its highlight ring is a c…
WcaleNieWolny Jun 13, 2026
bd74b78
fix(helper): highlight the REAL '+' button, not a 5x13 sliver
WcaleNieWolny Jun 13, 2026
90a9ae2
fix(helper): match the 16x18 SVG '+' button (probe-confirmed), restor…
WcaleNieWolny Jun 13, 2026
6ee0453
fix(helper): use the unclipped overlay for the '+' (direct ring was c…
WcaleNieWolny Jun 13, 2026
cb9e3af
fix(helper): highlight the VISIBLE '+' SVG, not the hidden duplicate …
WcaleNieWolny Jun 13, 2026
588e467
fix(helper): attach the '+' ring directly to the element, not a float…
WcaleNieWolny Jun 13, 2026
fd3f35f
fix(helper): tighten the '+' ring — drop the fat glow blob
WcaleNieWolny Jun 13, 2026
2ca4163
feat(helper): in-app intro/consent screen + launch helper at 'I don't…
WcaleNieWolny Jun 13, 2026
d32ec2f
feat(helper): success screen that sends the user back to the terminal
WcaleNieWolny Jun 13, 2026
9a5719f
fix(helper): center the consent screen buttons
WcaleNieWolny Jun 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
"test:upload": "bun test/test-upload-validation.mjs",
"test:fail-on-incompatible": "bun test/test-fail-on-incompatible.mjs",
"test:credentials": "bun test/test-credentials.mjs",
"test:asc-key-protocol": "bun test/test-asc-key-protocol.mjs",
"test:credentials-validation": "bun test/test-credentials-validation.mjs",
"test:android-service-account-validation": "bun test/test-android-service-account-validation.mjs",
"test:build-zip-filter": "bun test/test-build-zip-filter.mjs",
Expand Down
70 changes: 70 additions & 0 deletions cli/scripts/build-asc-key-helper.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#!/usr/bin/env bash
#
# Build the precompiled App Store Connect API-key helper (a native macOS Swift
# app) as a universal (arm64 + x86_64) release binary, ready to ship as a
# downloadable artifact for `@capgo/cli build credentials apple-key`.
#
# The CLI itself does NOT bundle this macOS-only binary in its npm tarball
# (that would bloat every Linux/Windows install). Instead it locates the binary
# at runtime via:
# 1. CAPGO_ASC_KEY_HELPER_PATH (dev / CI / this script's output)
# 2. ~/.capgo/asc-key-helper/capgo-asc-key-helper (cached download)
#
# Usage:
# scripts/build-asc-key-helper.sh <path-to-helper-swift-package> [out-dir]
#
# Example:
# scripts/build-asc-key-helper.sh ~/Developer/test-p8-extract dist-helper
# export CAPGO_ASC_KEY_HELPER_PATH="$PWD/dist-helper/capgo-asc-key-helper"
#
set -euo pipefail

if [[ "$(uname)" != "Darwin" ]]; then
echo "error: the ASC key helper can only be built on macOS." >&2
exit 1
fi

SRC_DIR="${1:-}"
OUT_DIR="${2:-dist-helper}"
PRODUCT_NAME="P8Extract" # SwiftPM executable product name
OUT_BINARY="capgo-asc-key-helper" # canonical name the CLI looks for

if [[ -z "$SRC_DIR" || ! -f "$SRC_DIR/Package.swift" ]]; then
echo "error: pass the helper Swift package dir (the folder with Package.swift)." >&2
echo "usage: $0 <path-to-helper-swift-package> [out-dir]" >&2
exit 1
fi

echo "› Building universal release binary from $SRC_DIR ..."
swift build \
--package-path "$SRC_DIR" \
-c release \
--arch arm64 \
--arch x86_64

BUILT="$SRC_DIR/.build/apple/Products/Release/$PRODUCT_NAME"
if [[ ! -f "$BUILT" ]]; then
# Fall back to the single-arch path if a universal build wasn't produced.
BUILT="$(find "$SRC_DIR/.build" -maxdepth 3 -name "$PRODUCT_NAME" -type f -perm -111 | head -1)"
fi
if [[ -z "${BUILT:-}" || ! -f "$BUILT" ]]; then
echo "error: could not find built product '$PRODUCT_NAME' under $SRC_DIR/.build" >&2
exit 1
fi
Comment on lines +50 to +58

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't copy an arbitrary .build executable.

If the expected release binary is missing, find ... | head -1 can pick up a stale debug or single-arch artifact from a previous build. That makes the published helper nondeterministic.

♻️ Proposed fix
 BUILT="$SRC_DIR/.build/apple/Products/Release/$PRODUCT_NAME"
 if [[ ! -f "$BUILT" ]]; then
-  # Fall back to the single-arch path if a universal build wasn't produced.
-  BUILT="$(find "$SRC_DIR/.build" -maxdepth 3 -name "$PRODUCT_NAME" -type f -perm -111 | head -1)"
-fi
-if [[ -z "${BUILT:-}" || ! -f "$BUILT" ]]; then
-  echo "error: could not find built product '$PRODUCT_NAME' under $SRC_DIR/.build" >&2
+  echo "error: could not find release product '$PRODUCT_NAME' at '$BUILT'" >&2
   exit 1
 fi
+ARCHS="$(lipo -archs "$BUILT")"
+if [[ "$ARCHS" != *arm64* || "$ARCHS" != *x86_64* ]]; then
+  echo "error: expected a universal binary, got: $ARCHS" >&2
+  exit 1
+fi
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
BUILT="$SRC_DIR/.build/apple/Products/Release/$PRODUCT_NAME"
if [[ ! -f "$BUILT" ]]; then
# Fall back to the single-arch path if a universal build wasn't produced.
BUILT="$(find "$SRC_DIR/.build" -maxdepth 3 -name "$PRODUCT_NAME" -type f -perm -111 | head -1)"
fi
if [[ -z "${BUILT:-}" || ! -f "$BUILT" ]]; then
echo "error: could not find built product '$PRODUCT_NAME' under $SRC_DIR/.build" >&2
exit 1
fi
BUILT="$SRC_DIR/.build/apple/Products/Release/$PRODUCT_NAME"
if [[ ! -f "$BUILT" ]]; then
echo "error: could not find release product '$PRODUCT_NAME' at '$BUILT'" >&2
exit 1
fi
ARCHS="$(lipo -archs "$BUILT")"
if [[ "$ARCHS" != *arm64* || "$ARCHS" != *x86_64* ]]; then
echo "error: expected a universal binary, got: $ARCHS" >&2
exit 1
fi
🤖 Prompt for 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.

In `@cli/scripts/build-asc-key-helper.sh` around lines 50 - 58, The fallback find
logic in build-asc-key-helper.sh can pick up arbitrary/stale executables;
restrict the search for BUILT to release-artifacts only by changing the fallback
that sets BUILT to use find with a path filter for release builds (e.g. -path
"*/Release/*/$PRODUCT_NAME" or -path "*/Products/Release/*/$PRODUCT_NAME"), keep
the -type f and -perm -111 checks, and then pick the first matching file; ensure
the variable names BUILT, PRODUCT_NAME and SRC_DIR are used unchanged and that
the existing existence check for BUILT remains after the new find.


mkdir -p "$OUT_DIR"
cp "$BUILT" "$OUT_DIR/$OUT_BINARY"
chmod +x "$OUT_DIR/$OUT_BINARY"

echo "› Architectures:"
lipo -info "$OUT_DIR/$OUT_BINARY" || true
echo "› SHA-256:"
shasum -a 256 "$OUT_DIR/$OUT_BINARY"

echo ""
echo "✅ Built $OUT_DIR/$OUT_BINARY"
echo " Try it: CAPGO_ASC_KEY_HELPER_PATH=\"$PWD/$OUT_DIR/$OUT_BINARY\" npx @capgo/cli build credentials apple-key"
echo ""
echo "ℹ️ For distribution, codesign + notarize this binary before publishing:"
echo " codesign --force --options runtime --sign \"Developer ID Application: …\" \"$OUT_DIR/$OUT_BINARY\""
echo " xcrun notarytool submit … && xcrun stapler staple …"
76 changes: 76 additions & 0 deletions cli/src/build/onboarding/asc-key/PROTOCOL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# App Store Connect key helper — stdout stats protocol

The `build credentials apple-key` command launches a native macOS helper (a
precompiled Swift app) that walks the user through creating an App Store Connect
**team** API key in an embedded browser, then captures the resulting credentials.
Comment on lines +3 to +5

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use the canonical npx @capgo/cli@latest ... form for CLI command examples.

Please render this command reference as npx @capgo/cli@latest build credentials apple-key to match repository docs conventions for customer-facing examples.

As per coding guidelines: “For Capgo CLI references in customer-facing command examples, always use npx @capgo/cli@latest ....”

🤖 Prompt for 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.

In `@cli/src/build/onboarding/asc-key/PROTOCOL.md` around lines 3 - 5, Update the
example CLI invocation in PROTOCOL.md to use the canonical repository form:
replace the current `build credentials apple-key` example with `npx
`@capgo/cli`@latest build credentials apple-key` so all customer-facing command
examples follow the “npx `@capgo/cli`@latest ...” convention.

Source: Coding guidelines


While it runs, the helper streams a **stats protocol** on **stdout** so the CLI
can forward usage statistics to PostHog and receive the final credentials. This
document is the contract between the Swift helper (`StatsProtocol.swift`) and the
CLI (`protocol.ts` / `helper.ts`).

## Wire format

Newline-delimited JSON ("NDJSON"). One JSON object per line on **stdout**. Every
line is tagged with `capgoAscKey` (the protocol version) so the reader can
ignore incidental stdout chatter. Human-readable diagnostics go to **stderr**
and are NOT part of this protocol.

```jsonc
{"capgoAscKey":1,"kind":"event","ts":12,"runId":"<uuid>","name":"step_changed","props":{ }}
{"capgoAscKey":1,"kind":"result","ts":900,"runId":"<uuid>","ok":true,"keyId":"…","issuerId":"…","privateKey":"…"}
{"capgoAscKey":1,"kind":"result","ts":900,"runId":"<uuid>","ok":false,"errorCode":"USER_CANCELLED","message":"…"}
```

| Field | Lines | Meaning |
| ------------- | ------------ | ---------------------------------------------------- |
| `capgoAscKey` | all | Protocol version (currently `1`). |
| `kind` | all | `"event"` or `"result"`. |
| `ts` | all | Milliseconds since the helper started. |
| `runId` | all | UUID correlating every line of one run. |
| `name` | event | snake_case event name. |
| `props` | event | Non-sensitive properties (never the private key). |
| `ok` | result | `true` = credentials present; `false` = error. |
| `keyId`/`issuerId`/`privateKey` | result (ok) | The captured credentials. |
| `errorCode`/`message` | result (!ok)| Failure reason. |

### Rules

- **`event` lines are forwarded to PostHog** (channel `app-store-connect-key`).
- The **terminal `result` line** carries the credentials on success. The
`privateKey` appears **only** here and is **never** forwarded to analytics.
As defence-in-depth, the CLI also strips any prop key matching
`private_key|secret|p8|pem|password|token` before sending tags.
- The reader tolerates non-protocol stdout lines (it skips anything without a
matching `capgoAscKey`), partial lines split across chunks, and a final
newline-less line.

## Events

| `name` | `props` | Emitted when |
| --------------------- | ---------------------------------------------------- | ----------------------------------------- |
| `helper_started` | `protocol_version`, `os_version` | The helper window appears. |
| `signed_in` | `team_count` | First authenticated session read. |
| `team_confirmed` | `is_switch`, `team_count` | User confirms a team in the dialog. |
| `api_access_checked` | `enabled`, `role_ok` | Team API-access capability is determined. |
| `api_access_denied` | `reason` (`not_enabled` \| `insufficient_role`) | Team can't create a key. |
| `step_changed` | `from`, `to`, `elapsed_ms_on_prev` | The guided step advances (the funnel). |
| `validation_started` | — | The new key is validated against Apple. |
| `validation_succeeded`| `duration_ms` | Validation passed. |
| `validation_failed` | `duration_ms` | Validation failed. |
| `helper_finished` | `ok`, `outcome` (`created`\|`cancelled`), `total_ms` | Just before the helper exits. |

New events can be added without bumping the protocol version — the CLI forwards
any `event` line generically (humanized name + `prop_*` tags). Bump
`ASC_PROTOCOL_VERSION` only for breaking envelope changes.

## Distribution of the precompiled helper

The macOS-only binary is **not** bundled in the npm tarball. The CLI locates it
at runtime:

1. `CAPGO_ASC_KEY_HELPER_PATH` — explicit override (dev / CI).
2. `~/.capgo/asc-key-helper/capgo-asc-key-helper` — cached download.

Build a universal binary from the helper Swift package with
`scripts/build-asc-key-helper.sh <helper-src-dir>`.
115 changes: 115 additions & 0 deletions cli/src/build/onboarding/asc-key/command.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { Buffer } from 'node:buffer'
import { homedir } from 'node:os'
import { join } from 'node:path'
import { exit, platform, stdout } from 'node:process'
import { intro, log, outro } from '@clack/prompts'
import { flushAnalytics, trackEvent } from '../../../analytics/track'
import { checkAlerts } from '../../../api/update'
import { updateSavedCredentials } from '../../credentials'
import { isMacOS, NotMacOSError, resolveHelperBinary, runAscKeyHelper } from './helper'
import { ASC_KEY_CHANNEL } from './protocol'
Comment on lines +1 to +11

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Use shared formatError(...) for the unexpected-error path.

Line 121 currently formats errors manually; this bypasses the CLI’s shared user-visible error formatter.

As per coding guidelines, “For user-visible error messages, format errors with formatError(...) instead of dumping raw exceptions when possible.”

♻️ Proposed change
 import { appendInternalLog, getInternalLogPath, startInternalLog } from '../../../support/internal-log'
+import { formatError } from '../../../utils'
 import { isMacOS, NotMacOSError, resolveHelperBinary, runAscKeyHelper } from './helper'
 import { ASC_KEY_CHANNEL } from './protocol'
@@
   catch (error) {
     if (error instanceof NotMacOSError)
       log.error(error.message)
     else
-      log.error(`Unexpected error: ${error instanceof Error ? error.message : String(error)}`)
+      log.error(`Unexpected error: ${formatError(error)}`)
     await flushAnalytics()
     exit(1)
   }

Also applies to: 117-122

🤖 Prompt for 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.

In `@cli/src/build/onboarding/asc-key/command.ts` around lines 1 - 11, Replace the
manual error-stringing in the unexpected-error catch branch with the CLI’s
shared formatter: import the shared formatError function from the CLI’s
error-formatting module and call formatError(err) (or formatError({ error: err
})) when logging or showing the error to the user instead of dumping raw
exception text; update the catch branch that currently builds the raw message
(the unexpected-error path in this file that runs alongside runAscKeyHelper /
ASC_KEY_CHANNEL) to use formatError and ensure the new import is added at the
top.

Source: Coding guidelines


export interface CreateAppleKeyOptions {
apikey?: string
/** When set, the captured key is saved into this app's iOS build credentials. */
appId?: string
/** Save into the per-project .capgo-credentials.json instead of the global file. */
local?: boolean
/** Print the captured Key ID / Issuer ID / .p8 path as JSON on stdout. */
json?: boolean
}

/**
* Guided creation of an App Store Connect **team** API key. Launches the native
* macOS helper (a precompiled Swift app that walks the user through Apple's web
* UI in an embedded browser), streams its stats protocol to PostHog, and
* captures the resulting key — issuer id, key id and the one-time .p8 — without
* the user ever copy-pasting a credential.
*/
export async function createAppleKeyCommand(options: CreateAppleKeyOptions = {}): Promise<void> {
await checkAlerts()
intro('App Store Connect API Key 🔑')

if (!isMacOS()) {
log.error('This guided flow needs the macOS helper app and only runs on macOS.')
log.info('On other platforms, create the key manually at https://appstoreconnect.apple.com/access/integrations/api '
+ 'then save it with `npx @capgo/cli build credentials save --platform ios --apple-key <AuthKey.p8> --apple-key-id <id> --apple-issuer-id <id>`.')
void trackEvent({ channel: ASC_KEY_CHANNEL, event: 'ASC Key: Unsupported Platform', icon: '🔑', apikey: options.apikey, tags: { os_platform: platform } })
await flushAnalytics()
exit(1)
}

if (!resolveHelperBinary()) {
log.error('Could not find the App Store Connect key helper binary.')
log.info('Set CAPGO_ASC_KEY_HELPER_PATH to a compiled helper, or upgrade to a CLI release that bundles it.')
void trackEvent({ channel: ASC_KEY_CHANNEL, event: 'ASC Key: Helper Missing', icon: '🔑', apikey: options.apikey })
Comment on lines +44 to +46

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Align helper-missing remediation text with the actual discovery model.

Line 45 tells users to “upgrade to a CLI release that bundles it,” but this feature’s rollout states the helper is discovered via env/cache/dev build and not bundled in npm tarballs. That guidance can send users to a dead end.

💡 Suggested wording update
-    log.info('Set CAPGO_ASC_KEY_HELPER_PATH to a compiled helper, or upgrade to a CLI release that bundles it.')
+    log.info('Set CAPGO_ASC_KEY_HELPER_PATH to a compiled helper, ensure the ~/.capgo/asc-key-helper cache is populated, or run from a local dev checkout with the helper built.')
🤖 Prompt for 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.

In `@cli/src/build/onboarding/asc-key/command.ts` around lines 44 - 46, Update the
helper-missing remediation text so it reflects the actual discovery model
instead of implying the binary is bundled in CLI releases: change the log.info
call that currently tells users to "upgrade to a CLI release that bundles it" to
instead instruct users to set CAPGO_ASC_KEY_HELPER_PATH to a compiled helper or
ensure the helper is available via their environment/cache/dev build process;
keep the log.error message and the trackEvent call (ASC_KEY_CHANNEL,
trackEvent(..., apikey: options.apikey)) unchanged.

await flushAnalytics()
exit(1)
}

log.step('Opening the guided helper… complete the steps in the window that appears.')

try {
const outcome = await runAscKeyHelper({
apikey: options.apikey,
onEvent: (event) => {
// Surface a few high-signal milestones in the terminal as they happen.
if (event.name === 'signed_in')
log.info('Signed in to App Store Connect.')
else if (event.name === 'api_access_denied')
log.warn(`API access unavailable for this team (${String(event.props.reason ?? 'unknown')}).`)
else if (event.name === 'validation_started')
log.step('Validating the new key with Apple…')
},
})

if (!outcome.ok) {
if (outcome.errorCode === 'USER_CANCELLED')
log.warn('Cancelled — no key was created.')
else
log.error(`Key creation failed (${outcome.errorCode}): ${outcome.message}`)
await flushAnalytics()
exit(1)
}

const { credentials, eventCount } = outcome
const p8Path = join(homedir(), '.appstoreconnect', 'private_keys', `AuthKey_${credentials.keyId}.p8`)

log.success('App Store Connect API key created and validated.')
log.info(`Key ID: ${credentials.keyId}`)
log.info(`Issuer ID: ${credentials.issuerId}`)
log.info(`Saved .p8: ${p8Path}`)

let savedToAppId: string | undefined
if (options.appId) {
const appleKeyContent = Buffer.from(credentials.privateKey, 'utf-8').toString('base64')
await updateSavedCredentials(options.appId, 'ios', {
APPLE_KEY_ID: credentials.keyId,
APPLE_ISSUER_ID: credentials.issuerId,
APPLE_KEY_CONTENT: appleKeyContent,
}, options.local)
savedToAppId = options.appId
log.success(`Saved to ${options.local ? 'local' : 'global'} build credentials for ${options.appId}.`)
}
else {
log.info('Save it into your build credentials with:')
log.info(` npx @capgo/cli build credentials save --platform ios --apple-key "${p8Path}" --apple-key-id "${credentials.keyId}" --apple-issuer-id "${credentials.issuerId}" --appId <your-app-id>`)
}

if (options.json) {
// Deliberately excludes the private key — it's on disk at p8Path.
stdout.write(`${JSON.stringify({ keyId: credentials.keyId, issuerId: credentials.issuerId, p8Path, savedToAppId, eventCount })}\n`)
Comment on lines +109 to +111

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep --json output machine-readable

When --json is used after a successful helper run, this JSON line is emitted only after the command has already written the intro/progress/success/info messages above and it is followed by the outro, so consumers parsing stdout as JSON receive a mixed human log stream rather than the documented JSON result. Please suppress or redirect the interactive output for --json, or emit the machine-readable payload on a clean stream.

Useful? React with 👍 / 👎.

}

await flushAnalytics()
outro('Done 🎉')
}
catch (error) {
if (error instanceof NotMacOSError)
log.error(error.message)
else
log.error(`Unexpected error: ${error instanceof Error ? error.message : String(error)}`)
await flushAnalytics()
exit(1)
}
}
Loading
Loading