@sanity/cli 6.3.1 → 6.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (193) hide show
  1. package/README.md +311 -452
  2. package/dist/actions/build/decorateIndexWithStagingScript.js +16 -0
  3. package/dist/actions/build/decorateIndexWithStagingScript.js.map +1 -0
  4. package/dist/actions/build/writeSanityRuntime.js +3 -2
  5. package/dist/actions/build/writeSanityRuntime.js.map +1 -1
  6. package/dist/actions/dataset/create.js +4 -0
  7. package/dist/actions/dataset/create.js.map +1 -1
  8. package/dist/actions/deploy/findUserApplicationForApp.js +1 -0
  9. package/dist/actions/deploy/findUserApplicationForApp.js.map +1 -1
  10. package/dist/actions/deploy/types.js +1 -1
  11. package/dist/actions/deploy/types.js.map +1 -1
  12. package/dist/actions/init/bootstrapLocalTemplate.js +16 -1
  13. package/dist/actions/init/bootstrapLocalTemplate.js.map +1 -1
  14. package/dist/actions/init/initApp.js +72 -0
  15. package/dist/actions/init/initApp.js.map +1 -0
  16. package/dist/actions/init/initHelpers.js +37 -0
  17. package/dist/actions/init/initHelpers.js.map +1 -0
  18. package/dist/actions/init/initNextJs.js +246 -0
  19. package/dist/actions/init/initNextJs.js.map +1 -0
  20. package/dist/actions/init/initStudio.js +127 -0
  21. package/dist/actions/init/initStudio.js.map +1 -0
  22. package/dist/actions/init/scaffoldTemplate.js +114 -0
  23. package/dist/actions/init/scaffoldTemplate.js.map +1 -0
  24. package/dist/actions/init/templates/appQuickstart.js +2 -1
  25. package/dist/actions/init/templates/appQuickstart.js.map +1 -1
  26. package/dist/actions/init/templates/appSanityUi.js +2 -1
  27. package/dist/actions/init/templates/appSanityUi.js.map +1 -1
  28. package/dist/actions/init/templates/nextjs/index.js +1 -2
  29. package/dist/actions/init/templates/nextjs/index.js.map +1 -1
  30. package/dist/actions/init/templates/shopify.js +6 -6
  31. package/dist/actions/init/templates/shopify.js.map +1 -1
  32. package/dist/actions/init/templates/shopifyOnline.js +2 -2
  33. package/dist/actions/init/templates/shopifyOnline.js.map +1 -1
  34. package/dist/actions/manifest/types.js +1 -1
  35. package/dist/actions/manifest/types.js.map +1 -1
  36. package/dist/actions/mcp/detectAvailableEditors.js +16 -3
  37. package/dist/actions/mcp/detectAvailableEditors.js.map +1 -1
  38. package/dist/actions/mcp/editorConfigs.js +192 -132
  39. package/dist/actions/mcp/editorConfigs.js.map +1 -1
  40. package/dist/actions/mcp/setupMCP.js +4 -1
  41. package/dist/actions/mcp/setupMCP.js.map +1 -1
  42. package/dist/actions/mcp/writeMCPConfig.js +2 -2
  43. package/dist/actions/mcp/writeMCPConfig.js.map +1 -1
  44. package/dist/actions/schema/extractSchema.js +5 -7
  45. package/dist/actions/schema/extractSchema.js.map +1 -1
  46. package/dist/actions/schema/types.js +3 -3
  47. package/dist/actions/schema/types.js.map +1 -1
  48. package/dist/actions/users/validateEmail.js +2 -2
  49. package/dist/actions/users/validateEmail.js.map +1 -1
  50. package/dist/commands/backups/disable.js +1 -1
  51. package/dist/commands/backups/disable.js.map +1 -1
  52. package/dist/commands/backups/download.js +1 -1
  53. package/dist/commands/backups/download.js.map +1 -1
  54. package/dist/commands/backups/enable.js +1 -1
  55. package/dist/commands/backups/enable.js.map +1 -1
  56. package/dist/commands/backups/list.js +1 -1
  57. package/dist/commands/backups/list.js.map +1 -1
  58. package/dist/commands/build.js +1 -1
  59. package/dist/commands/build.js.map +1 -1
  60. package/dist/commands/cors/add.js +1 -1
  61. package/dist/commands/cors/add.js.map +1 -1
  62. package/dist/commands/cors/delete.js +1 -1
  63. package/dist/commands/cors/delete.js.map +1 -1
  64. package/dist/commands/cors/list.js +2 -2
  65. package/dist/commands/cors/list.js.map +1 -1
  66. package/dist/commands/datasets/alias/create.js +1 -1
  67. package/dist/commands/datasets/alias/create.js.map +1 -1
  68. package/dist/commands/datasets/alias/delete.js +1 -1
  69. package/dist/commands/datasets/alias/delete.js.map +1 -1
  70. package/dist/commands/datasets/alias/link.js +1 -1
  71. package/dist/commands/datasets/alias/link.js.map +1 -1
  72. package/dist/commands/datasets/alias/unlink.js +1 -1
  73. package/dist/commands/datasets/alias/unlink.js.map +1 -1
  74. package/dist/commands/datasets/copy.js +15 -1
  75. package/dist/commands/datasets/copy.js.map +1 -1
  76. package/dist/commands/datasets/create.js +1 -1
  77. package/dist/commands/datasets/create.js.map +1 -1
  78. package/dist/commands/datasets/delete.js +1 -1
  79. package/dist/commands/datasets/delete.js.map +1 -1
  80. package/dist/commands/datasets/embeddings/enable.js +11 -0
  81. package/dist/commands/datasets/embeddings/enable.js.map +1 -1
  82. package/dist/commands/datasets/export.js +2 -2
  83. package/dist/commands/datasets/export.js.map +1 -1
  84. package/dist/commands/datasets/list.js +2 -2
  85. package/dist/commands/datasets/list.js.map +1 -1
  86. package/dist/commands/debug.js +1 -1
  87. package/dist/commands/debug.js.map +1 -1
  88. package/dist/commands/deploy.js +3 -3
  89. package/dist/commands/deploy.js.map +1 -1
  90. package/dist/commands/dev.js +5 -5
  91. package/dist/commands/dev.js.map +1 -1
  92. package/dist/commands/docs/browse.js +1 -1
  93. package/dist/commands/docs/browse.js.map +1 -1
  94. package/dist/commands/documents/delete.js +1 -1
  95. package/dist/commands/documents/delete.js.map +1 -1
  96. package/dist/commands/exec.js +2 -2
  97. package/dist/commands/exec.js.map +1 -1
  98. package/dist/commands/graphql/deploy.js +2 -2
  99. package/dist/commands/graphql/deploy.js.map +1 -1
  100. package/dist/commands/graphql/list.js +2 -2
  101. package/dist/commands/graphql/list.js.map +1 -1
  102. package/dist/commands/hooks/create.js +2 -2
  103. package/dist/commands/hooks/create.js.map +1 -1
  104. package/dist/commands/hooks/delete.js +5 -5
  105. package/dist/commands/hooks/delete.js.map +1 -1
  106. package/dist/commands/hooks/list.js +3 -3
  107. package/dist/commands/hooks/list.js.map +1 -1
  108. package/dist/commands/hooks/logs.js +5 -5
  109. package/dist/commands/hooks/logs.js.map +1 -1
  110. package/dist/commands/init.js +175 -490
  111. package/dist/commands/init.js.map +1 -1
  112. package/dist/commands/install.js +1 -1
  113. package/dist/commands/install.js.map +1 -1
  114. package/dist/commands/learn.js +1 -1
  115. package/dist/commands/learn.js.map +1 -1
  116. package/dist/commands/login.js +1 -1
  117. package/dist/commands/login.js.map +1 -1
  118. package/dist/commands/logout.js +1 -1
  119. package/dist/commands/logout.js.map +1 -1
  120. package/dist/commands/manage.js +1 -1
  121. package/dist/commands/manage.js.map +1 -1
  122. package/dist/commands/manifest/extract.js +2 -2
  123. package/dist/commands/manifest/extract.js.map +1 -1
  124. package/dist/commands/mcp/configure.js +1 -1
  125. package/dist/commands/mcp/configure.js.map +1 -1
  126. package/dist/commands/media/delete-aspect.js +1 -1
  127. package/dist/commands/media/delete-aspect.js.map +1 -1
  128. package/dist/commands/media/export.js +1 -1
  129. package/dist/commands/media/export.js.map +1 -1
  130. package/dist/commands/preview.js +3 -3
  131. package/dist/commands/preview.js.map +1 -1
  132. package/dist/commands/projects/list.js +4 -2
  133. package/dist/commands/projects/list.js.map +1 -1
  134. package/dist/commands/schemas/deploy.js +3 -4
  135. package/dist/commands/schemas/deploy.js.map +1 -1
  136. package/dist/commands/schemas/extract.js +3 -3
  137. package/dist/commands/schemas/extract.js.map +1 -1
  138. package/dist/commands/schemas/list.js +4 -5
  139. package/dist/commands/schemas/list.js.map +1 -1
  140. package/dist/commands/telemetry/disable.js +2 -2
  141. package/dist/commands/telemetry/disable.js.map +1 -1
  142. package/dist/commands/telemetry/enable.js +2 -2
  143. package/dist/commands/telemetry/enable.js.map +1 -1
  144. package/dist/commands/telemetry/status.js +2 -2
  145. package/dist/commands/telemetry/status.js.map +1 -1
  146. package/dist/commands/tokens/add.js +1 -1
  147. package/dist/commands/tokens/add.js.map +1 -1
  148. package/dist/commands/tokens/delete.js +1 -1
  149. package/dist/commands/tokens/delete.js.map +1 -1
  150. package/dist/commands/tokens/list.js +2 -2
  151. package/dist/commands/tokens/list.js.map +1 -1
  152. package/dist/commands/users/list.js +1 -1
  153. package/dist/commands/users/list.js.map +1 -1
  154. package/dist/commands/versions.js +1 -1
  155. package/dist/commands/versions.js.map +1 -1
  156. package/dist/hooks/prerun/injectEnvVariables.js +3 -5
  157. package/dist/hooks/prerun/injectEnvVariables.js.map +1 -1
  158. package/dist/server/vite/plugin-sanity-build-entries.js +3 -2
  159. package/dist/server/vite/plugin-sanity-build-entries.js.map +1 -1
  160. package/dist/services/datasets.js +2 -1
  161. package/dist/services/datasets.js.map +1 -1
  162. package/dist/telemetry/init.telemetry.js.map +1 -1
  163. package/dist/util/packageManager/installationInfo/detectPackages.js +13 -7
  164. package/dist/util/packageManager/installationInfo/detectPackages.js.map +1 -1
  165. package/dist/util/telemetry/createTelemetryStore.js +27 -12
  166. package/dist/util/telemetry/createTelemetryStore.js.map +1 -1
  167. package/dist/util/update/fetchUpdateInfo.js +40 -0
  168. package/dist/util/update/fetchUpdateInfo.js.map +1 -0
  169. package/dist/util/update/fetchUpdateInfo.worker.js +19 -0
  170. package/dist/util/update/fetchUpdateInfo.worker.js.map +1 -0
  171. package/dist/util/update/getRunnerUpdateCommand.js +33 -0
  172. package/dist/util/update/getRunnerUpdateCommand.js.map +1 -0
  173. package/dist/util/update/getUpdateCommand.js +6 -7
  174. package/dist/util/update/getUpdateCommand.js.map +1 -1
  175. package/dist/util/update/packageRunner.js +10 -0
  176. package/dist/util/update/packageRunner.js.map +1 -0
  177. package/dist/util/update/resolveRunnerPackage.js +45 -0
  178. package/dist/util/update/resolveRunnerPackage.js.map +1 -0
  179. package/dist/util/update/resolveUpdateTarget.js +31 -0
  180. package/dist/util/update/resolveUpdateTarget.js.map +1 -0
  181. package/dist/util/update/showNotificationUpdate.js +8 -6
  182. package/dist/util/update/showNotificationUpdate.js.map +1 -1
  183. package/dist/util/update/updateChecker.js +73 -38
  184. package/dist/util/update/updateChecker.js.map +1 -1
  185. package/dist/util/validateProjection.js +121 -0
  186. package/dist/util/validateProjection.js.map +1 -0
  187. package/oclif.manifest.json +698 -681
  188. package/package.json +24 -23
  189. package/templates/app-quickstart/src/App.tsx +2 -2
  190. package/templates/app-sanity-ui/src/App.tsx +2 -2
  191. package/templates/shopify/schemaTypes/objects/hotspot/imageWithProductHotspotsType.ts +1 -1
  192. package/dist/util/update/fetchLatestVersion.js +0 -21
  193. package/dist/util/update/fetchLatestVersion.js.map +0 -1
@@ -1,56 +1,44 @@
1
- import { existsSync } from 'node:fs';
2
- import { mkdir, writeFile } from 'node:fs/promises';
3
1
  import path from 'node:path';
4
2
  import { styleText } from 'node:util';
5
3
  import { Args, Flags } from '@oclif/core';
6
4
  import { CLIError } from '@oclif/core/errors';
7
- import { getCliToken, SanityCommand, subdebug } from '@sanity/cli-core';
5
+ import { SanityCommand, subdebug } from '@sanity/cli-core';
8
6
  import { confirm, input, logSymbols, select, Separator, spinner } from '@sanity/cli-core/ux';
9
7
  import { isHttpError } from '@sanity/client';
10
8
  import { frameworks } from '@vercel/frameworks';
11
- import { execa } from 'execa';
12
9
  import deburr from 'lodash-es/deburr.js';
13
10
  import { validateSession } from '../actions/auth/ensureAuthenticated.js';
14
11
  import { getProviderName } from '../actions/auth/getProviderName.js';
15
12
  import { login } from '../actions/auth/login/login.js';
16
13
  import { createDataset } from '../actions/dataset/create.js';
17
- import { bootstrapTemplate } from '../actions/init/bootstrapTemplate.js';
18
14
  import { checkNextJsReactCompatibility } from '../actions/init/checkNextJsReactCompatibility.js';
19
- import { countNestedFolders } from '../actions/init/countNestedFolders.js';
20
15
  import { determineAppTemplate } from '../actions/init/determineAppTemplate.js';
21
16
  import { createOrAppendEnvVars } from '../actions/init/env/createOrAppendEnvVars.js';
22
- import { fetchPostInitPrompt } from '../actions/init/fetchPostInitPrompt.js';
23
- import { tryGitInit } from '../actions/init/git.js';
17
+ import { initApp } from '../actions/init/initApp.js';
18
+ import { flagOrDefault, shouldPrompt, writeStagingEnvIfNeeded } from '../actions/init/initHelpers.js';
19
+ import { initNextJs } from '../actions/init/initNextJs.js';
20
+ import { initStudio } from '../actions/init/initStudio.js';
24
21
  import { checkIsRemoteTemplate, getGitHubRepoInfo } from '../actions/init/remoteTemplate.js';
25
- import { resolvePackageManager } from '../actions/init/resolvePackageManager.js';
26
- import templates from '../actions/init/templates/index.js';
27
- import { sanityCliTemplate, sanityConfigTemplate, sanityFolder, sanityStudioTemplate } from '../actions/init/templates/nextjs/index.js';
28
22
  import { setupMCP } from '../actions/mcp/setupMCP.js';
29
23
  import { findOrganizationByUserName } from '../actions/organizations/findOrganizationByUserName.js';
30
24
  import { getOrganizationChoices } from '../actions/organizations/getOrganizationChoices.js';
31
25
  import { getOrganizationsWithAttachGrantInfo } from '../actions/organizations/getOrganizationsWithAttachGrantInfo.js';
32
26
  import { hasProjectAttachGrant } from '../actions/organizations/hasProjectAttachGrant.js';
33
- import { promptForAppendEnv, promptForConfigFiles, promptForEmbeddedStudio, promptForNextTemplate, promptForStudioPath } from '../prompts/init/nextjs.js';
34
- import { promptForTypeScript } from '../prompts/init/promptForTypescript.js';
27
+ import { promptForConfigFiles } from '../prompts/init/nextjs.js';
35
28
  import { promptForDatasetName } from '../prompts/promptForDatasetName.js';
36
29
  import { promptForDefaultConfig } from '../prompts/promptForDefaultConfig.js';
37
30
  import { promptForOrganizationName } from '../prompts/promptForOrganizationName.js';
38
- import { createCorsOrigin, listCorsOrigins } from '../services/cors.js';
39
31
  import { createDataset as createDatasetService, listDatasets } from '../services/datasets.js';
40
32
  import { getProjectFeatures } from '../services/getProjectFeatures.js';
41
33
  import { createOrganization, listOrganizations } from '../services/organizations.js';
42
34
  import { getPlanId, getPlanIdFromCoupon } from '../services/plans.js';
43
- import { createProject, listProjects, updateProjectInitializedAt } from '../services/projects.js';
35
+ import { createProject, listProjects } from '../services/projects.js';
44
36
  import { getCliUser } from '../services/user.js';
45
37
  import { CLIInitStepCompleted } from '../telemetry/init.telemetry.js';
46
38
  import { detectFrameworkRecord } from '../util/detectFramework.js';
47
39
  import { absolutify, validateEmptyPath } from '../util/fsUtils.js';
48
40
  import { getProjectDefaults } from '../util/getProjectDefaults.js';
49
41
  import { getSanityEnv } from '../util/getSanityEnv.js';
50
- import { getPeerDependencies } from '../util/packageManager/getPeerDependencies.js';
51
- import { installDeclaredPackages, installNewPackages } from '../util/packageManager/installPackages.js';
52
- import { getPartialEnvWithNpmPath } from '../util/packageManager/packageManagerChoice.js';
53
- import { ImportDatasetCommand } from './datasets/import.js';
54
42
  const debug = subdebug('init');
55
43
  export class InitCommand extends SanityCommand {
56
44
  static args = {
@@ -395,8 +383,8 @@ export class InitCommand extends SanityCommand {
395
383
  this.log(`\nYou can find your project on Sanity Manage — https://www.sanity.io/manage/project/${projectId}\n`);
396
384
  return;
397
385
  }
398
- let initNext = this.flagOrDefault('nextjs-add-config-files', false);
399
- if (isNextJs && this.promptForUndefinedFlag(this.flags['nextjs-add-config-files'])) {
386
+ let initNext = flagOrDefault(this.flags['nextjs-add-config-files'], false);
387
+ if (isNextJs && shouldPrompt(this.isUnattended(), this.flags['nextjs-add-config-files'])) {
400
388
  initNext = await promptForConfigFiles();
401
389
  }
402
390
  this._trace.log({
@@ -419,9 +407,9 @@ export class InitCommand extends SanityCommand {
419
407
  sluggedName,
420
408
  workDir
421
409
  });
422
- // Set up MCP integration
410
+ // Set up MCP integration (skip in non-production environments)
423
411
  let mcpMode = 'prompt';
424
- if (!this.flags.mcp || !this.resolveIsInteractive()) {
412
+ if (!this.flags.mcp || !this.resolveIsInteractive() || getSanityEnv() !== 'production') {
425
413
  mcpMode = 'skip';
426
414
  } else if (this.flags.yes) {
427
415
  mcpMode = 'auto';
@@ -453,14 +441,25 @@ export class InitCommand extends SanityCommand {
453
441
  });
454
442
  }
455
443
  if (initNext) {
456
- await this.initNextJs({
444
+ await initNextJs({
457
445
  datasetName,
458
446
  detectedFramework,
459
447
  envFilename,
460
448
  mcpConfigured,
449
+ nextjsAppendEnv: this.flags['nextjs-append-env'],
450
+ nextjsEmbedStudio: this.flags['nextjs-embed-studio'],
451
+ output: this.output,
452
+ overwriteFiles: this.flags['overwrite-files'],
453
+ packageManager: this.flags['package-manager'],
461
454
  projectId,
455
+ template: this.flags.template,
456
+ trace: this._trace,
457
+ typescript: this.flags.typescript,
458
+ unattended: this.isUnattended(),
462
459
  workDir
463
460
  });
461
+ this._trace.complete();
462
+ return;
464
463
  }
465
464
  // user wants to write environment variables to file
466
465
  if (this.flags.env) {
@@ -475,178 +474,42 @@ export class InitCommand extends SanityCommand {
475
474
  output: this.output,
476
475
  outputPath
477
476
  });
478
- await this.writeStagingEnvIfNeeded(outputPath);
477
+ await writeStagingEnvIfNeeded(this.output, outputPath);
479
478
  this.exit(0);
480
479
  }
481
- // Prompt for template to use
482
- const templateName = await this.promptForTemplate();
483
- this._trace.log({
484
- selectedOption: templateName,
485
- step: 'selectProjectTemplate'
486
- });
487
- const template = templates[templateName];
488
- if (!remoteTemplateInfo && !template) {
489
- this.error(`Template "${templateName}" not found`, {
490
- exit: 1
491
- });
492
- }
493
- let useTypeScript = this.flags.typescript;
494
- if (!remoteTemplateInfo && template && template.typescriptOnly === true) {
495
- useTypeScript = true;
496
- } else if (this.promptForUndefinedFlag(this.flags.typescript)) {
497
- useTypeScript = await promptForTypeScript();
498
- this._trace.log({
499
- selectedOption: useTypeScript ? 'yes' : 'no',
500
- step: 'useTypeScript'
501
- });
502
- }
503
- // If the template has a sample dataset, prompt the user whether or not we should import it
504
- const importDatasetFlag = this.flags['import-dataset'];
505
- const shouldImport = template?.datasetUrl && (importDatasetFlag ?? (!this.isUnattended() && await this.promptForDatasetImport(template.importPrompt)));
506
- this._trace.log({
507
- selectedOption: shouldImport ? 'yes' : 'no',
508
- step: 'importTemplateDataset'
509
- });
510
- try {
511
- await updateProjectInitializedAt(projectId);
512
- } catch (err) {
513
- // Non-critical update
514
- debug('Failed to update cliInitializedAt metadata', err);
515
- }
516
- try {
517
- await bootstrapTemplate({
518
- autoUpdates: this.flags['auto-updates'],
519
- bearerToken: this.flags['template-token'],
520
- dataset: datasetName,
521
- organizationId,
522
- output: this.output,
523
- outputPath,
524
- overwriteFiles: this.flags['overwrite-files'],
525
- packageName: sluggedName,
526
- projectId,
527
- projectName: displayName || defaults.projectName,
528
- remoteTemplateInfo,
529
- templateName,
530
- useTypeScript
531
- });
532
- } catch (error) {
533
- if (error instanceof Error) {
534
- throw error;
535
- }
536
- throw new Error(String(error), {
537
- cause: error
538
- });
539
- }
540
- const pkgManager = await resolvePackageManager({
541
- interactive: !this.isUnattended(),
480
+ const sharedParams = {
481
+ autoUpdates: this.flags['auto-updates'],
482
+ defaults,
483
+ error: this.error.bind(this),
484
+ git: this.flags.git,
485
+ mcpConfigured,
486
+ noGit: this.flags['no-git'],
487
+ organizationId,
542
488
  output: this.output,
489
+ outputPath,
490
+ overwriteFiles: this.flags['overwrite-files'],
543
491
  packageManager: this.flags['package-manager'],
544
- targetDir: outputPath
545
- });
546
- this._trace.log({
547
- selectedOption: pkgManager,
548
- step: 'selectPackageManager'
549
- });
550
- // Now for the slow part... installing dependencies
551
- await installDeclaredPackages(outputPath, pkgManager, {
552
- output: this.output,
492
+ remoteTemplateInfo,
493
+ sluggedName,
494
+ template: this.flags.template,
495
+ templateToken: this.flags['template-token'],
496
+ trace: this._trace,
497
+ typescript: this.flags.typescript,
498
+ unattended: this.isUnattended(),
553
499
  workDir
554
- });
555
- const useGit = this.flags.git === undefined || Boolean(this.flags.git);
556
- const commitMessage = this.flags.git;
557
- await this.writeStagingEnvIfNeeded(outputPath);
558
- // Try initializing a git repository
559
- if (useGit) {
560
- tryGitInit(outputPath, typeof commitMessage === 'string' ? commitMessage : undefined);
561
- }
562
- // Prompt for dataset import (if a dataset is defined)
563
- if (shouldImport && template?.datasetUrl) {
564
- const token = await getCliToken();
565
- if (!token) {
566
- this.error('Authentication required to import dataset', {
567
- exit: 1
568
- });
569
- }
570
- await ImportDatasetCommand.run([
571
- template.datasetUrl,
572
- '--project-id',
573
- projectId,
574
- '--dataset',
575
- datasetName,
576
- '--token',
577
- token
578
- ], {
579
- root: outputPath
580
- });
581
- this.log('');
582
- this.log('If you want to delete the imported data, use');
583
- this.log(` ${styleText('cyan', `npx sanity dataset delete ${datasetName}`)}`);
584
- this.log('and create a new clean dataset with');
585
- this.log(` ${styleText('cyan', `npx sanity dataset create <name>`)}\n`);
586
- }
587
- const devCommandMap = {
588
- bun: 'bun dev',
589
- manual: 'npm run dev',
590
- npm: 'npm run dev',
591
- pnpm: 'pnpm dev',
592
- yarn: 'yarn dev'
593
500
  };
594
- const devCommand = devCommandMap[pkgManager];
595
- const isCurrentDir = outputPath === process.cwd();
596
- const goToProjectDir = `\n(${styleText('cyan', `cd ${outputPath}`)} to navigate to your new project directory)`;
597
- if (isAppTemplate) {
598
- //output for custom apps here
599
- this.log(`${logSymbols.success} ${styleText([
600
- 'green',
601
- 'bold'
602
- ], 'Success!')} Your custom app has been scaffolded.`);
603
- if (!isCurrentDir) this.log(goToProjectDir);
604
- this.log(`\n${styleText('bold', 'Next')}, configure the project(s) and dataset(s) your app should work with.`);
605
- this.log('\nGet started in `src/App.tsx`, or refer to our documentation for a walkthrough:');
606
- this.log(styleText([
607
- 'blue',
608
- 'underline'
609
- ], 'https://www.sanity.io/docs/app-sdk/sdk-configuration'));
610
- if (mcpConfigured && mcpConfigured.length > 0) {
611
- const message = await this.getPostInitMCPPrompt(mcpConfigured);
612
- this.log(`\n${message}`);
613
- this.log(`\nLearn more: ${styleText('cyan', 'https://mcp.sanity.io')}`);
614
- this.log(`\nHave feedback? Tell us in the community: ${styleText('cyan', 'https://www.sanity.io/community/join')}`);
615
- }
616
- this.log('\n');
617
- this.log(`Other helpful commands:`);
618
- this.log(`npx sanity docs browse to open the documentation in a browser`);
619
- this.log(`npx sanity dev to start the development server for your app`);
620
- this.log(`npx sanity deploy to deploy your app`);
621
- } else {
622
- //output for Studios here
623
- this.log(`✅ ${styleText([
624
- 'green',
625
- 'bold'
626
- ], 'Success!')} Your Studio has been created.`);
627
- if (!isCurrentDir) this.log(goToProjectDir);
628
- this.log(`\nGet started by running ${styleText('cyan', devCommand)} to launch your Studio's development server`);
629
- if (mcpConfigured && mcpConfigured.length > 0) {
630
- const message = await this.getPostInitMCPPrompt(mcpConfigured);
631
- this.log(`\n${message}`);
632
- this.log(`\nLearn more: ${styleText('cyan', 'https://mcp.sanity.io')}`);
633
- this.log(`\nHave feedback? Tell us in the community: ${styleText('cyan', 'https://www.sanity.io/community/join')}`);
634
- }
635
- this.log('\n');
636
- this.log(`Other helpful commands:`);
637
- this.log(`npx sanity docs browse to open the documentation in a browser`);
638
- this.log(`npx sanity manage to open the project settings in a browser`);
639
- this.log(`npx sanity help to explore the CLI manual`);
640
- }
641
- if (isFirstProject) {
642
- this._trace.log({
643
- selectedOption: 'yes',
644
- step: 'sendCommunityInvite'
645
- });
646
- const DISCORD_INVITE_LINK = 'https://www.sanity.io/community/join';
647
- this.log(`\nJoin the Sanity community: ${styleText('cyan', DISCORD_INVITE_LINK)}`);
648
- this.log('We look forward to seeing you there!\n');
649
- }
501
+ await (isAppTemplate ? initApp({
502
+ ...sharedParams,
503
+ datasetName,
504
+ projectId
505
+ }) : initStudio({
506
+ ...sharedParams,
507
+ datasetName,
508
+ displayName,
509
+ importDataset: this.flags['import-dataset'],
510
+ isFirstProject,
511
+ projectId
512
+ }));
650
513
  this._trace.complete();
651
514
  }
652
515
  checkFlagsInUnattendedMode({ createProjectName, isAppTemplate, isNextJs }) {
@@ -665,13 +528,8 @@ export class InitCommand extends SanityCommand {
665
528
  }
666
529
  return;
667
530
  }
668
- if (!this.flags['dataset']) {
669
- this.error(`\`--dataset\` must be specified in unattended mode`, {
670
- exit: 1
671
- });
672
- }
673
- // output-path is required in unattended mode when not using nextjs
674
- if (!isNextJs && !this.flags['output-path']) {
531
+ // output-path is required in unattended mode when not using nextjs or bare
532
+ if (!isNextJs && !this.flags.bare && !this.flags['output-path']) {
675
533
  this.error(`\`--output-path\` must be specified in unattended mode`, {
676
534
  exit: 1
677
535
  });
@@ -760,9 +618,6 @@ export class InitCommand extends SanityCommand {
760
618
  user: loggedInUser
761
619
  };
762
620
  }
763
- flagOrDefault(flag, defaultValue) {
764
- return typeof this.flags[flag] === 'boolean' ? this.flags[flag] : defaultValue;
765
- }
766
621
  async getOrCreateDataset(opts) {
767
622
  const visibility = this.flags.visibility;
768
623
  const dataset = this.flags.dataset;
@@ -796,6 +651,28 @@ export class InitCommand extends SanityCommand {
796
651
  userAction: 'none'
797
652
  };
798
653
  }
654
+ // In unattended mode without --dataset, default to "production" with public visibility
655
+ // (same behavior as --dataset-default)
656
+ if (this.isUnattended()) {
657
+ debug('Unattended mode without --dataset, defaulting to "production" dataset');
658
+ const datasetName = 'production';
659
+ const existing = datasets.find((ds)=>ds.name === datasetName);
660
+ if (!existing) {
661
+ await createDataset({
662
+ datasetName,
663
+ forcePublic: visibility === undefined,
664
+ isUnattended: true,
665
+ output: this.output,
666
+ projectFeatures,
667
+ projectId: opts.projectId,
668
+ visibility
669
+ });
670
+ }
671
+ return {
672
+ datasetName,
673
+ userAction: existing ? 'none' : 'create'
674
+ };
675
+ }
799
676
  if (datasets.length === 0) {
800
677
  debug('No datasets found for project, prompting for name');
801
678
  if (opts.showDefaultConfigPrompt) {
@@ -985,38 +862,36 @@ export class InitCommand extends SanityCommand {
985
862
  return undefined;
986
863
  }
987
864
  }
988
- async getPostInitMCPPrompt(editorsNames) {
989
- return fetchPostInitPrompt(new Intl.ListFormat('en').format(editorsNames));
990
- }
991
865
  async getProjectDetails({ isAppTemplate, newProject, planId, showDefaultConfigPrompt, user }) {
992
866
  if (isAppTemplate) {
993
- // If organization flag is provided, use it directly (skip prompt and API call)
994
- if (this.flags.organization) {
995
- return {
996
- datasetName: '',
997
- displayName: '',
998
- isFirstProject: false,
999
- organizationId: this.flags.organization,
1000
- projectId: ''
1001
- };
867
+ let organizationId = this.flags.organization;
868
+ if (!organizationId) {
869
+ let organizations;
870
+ try {
871
+ organizations = await listOrganizations();
872
+ } catch (err) {
873
+ this.error(`Failed to communicate with the Sanity API:\n${err.message}`, {
874
+ exit: 1
875
+ });
876
+ }
877
+ organizationId = await this.promptUserForOrganization({
878
+ isAppTemplate: true,
879
+ organizations,
880
+ user
881
+ });
1002
882
  }
1003
- // Interactive mode: fetch orgs and prompt
1004
- // Note: unattended mode without --organization is rejected by checkFlagsInUnattendedMode
1005
- const organizations = await listOrganizations({
1006
- includeImplicitMemberships: 'true',
1007
- includeMembers: 'true'
1008
- });
1009
- const appOrganizationId = await this.promptUserForOrganization({
1010
- isAppTemplate: true,
1011
- organizations,
883
+ const { datasetName, displayName, projectId } = await this.promptForAppTemplateSetup({
884
+ newProject,
885
+ organizationId,
886
+ planId,
1012
887
  user
1013
888
  });
1014
889
  return {
1015
- datasetName: '',
1016
- displayName: '',
890
+ datasetName,
891
+ displayName,
1017
892
  isFirstProject: false,
1018
- organizationId: appOrganizationId,
1019
- projectId: ''
893
+ organizationId,
894
+ projectId
1020
895
  };
1021
896
  }
1022
897
  debug('Prompting user to select or create a project');
@@ -1060,183 +935,90 @@ export class InitCommand extends SanityCommand {
1060
935
  });
1061
936
  return absolutify(inputPath);
1062
937
  }
1063
- async initNextJs({ datasetName, detectedFramework, envFilename, mcpConfigured, projectId, workDir }) {
1064
- let useTypeScript = this.flagOrDefault('typescript', true);
1065
- if (this.promptForUndefinedFlag(this.flags.typescript)) {
1066
- useTypeScript = await promptForTypeScript();
1067
- }
1068
- this._trace.log({
1069
- selectedOption: useTypeScript ? 'yes' : 'no',
1070
- step: 'useTypeScript'
1071
- });
1072
- const fileExtension = useTypeScript ? 'ts' : 'js';
1073
- let embeddedStudio = this.flagOrDefault('nextjs-embed-studio', true);
1074
- if (this.promptForUndefinedFlag(this.flags['nextjs-embed-studio'])) {
1075
- embeddedStudio = await promptForEmbeddedStudio();
1076
- }
1077
- let hasSrcFolder = false;
1078
- if (embeddedStudio) {
1079
- // find source path (app or src/app)
1080
- const appDir = 'app';
1081
- let srcPath = path.join(workDir, appDir);
1082
- if (!existsSync(srcPath)) {
1083
- srcPath = path.join(workDir, 'src', appDir);
1084
- hasSrcFolder = true;
1085
- if (!existsSync(srcPath)) {
1086
- try {
1087
- await mkdir(srcPath, {
1088
- recursive: true
1089
- });
1090
- } catch {
1091
- debug('Error creating folder %s', srcPath);
1092
- }
1093
- }
938
+ async promptForAppTemplateSetup({ newProject, organizationId, planId, user }) {
939
+ if (this.isUnattended()) {
940
+ if (!this.flags.project && !newProject) {
941
+ return {
942
+ datasetName: '',
943
+ displayName: '',
944
+ projectId: ''
945
+ };
1094
946
  }
1095
- const studioPath = this.isUnattended() ? '/studio' : await promptForStudioPath();
1096
- const embeddedStudioRouteFilePath = path.join(srcPath, `${studioPath}/`, `[[...tool]]/page.${fileExtension}x`);
1097
- // this selects the correct template string based on whether the user is using the app or pages directory and
1098
- // replaces the ":configPath:" placeholder in the template with the correct path to the sanity.config.ts file.
1099
- // we account for the user-defined embeddedStudioPath (default /studio) is accounted for by creating enough "../"
1100
- // relative paths to reach the root level of the project
1101
- await this.writeOrOverwrite(embeddedStudioRouteFilePath, sanityStudioTemplate.replace(':configPath:', `${'../'.repeat(countNestedFolders(embeddedStudioRouteFilePath.slice(workDir.length)))}sanity.config`), workDir);
1102
- const sanityConfigPath = path.join(workDir, `sanity.config.${fileExtension}`);
1103
- await this.writeOrOverwrite(sanityConfigPath, sanityConfigTemplate(hasSrcFolder).replace(':route:', embeddedStudioRouteFilePath.slice(workDir.length).replace('src/', '')).replace(':basePath:', studioPath), workDir);
1104
- }
1105
- const sanityCliPath = path.join(workDir, `sanity.cli.${fileExtension}`);
1106
- await this.writeOrOverwrite(sanityCliPath, sanityCliTemplate, workDir);
1107
- let templateToUse = this.flags.template ?? 'clean';
1108
- if (this.promptForUndefinedFlag(this.flags.template)) {
1109
- templateToUse = await promptForNextTemplate();
1110
- }
1111
- await this.writeSourceFiles({
1112
- fileExtension,
1113
- files: sanityFolder(useTypeScript, templateToUse),
1114
- folderPath: undefined,
1115
- srcFolderPrefix: hasSrcFolder,
1116
- workDir
1117
- });
1118
- let appendEnv = this.flagOrDefault('nextjs-append-env', true);
1119
- if (this.promptForUndefinedFlag(this.flags['nextjs-append-env'])) {
1120
- appendEnv = await promptForAppendEnv(envFilename);
947
+ const project = await this.getOrCreateProject({
948
+ newProject,
949
+ planId,
950
+ user
951
+ });
952
+ const dataset = await this.getOrCreateDataset({
953
+ displayName: project.displayName,
954
+ projectId: project.projectId,
955
+ showDefaultConfigPrompt: false
956
+ });
957
+ return {
958
+ datasetName: dataset.datasetName,
959
+ displayName: project.displayName,
960
+ projectId: project.projectId
961
+ };
1121
962
  }
1122
- if (appendEnv) {
1123
- await createOrAppendEnvVars({
1124
- envVars: {
1125
- DATASET: datasetName,
1126
- PROJECT_ID: projectId
963
+ const projects = (await listProjects()).toSorted((a, b)=>b.createdAt.localeCompare(a.createdAt));
964
+ const projectChoices = projects.map((project)=>({
965
+ name: `${project.displayName} (${project.id})`,
966
+ value: project.id
967
+ }));
968
+ const SKIP_PROJECT = '__skip__';
969
+ const NEW_PROJECT = '__new__';
970
+ const selected = await select({
971
+ choices: [
972
+ {
973
+ name: "Skip — I'll configure later",
974
+ value: SKIP_PROJECT
1127
975
  },
1128
- filename: envFilename,
1129
- framework: detectedFramework,
1130
- log: true,
1131
- output: this.output,
1132
- outputPath: workDir
976
+ {
977
+ name: 'Create new project',
978
+ value: NEW_PROJECT
979
+ },
980
+ ...projectChoices.length > 0 ? [
981
+ new Separator(),
982
+ ...projectChoices
983
+ ] : []
984
+ ],
985
+ message: 'Configure a project for this app?'
986
+ });
987
+ if (selected === SKIP_PROJECT) {
988
+ this._trace.log({
989
+ selectedOption: 'skip',
990
+ step: 'configureAppProject'
1133
991
  });
992
+ return {
993
+ datasetName: '',
994
+ displayName: '',
995
+ projectId: ''
996
+ };
1134
997
  }
1135
- if (embeddedStudio) {
1136
- const nextjsLocalDevOrigin = 'http://localhost:3000';
1137
- const existingCorsOrigins = await listCorsOrigins(projectId);
1138
- const hasExistingCorsOrigin = existingCorsOrigins.some((item)=>item.origin === nextjsLocalDevOrigin);
1139
- if (!hasExistingCorsOrigin) {
1140
- try {
1141
- const createCorsRes = await createCorsOrigin({
1142
- allowCredentials: true,
1143
- origin: nextjsLocalDevOrigin,
1144
- projectId
1145
- });
1146
- this.log(createCorsRes.id ? `Added ${nextjsLocalDevOrigin} to CORS origins` : `Failed to add ${nextjsLocalDevOrigin} to CORS origins`);
1147
- } catch (error) {
1148
- debug(`Error creating new CORS Origin ${nextjsLocalDevOrigin}: ${error}`);
1149
- this.error(`Failed to add ${nextjsLocalDevOrigin} to CORS origins: ${error}`, {
1150
- exit: 1
1151
- });
1152
- }
1153
- }
1154
- }
1155
- const chosen = await resolvePackageManager({
1156
- interactive: !this.isUnattended(),
1157
- output: this.output,
1158
- packageManager: this.flags['package-manager'],
1159
- targetDir: workDir
1160
- });
1161
998
  this._trace.log({
1162
- selectedOption: chosen,
1163
- step: 'selectPackageManager'
1164
- });
1165
- const packages = [
1166
- '@sanity/vision@5',
1167
- 'sanity@5',
1168
- '@sanity/image-url@2',
1169
- 'styled-components@6'
1170
- ];
1171
- if (templateToUse === 'blog') {
1172
- packages.push('@sanity/icons');
1173
- }
1174
- await installNewPackages({
1175
- packageManager: chosen,
1176
- packages
1177
- }, {
1178
- output: this.output,
1179
- workDir
999
+ selectedOption: selected === NEW_PROJECT ? 'create' : 'existing',
1000
+ step: 'configureAppProject'
1180
1001
  });
1181
- // will refactor this later
1182
- const execOptions = {
1183
- cwd: workDir,
1184
- encoding: 'utf8',
1185
- env: getPartialEnvWithNpmPath(workDir),
1186
- stdio: 'inherit'
1002
+ const project = selected === NEW_PROJECT ? await this.promptForProjectCreation({
1003
+ isUsersFirstProject: projects.length === 0,
1004
+ organizationId,
1005
+ organizations: [],
1006
+ planId,
1007
+ user
1008
+ }) : {
1009
+ displayName: projects.find((p)=>p.id === selected)?.displayName ?? '',
1010
+ projectId: selected
1187
1011
  };
1188
- switch(chosen){
1189
- case 'npm':
1190
- {
1191
- await execa('npm', [
1192
- 'install',
1193
- 'next-sanity@12'
1194
- ], execOptions);
1195
- break;
1196
- }
1197
- case 'pnpm':
1198
- {
1199
- await execa('pnpm', [
1200
- 'install',
1201
- 'next-sanity@12'
1202
- ], execOptions);
1203
- break;
1204
- }
1205
- case 'yarn':
1206
- {
1207
- const peerDeps = await getPeerDependencies('next-sanity@12', workDir);
1208
- await installNewPackages({
1209
- packageManager: 'yarn',
1210
- packages: [
1211
- 'next-sanity@12',
1212
- ...peerDeps
1213
- ]
1214
- }, {
1215
- output: this.output,
1216
- workDir
1217
- });
1218
- break;
1219
- }
1220
- default:
1221
- {
1222
- break;
1223
- }
1224
- }
1225
- this.log(`\n${styleText('green', 'Success!')} Your Sanity configuration files has been added to this project`);
1226
- if (mcpConfigured && mcpConfigured.length > 0) {
1227
- const message = await this.getPostInitMCPPrompt(mcpConfigured);
1228
- this.log(`\n${message}`);
1229
- this.log(`\nLearn more: ${styleText('cyan', 'https://mcp.sanity.io')}`);
1230
- this.log(`\nHave feedback? Tell us in the community: ${styleText('cyan', 'https://www.sanity.io/community/join')}`);
1231
- }
1232
- await this.writeStagingEnvIfNeeded(workDir);
1233
- this.exit(0);
1234
- }
1235
- async promptForDatasetImport(message) {
1236
- return confirm({
1237
- default: true,
1238
- message: message || 'This template includes a sample dataset, would you like to use it?'
1012
+ const dataset = await this.getOrCreateDataset({
1013
+ displayName: project.displayName,
1014
+ projectId: project.projectId,
1015
+ showDefaultConfigPrompt: false
1239
1016
  });
1017
+ return {
1018
+ datasetName: dataset.datasetName,
1019
+ displayName: project.displayName,
1020
+ projectId: project.projectId
1021
+ };
1240
1022
  }
1241
1023
  async promptForProjectCreation({ isUsersFirstProject, organizationId, organizations, planId, user }) {
1242
1024
  const projectName = await input({
@@ -1272,37 +1054,6 @@ export class InitCommand extends SanityCommand {
1272
1054
  userAction: 'create'
1273
1055
  };
1274
1056
  }
1275
- async promptForTemplate() {
1276
- const template = this.flags.template;
1277
- const defaultTemplate = this.isUnattended() || template ? template || 'clean' : null;
1278
- if (defaultTemplate) {
1279
- return defaultTemplate;
1280
- }
1281
- return select({
1282
- choices: [
1283
- {
1284
- name: 'Clean project with no predefined schema types',
1285
- value: 'clean'
1286
- },
1287
- {
1288
- name: 'Blog (schema)',
1289
- value: 'blog'
1290
- },
1291
- {
1292
- name: 'E-commerce (Shopify)',
1293
- value: 'shopify'
1294
- },
1295
- {
1296
- name: 'Movie project (schema + sample data)',
1297
- value: 'moviedb'
1298
- }
1299
- ],
1300
- message: 'Select project template'
1301
- });
1302
- }
1303
- promptForUndefinedFlag(flag) {
1304
- return !this.isUnattended() && flag === undefined;
1305
- }
1306
1057
  async promptUserForNewOrganization(user) {
1307
1058
  const name = await promptForOrganizationName(user);
1308
1059
  const spin = spinner('Creating organization').start();
@@ -1408,72 +1159,6 @@ export class InitCommand extends SanityCommand {
1408
1159
  }
1409
1160
  }
1410
1161
  }
1411
- async writeOrOverwrite(filePath, content, workDir) {
1412
- if (existsSync(filePath)) {
1413
- let overwrite = this.flagOrDefault('overwrite-files', false);
1414
- if (this.promptForUndefinedFlag(this.flags['overwrite-files'])) {
1415
- overwrite = await confirm({
1416
- default: false,
1417
- message: `File ${styleText('yellow', filePath.replace(workDir, ''))} already exists. Do you want to overwrite it?`
1418
- });
1419
- }
1420
- if (!overwrite) {
1421
- return;
1422
- }
1423
- }
1424
- // make folder if not exists
1425
- const folderPath = path.dirname(filePath);
1426
- try {
1427
- await mkdir(folderPath, {
1428
- recursive: true
1429
- });
1430
- } catch {
1431
- debug('Error creating folder %s', folderPath);
1432
- }
1433
- await writeFile(filePath, content, {
1434
- encoding: 'utf8'
1435
- });
1436
- }
1437
- // write sanity folder files
1438
- async writeSourceFiles({ fileExtension, files, folderPath, srcFolderPrefix, workDir }) {
1439
- for (const [filePath, content] of Object.entries(files)){
1440
- // check if file ends with full stop to indicate it's file and not directory (this only works with our template tree structure)
1441
- if (filePath.includes('.') && typeof content === 'string') {
1442
- await this.writeOrOverwrite(path.join(workDir, srcFolderPrefix ? 'src' : '', 'sanity', folderPath || '', `${filePath}${fileExtension}`), content, workDir);
1443
- } else {
1444
- await mkdir(path.join(workDir, srcFolderPrefix ? 'src' : '', 'sanity', filePath), {
1445
- recursive: true
1446
- });
1447
- if (typeof content === 'object') {
1448
- await this.writeSourceFiles({
1449
- fileExtension,
1450
- files: content,
1451
- folderPath: filePath,
1452
- srcFolderPrefix,
1453
- workDir
1454
- });
1455
- }
1456
- }
1457
- }
1458
- }
1459
- /**
1460
- * When running in a non-production Sanity environment (e.g. staging), write the
1461
- * `SANITY_INTERNAL_ENV` variable to a `.env` file in the output directory so that
1462
- * the bootstrapped project continues to target the same environment.
1463
- */ async writeStagingEnvIfNeeded(outputPath) {
1464
- const sanityEnv = getSanityEnv();
1465
- if (sanityEnv === 'production') return;
1466
- await createOrAppendEnvVars({
1467
- envVars: {
1468
- INTERNAL_ENV: sanityEnv
1469
- },
1470
- filename: '.env',
1471
- framework: null,
1472
- log: false,
1473
- output: this.output,
1474
- outputPath
1475
- });
1476
- }
1477
1162
  }
1478
1163
 
1479
1164
  //# sourceMappingURL=init.js.map