Skip to content

Commit d8c7a1e

Browse files
feat: add build onboarding for automated iOS credential setup (#547)
* feat: add `build onboarding` command for automated iOS credential setup Interactive Ink-based CLI flow that automates iOS certificate and provisioning profile creation via the App Store Connect API. User provides ONE API key (.p8 file), and the CLI handles: - CSR generation and iOS Distribution certificate creation - Bundle ID registration (or reuse) - App Store provisioning profile creation - Credential saving to ~/.capgo-credentials/credentials.json - Optional first build kick-off with live log streaming Key features: - macOS native file picker for .p8 selection - Auto-detect Key ID from .p8 filename - Progress persistence in ~/.capgo-credentials/onboarding/<appId>.json - Resume from interruption without losing progress - Smart cert/profile conflict resolution (revoke & recreate) - Ctrl+O to open App Store Connect in browser Also refactors requestBuildInternal to use a BuildLogger callback interface instead of direct @clack/prompts logging, enabling clean integration with the Ink UI without stdout interception hacks. * fix: address code review findings from PR #547 - Fix ESLint import order: ink before react in command.ts - Use explicit process import and @clack/prompts log instead of console.error for consistent CLI output - Only treat ENOENT as "no progress" in loadProgress, rethrow JSON corruption and permission errors - Clear onboarding progress when user skips credential overwrite to prevent stale resume state - Fix stale JSDoc: @param silent → @param logger in fetchWithRetry * fix: preserve silent build behavior by skipping log streaming When silent=true and no custom logger is provided, pass undefined to streamBuildLogs so the early return kicks in. This restores the pre-refactor behavior where silent SDK callers go directly to REST polling without WebSocket overhead. * docs: add build onboarding to native-builds skill documentation Documents the new `build onboarding` command, its architecture, conflict resolution behavior, and the BuildLogger callback interface. * fix(onboarding): incremental progress, backup credentials, early overwrite check - Save progress incrementally at each input step (p8Path, keyId, issuerId) so interrupted onboarding resumes at the right step - Show completed partial steps as checkmarks when resuming - Check for existing credentials at start (after platform select) instead of at the end — avoids creating orphaned certs/profiles - Backup existing credentials to credentials-DATE.copy.json before proceeding - Remove old overwrite prompt at save step - Fix: don't save keyId from filename extraction until user confirms - Fix: move async backup to useEffect to avoid Select onChange re-fire * fix(onboarding): exit instantly after completion screen * fix(onboarding): add TTY guard to fail fast in CI/pipes * fix(onboarding): sanitize appId in progress file path to prevent traversal * fix(onboarding): use capacitor config to resolve iOS platform directory Instead of hardcoding `ios/`, use `getPlatformDirFromCapacitorConfig` to respect custom `ios.path` settings in capacitor.config.ts. * chore: remove unused readdir import from progress.ts * fix(request): use block-style if, eslint-disable for console.log, fix hardcoded platform in error messages * fix(onboarding): show correct completion message based on build status Branch on buildUrl: if set, show "building in the cloud" + track link. If not (skipped, failed, no API key), show "credentials saved and ready". * fix: resolve all ESLint errors in onboarding and request modules - Fix import ordering (perfectionist/sort-imports, sort-named-imports) - Split multi-statement lines (style/max-statements-per-line) - Add explicit node:process and node:buffer imports - Use top-level type-only imports (import/consistent-type-specifier-style) * fix: address Copilot review findings - Replace process.exit() with Ink's exit() inside the Ink app to ensure proper terminal teardown - Fix verifyApiKey: include HTTP status in ascFetch error messages so 401/403 matching actually works - Fix P12 password JSDoc to match actual DEFAULT_P12_PASSWORD behavior - Update skill docs: existing credentials flow is backup+proceed or exit - Implement uploadProgress in default BuildLogger (10% interval logging) to restore upload feedback for non-onboarding build requests - Remove duplicate ❯ prompt wrappers around FilteredTextInput - Fix FilteredTextInput paste handling: strip forbidden chars from accumulated string, not just individual keystrokes * fix(request): restore upload spinner in default BuildLogger Replace the log.info() upload progress (printed separate lines at 10% intervals) with the original @clack/prompts spinner that shows a live animated percentage update on a single line.
1 parent 918f5ab commit d8c7a1e

File tree

15 files changed

+2471
-168
lines changed

15 files changed

+2471
-168
lines changed

build.mjs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,20 @@ const noopSupabaseNodeFetch = {
254254
},
255255
}
256256

257+
// Stub react-devtools-core — Ink optionally imports it for dev mode
258+
const stubReactDevtools = {
259+
name: 'stub-react-devtools',
260+
setup(build) {
261+
build.onResolve({ filter: /^react-devtools-core$/ }, args => ({
262+
path: args.path,
263+
namespace: 'stub-react-devtools',
264+
}))
265+
build.onLoad({ filter: /.*/, namespace: 'stub-react-devtools' }, () => ({
266+
contents: 'export default {}; export const connectToDevTools = () => {};',
267+
}))
268+
},
269+
}
270+
257271
// Fix for @capacitor/cli path assumptions in bundled builds
258272
// - __dirname gets baked in as the build machine path
259273
// - loadCLIConfig reads package.json from cliRootDir
@@ -309,6 +323,7 @@ const buildCLI = Bun.build({
309323
noopSupabaseRealtimeJs,
310324
stubPrompts,
311325
noopSupabaseAuthJs,
326+
stubReactDevtools,
312327
],
313328
})
314329

bun.lock

Lines changed: 155 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,11 @@
9191
"@supabase/supabase-js": "^2.79.0",
9292
"@tanstack/intent": "^0.0.23",
9393
"@types/adm-zip": "^0.5.7",
94+
"@types/jsonwebtoken": "^9.0.10",
9495
"@types/node": "^25.0.0",
96+
"@types/node-forge": "^1.3.14",
9597
"@types/prettyjson": "^0.0.33",
98+
"@types/react": "^18.3.28",
9699
"@types/tmp": "^0.2.6",
97100
"@vercel/ncc": "^0.38.4",
98101
"adm-zip": "^0.5.16",
@@ -111,5 +114,13 @@
111114
"typescript": "^5.9.3",
112115
"ws": "^8.18.3",
113116
"zod": "^4.3.6"
117+
},
118+
"dependencies": {
119+
"@inkjs/ui": "^2.0.0",
120+
"ink": "^5.2.1",
121+
"ink-spinner": "^5.0.0",
122+
"jsonwebtoken": "^9.0.3",
123+
"node-forge": "^1.3.3",
124+
"react": "^18.3.1"
114125
}
115126
}

skills/native-builds/SKILL.md

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,71 @@
11
---
22
name: native-builds
3-
description: Use when working with Capgo Cloud native iOS and Android build requests, credential storage, credential updates, and build output upload settings.
3+
description: Use when working with Capgo Cloud native iOS and Android build requests, onboarding, credential storage, credential updates, and build output upload settings.
44
---
55

66
# Capgo CLI Native Builds
77

88
Use this skill for Capgo Cloud native iOS and Android build workflows.
99

10+
## Onboarding (automated iOS setup)
11+
12+
### `build onboarding`
13+
14+
- Interactive command that automates iOS certificate and provisioning profile creation.
15+
- Reduces iOS setup from ~10 manual steps to 1 manual step (creating an API key) + 1 command.
16+
- Example: `npx @capgo/cli@latest build onboarding`
17+
- Notes:
18+
- Uses Ink (React for terminal) for the interactive UI — only command that uses Ink; all other commands use `@clack/prompts`.
19+
- Requires running inside a Capacitor project directory with an `ios/` folder.
20+
- The user creates ONE App Store Connect API key (.p8 file), then the CLI handles everything else.
21+
- On macOS, offers a native file picker dialog for .p8 selection.
22+
- Auto-detects Key ID from .p8 filename (e.g. `AuthKey_XXXX.p8`).
23+
- Progress persists in `~/.capgo-credentials/onboarding/<appId>.json` — safe to interrupt and resume.
24+
- Saves credentials to the same `~/.capgo-credentials/credentials.json` used by `build request`.
25+
- Optionally kicks off the first build at the end.
26+
27+
#### What it automates (iOS)
28+
29+
1. Verifies the API key with Apple
30+
2. Generates CSR + creates an `IOS_DISTRIBUTION` certificate via the App Store Connect API
31+
3. Registers or reuses the bundle ID
32+
4. Creates an `IOS_APP_STORE` provisioning profile
33+
5. Saves all credentials (certificate as .p12, profile, API key, team ID)
34+
6. Requests the first cloud build
35+
36+
#### Conflict resolution
37+
38+
- **Certificate limit reached**: lists existing certs, tags ones created by Capgo onboarding, lets the user pick one to revoke, then retries.
39+
- **Duplicate provisioning profiles**: detects profiles matching the `Capgo <appId> AppStore` naming pattern, deletes them, and retries.
40+
- **Existing credentials**: offers to backup existing credentials before proceeding, or exit onboarding.
41+
42+
#### Architecture
43+
44+
- `src/build/onboarding/command.ts` — entry point, launches Ink
45+
- `src/build/onboarding/apple-api.ts` — JWT auth + App Store Connect API (verify, create cert, create profile, revoke, delete)
46+
- `src/build/onboarding/csr.ts` — CSR generation + P12 creation via `node-forge`
47+
- `src/build/onboarding/progress.ts` — per-app progress persistence
48+
- `src/build/onboarding/file-picker.ts` — macOS native file picker via `osascript`
49+
- `src/build/onboarding/ui/app.tsx` — Ink app (state machine)
50+
- `src/build/onboarding/ui/components.tsx` — reusable UI components
51+
52+
#### BuildLogger callback interface
53+
54+
`requestBuildInternal` accepts an optional `BuildLogger` to receive log output via callbacks instead of writing directly to stdout. This enables clean integration with the Ink UI:
55+
56+
```typescript
57+
interface BuildLogger {
58+
info: (msg: string) => void
59+
error: (msg: string) => void
60+
warn: (msg: string) => void
61+
success: (msg: string) => void
62+
buildLog: (msg: string) => void
63+
uploadProgress: (percent: number) => void
64+
}
65+
```
66+
67+
---
68+
1069
## Core build request
1170

1271
### `build request [appId]`

0 commit comments

Comments
 (0)