Skip to content

Implement Ink-based onboarding experience#549

Merged
riderx merged 6 commits intomainfrom
new-init-command
Mar 22, 2026
Merged

Implement Ink-based onboarding experience#549
riderx merged 6 commits intomainfrom
new-init-command

Conversation

@riderx
Copy link
Member

@riderx riderx commented Mar 22, 2026

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

    • Fullscreen init onboarding (alias: onboarding) with persistent dashboard, phase roadmap, per-step progress cards, shared logs, resumable checkpoints, and resume guidance
    • macOS shows a native package.json file picker when auto-detection fails
    • Option to sync a web-onboarded pending app ID into local Capacitor config
    • Improved channel naming, dependency gating, encryption/upload recovery, and retry flows
  • Documentation

    • Updated onboarding docs and examples to reflect the new init flow, macOS picker, and pending-app sync behavior

riderx added 2 commits March 22, 2026 01:19
…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.
@chatgpt-codex-connector
Copy link

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@coderabbitai
Copy link

coderabbitai bot commented Mar 22, 2026

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Replaces the previous onboarding command with an Ink-based fullscreen build init (alias build onboarding) flow, adds workspace readiness checks, macOS package.json native picker, persistent multi-step progress/resume, channel/pending-app syncing to Capacitor config, org selection, dependency/retry recovery loops, and new init runtime/prompts/UI.

Changes

Cohort / File(s) Summary
CLI wiring & command rename
src/index.ts, src/build/onboarding/command.ts
Renamed build onboardingbuild init (keeps onboarding alias); exported entrypoint renamed to onboardingBuilderCommand; CI/non‑TTY error text and action handler updated to reference build init.
macOS file picker
src/build/onboarding/file-picker.ts
Added openMacFilePicker(script) helper; openFilePicker() delegates to it; new openPackageJsonPicker() returns a selected package.json path only when basename matches.
Build onboarding UI adjustments
src/build/onboarding/ui/app.tsx
Updated input handling to intercept Ctrl+C (sends SIGINT), added exitRequestedRef + exitOnboarding() with deduplication, and switched exit/retry messages to build init.
Init command & implementation
src/init/command.ts, src/init/index.ts
Large init implementation: workspace readiness (Capacitor/package.json probing), interactive install/init loops, dependency/version gating, channel name creation, pending-app → Capacitor config sync, retries for installer/encryption/upload, step persistence/resume, org selection; initApp exported.
Init runtime & prompts
src/init/runtime.tsx, src/init/prompts.ts
New Ink runtime and prompt abstraction: session lifecycle (ensure/stop), typed runtime state, prompt request/resolve APIs (confirm/text/select), CANCEL handling, log/spinner helpers and spinner tone logic.
Init UI (Ink) components
src/init/ui.ts, src/init/ui/app.tsx, src/init/ui/components.tsx
New Ink UI stack: InitInkApp, header/progress/current-step components, prompt views (confirm/text/select), spinner area, initOnboardingSteps definitions, and render helpers (welcome/frame/complete + resume formatting).
Init command helpers & recovery flows
src/init/prompts.ts, src/init/command.ts (helpers)
Added prompt wrappers and recovery utilities: selectRecoveryOption, askForExistingDirectoryPath, syncPendingAppIdToCapacitorConfig, normalizeConcreteVersion, getUpdaterInstallBlocker, and retry-capable steps for updater/install/encrypt/upload.
Documentation & SKILL notes
webdocs/init.mdx, skills/usage/SKILL.md, skills/native-builds/SKILL.md
Docs updated to describe the Ink-based fullscreen init flow, macOS package.json picker, pending-app sync behavior, persistent dashboard/phase/step progress, and the command rename/alias.

Sequence Diagram

sequenceDiagram
    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()
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Poem

🐇 I hopped into the terminal bright,
Fullscreen Ink glowed, steps lined up in sight.
I picked package.json from macOS with glee,
Synced pending IDs and resumed with tea.
Hooray — init finished, carrot snack—wee! 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.30% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: implementing an Ink-based onboarding experience, which is the primary focus of this PR across multiple new files and refactored components.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch new-init-command

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

Tip

You can disable sequence diagrams in the walkthrough.

Disable the reviews.sequence_diagrams setting to disable sequence diagrams in the walkthrough.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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

📥 Commits

Reviewing files that changed from the base of the PR and between a398227 and e41dd15.

📒 Files selected for processing (13)
  • skills/usage/SKILL.md
  • src/build/onboarding/command.ts
  • src/build/onboarding/file-picker.ts
  • src/build/onboarding/ui/app.tsx
  • src/index.ts
  • src/init/command.ts
  • src/init/index.ts
  • src/init/prompts.ts
  • src/init/runtime.tsx
  • src/init/ui.ts
  • src/init/ui/app.tsx
  • src/init/ui/components.tsx
  • webdocs/init.mdx

…oarding flow with improved error handling and file picker integration

Co-authored-by: Copilot <copilot@github.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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, and started variables will persist across multiple invocations within the same process. If tests or any multi-call scenario run without explicitly calling stopInitInkSession(), stale state could leak.

Consider exposing a resetInitRuntime() helper for testing, or document that callers must always call stopInitInkSession() 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: Pass exit directly to setTimeout to 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: The intro function ignores its message parameter.

The _message parameter 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

📥 Commits

Reviewing files that changed from the base of the PR and between e41dd15 and 9be92ec.

📒 Files selected for processing (10)
  • skills/native-builds/SKILL.md
  • skills/usage/SKILL.md
  • src/build/onboarding/command.ts
  • src/build/onboarding/file-picker.ts
  • src/build/onboarding/ui/app.tsx
  • src/index.ts
  • src/init/command.ts
  • src/init/prompts.ts
  • src/init/runtime.tsx
  • src/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

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
src/init/command.ts (1)

317-382: Split ensureWorkspaceReadyForInit() 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.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: deb1e53f-2f76-48dc-9f6a-658278b4a3dd

📥 Commits

Reviewing files that changed from the base of the PR and between 9be92ec and bb9bc7e.

📒 Files selected for processing (1)
  • src/init/command.ts

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (4)
src/init/command.ts (4)

2065-2067: ⚠️ Potential issue | 🟡 Minor

Manual upload fallback command is incomplete.

The fallback message only shows --channel but omits the --bundle version and --delta-only flag 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

webDir checks don't handle JSON config files.

The string includes checks (webDir: 'out', webDir: "out") only match JS/TS object literal syntax. A capacitor.config.json file 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 | 🟠 Major

Instant-update prompt is unreachable.

versionToInstall is always set to '^5.0.0', '^6.0.0', '^7.0.0', or '^8.0.0' by lines 1375-1390. The condition versionToInstall === '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 | 🟡 Minor

Manual 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 persisting globalPathToPackageJson. On resume, the wrong package.json may 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

📥 Commits

Reviewing files that changed from the base of the PR and between bb9bc7e and b34e714.

📒 Files selected for processing (5)
  • src/init/command.ts
  • src/init/prompts.ts
  • src/init/runtime.tsx
  • src/init/ui/app.tsx
  • src/init/ui/components.tsx
✅ Files skipped from review due to trivial changes (2)
  • src/init/ui/components.tsx
  • src/init/prompts.ts

@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
6 Security Hotspots

See analysis details on SonarQube Cloud

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
src/init/command.ts (3)

147-153: Move inline regex patterns to module scope.

The regex patterns in getSuggestedAppName are 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/g

Then 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.-]+)?/i

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 92f56001-f424-4816-9a7a-b8e1e8c30307

📥 Commits

Reviewing files that changed from the base of the PR and between b34e714 and df101f3.

📒 Files selected for processing (1)
  • src/init/command.ts

@riderx riderx merged commit 8b0d90d into main Mar 22, 2026
19 of 20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant