@sanity/cli 3.65.2-corel.472 → 3.66.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.
- package/lib/_chunks-cjs/cli.js +445 -1082
- package/lib/_chunks-cjs/cli.js.map +1 -1
- package/lib/index.d.mts +0 -1
- package/lib/index.d.ts +0 -1
- package/package.json +7 -7
- package/src/CommandRunner.ts +1 -2
- package/src/actions/init-project/bootstrapRemoteTemplate.ts +10 -7
- package/src/actions/init-project/initProject.ts +1 -17
- package/src/commands/index.ts +0 -2
- package/src/commands/init/initCommand.ts +0 -9
- package/src/types.ts +0 -1
- package/src/util/remoteTemplate.ts +16 -0
- package/src/actions/init-plugin/initPlugin.ts +0 -119
- package/src/actions/init-plugin/pluginTemplates.ts +0 -38
- package/src/actions/init-project/reconfigureV2Project.ts +0 -446
- package/src/commands/upgrade/upgradeCommand.ts +0 -38
- package/src/commands/upgrade/upgradeDependencies.ts +0 -289
@@ -1,446 +0,0 @@
|
|
1
|
-
import path from 'node:path'
|
2
|
-
|
3
|
-
import {type DatasetAclMode} from '@sanity/client'
|
4
|
-
import {noop} from 'lodash'
|
5
|
-
|
6
|
-
import {type InitFlags} from '../../commands/init/initCommand'
|
7
|
-
import {debug} from '../../debug'
|
8
|
-
import {type CliCommandArguments, type CliCommandContext, type SanityJson} from '../../types'
|
9
|
-
import {getUserConfig} from '../../util/getUserConfig'
|
10
|
-
import {pathExists} from '../../util/pathExists'
|
11
|
-
import {readJson} from '../../util/readJson'
|
12
|
-
import {writeJson} from '../../util/writeJson'
|
13
|
-
import {login, type LoginFlags} from '../login/login'
|
14
|
-
import {createProject} from '../project/createProject'
|
15
|
-
import {promptForDatasetName} from './promptForDatasetName'
|
16
|
-
import {promptForAclMode, promptForDefaultConfig} from './prompts'
|
17
|
-
|
18
|
-
/* eslint-disable no-process-env */
|
19
|
-
const isCI = process.env.CI
|
20
|
-
/* eslint-enable no-process-env */
|
21
|
-
|
22
|
-
// eslint-disable-next-line max-statements, complexity
|
23
|
-
export async function reconfigureV2Project(
|
24
|
-
args: CliCommandArguments<InitFlags>,
|
25
|
-
context: CliCommandContext,
|
26
|
-
): Promise<void> {
|
27
|
-
const {output, prompt, workDir, apiClient, yarn, chalk} = context
|
28
|
-
const cliFlags = args.extOptions
|
29
|
-
const unattended = cliFlags.y || cliFlags.yes
|
30
|
-
const print = unattended ? noop : output.print
|
31
|
-
|
32
|
-
let defaultConfig = cliFlags['dataset-default']
|
33
|
-
let showDefaultConfigPrompt = !defaultConfig
|
34
|
-
|
35
|
-
let selectedPlan: string | undefined
|
36
|
-
|
37
|
-
// Check if we have a project manifest already
|
38
|
-
const manifestPath = path.join(workDir, 'sanity.json')
|
39
|
-
let projectManifest = await readJson<SanityJson>(manifestPath)
|
40
|
-
|
41
|
-
// If we are in a Sanity studio project folder and the project manifest has projectId/dataset,
|
42
|
-
// ASK if we want to reconfigure. If no projectId/dataset is present, we assume reconfigure
|
43
|
-
const hasProjectId = projectManifest && projectManifest.api && projectManifest.api.projectId
|
44
|
-
|
45
|
-
print(`The Sanity Studio in this folder will be tied to a new project on Sanity.io!`)
|
46
|
-
if (hasProjectId) {
|
47
|
-
print('The previous project configuration will be overwritten.')
|
48
|
-
}
|
49
|
-
print(`We're first going to make sure you have an account with Sanity.io. Hang on.`)
|
50
|
-
print('Press ctrl + C at any time to quit.\n')
|
51
|
-
|
52
|
-
// If the user isn't already authenticated, make it so
|
53
|
-
const userConfig = getUserConfig()
|
54
|
-
const hasToken = userConfig.get('authToken')
|
55
|
-
|
56
|
-
debug(hasToken ? 'User already has a token' : 'User has no token')
|
57
|
-
|
58
|
-
if (hasToken) {
|
59
|
-
print('Looks like you already have a Sanity-account. Sweet!\n')
|
60
|
-
} else if (!unattended) {
|
61
|
-
await getOrCreateUser()
|
62
|
-
}
|
63
|
-
|
64
|
-
const flags = await prepareFlags()
|
65
|
-
|
66
|
-
// We're authenticated, now lets select or create a project
|
67
|
-
debug('Prompting user to select or create a project')
|
68
|
-
const {projectId, displayName, isFirstProject} = await getOrCreateProject()
|
69
|
-
debug(`Project with name ${displayName} selected`)
|
70
|
-
|
71
|
-
// Now let's pick or create a dataset
|
72
|
-
debug('Prompting user to select or create a dataset')
|
73
|
-
const {datasetName} = await getOrCreateDataset({
|
74
|
-
projectId,
|
75
|
-
displayName,
|
76
|
-
dataset: flags.dataset,
|
77
|
-
aclMode: flags.visibility,
|
78
|
-
defaultConfig: flags['dataset-default'],
|
79
|
-
})
|
80
|
-
|
81
|
-
debug(`Dataset with name ${datasetName} selected`)
|
82
|
-
|
83
|
-
const outputPath = workDir
|
84
|
-
let successMessage
|
85
|
-
|
86
|
-
// Rewrite project manifest (sanity.json)
|
87
|
-
const projectInfo = projectManifest.project || {}
|
88
|
-
const newProps = {
|
89
|
-
root: true,
|
90
|
-
api: {
|
91
|
-
...(projectManifest.api || {}),
|
92
|
-
projectId,
|
93
|
-
dataset: datasetName,
|
94
|
-
},
|
95
|
-
project: {
|
96
|
-
...projectInfo,
|
97
|
-
// Keep original name if present
|
98
|
-
name: projectInfo.name || displayName,
|
99
|
-
},
|
100
|
-
}
|
101
|
-
|
102
|
-
// Ensure root, api and project keys are at top to follow sanity.json key order convention
|
103
|
-
projectManifest = {
|
104
|
-
...newProps,
|
105
|
-
...projectManifest,
|
106
|
-
...newProps,
|
107
|
-
}
|
108
|
-
|
109
|
-
await writeJson(manifestPath, projectManifest)
|
110
|
-
|
111
|
-
const hasNodeModules = await pathExists(path.join(workDir, 'node_modules'))
|
112
|
-
if (hasNodeModules) {
|
113
|
-
print('Skipping installation of dependencies since node_modules exists.')
|
114
|
-
print('Run sanity install to reinstall dependencies')
|
115
|
-
} else {
|
116
|
-
try {
|
117
|
-
await yarn(['install'], {...output, rootDir: workDir})
|
118
|
-
} catch (err) {
|
119
|
-
throw err
|
120
|
-
}
|
121
|
-
}
|
122
|
-
|
123
|
-
print(`\n${chalk.green('Success!')} Now what?\n`)
|
124
|
-
|
125
|
-
const isCurrentDir = outputPath === process.cwd()
|
126
|
-
if (!isCurrentDir) {
|
127
|
-
print(`▪ ${chalk.cyan(`cd ${outputPath}`)}, then:`)
|
128
|
-
}
|
129
|
-
|
130
|
-
print(`▪ ${chalk.cyan('sanity docs')} to open the documentation in a browser`)
|
131
|
-
print(`▪ ${chalk.cyan('sanity manage')} to open the project settings in a browser`)
|
132
|
-
print(`▪ ${chalk.cyan('sanity help')} to explore the CLI manual`)
|
133
|
-
print(`▪ ${chalk.green('sanity start')} to run your studio\n`) // v2 uses `start`, not `dev`
|
134
|
-
|
135
|
-
if (successMessage) {
|
136
|
-
print(`\n${successMessage}`)
|
137
|
-
}
|
138
|
-
|
139
|
-
const sendInvite =
|
140
|
-
isFirstProject &&
|
141
|
-
(await prompt.single({
|
142
|
-
type: 'confirm',
|
143
|
-
message:
|
144
|
-
'We have an excellent developer community, would you like us to send you an invitation to join?',
|
145
|
-
default: true,
|
146
|
-
}))
|
147
|
-
|
148
|
-
if (sendInvite) {
|
149
|
-
// Intentionally leave the promise "dangling" since we don't want to stall while waiting for this
|
150
|
-
apiClient({requireProject: false})
|
151
|
-
.request({
|
152
|
-
uri: '/invitations/community',
|
153
|
-
method: 'POST',
|
154
|
-
})
|
155
|
-
.catch(noop)
|
156
|
-
}
|
157
|
-
|
158
|
-
async function getOrCreateUser() {
|
159
|
-
print(`We can't find any auth credentials in your Sanity config`)
|
160
|
-
print('- log in or create a new account\n')
|
161
|
-
|
162
|
-
// Provide login options (`sanity login`)
|
163
|
-
const {extOptions, ...otherArgs} = args
|
164
|
-
const loginArgs: CliCommandArguments<LoginFlags> = {...otherArgs, extOptions: {}}
|
165
|
-
await login(loginArgs, context)
|
166
|
-
|
167
|
-
print("Good stuff, you're now authenticated. You'll need a project to keep your")
|
168
|
-
print('datasets and collaborators safe and snug.')
|
169
|
-
}
|
170
|
-
|
171
|
-
async function getOrCreateProject(): Promise<{
|
172
|
-
projectId: string
|
173
|
-
displayName: string
|
174
|
-
isFirstProject: boolean
|
175
|
-
}> {
|
176
|
-
let projects
|
177
|
-
try {
|
178
|
-
projects = await apiClient({requireProject: false}).projects.list({includeMembers: false})
|
179
|
-
} catch (err) {
|
180
|
-
if (unattended && flags.project) {
|
181
|
-
return {projectId: flags.project, displayName: 'Unknown project', isFirstProject: false}
|
182
|
-
}
|
183
|
-
|
184
|
-
throw new Error(`Failed to communicate with the Sanity API:\n${err.message}`)
|
185
|
-
}
|
186
|
-
|
187
|
-
if (projects.length === 0 && unattended) {
|
188
|
-
throw new Error('No projects found for current user')
|
189
|
-
}
|
190
|
-
|
191
|
-
if (flags.project) {
|
192
|
-
const project = projects.find((proj) => proj.id === flags.project)
|
193
|
-
if (!project && !unattended) {
|
194
|
-
throw new Error(
|
195
|
-
`Given project ID (${flags.project}) not found, or you do not have access to it`,
|
196
|
-
)
|
197
|
-
}
|
198
|
-
|
199
|
-
return {
|
200
|
-
projectId: flags.project,
|
201
|
-
displayName: project ? project.displayName : 'Unknown project',
|
202
|
-
isFirstProject: false,
|
203
|
-
}
|
204
|
-
}
|
205
|
-
|
206
|
-
// If the user has no projects or is using a coupon (which can only be applied to new projects)
|
207
|
-
// just ask for project details instead of showing a list of projects
|
208
|
-
const isUsersFirstProject = projects.length === 0
|
209
|
-
if (isUsersFirstProject) {
|
210
|
-
debug('No projects found for user, prompting for name')
|
211
|
-
|
212
|
-
const projectName = await prompt.single({type: 'input', message: 'Project name:'})
|
213
|
-
return createProject(apiClient, {
|
214
|
-
displayName: projectName,
|
215
|
-
subscription: selectedPlan ? {planId: selectedPlan} : undefined,
|
216
|
-
}).then((response) => ({
|
217
|
-
...response,
|
218
|
-
isFirstProject: isUsersFirstProject,
|
219
|
-
}))
|
220
|
-
}
|
221
|
-
|
222
|
-
debug(`User has ${projects.length} project(s) already, showing list of choices`)
|
223
|
-
|
224
|
-
const projectChoices = projects.map((project) => ({
|
225
|
-
value: project.id,
|
226
|
-
name: `${project.displayName} [${project.id}]`,
|
227
|
-
}))
|
228
|
-
|
229
|
-
const selected = await prompt.single({
|
230
|
-
message: 'Select project to use',
|
231
|
-
type: 'list',
|
232
|
-
choices: [
|
233
|
-
{value: 'new', name: 'Create new project'},
|
234
|
-
new prompt.Separator(),
|
235
|
-
...projectChoices,
|
236
|
-
],
|
237
|
-
})
|
238
|
-
|
239
|
-
if (selected === 'new') {
|
240
|
-
debug('User wants to create a new project, prompting for name')
|
241
|
-
return createProject(apiClient, {
|
242
|
-
displayName: await prompt.single({
|
243
|
-
type: 'input',
|
244
|
-
message: 'Your project name:',
|
245
|
-
default: 'My Sanity Project',
|
246
|
-
}),
|
247
|
-
subscription: selectedPlan ? {planId: selectedPlan} : undefined,
|
248
|
-
}).then((response) => ({
|
249
|
-
...response,
|
250
|
-
isFirstProject: isUsersFirstProject,
|
251
|
-
}))
|
252
|
-
}
|
253
|
-
|
254
|
-
debug(`Returning selected project (${selected})`)
|
255
|
-
return {
|
256
|
-
projectId: selected,
|
257
|
-
displayName: projects.find((proj) => proj.id === selected)?.displayName || '',
|
258
|
-
isFirstProject: isUsersFirstProject,
|
259
|
-
}
|
260
|
-
}
|
261
|
-
|
262
|
-
async function getOrCreateDataset(opts: {
|
263
|
-
projectId: string
|
264
|
-
displayName: string
|
265
|
-
dataset?: string
|
266
|
-
aclMode?: string
|
267
|
-
defaultConfig?: boolean
|
268
|
-
}) {
|
269
|
-
if (opts.dataset && isCI) {
|
270
|
-
return {datasetName: opts.dataset}
|
271
|
-
}
|
272
|
-
|
273
|
-
const client = apiClient({api: {projectId: opts.projectId}})
|
274
|
-
const [datasets, projectFeatures] = await Promise.all([
|
275
|
-
client.datasets.list(),
|
276
|
-
client.request({uri: '/features'}),
|
277
|
-
])
|
278
|
-
|
279
|
-
const privateDatasetsAllowed = projectFeatures.includes('privateDataset')
|
280
|
-
const allowedModes = privateDatasetsAllowed ? ['public', 'private'] : ['public']
|
281
|
-
|
282
|
-
if (opts.aclMode && !allowedModes.includes(opts.aclMode)) {
|
283
|
-
throw new Error(`Visibility mode "${opts.aclMode}" not allowed`)
|
284
|
-
}
|
285
|
-
|
286
|
-
// Getter in order to present prompts in a more logical order
|
287
|
-
const getAclMode = async (): Promise<string> => {
|
288
|
-
if (opts.aclMode) {
|
289
|
-
return opts.aclMode
|
290
|
-
}
|
291
|
-
|
292
|
-
if (unattended || !privateDatasetsAllowed || defaultConfig) {
|
293
|
-
return 'public'
|
294
|
-
}
|
295
|
-
|
296
|
-
if (privateDatasetsAllowed) {
|
297
|
-
const mode = await promptForAclMode(prompt, output)
|
298
|
-
return mode
|
299
|
-
}
|
300
|
-
|
301
|
-
return 'public'
|
302
|
-
}
|
303
|
-
|
304
|
-
if (opts.dataset) {
|
305
|
-
debug('User has specified dataset through a flag (%s)', opts.dataset)
|
306
|
-
const existing = datasets.find((ds) => ds.name === opts.dataset)
|
307
|
-
|
308
|
-
if (!existing) {
|
309
|
-
debug('Specified dataset not found, creating it')
|
310
|
-
const aclMode = await getAclMode()
|
311
|
-
const spinner = context.output.spinner('Creating dataset').start()
|
312
|
-
await client.datasets.create(opts.dataset, {aclMode: aclMode as DatasetAclMode})
|
313
|
-
spinner.succeed()
|
314
|
-
}
|
315
|
-
|
316
|
-
return {datasetName: opts.dataset}
|
317
|
-
}
|
318
|
-
|
319
|
-
const datasetInfo =
|
320
|
-
'Your content will be stored in a dataset that can be public or private, depending on\nwhether you want to query your content with or without authentication.\nThe default dataset configuration has a public dataset named "production".'
|
321
|
-
|
322
|
-
if (datasets.length === 0) {
|
323
|
-
debug('No datasets found for project, prompting for name')
|
324
|
-
if (showDefaultConfigPrompt) {
|
325
|
-
output.print(datasetInfo)
|
326
|
-
defaultConfig = await promptForDefaultConfig(prompt)
|
327
|
-
}
|
328
|
-
const name = defaultConfig
|
329
|
-
? 'production'
|
330
|
-
: await promptForDatasetName(prompt, {
|
331
|
-
message: 'Name of your first dataset:',
|
332
|
-
})
|
333
|
-
const aclMode = await getAclMode()
|
334
|
-
const spinner = context.output.spinner('Creating dataset').start()
|
335
|
-
await client.datasets.create(name, {aclMode: aclMode as DatasetAclMode})
|
336
|
-
spinner.succeed()
|
337
|
-
return {datasetName: name}
|
338
|
-
}
|
339
|
-
|
340
|
-
debug(`User has ${datasets.length} dataset(s) already, showing list of choices`)
|
341
|
-
const datasetChoices = datasets.map((dataset) => ({value: dataset.name}))
|
342
|
-
|
343
|
-
const selected = await prompt.single({
|
344
|
-
message: 'Select dataset to use',
|
345
|
-
type: 'list',
|
346
|
-
choices: [
|
347
|
-
{value: 'new', name: 'Create new dataset'},
|
348
|
-
new prompt.Separator(),
|
349
|
-
...datasetChoices,
|
350
|
-
],
|
351
|
-
})
|
352
|
-
|
353
|
-
if (selected === 'new') {
|
354
|
-
const existingDatasetNames = datasets.map((ds) => ds.name)
|
355
|
-
debug('User wants to create a new dataset, prompting for name')
|
356
|
-
if (showDefaultConfigPrompt && !existingDatasetNames.includes('production')) {
|
357
|
-
output.print(datasetInfo)
|
358
|
-
defaultConfig = await promptForDefaultConfig(prompt)
|
359
|
-
}
|
360
|
-
|
361
|
-
const newDatasetName = defaultConfig
|
362
|
-
? 'production'
|
363
|
-
: await promptForDatasetName(
|
364
|
-
prompt,
|
365
|
-
{
|
366
|
-
message: 'Dataset name:',
|
367
|
-
},
|
368
|
-
existingDatasetNames,
|
369
|
-
)
|
370
|
-
const aclMode = await getAclMode()
|
371
|
-
const spinner = context.output.spinner('Creating dataset').start()
|
372
|
-
await client.datasets.create(newDatasetName, {aclMode: aclMode as DatasetAclMode})
|
373
|
-
spinner.succeed()
|
374
|
-
return {datasetName: newDatasetName}
|
375
|
-
}
|
376
|
-
|
377
|
-
debug(`Returning selected dataset (${selected})`)
|
378
|
-
return {datasetName: selected}
|
379
|
-
}
|
380
|
-
|
381
|
-
async function prepareFlags() {
|
382
|
-
const createProjectName = cliFlags['create-project']
|
383
|
-
if (cliFlags.dataset || cliFlags.visibility || cliFlags['dataset-default'] || unattended) {
|
384
|
-
showDefaultConfigPrompt = false
|
385
|
-
}
|
386
|
-
|
387
|
-
if (cliFlags.project && createProjectName) {
|
388
|
-
throw new Error(
|
389
|
-
'Both `--project` and `--create-project` specified, only a single is supported',
|
390
|
-
)
|
391
|
-
}
|
392
|
-
|
393
|
-
if (createProjectName === true) {
|
394
|
-
throw new Error('Please specify a project name (`--create-project <name>`)')
|
395
|
-
}
|
396
|
-
|
397
|
-
if (typeof createProjectName === 'string' && createProjectName.trim().length === 0) {
|
398
|
-
throw new Error('Please specify a project name (`--create-project <name>`)')
|
399
|
-
}
|
400
|
-
|
401
|
-
if (unattended) {
|
402
|
-
debug('Unattended mode, validating required options')
|
403
|
-
const requiredForUnattended = ['dataset', 'output-path'] as const
|
404
|
-
requiredForUnattended.forEach((flag) => {
|
405
|
-
if (!cliFlags[flag]) {
|
406
|
-
throw new Error(`\`--${flag}\` must be specified in unattended mode`)
|
407
|
-
}
|
408
|
-
})
|
409
|
-
|
410
|
-
if (!cliFlags.project && !createProjectName) {
|
411
|
-
throw new Error(
|
412
|
-
'`--project <id>` or `--create-project <name>` must be specified in unattended mode',
|
413
|
-
)
|
414
|
-
}
|
415
|
-
}
|
416
|
-
|
417
|
-
if (createProjectName) {
|
418
|
-
debug('--create-project specified, creating a new project')
|
419
|
-
const createdProject = await createProject(apiClient, {
|
420
|
-
displayName: createProjectName.trim(),
|
421
|
-
subscription: selectedPlan ? {planId: selectedPlan} : undefined,
|
422
|
-
})
|
423
|
-
debug('Project with ID %s created', createdProject.projectId)
|
424
|
-
|
425
|
-
if (cliFlags.dataset) {
|
426
|
-
debug('--dataset specified, creating dataset (%s)', cliFlags.dataset)
|
427
|
-
const client = apiClient({api: {projectId: createdProject.projectId}})
|
428
|
-
const spinner = context.output.spinner('Creating dataset').start()
|
429
|
-
|
430
|
-
const createBody = cliFlags.visibility
|
431
|
-
? {aclMode: cliFlags.visibility as DatasetAclMode}
|
432
|
-
: {}
|
433
|
-
|
434
|
-
await client.datasets.create(cliFlags.dataset, createBody)
|
435
|
-
spinner.succeed()
|
436
|
-
}
|
437
|
-
|
438
|
-
const newFlags = {...cliFlags, project: createdProject.projectId}
|
439
|
-
delete newFlags['create-project']
|
440
|
-
|
441
|
-
return newFlags
|
442
|
-
}
|
443
|
-
|
444
|
-
return cliFlags
|
445
|
-
}
|
446
|
-
}
|
@@ -1,38 +0,0 @@
|
|
1
|
-
import {type CliCommandDefinition} from '../../types'
|
2
|
-
import upgradeDependencies from './upgradeDependencies'
|
3
|
-
|
4
|
-
const helpText = `
|
5
|
-
Upgrades installed Sanity modules to the latest available version within the
|
6
|
-
semantic versioning range specified in "package.json".
|
7
|
-
|
8
|
-
If a specific module name is provided, only that module will be upgraded.
|
9
|
-
|
10
|
-
Options
|
11
|
-
--range [range] Version range to upgrade to, eg '^2.2.7' or '2.1.x'
|
12
|
-
--tag [tag] Tagged release to upgrade to, eg 'canary' or 'some-feature'
|
13
|
-
--save-exact Pin the resolved version numbers in package.json (no ^ prefix)
|
14
|
-
|
15
|
-
Examples
|
16
|
-
# Upgrade modules to the latest semver compatible versions
|
17
|
-
sanity upgrade
|
18
|
-
|
19
|
-
# Update to the latest within the 2.2 range
|
20
|
-
sanity upgrade --range 2.2.x
|
21
|
-
|
22
|
-
# Update to the latest semver compatible versions and pin the versions
|
23
|
-
sanity upgrade --save-exact
|
24
|
-
|
25
|
-
# Update to the latest 'canary' npm tag
|
26
|
-
sanity upgrade --tag canary
|
27
|
-
`
|
28
|
-
|
29
|
-
const upgradeCommand: CliCommandDefinition = {
|
30
|
-
name: 'upgrade',
|
31
|
-
signature: '[--tag DIST_TAG] [--range SEMVER_RANGE] [--save-exact]',
|
32
|
-
description: 'Upgrades all (or some) Sanity modules to their latest versions',
|
33
|
-
action: upgradeDependencies,
|
34
|
-
hideFromHelp: true,
|
35
|
-
helpText,
|
36
|
-
}
|
37
|
-
|
38
|
-
export default upgradeCommand
|