@sanity/cli 6.0.0-alpha.7 → 6.0.0-alpha.8

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 (144) hide show
  1. package/README.md +75 -75
  2. package/dist/actions/build/renderDocumentWorker/tryLoadDocumentComponent.js +2 -1
  3. package/dist/actions/build/renderDocumentWorker/tryLoadDocumentComponent.js.map +1 -1
  4. package/dist/actions/init/bootstrapLocalTemplate.d.ts +13 -0
  5. package/dist/actions/init/bootstrapLocalTemplate.js +136 -0
  6. package/dist/actions/init/bootstrapLocalTemplate.js.map +1 -0
  7. package/dist/actions/init/bootstrapRemoteTemplate.d.ts +12 -0
  8. package/dist/actions/init/bootstrapRemoteTemplate.js +109 -0
  9. package/dist/actions/init/bootstrapRemoteTemplate.js.map +1 -0
  10. package/dist/actions/init/bootstrapTemplate.d.ts +18 -0
  11. package/dist/actions/init/bootstrapTemplate.js +32 -0
  12. package/dist/actions/init/bootstrapTemplate.js.map +1 -0
  13. package/dist/actions/init/checkNextJsReactCompatibility.d.ts +9 -0
  14. package/dist/actions/init/checkNextJsReactCompatibility.js +21 -0
  15. package/dist/actions/init/checkNextJsReactCompatibility.js.map +1 -0
  16. package/dist/actions/init/countNestedFolders.d.ts +1 -0
  17. package/dist/actions/init/countNestedFolders.js +6 -0
  18. package/dist/actions/init/countNestedFolders.js.map +1 -0
  19. package/dist/actions/init/createAppCliConfig.d.ts +5 -0
  20. package/dist/actions/init/createAppCliConfig.js +19 -0
  21. package/dist/actions/init/createAppCliConfig.js.map +1 -0
  22. package/dist/actions/init/createCliConfig.d.ts +6 -0
  23. package/dist/actions/init/createCliConfig.js +27 -0
  24. package/dist/actions/init/createCliConfig.js.map +1 -0
  25. package/dist/actions/init/createPackageManifest.d.ts +6 -0
  26. package/dist/actions/init/createPackageManifest.js +86 -0
  27. package/dist/actions/init/createPackageManifest.js.map +1 -0
  28. package/dist/actions/init/createStudioConfig.d.ts +13 -0
  29. package/dist/actions/init/createStudioConfig.js +41 -0
  30. package/dist/actions/init/createStudioConfig.js.map +1 -0
  31. package/dist/actions/init/env/createOrAppendEnvVars.d.ts +12 -0
  32. package/dist/actions/init/env/createOrAppendEnvVars.js +25 -0
  33. package/dist/actions/init/env/createOrAppendEnvVars.js.map +1 -0
  34. package/dist/actions/init/env/parseAndUpdateEnvVars.d.ts +9 -0
  35. package/dist/actions/init/env/parseAndUpdateEnvVars.js +42 -0
  36. package/dist/actions/init/env/parseAndUpdateEnvVars.js.map +1 -0
  37. package/dist/actions/init/env/writeEnvVarsToFile.d.ts +12 -0
  38. package/dist/actions/init/env/writeEnvVarsToFile.js +49 -0
  39. package/dist/actions/init/env/writeEnvVarsToFile.js.map +1 -0
  40. package/dist/actions/init/fetchPostInitPrompt.d.ts +6 -0
  41. package/dist/actions/init/fetchPostInitPrompt.js +30 -0
  42. package/dist/actions/init/fetchPostInitPrompt.js.map +1 -0
  43. package/dist/actions/init/git.d.ts +1 -0
  44. package/dist/actions/init/git.js +65 -0
  45. package/dist/actions/init/git.js.map +1 -0
  46. package/dist/actions/init/processTemplate.d.ts +7 -0
  47. package/dist/actions/init/processTemplate.js +56 -0
  48. package/dist/actions/init/processTemplate.js.map +1 -0
  49. package/dist/actions/init/remoteTemplate.d.ts +0 -3
  50. package/dist/actions/init/remoteTemplate.js +2 -40
  51. package/dist/actions/init/remoteTemplate.js.map +1 -1
  52. package/dist/actions/init/resolvePackageManager.d.ts +10 -0
  53. package/dist/actions/init/resolvePackageManager.js +20 -0
  54. package/dist/actions/init/resolvePackageManager.js.map +1 -0
  55. package/dist/actions/init/setupMCP.d.ts +21 -0
  56. package/dist/actions/init/setupMCP.js +258 -0
  57. package/dist/actions/init/setupMCP.js.map +1 -0
  58. package/dist/actions/init/templates/appQuickstart.d.ts +3 -0
  59. package/dist/actions/init/templates/appQuickstart.js +28 -0
  60. package/dist/actions/init/templates/appQuickstart.js.map +1 -0
  61. package/dist/actions/init/templates/appSanityUi.d.ts +3 -0
  62. package/dist/actions/init/templates/appSanityUi.js +30 -0
  63. package/dist/actions/init/templates/appSanityUi.js.map +1 -0
  64. package/dist/actions/init/templates/blog.d.ts +3 -0
  65. package/dist/actions/init/templates/blog.js +4 -0
  66. package/dist/actions/init/templates/blog.js.map +1 -0
  67. package/dist/actions/init/templates/clean.d.ts +3 -0
  68. package/dist/actions/init/templates/clean.js +4 -0
  69. package/dist/actions/init/templates/clean.js.map +1 -0
  70. package/dist/actions/init/templates/getStarted.d.ts +3 -0
  71. package/dist/actions/init/templates/getStarted.js +35 -0
  72. package/dist/actions/init/templates/getStarted.js.map +1 -0
  73. package/dist/actions/init/templates/index.d.ts +3 -0
  74. package/dist/actions/init/templates/index.js +23 -0
  75. package/dist/actions/init/templates/index.js.map +1 -0
  76. package/dist/actions/init/templates/moviedb.d.ts +3 -0
  77. package/dist/actions/init/templates/moviedb.js +34 -0
  78. package/dist/actions/init/templates/moviedb.js.map +1 -0
  79. package/dist/actions/init/templates/nextjs/index.d.ts +6 -0
  80. package/dist/actions/init/templates/nextjs/index.js +213 -0
  81. package/dist/actions/init/templates/nextjs/index.js.map +1 -0
  82. package/dist/actions/init/templates/nextjs/schemaTypes/blog.d.ts +3 -0
  83. package/dist/actions/init/templates/nextjs/schemaTypes/blog.js +247 -0
  84. package/dist/actions/init/templates/nextjs/schemaTypes/blog.js.map +1 -0
  85. package/dist/actions/init/templates/quickstart.d.ts +3 -0
  86. package/dist/actions/init/templates/quickstart.js +4 -0
  87. package/dist/actions/init/templates/quickstart.js.map +1 -0
  88. package/dist/actions/init/templates/shopify.d.ts +3 -0
  89. package/dist/actions/init/templates/shopify.js +77 -0
  90. package/dist/actions/init/templates/shopify.js.map +1 -0
  91. package/dist/actions/init/templates/shopifyOnline.d.ts +3 -0
  92. package/dist/actions/init/templates/shopifyOnline.js +49 -0
  93. package/dist/actions/init/templates/shopifyOnline.js.map +1 -0
  94. package/dist/actions/init/types.d.ts +15 -0
  95. package/dist/actions/init/types.js +3 -0
  96. package/dist/actions/init/types.js.map +1 -0
  97. package/dist/actions/init/updateInitialTemplateMetadata.d.ts +1 -0
  98. package/dist/actions/init/updateInitialTemplateMetadata.js +17 -0
  99. package/dist/actions/init/updateInitialTemplateMetadata.js.map +1 -0
  100. package/dist/commands/init.d.ts +24 -14
  101. package/dist/commands/init.js +544 -26
  102. package/dist/commands/init.js.map +1 -1
  103. package/dist/prompts/init/nextjs.d.ts +5 -0
  104. package/dist/prompts/init/nextjs.js +56 -0
  105. package/dist/prompts/init/nextjs.js.map +1 -0
  106. package/dist/prompts/init/promptForTypescript.d.ts +0 -1
  107. package/dist/prompts/init/promptForTypescript.js +0 -6
  108. package/dist/prompts/init/promptForTypescript.js.map +1 -1
  109. package/dist/services/mcp.d.ts +10 -0
  110. package/dist/services/mcp.js +16 -0
  111. package/dist/services/mcp.js.map +1 -1
  112. package/dist/services/projects.d.ts +5 -2
  113. package/dist/services/projects.js +37 -0
  114. package/dist/services/projects.js.map +1 -1
  115. package/dist/studioDependencies.d.ts +16 -0
  116. package/dist/studioDependencies.js +24 -0
  117. package/dist/studioDependencies.js.map +1 -0
  118. package/dist/types.d.ts +33 -0
  119. package/dist/types.js.map +1 -1
  120. package/dist/typings/deepSortObject.d.js +2 -0
  121. package/dist/typings/deepSortObject.d.js.map +1 -0
  122. package/dist/util/copy.d.ts +5 -0
  123. package/dist/util/copy.js +37 -0
  124. package/dist/util/copy.js.map +1 -0
  125. package/dist/util/frameworkPort.d.ts +12 -0
  126. package/dist/util/frameworkPort.js +61 -0
  127. package/dist/util/frameworkPort.js.map +1 -0
  128. package/dist/util/fsUtils.d.ts +2 -0
  129. package/dist/util/fsUtils.js +34 -0
  130. package/dist/util/fsUtils.js.map +1 -0
  131. package/dist/util/getProjectDefaults.d.ts +11 -0
  132. package/dist/util/getProjectDefaults.js +77 -0
  133. package/dist/util/getProjectDefaults.js.map +1 -0
  134. package/dist/util/packageManager/packageManagerChoice.d.ts +2 -0
  135. package/dist/util/packageManager/packageManagerChoice.js +8 -0
  136. package/dist/util/packageManager/packageManagerChoice.js.map +1 -1
  137. package/dist/util/readdirRecursive.d.ts +5 -0
  138. package/dist/util/readdirRecursive.js +24 -0
  139. package/dist/util/readdirRecursive.js.map +1 -0
  140. package/dist/util/resolveLatestVersions.d.ts +7 -0
  141. package/dist/util/resolveLatestVersions.js +21 -0
  142. package/dist/util/resolveLatestVersions.js.map +1 -0
  143. package/oclif.manifest.json +36 -29
  144. package/package.json +17 -8
@@ -1,26 +1,49 @@
1
1
  // @Todo will remove by time migration of this command is complete
2
- /* eslint-disable @typescript-eslint/no-unused-vars */ import { Args, Flags } from '@oclif/core';
2
+ import { existsSync } from 'node:fs';
3
+ import { mkdir, writeFile } from 'node:fs/promises';
4
+ import path from 'node:path';
5
+ import { Args, Flags } from '@oclif/core';
3
6
  import { CLIError } from '@oclif/core/errors';
4
7
  import { getCliToken, SanityCommand, subdebug } from '@sanity/cli-core';
5
8
  import { chalk, confirm, input, logSymbols, select, Separator, spinner } from '@sanity/cli-core/ux';
6
9
  import { isHttpError } from '@sanity/client';
10
+ import { DatasetImportCommand } from '@sanity/import';
7
11
  import { frameworks } from '@vercel/frameworks';
8
12
  import { detectFrameworkRecord, LocalFileSystemDetector } from '@vercel/fs-detectors';
13
+ import { execa } from 'execa';
14
+ import { deburr } from 'lodash-es';
9
15
  import { getProviderName } from '../actions/auth/getProviderName.js';
10
16
  import { login } from '../actions/auth/login/login.js';
11
17
  import { createDataset } from '../actions/dataset/create.js';
18
+ import { bootstrapTemplate } from '../actions/init/bootstrapTemplate.js';
19
+ import { checkNextJsReactCompatibility } from '../actions/init/checkNextJsReactCompatibility.js';
20
+ import { countNestedFolders } from '../actions/init/countNestedFolders.js';
12
21
  import { determineAppTemplate } from '../actions/init/determineAppTemplate.js';
22
+ import { createOrAppendEnvVars } from '../actions/init/env/createOrAppendEnvVars.js';
23
+ import { fetchPostInitPrompt } from '../actions/init/fetchPostInitPrompt.js';
24
+ import { tryGitInit } from '../actions/init/git.js';
13
25
  import { checkIsRemoteTemplate, getGitHubRepoInfo } from '../actions/init/remoteTemplate.js';
26
+ import { resolvePackageManager } from '../actions/init/resolvePackageManager.js';
27
+ import { setupMCP } from '../actions/init/setupMCP.js';
28
+ import templates from '../actions/init/templates/index.js';
29
+ import { sanityCliTemplate, sanityConfigTemplate, sanityFolder, sanityStudioTemplate } from '../actions/init/templates/nextjs/index.js';
14
30
  import { getOrganizationChoices } from '../actions/organizations/getOrganizationChoices.js';
15
31
  import { getOrganizationsWithAttachGrantInfo } from '../actions/organizations/getOrganizationsWithAttachGrantInfo.js';
16
32
  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';
17
35
  import { promptForDatasetName } from '../prompts/promptForDatasetName.js';
36
+ import { createCorsOrigin, listCorsOrigins } from '../services/cors.js';
18
37
  import { createDataset as createDatasetService, listDatasets } from '../services/datasets.js';
19
38
  import { getProjectFeatures } from '../services/getProjectFeatures.js';
20
39
  import { createOrganization, listOrganizations } from '../services/organizations.js';
21
40
  import { getPlanId, getPlanIdFromCoupon } from '../services/plans.js';
22
- import { createProject, listProjects } from '../services/projects.js';
41
+ import { createProject, listProjects, updateProjectInitializedAt } from '../services/projects.js';
23
42
  import { getCliUser } from '../services/user.js';
43
+ import { absolutify, validateEmptyPath } from '../util/fsUtils.js';
44
+ import { getProjectDefaults } from '../util/getProjectDefaults.js';
45
+ import { installDeclaredPackages, installNewPackages } from '../util/packageManager/installPackages.js';
46
+ import { getPartialEnvWithNpmPath } from '../util/packageManager/packageManagerChoice.js';
24
47
  const debug = subdebug('init');
25
48
  export class InitCommand extends SanityCommand {
26
49
  static args = {
@@ -116,19 +139,19 @@ export class InitCommand extends SanityCommand {
116
139
  }),
117
140
  'nextjs-add-config-files': Flags.boolean({
118
141
  allowNo: true,
119
- default: true,
142
+ default: undefined,
120
143
  description: 'Add config files to Next.js project',
121
144
  helpGroup: 'Next.js'
122
145
  }),
123
146
  'nextjs-append-env': Flags.boolean({
124
147
  allowNo: true,
125
- default: true,
148
+ default: undefined,
126
149
  description: 'Append project ID and dataset to .env file',
127
150
  helpGroup: 'Next.js'
128
151
  }),
129
152
  'nextjs-embed-studio': Flags.boolean({
130
153
  allowNo: true,
131
- default: true,
154
+ default: undefined,
132
155
  description: 'Embed the Studio in Next.js application',
133
156
  helpGroup: 'Next.js'
134
157
  }),
@@ -154,7 +177,8 @@ export class InitCommand extends SanityCommand {
154
177
  helpValue: '<path>'
155
178
  }),
156
179
  'overwrite-files': Flags.boolean({
157
- default: false,
180
+ allowNo: true,
181
+ default: undefined,
158
182
  description: 'Overwrite existing files'
159
183
  }),
160
184
  'package-manager': Flags.string({
@@ -187,6 +211,11 @@ export class InitCommand extends SanityCommand {
187
211
  description: 'Login provider to use',
188
212
  helpValue: '<provider>'
189
213
  }),
214
+ quickstart: Flags.boolean({
215
+ deprecated: true,
216
+ description: 'Used for initializing a project from a server schema that is saved in the Journey API',
217
+ hidden: true
218
+ }),
190
219
  reconfigure: Flags.boolean({
191
220
  deprecated: {
192
221
  message: 'This flag is no longer supported',
@@ -196,7 +225,6 @@ export class InitCommand extends SanityCommand {
196
225
  hidden: true
197
226
  }),
198
227
  template: Flags.string({
199
- default: 'clean',
200
228
  description: 'Project template to use [default: "clean"]',
201
229
  exclusive: [
202
230
  'bare'
@@ -211,7 +239,7 @@ export class InitCommand extends SanityCommand {
211
239
  }),
212
240
  typescript: Flags.boolean({
213
241
  allowNo: true,
214
- default: true,
242
+ default: undefined,
215
243
  description: 'Enable TypeScript support',
216
244
  exclusive: [
217
245
  'bare'
@@ -232,14 +260,14 @@ export class InitCommand extends SanityCommand {
232
260
  })
233
261
  };
234
262
  async run() {
235
- const { args, flags } = await this.parse(InitCommand);
263
+ const workDir = process.cwd();
236
264
  const createProjectName = this.flags['create-project'];
237
265
  // For backwards "compatibility" - we used to allow `sanity init plugin`,
238
266
  // and no longer do - but instead of printing an error about an unknown
239
267
  // _command_, we want to acknowledge that the user is trying to do something
240
268
  // that no longer exists but might have at some point in the past.
241
- if (args.type) {
242
- this.error(args.type === 'plugin' ? 'Initializing plugins through the CLI is no longer supported' : `Unknown init type "${args.type}"`, {
269
+ if (this.args.type) {
270
+ this.error(this.args.type === 'plugin' ? 'Initializing plugins through the CLI is no longer supported' : `Unknown init type "${this.args.type}"`, {
243
271
  exit: 1
244
272
  });
245
273
  }
@@ -269,8 +297,8 @@ export class InitCommand extends SanityCommand {
269
297
  });
270
298
  const isNextJs = detectedFramework?.slug === 'nextjs';
271
299
  let remoteTemplateInfo;
272
- if (flags.template && checkIsRemoteTemplate(flags.template)) {
273
- remoteTemplateInfo = await getGitHubRepoInfo(flags.template, flags['template-token']);
300
+ if (this.flags.template && checkIsRemoteTemplate(this.flags.template)) {
301
+ remoteTemplateInfo = await getGitHubRepoInfo(this.flags.template, this.flags['template-token']);
274
302
  }
275
303
  if (detectedFramework && detectedFramework.slug !== 'sanity' && remoteTemplateInfo) {
276
304
  this.error(`A remote template cannot be used with a detected framework. Detected: ${detectedFramework.name}`, {
@@ -309,24 +337,26 @@ export class InitCommand extends SanityCommand {
309
337
  if (detectedFramework && detectedFramework.slug === 'nextjs') {
310
338
  envFilenameDefault = '.env.local';
311
339
  }
312
- const _envFilename = typeof flags.env === 'string' ? flags.env : envFilenameDefault;
340
+ const envFilename = typeof this.flags.env === 'string' ? this.flags.env : envFilenameDefault;
313
341
  // If the user isn't already autenticated, make it so
314
342
  const { user } = await this.ensureAuthenticated();
315
- // skip project / dataset prompting
316
- const template = this.flags.template;
317
- const isAppTemplate = template ? determineAppTemplate(template) : false // Default to false
343
+ const isAppTemplate = this.flags.template ? determineAppTemplate(this.flags.template) : false // Default to false
318
344
  ;
319
345
  if (!isAppTemplate) {
320
346
  this.log(`${logSymbols.success} Fetching existing projects`);
321
347
  this.log('');
322
348
  }
323
- const _newProjectId = createProjectName && await this.createProjectFromName({
324
- createProjectName,
325
- planId,
326
- user
327
- });
328
- const { datasetName, projectId } = await this.getProjectDetails({
349
+ let newProject;
350
+ if (createProjectName) {
351
+ newProject = await this.createProjectFromName({
352
+ createProjectName,
353
+ planId,
354
+ user
355
+ });
356
+ }
357
+ const { datasetName, displayName, isFirstProject, organizationId, projectId } = await this.getProjectDetails({
329
358
  isAppTemplate,
359
+ newProject,
330
360
  planId,
331
361
  showDefaultConfigPrompt,
332
362
  user
@@ -340,6 +370,227 @@ export class InitCommand extends SanityCommand {
340
370
  this.log(`\nYou can find your project on Sanity Manage — https://www.sanity.io/manage/project/${projectId}\n`);
341
371
  return;
342
372
  }
373
+ let initNext = this.flagOrDefault('nextjs-add-config-files', false);
374
+ if (isNextJs && this.promptForUndefinedFlag(this.flags['nextjs-add-config-files'])) {
375
+ initNext = await promptForConfigFiles();
376
+ }
377
+ // @todo
378
+ // trace.log({
379
+ // step: 'useDetectedFramework',
380
+ // selectedOption: initNext ? 'yes' : 'no',
381
+ // detectedFramework: detectedFramework?.name,
382
+ // })
383
+ const sluggedName = deburr(displayName.toLowerCase()).replaceAll(/\s+/g, '-').replaceAll(/[^a-z0-9-]/g, '');
384
+ // add more frameworks to this as we add support for them
385
+ // this is used to skip the getProjectInfo prompt
386
+ const initFramework = initNext;
387
+ // Gather project defaults based on environment
388
+ const defaults = await getProjectDefaults({
389
+ isPlugin: false,
390
+ workDir
391
+ });
392
+ // Prompt the user for required information
393
+ const outputPath = await this.getProjectOutputPath({
394
+ initFramework,
395
+ sluggedName,
396
+ workDir
397
+ });
398
+ // Set up MCP integration
399
+ const mcpResult = await setupMCP({
400
+ mcp: this.flags.mcp,
401
+ output: this.output
402
+ });
403
+ // @todo
404
+ // trace.log({
405
+ // step: 'mcpSetup',
406
+ // detectedEditors: mcpResult.detectedEditors,
407
+ // configuredEditors: mcpResult.configuredEditors,
408
+ // skipped: mcpResult.skipped,
409
+ // })
410
+ // if (mcpResult.error) {
411
+ // trace.error(mcpResult.error)
412
+ // }
413
+ const mcpConfigured = mcpResult.configuredEditors;
414
+ if (isNextJs) {
415
+ await checkNextJsReactCompatibility({
416
+ detectedFramework,
417
+ output: this.output,
418
+ outputPath
419
+ });
420
+ }
421
+ if (initNext) {
422
+ await this.initNextJs({
423
+ datasetName,
424
+ detectedFramework,
425
+ envFilename,
426
+ mcpConfigured,
427
+ projectId,
428
+ workDir
429
+ });
430
+ }
431
+ // user wants to write environment variables to file
432
+ if (this.flags.env) {
433
+ await createOrAppendEnvVars({
434
+ envVars: {
435
+ DATASET: datasetName,
436
+ PROJECT_ID: projectId
437
+ },
438
+ filename: envFilename,
439
+ framework: detectedFramework,
440
+ log: false,
441
+ output: this.output,
442
+ outputPath
443
+ });
444
+ this.exit(0);
445
+ }
446
+ // Prompt for template to use
447
+ const templateName = await this.promptForTemplate();
448
+ // @todo
449
+ // trace.log({step: 'selectProjectTemplate', selectedOption: templateName})
450
+ const template = templates[templateName];
451
+ if (!remoteTemplateInfo && !template) {
452
+ this.error(`Template "${templateName}" not found`, {
453
+ exit: 1
454
+ });
455
+ }
456
+ let useTypeScript = this.flags.typescript;
457
+ if (!remoteTemplateInfo && template && template.typescriptOnly === true) {
458
+ useTypeScript = true;
459
+ } else if (this.promptForUndefinedFlag(this.flags.typescript)) {
460
+ useTypeScript = await promptForTypeScript();
461
+ // @todo
462
+ // trace.log({step: 'useTypeScript', selectedOption: useTypeScript ? 'yes' : 'no'})
463
+ }
464
+ // If the template has a sample dataset, prompt the user whether or not we should import it
465
+ const shouldImport = !this.isUnattended() && template?.datasetUrl && await this.promptForDatasetImport(template.importPrompt);
466
+ // @todo
467
+ // trace.log({step: 'importTemplateDataset', selectedOption: shouldImport ? 'yes' : 'no'})
468
+ try {
469
+ await updateProjectInitializedAt(projectId);
470
+ } catch (err) {
471
+ // Non-critical update
472
+ debug('Failed to update cliInitializedAt metadata', err);
473
+ }
474
+ try {
475
+ await bootstrapTemplate({
476
+ autoUpdates: this.flags['auto-updates'],
477
+ bearerToken: this.flags['template-token'],
478
+ dataset: datasetName,
479
+ organizationId,
480
+ output: this.output,
481
+ outputPath,
482
+ overwriteFiles: this.flags['overwrite-files'],
483
+ packageName: sluggedName,
484
+ projectId,
485
+ projectName: displayName || defaults.projectName,
486
+ remoteTemplateInfo,
487
+ templateName,
488
+ useTypeScript
489
+ });
490
+ } catch (error) {
491
+ if (error instanceof Error) {
492
+ throw error;
493
+ }
494
+ throw new Error(String(error));
495
+ }
496
+ const pkgManager = await resolvePackageManager({
497
+ interactive: !this.isUnattended(),
498
+ output: this.output,
499
+ packageManager: this.flags['package-manager'],
500
+ targetDir: outputPath
501
+ });
502
+ // @todo
503
+ // trace.log({selectedOption: pkgManager, step: 'selectPackageManager', })
504
+ // Now for the slow part... installing dependencies
505
+ await installDeclaredPackages(outputPath, pkgManager, {
506
+ output: this.output,
507
+ workDir
508
+ });
509
+ const useGit = this.flags.git === undefined || Boolean(this.flags.git);
510
+ const commitMessage = this.flags.git;
511
+ // Try initializing a git repository
512
+ if (useGit) {
513
+ tryGitInit(outputPath, typeof commitMessage === 'string' ? commitMessage : undefined);
514
+ }
515
+ // Prompt for dataset import (if a dataset is defined)
516
+ if (shouldImport && template?.datasetUrl) {
517
+ const token = await getCliToken();
518
+ if (!token) {
519
+ this.error('Authentication required to import dataset', {
520
+ exit: 1
521
+ });
522
+ }
523
+ await DatasetImportCommand.run([
524
+ template.datasetUrl,
525
+ '--project',
526
+ projectId,
527
+ '--dataset',
528
+ datasetName,
529
+ '--token',
530
+ token
531
+ ], {
532
+ root: outputPath
533
+ });
534
+ this.log('');
535
+ this.log('If you want to delete the imported data, use');
536
+ this.log(` ${chalk.cyan(`npx sanity dataset delete ${datasetName}`)}`);
537
+ this.log('and create a new clean dataset with');
538
+ this.log(` ${chalk.cyan(`npx sanity dataset create <name>`)}\n`);
539
+ }
540
+ const devCommandMap = {
541
+ bun: 'bun dev',
542
+ manual: 'npm run dev',
543
+ npm: 'npm run dev',
544
+ pnpm: 'pnpm dev',
545
+ yarn: 'yarn dev'
546
+ };
547
+ const devCommand = devCommandMap[pkgManager];
548
+ const isCurrentDir = outputPath === process.cwd();
549
+ const goToProjectDir = `\n(${chalk.cyan(`cd ${outputPath}`)} to navigate to your new project directory)`;
550
+ if (isAppTemplate) {
551
+ //output for custom apps here
552
+ this.log(`${logSymbols.success} ${chalk.green.bold('Success!')} Your custom app has been scaffolded.`);
553
+ if (!isCurrentDir) this.log(goToProjectDir);
554
+ this.log(`\n${chalk.bold('Next')}, configure the project(s) and dataset(s) your app should work with.`);
555
+ this.log('\nGet started in `src/App.tsx`, or refer to our documentation for a walkthrough:');
556
+ this.log(chalk.blue.underline('https://www.sanity.io/docs/app-sdk/sdk-configuration'));
557
+ if (mcpConfigured && mcpConfigured.length > 0) {
558
+ const message = await this.getPostInitMCPPrompt(mcpConfigured);
559
+ this.log(`\n${message}`);
560
+ this.log(`\nLearn more: ${chalk.cyan('https://mcp.sanity.io')}`);
561
+ this.log(`\nHave feedback? Tell us in the community: ${chalk.cyan('https://www.sanity.io/community/join')}`);
562
+ }
563
+ this.log('\n');
564
+ this.log(`Other helpful commands:`);
565
+ this.log(`npx sanity docs browse to open the documentation in a browser`);
566
+ this.log(`npx sanity dev to start the development server for your app`);
567
+ this.log(`npx sanity deploy to deploy your app`);
568
+ } else {
569
+ //output for Studios here
570
+ this.log(`✅ ${chalk.green.bold('Success!')} Your Studio has been created.`);
571
+ if (!isCurrentDir) this.log(goToProjectDir);
572
+ this.log(`\nGet started by running ${chalk.cyan(devCommand)} to launch your Studio's development server`);
573
+ if (mcpConfigured && mcpConfigured.length > 0) {
574
+ const message = await this.getPostInitMCPPrompt(mcpConfigured);
575
+ this.log(`\n${message}`);
576
+ this.log(`\nLearn more: ${chalk.cyan('https://mcp.sanity.io')}`);
577
+ this.log(`\nHave feedback? Tell us in the community: ${chalk.cyan('https://www.sanity.io/community/join')}`);
578
+ }
579
+ this.log('\n');
580
+ this.log(`Other helpful commands:`);
581
+ this.log(`npx sanity docs browse to open the documentation in a browser`);
582
+ this.log(`npx sanity manage to open the project settings in a browser`);
583
+ this.log(`npx sanity help to explore the CLI manual`);
584
+ }
585
+ if (isFirstProject) {
586
+ // @todo
587
+ // trace.log({step: 'sendCommunityInvite', selectedOption: 'yes'})
588
+ const DISCORD_INVITE_LINK = 'https://www.sanity.io/community/join';
589
+ this.log(`\nJoin the Sanity community: ${chalk.cyan(DISCORD_INVITE_LINK)}`);
590
+ this.log('We look forward to seeing you there!\n');
591
+ }
592
+ // @todo
593
+ // trace.complete()
343
594
  }
344
595
  checkFlagsInUnattendedMode({ createProjectName, isNextJs }) {
345
596
  debug('Unattended mode, validating required options');
@@ -437,6 +688,9 @@ export class InitCommand extends SanityCommand {
437
688
  user
438
689
  };
439
690
  }
691
+ flagOrDefault(flag, defaultValue) {
692
+ return typeof this.flags[flag] === 'boolean' ? this.flags[flag] : defaultValue;
693
+ }
440
694
  async getOrCreateDataset(opts) {
441
695
  const visibility = this.flags.visibility;
442
696
  const dataset = this.flags.dataset;
@@ -537,8 +791,8 @@ export class InitCommand extends SanityCommand {
537
791
  userAction: 'select'
538
792
  };
539
793
  }
540
- async getOrCreateProject({ planId, user }) {
541
- const projectId = this.flags.project;
794
+ async getOrCreateProject({ newProject, planId, user }) {
795
+ const projectId = this.flags.project || newProject;
542
796
  const organizationId = this.flags.organization;
543
797
  let projects;
544
798
  let organizations;
@@ -662,7 +916,10 @@ export class InitCommand extends SanityCommand {
662
916
  return undefined;
663
917
  }
664
918
  }
665
- async getProjectDetails({ isAppTemplate, planId, showDefaultConfigPrompt, user }) {
919
+ async getPostInitMCPPrompt(editorsNames) {
920
+ return fetchPostInitPrompt(new Intl.ListFormat('en').format(editorsNames));
921
+ }
922
+ async getProjectDetails({ isAppTemplate, newProject, planId, showDefaultConfigPrompt, user }) {
666
923
  if (isAppTemplate) {
667
924
  const organizations = await listOrganizations({
668
925
  includeImplicitMemberships: 'true',
@@ -682,6 +939,7 @@ export class InitCommand extends SanityCommand {
682
939
  }
683
940
  debug('Prompting user to select or create a project');
684
941
  const project = await this.getOrCreateProject({
942
+ newProject,
685
943
  planId,
686
944
  user
687
945
  });
@@ -708,6 +966,187 @@ export class InitCommand extends SanityCommand {
708
966
  projectId: project.projectId
709
967
  };
710
968
  }
969
+ async getProjectOutputPath({ initFramework, sluggedName, workDir }) {
970
+ const outputPath = this.flags['output-path'];
971
+ const specifiedPath = outputPath && path.resolve(outputPath);
972
+ if (this.isUnattended() || specifiedPath || this.flags.env || initFramework) {
973
+ return specifiedPath || workDir;
974
+ }
975
+ const inputPath = await input({
976
+ default: path.join(workDir, sluggedName),
977
+ message: 'Project output path:',
978
+ validate: validateEmptyPath
979
+ });
980
+ return absolutify(inputPath);
981
+ }
982
+ async initNextJs({ datasetName, detectedFramework, envFilename, mcpConfigured, projectId, workDir }) {
983
+ let useTypeScript = this.flagOrDefault('typescript', true);
984
+ if (this.promptForUndefinedFlag(this.flags.typescript)) {
985
+ useTypeScript = await promptForTypeScript();
986
+ }
987
+ // @todo
988
+ // trace.log({step: 'useTypeScript', selectedOption: useTypeScript ? 'yes' : 'no'})
989
+ const fileExtension = useTypeScript ? 'ts' : 'js';
990
+ let embeddedStudio = this.flagOrDefault('nextjs-embed-studio', true);
991
+ if (this.promptForUndefinedFlag(this.flags['nextjs-embed-studio'])) {
992
+ embeddedStudio = await promptForEmbeddedStudio();
993
+ }
994
+ let hasSrcFolder = false;
995
+ if (embeddedStudio) {
996
+ // find source path (app or src/app)
997
+ const appDir = 'app';
998
+ let srcPath = path.join(workDir, appDir);
999
+ if (!existsSync(srcPath)) {
1000
+ srcPath = path.join(workDir, 'src', appDir);
1001
+ hasSrcFolder = true;
1002
+ if (!existsSync(srcPath)) {
1003
+ try {
1004
+ await mkdir(srcPath, {
1005
+ recursive: true
1006
+ });
1007
+ } catch {
1008
+ debug('Error creating folder %s', srcPath);
1009
+ }
1010
+ }
1011
+ }
1012
+ const studioPath = this.isUnattended() ? '/studio' : await promptForStudioPath();
1013
+ const embeddedStudioRouteFilePath = path.join(srcPath, `${studioPath}/`, `[[...tool]]/page.${fileExtension}x`);
1014
+ // this selects the correct template string based on whether the user is using the app or pages directory and
1015
+ // replaces the ":configPath:" placeholder in the template with the correct path to the sanity.config.ts file.
1016
+ // we account for the user-defined embeddedStudioPath (default /studio) is accounted for by creating enough "../"
1017
+ // relative paths to reach the root level of the project
1018
+ await this.writeOrOverwrite(embeddedStudioRouteFilePath, sanityStudioTemplate.replace(':configPath:', `${'../'.repeat(countNestedFolders(embeddedStudioRouteFilePath.slice(workDir.length)))}sanity.config`), workDir);
1019
+ const sanityConfigPath = path.join(workDir, `sanity.config.${fileExtension}`);
1020
+ await this.writeOrOverwrite(sanityConfigPath, sanityConfigTemplate(hasSrcFolder).replace(':route:', embeddedStudioRouteFilePath.slice(workDir.length).replace('src/', '')).replace(':basePath:', studioPath), workDir);
1021
+ }
1022
+ const sanityCliPath = path.join(workDir, `sanity.cli.${fileExtension}`);
1023
+ await this.writeOrOverwrite(sanityCliPath, sanityCliTemplate, workDir);
1024
+ let templateToUse = this.flags.template ?? 'clean';
1025
+ if (this.promptForUndefinedFlag(this.flags.template)) {
1026
+ templateToUse = await promptForNextTemplate();
1027
+ }
1028
+ await this.writeSourceFiles({
1029
+ fileExtension,
1030
+ files: sanityFolder(useTypeScript, templateToUse),
1031
+ folderPath: undefined,
1032
+ srcFolderPrefix: hasSrcFolder,
1033
+ workDir
1034
+ });
1035
+ let appendEnv = this.flagOrDefault('nextjs-append-env', true);
1036
+ if (this.promptForUndefinedFlag(this.flags['nextjs-append-env'])) {
1037
+ appendEnv = await promptForAppendEnv(envFilename);
1038
+ }
1039
+ if (appendEnv) {
1040
+ await createOrAppendEnvVars({
1041
+ envVars: {
1042
+ DATASET: datasetName,
1043
+ PROJECT_ID: projectId
1044
+ },
1045
+ filename: envFilename,
1046
+ framework: detectedFramework,
1047
+ log: true,
1048
+ output: this.output,
1049
+ outputPath: workDir
1050
+ });
1051
+ }
1052
+ if (embeddedStudio) {
1053
+ const nextjsLocalDevOrigin = 'http://localhost:3000';
1054
+ const existingCorsOrigins = await listCorsOrigins(projectId);
1055
+ const hasExistingCorsOrigin = existingCorsOrigins.some((item)=>item.origin === nextjsLocalDevOrigin);
1056
+ if (!hasExistingCorsOrigin) {
1057
+ try {
1058
+ const createCorsRes = await createCorsOrigin({
1059
+ allowCredentials: true,
1060
+ origin: nextjsLocalDevOrigin,
1061
+ projectId
1062
+ });
1063
+ this.log(createCorsRes.id ? `Added ${nextjsLocalDevOrigin} to CORS origins` : `Failed to add ${nextjsLocalDevOrigin} to CORS origins`);
1064
+ } catch (error) {
1065
+ debug(`Error creating new CORS Origin ${nextjsLocalDevOrigin}: ${error}`);
1066
+ this.error(`Failed to add ${nextjsLocalDevOrigin} to CORS origins: ${error}`, {
1067
+ exit: 1
1068
+ });
1069
+ }
1070
+ }
1071
+ }
1072
+ const chosen = await resolvePackageManager({
1073
+ interactive: !this.isUnattended(),
1074
+ output: this.output,
1075
+ packageManager: this.flags['package-manager'],
1076
+ targetDir: workDir
1077
+ });
1078
+ // @todo
1079
+ // trace.log({step: 'selectPackageManager', selectedOption: chosen})
1080
+ const packages = [
1081
+ '@sanity/vision@4',
1082
+ 'sanity@4',
1083
+ '@sanity/image-url@1',
1084
+ 'styled-components@6'
1085
+ ];
1086
+ if (templateToUse === 'blog') {
1087
+ packages.push('@sanity/icons');
1088
+ }
1089
+ await installNewPackages({
1090
+ packageManager: chosen,
1091
+ packages
1092
+ }, {
1093
+ output: this.output,
1094
+ workDir
1095
+ });
1096
+ // will refactor this later
1097
+ const execOptions = {
1098
+ cwd: workDir,
1099
+ encoding: 'utf8',
1100
+ env: getPartialEnvWithNpmPath(workDir),
1101
+ stdio: 'inherit'
1102
+ };
1103
+ switch(chosen){
1104
+ case 'npm':
1105
+ {
1106
+ await execa('npm', [
1107
+ 'install',
1108
+ '--legacy-peer-deps',
1109
+ 'next-sanity@11'
1110
+ ], execOptions);
1111
+ break;
1112
+ }
1113
+ case 'pnpm':
1114
+ {
1115
+ await execa('pnpm', [
1116
+ 'install',
1117
+ 'next-sanity@11'
1118
+ ], execOptions);
1119
+ break;
1120
+ }
1121
+ case 'yarn':
1122
+ {
1123
+ await execa('npx', [
1124
+ 'install-peerdeps',
1125
+ '--yarn',
1126
+ 'next-sanity@11'
1127
+ ], execOptions);
1128
+ break;
1129
+ }
1130
+ default:
1131
+ {
1132
+ break;
1133
+ }
1134
+ }
1135
+ this.log(`\n${chalk.green('Success!')} Your Sanity configuration files has been added to this project`);
1136
+ if (mcpConfigured && mcpConfigured.length > 0) {
1137
+ const message = await this.getPostInitMCPPrompt(mcpConfigured);
1138
+ this.log(`\n${message}`);
1139
+ this.log(`\nLearn more: ${chalk.cyan('https://mcp.sanity.io')}`);
1140
+ this.log(`\nHave feedback? Tell us in the community: ${chalk.cyan('https://www.sanity.io/community/join')}`);
1141
+ }
1142
+ this.exit(0);
1143
+ }
1144
+ async promptForDatasetImport(message) {
1145
+ return confirm({
1146
+ default: true,
1147
+ message: message || 'This template includes a sample dataset, would you like to use it?'
1148
+ });
1149
+ }
711
1150
  async promptForDefaultConfig() {
712
1151
  return confirm({
713
1152
  default: true,
@@ -748,6 +1187,37 @@ export class InitCommand extends SanityCommand {
748
1187
  userAction: 'create'
749
1188
  };
750
1189
  }
1190
+ async promptForTemplate() {
1191
+ const template = this.flags.template;
1192
+ const defaultTemplate = this.isUnattended() || template ? template || 'clean' : null;
1193
+ if (defaultTemplate) {
1194
+ return defaultTemplate;
1195
+ }
1196
+ return select({
1197
+ choices: [
1198
+ {
1199
+ name: 'Clean project with no predefined schema types',
1200
+ value: 'clean'
1201
+ },
1202
+ {
1203
+ name: 'Blog (schema)',
1204
+ value: 'blog'
1205
+ },
1206
+ {
1207
+ name: 'E-commerce (Shopify)',
1208
+ value: 'shopify'
1209
+ },
1210
+ {
1211
+ name: 'Movie project (schema + sample data)',
1212
+ value: 'moviedb'
1213
+ }
1214
+ ],
1215
+ message: 'Select project template'
1216
+ });
1217
+ }
1218
+ promptForUndefinedFlag(flag) {
1219
+ return !this.isUnattended() && flag === undefined;
1220
+ }
751
1221
  async promptUserForNewOrganization(user) {
752
1222
  const name = await input({
753
1223
  default: user ? user.name : undefined,
@@ -863,6 +1333,54 @@ export class InitCommand extends SanityCommand {
863
1333
  }
864
1334
  }
865
1335
  }
1336
+ async writeOrOverwrite(filePath, content, workDir) {
1337
+ if (existsSync(filePath)) {
1338
+ let overwrite = this.flagOrDefault('overwrite-files', false);
1339
+ if (this.promptForUndefinedFlag(this.flags['overwrite-files'])) {
1340
+ overwrite = await confirm({
1341
+ default: false,
1342
+ message: `File ${chalk.yellow(filePath.replace(workDir, ''))} already exists. Do you want to overwrite it?`
1343
+ });
1344
+ }
1345
+ if (!overwrite) {
1346
+ return;
1347
+ }
1348
+ }
1349
+ // make folder if not exists
1350
+ const folderPath = path.dirname(filePath);
1351
+ try {
1352
+ await mkdir(folderPath, {
1353
+ recursive: true
1354
+ });
1355
+ } catch {
1356
+ debug('Error creating folder %s', folderPath);
1357
+ }
1358
+ await writeFile(filePath, content, {
1359
+ encoding: 'utf8'
1360
+ });
1361
+ }
1362
+ // write sanity folder files
1363
+ async writeSourceFiles({ fileExtension, files, folderPath, srcFolderPrefix, workDir }) {
1364
+ for (const [filePath, content] of Object.entries(files)){
1365
+ // check if file ends with full stop to indicate it's file and not directory (this only works with our template tree structure)
1366
+ if (filePath.includes('.') && typeof content === 'string') {
1367
+ await this.writeOrOverwrite(path.join(workDir, srcFolderPrefix ? 'src' : '', 'sanity', folderPath || '', `${filePath}${fileExtension}`), content, workDir);
1368
+ } else {
1369
+ await mkdir(path.join(workDir, srcFolderPrefix ? 'src' : '', 'sanity', filePath), {
1370
+ recursive: true
1371
+ });
1372
+ if (typeof content === 'object') {
1373
+ await this.writeSourceFiles({
1374
+ fileExtension,
1375
+ files: content,
1376
+ folderPath: filePath,
1377
+ srcFolderPrefix,
1378
+ workDir
1379
+ });
1380
+ }
1381
+ }
1382
+ }
1383
+ }
866
1384
  }
867
1385
 
868
1386
  //# sourceMappingURL=init.js.map