Conversation
…e prompts and progress tracking - Added onboarding steps and UI components for a guided setup process. - Introduced runtime management for session state, logging, and prompts. - Created reusable prompt components for confirmation, text input, and selection. - Enhanced user experience with progress indicators and session management. - Updated documentation to reflect new onboarding features and functionality.
…apacitor initialization
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
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:
📝 WalkthroughWalkthroughReplaces the previous onboarding command with an Ink-based fullscreen Changes
Sequence DiagramsequenceDiagram
actor User
participant CLI as Init Command
participant Workspace as Workspace<br/>Detection
participant Runtime as Init Runtime
participant UI as Ink UI
participant Prompts as Prompt Components
User->>CLI: run `capgo build init`
CLI->>Workspace: locate Capacitor config & package.json
Workspace-->>CLI: return paths / readiness
CLI->>Runtime: ensureInitInkSession()
Runtime->>UI: render welcome screen
loop For each onboarding step
CLI->>Runtime: renderInitOnboardingFrame(step)
Runtime->>UI: update progress & logs
alt step needs user input
CLI->>Runtime: requestInitText/Confirm/Select
Runtime->>Prompts: show prompt component
Prompts->>User: collect input or CANCEL
Prompts-->>Runtime: resolve value or CANCEL
Runtime-->>CLI: return result
else automatic step
CLI->>CLI: execute step logic (install, upload, sync)
end
end
CLI->>Runtime: renderInitOnboardingComplete()
Runtime->>UI: show completion
CLI->>Runtime: stopInitInkSession()
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment Tip You can disable sequence diagrams in the walkthrough.Disable the |
There was a problem hiding this comment.
Actionable comments posted: 11
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/build/onboarding/file-picker.ts`:
- Around line 42-43: The picker can return any file; update
openPackageJsonPicker to validate the chosen path before returning: call
openMacFilePicker as before, then check the result (e.g., using
path.basename(result) === 'package.json' or case-insensitive compare) and if it
doesn't match return null (or re-run the picker flow) so only a true
package.json path is passed into the dependency-detection flow; reference
openPackageJsonPicker and openMacFilePicker when adding this guard.
In `@src/build/onboarding/ui/app.tsx`:
- Around line 97-110: The Ctrl+C handler currently calls exitOnboarding/exit
which only unmounts the Ink tree but does not cancel in-flight async step
effects (e.g., createCertificate, revokeCertificate, createProfile,
deleteProfile, requestBuildInternal); modify exitOnboarding to also cancel the
running step by invoking the step cancellation mechanism (for example, call
abort on a shared AbortController stored in a ref like currentStepAbortRef or
set a cancelledRef flag the step effect checks) before calling exit(), and
ensure the step effects (the functions referenced in the step effect) are wired
to respect that AbortSignal/cancelled flag so they stop work when Ctrl+C is
pressed.
In `@src/index.ts`:
- Around line 734-736: You removed the legacy "build onboarding" command when
adding .command('init'), which breaks resume paths that still call the old name;
reintroduce a backward-compatible alias by wiring the old command name to the
same handler (onboardingBuilderCommand) — either add a separate .command('build
onboarding').action(onboardingBuilderCommand) or use the CLI library's alias
feature alongside the existing .command('init') so both names invoke
onboardingBuilderCommand, and update the related docs in skills/usage/SKILL.md
to reflect the alias if needed.
In `@src/init/command.ts`:
- Around line 1982-1998: The uploadBundleInternal call can throw and currently
escapes the loop; wrap the upload call inside the existing loop in a try/catch
around uploadBundleInternal(...) so thrown exceptions are handled the same as a
{ success: false } response: in the catch call s.stop('Upload failed ❌'), await
selectRecoveryOption(orgId, apikey, 'Bundle upload failed. What do you want to
do?', [{ value: 'retry', label: 'Retry bundle upload' }]) and continue the loop;
ensure you still pass the same arguments (globalChannelName, delta, newVersion,
etc.) to uploadBundleInternal and only let exceptions propagate after the
recovery options are exhausted.
- Around line 1351-1358: The code path that detects an existing updater
(getInstalledVersion('@capgo/capacitor-updater', dirname(path), path)) never
refreshes pkgVersion from the app bundle, so pkgVersion stays the initial
'1.0.0' and can overwrite the real bundle version later; fix by reading the
bundle version from path whenever an installedVersion is found (i.e. call
getBundleVersion(undefined, path) and assign to pkgVersion inside the branch
where installedVersion is truthy, or move the pkgVersion = getBundleVersion(...)
assignment out of the else so it runs in both cases), using the existing symbols
getInstalledVersion, pkgVersion, dirname(path), and getBundleVersion to locate
and update the code.
- Around line 474-477: The call to markSnag is passing the shrug emoji as the
fifth parameter (appId) instead of the sixth (icon), which records app-id=🤷 and
leaves the wrong icon; change the invocation of markSnag in this cancel branch
(the call in src/init/command.ts) to pass a placeholder for appId (e.g.,
undefined or null) as the fifth argument and pass '🤷' as the sixth argument so
the icon parameter receives the emoji (i.e., markSnag('onboarding-v2', orgId,
apikey, 'canceled', undefined, '🤷')).
- Around line 382-389: markStepDone currently persists only step_done,
pathToPackageJson and channelName; update the temp write so it also saves the
runtime state required for resumption — include platform, delta, and
currentVersion (or their runtime equivalents) when calling
writeFileSync(tmpObject!, JSON.stringify(...)) after readTmpObj(); ensure you
pull the correct values (use existing variables like platform, delta,
currentVersion) and mirror the same change in the other temp-write sites that
use readTmpObj()/writeFileSync(tmpObject!, ...) so resumed runs don't default
platform to 'ios' or lose delta/version state.
- Around line 1288-1303: The parse() calls in getUpdaterInstallBlocker (and
similarly in addUpdaterStep) assume a concrete semver but may receive range
specifiers from package.json; before calling parse() or lessThan(), first
normalize or validate the version: use semver.valid() or semver.coerce() (or
fallback to the resolved version from getAllPackagesDependencies) to obtain a
concrete version string, and if that fails return a clear blocker message
instructing the user to pin a concrete `@capacitor/core` version; update both
getUpdaterInstallBlocker and addUpdaterStep to perform this
validation/normalization around the coreVersion variable and only call
parse(coreVersion) when a concrete version is obtained.
In `@src/init/prompts.ts`:
- Around line 83-86: The stop function currently infers tone from the message
content which treats any non-❌ message as success; change stop(message?: string)
to stop(message?: string, tone?: 'success' | 'neutral' | 'error') so callers can
explicitly indicate tone, map tone to colors when calling pushInitLog (e.g.,
'success' -> 'green', 'neutral' -> 'yellow' or grey, 'error' -> 'red'), and
update all callers that represent failures or neutral states (e.g., those
passing "Missing build script" or "Cannot auto-inject code") to pass the
appropriate tone; keep the function name stop and use pushInitLog(message,
color) to perform logging.
In `@src/init/runtime.tsx`:
- Around line 95-99: The current ensureInitInkSession returns early on non-TTY
causing pending prompt promises and spinner-only logs to hang; update
ensureInitInkSession to detect !process.stdin.isTTY || !process.stdout.isTTY and
either throw a clear error to fail fast or initialize a non-interactive
fallback: set started = true and create a plain stdio logger (disable
spinner/prompt APIs) so prompt-producing code paths (the prompt APIs) never
create unresolved promises; reference ensureInitInkSession, started, and the
process.stdin.isTTY/process.stdout.isTTY checks and ensure downstream code uses
the nonInteractive/plain-logger branch instead of the interactive spinner-based
APIs.
In `@src/init/ui/app.tsx`:
- Around line 15-18: Replace the hard minimums that force layout bigger than the
terminal: change contentWidth from Math.max(60, columns - 6) to remove the 60
floor (e.g. contentWidth = Math.max(0, columns - 6)) and change the visibleLogs
window from snapshot.logs.slice(-Math.max(6, rows - 14)) to use stdout height
instead (e.g. snapshot.logs.slice(-Math.max(0, rows - 14))). Update the code
around the variables columns, rows, contentWidth, and visibleLogs so the layout
is clamped by stdout values instead of forcing 60 columns / 6 log lines.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 918bd773-a767-4291-a3db-b8cd1d79e29f
📒 Files selected for processing (13)
skills/usage/SKILL.mdsrc/build/onboarding/command.tssrc/build/onboarding/file-picker.tssrc/build/onboarding/ui/app.tsxsrc/index.tssrc/init/command.tssrc/init/index.tssrc/init/prompts.tssrc/init/runtime.tsxsrc/init/ui.tssrc/init/ui/app.tsxsrc/init/ui/components.tsxwebdocs/init.mdx
…oarding flow with improved error handling and file picker integration Co-authored-by: Copilot <copilot@github.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (4)
src/init/runtime.tsx (1)
69-75: Module-level mutable state may cause issues in test scenarios.The module-scoped
state,listeners,inkApp, andstartedvariables will persist across multiple invocations within the same process. If tests or any multi-call scenario run without explicitly callingstopInitInkSession(), stale state could leak.Consider exposing a
resetInitRuntime()helper for testing, or document that callers must always callstopInitInkSession()on exit paths.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/init/runtime.tsx` around lines 69 - 75, Module-level mutable variables (state, listeners, inkApp, started) persist across in-process invocations and can leak between tests; add a reset helper that clears them so tests can restore clean state. Implement a named export like resetInitRuntime() that sets state back to its initial value (empty logs), clears the listeners Set, sets inkApp = undefined and started = false, and invoke that helper from stopInitInkSession() or make it available to tests so callers can call resetInitRuntime() in teardown; reference the symbols state, listeners, inkApp, started and stopInitInkSession() when locating where to add and call the reset logic.src/init/command.ts (1)
138-144: Move regex patterns to module scope for performance.ESLint flags these inline regex patterns. Moving them to module scope avoids recompilation on every call.
♻️ Suggested refactor
+const NAME_SEPARATOR_REGEX = /[-_]+/g +const WORD_START_REGEX = /\b\w/g + function getSuggestedAppName(projectDir: string) { const packageJson = getPackageJsonData(findNearestPackageJson(projectDir)) const rawName = packageJson?.name?.split('/').pop() || path.basename(projectDir) return rawName - .replace(/[-_]+/g, ' ') - .replace(/\b\w/g, char => char.toUpperCase()) + .replace(NAME_SEPARATOR_REGEX, ' ') + .replace(WORD_START_REGEX, char => char.toUpperCase()) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/init/command.ts` around lines 138 - 144, The inline regex literals in getSuggestedAppName are recompiled on every call; extract them to module-scope constants (e.g., SCOPE_REGEX = /[-_]+/g and CAPITALIZE_REGEX = /\b\w/g) and replace the inline patterns in getSuggestedAppName to use these constants so the regexes are created once and reused across calls.src/build/onboarding/ui/app.tsx (1)
103-103: Passexitdirectly tosetTimeoutto avoid extra closure.ESLint flags this allocation. Since
exit()takes no arguments here, you can pass it directly.♻️ Suggested fix
- setTimeout(() => exit(), 50) + setTimeout(exit, 50)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/build/onboarding/ui/app.tsx` at line 103, Replace the extra closure around exit in the timeout call: locate the setTimeout(() => exit(), 50) usage in the onboarding UI component (app.tsx) and change it to pass the exit function directly to setTimeout (i.e., call setTimeout with exit as the first argument and 50 as the delay) to avoid the unnecessary allocation flagged by ESLint.src/init/prompts.ts (1)
36-38: Theintrofunction ignores its message parameter.The
_messageparameter is prefixed with underscore indicating intentional non-use, but callers may expect the message to be displayed. Consider either using the message or documenting why it's ignored.💡 If the message should be displayed
export function intro(_message: string) { ensureInitInkSession() + // Consider: pushInitLog(_message, 'cyan') or setInitScreen with intro }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/init/prompts.ts` around lines 36 - 38, The intro function currently ignores its _message parameter; change it to actually use the passed-in message by removing the underscore (rename _message to message) and forwarding it to the initialization/display path: either update ensureInitInkSession to accept an optional message argument and pass message into ensureInitInkSession(message), or call the appropriate UI/log/display helper (e.g., a displayIntro or logger function) from intro so the provided message is shown to callers; ensure symbol names referenced are intro, ensureInitInkSession and the former _message parameter.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/init/command.ts`:
- Line 2266: The assignment options.apikey = apikeyCommand || findSavedKey(true)
can throw because findSavedKey(true) throws when no saved key exists; wrap the
call in a try/catch or call a non-throwing helper to avoid crashing in
non-interactive contexts. Modify the logic around options.apikey so that you
first set options.apikey = apikeyCommand if present, otherwise attempt to call
findSavedKey(true) inside a try block and fall back to undefined (or a safe
default) in the catch; alternatively add/use a safe variant like
findSavedKeySafe that returns null/undefined instead of throwing. Ensure you
update the code paths that rely on options.apikey accordingly (references:
options.apikey, apikeyCommand, findSavedKey).
---
Nitpick comments:
In `@src/build/onboarding/ui/app.tsx`:
- Line 103: Replace the extra closure around exit in the timeout call: locate
the setTimeout(() => exit(), 50) usage in the onboarding UI component (app.tsx)
and change it to pass the exit function directly to setTimeout (i.e., call
setTimeout with exit as the first argument and 50 as the delay) to avoid the
unnecessary allocation flagged by ESLint.
In `@src/init/command.ts`:
- Around line 138-144: The inline regex literals in getSuggestedAppName are
recompiled on every call; extract them to module-scope constants (e.g.,
SCOPE_REGEX = /[-_]+/g and CAPITALIZE_REGEX = /\b\w/g) and replace the inline
patterns in getSuggestedAppName to use these constants so the regexes are
created once and reused across calls.
In `@src/init/prompts.ts`:
- Around line 36-38: The intro function currently ignores its _message
parameter; change it to actually use the passed-in message by removing the
underscore (rename _message to message) and forwarding it to the
initialization/display path: either update ensureInitInkSession to accept an
optional message argument and pass message into ensureInitInkSession(message),
or call the appropriate UI/log/display helper (e.g., a displayIntro or logger
function) from intro so the provided message is shown to callers; ensure symbol
names referenced are intro, ensureInitInkSession and the former _message
parameter.
In `@src/init/runtime.tsx`:
- Around line 69-75: Module-level mutable variables (state, listeners, inkApp,
started) persist across in-process invocations and can leak between tests; add a
reset helper that clears them so tests can restore clean state. Implement a
named export like resetInitRuntime() that sets state back to its initial value
(empty logs), clears the listeners Set, sets inkApp = undefined and started =
false, and invoke that helper from stopInitInkSession() or make it available to
tests so callers can call resetInitRuntime() in teardown; reference the symbols
state, listeners, inkApp, started and stopInitInkSession() when locating where
to add and call the reset logic.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: bcde3c1c-77c8-4b0c-aea8-cb59e9b55019
📒 Files selected for processing (10)
skills/native-builds/SKILL.mdskills/usage/SKILL.mdsrc/build/onboarding/command.tssrc/build/onboarding/file-picker.tssrc/build/onboarding/ui/app.tsxsrc/index.tssrc/init/command.tssrc/init/prompts.tssrc/init/runtime.tsxsrc/init/ui/app.tsx
🚧 Files skipped from review as they are similar to previous changes (5)
- src/build/onboarding/command.ts
- src/index.ts
- skills/usage/SKILL.md
- src/init/ui/app.tsx
- src/build/onboarding/file-picker.ts
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (1)
src/init/command.ts (1)
317-382: SplitensureWorkspaceReadyForInit()into branch helpers.This loop now owns project discovery, framework readiness validation, guided Capacitor init, and empty-folder bootstrap. Extracting those cases into small helpers would make the retry flow easier to test and should clear the current complexity failure.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/init/command.ts` around lines 317 - 382, The function ensureWorkspaceReadyForInit is doing four distinct flows (capacitor-config-present, framework-detected, package.json-present, empty-folder bootstrap); extract each branch into a small helper (e.g., handleExistingCapacitorConfig(nearestCapacitorConfig, projectType, projectDir), handleFrameworkProject(projectType, projectDir, initialAppId), handlePackageJsonProject(projectType, projectDir, initialAppId), handleEmptyFolderFlow(initialAppId)) that encapsulates the current logic and returns the same results/side-effects (string | undefined or triggers exit/continue); keep the outer loop in ensureWorkspaceReadyForInit to perform discovery via findNearestCapacitorConfig, findNearestPackageJson, findProjectType and delegate to these helpers, preserving usage of getFrameworkKind, getFrameworkSetupIssues, maybeRunCapacitorInit, waitUntilSetupIsDone, runCreateAppTemplate, pConfirm, cancelBeforeAuthenticatedOnboarding, and exitBeforeAuthenticatedOnboarding so the retry flow and return values remain identical but each branch is independently testable and much simpler.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/init/command.ts`:
- Around line 2057-2059: The fallback message printed via pLog.info currently
only includes the channel flag; update the message to mirror the actual upload
arguments chosen earlier (including the newVersion and delta flags/values) so
the manual command reproduces the same behavior. Locate where pLog.info is
called (using pm.runner and globalChannelName) and append the appropriate
flags/values for newVersion (e.g., --new-version or equivalent variable) and
delta (e.g., --delta or equivalent variable/state) to the string so the
suggested command matches the upload flow's selected options.
- Around line 1364-1406: The code always assigns a careted version string to
versionToInstall so the branch checking versionToInstall === 'latest' is never
hit; make the installer use the literal 'latest' when you intend to offer the
instant-update prompt (e.g., for newest `@capacitor/core` versions). Modify the
final else (and/or adjust the version selection logic around versionToInstall)
to set versionToInstall = 'latest' (and update the pLog message) for the case
where you want to enable the instant-update flow; keep references to
versionToInstall, doDirectInstall, delta, directInstall and the prompt/cancel
flow (pConfirm and cancelCommand) intact so the doDirectInstall branch can run
as intended. Ensure installedVersion/getInstalledVersion behavior remains the
same and only switch to 'latest' when you intend to prompt for instant updates.
- Around line 1291-1306: The typed package.json branch currently returns without
persisting the chosen path, so set the globalPathToPackageJson (the same global
used by the picker/tree branches) to the validated packageJsonPath and call
markStepDone(...) before returning; then continue to call
getAllPackagesDependencies(packageJsonPath) (or
getAllPackagesDependencies(undefined, packageJsonPath) as existing) and return
the same object so later upload/resume logic uses the typed package.json. Ensure
this change is applied inside the block handling the pText result, around where
pIsCancel/packageJsonPath are handled.
---
Nitpick comments:
In `@src/init/command.ts`:
- Around line 317-382: The function ensureWorkspaceReadyForInit is doing four
distinct flows (capacitor-config-present, framework-detected,
package.json-present, empty-folder bootstrap); extract each branch into a small
helper (e.g., handleExistingCapacitorConfig(nearestCapacitorConfig, projectType,
projectDir), handleFrameworkProject(projectType, projectDir, initialAppId),
handlePackageJsonProject(projectType, projectDir, initialAppId),
handleEmptyFolderFlow(initialAppId)) that encapsulates the current logic and
returns the same results/side-effects (string | undefined or triggers
exit/continue); keep the outer loop in ensureWorkspaceReadyForInit to perform
discovery via findNearestCapacitorConfig, findNearestPackageJson,
findProjectType and delegate to these helpers, preserving usage of
getFrameworkKind, getFrameworkSetupIssues, maybeRunCapacitorInit,
waitUntilSetupIsDone, runCreateAppTemplate, pConfirm,
cancelBeforeAuthenticatedOnboarding, and exitBeforeAuthenticatedOnboarding so
the retry flow and return values remain identical but each branch is
independently testable and much simpler.
… safety and error management
There was a problem hiding this comment.
♻️ Duplicate comments (4)
src/init/command.ts (4)
2065-2067:⚠️ Potential issue | 🟡 MinorManual upload fallback command is incomplete.
The fallback message only shows
--channelbut omits the--bundleversion and--delta-onlyflag that were chosen earlier. Users running this command manually won't reproduce the same upload behavior.🛠️ Proposed fix
else { - pLog.info(`Upload yourself with command: ${pm.runner} `@capgo/cli`@latest bundle upload --channel ${globalChannelName}`) + const manualUploadParts = [ + `${pm.runner} `@capgo/cli`@latest bundle upload`, + `--bundle ${newVersion}`, + `--channel ${globalChannelName}`, + ] + if (delta) { + manualUploadParts.push('--delta-only') + } + pLog.info(`Upload yourself with command: ${manualUploadParts.join(' ')}`) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/init/command.ts` around lines 2065 - 2067, The fallback pLog.info message currently printed in the else branch (the pLog.info call using pm.runner and globalChannelName) is missing the chosen bundle/version and the delta-only flag; update that message to include the same --bundle value (use the variable used earlier for the bundle/version, e.g., bundleVersion or --bundle value referenced in the upload logic) and conditionally append --delta-only when the delta-only option (e.g., deltaOnly or isDeltaOnly) was selected so the printed manual command reproduces the exact behavior.
160-180:⚠️ Potential issue | 🟡 Minor
webDirchecks don't handle JSON config files.The string includes checks (
webDir: 'out',webDir: "out") only match JS/TS object literal syntax. Acapacitor.config.jsonfile uses"webDir": "out"with quoted keys, so projects using JSON config will incorrectly be told their setup is broken.🛠️ Proposed fix using regex patterns
+const nextWebDirPattern = /["']?webDir["']?\s*:\s*["']out["']/ +const nuxtWebDirPattern = /["']?webDir["']?\s*:\s*["']\.output\/public["']/ + if (frameworkKind === 'nextjs') { const nextConfig = readExistingFile(findNearestNamedFile(projectDir, ['next.config.ts', 'next.config.js', 'next.config.mjs'])) if (!nextConfig?.includes('output') || !nextConfig.includes('export')) { issues.push('Next.js must use static export (`output: \'export\'`).') } const capacitorConfig = readExistingFile(capacitorConfigPath) - if (capacitorConfig && !capacitorConfig.includes('webDir: \'out\'') && !capacitorConfig.includes('webDir: "out"')) { + if (capacitorConfig && !nextWebDirPattern.test(capacitorConfig)) { issues.push('Capacitor `webDir` should point to `out` for Next.js.') } } if (frameworkKind === 'nuxtjs') { const nuxtConfig = readExistingFile(findNearestNamedFile(projectDir, ['nuxt.config.ts', 'nuxt.config.js'])) if (!nuxtConfig?.includes('preset') || !nuxtConfig.includes('static')) { issues.push('Nuxt must use static Nitro output (`nitro.preset = "static"`).') } const capacitorConfig = readExistingFile(capacitorConfigPath) - if (capacitorConfig && !capacitorConfig.includes('webDir: \'.output/public\'') && !capacitorConfig.includes('webDir: ".output/public"')) { + if (capacitorConfig && !nuxtWebDirPattern.test(capacitorConfig)) { issues.push('Capacitor `webDir` should point to `.output/public` for Nuxt.') } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/init/command.ts` around lines 160 - 180, The current checks in the frameworkKind branches use literal substring checks for "webDir: 'out'" / "webDir: \"out\"" which miss JSON-style keys (e.g. "webDir": "out"); update the capacitorConfig validation (the block that reads capacitorConfig via readExistingFile(capacitorConfigPath) and pushes into issues) to use a regex-based check that matches both JS/TS object literal and JSON key/value styles for the expected webDir values (for Next.js: out; for Nuxt: .output/public). Modify the capacitorConfig conditionals so they test against a regex like one that allows optional quotes around the key, optional whitespace, a colon, and quoted value (and similarly for .output/public), and keep pushing the same issue messages into issues when no match is found.
1401-1405:⚠️ Potential issue | 🟠 MajorInstant-update prompt is unreachable.
versionToInstallis always set to'^5.0.0','^6.0.0','^7.0.0', or'^8.0.0'by lines 1375-1390. The conditionversionToInstall === 'latest'at line 1402 will never be true, so the instant-update prompt is never shown.🛠️ Proposed fix
+ let shouldOfferInstantUpdate = false if (lessThan(parse(coreVersion), parse('6.0.0'))) { pLog.info(`@capacitor/core version is ${coreVersion}, installing compatible capacitor-updater v5`) pLog.warn(`Consider upgrading to Capacitor v6 or higher to support the latest mobile OS features: ${urlMigrateV6}`) versionToInstall = '^5.0.0' } // ... other version checks ... else { pLog.info(`@capacitor/core version is ${coreVersion}, installing latest capacitor-updater v8+`) versionToInstall = '^8.0.0' + shouldOfferInstantUpdate = true } // Later in the code: - if (versionToInstall === 'latest') { + if (shouldOfferInstantUpdate) { doDirectInstall = await pConfirm({ message: `Do you want to set instant updates...` })🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/init/command.ts` around lines 1401 - 1405, The instant-update prompt is never shown because versionToInstall is overwritten with a default like '^5.0.0' before the check; preserve the user's original selection (or move the prompt) so the check can detect 'latest': capture the raw selection into a separate variable (e.g., selectedVersion) before the block that sets versionToInstall, then change the condition to if (selectedVersion === 'latest') and keep the pConfirm(...) / cancelCommand(doDirectInstall, orgId, apikey) calls using appId, orgId, apikey; alternatively, move the pConfirm/cancelCommand block to run before the code that assigns the default caret versions to versionToInstall.
1298-1314:⚠️ Potential issue | 🟡 MinorManual path entry branch doesn't persist the selected path.
The tree selector (line 1294) and default branch (line 1320) call
markStepDone, but this manual text entry branch returns directly without persistingglobalPathToPackageJson. On resume, the wrongpackage.jsonmay be used.🛠️ Proposed fix
const packageJsonPath = await pText({ message: 'Enter path to package.json file:', validate: (value) => { if (!value?.trim()) return 'Path is required.' if (!existsSync(value)) return `Path ${value} does not exist` if (path.basename(value) !== PACKNAME) return 'Selected a file that is not a package.json file' }, }) as string if (pIsCancel(packageJsonPath)) { pCancel('Operation cancelled.') exit(1) } + const selectedPackageJsonPath = packageJsonPath.trim() + markStepDone(stepsDone, selectedPackageJsonPath) - return { dependencies: await getAllPackagesDependencies(undefined, packageJsonPath), path: packageJsonPath } + return { dependencies: await getAllPackagesDependencies(undefined, selectedPackageJsonPath), path: selectedPackageJsonPath }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/init/command.ts` around lines 1298 - 1314, The manual pText branch currently returns without persisting the chosen path or marking the step done; update the branch that sets packageJsonPath (from pText) to assign the value to globalPathToPackageJson and call markStepDone(...) with the appropriate step id before returning the result from getAllPackagesDependencies(packageJsonPath); ensure you still handle pIsCancel/pCancel and exit as before and use the same symbols packageJsonPath, globalPathToPackageJson, markStepDone, and getAllPackagesDependencies so the selected path is persisted like the tree/default branches.
🧹 Nitpick comments (3)
src/init/command.ts (3)
900-901: Move role normalization regex to module scope.The regex at line 900 is recreated on every call to
selectOrganizationForInit.♻️ Suggested refactor
+const orgRolePrefixPattern = /^org_/ + async function selectOrganizationForInit(...) { // ... - const normalizeRole = (role: string | null | undefined) => role?.replace(/^org_/, '') ?? '' + const normalizeRole = (role: string | null | undefined) => role?.replace(orgRolePrefixPattern, '') ?? ''🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/init/command.ts` around lines 900 - 901, The normalizeRole helper inside selectOrganizationForInit recreates the /^org_/ regex on every call; extract that regex to module scope as a constant (e.g., ORG_PREFIX_RE) and update normalizeRole to use that constant, leaving the rest of the logic unchanged (the function name normalizeRole, its use in creating normalizedRoles from roles.map, and the Set creation should remain the same so callers like selectOrganizationForInit continue to work).
145-151: Move regex patterns to module scope for performance.The regex patterns on lines 149-150 are recreated on every call. Per static analysis, move them to module scope.
♻️ Suggested refactor
+const kebabSnakeToSpacePattern = /[-_]+/g +const capitalizeFirstLetterPattern = /\b\w/g + function getSuggestedAppName(projectDir: string) { const packageJson = getPackageJsonData(findNearestPackageJson(projectDir)) const rawName = packageJson?.name?.split('/').pop() || path.basename(projectDir) return rawName - .replaceAll(/[-_]+/g, ' ') - .replaceAll(/\b\w/g, char => char.toUpperCase()) + .replaceAll(kebabSnakeToSpacePattern, ' ') + .replaceAll(capitalizeFirstLetterPattern, char => char.toUpperCase()) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/init/command.ts` around lines 145 - 151, The two RegExp literals used inside getSuggestedAppName (the /[-_]+/g and /\b\w/g patterns) should be hoisted to module-scope constants to avoid reallocating them on every call; create descriptive consts (e.g., REPLACE_SEPARATORS and CAPITALIZE_WORD) at top-level and replace the inline regex usages in getSuggestedAppName with those constants so the function calls replaceAll(REPLACE_SEPARATORS, ' ') and replaceAll(CAPITALIZE_WORD, char => char.toUpperCase()).
712-728: Good version normalization handling, but move regex to module scope.This function properly handles semver range specifiers by extracting the concrete version. The fallback regex at line 722 should be moved to module scope per static analysis.
♻️ Suggested refactor
+const semverExtractPattern = /\d+\.\d+\.\d+(?:-[0-9A-Z.-]+)?/i + function normalizeConcreteVersion(version: string | undefined) { if (!version) return undefined const trimmedVersion = version.trim() if (!trimmedVersion || trimmedVersion === 'latest') return undefined if (canParse(trimmedVersion)) return format(parse(trimmedVersion)) - const fallbackMatch = /\d+\.\d+\.\d+(?:-[0-9A-Z.-]+)?/i.exec(trimmedVersion) + const fallbackMatch = semverExtractPattern.exec(trimmedVersion) if (!fallbackMatch?.[0]) return undefined if (!canParse(fallbackMatch[0])) return undefined return format(parse(fallbackMatch[0])) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/init/command.ts` around lines 712 - 728, The fallback regex used inside normalizeConcreteVersion should be pulled out to module scope as a named constant (e.g., FALLBACK_VERSION_REGEX) so it isn't re-created on every call and satisfies static analysis; replace the inline /\d+\.\d+\.\d+(?:-[0-9A-Z.-]+)?/i with that constant, and update normalizeConcreteVersion to reference FALLBACK_VERSION_REGEX.exec(trimmedVersion) (keeping the case-insensitive flag and same pattern semantics).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@src/init/command.ts`:
- Around line 2065-2067: The fallback pLog.info message currently printed in the
else branch (the pLog.info call using pm.runner and globalChannelName) is
missing the chosen bundle/version and the delta-only flag; update that message
to include the same --bundle value (use the variable used earlier for the
bundle/version, e.g., bundleVersion or --bundle value referenced in the upload
logic) and conditionally append --delta-only when the delta-only option (e.g.,
deltaOnly or isDeltaOnly) was selected so the printed manual command reproduces
the exact behavior.
- Around line 160-180: The current checks in the frameworkKind branches use
literal substring checks for "webDir: 'out'" / "webDir: \"out\"" which miss
JSON-style keys (e.g. "webDir": "out"); update the capacitorConfig validation
(the block that reads capacitorConfig via readExistingFile(capacitorConfigPath)
and pushes into issues) to use a regex-based check that matches both JS/TS
object literal and JSON key/value styles for the expected webDir values (for
Next.js: out; for Nuxt: .output/public). Modify the capacitorConfig conditionals
so they test against a regex like one that allows optional quotes around the
key, optional whitespace, a colon, and quoted value (and similarly for
.output/public), and keep pushing the same issue messages into issues when no
match is found.
- Around line 1401-1405: The instant-update prompt is never shown because
versionToInstall is overwritten with a default like '^5.0.0' before the check;
preserve the user's original selection (or move the prompt) so the check can
detect 'latest': capture the raw selection into a separate variable (e.g.,
selectedVersion) before the block that sets versionToInstall, then change the
condition to if (selectedVersion === 'latest') and keep the pConfirm(...) /
cancelCommand(doDirectInstall, orgId, apikey) calls using appId, orgId, apikey;
alternatively, move the pConfirm/cancelCommand block to run before the code that
assigns the default caret versions to versionToInstall.
- Around line 1298-1314: The manual pText branch currently returns without
persisting the chosen path or marking the step done; update the branch that sets
packageJsonPath (from pText) to assign the value to globalPathToPackageJson and
call markStepDone(...) with the appropriate step id before returning the result
from getAllPackagesDependencies(packageJsonPath); ensure you still handle
pIsCancel/pCancel and exit as before and use the same symbols packageJsonPath,
globalPathToPackageJson, markStepDone, and getAllPackagesDependencies so the
selected path is persisted like the tree/default branches.
---
Nitpick comments:
In `@src/init/command.ts`:
- Around line 900-901: The normalizeRole helper inside selectOrganizationForInit
recreates the /^org_/ regex on every call; extract that regex to module scope as
a constant (e.g., ORG_PREFIX_RE) and update normalizeRole to use that constant,
leaving the rest of the logic unchanged (the function name normalizeRole, its
use in creating normalizedRoles from roles.map, and the Set creation should
remain the same so callers like selectOrganizationForInit continue to work).
- Around line 145-151: The two RegExp literals used inside getSuggestedAppName
(the /[-_]+/g and /\b\w/g patterns) should be hoisted to module-scope constants
to avoid reallocating them on every call; create descriptive consts (e.g.,
REPLACE_SEPARATORS and CAPITALIZE_WORD) at top-level and replace the inline
regex usages in getSuggestedAppName with those constants so the function calls
replaceAll(REPLACE_SEPARATORS, ' ') and replaceAll(CAPITALIZE_WORD, char =>
char.toUpperCase()).
- Around line 712-728: The fallback regex used inside normalizeConcreteVersion
should be pulled out to module scope as a named constant (e.g.,
FALLBACK_VERSION_REGEX) so it isn't re-created on every call and satisfies
static analysis; replace the inline /\d+\.\d+\.\d+(?:-[0-9A-Z.-]+)?/i with that
constant, and update normalizeConcreteVersion to reference
FALLBACK_VERSION_REGEX.exec(trimmedVersion) (keeping the case-insensitive flag
and same pattern semantics).
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 59831f19-a012-4f39-a232-c0850615fd2f
📒 Files selected for processing (5)
src/init/command.tssrc/init/prompts.tssrc/init/runtime.tsxsrc/init/ui/app.tsxsrc/init/ui/components.tsx
✅ Files skipped from review due to trivial changes (2)
- src/init/ui/components.tsx
- src/init/prompts.ts
|
There was a problem hiding this comment.
🧹 Nitpick comments (3)
src/init/command.ts (3)
147-153: Move inline regex patterns to module scope.The regex patterns in
getSuggestedAppNameare recompiled on every call. Static analysis flagged these for performance optimization.♻️ Proposed fix
Add these patterns at module scope (around line 36):
+const hyphenUnderscorePattern = /[-_]+/g +const wordBoundaryPattern = /\b\w/gThen update the function:
function getSuggestedAppName(projectDir: string) { const packageJson = getPackageJsonData(findNearestPackageJson(projectDir)) const rawName = packageJson?.name?.split('/').pop() || path.basename(projectDir) return rawName - .replaceAll(/[-_]+/g, ' ') - .replaceAll(/\b\w/g, char => char.toUpperCase()) + .replaceAll(hyphenUnderscorePattern, ' ') + .replaceAll(wordBoundaryPattern, char => char.toUpperCase()) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/init/command.ts` around lines 147 - 153, getSuggestedAppName currently constructs two RegExp literals on every call which hurts performance; move those regexes to module scope as constants (e.g. NAME_SPLIT_RE and WORD_BOUNDARY_RE) and update getSuggestedAppName to use those constants instead of inline /[-_]+/g and /\b\w/g so the patterns are compiled once and reused; ensure the constants are declared near the top of the module and referenced by name inside getSuggestedAppName.
714-730: Move version extraction regex to module scope.The regex pattern at line 724 is recompiled on every call to
normalizeConcreteVersion. Static analysis flagged this for performance optimization.♻️ Proposed fix
Add at module scope (around line 36):
+const concreteVersionPattern = /\d+\.\d+\.\d+(?:-[0-9A-Z.-]+)?/iThen update the function:
function normalizeConcreteVersion(version: string | undefined) { // ... - const fallbackMatch = /\d+\.\d+\.\d+(?:-[0-9A-Z.-]+)?/i.exec(trimmedVersion) + const fallbackMatch = concreteVersionPattern.exec(trimmedVersion) // ... }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/init/command.ts` around lines 714 - 730, Move the inline regex in normalizeConcreteVersion to module scope to avoid recompiling on every call: define a top-level constant (e.g. FALLBACK_VERSION_REGEX = /\d+\.\d+\.\d+(?:-[0-9A-Z.-]+)?/i) near other module-level constants and replace the local /.../.exec(...) usage in normalizeConcreteVersion with FALLBACK_VERSION_REGEX.exec(trimmedVersion) so the function still uses the same pattern but reuses the compiled regex.
890-950: Move role normalization regex to module scope.The regex pattern at line 902 is recompiled on every call. Static analysis flagged this for performance optimization.
♻️ Proposed fix
Add at module scope:
+const orgRolePrefixPattern = /^org_/Then update line 902:
- const normalizeRole = (role: string | null | undefined) => role?.replace(/^org_/, '') ?? '' + const normalizeRole = (role: string | null | undefined) => role?.replace(orgRolePrefixPattern, '') ?? ''🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/init/command.ts` around lines 890 - 950, The normalizeRole closure inside selectOrganizationForInit recompiles the /^org_/ regex on every call; move that regex to module scope (e.g., const ORG_ROLE_PREFIX = /^org_/) and update normalizeRole (and any other callers) to use ORG_ROLE_PREFIX.replace or ORG_ROLE_PREFIX in replace logic so the regex is compiled once at module load time; keep the function name selectOrganizationForInit and normalizeRole unchanged and only replace the inline /^org_/ with the new ORG_ROLE_PREFIX constant.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/init/command.ts`:
- Around line 147-153: getSuggestedAppName currently constructs two RegExp
literals on every call which hurts performance; move those regexes to module
scope as constants (e.g. NAME_SPLIT_RE and WORD_BOUNDARY_RE) and update
getSuggestedAppName to use those constants instead of inline /[-_]+/g and
/\b\w/g so the patterns are compiled once and reused; ensure the constants are
declared near the top of the module and referenced by name inside
getSuggestedAppName.
- Around line 714-730: Move the inline regex in normalizeConcreteVersion to
module scope to avoid recompiling on every call: define a top-level constant
(e.g. FALLBACK_VERSION_REGEX = /\d+\.\d+\.\d+(?:-[0-9A-Z.-]+)?/i) near other
module-level constants and replace the local /.../.exec(...) usage in
normalizeConcreteVersion with FALLBACK_VERSION_REGEX.exec(trimmedVersion) so the
function still uses the same pattern but reuses the compiled regex.
- Around line 890-950: The normalizeRole closure inside
selectOrganizationForInit recompiles the /^org_/ regex on every call; move that
regex to module scope (e.g., const ORG_ROLE_PREFIX = /^org_/) and update
normalizeRole (and any other callers) to use ORG_ROLE_PREFIX.replace or
ORG_ROLE_PREFIX in replace logic so the regex is compiled once at module load
time; keep the function name selectOrganizationForInit and normalizeRole
unchanged and only replace the inline /^org_/ with the new ORG_ROLE_PREFIX
constant.


Introduce an interactive onboarding process with progress tracking and framework detection. Enhance user experience with reusable prompt components and session management. Update documentation to reflect these new features.
Summary by CodeRabbit
New Features
Documentation