Skip to content

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

Open
rexxars wants to merge 13 commits intobundled-createfrom
refactor/extract-init-cmd-to-action
Open

refactor(init): move init logic to action pattern#748
rexxars wants to merge 13 commits intobundled-createfrom
refactor/extract-init-cmd-to-action

Conversation

@rexxars
Copy link
Member

@rexxars rexxars commented Mar 19, 2026

Description

Note

This is a PR that goes into the bundled-create branch - not main. We want to do a series of proper tests as these changes are rather large. And so we need to do a full alpha release to try this out, but I still want to get your opinions and feedback on the refactor one step at a time. This is part 1 of a larger init/create-sanity refactor - the goal being to ship a much smaller create-sanity module, while still allowing sanity init to work as before.

This moves the init command logic out of the actual oclif "command" and into an action. The rationale behind this is two-fold:

  1. The init command flow is currently very hard to follow - too many concerns - app init, studio init, template init, remote template init, next.js app init… By moving the logic into a separate "action" space, it will be easier to break these individual flows apart and create reusable utils across them. This will be in a follow-up PR.
  2. I want to make the create-sanity command not rely on @sanity/cli in its entirety, but instead ship only the parts that we need for the init portion. By relying on oclif internals, this is harder to do: ideally (since init is a single command) we could not rely on the entire oclif framework for it but instead parse args and call the action logic directly, then post create we have the entire CLI available for use. For this to work, we need the init logic "core" to be tree-shakeable and use the minimal amount of dependencies for those operations, so we can bundle it all into one file and ship create-sanity without dependencies.

What to review

This is quite a big change and it is almost unreasonable to manually review the move from the commands/init.ts to actions/* - this was all done my opus and I'm having to rely on manual and automatic tests here.

rexxars and others added 8 commits March 19, 2026 14:45
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move all business logic from the oclif InitCommand class into a pure
initAction(options, context) function. InitCommand becomes a thin
wrapper that parses flags and delegates. This enables the standalone
create-sanity bundling planned in Phase 3.

- Created initAction.ts with all extracted private methods as
  module-level functions
- Added argType and reconfigure fields to InitOptions for argument
  and deprecated flag validation
- Updated flagsToInitOptions to accept oclif args parameter
- Made bootstrapTemplate accept optional overwriteFiles and
  useTypeScript to match pre-existing runtime behavior
- InitCommand.run() now catches InitError and translates to oclif
  error/exit calls

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Use dynamic import for ImportDatasetCommand to avoid pulling oclif
  into the initAction bundle
- Replace process.cwd() with workDir from context in two spots
- Fix JSDoc brace escaping in initError.ts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add 3 focused tests that call initAction() directly with plain objects,
validating the extraction works standalone without going through oclif.
This provides a foundation for the future create-sanity entry point.

Tests cover:
- Deprecated reconfigure flag throws InitError
- Bare mode outputs project details and returns cleanly
- Unauthenticated unattended mode throws InitError

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The ImportDatasetCommand dynamic import is deliberate to keep initAction
decoupled from oclif. Will be replaced with npx spawn in Phase 3.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@claude
Copy link

claude bot commented Mar 19, 2026

Claude finished @rexxars's task in 3m 13s —— View job


Code Review

  • Gather context
  • Read changed files
  • Identify issues
  • Post review

1. bun excluded from --package-manager flag and InitOptions type

init.ts:147 restricts --package-manager to ['npm', 'yarn', 'pnpm'], but bun is a fully supported PackageManager — it's auto-detected, appears in devCommandMap, and has its own case 'bun' branch in doInitNextJs. Users can't pass --package-manager bun because oclif will reject it.

Additionally, types.ts:52 defines packageManager?: 'npm' | 'pnpm' | 'yarn'bun is missing here too, creating a type inconsistency with PackageManager from packageManagerChoice.ts.

Fix: add 'bun' to the flag's options array and to InitOptions['packageManager']. Fix this →


2. ImportDatasetCommand dynamic import still couples initAction to oclif

initAction.ts:442:

const {ImportDatasetCommand} = await import('../../commands/datasets/import.js')
await ImportDatasetCommand.run([...], {root: outputPath})

The stated goal of this refactor is to decouple initAction from oclif so it can be bundled independently for create-sanity. This import pulls in an oclif command, defeating that goal for the dataset-import path. The TODO comment acknowledges this, but it's worth calling out explicitly since it's in the critical path for templates that have sample datasets (e.g. moviedb).


3. Silent 'Unknown project' fallthrough in unattended mode

initAction.ts:828-841: when a user specifies --project <id> in unattended mode and the project ID is not in the user's project list, the action continues silently with displayName: 'Unknown project' rather than erroring. Downstream API calls (dataset creation, etc.) will then fail with raw HTTP errors rather than a clean InitError.

Consider throwing when the project isn't found in unattended mode:

if (!proj) {
  throw new InitError(
    `Given project ID (${projectId}) not found, or you do not have access to it`,
    1,
  )
}

The existing catch block at lines 806-821 already handles the case where the API call itself fails, so this wouldn't regress offline/error-tolerant scenarios.

@github-actions
Copy link

github-actions bot commented Mar 19, 2026

Coverage Delta

File Statements
packages/@sanity/cli/src/actions/init/bootstrapTemplate.ts 0.0% (±0%)
packages/@sanity/cli/src/actions/init/initAction.ts 92.2% (new)
packages/@sanity/cli/src/actions/init/initError.ts 100.0% (new)
packages/@sanity/cli/src/actions/init/types.ts 100.0% (new)
packages/@sanity/cli/src/commands/init.ts 100.0% (+ 7.4%)
packages/@sanity/cli/src/telemetry/init.telemetry.ts 100.0% (±0%)

Comparing 6 changed files against main @ c289c169e7f7020f4722d2db52338dc4327d84c8

Overall Coverage

Metric Coverage
Statements 81.6% (+ 0.0%)
Branches 70.7% (+ 0.0%)
Functions 80.0% (+ 0.1%)
Lines 82.1% (+ 0.0%)

rexxars and others added 5 commits March 19, 2026 16:16
- Wrap getCliUser() in try-catch after login, re-throw as InitError
- Replace throw-as-control-flow (InitError with exitCode 0) with plain
  return - both the --env path and doInitNextJs now return normally
  instead of using exceptions for successful early exits
- Fix missing .js extension on type-only import in types.ts
- Remove dead exitCode === 0 handling from InitCommand

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add trace.complete() to all early-return paths (bare mode, Next.js
  init, env-only mode) so telemetry is not silently dropped
- Add explicit return undefined in verifyCoupon/verifyPlan when
  falling through to default plan, instead of implicit undefined
- Add init.command.test.ts covering mcpMode computation and error
  handling in InitCommand.run(), restoring 100% coverage

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The visibility field is `'private' | 'public' | undefined` but was
cast to `'private' | 'public'`, silently hiding the undefined case.
Make the telemetry type optional to match reality and drop the cast.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove no-op try/catch around bootstrapTemplate that only re-threw
- Add missing bun case for next-sanity@11 installation in Next.js flow

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace redundant detectedFramework.slug check with existing isNextJs
- Extract error message properly in CORS origin error to avoid [object Object]

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@rexxars rexxars changed the base branch from main to bundled-create March 21, 2026 17:00
@rexxars rexxars marked this pull request as ready for review March 21, 2026 17:45
@rexxars rexxars requested a review from a team as a code owner March 21, 2026 17:45
@rexxars rexxars requested review from binoy14, colepeters and mttdnt and removed request for a team and colepeters March 21, 2026 17:45
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