feat(cli): generate GitHub Actions workflow + auto-push CAPGO_TOKEN#2315
Conversation
When `capgo build init` detects a GitHub remote after a successful first
build, the wizard now offers a 3-option choice instead of the existing
"upload secrets? yes/no":
• Yes — set the secrets AND create a workflow file
• Yes — set ONLY the secrets
• No
The "No" branch then offers a .env export fallback ("Do you want to
export the credentials as a .env so that you can setup CI/CD later?"),
reusing the renderer from `build credentials manage`.
What's new in v1
================
1. CAPGO_TOKEN auto-push (cli/src/build/onboarding/ci-secrets.ts)
`createCiSecretEntries` now takes an optional API key arg. When
provided, the bundle pushed to GitHub/GitLab includes CAPGO_TOKEN
alongside build credentials, so the generated workflow can
authenticate without the user manually running
`gh secret set CAPGO_TOKEN` afterward. Both onboarding wizards pass
`apikey ?? findSavedKey(...)` at credentials-save time.
2. Workflow generator (cli/src/build/onboarding/workflow-generator.ts)
Pure function that produces a `.github/workflows/capgo-build.yml`
with `workflow_dispatch` trigger, branched on the four package
managers (bun / npm / pnpm / yarn). Per maintainer convention, the
bun branch includes BOTH `oven-sh/setup-bun@v2` AND
`actions/setup-node@v4` — bun's Node compat isn't perfect and many
build pipelines still need Node on PATH. Backed by 12 unit tests
in cli/test/test-workflow-generator.mjs.
3. Workflow writer (cli/src/build/onboarding/workflow-writer.ts)
Thin file-I/O wrapper. Returns `kind: 'exists'` with both contents
when the target file is already present, so the wizard can show a
line-count summary and ask for explicit overwrite confirmation
before clobbering.
4. .env export reuse (cli/src/build/onboarding/env-export.ts)
Reuses `renderEnvFile` from `build credentials manage` (refactored
to take `({ appId, local, platform, creds })` — one minimal
signature change, same comment header / .gitignore reminder /
provisioning-map base64 fallback). Writes to mode 0600, refuses to
silently overwrite, surfaces the same overwrite-confirm prompt.
5. Build script picker (both wizards)
When the user picks "secrets + workflow", the wizard prompts for
which package.json script builds the web assets BEFORE running
`capgo build request`. Always asks — never auto-picks blindly. Lists
all `scripts{}`, surfaces a "recommended" hint sourced from
`findBuildCommandForProjectType()` when the matching script exists,
plus escape hatches for "Type a custom command…" and "Skip build
step (my app is raw HTML)".
Routing
=======
GitHub-only for v1 — GitLab keeps the existing 2-option `ask-ci-secrets`
flow. In the multi-target picker, picking GitHub routes to the new
3-option prompt; picking GitLab routes to the legacy 2-option prompt.
`uploading-ci-secrets` branches on `setupMode`:
• `with-workflow` → loads `getPackageScripts()` + project-type
recommendation → `pick-build-script` → `writing-workflow-file`
(which checks for existing file and may route to
`confirm-workflow-overwrite`)
• `secrets-only` / `undecided` (GitLab) → `build-complete`
The `ask-export-env` "no" path on the declined branch is reachable from
the new 3-option prompt; `ci-secrets-target-select` "skip" still goes
straight to `build-complete` (no second-chance prompt — keeping that
exit minimal).
What this v1 deliberately doesn't do
=====================================
- GitLab `.gitlab-ci.yml` generation (structurally different, follow-up)
- Push / pull_request triggers (only `workflow_dispatch` — manual until
the user trusts it)
- Monorepo subdirectory detection (`working-directory`)
- Modifying / merging into existing non-Capgo workflows
- webDir verification after the build step
Build / lint / typecheck / test all green via `bun run cli:check`.
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds GitHub Actions workflow generation, ChangesGitHub Actions Workflow & Environment Export Onboarding
Estimated code review effort 🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Suggested reviewers
🚥 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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 8
🤖 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/onboarding/android/ui/app.tsx`:
- Around line 120-159: Extract normalizePackageManager and
buildScriptPickerOptions into a shared module (e.g., workflow-ui-helpers.ts),
export them, and replace the local implementations in this file with imports
from that module so both Android and iOS use the same helpers; also replace the
Object.prototype.hasOwnProperty.call(scripts, recommended) usage in
buildScriptPickerOptions with Object.hasOwn(scripts, recommended) before
importing/using the shared helper to ensure modern shorthand is used everywhere.
- Around line 30-34: Imports for the utility functions are duplicated and out of
order; remove the separate import listing findBuildCommandForProjectType,
findProjectType, getPackageScripts, and getPMAndCommand and instead add those
symbols to the existing utils import so all utilities are consolidated in one
import, then reorder imports to satisfy ESLint grouping (external modules,
project utils, relative files) and eliminate the duplicate import. Ensure you
keep defaultExportPath, exportCredentialsToEnv, writeWorkflowFile,
WORKFLOW_PATH, and BuildScriptChoice/PackageManager imports unchanged while
consolidating the four utility functions into the single utilities import.
- Around line 1036-1040: The current block assigns capgoKey and then
conditionally calls findSavedKey(true) in a try/catch on the same line; split
the statements so each statement is on its own line and avoid inline try/catch.
Replace the try/catch + findSavedKey(true) with a silent lookup by calling
findSavedKeySilent() (to match the iOS pattern) when apikey is falsy, or if you
must keep findSavedKey use it on its own line inside a try/catch block; update
references to capgoKey, apikey, findSavedKey(true) accordingly so the code
conforms to max-statements-per-line and uses findSavedKeySilent for consistency.
In `@cli/src/build/onboarding/ci-secrets.ts`:
- Around line 125-135: The code currently treats whitespace-only apiKey as
valid; update the check around the CAPGO_TOKEN creation to use a trimmed value
(e.g., const trimmed = apiKey?.trim()) and only call entries.push for
CAPGO_TOKEN when trimmed is non-empty; when pushing, use the trimmed value for
the value field and keep masked: true to avoid storing a whitespace-only secret
(refer to apiKey, 'CAPGO_TOKEN', and the entries.push call).
In `@cli/src/build/onboarding/env-export.ts`:
- Line 15: Extract renderEnvFile and its helper escapeDotenvValue into a new
shared module (cli/src/build/env-render.ts) that exports both functions, then
update both cli/src/build/onboarding/env-export.ts and
cli/src/build/credentials-manage.ts to import renderEnvFile (and
escapeDotenvValue if needed) from that new module instead of
credentials-manage.ts; remove the original renderEnvFile implementation from
credentials-manage.ts so the only source of truth is the new env-render module.
In `@cli/src/build/onboarding/ui/app.tsx`:
- Line 211: Remove the unused local state pendingCustomCommand and its setter
setPendingCustomCommand: locate the useState declaration for
pendingCustomCommand and any references to setPendingCustomCommand (they are
redundant because the custom command is already stored in
buildScriptChoice.command) and delete those lines; ensure no other code reads
pendingCustomCommand and that buildScriptChoice.command continues to be used for
the custom command flow.
- Line 126: Replace the old hasOwnProperty call with the modern Object.hasOwn
usage: anywhere you currently use Object.prototype.hasOwnProperty.call(scripts,
recommended) (e.g., the conditional checking recommended against scripts and the
other occurrence around the symbol referenced at the lower occurrence) change it
to Object.hasOwn(scripts, recommended); update both occurrences so the checks
use Object.hasOwn and refer to the same variables (scripts and recommended).
- Around line 31-35: Reorder and consolidate the imports to satisfy the ESLint
rules: remove the duplicate import of findBuildCommandForProjectType,
findProjectType, getPackageScripts and merge those symbols into the existing
getPMAndCommand import (the earlier import on line ~20), then re-sort imports so
grouped builtin/third-party/local imports follow the configured
perfectionist/sort-imports order and no duplicate import specifiers remain;
ensure defaultExportPath, exportCredentialsToEnv, writeWorkflowFile,
WORKFLOW_PATH, BuildScriptChoice, PackageManager, and BuildCredentials remain
imported exactly once with consistent ordering.
🪄 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: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 57a45754-e9f1-46cc-be00-690422299c62
📒 Files selected for processing (11)
cli/src/build/credentials-manage.tscli/src/build/onboarding/android/types.tscli/src/build/onboarding/android/ui/app.tsxcli/src/build/onboarding/ci-secrets.tscli/src/build/onboarding/env-export.tscli/src/build/onboarding/types.tscli/src/build/onboarding/ui/app.tsxcli/src/build/onboarding/workflow-generator.tscli/src/build/onboarding/workflow-writer.tscli/test/test-ci-secrets.mjscli/test/test-workflow-generator.mjs
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a2ca346e86
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (opts.buildScript.type !== 'skip') { | ||
| lines.push('') | ||
| lines.push(' - name: Build web assets') | ||
| lines.push(` run: ${buildCommand(opts.packageManager, opts.buildScript)}`) |
There was a problem hiding this comment.
Quote custom build commands in generated workflow
The build step writes run: as an unquoted plain scalar, so a user-provided custom command that contains YAML-significant sequences (for example : or #, such as echo "a: b") produces invalid workflow YAML and GitHub will refuse to run it. This can be triggered directly from the new pick-build-script-custom path, so the generator should emit the command in a safe form (e.g., block scalar or properly quoted string) instead of interpolating it raw.
Useful? React with 👍 / 👎.
Merging this PR will improve performance by 82.25%
|
| Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|
| ⚡ | /updates manifest response with metadata |
204.5 µs | 112.2 µs | +82.25% |
Tip
Curious why this is faster? Comment @codspeedbot explain why this is faster on this PR, or directly use the CodSpeed MCP with your agent.
Comparing feat/cli-github-actions-workflow (a35ba39) with main (2d18719)
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. ↩
BataraSurya
left a comment
There was a problem hiding this comment.
I think the generated Yarn template currently straddles two Yarn generations in a way that can make the workflow fail for common Yarn Classic projects.
The install/build pieces are Classic-compatible (yarn install --frozen-lockfile and yarn <script>), and the test even notes "yarn classic invokes scripts without run". But the Capgo steps use yarn dlx, which is a Yarn Berry command; GitHub-hosted runners commonly provide Yarn 1.x unless the project enables Corepack / ships a Berry release. For a repo detected as Yarn purely from yarn.lock, the generated workflow can therefore reach the Capgo build step and fail with Command "dlx" not found.
Could this either use npx @capgo/cli@latest ... for the Yarn template, or explicitly enable/pin Corepack/Yarn Berry before emitting yarn dlx? A regression test that models a Yarn Classic workflow would catch the mismatch.
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 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/onboarding/android/ui/app.tsx`:
- Around line 1147-1167: The code advances to setStep('confirm-secrets-push')
even when getCiSecretRepoLabelAsync fails (repoLabel is null), allowing a GitHub
upload without a resolved owner/repo; change the flow so that after awaiting
getCiSecretRepoLabelAsync(ciSecretTarget) you treat a null/failed repoLabel as a
hard stop: set an error/needs-retry state (e.g., update a ciSecretRepoError or
keep cancelled handling) and return early instead of calling
setCiSecretRepoLabel or continuing to listExistingCiSecretKeysAsync and
setStep('confirm-secrets-push'); apply the same guard/update to the similar
block referenced at lines 2658-2717 so unresolved owner/repo always forces
retry/cancel and never reaches confirm-secrets-push.
In `@cli/src/build/onboarding/ui/app.tsx`:
- Around line 1174-1197: The code currently allows proceeding to
setStep('confirm-secrets-push') even when getCiSecretRepoLabelAsync failed and
repoLabel is null; change the GitHub path to fail-closed by verifying repoLabel
is non-null before enabling the upload flow: after awaiting
getCiSecretRepoLabelAsync (and before any setStep('confirm-secrets-push') for
provider === 'github'), detect a null/undefined repoLabel and instead set an
error/blocked UI state (e.g. setCiSecretCheckPhase to an error message and abort
or setStep to a safe fallback) and do not call setStep('confirm-secrets-push');
ensure the same null-check behavior is applied to the duplicate block around
lines 2918-2973 so uploads cannot proceed without a resolved owner/repo.
In `@cli/src/build/onboarding/ui/components.tsx`:
- Around line 5-6: The import order in components.tsx is reversed for the lint
rule; move the type-only import for DiffLine (import type { DiffLine } from
'../diff-utils.js') above the value import of React (import React, { useEffect,
useState } from 'react') so the type import precedes the value import,
satisfying perfectionist/sort-imports; update the two import lines accordingly
and run the linter to confirm no other grouping/order issues.
In `@cli/test/test-diff-utils.mjs`:
- Around line 24-27: Remove the unused helper function "assert" from the file by
deleting the function declaration function assert(condition, message) { if
(!condition) throw new Error(message || 'Assertion failed') }, ensuring no other
code references "assert" before removal; if references exist, replace them with
standard test assertions or throw new Error directly.
- Around line 103-105: The test file is using the global process
(process.exit(1)); import the Node process module and use that instead: add
"import process from 'node:process';" at the top of cli/test/test-diff-utils.mjs
and leave the call to process.exit(1) as-is (or alternatively import { exit }
from 'node:process' and call exit(1)); ensure any other uses of process in this
file refer to the imported symbol (process or exit) to satisfy
node/prefer-global/process.
🪄 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: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: a5a80bc4-1821-4e27-bfdd-83c00226daf9
⛔ Files ignored due to path filters (1)
bun.lockis excluded by!**/*.lock
📒 Files selected for processing (11)
cli/src/build/onboarding/analytics.tscli/src/build/onboarding/android/types.tscli/src/build/onboarding/android/ui/app.tsxcli/src/build/onboarding/ci-secrets.tscli/src/build/onboarding/command.tscli/src/build/onboarding/diff-utils.tscli/src/build/onboarding/types.tscli/src/build/onboarding/ui/app.tsxcli/src/build/onboarding/ui/components.tsxcli/test/test-ci-secrets.mjscli/test/test-diff-utils.mjs
…ns-workflow # Conflicts: # cli/src/build/onboarding/android/ui/app.tsx # cli/src/build/onboarding/ui/app.tsx
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
cli/src/build/onboarding/android/ui/app.tsx (1)
3147-3181: 🧹 Nitpick | 🔵 Trivial | 💤 Low valueUnreachable step
confirm-workflow-overwrite— consider removing dead code.This step's UI is rendered but no code path transitions to it. Searching for
setStep('confirm-workflow-overwrite')returns zero matches in this file. The workflow preview flow goes directly frompreview-workflow-fileto eitherview-workflow-difforwriting-workflow-file, bypassing this step entirely.This appears to be leftover code from an earlier design that was replaced by the diff-based preview approach. Along with the unused
workflowExistingContentstate, this dead code adds maintenance burden.♻️ Suggested cleanup
Remove the unreachable step UI and related unused state:
- const [workflowExistingContent, setWorkflowExistingContent] = useState<string | null>(null) + const [workflowExistingContent] = useState<string | null>(null)Or remove entirely if the
confirm-workflow-overwritestep UI is also removed, sinceworkflowExistingContentis only read in that unreachable block.🤖 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/android/ui/app.tsx` around lines 3147 - 3181, The UI block guarded by step === 'confirm-workflow-overwrite' is unreachable and should be removed along with any related unused state; delete the JSX block that renders the confirm-workflow-overwrite step (the Box containing WORKFLOW_PATH, the lines count using workflowExistingContent and workflowProposedContent, and the Select that calls setStep) and also remove the associated state variables (e.g., workflowExistingContent) and any imports or constants only used by that block to avoid dead code; ensure no remaining references to 'confirm-workflow-overwrite' or those state variables remain in the file.
🤖 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.
Outside diff comments:
In `@cli/src/build/onboarding/android/ui/app.tsx`:
- Around line 3147-3181: The UI block guarded by step ===
'confirm-workflow-overwrite' is unreachable and should be removed along with any
related unused state; delete the JSX block that renders the
confirm-workflow-overwrite step (the Box containing WORKFLOW_PATH, the lines
count using workflowExistingContent and workflowProposedContent, and the Select
that calls setStep) and also remove the associated state variables (e.g.,
workflowExistingContent) and any imports or constants only used by that block to
avoid dead code; ensure no remaining references to 'confirm-workflow-overwrite'
or those state variables remain in the file.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 341678c5-76ea-4970-8f02-e0b1a98ba446
📒 Files selected for processing (4)
cli/src/build/onboarding/android/types.tscli/src/build/onboarding/android/ui/app.tsxcli/src/build/onboarding/types.tscli/src/build/onboarding/ui/app.tsx
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
|



Summary
When
capgo build initdetects a GitHub remote after a successful first build, the wizard now offers a 3-option choice replacing the existing yes/no secrets upload:The "No" branch then offers a
.envexport fallback so the user can wire up CI later viagh secret set -f, reusing the renderer frombuild credentials manage.cc @riderx
What's new in v1
1. CAPGO_TOKEN auto-push
createCiSecretEntriesnow takes an optional API key. When provided, the bundle pushed to GitHub/GitLab includesCAPGO_TOKENalongside build credentials — the generated workflow can authenticate without the user manually runninggh secret set CAPGO_TOKENafterward.2. Workflow generator (
cli/src/build/onboarding/workflow-generator.ts)Pure function that produces
.github/workflows/capgo-build.yml:workflow_dispatchtrigger withplatform+build_modeinputs (defaults to the platform being onboarded)capgo build requeststep, branched on package managerenv:block enumerates the exact secret names that were pushed — no drift between what we set and what we reference$GITHUB_STEP_SUMMARYPer maintainer convention, the bun template includes BOTH
oven-sh/setup-bun@v2ANDactions/setup-node@v4— bun's Node compat isn't perfect and many build pipelines still neednodeon PATH.12 unit tests in
cli/test/test-workflow-generator.mjscovering all four PMs, custom commands, skip-build mode, monorepo-friendly secrets enumeration, and theworkflow_dispatch-only constraint.3. Workflow writer (
cli/src/build/onboarding/workflow-writer.ts)Thin file-I/O wrapper. Returns
kind: 'exists'with both contents when the target file is already present, so the wizard can show a line-count comparison and ask for explicit overwrite confirmation before clobbering.4. .env export reuse (
cli/src/build/onboarding/env-export.ts)Reuses
renderEnvFilefrombuild credentials manage(refactored to take({ appId, local, platform, creds })— one minimal signature change, same comment header /.gitignorereminder / provisioning-map base64 fallback). Writes mode 0600, refuses to silently overwrite.5. Build script picker (both wizards)
When the user picks "secrets + workflow", the wizard always prompts for which
package.jsonscript builds the web assets BEFORE invokingcapgo build request— never auto-picks blindly. Lists every script inscripts{}, surfaces a "recommended" hint sourced fromfindBuildCommandForProjectType()when the matching script exists, plus escape hatches:Type a custom command…(text input, e.g.make web,bash scripts/build.sh)Skip build step (my app is raw HTML)for plain HTML/JS Capacitor appsRouting
ask-ci-secretsflow. In the multi-target picker, picking GitHub routes to the new 3-option prompt; GitLab routes to the legacy prompt.uploading-ci-secretsbranches onsetupMode:with-workflow→ loads scripts + project-type recommendation →pick-build-script→writing-workflow-file(with overwrite confirm if file exists)secrets-only/undecided(GitLab) →build-completeci-secrets-target-select"skip" still goes straight tobuild-complete(no second-chance prompt).What v1 deliberately doesn't do
.gitlab-ci.ymlgeneration (structurally different, follow-up)workflow_dispatch— manual until the user trusts it)working-directory)Test plan
bun run cli:check(lint + typecheck + build + test) green locallybun test/test-workflow-generator.mjs— 12 new tests passbun test/test-ci-secrets.mjs— 11 tests pass (8 existing + 3 new CAPGO_TOKEN cases)capgo build init --platform iosin a repo with a GitHub remote — confirm new 3-option prompt fires; pick "secrets + workflow"; verify a.github/workflows/capgo-build.ymllands with the right PM template--platform android— confirm the workflow defaults toandroid.env.capgo.<appId>.<platform>written at cwd with 0600capgo-build.yml→ confirm overwrite prompt shows line countsask-ci-secrets2-option flow still fires (no GitLab workflow generation)bun.lockrepo — confirm bun template includes both setup-bun + setup-nodeSummary by CodeRabbit
New Features
.envexport with overwrite/exists/empty handling.envrenderer (permission-tightening)Tests