refactor(init): move init logic to action pattern#982
Conversation
|
Claude finished @mttdnt's task in 2m 14s —— View job PR Review
ChangesetNo changeset is present, which is correct — this is a pure structural refactor with no public API or user-visible behavior changes. IssuesBug: unsafe } catch (err) {
throw new InitError(`Failed to communicate with the Sanity API:\n${err.message}`, 1)
}
const message = err instanceof Error ? err.message : String(err)
throw new InitError(`Failed to communicate with the Sanity API:\n${message}`, 1)Minor: Coverage dropped 3.1% on |
📦 Bundle Stats —
|
| Metric | Value | vs main (511eb5c) |
|---|---|---|
| Internal (raw) | 2.1 KB | - |
| Internal (gzip) | 799 B | - |
| Bundled (raw) | 10.94 MB | - |
| Bundled (gzip) | 2.05 MB | - |
| Import time | 828ms | +6ms, +0.7% |
bin:sanity
| Metric | Value | vs main (511eb5c) |
|---|---|---|
| Internal (raw) | 975 B | - |
| Internal (gzip) | 460 B | - |
| Bundled (raw) | 9.84 MB | - |
| Bundled (gzip) | 1.77 MB | - |
| Import time | 1.97s | +5ms, +0.2% |
🗺️ View treemap · Artifacts
Details
- Import time regressions over 10% are flagged with
⚠️ - Sizes shown as raw / gzip 🗜️. Internal bytes = own code only. Total bytes = with all dependencies. Import time = Node.js cold-start median.
📦 Bundle Stats — @sanity/cli-core
Compared against main (511eb5cf)
| Metric | Value | vs main (511eb5c) |
|---|---|---|
| Internal (raw) | 93.8 KB | - |
| Internal (gzip) | 21.9 KB | - |
| Bundled (raw) | 21.62 MB | - |
| Bundled (gzip) | 3.42 MB | - |
| Import time | 786ms | +6ms, +0.8% |
🗺️ View treemap · Artifacts
Details
- Import time regressions over 10% are flagged with
⚠️ - Sizes shown as raw / gzip 🗜️. Internal bytes = own code only. Total bytes = with all dependencies. Import time = Node.js cold-start median.
📦 Bundle Stats — create-sanity
Compared against main (511eb5cf)
| Metric | Value | vs main (511eb5c) |
|---|---|---|
| Internal (raw) | 976 B | - |
| Internal (gzip) | 507 B | - |
| Bundled (raw) | 50.7 KB | - |
| Bundled (gzip) | 12.6 KB | - |
| Import time | ❌ ChildProcess denied: node | - |
Details
- Import time regressions over 10% are flagged with
⚠️ - Sizes shown as raw / gzip 🗜️. Internal bytes = own code only. Total bytes = with all dependencies. Import time = Node.js cold-start median.
Coverage Delta
Comparing 11 changed files against main @ Overall Coverage
|
Extract `InitCommand` body into a framework-agnostic `initAction` function, keeping `commands/init.ts` as a thin oclif wrapper that parses flags and forwards them via `flagsToInitOptions`. The action is structured around `InitOptions` + `InitContext` so the init flow can be exercised (and tested) without going through oclif. `InitError` lets the action signal exit codes to the command without calling `this.error`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ef073f3 to
03deba9
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 3 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 03deba9. Configure here.
| sanityStudioTemplate.replace( | ||
| ':configPath:', | ||
| `${'../'.repeat(countNestedFolders(path.dirname(embeddedStudioRouteFilePath.slice(workDir.length))))}sanity.config`, | ||
| `${'../'.repeat(countNestedFolders(embeddedStudioRouteFilePath.slice(workDir.length)))}sanity.config`, |
There was a problem hiding this comment.
Missing path.dirname causes off-by-one in import path
High Severity
The path.dirname() wrapper was removed from the countNestedFolders call when computing the relative import path for the embedded studio route. countNestedFolders counts all path segments including the filename (page.tsx), so without path.dirname stripping it first, the count is one too high. This generates one extra ../ in the import, producing a broken sanity.config import in the generated Next.js studio route file. The existing countNestedFolders tests confirm the old path.dirname-based approach was correct.
Reviewed by Cursor Bugbot for commit 03deba9. Configure here.
| // - Interactive: prompt user | ||
| let mcpMode: 'auto' | 'prompt' | 'skip' = 'prompt' | ||
| if (!this.flags.mcp || !this.resolveIsInteractive() || getSanityEnv() !== 'production') { | ||
| if (!this.flags.mcp || !isInteractive()) { |
There was a problem hiding this comment.
MCP non-production environment skip check was dropped
Low Severity
The old mcpMode computation included a getSanityEnv() !== 'production' condition that skipped MCP setup in non-production environments (e.g. staging). This check was dropped in the refactor, so MCP setup will now run in staging/dev environments where it was previously skipped. Since the PR states no behavior changes are intended, this appears to be an accidental omission.
Reviewed by Cursor Bugbot for commit 03deba9. Configure here.
| organizations: [], | ||
| planId, | ||
| user, | ||
| }) |
There was a problem hiding this comment.
Coupon not forwarded in app template project creation
Medium Severity
In promptForAppTemplateSetup, when creating a new project, coupon is hardcoded to undefined in both the interactive and unattended paths. The old code's promptForProjectCreation read this.flags.coupon directly, so it always had access to the coupon. Now that coupon is an explicit parameter, it needs to be threaded through — but promptForAppTemplateSetup never receives or forwards it, so coupon metadata is lost for app template project creation.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 03deba9. Configure here.


Description
Extracts the body of
InitCommandinto a framework-agnosticinitActionfunction.commands/init.tsbecomes a thin oclif wrapper: it declares flags, builds anInitOptionsviaflagsToInitOptions, and forwards toinitActionwith anInitContext.InitErrorlets the action signal exit codes without callingthis.errordirectly.This is a pure structural refactor — the same commits that are bundled into #845 (
refactor(init): move init logic to action pattern,refactor(init): split init flows into separate functions,fix(init): restore unattended mode behavior in initAction), applied on top of currentmainrather than on thebundled-createbranch. The intent is to land this independently so downstream work (e.g. thecreate-sanitybundling in #845) has a smaller diff.The refactor has been reconciled with the app-init project/dataset selection flow that landed on main in #968:
initActioncontains thepromptForAppTemplateSetupflow (skip / new / existing project → dataset)initAppacceptsdatasetName/projectIdand renders them in the success messageconfigureAppProjecttelemetry stepFiles not touched (kept at main state):
bootstrapLocalTemplate.ts,templates/*.ts,datasets/copy.ts,services/datasets.ts.What to review
packages/@sanity/cli/src/actions/init/initAction.ts— new entry point containing what used to beInitCommand's methods as module-level functions.packages/@sanity/cli/src/commands/init.ts— reduced from ~1360 lines to ~237; flag schema only.packages/@sanity/cli/src/actions/init/types.ts—InitOptions,InitContext,flagsToInitOptions.packages/@sanity/cli/src/actions/init/initError.ts— typed exit-code error.initApp.ts,initStudio.ts,initNextJs.ts,scaffoldTemplate.ts,bootstrapTemplate.ts,initHelpers.ts) for the signature/parameter passing changes.init.telemetry.tsfor the newConfigureAppProjectStep.initAction.test.ts,flagsToInitOptions.test.ts,initError.test.ts,init.command.test.tsare new. Existing init tests were updated to match the refactored call sites.Testing
pnpm test— 2585 / 2585 passingpnpm check:types— cleanpnpm check:lint— no new warningspnpm build:cli— cleanNo behavior changes intended. If any surface in
initbehaves differently after this PR, that's a bug to fix here.🤖 Generated with Claude Code
Note
Medium Risk
Large refactor of the CLI
initentrypoint and its error/option plumbing; while intended to be behavior-preserving, it touches core project/dataset creation, auth gating, and scaffolding paths where regressions could affect initialization flows.Overview
Moves the bulk of
sanity initout ofInitCommandinto a newinitAction(options, context)and introducesInitOptions/InitContextplusflagsToInitOptionsto translate oclif flags into action inputs.Reworks init sub-flows (
initStudio,initApp,initNextJs,scaffoldTemplate,bootstrapTemplate) to take the consolidatedoptionsobject, centralizes git/env/package-manager handling, and replaces directoutput.error/oclif exits with a newInitErrorcarrying an exit code.Adds focused unit tests for the new action layer and command wrapper behavior (flag mapping, MCP mode computation, and
InitErrortranslation), and loosens init telemetry’s datasetvisibilityfield to be optional.Reviewed by Cursor Bugbot for commit 03deba9. Bugbot is set up for automated code reviews on this repo. Configure here.