@sanity/cli 6.4.0 → 6.5.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/README.md +14 -8
- package/dist/actions/build/buildApp.js +12 -4
- package/dist/actions/build/buildApp.js.map +1 -1
- package/dist/actions/build/buildStaticFiles.js +3 -1
- package/dist/actions/build/buildStaticFiles.js.map +1 -1
- package/dist/actions/build/buildStudio.js +29 -7
- package/dist/actions/build/buildStudio.js.map +1 -1
- package/dist/actions/build/buildVendorDependencies.js +7 -7
- package/dist/actions/build/buildVendorDependencies.js.map +1 -1
- package/dist/actions/build/checkRequiredDependencies.js +4 -4
- package/dist/actions/build/checkRequiredDependencies.js.map +1 -1
- package/dist/actions/build/checkStudioDependencyVersions.js +9 -9
- package/dist/actions/build/checkStudioDependencyVersions.js.map +1 -1
- package/dist/actions/build/getAutoUpdatesImportMap.js +9 -0
- package/dist/actions/build/getAutoUpdatesImportMap.js.map +1 -1
- package/dist/actions/build/getViteConfig.js +2 -1
- package/dist/actions/build/getViteConfig.js.map +1 -1
- package/dist/actions/build/renderDocument.js.map +1 -1
- package/dist/actions/build/renderDocumentWorker/addTimestampImportMapScriptToHtml.js +34 -14
- package/dist/actions/build/renderDocumentWorker/addTimestampImportMapScriptToHtml.js.map +1 -1
- package/dist/actions/build/renderDocumentWorker/getDocumentHtml.js +2 -2
- package/dist/actions/build/renderDocumentWorker/getDocumentHtml.js.map +1 -1
- package/dist/actions/build/renderDocumentWorker/renderDocumentWorker.js +2 -2
- package/dist/actions/build/renderDocumentWorker/renderDocumentWorker.js.map +1 -1
- package/dist/actions/codemods/reactIconsV3.js +2 -2
- package/dist/actions/codemods/reactIconsV3.js.map +1 -1
- package/dist/actions/dev/startStudioDevServer.js +2 -2
- package/dist/actions/dev/startStudioDevServer.js.map +1 -1
- package/dist/actions/doctor/types.js.map +1 -1
- package/dist/actions/graphql/resolveGraphQLApisFromWorkspaces.js.map +1 -1
- package/dist/actions/init/bootstrapTemplate.js.map +1 -1
- package/dist/actions/init/checkNextJsReactCompatibility.js +3 -3
- package/dist/actions/init/checkNextJsReactCompatibility.js.map +1 -1
- package/dist/actions/init/initAction.js +287 -0
- package/dist/actions/init/initAction.js.map +1 -0
- package/dist/actions/init/initApp.js +7 -16
- package/dist/actions/init/initApp.js.map +1 -1
- package/dist/actions/init/initError.js +10 -0
- package/dist/actions/init/initError.js.map +1 -0
- package/dist/actions/init/initHelpers.js +3 -12
- package/dist/actions/init/initHelpers.js.map +1 -1
- package/dist/actions/init/initNextJs.js +17 -20
- package/dist/actions/init/initNextJs.js.map +1 -1
- package/dist/actions/init/initStudio.js +11 -20
- package/dist/actions/init/initStudio.js.map +1 -1
- package/dist/actions/init/plan/getPlan.js +15 -0
- package/dist/actions/init/plan/getPlan.js.map +1 -0
- package/dist/actions/init/plan/verifyCoupon.js +35 -0
- package/dist/actions/init/plan/verifyCoupon.js.map +1 -0
- package/dist/actions/init/plan/verifyPlan.js +34 -0
- package/dist/actions/init/plan/verifyPlan.js.map +1 -0
- package/dist/actions/init/project/createProjectFromName.js +44 -0
- package/dist/actions/init/project/createProjectFromName.js.map +1 -0
- package/dist/actions/init/project/getOrCreateDataset.js +126 -0
- package/dist/actions/init/project/getOrCreateDataset.js.map +1 -0
- package/dist/actions/init/project/getOrCreateProject.js +128 -0
- package/dist/actions/init/project/getOrCreateProject.js.map +1 -0
- package/dist/actions/init/project/getProjectDetails.js +87 -0
- package/dist/actions/init/project/getProjectDetails.js.map +1 -0
- package/dist/actions/init/project/getProjectOutputPath.js +17 -0
- package/dist/actions/init/project/getProjectOutputPath.js.map +1 -0
- package/dist/actions/init/project/promptForAppTemplateSetup.js +112 -0
- package/dist/actions/init/project/promptForAppTemplateSetup.js.map +1 -0
- package/dist/actions/init/project/promptForProjectCreation.js +40 -0
- package/dist/actions/init/project/promptForProjectCreation.js.map +1 -0
- package/dist/actions/init/project/promptUserForNewOrganization.js +12 -0
- package/dist/actions/init/project/promptUserForNewOrganization.js.map +1 -0
- package/dist/actions/init/project/promptUserForOrganization.js +38 -0
- package/dist/actions/init/project/promptUserForOrganization.js.map +1 -0
- package/dist/actions/init/scaffoldTemplate.js +23 -29
- package/dist/actions/init/scaffoldTemplate.js.map +1 -1
- package/dist/actions/init/types.js +47 -1
- package/dist/actions/init/types.js.map +1 -1
- package/dist/actions/manifest/types.js +0 -1
- package/dist/actions/manifest/types.js.map +1 -1
- package/dist/actions/versions/buildPackageArray.js +2 -2
- package/dist/actions/versions/buildPackageArray.js.map +1 -1
- package/dist/actions/versions/findSanityModulesVersions.js +3 -3
- package/dist/actions/versions/findSanityModulesVersions.js.map +1 -1
- package/dist/commands/datasets/copy.js.map +1 -1
- package/dist/commands/init.js +11 -911
- package/dist/commands/init.js.map +1 -1
- package/dist/server/vite/plugin-sanity-build-entries.js +2 -1
- package/dist/server/vite/plugin-sanity-build-entries.js.map +1 -1
- package/dist/services/datasets.js.map +1 -1
- package/dist/telemetry/init.telemetry.js.map +1 -1
- package/dist/util/compareDependencyVersions.js +4 -4
- package/dist/util/compareDependencyVersions.js.map +1 -1
- package/dist/util/createExpiringConfig.js +1 -1
- package/dist/util/createExpiringConfig.js.map +1 -1
- package/dist/util/packageManager/installationInfo/analyzeIssues.js +7 -7
- package/dist/util/packageManager/installationInfo/analyzeIssues.js.map +1 -1
- package/dist/util/packageManager/installationInfo/types.js.map +1 -1
- package/dist/util/packageManager/packageManagerChoice.js +2 -2
- package/dist/util/packageManager/packageManagerChoice.js.map +1 -1
- package/dist/util/packageManager/preferredPm.js +106 -0
- package/dist/util/packageManager/preferredPm.js.map +1 -0
- package/oclif.manifest.json +526 -526
- package/package.json +23 -22
package/dist/commands/init.js
CHANGED
|
@@ -1,45 +1,10 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
|
-
import { styleText } from 'node:util';
|
|
3
1
|
import { Args, Flags } from '@oclif/core';
|
|
4
2
|
import { CLIError } from '@oclif/core/errors';
|
|
5
|
-
import { SanityCommand
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import deburr from 'lodash-es/deburr.js';
|
|
10
|
-
import { validateSession } from '../actions/auth/ensureAuthenticated.js';
|
|
11
|
-
import { getProviderName } from '../actions/auth/getProviderName.js';
|
|
12
|
-
import { login } from '../actions/auth/login/login.js';
|
|
13
|
-
import { createDataset } from '../actions/dataset/create.js';
|
|
14
|
-
import { checkNextJsReactCompatibility } from '../actions/init/checkNextJsReactCompatibility.js';
|
|
15
|
-
import { determineAppTemplate } from '../actions/init/determineAppTemplate.js';
|
|
16
|
-
import { createOrAppendEnvVars } from '../actions/init/env/createOrAppendEnvVars.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';
|
|
21
|
-
import { checkIsRemoteTemplate, getGitHubRepoInfo } from '../actions/init/remoteTemplate.js';
|
|
22
|
-
import { setupMCP } from '../actions/mcp/setupMCP.js';
|
|
23
|
-
import { findOrganizationByUserName } from '../actions/organizations/findOrganizationByUserName.js';
|
|
24
|
-
import { getOrganizationChoices } from '../actions/organizations/getOrganizationChoices.js';
|
|
25
|
-
import { getOrganizationsWithAttachGrantInfo } from '../actions/organizations/getOrganizationsWithAttachGrantInfo.js';
|
|
26
|
-
import { hasProjectAttachGrant } from '../actions/organizations/hasProjectAttachGrant.js';
|
|
27
|
-
import { promptForConfigFiles } from '../prompts/init/nextjs.js';
|
|
28
|
-
import { promptForDatasetName } from '../prompts/promptForDatasetName.js';
|
|
29
|
-
import { promptForDefaultConfig } from '../prompts/promptForDefaultConfig.js';
|
|
30
|
-
import { promptForOrganizationName } from '../prompts/promptForOrganizationName.js';
|
|
31
|
-
import { createDataset as createDatasetService, listDatasets } from '../services/datasets.js';
|
|
32
|
-
import { getProjectFeatures } from '../services/getProjectFeatures.js';
|
|
33
|
-
import { createOrganization, listOrganizations } from '../services/organizations.js';
|
|
34
|
-
import { getPlanId, getPlanIdFromCoupon } from '../services/plans.js';
|
|
35
|
-
import { createProject, listProjects } from '../services/projects.js';
|
|
36
|
-
import { getCliUser } from '../services/user.js';
|
|
37
|
-
import { CLIInitStepCompleted } from '../telemetry/init.telemetry.js';
|
|
38
|
-
import { detectFrameworkRecord } from '../util/detectFramework.js';
|
|
39
|
-
import { absolutify, validateEmptyPath } from '../util/fsUtils.js';
|
|
40
|
-
import { getProjectDefaults } from '../util/getProjectDefaults.js';
|
|
3
|
+
import { SanityCommand } from '@sanity/cli-core';
|
|
4
|
+
import { initAction } from '../actions/init/initAction.js';
|
|
5
|
+
import { InitError } from '../actions/init/initError.js';
|
|
6
|
+
import { flagsToInitOptions } from '../actions/init/types.js';
|
|
41
7
|
import { getSanityEnv } from '../util/getSanityEnv.js';
|
|
42
|
-
const debug = subdebug('init');
|
|
43
8
|
export class InitCommand extends SanityCommand {
|
|
44
9
|
static args = {
|
|
45
10
|
type: Args.string({
|
|
@@ -272,891 +237,26 @@ export class InitCommand extends SanityCommand {
|
|
|
272
237
|
description: 'Unattended mode, answers "yes" to any "yes/no" prompt and otherwise uses defaults'
|
|
273
238
|
})
|
|
274
239
|
};
|
|
275
|
-
_trace;
|
|
276
240
|
async run() {
|
|
277
|
-
const workDir = process.cwd();
|
|
278
|
-
const createProjectName = this.flags['project-name'] ?? this.flags['create-project'];
|
|
279
|
-
// For backwards "compatibility" - we used to allow `sanity init plugin`,
|
|
280
|
-
// and no longer do - but instead of printing an error about an unknown
|
|
281
|
-
// _command_, we want to acknowledge that the user is trying to do something
|
|
282
|
-
// that no longer exists but might have at some point in the past.
|
|
283
|
-
if (this.args.type) {
|
|
284
|
-
this.error(this.args.type === 'plugin' ? 'Initializing plugins through the CLI is no longer supported' : `Unknown init type "${this.args.type}"`, {
|
|
285
|
-
exit: 1
|
|
286
|
-
});
|
|
287
|
-
}
|
|
288
|
-
this._trace = this.telemetry.trace(CLIInitStepCompleted);
|
|
289
|
-
// Slightly more helpful message for removed flags rather than just saying the flag
|
|
290
|
-
// does not exist.
|
|
291
|
-
if (this.flags.reconfigure) {
|
|
292
|
-
this.error('--reconfigure is deprecated - manual configuration is now required', {
|
|
293
|
-
exit: 1
|
|
294
|
-
});
|
|
295
|
-
}
|
|
296
|
-
// Oclif doesn't support custom exclusive error messaging
|
|
297
|
-
if (this.flags.project && this.flags.organization) {
|
|
298
|
-
this.error('You have specified both a project and an organization. To move a project to an organization please visit https://www.sanity.io/manage', {
|
|
299
|
-
exit: 1
|
|
300
|
-
});
|
|
301
|
-
}
|
|
302
|
-
const defaultConfig = this.flags['dataset-default'];
|
|
303
|
-
let showDefaultConfigPrompt = !defaultConfig;
|
|
304
|
-
if (this.flags.dataset || this.flags.visibility || this.flags['dataset-default'] || this.isUnattended()) {
|
|
305
|
-
showDefaultConfigPrompt = false;
|
|
306
|
-
}
|
|
307
|
-
const detectedFramework = await detectFrameworkRecord({
|
|
308
|
-
frameworkList: frameworks,
|
|
309
|
-
rootPath: process.cwd()
|
|
310
|
-
});
|
|
311
|
-
const isNextJs = detectedFramework?.slug === 'nextjs';
|
|
312
|
-
let remoteTemplateInfo;
|
|
313
|
-
if (this.flags.template && checkIsRemoteTemplate(this.flags.template)) {
|
|
314
|
-
remoteTemplateInfo = await getGitHubRepoInfo(this.flags.template, this.flags['template-token']);
|
|
315
|
-
}
|
|
316
|
-
if (detectedFramework && detectedFramework.slug !== 'sanity' && remoteTemplateInfo) {
|
|
317
|
-
this.error(`A remote template cannot be used with a detected framework. Detected: ${detectedFramework.name}`, {
|
|
318
|
-
exit: 1
|
|
319
|
-
});
|
|
320
|
-
}
|
|
321
|
-
const isAppTemplate = this.flags.template ? determineAppTemplate(this.flags.template) : false // Default to false
|
|
322
|
-
;
|
|
323
|
-
// Checks flags are present when in unattended mode
|
|
324
|
-
if (this.isUnattended()) {
|
|
325
|
-
this.checkFlagsInUnattendedMode({
|
|
326
|
-
createProjectName,
|
|
327
|
-
isAppTemplate,
|
|
328
|
-
isNextJs
|
|
329
|
-
});
|
|
330
|
-
}
|
|
331
|
-
this._trace.start();
|
|
332
|
-
this._trace.log({
|
|
333
|
-
flags: {
|
|
334
|
-
bare: this.flags.bare,
|
|
335
|
-
coupon: this.flags.coupon,
|
|
336
|
-
defaultConfig,
|
|
337
|
-
env: this.flags.env,
|
|
338
|
-
git: this.flags.git,
|
|
339
|
-
plan: this.flags['project-plan'],
|
|
340
|
-
reconfigure: this.flags.reconfigure,
|
|
341
|
-
unattended: this.isUnattended()
|
|
342
|
-
},
|
|
343
|
-
step: 'start'
|
|
344
|
-
});
|
|
345
|
-
// Plan can be set through `--project-plan`, or implied through `--coupon`.
|
|
346
|
-
// As coupons can expire and project plans might change/be removed, we need to
|
|
347
|
-
// verify that the passed flags are valid. The complexity of this is hidden in the
|
|
348
|
-
// below plan methods, eventually returning a plan ID or undefined if we are told to
|
|
349
|
-
// use the default plan.
|
|
350
|
-
const planId = await this.getPlan();
|
|
351
|
-
let envFilenameDefault = '.env';
|
|
352
|
-
if (detectedFramework && detectedFramework.slug === 'nextjs') {
|
|
353
|
-
envFilenameDefault = '.env.local';
|
|
354
|
-
}
|
|
355
|
-
const envFilename = typeof this.flags.env === 'string' ? this.flags.env : envFilenameDefault;
|
|
356
|
-
// If the user isn't already autenticated, make it so
|
|
357
|
-
const { user } = await this.ensureAuthenticated();
|
|
358
|
-
if (!isAppTemplate) {
|
|
359
|
-
this.log(`${logSymbols.success} Fetching existing projects`);
|
|
360
|
-
this.log('');
|
|
361
|
-
}
|
|
362
|
-
let newProject;
|
|
363
|
-
if (createProjectName) {
|
|
364
|
-
newProject = await this.createProjectFromName({
|
|
365
|
-
createProjectName,
|
|
366
|
-
planId,
|
|
367
|
-
user
|
|
368
|
-
});
|
|
369
|
-
}
|
|
370
|
-
const { datasetName, displayName, isFirstProject, organizationId, projectId } = await this.getProjectDetails({
|
|
371
|
-
isAppTemplate,
|
|
372
|
-
newProject,
|
|
373
|
-
planId,
|
|
374
|
-
showDefaultConfigPrompt,
|
|
375
|
-
user
|
|
376
|
-
});
|
|
377
|
-
// If user doesn't want to output any template code
|
|
378
|
-
if (this.flags.bare) {
|
|
379
|
-
this.log(`${logSymbols.success} Below are your project details`);
|
|
380
|
-
this.log('');
|
|
381
|
-
this.log(`Project ID: ${styleText('cyan', projectId)}`);
|
|
382
|
-
this.log(`Dataset: ${styleText('cyan', datasetName)}`);
|
|
383
|
-
this.log(`\nYou can find your project on Sanity Manage — https://www.sanity.io/manage/project/${projectId}\n`);
|
|
384
|
-
return;
|
|
385
|
-
}
|
|
386
|
-
let initNext = flagOrDefault(this.flags['nextjs-add-config-files'], false);
|
|
387
|
-
if (isNextJs && shouldPrompt(this.isUnattended(), this.flags['nextjs-add-config-files'])) {
|
|
388
|
-
initNext = await promptForConfigFiles();
|
|
389
|
-
}
|
|
390
|
-
this._trace.log({
|
|
391
|
-
detectedFramework: detectedFramework?.name,
|
|
392
|
-
selectedOption: initNext ? 'yes' : 'no',
|
|
393
|
-
step: 'useDetectedFramework'
|
|
394
|
-
});
|
|
395
|
-
const sluggedName = deburr(displayName.toLowerCase()).replaceAll(/\s+/g, '-').replaceAll(/[^a-z0-9-]/g, '');
|
|
396
|
-
// add more frameworks to this as we add support for them
|
|
397
|
-
// this is used to skip the getProjectInfo prompt
|
|
398
|
-
const initFramework = initNext;
|
|
399
|
-
// Gather project defaults based on environment
|
|
400
|
-
const defaults = await getProjectDefaults({
|
|
401
|
-
isPlugin: false,
|
|
402
|
-
workDir
|
|
403
|
-
});
|
|
404
|
-
// Prompt the user for required information
|
|
405
|
-
const outputPath = await this.getProjectOutputPath({
|
|
406
|
-
initFramework,
|
|
407
|
-
sluggedName,
|
|
408
|
-
workDir
|
|
409
|
-
});
|
|
410
|
-
// Set up MCP integration (skip in non-production environments)
|
|
411
241
|
let mcpMode = 'prompt';
|
|
412
242
|
if (!this.flags.mcp || !this.resolveIsInteractive() || getSanityEnv() !== 'production') {
|
|
413
243
|
mcpMode = 'skip';
|
|
414
244
|
} else if (this.flags.yes) {
|
|
415
245
|
mcpMode = 'auto';
|
|
416
246
|
}
|
|
417
|
-
const mcpResult = await setupMCP({
|
|
418
|
-
mode: mcpMode
|
|
419
|
-
});
|
|
420
|
-
this._trace.log({
|
|
421
|
-
configuredEditors: mcpResult.configuredEditors,
|
|
422
|
-
detectedEditors: mcpResult.detectedEditors,
|
|
423
|
-
skipped: mcpResult.skipped,
|
|
424
|
-
step: 'mcpSetup'
|
|
425
|
-
});
|
|
426
|
-
if (mcpResult.error) {
|
|
427
|
-
this._trace.error(mcpResult.error);
|
|
428
|
-
}
|
|
429
|
-
const mcpConfigured = mcpResult.configuredEditors;
|
|
430
|
-
// Show checkmark for editors that were already configured
|
|
431
|
-
const { alreadyConfiguredEditors } = mcpResult;
|
|
432
|
-
if (alreadyConfiguredEditors.length > 0) {
|
|
433
|
-
const label = alreadyConfiguredEditors.length === 1 ? `${alreadyConfiguredEditors[0]} already configured for Sanity MCP` : `${alreadyConfiguredEditors.length} editors already configured for Sanity MCP`;
|
|
434
|
-
spinner(label).start().succeed();
|
|
435
|
-
}
|
|
436
|
-
if (isNextJs) {
|
|
437
|
-
await checkNextJsReactCompatibility({
|
|
438
|
-
detectedFramework,
|
|
439
|
-
output: this.output,
|
|
440
|
-
outputPath
|
|
441
|
-
});
|
|
442
|
-
}
|
|
443
|
-
if (initNext) {
|
|
444
|
-
await initNextJs({
|
|
445
|
-
datasetName,
|
|
446
|
-
detectedFramework,
|
|
447
|
-
envFilename,
|
|
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'],
|
|
454
|
-
projectId,
|
|
455
|
-
template: this.flags.template,
|
|
456
|
-
trace: this._trace,
|
|
457
|
-
typescript: this.flags.typescript,
|
|
458
|
-
unattended: this.isUnattended(),
|
|
459
|
-
workDir
|
|
460
|
-
});
|
|
461
|
-
this._trace.complete();
|
|
462
|
-
return;
|
|
463
|
-
}
|
|
464
|
-
// user wants to write environment variables to file
|
|
465
|
-
if (this.flags.env) {
|
|
466
|
-
await createOrAppendEnvVars({
|
|
467
|
-
envVars: {
|
|
468
|
-
DATASET: datasetName,
|
|
469
|
-
PROJECT_ID: projectId
|
|
470
|
-
},
|
|
471
|
-
filename: envFilename,
|
|
472
|
-
framework: detectedFramework,
|
|
473
|
-
log: false,
|
|
474
|
-
output: this.output,
|
|
475
|
-
outputPath
|
|
476
|
-
});
|
|
477
|
-
await writeStagingEnvIfNeeded(this.output, outputPath);
|
|
478
|
-
this.exit(0);
|
|
479
|
-
}
|
|
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,
|
|
488
|
-
output: this.output,
|
|
489
|
-
outputPath,
|
|
490
|
-
overwriteFiles: this.flags['overwrite-files'],
|
|
491
|
-
packageManager: this.flags['package-manager'],
|
|
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(),
|
|
499
|
-
workDir
|
|
500
|
-
};
|
|
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
|
-
}));
|
|
513
|
-
this._trace.complete();
|
|
514
|
-
}
|
|
515
|
-
checkFlagsInUnattendedMode({ createProjectName, isAppTemplate, isNextJs }) {
|
|
516
|
-
debug('Unattended mode, validating required options');
|
|
517
|
-
// App templates only require --organization and --output-path
|
|
518
|
-
if (isAppTemplate) {
|
|
519
|
-
if (!this.flags['output-path']) {
|
|
520
|
-
this.error('`--output-path` must be specified in unattended mode', {
|
|
521
|
-
exit: 1
|
|
522
|
-
});
|
|
523
|
-
}
|
|
524
|
-
if (!this.flags.organization) {
|
|
525
|
-
this.error('The --organization flag is required for app templates in unattended mode. ' + 'Use --organization <id> to specify which organization to use.', {
|
|
526
|
-
exit: 1
|
|
527
|
-
});
|
|
528
|
-
}
|
|
529
|
-
return;
|
|
530
|
-
}
|
|
531
|
-
// output-path is required in unattended mode when not using nextjs or bare
|
|
532
|
-
if (!isNextJs && !this.flags.bare && !this.flags['output-path']) {
|
|
533
|
-
this.error(`\`--output-path\` must be specified in unattended mode`, {
|
|
534
|
-
exit: 1
|
|
535
|
-
});
|
|
536
|
-
}
|
|
537
|
-
if (!this.flags.project && !createProjectName) {
|
|
538
|
-
this.error('`--project <id>` or `--project-name <name>` must be specified in unattended mode', {
|
|
539
|
-
exit: 1
|
|
540
|
-
});
|
|
541
|
-
}
|
|
542
|
-
if (createProjectName && !this.flags.organization) {
|
|
543
|
-
this.error('`--project-name` requires `--organization <id>` in unattended mode', {
|
|
544
|
-
exit: 1
|
|
545
|
-
});
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
async createProjectFromName({ createProjectName, planId, user }) {
|
|
549
|
-
debug('--project-name specified, creating a new project');
|
|
550
|
-
let orgForCreateProjectFlag = this.flags.organization;
|
|
551
|
-
if (!orgForCreateProjectFlag) {
|
|
552
|
-
debug('no organization specified, selecting one');
|
|
553
|
-
const organizations = await listOrganizations();
|
|
554
|
-
orgForCreateProjectFlag = await this.promptUserForOrganization({
|
|
555
|
-
organizations,
|
|
556
|
-
user
|
|
557
|
-
});
|
|
558
|
-
}
|
|
559
|
-
debug('creating a new project');
|
|
560
|
-
const createdProject = await createProject({
|
|
561
|
-
displayName: createProjectName.trim(),
|
|
562
|
-
metadata: {
|
|
563
|
-
coupon: this.flags.coupon
|
|
564
|
-
},
|
|
565
|
-
organizationId: orgForCreateProjectFlag,
|
|
566
|
-
subscription: planId ? {
|
|
567
|
-
planId
|
|
568
|
-
} : undefined
|
|
569
|
-
});
|
|
570
|
-
debug('Project with ID %s created', createdProject.projectId);
|
|
571
|
-
if (this.flags.dataset) {
|
|
572
|
-
debug('--dataset specified, creating dataset (%s)', this.flags.dataset);
|
|
573
|
-
const spin = spinner('Creating dataset').start();
|
|
574
|
-
await createDatasetService({
|
|
575
|
-
aclMode: this.flags.visibility,
|
|
576
|
-
datasetName: this.flags.dataset,
|
|
577
|
-
projectId: createdProject.projectId
|
|
578
|
-
});
|
|
579
|
-
spin.succeed();
|
|
580
|
-
}
|
|
581
|
-
return createdProject.projectId;
|
|
582
|
-
}
|
|
583
|
-
// @todo do we actually need to be authenticated for init? check flags and determine.
|
|
584
|
-
async ensureAuthenticated() {
|
|
585
|
-
const user = await validateSession();
|
|
586
|
-
if (user) {
|
|
587
|
-
this._trace.log({
|
|
588
|
-
alreadyLoggedIn: true,
|
|
589
|
-
step: 'login'
|
|
590
|
-
});
|
|
591
|
-
this.log(`${logSymbols.success} You are logged in as ${user.email} using ${getProviderName(user.provider)}`);
|
|
592
|
-
return {
|
|
593
|
-
user
|
|
594
|
-
};
|
|
595
|
-
}
|
|
596
|
-
if (this.isUnattended()) {
|
|
597
|
-
this.error('Must be logged in to run this command in unattended mode, run `sanity login`', {
|
|
598
|
-
exit: 1
|
|
599
|
-
});
|
|
600
|
-
}
|
|
601
|
-
this._trace.log({
|
|
602
|
-
step: 'login'
|
|
603
|
-
});
|
|
604
247
|
try {
|
|
605
|
-
await
|
|
248
|
+
await initAction(flagsToInitOptions(this.flags, this.isUnattended(), this.args, mcpMode), {
|
|
606
249
|
output: this.output,
|
|
607
|
-
telemetry: this.
|
|
250
|
+
telemetry: this.telemetry,
|
|
251
|
+
workDir: process.cwd()
|
|
608
252
|
});
|
|
609
253
|
} catch (error) {
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
});
|
|
614
|
-
}
|
|
615
|
-
const loggedInUser = await getCliUser();
|
|
616
|
-
this.log(`${logSymbols.success} You are logged in as ${loggedInUser.email} using ${getProviderName(loggedInUser.provider)}`);
|
|
617
|
-
return {
|
|
618
|
-
user: loggedInUser
|
|
619
|
-
};
|
|
620
|
-
}
|
|
621
|
-
async getOrCreateDataset(opts) {
|
|
622
|
-
const visibility = this.flags.visibility;
|
|
623
|
-
const dataset = this.flags.dataset;
|
|
624
|
-
let defaultConfig = this.flags['dataset-default'];
|
|
625
|
-
if (dataset && this.isUnattended()) {
|
|
626
|
-
return {
|
|
627
|
-
datasetName: dataset,
|
|
628
|
-
userAction: 'none'
|
|
629
|
-
};
|
|
630
|
-
}
|
|
631
|
-
const [datasets, projectFeatures] = await Promise.all([
|
|
632
|
-
listDatasets(opts.projectId),
|
|
633
|
-
getProjectFeatures(opts.projectId)
|
|
634
|
-
]);
|
|
635
|
-
if (dataset) {
|
|
636
|
-
debug('User has specified dataset through a flag (%s)', dataset);
|
|
637
|
-
const existing = datasets.find((ds)=>ds.name === dataset);
|
|
638
|
-
if (!existing) {
|
|
639
|
-
debug('Specified dataset not found, creating it');
|
|
640
|
-
await createDataset({
|
|
641
|
-
datasetName: dataset,
|
|
642
|
-
forcePublic: defaultConfig,
|
|
643
|
-
output: this.output,
|
|
644
|
-
projectFeatures,
|
|
645
|
-
projectId: opts.projectId,
|
|
646
|
-
visibility
|
|
647
|
-
});
|
|
648
|
-
}
|
|
649
|
-
return {
|
|
650
|
-
datasetName: dataset,
|
|
651
|
-
userAction: 'none'
|
|
652
|
-
};
|
|
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
|
-
}
|
|
676
|
-
if (datasets.length === 0) {
|
|
677
|
-
debug('No datasets found for project, prompting for name');
|
|
678
|
-
if (opts.showDefaultConfigPrompt) {
|
|
679
|
-
defaultConfig = await promptForDefaultConfig();
|
|
680
|
-
}
|
|
681
|
-
const name = defaultConfig ? 'production' : await promptForDatasetName({
|
|
682
|
-
message: 'Name of your first dataset:'
|
|
683
|
-
});
|
|
684
|
-
await createDataset({
|
|
685
|
-
datasetName: name,
|
|
686
|
-
forcePublic: defaultConfig,
|
|
687
|
-
output: this.output,
|
|
688
|
-
projectFeatures,
|
|
689
|
-
projectId: opts.projectId,
|
|
690
|
-
visibility
|
|
691
|
-
});
|
|
692
|
-
return {
|
|
693
|
-
datasetName: name,
|
|
694
|
-
userAction: 'create'
|
|
695
|
-
};
|
|
696
|
-
}
|
|
697
|
-
debug(`User has ${datasets.length} dataset(s) already, showing list of choices`);
|
|
698
|
-
const datasetChoices = datasets.map((dataset)=>({
|
|
699
|
-
value: dataset.name
|
|
700
|
-
}));
|
|
701
|
-
const selected = await select({
|
|
702
|
-
choices: [
|
|
703
|
-
{
|
|
704
|
-
name: 'Create new dataset',
|
|
705
|
-
value: 'new'
|
|
706
|
-
},
|
|
707
|
-
new Separator(),
|
|
708
|
-
...datasetChoices
|
|
709
|
-
],
|
|
710
|
-
message: 'Select dataset to use'
|
|
711
|
-
});
|
|
712
|
-
if (selected === 'new') {
|
|
713
|
-
const existingDatasetNames = datasets.map((ds)=>ds.name);
|
|
714
|
-
debug('User wants to create a new dataset, prompting for name');
|
|
715
|
-
if (opts.showDefaultConfigPrompt && !existingDatasetNames.includes('production')) {
|
|
716
|
-
defaultConfig = await promptForDefaultConfig();
|
|
717
|
-
}
|
|
718
|
-
const newDatasetName = defaultConfig ? 'production' : await promptForDatasetName({
|
|
719
|
-
message: 'Dataset name:'
|
|
720
|
-
}, existingDatasetNames);
|
|
721
|
-
await createDataset({
|
|
722
|
-
datasetName: newDatasetName,
|
|
723
|
-
forcePublic: defaultConfig,
|
|
724
|
-
output: this.output,
|
|
725
|
-
projectFeatures,
|
|
726
|
-
projectId: opts.projectId,
|
|
727
|
-
visibility
|
|
728
|
-
});
|
|
729
|
-
return {
|
|
730
|
-
datasetName: newDatasetName,
|
|
731
|
-
userAction: 'create'
|
|
732
|
-
};
|
|
733
|
-
}
|
|
734
|
-
debug(`Returning selected dataset (${selected})`);
|
|
735
|
-
return {
|
|
736
|
-
datasetName: selected,
|
|
737
|
-
userAction: 'select'
|
|
738
|
-
};
|
|
739
|
-
}
|
|
740
|
-
async getOrCreateProject({ newProject, planId, user }) {
|
|
741
|
-
const projectId = this.flags.project || newProject;
|
|
742
|
-
const organizationId = this.flags.organization;
|
|
743
|
-
let projects;
|
|
744
|
-
let organizations;
|
|
745
|
-
try {
|
|
746
|
-
const [allProjects, allOrgs] = await Promise.all([
|
|
747
|
-
listProjects(),
|
|
748
|
-
listOrganizations()
|
|
749
|
-
]);
|
|
750
|
-
projects = allProjects.toSorted((a, b)=>b.createdAt.localeCompare(a.createdAt));
|
|
751
|
-
organizations = allOrgs;
|
|
752
|
-
} catch (err) {
|
|
753
|
-
if (this.isUnattended() && projectId) {
|
|
754
|
-
return {
|
|
755
|
-
displayName: 'Unknown project',
|
|
756
|
-
isFirstProject: false,
|
|
757
|
-
projectId,
|
|
758
|
-
userAction: 'select'
|
|
759
|
-
};
|
|
760
|
-
}
|
|
761
|
-
this.error(`Failed to communicate with the Sanity API:\n${err.message}`, {
|
|
762
|
-
exit: 1
|
|
763
|
-
});
|
|
764
|
-
}
|
|
765
|
-
if (projects.length === 0 && this.isUnattended()) {
|
|
766
|
-
this.error('No projects found for current user', {
|
|
767
|
-
exit: 1
|
|
768
|
-
});
|
|
769
|
-
}
|
|
770
|
-
if (projectId) {
|
|
771
|
-
const project = projects.find((proj)=>proj.id === projectId);
|
|
772
|
-
if (!project && !this.isUnattended()) {
|
|
773
|
-
this.error(`Given project ID (${projectId}) not found, or you do not have access to it`, {
|
|
774
|
-
exit: 1
|
|
775
|
-
});
|
|
776
|
-
}
|
|
777
|
-
return {
|
|
778
|
-
displayName: project ? project.displayName : 'Unknown project',
|
|
779
|
-
isFirstProject: false,
|
|
780
|
-
projectId,
|
|
781
|
-
userAction: 'select'
|
|
782
|
-
};
|
|
783
|
-
}
|
|
784
|
-
if (organizationId) {
|
|
785
|
-
const organization = organizations.find((org)=>org.id === organizationId) || organizations.find((org)=>org.slug === organizationId);
|
|
786
|
-
if (!organization) {
|
|
787
|
-
this.error(`Given organization ID (${organizationId}) not found, or you do not have access to it`, {
|
|
788
|
-
exit: 1
|
|
789
|
-
});
|
|
790
|
-
}
|
|
791
|
-
if (!await hasProjectAttachGrant(organizationId)) {
|
|
792
|
-
this.error('You lack the necessary permissions to attach a project to this organization', {
|
|
793
|
-
exit: 1
|
|
794
|
-
});
|
|
795
|
-
}
|
|
796
|
-
}
|
|
797
|
-
// If the user has no projects or is using a coupon (which can only be applied to new projects)
|
|
798
|
-
// just ask for project details instead of showing a list of projects
|
|
799
|
-
const isUsersFirstProject = projects.length === 0;
|
|
800
|
-
if (isUsersFirstProject || this.flags.coupon) {
|
|
801
|
-
debug(isUsersFirstProject ? 'No projects found for user, prompting for name' : 'Using a coupon - skipping project selection');
|
|
802
|
-
const newProject = await this.promptForProjectCreation({
|
|
803
|
-
isUsersFirstProject,
|
|
804
|
-
organizationId,
|
|
805
|
-
organizations,
|
|
806
|
-
planId,
|
|
807
|
-
user
|
|
808
|
-
});
|
|
809
|
-
return {
|
|
810
|
-
...newProject,
|
|
811
|
-
isFirstProject: isUsersFirstProject,
|
|
812
|
-
userAction: 'create'
|
|
813
|
-
};
|
|
814
|
-
}
|
|
815
|
-
debug(`User has ${projects.length} project(s) already, showing list of choices`);
|
|
816
|
-
const projectChoices = projects.map((project)=>({
|
|
817
|
-
name: `${project.displayName} (${project.id})`,
|
|
818
|
-
value: project.id
|
|
819
|
-
}));
|
|
820
|
-
const selected = await select({
|
|
821
|
-
choices: [
|
|
822
|
-
{
|
|
823
|
-
name: 'Create new project',
|
|
824
|
-
value: 'new'
|
|
825
|
-
},
|
|
826
|
-
new Separator(),
|
|
827
|
-
...projectChoices
|
|
828
|
-
],
|
|
829
|
-
message: 'Create a new project or select an existing one'
|
|
830
|
-
});
|
|
831
|
-
if (selected === 'new') {
|
|
832
|
-
debug('User wants to create a new project, prompting for name');
|
|
833
|
-
const newProject = await this.promptForProjectCreation({
|
|
834
|
-
isUsersFirstProject,
|
|
835
|
-
organizationId,
|
|
836
|
-
organizations,
|
|
837
|
-
planId,
|
|
838
|
-
user
|
|
839
|
-
});
|
|
840
|
-
return {
|
|
841
|
-
...newProject,
|
|
842
|
-
isFirstProject: isUsersFirstProject,
|
|
843
|
-
userAction: 'create'
|
|
844
|
-
};
|
|
845
|
-
}
|
|
846
|
-
debug(`Returning selected project (${selected})`);
|
|
847
|
-
return {
|
|
848
|
-
displayName: projects.find((proj)=>proj.id === selected)?.displayName || '',
|
|
849
|
-
isFirstProject: isUsersFirstProject,
|
|
850
|
-
projectId: selected,
|
|
851
|
-
userAction: 'select'
|
|
852
|
-
};
|
|
853
|
-
}
|
|
854
|
-
async getPlan() {
|
|
855
|
-
const intendedPlan = this.flags['project-plan'];
|
|
856
|
-
const intendedCoupon = this.flags.coupon;
|
|
857
|
-
if (intendedCoupon) {
|
|
858
|
-
return this.verifyCoupon(intendedCoupon);
|
|
859
|
-
} else if (intendedPlan) {
|
|
860
|
-
return this.verifyPlan(intendedPlan);
|
|
861
|
-
} else {
|
|
862
|
-
return undefined;
|
|
863
|
-
}
|
|
864
|
-
}
|
|
865
|
-
async getProjectDetails({ isAppTemplate, newProject, planId, showDefaultConfigPrompt, user }) {
|
|
866
|
-
if (isAppTemplate) {
|
|
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
|
-
});
|
|
882
|
-
}
|
|
883
|
-
const { datasetName, displayName, projectId } = await this.promptForAppTemplateSetup({
|
|
884
|
-
newProject,
|
|
885
|
-
organizationId,
|
|
886
|
-
planId,
|
|
887
|
-
user
|
|
888
|
-
});
|
|
889
|
-
return {
|
|
890
|
-
datasetName,
|
|
891
|
-
displayName,
|
|
892
|
-
isFirstProject: false,
|
|
893
|
-
organizationId,
|
|
894
|
-
projectId
|
|
895
|
-
};
|
|
896
|
-
}
|
|
897
|
-
debug('Prompting user to select or create a project');
|
|
898
|
-
const project = await this.getOrCreateProject({
|
|
899
|
-
newProject,
|
|
900
|
-
planId,
|
|
901
|
-
user
|
|
902
|
-
});
|
|
903
|
-
debug(`Project with name ${project.displayName} selected`);
|
|
904
|
-
// Now let's pick or create a dataset
|
|
905
|
-
debug('Prompting user to select or create a dataset');
|
|
906
|
-
const dataset = await this.getOrCreateDataset({
|
|
907
|
-
displayName: project.displayName,
|
|
908
|
-
projectId: project.projectId,
|
|
909
|
-
showDefaultConfigPrompt
|
|
910
|
-
});
|
|
911
|
-
debug(`Dataset with name ${dataset.datasetName} selected`);
|
|
912
|
-
this._trace.log({
|
|
913
|
-
datasetName: dataset.datasetName,
|
|
914
|
-
selectedOption: dataset.userAction,
|
|
915
|
-
step: 'createOrSelectDataset',
|
|
916
|
-
visibility: this.flags.visibility
|
|
917
|
-
});
|
|
918
|
-
return {
|
|
919
|
-
datasetName: dataset.datasetName,
|
|
920
|
-
displayName: project.displayName,
|
|
921
|
-
isFirstProject: project.isFirstProject,
|
|
922
|
-
projectId: project.projectId
|
|
923
|
-
};
|
|
924
|
-
}
|
|
925
|
-
async getProjectOutputPath({ initFramework, sluggedName, workDir }) {
|
|
926
|
-
const outputPath = this.flags['output-path'];
|
|
927
|
-
const specifiedPath = outputPath && path.resolve(outputPath);
|
|
928
|
-
if (this.isUnattended() || specifiedPath || this.flags.env || initFramework) {
|
|
929
|
-
return specifiedPath || workDir;
|
|
930
|
-
}
|
|
931
|
-
const inputPath = await input({
|
|
932
|
-
default: path.join(workDir, sluggedName),
|
|
933
|
-
message: 'Project output path:',
|
|
934
|
-
validate: validateEmptyPath
|
|
935
|
-
});
|
|
936
|
-
return absolutify(inputPath);
|
|
937
|
-
}
|
|
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
|
-
};
|
|
946
|
-
}
|
|
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
|
-
};
|
|
962
|
-
}
|
|
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
|
|
975
|
-
},
|
|
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'
|
|
991
|
-
});
|
|
992
|
-
return {
|
|
993
|
-
datasetName: '',
|
|
994
|
-
displayName: '',
|
|
995
|
-
projectId: ''
|
|
996
|
-
};
|
|
997
|
-
}
|
|
998
|
-
this._trace.log({
|
|
999
|
-
selectedOption: selected === NEW_PROJECT ? 'create' : 'existing',
|
|
1000
|
-
step: 'configureAppProject'
|
|
1001
|
-
});
|
|
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
|
|
1011
|
-
};
|
|
1012
|
-
const dataset = await this.getOrCreateDataset({
|
|
1013
|
-
displayName: project.displayName,
|
|
1014
|
-
projectId: project.projectId,
|
|
1015
|
-
showDefaultConfigPrompt: false
|
|
1016
|
-
});
|
|
1017
|
-
return {
|
|
1018
|
-
datasetName: dataset.datasetName,
|
|
1019
|
-
displayName: project.displayName,
|
|
1020
|
-
projectId: project.projectId
|
|
1021
|
-
};
|
|
1022
|
-
}
|
|
1023
|
-
async promptForProjectCreation({ isUsersFirstProject, organizationId, organizations, planId, user }) {
|
|
1024
|
-
const projectName = await input({
|
|
1025
|
-
default: 'My Sanity Project',
|
|
1026
|
-
message: 'Project name:',
|
|
1027
|
-
validate (input) {
|
|
1028
|
-
if (!input || input.trim() === '') {
|
|
1029
|
-
return 'Project name cannot be empty';
|
|
1030
|
-
}
|
|
1031
|
-
if (input.length > 80) {
|
|
1032
|
-
return 'Project name cannot be longer than 80 characters';
|
|
1033
|
-
}
|
|
1034
|
-
return true;
|
|
1035
|
-
}
|
|
1036
|
-
});
|
|
1037
|
-
const organization = organizationId || await this.promptUserForOrganization({
|
|
1038
|
-
organizations,
|
|
1039
|
-
user
|
|
1040
|
-
});
|
|
1041
|
-
const newProject = await createProject({
|
|
1042
|
-
displayName: projectName,
|
|
1043
|
-
metadata: {
|
|
1044
|
-
coupon: this.flags.coupon
|
|
1045
|
-
},
|
|
1046
|
-
organizationId: organization,
|
|
1047
|
-
subscription: planId ? {
|
|
1048
|
-
planId
|
|
1049
|
-
} : undefined
|
|
1050
|
-
});
|
|
1051
|
-
return {
|
|
1052
|
-
...newProject,
|
|
1053
|
-
isFirstProject: isUsersFirstProject,
|
|
1054
|
-
userAction: 'create'
|
|
1055
|
-
};
|
|
1056
|
-
}
|
|
1057
|
-
async promptUserForNewOrganization(user) {
|
|
1058
|
-
const name = await promptForOrganizationName(user);
|
|
1059
|
-
const spin = spinner('Creating organization').start();
|
|
1060
|
-
const organization = await createOrganization(name);
|
|
1061
|
-
spin.succeed();
|
|
1062
|
-
return organization;
|
|
1063
|
-
}
|
|
1064
|
-
async promptUserForOrganization({ isAppTemplate = false, organizations, user }) {
|
|
1065
|
-
// If the user has no organizations, prompt them to create one with the same name as
|
|
1066
|
-
// their user, but allow them to customize it if they want
|
|
1067
|
-
if (organizations.length === 0) {
|
|
1068
|
-
const newOrganization = await this.promptUserForNewOrganization(user);
|
|
1069
|
-
return newOrganization.id;
|
|
1070
|
-
}
|
|
1071
|
-
let organizationChoices;
|
|
1072
|
-
let defaultOrganizationId;
|
|
1073
|
-
if (isAppTemplate) {
|
|
1074
|
-
// For app templates, all organizations are valid — no attach grant check needed
|
|
1075
|
-
organizationChoices = getOrganizationChoices(organizations);
|
|
1076
|
-
defaultOrganizationId = organizations.length === 1 ? organizations[0].id : findOrganizationByUserName(organizations, user);
|
|
1077
|
-
} else {
|
|
1078
|
-
// For studio projects, check which organizations the user can attach projects to
|
|
1079
|
-
debug(`User has ${organizations.length} organization(s), checking attach access`);
|
|
1080
|
-
const withGrantInfo = await getOrganizationsWithAttachGrantInfo(organizations);
|
|
1081
|
-
const withAttach = withGrantInfo.filter(({ hasAttachGrant })=>hasAttachGrant);
|
|
1082
|
-
debug('User has attach access to %d organizations.', withAttach.length);
|
|
1083
|
-
organizationChoices = getOrganizationChoices(withGrantInfo);
|
|
1084
|
-
defaultOrganizationId = withAttach.length === 1 ? withAttach[0].organization.id : findOrganizationByUserName(organizations, user);
|
|
1085
|
-
}
|
|
1086
|
-
const chosenOrg = await select({
|
|
1087
|
-
choices: organizationChoices,
|
|
1088
|
-
default: defaultOrganizationId || undefined,
|
|
1089
|
-
message: 'Select organization:'
|
|
1090
|
-
});
|
|
1091
|
-
if (chosenOrg === '-new-') {
|
|
1092
|
-
const newOrganization = await this.promptUserForNewOrganization(user);
|
|
1093
|
-
return newOrganization.id;
|
|
1094
|
-
}
|
|
1095
|
-
return chosenOrg || undefined;
|
|
1096
|
-
}
|
|
1097
|
-
async verifyCoupon(intendedCoupon) {
|
|
1098
|
-
try {
|
|
1099
|
-
const planId = await getPlanIdFromCoupon(intendedCoupon);
|
|
1100
|
-
this.log(`Coupon "${intendedCoupon}" validated!\n`);
|
|
1101
|
-
return planId;
|
|
1102
|
-
} catch (err) {
|
|
1103
|
-
if (!isHttpError(err) || err.statusCode !== 404) {
|
|
1104
|
-
const message = err instanceof Error ? err.message : `${err}`;
|
|
1105
|
-
this.error(`Unable to validate coupon, please try again later:\n\n${message}`, {
|
|
1106
|
-
exit: 1
|
|
1107
|
-
});
|
|
1108
|
-
}
|
|
1109
|
-
const useDefaultPlan = this.isUnattended() || await confirm({
|
|
1110
|
-
default: true,
|
|
1111
|
-
message: `Coupon "${intendedCoupon}" is not available, use default plan instead?`
|
|
1112
|
-
});
|
|
1113
|
-
if (this.isUnattended()) {
|
|
1114
|
-
this.warn(`Coupon "${intendedCoupon}" is not available - using default plan`);
|
|
1115
|
-
}
|
|
1116
|
-
this._trace.log({
|
|
1117
|
-
coupon: intendedCoupon,
|
|
1118
|
-
selectedOption: useDefaultPlan ? 'yes' : 'no',
|
|
1119
|
-
step: 'useDefaultPlanCoupon'
|
|
1120
|
-
});
|
|
1121
|
-
if (useDefaultPlan) {
|
|
1122
|
-
this.log('Using default plan.');
|
|
1123
|
-
} else {
|
|
1124
|
-
this.error(`Coupon "${intendedCoupon}" does not exist`, {
|
|
1125
|
-
exit: 1
|
|
1126
|
-
});
|
|
1127
|
-
}
|
|
1128
|
-
}
|
|
1129
|
-
}
|
|
1130
|
-
async verifyPlan(intendedPlan) {
|
|
1131
|
-
try {
|
|
1132
|
-
const planId = await getPlanId(intendedPlan);
|
|
1133
|
-
return planId;
|
|
1134
|
-
} catch (err) {
|
|
1135
|
-
if (!isHttpError(err) || err.statusCode !== 404) {
|
|
1136
|
-
const message = err instanceof Error ? err.message : `${err}`;
|
|
1137
|
-
this.error(`Unable to validate plan, please try again later:\n\n${message}`, {
|
|
1138
|
-
exit: 1
|
|
1139
|
-
});
|
|
1140
|
-
}
|
|
1141
|
-
const useDefaultPlan = this.isUnattended() || await confirm({
|
|
1142
|
-
default: true,
|
|
1143
|
-
message: `Project plan "${intendedPlan}" does not exist, use default plan instead?`
|
|
1144
|
-
});
|
|
1145
|
-
if (this.isUnattended()) {
|
|
1146
|
-
this.warn(`Project plan "${intendedPlan}" does not exist - using default plan`);
|
|
1147
|
-
}
|
|
1148
|
-
this._trace.log({
|
|
1149
|
-
planId: intendedPlan,
|
|
1150
|
-
selectedOption: useDefaultPlan ? 'yes' : 'no',
|
|
1151
|
-
step: 'useDefaultPlanId'
|
|
1152
|
-
});
|
|
1153
|
-
if (useDefaultPlan) {
|
|
1154
|
-
this.log('Using default plan.');
|
|
1155
|
-
} else {
|
|
1156
|
-
this.error(`Plan id "${intendedPlan}" does not exist`, {
|
|
1157
|
-
exit: 1
|
|
254
|
+
if (error instanceof InitError) {
|
|
255
|
+
this.error(error.message, {
|
|
256
|
+
exit: error.exitCode
|
|
1158
257
|
});
|
|
1159
258
|
}
|
|
259
|
+
throw error;
|
|
1160
260
|
}
|
|
1161
261
|
}
|
|
1162
262
|
}
|