Skip to content

refactor(init): move init logic to action pattern#982

Closed
mttdnt wants to merge 1 commit intomainfrom
refactor/init-action-pattern
Closed

refactor(init): move init logic to action pattern#982
mttdnt wants to merge 1 commit intomainfrom
refactor/init-action-pattern

Conversation

@mttdnt
Copy link
Copy Markdown
Contributor

@mttdnt mttdnt commented Apr 20, 2026

Description

Extracts the body of InitCommand into a framework-agnostic initAction function. commands/init.ts becomes a thin oclif wrapper: it declares flags, builds an InitOptions via flagsToInitOptions, and forwards to initAction with an InitContext. InitError lets the action signal exit codes without calling this.error directly.

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 current main rather than on the bundled-create branch. The intent is to land this independently so downstream work (e.g. the create-sanity bundling 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:

  • initAction contains the promptForAppTemplateSetup flow (skip / new / existing project → dataset)
  • initApp accepts datasetName/projectId and renders them in the success message
  • New configureAppProject telemetry step

Files 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 be InitCommand'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.tsInitOptions, InitContext, flagsToInitOptions.
  • packages/@sanity/cli/src/actions/init/initError.ts — typed exit-code error.
  • Flow files (initApp.ts, initStudio.ts, initNextJs.ts, scaffoldTemplate.ts, bootstrapTemplate.ts, initHelpers.ts) for the signature/parameter passing changes.
  • init.telemetry.ts for the new ConfigureAppProjectStep.
  • Tests: initAction.test.ts, flagsToInitOptions.test.ts, initError.test.ts, init.command.test.ts are new. Existing init tests were updated to match the refactored call sites.

Testing

  • pnpm test — 2585 / 2585 passing
  • pnpm check:types — clean
  • pnpm check:lint — no new warnings
  • pnpm build:cli — clean

No behavior changes intended. If any surface in init behaves differently after this PR, that's a bug to fix here.

🤖 Generated with Claude Code


Note

Medium Risk
Large refactor of the CLI init entrypoint 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 init out of InitCommand into a new initAction(options, context) and introduces InitOptions/InitContext plus flagsToInitOptions to translate oclif flags into action inputs.

Reworks init sub-flows (initStudio, initApp, initNextJs, scaffoldTemplate, bootstrapTemplate) to take the consolidated options object, centralizes git/env/package-manager handling, and replaces direct output.error/oclif exits with a new InitError carrying an exit code.

Adds focused unit tests for the new action layer and command wrapper behavior (flag mapping, MCP mode computation, and InitError translation), and loosens init telemetry’s dataset visibility field to be optional.

Reviewed by Cursor Bugbot for commit 03deba9. Bugbot is set up for automated code reviews on this repo. Configure here.

@claude
Copy link
Copy Markdown

claude Bot commented Apr 20, 2026

Claude finished @mttdnt's task in 2m 14s —— View job


PR Review

  • Gather context: read key changed files and diff
  • Check for changeset file
  • Review initAction.ts (main entry point)
  • Review types.ts, initError.ts
  • Review flow files and test coverage
  • Review commands/init.ts
  • Compile findings and post review

Changeset

No changeset is present, which is correct — this is a pure structural refactor with no public API or user-visible behavior changes.


Issues

Bug: unsafe err.message access without type guardinitAction.ts:817

} catch (err) {
  throw new InitError(`Failed to communicate with the Sanity API:\n${err.message}`, 1)
}

err can be any thrown value (string, number, non-Error object). With useUnknownInCatchVariables: false in the base tsconfig, TypeScript won't flag this, but .message will be undefined if a non-Error value is thrown, silently corrupting the error message. Every other catch block in this same file uses the defensive pattern — e.g., lines 645, 1162, 1203:

const message = err instanceof Error ? err.message : String(err)
throw new InitError(`Failed to communicate with the Sanity API:\n${message}`, 1)

Fix this →


Minor: initNextJs.ts coverage regression

Coverage dropped 3.1% on initNextJs.ts (now 86.7%). Given the refactor touched that file, it's worth checking whether the uncovered paths were already untested or newly unreachable after the signature changes.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 20, 2026

📦 Bundle Stats — @sanity/cli

Compared against main (511eb5cf)

@sanity/cli

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.

@mttdnt mttdnt marked this pull request as ready for review April 20, 2026 19:36
@mttdnt mttdnt requested a review from a team as a code owner April 20, 2026 19:36
@mttdnt mttdnt requested review from mariuslundgard and removed request for a team April 20, 2026 19:36
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 20, 2026

Coverage Delta

File Statements
packages/@sanity/cli/src/actions/init/bootstrapTemplate.ts 0.0% (±0%)
packages/@sanity/cli/src/actions/init/initAction.ts 94.6% (new)
packages/@sanity/cli/src/actions/init/initApp.ts 96.4% (+ 0.4%)
packages/@sanity/cli/src/actions/init/initError.ts 100.0% (new)
packages/@sanity/cli/src/actions/init/initHelpers.ts 100.0% (±0%)
packages/@sanity/cli/src/actions/init/initNextJs.ts 86.7% (- 3.1%)
packages/@sanity/cli/src/actions/init/initStudio.ts 87.0% (+ 0.6%)
packages/@sanity/cli/src/actions/init/scaffoldTemplate.ts 92.0% (+ 9.9%)
packages/@sanity/cli/src/actions/init/types.ts 100.0% (new)
packages/@sanity/cli/src/commands/init.ts 100.0% (+ 4.9%)
packages/@sanity/cli/src/telemetry/init.telemetry.ts 100.0% (±0%)

Comparing 11 changed files against main @ 511eb5cfbe7691b4b3153c480ba832b7aae55f78

Overall Coverage

Metric Coverage
Statements 84.0% (+ 0.0%)
Branches 73.8% (+ 0.0%)
Functions 83.9% (+ 0.0%)
Lines 84.4% (+ 0.0%)

@mttdnt mttdnt requested a review from binoy14 April 20, 2026 19:51
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>
@mttdnt mttdnt force-pushed the refactor/init-action-pattern branch from ef073f3 to 03deba9 Compare April 21, 2026 21:29
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 3 potential issues.

Fix All in Cursor

❌ 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`,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Fix in Cursor Fix in Web

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()) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 03deba9. Configure here.

organizations: [],
planId,
user,
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 03deba9. Configure here.

@mttdnt mttdnt closed this Apr 22, 2026
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