@sanity/cli 3.59.2-canary.33 → 3.59.2-corel-presentation-lcapi.562

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 (48) hide show
  1. package/lib/_chunks-cjs/cli.js +34360 -26058
  2. package/lib/_chunks-cjs/cli.js.map +1 -1
  3. package/lib/_chunks-cjs/cliWorker.js.map +1 -1
  4. package/lib/_chunks-cjs/generateAction.js.map +1 -1
  5. package/lib/_chunks-cjs/getCliConfig.js +1 -1
  6. package/lib/_chunks-cjs/getCliConfig.js.map +1 -1
  7. package/lib/_chunks-cjs/journeyConfig.js.map +1 -1
  8. package/lib/_chunks-cjs/loadEnv.js +200 -202
  9. package/lib/_chunks-cjs/loadEnv.js.map +1 -1
  10. package/lib/index.d.mts +37 -1
  11. package/lib/index.d.ts +37 -1
  12. package/lib/index.esm.js +223 -224
  13. package/lib/index.esm.js.map +1 -1
  14. package/lib/index.js.map +1 -1
  15. package/lib/index.mjs +223 -224
  16. package/lib/index.mjs.map +1 -1
  17. package/lib/workers/getCliConfig.js.map +1 -1
  18. package/lib/workers/typegenGenerate.js.map +1 -1
  19. package/package.json +17 -19
  20. package/src/CommandRunner.ts +1 -2
  21. package/src/actions/init-project/{bootstrapTemplate.ts → bootstrapLocalTemplate.ts} +8 -21
  22. package/src/actions/init-project/bootstrapRemoteTemplate.ts +118 -0
  23. package/src/actions/init-project/git.ts +2 -2
  24. package/src/actions/init-project/initProject.ts +158 -146
  25. package/src/actions/init-project/readPackageJson.ts +18 -0
  26. package/src/actions/init-project/templates/nextjs/index.ts +16 -0
  27. package/src/actions/init-project/templates/nextjs/schemaTypes/blog.ts +2 -2
  28. package/src/actions/init-project/updateInitialTemplateMetadata.ts +24 -0
  29. package/src/actions/login/login.ts +2 -3
  30. package/src/actions/versions/findSanityModuleVersions.ts +0 -1
  31. package/src/commands/index.ts +2 -2
  32. package/src/commands/init/initCommand.ts +7 -67
  33. package/src/commands/learn/learnCommand.ts +20 -0
  34. package/src/commands/logout/logoutCommand.ts +1 -1
  35. package/src/outputters/cliOutputter.ts +21 -8
  36. package/src/studioDependencies.ts +1 -1
  37. package/src/types.ts +41 -1
  38. package/src/util/frameworkPort.ts +63 -0
  39. package/src/util/generateCommandsDocumentation.ts +7 -4
  40. package/src/util/getCliConfig.ts +1 -1
  41. package/src/util/getProviderName.ts +9 -0
  42. package/src/util/remoteTemplate.ts +320 -0
  43. package/templates/get-started/plugins/sanity-plugin-tutorial/GetStartedTutorial.tsx +4 -4
  44. package/src/actions/init-plugin/initPlugin.ts +0 -119
  45. package/src/actions/init-plugin/pluginTemplates.ts +0 -38
  46. package/src/actions/init-project/reconfigureV2Project.ts +0 -446
  47. package/src/commands/upgrade/upgradeCommand.ts +0 -38
  48. package/src/commands/upgrade/upgradeDependencies.ts +0 -289
@@ -1,16 +1,16 @@
1
- import {existsSync, readFileSync} from 'node:fs'
1
+ import {existsSync} from 'node:fs'
2
2
  import fs from 'node:fs/promises'
3
3
  import path from 'node:path'
4
4
 
5
5
  import {type DatasetAclMode, type SanityProject} from '@sanity/client'
6
6
  import {type Framework} from '@vercel/frameworks'
7
+ import {type detectFrameworkRecord} from '@vercel/fs-detectors'
7
8
  import dotenv from 'dotenv'
8
9
  import execa, {type CommonOptions} from 'execa'
9
10
  import {deburr, noop} from 'lodash'
10
11
  import pFilter from 'p-filter'
11
12
  import resolveFrom from 'resolve-from'
12
- import {evaluate, patch} from 'silver-fleece'
13
- import which from 'which'
13
+ import semver from 'semver'
14
14
 
15
15
  import {CLIInitStepCompleted} from '../../__telemetry__/init.telemetry'
16
16
  import {type InitFlags} from '../../commands/init/initCommand'
@@ -33,17 +33,21 @@ import {
33
33
  type CliCommandDefinition,
34
34
  type SanityCore,
35
35
  type SanityModuleInternal,
36
+ type SanityUser,
36
37
  } from '../../types'
37
38
  import {getClientWrapper} from '../../util/clientWrapper'
38
39
  import {dynamicRequire} from '../../util/dynamicRequire'
39
40
  import {getProjectDefaults, type ProjectDefaults} from '../../util/getProjectDefaults'
41
+ import {getProviderName} from '../../util/getProviderName'
40
42
  import {getUserConfig} from '../../util/getUserConfig'
41
43
  import {isCommandGroup} from '../../util/isCommandGroup'
42
44
  import {isInteractive} from '../../util/isInteractive'
43
45
  import {fetchJourneyConfig} from '../../util/journeyConfig'
46
+ import {checkIsRemoteTemplate, getGitHubRepoInfo, type RepoInfo} from '../../util/remoteTemplate'
44
47
  import {login, type LoginFlags} from '../login/login'
45
48
  import {createProject} from '../project/createProject'
46
- import {type BootstrapOptions, bootstrapTemplate} from './bootstrapTemplate'
49
+ import {bootstrapLocalTemplate} from './bootstrapLocalTemplate'
50
+ import {bootstrapRemoteTemplate} from './bootstrapRemoteTemplate'
47
51
  import {type GenerateConfigOptions} from './createStudioConfig'
48
52
  import {absolutify, validateEmptyPath} from './fsUtils'
49
53
  import {tryGitInit} from './git'
@@ -55,7 +59,7 @@ import {
55
59
  promptForNextTemplate,
56
60
  promptForStudioPath,
57
61
  } from './prompts/nextjs'
58
- import {reconfigureV2Project} from './reconfigureV2Project'
62
+ import {readPackageJson} from './readPackageJson'
59
63
  import templates from './templates'
60
64
  import {
61
65
  sanityCliTemplate,
@@ -65,19 +69,13 @@ import {
65
69
  } from './templates/nextjs'
66
70
 
67
71
  // eslint-disable-next-line no-process-env
68
- const isCI = process.env.CI
72
+ const isCI = Boolean(process.env.CI)
69
73
 
70
74
  /**
71
75
  * @deprecated - No longer used
72
76
  */
73
77
  export interface InitOptions {
74
78
  template: string
75
- // /**
76
- // * Used for initializing a project from a server schema that is saved in the Journey API
77
- // * This will override the `template` option.
78
- // * @beta
79
- // */
80
- // journeyProjectId?: string
81
79
  outputDir: string
82
80
  name: string
83
81
  displayName: string
@@ -110,24 +108,20 @@ export interface ProjectOrganization {
110
108
  // eslint-disable-next-line max-statements, complexity
111
109
  export default async function initSanity(
112
110
  args: CliCommandArguments<InitFlags>,
113
- context: CliCommandContext & {detectedFramework: Framework | null},
111
+ context: CliCommandContext & {
112
+ detectedFramework: Awaited<ReturnType<typeof detectFrameworkRecord>>
113
+ },
114
114
  ): Promise<void> {
115
- const {
116
- output,
117
- prompt,
118
- workDir,
119
- apiClient,
120
- chalk,
121
- sanityMajorVersion,
122
- telemetry,
123
- detectedFramework,
124
- } = context
115
+ const {output, prompt, workDir, apiClient, chalk, telemetry, detectedFramework} = context
125
116
 
126
117
  const trace = telemetry.trace(CLIInitStepCompleted)
127
118
 
128
119
  const cliFlags = args.extOptions
129
120
  const unattended = cliFlags.y || cliFlags.yes
130
121
  const print = unattended ? noop : output.print
122
+ const success = output.success
123
+ const warn = output.warn
124
+
131
125
  const intendedPlan = cliFlags['project-plan']
132
126
  const intendedCoupon = cliFlags.coupon
133
127
  const reconfigure = cliFlags.reconfigure
@@ -137,6 +131,11 @@ export default async function initSanity(
137
131
  const env = cliFlags.env
138
132
  const packageManager = cliFlags['package-manager']
139
133
 
134
+ let remoteTemplateInfo: RepoInfo | undefined
135
+ if (cliFlags.template && checkIsRemoteTemplate(cliFlags.template)) {
136
+ remoteTemplateInfo = await getGitHubRepoInfo(cliFlags.template, cliFlags['template-token'])
137
+ }
138
+
140
139
  let defaultConfig = cliFlags['dataset-default']
141
140
  let showDefaultConfigPrompt = !defaultConfig
142
141
 
@@ -155,9 +154,10 @@ export default async function initSanity(
155
154
  },
156
155
  })
157
156
 
158
- if (sanityMajorVersion === 2) {
159
- await reconfigureV2Project(args, context)
160
- return
157
+ if (detectedFramework && detectedFramework.slug !== 'sanity' && remoteTemplateInfo) {
158
+ throw new Error(
159
+ `A remote template cannot be used with a detected framework. Detected: ${detectedFramework.name}`,
160
+ )
161
161
  }
162
162
 
163
163
  // Only allow either --project-plan or --coupon
@@ -246,24 +246,8 @@ export default async function initSanity(
246
246
  }
247
247
  const envFilename = typeof env === 'string' ? env : envFilenameDefault
248
248
  if (!envFilename.startsWith('.env')) {
249
- throw new Error(`Env filename must start with .env`)
250
- }
251
-
252
- const usingBareOrEnv = cliFlags.bare || cliFlags.env
253
- print(
254
- cliFlags.quickstart
255
- ? "You're ejecting a remote Sanity project!"
256
- : `You're setting up a new project!`,
257
- )
258
- print(`We'll make sure you have an account with Sanity.io. ${usingBareOrEnv ? '' : `Then we'll`}`)
259
- if (!usingBareOrEnv) {
260
- print('install an open-source JS content editor that connects to')
261
- print('the real-time hosted API on Sanity.io. Hang on.\n')
249
+ throw new Error('Env filename must start with .env')
262
250
  }
263
- print('Press ctrl + C at any time to quit.\n')
264
- print('Prefer web interfaces to terminals?')
265
- print('You can also set up best practice Sanity projects with')
266
- print('your favorite frontends on https://www.sanity.io/templates\n')
267
251
 
268
252
  // If the user isn't already authenticated, make it so
269
253
  const userConfig = getUserConfig()
@@ -272,12 +256,20 @@ export default async function initSanity(
272
256
  debug(hasToken ? 'User already has a token' : 'User has no token')
273
257
  if (hasToken) {
274
258
  trace.log({step: 'login', alreadyLoggedIn: true})
275
- print('Looks like you already have a Sanity-account. Sweet!\n')
259
+ const user = await getUserData(apiClient)
260
+ success('You are logged in as %s using %s', user.email, getProviderName(user.provider))
276
261
  } else if (!unattended) {
277
262
  trace.log({step: 'login'})
278
263
  await getOrCreateUser()
279
264
  }
280
265
 
266
+ let introMessage = 'Fetching existing projects'
267
+ if (cliFlags.quickstart) {
268
+ introMessage = "Eject your existing project's Sanity configuration"
269
+ }
270
+ success(introMessage)
271
+ print('')
272
+
281
273
  const flags = await prepareFlags()
282
274
  // We're authenticated, now lets select or create a project
283
275
  const {projectId, displayName, isFirstProject, datasetName, schemaUrl} = await getProjectDetails()
@@ -288,7 +280,8 @@ export default async function initSanity(
288
280
 
289
281
  // If user doesn't want to output any template code
290
282
  if (bareOutput) {
291
- print(`\n${chalk.green('Success!')} Below are your project details:\n`)
283
+ success('Below are your project details')
284
+ print('')
292
285
  print(`Project ID: ${chalk.cyan(projectId)}`)
293
286
  print(`Dataset: ${chalk.cyan(datasetName)}`)
294
287
  print(
@@ -298,7 +291,8 @@ export default async function initSanity(
298
291
  }
299
292
 
300
293
  let initNext = false
301
- if (detectedFramework?.slug === 'nextjs') {
294
+ const isNextJs = detectedFramework?.slug === 'nextjs'
295
+ if (isNextJs) {
302
296
  initNext = await prompt.single({
303
297
  type: 'confirm',
304
298
  message:
@@ -327,6 +321,26 @@ export default async function initSanity(
327
321
  // Ensure we are using the output path provided by user
328
322
  outputPath = answers.outputPath
329
323
 
324
+ if (isNextJs) {
325
+ const packageJson = readPackageJson(`${outputPath}/package.json`)
326
+ const reactVersion = packageJson?.dependencies?.react
327
+
328
+ if (reactVersion) {
329
+ const isUsingReact19 = semver.coerce(reactVersion)?.major === 19
330
+ const isUsingNextJs15 = semver.coerce(detectedFramework?.detectedVersion)?.major === 15
331
+
332
+ if (isUsingNextJs15 && isUsingReact19) {
333
+ warn('╭────────────────────────────────────────────────────────────╮')
334
+ warn('│ │')
335
+ warn('│ It looks like you are using Next.js 15 and React 19 │')
336
+ warn('│ Please read our compatibility guide. │')
337
+ warn('│ https://www.sanity.io/help/react-19 │')
338
+ warn('│ │')
339
+ warn('╰────────────────────────────────────────────────────────────╯')
340
+ }
341
+ }
342
+ }
343
+
330
344
  if (initNext) {
331
345
  const useTypeScript = unattended ? true : await promptForTypeScript(prompt)
332
346
  trace.log({step: 'useTypeScript', selectedOption: useTypeScript ? 'yes' : 'no'})
@@ -419,21 +433,6 @@ export default async function initSanity(
419
433
 
420
434
  await writeSourceFiles(sanityFolder(useTypeScript, templateToUse), undefined, hasSrcFolder)
421
435
 
422
- // set tsconfig.json target to ES2017
423
- const tsConfigPath = path.join(workDir, 'tsconfig.json')
424
-
425
- if (useTypeScript && existsSync(tsConfigPath)) {
426
- const tsConfigFile = readFileSync(tsConfigPath, 'utf8')
427
- const config = evaluate(tsConfigFile)
428
-
429
- if (config.compilerOptions.target?.toLowerCase() !== 'es2017') {
430
- config.compilerOptions.target = 'ES2017'
431
-
432
- const newConfig = patch(tsConfigFile, config)
433
- await fs.writeFile(tsConfigPath, Buffer.from(newConfig))
434
- }
435
- }
436
-
437
436
  const appendEnv = unattended ? true : await promptForAppendEnv(prompt, envFilename)
438
437
 
439
438
  if (appendEnv) {
@@ -496,7 +495,7 @@ export default async function initSanity(
496
495
  }
497
496
 
498
497
  if (chosen === 'npm') {
499
- await execa('npm', ['install', 'next-sanity@9'], execOptions)
498
+ await execa('npm', ['install', '--legacy-peer-deps', 'next-sanity@9'], execOptions)
500
499
  } else if (chosen === 'yarn') {
501
500
  await execa('npx', ['install-peerdeps', '--yarn', 'next-sanity@9'], execOptions)
502
501
  } else if (chosen === 'pnpm') {
@@ -553,18 +552,20 @@ export default async function initSanity(
553
552
  const templateName = await selectProjectTemplate()
554
553
  trace.log({step: 'selectProjectTemplate', selectedOption: templateName})
555
554
  const template = templates[templateName]
556
- if (!template) {
555
+ if (!remoteTemplateInfo && !template) {
557
556
  throw new Error(`Template "${templateName}" not found`)
558
557
  }
559
558
 
560
559
  // Use typescript?
561
- const typescriptOnly = template.typescriptOnly === true
562
560
  let useTypeScript = true
563
- if (!typescriptOnly && typeof cliFlags.typescript === 'boolean') {
564
- useTypeScript = cliFlags.typescript
565
- } else if (!typescriptOnly && !unattended) {
566
- useTypeScript = await promptForTypeScript(prompt)
567
- trace.log({step: 'useTypeScript', selectedOption: useTypeScript ? 'yes' : 'no'})
561
+ if (!remoteTemplateInfo && template) {
562
+ const typescriptOnly = template.typescriptOnly === true
563
+ if (!typescriptOnly && typeof cliFlags.typescript === 'boolean') {
564
+ useTypeScript = cliFlags.typescript
565
+ } else if (!typescriptOnly && !unattended) {
566
+ useTypeScript = await promptForTypeScript(prompt)
567
+ trace.log({step: 'useTypeScript', selectedOption: useTypeScript ? 'yes' : 'no'})
568
+ }
568
569
  }
569
570
 
570
571
  // we enable auto-updates by default, but allow users to specify otherwise
@@ -573,47 +574,15 @@ export default async function initSanity(
573
574
  autoUpdates = cliFlags['auto-updates']
574
575
  }
575
576
 
576
- // Build a full set of resolved options
577
- const templateOptions: BootstrapOptions = {
578
- outputPath,
579
- packageName: sluggedName,
580
- templateName,
581
- schemaUrl,
582
- useTypeScript,
583
- variables: {
584
- autoUpdates,
585
- dataset: datasetName,
586
- projectId,
587
- projectName: displayName || answers.projectName,
588
- },
589
- }
590
-
591
577
  // If the template has a sample dataset, prompt the user whether or not we should import it
592
578
  const shouldImport =
593
- !unattended && template.datasetUrl && (await promptForDatasetImport(template.importPrompt))
579
+ !unattended && template?.datasetUrl && (await promptForDatasetImport(template.importPrompt))
594
580
 
595
581
  trace.log({step: 'importTemplateDataset', selectedOption: shouldImport ? 'yes' : 'no'})
596
582
 
597
583
  const [_, bootstrapPromise] = await Promise.allSettled([
598
- // record template files attempted to be created locally
599
- apiClient({api: {projectId: projectId}})
600
- .request<SanityProject>({uri: `/projects/${projectId}`})
601
- .then((project: SanityProject) => {
602
- if (!project?.metadata?.cliInitializedAt) {
603
- return apiClient({api: {projectId}}).request({
604
- method: 'PATCH',
605
- uri: `/projects/${projectId}`,
606
- body: {metadata: {cliInitializedAt: new Date().toISOString()}},
607
- })
608
- }
609
- return Promise.resolve()
610
- })
611
- .catch(() => {
612
- // Non-critical update
613
- debug('Failed to update cliInitializedAt metadata')
614
- }),
615
- // Bootstrap Sanity, creating required project files, manifests etc
616
- bootstrapTemplate(templateOptions, context),
584
+ updateProjectCliInitializedMetadata(),
585
+ bootstrapTemplate(),
617
586
  ])
618
587
 
619
588
  if (bootstrapPromise.status === 'rejected' && bootstrapPromise.reason instanceof Error) {
@@ -667,13 +636,11 @@ export default async function initSanity(
667
636
  context,
668
637
  })
669
638
 
670
- if (await hasGlobalCli()) {
671
- print('')
672
- print('If you want to delete the imported data, use')
673
- print(` ${chalk.cyan(`sanity dataset delete ${datasetName}`)}`)
674
- print('and create a new clean dataset with')
675
- print(` ${chalk.cyan(`sanity dataset create <name>`)}\n`)
676
- }
639
+ print('')
640
+ print('If you want to delete the imported data, use')
641
+ print(` ${chalk.cyan(`npx sanity dataset delete ${datasetName}`)}`)
642
+ print('and create a new clean dataset with')
643
+ print(` ${chalk.cyan(`npx sanity dataset create <name>`)}\n`)
677
644
  }
678
645
 
679
646
  const devCommandMap: Record<PackageManager, string> = {
@@ -695,12 +662,10 @@ export default async function initSanity(
695
662
  print(`Then: ${chalk.cyan(devCommand)} - to run Sanity Studio\n`)
696
663
  }
697
664
 
698
- if (await hasGlobalCli()) {
699
- print(`Other helpful commands`)
700
- print(`sanity docs - to open the documentation in a browser`)
701
- print(`sanity manage - to open the project settings in a browser`)
702
- print(`sanity help - to explore the CLI manual`)
703
- }
665
+ print(`Other helpful commands`)
666
+ print(`npx sanity docs - to open the documentation in a browser`)
667
+ print(`npx sanity manage - to open the project settings in a browser`)
668
+ print(`npx sanity help - to explore the CLI manual`)
704
669
 
705
670
  const sendInvite =
706
671
  isFirstProject &&
@@ -725,16 +690,13 @@ export default async function initSanity(
725
690
  trace.complete()
726
691
 
727
692
  async function getOrCreateUser() {
728
- print(`We can't find any auth credentials in your Sanity config`)
729
- print('- log in or create a new account\n')
693
+ warn('No authentication credentials found in your Sanity config')
694
+ print('')
730
695
 
731
696
  // Provide login options (`sanity login`)
732
697
  const {extOptions, ...otherArgs} = args
733
698
  const loginArgs: CliCommandArguments<LoginFlags> = {...otherArgs, extOptions: {}}
734
699
  await login(loginArgs, {...context, telemetry: trace.newContext('login')})
735
-
736
- print("Good stuff, you're now authenticated. You'll need a project to keep your")
737
- print('datasets and collaborators safe and snug.')
738
700
  }
739
701
 
740
702
  async function getProjectDetails(): Promise<{
@@ -795,21 +757,19 @@ export default async function initSanity(
795
757
  isFirstProject: boolean
796
758
  userAction: 'create' | 'select'
797
759
  }> {
798
- const spinner = context.output.spinner('Fetching existing projects').start()
760
+ const client = apiClient({requireUser: true, requireProject: false})
799
761
  let projects
800
762
  let organizations: ProjectOrganization[]
763
+
801
764
  try {
802
- const client = apiClient({requireUser: true, requireProject: false})
803
765
  const [allProjects, allOrgs] = await Promise.all([
804
766
  client.projects.list({includeMembers: false}),
805
767
  client.request({uri: '/organizations'}),
806
768
  ])
807
769
  projects = allProjects.sort((a, b) => b.createdAt.localeCompare(a.createdAt))
808
770
  organizations = allOrgs
809
- spinner.succeed()
810
771
  } catch (err) {
811
772
  if (unattended && flags.project) {
812
- spinner.succeed()
813
773
  return {
814
774
  projectId: flags.project,
815
775
  displayName: 'Unknown project',
@@ -817,7 +777,6 @@ export default async function initSanity(
817
777
  userAction: 'select',
818
778
  }
819
779
  }
820
- spinner.fail()
821
780
  throw new Error(`Failed to communicate with the Sanity API:\n${err.message}`)
822
781
  }
823
782
 
@@ -887,11 +846,11 @@ export default async function initSanity(
887
846
 
888
847
  const projectChoices = projects.map((project) => ({
889
848
  value: project.id,
890
- name: `${project.displayName} [${project.id}]`,
849
+ name: `${project.displayName} (${project.id})`,
891
850
  }))
892
851
 
893
852
  const selected = await prompt.single({
894
- message: 'Select project to use',
853
+ message: 'Create a new project or select an existing one',
895
854
  type: 'list',
896
855
  choices: [
897
856
  {value: 'new', name: 'Create new project'},
@@ -1070,25 +1029,77 @@ export default async function initSanity(
1070
1029
  type: 'list',
1071
1030
  choices: [
1072
1031
  {
1073
- value: 'moviedb',
1074
- name: 'Movie project (schema + sample data)',
1075
- },
1076
- {
1077
- value: 'shopify',
1078
- name: 'E-commerce (Shopify)',
1032
+ value: 'clean',
1033
+ name: 'Clean project with no predefined schema types',
1079
1034
  },
1080
1035
  {
1081
1036
  value: 'blog',
1082
1037
  name: 'Blog (schema)',
1083
1038
  },
1084
1039
  {
1085
- value: 'clean',
1086
- name: 'Clean project with no predefined schema types',
1040
+ value: 'shopify',
1041
+ name: 'E-commerce (Shopify)',
1042
+ },
1043
+ {
1044
+ value: 'moviedb',
1045
+ name: 'Movie project (schema + sample data)',
1087
1046
  },
1088
1047
  ],
1089
1048
  })
1090
1049
  }
1091
1050
 
1051
+ async function updateProjectCliInitializedMetadata() {
1052
+ try {
1053
+ const client = apiClient({api: {projectId}})
1054
+ const project = await client.request<SanityProject>({uri: `/projects/${projectId}`})
1055
+
1056
+ if (!project?.metadata?.cliInitializedAt) {
1057
+ await client.request({
1058
+ method: 'PATCH',
1059
+ uri: `/projects/${projectId}`,
1060
+ body: {metadata: {cliInitializedAt: new Date().toISOString()}},
1061
+ })
1062
+ }
1063
+ } catch (err) {
1064
+ // Non-critical update
1065
+ debug('Failed to update cliInitializedAt metadata')
1066
+ }
1067
+ }
1068
+
1069
+ async function bootstrapTemplate() {
1070
+ const bootstrapVariables: GenerateConfigOptions['variables'] = {
1071
+ autoUpdates,
1072
+ dataset: datasetName,
1073
+ projectId,
1074
+ projectName: displayName || answers.projectName,
1075
+ }
1076
+
1077
+ if (remoteTemplateInfo) {
1078
+ return bootstrapRemoteTemplate(
1079
+ {
1080
+ outputPath,
1081
+ packageName: sluggedName,
1082
+ repoInfo: remoteTemplateInfo,
1083
+ bearerToken: cliFlags['template-token'],
1084
+ variables: bootstrapVariables,
1085
+ },
1086
+ context,
1087
+ )
1088
+ }
1089
+
1090
+ return bootstrapLocalTemplate(
1091
+ {
1092
+ outputPath,
1093
+ packageName: sluggedName,
1094
+ templateName,
1095
+ schemaUrl,
1096
+ useTypeScript,
1097
+ variables: bootstrapVariables,
1098
+ },
1099
+ context,
1100
+ )
1101
+ }
1102
+
1092
1103
  async function getProjectInfo(): Promise<ProjectDefaults & {outputPath: string}> {
1093
1104
  const specifiedPath = flags['output-path'] && path.resolve(flags['output-path'])
1094
1105
 
@@ -1451,6 +1462,16 @@ async function getPlanFromCoupon(apiClient: CliApiClient, couponCode: string): P
1451
1462
  return planId
1452
1463
  }
1453
1464
 
1465
+ async function getUserData(apiClient: CliApiClient): Promise<SanityUser> {
1466
+ return await apiClient({
1467
+ requireUser: true,
1468
+ requireProject: false,
1469
+ }).request({
1470
+ method: 'GET',
1471
+ uri: 'users/me',
1472
+ })
1473
+ }
1474
+
1454
1475
  async function getPlanFromId(apiClient: CliApiClient, planId: string): Promise<string> {
1455
1476
  const response = await apiClient({
1456
1477
  requireUser: false,
@@ -1509,12 +1530,3 @@ function getImportCommand(
1509
1530
  !isCommandGroup(cmd) && cmd.name === 'import' && cmd.group === 'dataset',
1510
1531
  )
1511
1532
  }
1512
-
1513
- async function hasGlobalCli(): Promise<boolean> {
1514
- try {
1515
- const globalCliPath = await which('sanity')
1516
- return Boolean(globalCliPath)
1517
- } catch (err) {
1518
- return false
1519
- }
1520
- }
@@ -0,0 +1,18 @@
1
+ import fs from 'node:fs'
2
+
3
+ import {type PackageJson} from '../../types'
4
+
5
+ /**
6
+ * Read the `package.json` file at the given path
7
+ *
8
+ * @param filePath - Path to package.json to read
9
+ * @returns The parsed package.json
10
+ */
11
+ export function readPackageJson(filePath: string): PackageJson | undefined {
12
+ try {
13
+ // eslint-disable-next-line no-sync
14
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'))
15
+ } catch (err) {
16
+ return undefined
17
+ }
18
+ }
@@ -165,6 +165,21 @@ export const client = createClient({
165
165
  })
166
166
  `
167
167
 
168
+ const live = `// Querying with "sanityFetch" will keep content automatically updated
169
+ // Before using it, import and render "<SanityLive />" in your layout, see
170
+ // https://github.com/sanity-io/next-sanity#live-content-api for more information.
171
+ import { defineLive } from "next-sanity";
172
+ import { client } from './client'
173
+
174
+ export const { sanityFetch, SanityLive } = defineLive({
175
+ client: client.withConfig({
176
+ // Live content is currently only available on the experimental API
177
+ // https://www.sanity.io/docs/api-versioning
178
+ apiVersion: 'vX'
179
+ })
180
+ });
181
+ `
182
+
168
183
  const imageTS = `import createImageUrlBuilder from '@sanity/image-url'
169
184
  import { SanityImageSource } from "@sanity/image-url/lib/types/types";
170
185
 
@@ -201,6 +216,7 @@ export const sanityFolder = (
201
216
  'env.': useTypeScript ? envTS : envJS,
202
217
  'lib': {
203
218
  'client.': client,
219
+ 'live.': live,
204
220
  'image.': useTypeScript ? imageTS : imageJS,
205
221
  },
206
222
  }
@@ -186,11 +186,11 @@ export const postType = defineType({
186
186
  hotspot: true,
187
187
  },
188
188
  fields: [
189
- {
189
+ defineField({
190
190
  name: 'alt',
191
191
  type: 'string',
192
192
  title: 'Alternative text',
193
- }
193
+ })
194
194
  ]
195
195
  }),
196
196
  defineField({
@@ -0,0 +1,24 @@
1
+ import {debug} from '../../debug'
2
+ import {type CliApiClient} from '../../types'
3
+
4
+ export async function updateInitialTemplateMetadata(
5
+ apiClient: CliApiClient,
6
+ projectId: string,
7
+ templateName: string,
8
+ ): Promise<void> {
9
+ try {
10
+ await apiClient({api: {projectId}}).request({
11
+ method: 'PATCH',
12
+ uri: `/projects/${projectId}`,
13
+ body: {metadata: {initialTemplate: templateName}},
14
+ })
15
+ } catch (err: unknown) {
16
+ // Non-critical that we update this metadata, and user does not need to be aware
17
+ let message = typeof err === 'string' ? err : '<unknown error>'
18
+ if (err instanceof Error) {
19
+ message = err.message
20
+ }
21
+
22
+ debug('Failed to update initial template metadata for project: %s', message)
23
+ }
24
+ }
@@ -2,7 +2,6 @@ import http, {type Server} from 'node:http'
2
2
  import os from 'node:os'
3
3
 
4
4
  import {type SanityClient} from '@sanity/client'
5
- import chalk from 'chalk'
6
5
  import open from 'open'
7
6
 
8
7
  import {debug as debugIt} from '../../debug'
@@ -146,7 +145,7 @@ export async function login(
146
145
  })
147
146
  }
148
147
 
149
- output.print(chalk.green('Login successful'))
148
+ output.success('Login successful')
150
149
  trace.complete()
151
150
  }
152
151
 
@@ -335,7 +334,7 @@ async function promptProviders(
335
334
 
336
335
  const provider = await prompt.single({
337
336
  type: 'list',
338
- message: 'Login type',
337
+ message: 'Please log in or create a new account',
339
338
  choices: providers.map((choice) => choice.title),
340
339
  })
341
340
 
@@ -21,7 +21,6 @@ import {getLocalVersion} from '../../util/getLocalVersion'
21
21
  const PACKAGES_TO_EXCLUDE = [
22
22
  '@sanity/block-content-to-html',
23
23
  '@sanity/block-content-to-react',
24
- '@sanity/block-tools',
25
24
  '@sanity/client',
26
25
  ]
27
26