@sanity/cli 7.7.1 → 7.8.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 +17 -2
- package/dist/actions/auth/ensureAuthenticated.js +1 -1
- package/dist/actions/auth/ensureAuthenticated.js.map +1 -1
- package/dist/actions/auth/login/validateToken.js +1 -1
- package/dist/actions/auth/login/validateToken.js.map +1 -1
- package/dist/actions/build/buildApp.js +2 -3
- package/dist/actions/build/buildApp.js.map +1 -1
- package/dist/actions/build/buildStudio.js +1 -2
- package/dist/actions/build/buildStudio.js.map +1 -1
- package/dist/actions/deploy/createUserApplication.js +8 -8
- package/dist/actions/deploy/createUserApplication.js.map +1 -1
- package/dist/actions/deploy/deployApp.js +121 -24
- package/dist/actions/deploy/deployApp.js.map +1 -1
- package/dist/actions/deploy/deployChecks.js +90 -66
- package/dist/actions/deploy/deployChecks.js.map +1 -1
- package/dist/actions/deploy/deployRunner.js +8 -8
- package/dist/actions/deploy/deployRunner.js.map +1 -1
- package/dist/actions/deploy/deployStudio.js +126 -23
- package/dist/actions/deploy/deployStudio.js.map +1 -1
- package/dist/actions/deploy/deploymentPlan.js +29 -30
- package/dist/actions/deploy/deploymentPlan.js.map +1 -1
- package/dist/actions/deploy/findUserApplication.js +39 -14
- package/dist/actions/deploy/findUserApplication.js.map +1 -1
- package/dist/actions/deploy/resolveDeployTarget.js +43 -0
- package/dist/actions/deploy/resolveDeployTarget.js.map +1 -1
- package/dist/actions/dev/servers/getDevServerConfig.js +1 -1
- package/dist/actions/dev/servers/getDevServerConfig.js.map +1 -1
- package/dist/actions/dev/servers/startStudioDevServer.js +1 -2
- package/dist/actions/dev/servers/startStudioDevServer.js.map +1 -1
- package/dist/actions/graphql/SchemaError.js.map +1 -1
- package/dist/actions/graphql/extractGraphQLAPIs.js +3 -1
- package/dist/actions/graphql/extractGraphQLAPIs.js.map +1 -1
- package/dist/actions/manifest/extractCoreAppManifest.js +1 -1
- package/dist/actions/manifest/extractCoreAppManifest.js.map +1 -1
- package/dist/actions/organizations/getOrganization.js +1 -1
- package/dist/actions/organizations/getOrganization.js.map +1 -1
- package/dist/actions/schema/validateAction.js +1 -1
- package/dist/actions/schema/validateAction.js.map +1 -1
- package/dist/actions/skills/setupSkills.js +1 -1
- package/dist/actions/skills/setupSkills.js.map +1 -1
- package/dist/actions/undeploy/adapters.js +101 -0
- package/dist/actions/undeploy/adapters.js.map +1 -0
- package/dist/actions/undeploy/runUndeploy.js +165 -0
- package/dist/actions/undeploy/runUndeploy.js.map +1 -0
- package/dist/actions/undeploy/undeployPlan.js +108 -0
- package/dist/actions/undeploy/undeployPlan.js.map +1 -0
- package/dist/commands/datasets/copy.js +2 -1
- package/dist/commands/datasets/copy.js.map +1 -1
- package/dist/commands/mcp/configure.js +1 -1
- package/dist/commands/mcp/configure.js.map +1 -1
- package/dist/commands/projects/create.js +18 -13
- package/dist/commands/projects/create.js.map +1 -1
- package/dist/commands/projects/list.js +2 -1
- package/dist/commands/projects/list.js.map +1 -1
- package/dist/commands/schemas/validate.js +3 -2
- package/dist/commands/schemas/validate.js.map +1 -1
- package/dist/commands/skills/install.js +1 -1
- package/dist/commands/skills/install.js.map +1 -1
- package/dist/commands/tokens/list.js +1 -1
- package/dist/commands/tokens/list.js.map +1 -1
- package/dist/commands/undeploy.js +36 -74
- package/dist/commands/undeploy.js.map +1 -1
- package/dist/util/checks.js +83 -0
- package/dist/util/checks.js.map +1 -0
- package/dist/util/toStderrOutput.js +13 -0
- package/dist/util/toStderrOutput.js.map +1 -0
- package/oclif.manifest.json +1007 -980
- package/package.json +7 -8
- package/dist/actions/undeploy/getStudioOrAppUserApplication.js +0 -36
- package/dist/actions/undeploy/getStudioOrAppUserApplication.js.map +0 -1
- package/dist/util/compareDependencyVersions.js +0 -110
- package/dist/util/compareDependencyVersions.js.map +0 -1
- package/dist/util/getErrorMessage.js +0 -28
- package/dist/util/getErrorMessage.js.map +0 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Args, Flags } from '@oclif/core';
|
|
2
2
|
import { CLIError } from '@oclif/core/errors';
|
|
3
|
-
import {
|
|
3
|
+
import { subdebug } from '@sanity/cli-core/debug';
|
|
4
|
+
import { SanityCommand } from '@sanity/cli-core/SanityCommand';
|
|
4
5
|
import { confirm, spinner } from '@sanity/cli-core/ux';
|
|
5
6
|
import { createDataset } from '../../actions/dataset/create.js';
|
|
6
7
|
import { validateDatasetName } from '../../actions/dataset/validateDatasetName.js';
|
|
@@ -104,7 +105,7 @@ export class CreateProjectCommand extends SanityCommand {
|
|
|
104
105
|
});
|
|
105
106
|
} catch (error) {
|
|
106
107
|
const errorText = organization ? `Failed to retrieve organization ${organization}` : 'Failed to retrieve an organization';
|
|
107
|
-
this.error(`${errorText}: ${error}`, {
|
|
108
|
+
return this.output.error(`${errorText}: ${error}`, {
|
|
108
109
|
exit: 1
|
|
109
110
|
});
|
|
110
111
|
}
|
|
@@ -122,11 +123,15 @@ export class CreateProjectCommand extends SanityCommand {
|
|
|
122
123
|
} catch (error) {
|
|
123
124
|
spin.fail();
|
|
124
125
|
debug(`Failed to create project: ${error}`);
|
|
125
|
-
this.error(`Failed to create project: ${error}`, {
|
|
126
|
+
return this.output.error(`Failed to create project: ${error}`, {
|
|
126
127
|
exit: 1
|
|
127
128
|
});
|
|
128
129
|
}
|
|
129
|
-
|
|
130
|
+
let newDataset;
|
|
131
|
+
// If dataset name specified, or in attended mode (to prompt for dataset name), create a dataset.
|
|
132
|
+
if (dataset || !this.isUnattended()) {
|
|
133
|
+
newDataset = await this.handleDatasetCreation(newProject.projectId, dataset, datasetVisibility);
|
|
134
|
+
}
|
|
130
135
|
this.printProjectCreationSuccess(chosenOrganization, newProject, newDataset);
|
|
131
136
|
}
|
|
132
137
|
async handleDatasetCreation(projectId, datasetFromFlag, datasetVisibility) {
|
|
@@ -160,24 +165,24 @@ export class CreateProjectCommand extends SanityCommand {
|
|
|
160
165
|
return;
|
|
161
166
|
} catch (error) {
|
|
162
167
|
debug(`Error creating dataset: ${error}`);
|
|
163
|
-
this.warn(`Project created but dataset creation failed: ${error}`);
|
|
168
|
+
this.output.warn(`Project created but dataset creation failed: ${error}`);
|
|
164
169
|
return;
|
|
165
170
|
}
|
|
166
171
|
}
|
|
167
172
|
async printProjectCreationSuccess(organization, project, dataset) {
|
|
168
173
|
if (this.flags.json) {
|
|
169
|
-
this.log(JSON.stringify(project, null, 2));
|
|
174
|
+
this.output.log(JSON.stringify(project, null, 2));
|
|
170
175
|
return;
|
|
171
176
|
}
|
|
172
|
-
this.log(`Project created successfully!`);
|
|
173
|
-
this.log(`ID: ${project.projectId}`);
|
|
174
|
-
this.log(`Name: ${project.displayName}`);
|
|
175
|
-
this.log(`Organization: ${organization?.name || 'Personal'}`);
|
|
177
|
+
this.output.log(`Project created successfully!`);
|
|
178
|
+
this.output.log(`ID: ${project.projectId}`);
|
|
179
|
+
this.output.log(`Name: ${project.displayName}`);
|
|
180
|
+
this.output.log(`Organization: ${organization?.name || 'Personal'}`);
|
|
176
181
|
if (dataset) {
|
|
177
|
-
this.log(`Dataset: ${dataset.datasetName} (${dataset.aclMode})`);
|
|
182
|
+
this.output.log(`Dataset: ${dataset.datasetName} (${dataset.aclMode})`);
|
|
178
183
|
}
|
|
179
|
-
this.log(``);
|
|
180
|
-
this.log(`Manage your project: ${getManageUrl(project.projectId)}`);
|
|
184
|
+
this.output.log(``);
|
|
185
|
+
this.output.log(`Manage your project: ${getManageUrl(project.projectId)}`);
|
|
181
186
|
}
|
|
182
187
|
}
|
|
183
188
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/commands/projects/create.ts"],"sourcesContent":["import {Args, Flags} from '@oclif/core'\nimport {CLIError} from '@oclif/core/errors'\nimport {SanityCommand, subdebug} from '@sanity/cli-core'\nimport {confirm, spinner} from '@sanity/cli-core/ux'\nimport {DatasetResponse} from '@sanity/client'\n\nimport {createDataset} from '../../actions/dataset/create.js'\nimport {validateDatasetName} from '../../actions/dataset/validateDatasetName.js'\nimport {getOrganization} from '../../actions/organizations/getOrganization.js'\nimport {getManageUrl} from '../../actions/projects/getManageUrl.js'\nimport {promptForDatasetName} from '../../prompts/promptForDatasetName.js'\nimport {promptForDefaultConfig} from '../../prompts/promptForDefaultConfig.js'\nimport {promptForProjectName} from '../../prompts/promptForProjectName.js'\nimport {listDatasets} from '../../services/datasets.js'\nimport {getProjectFeatures} from '../../services/getProjectFeatures.js'\nimport {OrganizationCreateResponse, ProjectOrganization} from '../../services/organizations.js'\nimport {createProject, CreateProjectResult} from '../../services/projects.js'\nimport {getCliUser} from '../../services/user.js'\n\nconst debug = subdebug('projects:create')\n\nexport class CreateProjectCommand extends SanityCommand<typeof CreateProjectCommand> {\n static override args = {\n projectName: Args.string({\n description: 'Name of the project to create',\n required: false,\n }),\n }\n\n static override description = 'Create a new Sanity project'\n\n static override examples = [\n {\n command: '<%= config.bin %> <%= command.id %>',\n description: 'Interactively create a project',\n },\n {\n command: '<%= config.bin %> <%= command.id %> \"My New Project\"',\n description: 'Create a project named \"My New Project\"',\n },\n {\n command: '<%= config.bin %> <%= command.id %> \"My Project\" --organization=my-org',\n description: 'Create a project in a specific organization',\n },\n {\n command:\n '<%= config.bin %> <%= command.id %> \"My Project\" --dataset=staging --dataset-visibility=private',\n description: 'Create a project with a private dataset named \"staging\"',\n },\n {\n command: '<%= config.bin %> <%= command.id %> \"CI Project\" --yes --json',\n description: 'Create a project non-interactively with JSON output',\n },\n ]\n\n static override flags = {\n dataset: Flags.string({\n description: 'Create a dataset. Prompts for visibility unless specified or --yes used',\n parse: async (input) => {\n const datasetNameError = validateDatasetName(input)\n if (datasetNameError) {\n throw new CLIError(datasetNameError, {exit: 1})\n }\n\n return input\n },\n }),\n 'dataset-visibility': Flags.string({\n description: 'Dataset visibility: public or private',\n options: ['private', 'public'],\n }),\n json: Flags.boolean({\n default: false,\n description: 'Output in JSON format',\n }),\n organization: Flags.string({\n description: 'Organization to create the project in',\n helpValue: '<slug|id>',\n }),\n yes: Flags.boolean({\n char: 'y',\n default: false,\n description:\n 'Skip prompts and use defaults (project: \"My Sanity Project\", dataset: production, visibility: public)',\n }),\n }\n\n static override hiddenAliases: string[] = ['project:create']\n\n public async run(): Promise<void> {\n const {args, flags} = await this.parse(CreateProjectCommand)\n const {projectName} = args\n const {dataset, 'dataset-visibility': datasetVisibility, organization, yes} = flags\n const user = await getCliUser()\n\n const finalProjectName =\n projectName ||\n (yes || this.isUnattended() ? 'My Sanity Project' : await promptForProjectName())\n\n debug('Creating project with options: %O', {\n dataset,\n datasetVisibility,\n organizationId: organization,\n projectName,\n unattended: yes,\n })\n\n let chosenOrganization: OrganizationCreateResponse | ProjectOrganization | undefined\n try {\n chosenOrganization = await getOrganization({\n isUnattended: this.isUnattended(),\n output: this.output,\n requestedId: organization,\n user,\n })\n } catch (error) {\n const errorText = organization\n ? `Failed to retrieve organization ${organization}`\n : 'Failed to retrieve an organization'\n this.error(`${errorText}: ${error}`, {exit: 1})\n }\n\n const spin = spinner('Creating project').start()\n let newProject\n try {\n newProject = await createProject({\n displayName: finalProjectName,\n metadata: {\n integration: 'cli',\n },\n organizationId: chosenOrganization?.id,\n })\n spin.succeed('Project created successfully')\n } catch (error) {\n spin.fail()\n debug(`Failed to create project: ${error}`)\n this.error(`Failed to create project: ${error}`, {exit: 1})\n }\n\n const newDataset = await this.handleDatasetCreation(\n newProject.projectId,\n dataset,\n datasetVisibility,\n )\n\n this.printProjectCreationSuccess(chosenOrganization, newProject, newDataset)\n }\n\n private async handleDatasetCreation(\n projectId: string,\n datasetFromFlag?: string,\n datasetVisibility?: string,\n ): Promise<DatasetResponse | undefined> {\n try {\n let datasetName: string | undefined = datasetFromFlag\n const existingDatasets = await listDatasets(projectId)\n const existingDatasetNames = existingDatasets.map((ds) => ds.name)\n\n // Prompt for dataset in interactive mode if not provided\n if (!datasetName && !this.isUnattended()) {\n const wantsDataset = await confirm({\n default: true,\n message: 'Would you like to create a dataset?',\n })\n\n if (wantsDataset) {\n const defaultConfig = await promptForDefaultConfig()\n\n datasetName = defaultConfig\n ? 'production'\n : await promptForDatasetName({}, existingDatasetNames)\n }\n }\n\n // Create dataset if we have a name\n if (datasetName) {\n const projectFeatures = await getProjectFeatures(projectId)\n return await createDataset({\n datasetName,\n isUnattended: this.isUnattended(),\n output: this.output,\n projectFeatures,\n projectId,\n visibility: datasetVisibility,\n })\n }\n\n return\n } catch (error) {\n debug(`Error creating dataset: ${error}`)\n this.warn(`Project created but dataset creation failed: ${error}`)\n return\n }\n }\n\n private async printProjectCreationSuccess(\n organization: OrganizationCreateResponse | ProjectOrganization | undefined,\n project: CreateProjectResult,\n dataset: DatasetResponse | undefined,\n ) {\n if (this.flags.json) {\n this.log(JSON.stringify(project, null, 2))\n return\n }\n\n this.log(`Project created successfully!`)\n this.log(`ID: ${project.projectId}`)\n this.log(`Name: ${project.displayName}`)\n this.log(`Organization: ${organization?.name || 'Personal'}`)\n\n if (dataset) {\n this.log(`Dataset: ${dataset.datasetName} (${dataset.aclMode})`)\n }\n\n this.log(``)\n this.log(`Manage your project: ${getManageUrl(project.projectId)}`)\n }\n}\n"],"names":["Args","Flags","CLIError","SanityCommand","subdebug","confirm","spinner","createDataset","validateDatasetName","getOrganization","getManageUrl","promptForDatasetName","promptForDefaultConfig","promptForProjectName","listDatasets","getProjectFeatures","createProject","getCliUser","debug","CreateProjectCommand","args","projectName","string","description","required","examples","command","flags","dataset","parse","input","datasetNameError","exit","options","json","boolean","default","organization","helpValue","yes","char","hiddenAliases","run","datasetVisibility","user","finalProjectName","isUnattended","organizationId","unattended","chosenOrganization","output","requestedId","error","errorText","spin","start","newProject","displayName","metadata","integration","id","succeed","fail","newDataset","handleDatasetCreation","projectId","printProjectCreationSuccess","datasetFromFlag","datasetName","existingDatasets","existingDatasetNames","map","ds","name","wantsDataset","message","defaultConfig","projectFeatures","visibility","warn","project","log","JSON","stringify","aclMode"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/commands/projects/create.ts"],"sourcesContent":["import {Args, Flags} from '@oclif/core'\nimport {CLIError} from '@oclif/core/errors'\nimport {subdebug} from '@sanity/cli-core/debug'\nimport {SanityCommand} from '@sanity/cli-core/SanityCommand'\nimport {confirm, spinner} from '@sanity/cli-core/ux'\nimport {type DatasetResponse} from '@sanity/client'\n\nimport {createDataset} from '../../actions/dataset/create.js'\nimport {validateDatasetName} from '../../actions/dataset/validateDatasetName.js'\nimport {getOrganization} from '../../actions/organizations/getOrganization.js'\nimport {getManageUrl} from '../../actions/projects/getManageUrl.js'\nimport {promptForDatasetName} from '../../prompts/promptForDatasetName.js'\nimport {promptForDefaultConfig} from '../../prompts/promptForDefaultConfig.js'\nimport {promptForProjectName} from '../../prompts/promptForProjectName.js'\nimport {listDatasets} from '../../services/datasets.js'\nimport {getProjectFeatures} from '../../services/getProjectFeatures.js'\nimport {\n type OrganizationCreateResponse,\n type ProjectOrganization,\n} from '../../services/organizations.js'\nimport {createProject, type CreateProjectResult} from '../../services/projects.js'\nimport {getCliUser} from '../../services/user.js'\n\nconst debug = subdebug('projects:create')\n\nexport class CreateProjectCommand extends SanityCommand<typeof CreateProjectCommand> {\n static override args = {\n projectName: Args.string({\n description: 'Name of the project to create',\n required: false,\n }),\n }\n\n static override description = 'Create a new Sanity project'\n\n static override examples = [\n {\n command: '<%= config.bin %> <%= command.id %>',\n description: 'Interactively create a project',\n },\n {\n command: '<%= config.bin %> <%= command.id %> \"My New Project\"',\n description: 'Create a project named \"My New Project\"',\n },\n {\n command: '<%= config.bin %> <%= command.id %> \"My Project\" --organization=my-org',\n description: 'Create a project in a specific organization',\n },\n {\n command:\n '<%= config.bin %> <%= command.id %> \"My Project\" --dataset=staging --dataset-visibility=private',\n description: 'Create a project with a private dataset named \"staging\"',\n },\n {\n command: '<%= config.bin %> <%= command.id %> \"CI Project\" --yes --json',\n description: 'Create a project non-interactively with JSON output',\n },\n ]\n\n static override flags = {\n dataset: Flags.string({\n description: 'Create a dataset. Prompts for visibility unless specified or --yes used',\n parse: async (input) => {\n const datasetNameError = validateDatasetName(input)\n if (datasetNameError) {\n throw new CLIError(datasetNameError, {exit: 1})\n }\n\n return input\n },\n }),\n 'dataset-visibility': Flags.string({\n description: 'Dataset visibility: public or private',\n options: ['private', 'public'],\n }),\n json: Flags.boolean({\n default: false,\n description: 'Output in JSON format',\n }),\n organization: Flags.string({\n description: 'Organization to create the project in',\n helpValue: '<slug|id>',\n }),\n yes: Flags.boolean({\n char: 'y',\n default: false,\n description:\n 'Skip prompts and use defaults (project: \"My Sanity Project\", dataset: production, visibility: public)',\n }),\n }\n\n static override hiddenAliases: string[] = ['project:create']\n\n public async run(): Promise<void> {\n const {args, flags} = await this.parse(CreateProjectCommand)\n const {projectName} = args\n const {dataset, 'dataset-visibility': datasetVisibility, organization, yes} = flags\n const user = await getCliUser()\n\n const finalProjectName =\n projectName ||\n (yes || this.isUnattended() ? 'My Sanity Project' : await promptForProjectName())\n\n debug('Creating project with options: %O', {\n dataset,\n datasetVisibility,\n organizationId: organization,\n projectName,\n unattended: yes,\n })\n\n let chosenOrganization: OrganizationCreateResponse | ProjectOrganization | undefined\n try {\n chosenOrganization = await getOrganization({\n isUnattended: this.isUnattended(),\n output: this.output,\n requestedId: organization,\n user,\n })\n } catch (error) {\n const errorText = organization\n ? `Failed to retrieve organization ${organization}`\n : 'Failed to retrieve an organization'\n return this.output.error(`${errorText}: ${error}`, {exit: 1})\n }\n\n const spin = spinner('Creating project').start()\n let newProject\n try {\n newProject = await createProject({\n displayName: finalProjectName,\n metadata: {\n integration: 'cli',\n },\n organizationId: chosenOrganization?.id,\n })\n spin.succeed('Project created successfully')\n } catch (error) {\n spin.fail()\n debug(`Failed to create project: ${error}`)\n return this.output.error(`Failed to create project: ${error}`, {exit: 1})\n }\n\n let newDataset: DatasetResponse | undefined\n // If dataset name specified, or in attended mode (to prompt for dataset name), create a dataset.\n if (dataset || !this.isUnattended()) {\n newDataset = await this.handleDatasetCreation(\n newProject.projectId,\n dataset,\n datasetVisibility,\n )\n }\n\n this.printProjectCreationSuccess(chosenOrganization, newProject, newDataset)\n }\n\n private async handleDatasetCreation(\n projectId: string,\n datasetFromFlag?: string,\n datasetVisibility?: string,\n ): Promise<DatasetResponse | undefined> {\n try {\n let datasetName: string | undefined = datasetFromFlag\n const existingDatasets = await listDatasets(projectId)\n const existingDatasetNames = existingDatasets.map((ds) => ds.name)\n\n // Prompt for dataset in interactive mode if not provided\n if (!datasetName && !this.isUnattended()) {\n const wantsDataset = await confirm({\n default: true,\n message: 'Would you like to create a dataset?',\n })\n\n if (wantsDataset) {\n const defaultConfig = await promptForDefaultConfig()\n\n datasetName = defaultConfig\n ? 'production'\n : await promptForDatasetName({}, existingDatasetNames)\n }\n }\n\n // Create dataset if we have a name\n if (datasetName) {\n const projectFeatures = await getProjectFeatures(projectId)\n return await createDataset({\n datasetName,\n isUnattended: this.isUnattended(),\n output: this.output,\n projectFeatures,\n projectId,\n visibility: datasetVisibility,\n })\n }\n\n return\n } catch (error) {\n debug(`Error creating dataset: ${error}`)\n this.output.warn(`Project created but dataset creation failed: ${error}`)\n return\n }\n }\n\n private async printProjectCreationSuccess(\n organization: OrganizationCreateResponse | ProjectOrganization | undefined,\n project: CreateProjectResult,\n dataset: DatasetResponse | undefined,\n ) {\n if (this.flags.json) {\n this.output.log(JSON.stringify(project, null, 2))\n return\n }\n\n this.output.log(`Project created successfully!`)\n this.output.log(`ID: ${project.projectId}`)\n this.output.log(`Name: ${project.displayName}`)\n this.output.log(`Organization: ${organization?.name || 'Personal'}`)\n\n if (dataset) {\n this.output.log(`Dataset: ${dataset.datasetName} (${dataset.aclMode})`)\n }\n\n this.output.log(``)\n this.output.log(`Manage your project: ${getManageUrl(project.projectId)}`)\n }\n}\n"],"names":["Args","Flags","CLIError","subdebug","SanityCommand","confirm","spinner","createDataset","validateDatasetName","getOrganization","getManageUrl","promptForDatasetName","promptForDefaultConfig","promptForProjectName","listDatasets","getProjectFeatures","createProject","getCliUser","debug","CreateProjectCommand","args","projectName","string","description","required","examples","command","flags","dataset","parse","input","datasetNameError","exit","options","json","boolean","default","organization","helpValue","yes","char","hiddenAliases","run","datasetVisibility","user","finalProjectName","isUnattended","organizationId","unattended","chosenOrganization","output","requestedId","error","errorText","spin","start","newProject","displayName","metadata","integration","id","succeed","fail","newDataset","handleDatasetCreation","projectId","printProjectCreationSuccess","datasetFromFlag","datasetName","existingDatasets","existingDatasetNames","map","ds","name","wantsDataset","message","defaultConfig","projectFeatures","visibility","warn","project","log","JSON","stringify","aclMode"],"mappings":"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"}
|
|
@@ -3,6 +3,7 @@ import { Flags } from '@oclif/core';
|
|
|
3
3
|
import { SanityCommand, subdebug } from '@sanity/cli-core';
|
|
4
4
|
import size from 'lodash-es/size.js';
|
|
5
5
|
import sortBy from 'lodash-es/sortBy.js';
|
|
6
|
+
import { getManageUrl } from '../../actions/projects/getManageUrl.js';
|
|
6
7
|
import { listProjects } from '../../services/projects.js';
|
|
7
8
|
const sortFields = [
|
|
8
9
|
'id',
|
|
@@ -47,7 +48,7 @@ export class List extends SanityCommand {
|
|
|
47
48
|
try {
|
|
48
49
|
const projects = await listProjects();
|
|
49
50
|
const ordered = sortBy(projects.map(({ createdAt, displayName, id, members = [] })=>{
|
|
50
|
-
const manage =
|
|
51
|
+
const manage = getManageUrl(id);
|
|
51
52
|
return [
|
|
52
53
|
id,
|
|
53
54
|
members.length,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/commands/projects/list.ts"],"sourcesContent":["import {styleText} from 'node:util'\n\nimport {Flags} from '@oclif/core'\nimport {SanityCommand, subdebug} from '@sanity/cli-core'\nimport size from 'lodash-es/size.js'\nimport sortBy from 'lodash-es/sortBy.js'\n\nimport {listProjects} from '../../services/projects.js'\n\nconst sortFields = ['id', 'members', 'name', 'url', 'created']\n\nconst projectsDebug = subdebug('projects')\n\nexport class List extends SanityCommand<typeof List> {\n static override description = 'List your projects'\n static override examples = [\n {\n command: '<%= config.bin %> <%= command.id %>',\n description: 'List projects',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --sort=members --order=asc',\n description: 'List projects sorted by member count, ascending',\n },\n ]\n\n static override flags = {\n order: Flags.string({\n default: 'desc',\n description: 'Sort direction',\n options: ['asc', 'desc'],\n }),\n sort: Flags.string({\n default: 'created',\n description: 'Sort field',\n options: sortFields,\n }),\n }\n\n static override hiddenAliases: string[] = ['project:list']\n\n public async run() {\n const {order, sort} = this.flags\n\n try {\n const projects = await listProjects()\n const ordered = sortBy(\n projects.map(({createdAt, displayName, id, members = []}) => {\n const manage =
|
|
1
|
+
{"version":3,"sources":["../../../src/commands/projects/list.ts"],"sourcesContent":["import {styleText} from 'node:util'\n\nimport {Flags} from '@oclif/core'\nimport {SanityCommand, subdebug} from '@sanity/cli-core'\nimport size from 'lodash-es/size.js'\nimport sortBy from 'lodash-es/sortBy.js'\n\nimport {getManageUrl} from '../../actions/projects/getManageUrl.js'\nimport {listProjects} from '../../services/projects.js'\n\nconst sortFields = ['id', 'members', 'name', 'url', 'created']\n\nconst projectsDebug = subdebug('projects')\n\nexport class List extends SanityCommand<typeof List> {\n static override description = 'List your projects'\n static override examples = [\n {\n command: '<%= config.bin %> <%= command.id %>',\n description: 'List projects',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --sort=members --order=asc',\n description: 'List projects sorted by member count, ascending',\n },\n ]\n\n static override flags = {\n order: Flags.string({\n default: 'desc',\n description: 'Sort direction',\n options: ['asc', 'desc'],\n }),\n sort: Flags.string({\n default: 'created',\n description: 'Sort field',\n options: sortFields,\n }),\n }\n\n static override hiddenAliases: string[] = ['project:list']\n\n public async run() {\n const {order, sort} = this.flags\n\n try {\n const projects = await listProjects()\n const ordered = sortBy(\n projects.map(({createdAt, displayName, id, members = []}) => {\n const manage = getManageUrl(id)\n return [id, members.length, displayName, manage, createdAt].map(String)\n }),\n [sortFields.indexOf(sort)],\n )\n\n const rows = order === 'asc' ? ordered : ordered.toReversed()\n\n // Initialize maxWidths with the width of each header\n const maxWidths = sortFields.map((str) => size(str))\n\n // Calculate maximum width for each column\n for (const row of rows) {\n for (const [i, element] of row.entries()) {\n maxWidths[i] = Math.max(size(element), maxWidths[i])\n }\n }\n\n const printRow = (row: string[]) =>\n row.map((col, i) => `${col}`.padEnd(maxWidths[i])).join(' ')\n\n this.log(styleText('cyan', printRow(sortFields)))\n for (const row of rows) this.log(printRow(row))\n } catch (error) {\n projectsDebug('Error listing projects', error)\n this.error('Failed to list projects', {exit: 1})\n }\n }\n}\n"],"names":["styleText","Flags","SanityCommand","subdebug","size","sortBy","getManageUrl","listProjects","sortFields","projectsDebug","List","description","examples","command","flags","order","string","default","options","sort","hiddenAliases","run","projects","ordered","map","createdAt","displayName","id","members","manage","length","String","indexOf","rows","toReversed","maxWidths","str","row","i","element","entries","Math","max","printRow","col","padEnd","join","log","error","exit"],"mappings":"AAAA,SAAQA,SAAS,QAAO,YAAW;AAEnC,SAAQC,KAAK,QAAO,cAAa;AACjC,SAAQC,aAAa,EAAEC,QAAQ,QAAO,mBAAkB;AACxD,OAAOC,UAAU,oBAAmB;AACpC,OAAOC,YAAY,sBAAqB;AAExC,SAAQC,YAAY,QAAO,yCAAwC;AACnE,SAAQC,YAAY,QAAO,6BAA4B;AAEvD,MAAMC,aAAa;IAAC;IAAM;IAAW;IAAQ;IAAO;CAAU;AAE9D,MAAMC,gBAAgBN,SAAS;AAE/B,OAAO,MAAMO,aAAaR;IACxB,OAAgBS,cAAc,qBAAoB;IAClD,OAAgBC,WAAW;QACzB;YACEC,SAAS;YACTF,aAAa;QACf;QACA;YACEE,SAAS;YACTF,aAAa;QACf;KACD,CAAA;IAED,OAAgBG,QAAQ;QACtBC,OAAOd,MAAMe,MAAM,CAAC;YAClBC,SAAS;YACTN,aAAa;YACbO,SAAS;gBAAC;gBAAO;aAAO;QAC1B;QACAC,MAAMlB,MAAMe,MAAM,CAAC;YACjBC,SAAS;YACTN,aAAa;YACbO,SAASV;QACX;IACF,EAAC;IAED,OAAgBY,gBAA0B;QAAC;KAAe,CAAA;IAE1D,MAAaC,MAAM;QACjB,MAAM,EAACN,KAAK,EAAEI,IAAI,EAAC,GAAG,IAAI,CAACL,KAAK;QAEhC,IAAI;YACF,MAAMQ,WAAW,MAAMf;YACvB,MAAMgB,UAAUlB,OACdiB,SAASE,GAAG,CAAC,CAAC,EAACC,SAAS,EAAEC,WAAW,EAAEC,EAAE,EAAEC,UAAU,EAAE,EAAC;gBACtD,MAAMC,SAASvB,aAAaqB;gBAC5B,OAAO;oBAACA;oBAAIC,QAAQE,MAAM;oBAAEJ;oBAAaG;oBAAQJ;iBAAU,CAACD,GAAG,CAACO;YAClE,IACA;gBAACvB,WAAWwB,OAAO,CAACb;aAAM;YAG5B,MAAMc,OAAOlB,UAAU,QAAQQ,UAAUA,QAAQW,UAAU;YAE3D,qDAAqD;YACrD,MAAMC,YAAY3B,WAAWgB,GAAG,CAAC,CAACY,MAAQhC,KAAKgC;YAE/C,0CAA0C;YAC1C,KAAK,MAAMC,OAAOJ,KAAM;gBACtB,KAAK,MAAM,CAACK,GAAGC,QAAQ,IAAIF,IAAIG,OAAO,GAAI;oBACxCL,SAAS,CAACG,EAAE,GAAGG,KAAKC,GAAG,CAACtC,KAAKmC,UAAUJ,SAAS,CAACG,EAAE;gBACrD;YACF;YAEA,MAAMK,WAAW,CAACN,MAChBA,IAAIb,GAAG,CAAC,CAACoB,KAAKN,IAAM,GAAGM,KAAK,CAACC,MAAM,CAACV,SAAS,CAACG,EAAE,GAAGQ,IAAI,CAAC;YAE1D,IAAI,CAACC,GAAG,CAAC/C,UAAU,QAAQ2C,SAASnC;YACpC,KAAK,MAAM6B,OAAOJ,KAAM,IAAI,CAACc,GAAG,CAACJ,SAASN;QAC5C,EAAE,OAAOW,OAAO;YACdvC,cAAc,0BAA0BuC;YACxC,IAAI,CAACA,KAAK,CAAC,2BAA2B;gBAACC,MAAM;YAAC;QAChD;IACF;AACF"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
|
-
import {
|
|
2
|
+
import { subdebug } from '@sanity/cli-core/debug';
|
|
3
|
+
import { SanityCommand } from '@sanity/cli-core/SanityCommand';
|
|
3
4
|
import { validateAction } from '../../actions/schema/validateAction.js';
|
|
4
5
|
const debug = subdebug('schema:validate');
|
|
5
6
|
export class SchemaValidate extends SanityCommand {
|
|
@@ -66,7 +67,7 @@ export class SchemaValidate extends SanityCommand {
|
|
|
66
67
|
await validateAction(options);
|
|
67
68
|
} catch (error) {
|
|
68
69
|
debug('Error validating schema', error);
|
|
69
|
-
this.error(`Error validating schema: ${error instanceof Error ? error.message : String(error)}`, {
|
|
70
|
+
return this.output.error(`Error validating schema: ${error instanceof Error ? error.message : String(error)}`, {
|
|
70
71
|
exit: 1
|
|
71
72
|
});
|
|
72
73
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/commands/schemas/validate.ts"],"sourcesContent":["import {Flags} from '@oclif/core'\nimport {
|
|
1
|
+
{"version":3,"sources":["../../../src/commands/schemas/validate.ts"],"sourcesContent":["import {Flags} from '@oclif/core'\nimport {subdebug} from '@sanity/cli-core/debug'\nimport {SanityCommand} from '@sanity/cli-core/SanityCommand'\n\nimport {validateAction} from '../../actions/schema/validateAction.js'\n\nconst debug = subdebug('schema:validate')\nexport class SchemaValidate extends SanityCommand<typeof SchemaValidate> {\n static override description = 'Validates all schema types specified in a workspace'\n\n static override examples = [\n {\n command: '<%= config.bin %> <%= command.id %> --workspace default',\n description: 'Validates all schema types in a Sanity project with more than one workspace',\n },\n {\n command: '<%= config.bin %> <%= command.id %> > report.txt',\n description: 'Save the results of the report into a file',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --level error',\n description: 'Report out only errors',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --debug-metafile-path metafile.json',\n description:\n 'Generate a report which can be analyzed with https://esbuild.github.io/analyze/',\n },\n ]\n\n static override flags = {\n 'debug-metafile-path': Flags.string({\n description:\n 'Optional path where a metafile will be written for build analysis. Only written on successful validation. Can be analyzed at https://esbuild.github.io/analyze/',\n helpGroup: 'DEBUG',\n }),\n format: Flags.string({\n default: 'pretty',\n description: 'The output format used to print schema errors and warnings',\n options: ['pretty', 'ndjson', 'json'],\n }),\n level: Flags.string({\n default: 'warning',\n description: 'The minimum level reported out',\n options: ['error', 'warning'],\n }),\n workspace: Flags.string({\n description: 'The name of the workspace to use when validating all schema types',\n }),\n }\n\n static override hiddenAliases: string[] = ['schema:validate']\n\n public async run(): Promise<void> {\n const {flags} = await this.parse(SchemaValidate)\n\n const workDir = (await this.getProjectRoot()).directory\n\n const options = {\n debugMetafilePath: flags['debug-metafile-path'],\n format: flags.format,\n level: flags.level as 'error' | 'warning',\n output: this.output,\n workDir,\n workspace: flags.workspace,\n }\n\n try {\n await validateAction(options)\n } catch (error) {\n debug('Error validating schema', error)\n return this.output.error(\n `Error validating schema: ${error instanceof Error ? error.message : String(error)}`,\n {exit: 1},\n )\n }\n }\n}\n"],"names":["Flags","subdebug","SanityCommand","validateAction","debug","SchemaValidate","description","examples","command","flags","string","helpGroup","format","default","options","level","workspace","hiddenAliases","run","parse","workDir","getProjectRoot","directory","debugMetafilePath","output","error","Error","message","String","exit"],"mappings":"AAAA,SAAQA,KAAK,QAAO,cAAa;AACjC,SAAQC,QAAQ,QAAO,yBAAwB;AAC/C,SAAQC,aAAa,QAAO,iCAAgC;AAE5D,SAAQC,cAAc,QAAO,yCAAwC;AAErE,MAAMC,QAAQH,SAAS;AACvB,OAAO,MAAMI,uBAAuBH;IAClC,OAAgBI,cAAc,sDAAqD;IAEnF,OAAgBC,WAAW;QACzB;YACEC,SAAS;YACTF,aAAa;QACf;QACA;YACEE,SAAS;YACTF,aAAa;QACf;QACA;YACEE,SAAS;YACTF,aAAa;QACf;QACA;YACEE,SAAS;YACTF,aACE;QACJ;KACD,CAAA;IAED,OAAgBG,QAAQ;QACtB,uBAAuBT,MAAMU,MAAM,CAAC;YAClCJ,aACE;YACFK,WAAW;QACb;QACAC,QAAQZ,MAAMU,MAAM,CAAC;YACnBG,SAAS;YACTP,aAAa;YACbQ,SAAS;gBAAC;gBAAU;gBAAU;aAAO;QACvC;QACAC,OAAOf,MAAMU,MAAM,CAAC;YAClBG,SAAS;YACTP,aAAa;YACbQ,SAAS;gBAAC;gBAAS;aAAU;QAC/B;QACAE,WAAWhB,MAAMU,MAAM,CAAC;YACtBJ,aAAa;QACf;IACF,EAAC;IAED,OAAgBW,gBAA0B;QAAC;KAAkB,CAAA;IAE7D,MAAaC,MAAqB;QAChC,MAAM,EAACT,KAAK,EAAC,GAAG,MAAM,IAAI,CAACU,KAAK,CAACd;QAEjC,MAAMe,UAAU,AAAC,CAAA,MAAM,IAAI,CAACC,cAAc,EAAC,EAAGC,SAAS;QAEvD,MAAMR,UAAU;YACdS,mBAAmBd,KAAK,CAAC,sBAAsB;YAC/CG,QAAQH,MAAMG,MAAM;YACpBG,OAAON,MAAMM,KAAK;YAClBS,QAAQ,IAAI,CAACA,MAAM;YACnBJ;YACAJ,WAAWP,MAAMO,SAAS;QAC5B;QAEA,IAAI;YACF,MAAMb,eAAeW;QACvB,EAAE,OAAOW,OAAO;YACdrB,MAAM,2BAA2BqB;YACjC,OAAO,IAAI,CAACD,MAAM,CAACC,KAAK,CACtB,CAAC,yBAAyB,EAAEA,iBAAiBC,QAAQD,MAAME,OAAO,GAAGC,OAAOH,QAAQ,EACpF;gBAACI,MAAM;YAAC;QAEZ;IACF;AACF"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { SanityCommand, subdebug } from '@sanity/cli-core';
|
|
2
|
+
import { getErrorMessage, toError } from '@sanity/cli-core/errors';
|
|
2
3
|
import { configureSkills } from '../../actions/skills/configureSkills.js';
|
|
3
4
|
import { SkillsInstallTrace } from '../../telemetry/skills.telemetry.js';
|
|
4
|
-
import { getErrorMessage, toError } from '../../util/getErrorMessage.js';
|
|
5
5
|
const debug = subdebug('skills:install');
|
|
6
6
|
export class InstallSkillsCommand extends SanityCommand {
|
|
7
7
|
static description = 'Install Sanity agent skills for detected AI editors (Antigravity, Claude Code, Cline, Cline CLI, Codex CLI, Cursor, Gemini CLI, GitHub Copilot CLI, OpenCode, VS Code, VS Code Insiders)';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/commands/skills/install.ts"],"sourcesContent":["import {SanityCommand, subdebug} from '@sanity/cli-core'\
|
|
1
|
+
{"version":3,"sources":["../../../src/commands/skills/install.ts"],"sourcesContent":["import {SanityCommand, subdebug} from '@sanity/cli-core'\nimport {getErrorMessage, toError} from '@sanity/cli-core/errors'\n\nimport {configureSkills} from '../../actions/skills/configureSkills.js'\nimport {SkillsInstallTrace} from '../../telemetry/skills.telemetry.js'\n\nconst debug = subdebug('skills:install')\n\nexport class InstallSkillsCommand extends SanityCommand<typeof InstallSkillsCommand> {\n static override description =\n 'Install Sanity agent skills for detected AI editors (Antigravity, Claude Code, Cline, Cline CLI, Codex CLI, Cursor, Gemini CLI, GitHub Copilot CLI, OpenCode, VS Code, VS Code Insiders)'\n\n static override examples = [\n {\n command: '<%= config.bin %> <%= command.id %>',\n description: 'Install Sanity agent skills for detected AI editors',\n },\n ]\n\n public async run(): Promise<void> {\n const trace = this.telemetry.trace(SkillsInstallTrace)\n trace.start()\n\n try {\n const result = await configureSkills({output: this.output})\n\n trace.log({\n detectedEditors: result.detectedEditors,\n installedAgents: result.installedAgents,\n })\n\n if (result.error) {\n trace.error(result.error)\n } else {\n trace.complete()\n }\n } catch (error) {\n debug('Skills installation failed: %O', error)\n trace.error(toError(error))\n this.error(getErrorMessage(error), {exit: 1})\n }\n }\n}\n"],"names":["SanityCommand","subdebug","getErrorMessage","toError","configureSkills","SkillsInstallTrace","debug","InstallSkillsCommand","description","examples","command","run","trace","telemetry","start","result","output","log","detectedEditors","installedAgents","error","complete","exit"],"mappings":"AAAA,SAAQA,aAAa,EAAEC,QAAQ,QAAO,mBAAkB;AACxD,SAAQC,eAAe,EAAEC,OAAO,QAAO,0BAAyB;AAEhE,SAAQC,eAAe,QAAO,0CAAyC;AACvE,SAAQC,kBAAkB,QAAO,sCAAqC;AAEtE,MAAMC,QAAQL,SAAS;AAEvB,OAAO,MAAMM,6BAA6BP;IACxC,OAAgBQ,cACd,2LAA0L;IAE5L,OAAgBC,WAAW;QACzB;YACEC,SAAS;YACTF,aAAa;QACf;KACD,CAAA;IAED,MAAaG,MAAqB;QAChC,MAAMC,QAAQ,IAAI,CAACC,SAAS,CAACD,KAAK,CAACP;QACnCO,MAAME,KAAK;QAEX,IAAI;YACF,MAAMC,SAAS,MAAMX,gBAAgB;gBAACY,QAAQ,IAAI,CAACA,MAAM;YAAA;YAEzDJ,MAAMK,GAAG,CAAC;gBACRC,iBAAiBH,OAAOG,eAAe;gBACvCC,iBAAiBJ,OAAOI,eAAe;YACzC;YAEA,IAAIJ,OAAOK,KAAK,EAAE;gBAChBR,MAAMQ,KAAK,CAACL,OAAOK,KAAK;YAC1B,OAAO;gBACLR,MAAMS,QAAQ;YAChB;QACF,EAAE,OAAOD,OAAO;YACdd,MAAM,kCAAkCc;YACxCR,MAAMQ,KAAK,CAACjB,QAAQiB;YACpB,IAAI,CAACA,KAAK,CAAClB,gBAAgBkB,QAAQ;gBAACE,MAAM;YAAC;QAC7C;IACF;AACF"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
2
|
import { SanityCommand, subdebug } from '@sanity/cli-core';
|
|
3
|
+
import { getErrorMessage } from '@sanity/cli-core/errors';
|
|
3
4
|
import { Table } from 'console-table-printer';
|
|
4
5
|
import { promptForProject } from '../../prompts/promptForProject.js';
|
|
5
6
|
import { getTokens } from '../../services/tokens.js';
|
|
6
|
-
import { getErrorMessage } from '../../util/getErrorMessage.js';
|
|
7
7
|
import { getProjectIdFlag } from '../../util/sharedFlags.js';
|
|
8
8
|
const listTokenDebug = subdebug('tokens:list');
|
|
9
9
|
export class TokensListCommand extends SanityCommand {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/commands/tokens/list.ts"],"sourcesContent":["import {Flags} from '@oclif/core'\nimport {SanityCommand, subdebug} from '@sanity/cli-core'\nimport {Table} from 'console-table-printer'\n\nimport {type Token} from '../../actions/tokens/types.js'\nimport {promptForProject} from '../../prompts/promptForProject.js'\nimport {getTokens} from '../../services/tokens.js'\nimport {
|
|
1
|
+
{"version":3,"sources":["../../../src/commands/tokens/list.ts"],"sourcesContent":["import {Flags} from '@oclif/core'\nimport {SanityCommand, subdebug} from '@sanity/cli-core'\nimport {getErrorMessage} from '@sanity/cli-core/errors'\nimport {Table} from 'console-table-printer'\n\nimport {type Token} from '../../actions/tokens/types.js'\nimport {promptForProject} from '../../prompts/promptForProject.js'\nimport {getTokens} from '../../services/tokens.js'\nimport {getProjectIdFlag} from '../../util/sharedFlags.js'\n\nconst listTokenDebug = subdebug('tokens:list')\n\nexport class TokensListCommand extends SanityCommand<typeof TokensListCommand> {\n static override description = 'List API tokens for the project'\n static override examples = [\n {\n command: '<%= config.bin %> <%= command.id %>',\n description: 'List tokens for the project',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --json',\n description: 'List tokens in JSON format',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --project-id abc123',\n description: 'List tokens for a specific project',\n },\n ]\n static override flags = {\n ...getProjectIdFlag({\n description: 'Project ID to list tokens for',\n semantics: 'override',\n }),\n json: Flags.boolean({\n default: false,\n description: 'Output tokens in JSON format',\n }),\n }\n\n static override hiddenAliases: string[] = ['token:list']\n\n public async run(): Promise<void> {\n const {flags} = await this.parse(TokensListCommand)\n const {json} = flags\n const outputJson = json ?? false\n\n // Ensure we have project context\n const projectId = await this.getProjectId({\n fallback: () =>\n promptForProject({\n requiredPermissions: [{grant: 'read', permission: 'sanity.project.tokens'}],\n }),\n })\n\n let tokens: Token[]\n try {\n tokens = await getTokens(projectId)\n } catch (error) {\n const message = getErrorMessage(error)\n listTokenDebug(`Error fetching tokens for project ${projectId}`, error)\n this.error(`Token list retrieval failed:\\n${message}`, {exit: 1})\n }\n\n if (outputJson) {\n this.log(JSON.stringify(tokens, null, 2))\n return\n }\n\n if (tokens.length === 0) {\n this.log('No API tokens found for this project.')\n return\n }\n\n const table = new Table({\n columns: [\n {alignment: 'left', maxLen: 40, name: 'label', title: 'Label'},\n {alignment: 'left', maxLen: 20, name: 'id', title: 'Token ID'},\n {alignment: 'left', maxLen: 30, name: 'roles', title: 'Roles'},\n ],\n title: `Found ${tokens.length} API tokens`,\n })\n\n for (const token of tokens) {\n const roles = token.roles?.map((role) => role.title).join(', ') || 'No roles'\n const truncatedLabel =\n token.label.length > 37 ? `${token.label.slice(0, 37)}...` : token.label\n const truncatedRoles = roles.length > 27 ? `${roles.slice(0, 27)}...` : roles\n\n table.addRow({\n id: token.id,\n label: truncatedLabel,\n roles: truncatedRoles,\n })\n }\n\n table.printTable()\n }\n}\n"],"names":["Flags","SanityCommand","subdebug","getErrorMessage","Table","promptForProject","getTokens","getProjectIdFlag","listTokenDebug","TokensListCommand","description","examples","command","flags","semantics","json","boolean","default","hiddenAliases","run","parse","outputJson","projectId","getProjectId","fallback","requiredPermissions","grant","permission","tokens","error","message","exit","log","JSON","stringify","length","table","columns","alignment","maxLen","name","title","token","roles","map","role","join","truncatedLabel","label","slice","truncatedRoles","addRow","id","printTable"],"mappings":"AAAA,SAAQA,KAAK,QAAO,cAAa;AACjC,SAAQC,aAAa,EAAEC,QAAQ,QAAO,mBAAkB;AACxD,SAAQC,eAAe,QAAO,0BAAyB;AACvD,SAAQC,KAAK,QAAO,wBAAuB;AAG3C,SAAQC,gBAAgB,QAAO,oCAAmC;AAClE,SAAQC,SAAS,QAAO,2BAA0B;AAClD,SAAQC,gBAAgB,QAAO,4BAA2B;AAE1D,MAAMC,iBAAiBN,SAAS;AAEhC,OAAO,MAAMO,0BAA0BR;IACrC,OAAgBS,cAAc,kCAAiC;IAC/D,OAAgBC,WAAW;QACzB;YACEC,SAAS;YACTF,aAAa;QACf;QACA;YACEE,SAAS;YACTF,aAAa;QACf;QACA;YACEE,SAAS;YACTF,aAAa;QACf;KACD,CAAA;IACD,OAAgBG,QAAQ;QACtB,GAAGN,iBAAiB;YAClBG,aAAa;YACbI,WAAW;QACb,EAAE;QACFC,MAAMf,MAAMgB,OAAO,CAAC;YAClBC,SAAS;YACTP,aAAa;QACf;IACF,EAAC;IAED,OAAgBQ,gBAA0B;QAAC;KAAa,CAAA;IAExD,MAAaC,MAAqB;QAChC,MAAM,EAACN,KAAK,EAAC,GAAG,MAAM,IAAI,CAACO,KAAK,CAACX;QACjC,MAAM,EAACM,IAAI,EAAC,GAAGF;QACf,MAAMQ,aAAaN,QAAQ;QAE3B,iCAAiC;QACjC,MAAMO,YAAY,MAAM,IAAI,CAACC,YAAY,CAAC;YACxCC,UAAU,IACRnB,iBAAiB;oBACfoB,qBAAqB;wBAAC;4BAACC,OAAO;4BAAQC,YAAY;wBAAuB;qBAAE;gBAC7E;QACJ;QAEA,IAAIC;QACJ,IAAI;YACFA,SAAS,MAAMtB,UAAUgB;QAC3B,EAAE,OAAOO,OAAO;YACd,MAAMC,UAAU3B,gBAAgB0B;YAChCrB,eAAe,CAAC,kCAAkC,EAAEc,WAAW,EAAEO;YACjE,IAAI,CAACA,KAAK,CAAC,CAAC,8BAA8B,EAAEC,SAAS,EAAE;gBAACC,MAAM;YAAC;QACjE;QAEA,IAAIV,YAAY;YACd,IAAI,CAACW,GAAG,CAACC,KAAKC,SAAS,CAACN,QAAQ,MAAM;YACtC;QACF;QAEA,IAAIA,OAAOO,MAAM,KAAK,GAAG;YACvB,IAAI,CAACH,GAAG,CAAC;YACT;QACF;QAEA,MAAMI,QAAQ,IAAIhC,MAAM;YACtBiC,SAAS;gBACP;oBAACC,WAAW;oBAAQC,QAAQ;oBAAIC,MAAM;oBAASC,OAAO;gBAAO;gBAC7D;oBAACH,WAAW;oBAAQC,QAAQ;oBAAIC,MAAM;oBAAMC,OAAO;gBAAU;gBAC7D;oBAACH,WAAW;oBAAQC,QAAQ;oBAAIC,MAAM;oBAASC,OAAO;gBAAO;aAC9D;YACDA,OAAO,CAAC,MAAM,EAAEb,OAAOO,MAAM,CAAC,WAAW,CAAC;QAC5C;QAEA,KAAK,MAAMO,SAASd,OAAQ;YAC1B,MAAMe,QAAQD,MAAMC,KAAK,EAAEC,IAAI,CAACC,OAASA,KAAKJ,KAAK,EAAEK,KAAK,SAAS;YACnE,MAAMC,iBACJL,MAAMM,KAAK,CAACb,MAAM,GAAG,KAAK,GAAGO,MAAMM,KAAK,CAACC,KAAK,CAAC,GAAG,IAAI,GAAG,CAAC,GAAGP,MAAMM,KAAK;YAC1E,MAAME,iBAAiBP,MAAMR,MAAM,GAAG,KAAK,GAAGQ,MAAMM,KAAK,CAAC,GAAG,IAAI,GAAG,CAAC,GAAGN;YAExEP,MAAMe,MAAM,CAAC;gBACXC,IAAIV,MAAMU,EAAE;gBACZJ,OAAOD;gBACPJ,OAAOO;YACT;QACF;QAEAd,MAAMiB,UAAU;IAClB;AACF"}
|
|
@@ -1,13 +1,34 @@
|
|
|
1
|
-
import { styleText } from 'node:util';
|
|
2
1
|
import { Flags } from '@oclif/core';
|
|
3
2
|
import { SanityCommand } from '@sanity/cli-core';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { deleteUserApplication } from '../services/userApplications.js';
|
|
3
|
+
import { createAppUndeployAdapter, createStudioUndeployAdapter } from '../actions/undeploy/adapters.js';
|
|
4
|
+
import { runUndeploy } from '../actions/undeploy/runUndeploy.js';
|
|
7
5
|
import { determineIsApp } from '../util/determineIsApp.js';
|
|
8
6
|
export class UndeployCommand extends SanityCommand {
|
|
9
7
|
static description = 'Removes the deployed Sanity Studio/App from Sanity hosting';
|
|
8
|
+
static examples = [
|
|
9
|
+
{
|
|
10
|
+
command: '<%= config.bin %> <%= command.id %>',
|
|
11
|
+
description: 'Undeploy the studio or application after confirming'
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
command: '<%= config.bin %> <%= command.id %> --dry-run',
|
|
15
|
+
description: 'Report what would be undeployed without deleting anything'
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
command: '<%= config.bin %> <%= command.id %> --json --yes',
|
|
19
|
+
description: 'Undeploy without prompting and report the result as JSON'
|
|
20
|
+
}
|
|
21
|
+
];
|
|
10
22
|
static flags = {
|
|
23
|
+
'dry-run': Flags.boolean({
|
|
24
|
+
default: false,
|
|
25
|
+
description: 'Report what would be undeployed without deleting anything'
|
|
26
|
+
}),
|
|
27
|
+
json: Flags.boolean({
|
|
28
|
+
char: 'j',
|
|
29
|
+
default: false,
|
|
30
|
+
description: 'Output the result as JSON'
|
|
31
|
+
}),
|
|
11
32
|
yes: Flags.boolean({
|
|
12
33
|
char: 'y',
|
|
13
34
|
default: false,
|
|
@@ -17,76 +38,17 @@ export class UndeployCommand extends SanityCommand {
|
|
|
17
38
|
async run() {
|
|
18
39
|
const { flags } = await this.parse(UndeployCommand);
|
|
19
40
|
const cliConfig = await this.getCliConfig();
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
this.log('Application with the given ID does not exist.');
|
|
32
|
-
this.log('Nothing to undeploy.');
|
|
33
|
-
} else {
|
|
34
|
-
this.log('Your project has not been assigned an app ID or a studio hostname,');
|
|
35
|
-
this.log('or the `appId` or `studioHost` provided does not exist.');
|
|
36
|
-
this.log('Nothing to undeploy.');
|
|
37
|
-
}
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
spin.succeed();
|
|
41
|
-
const url = `https://${styleText('yellow', userApplication.appHost)}.sanity.studio`;
|
|
42
|
-
if (!flags.yes) {
|
|
43
|
-
let message = `This will undeploy ${url} and make it unavailable for your users.\nThe hostname will be available for anyone to claim.\nAre you ${styleText('red', 'sure')} you want to undeploy?`;
|
|
44
|
-
if (isApp) {
|
|
45
|
-
message = `This will undeploy the following application:
|
|
46
|
-
|
|
47
|
-
Title: ${styleText('yellow', userApplication.title || '(untitled application)')}
|
|
48
|
-
ID: ${styleText('yellow', userApplication.id)}
|
|
49
|
-
|
|
50
|
-
The application will no longer be available for any of your users if you proceed.
|
|
51
|
-
|
|
52
|
-
Are you ${styleText('red', 'sure')} you want to undeploy?`;
|
|
53
|
-
}
|
|
54
|
-
const shouldUndeploy = await confirm({
|
|
55
|
-
default: false,
|
|
56
|
-
message
|
|
57
|
-
});
|
|
58
|
-
if (!shouldUndeploy) {
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
spin = spinner(`Undeploying ${isApp ? 'application' : 'studio'}`).start();
|
|
63
|
-
await deleteUserApplication({
|
|
64
|
-
applicationId: userApplication.id,
|
|
65
|
-
appType: isApp ? 'coreApp' : 'studio'
|
|
66
|
-
});
|
|
67
|
-
spin.succeed();
|
|
68
|
-
if (isApp) {
|
|
69
|
-
this.log(`\n${styleText('bold', '⏱️ Application undeploy scheduled.')} It might be a few minutes until ${userApplication.title ? styleText('italic', `'${userApplication.title}'`) : 'your application'} is unavailable.`);
|
|
70
|
-
this.log(`\n${styleText('bold', 'Remember to remove `deployment.appId` from your application configuration')} to avoid errors when redeploying.`);
|
|
71
|
-
} else {
|
|
72
|
-
this.log(`\nStudio undeploy scheduled. It might be a few minutes until ${url} is unavailable.`);
|
|
73
|
-
}
|
|
74
|
-
} catch (error) {
|
|
75
|
-
spin.fail();
|
|
76
|
-
if (error.message === NO_APP_ID) {
|
|
77
|
-
this.log('No application ID provided.');
|
|
78
|
-
this.log('Please set id in `deployment.appId` in sanity.cli.js or sanity.cli.ts.');
|
|
79
|
-
this.log('Nothing to undeploy.');
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
if (error.message === NO_APP_ID_OR_STUDIO_HOST) {
|
|
83
|
-
this.log('No application ID or studio host provided.');
|
|
84
|
-
this.log('Please set id in `deployment.appId` in sanity.cli.js or sanity.cli.ts.');
|
|
85
|
-
this.log('Nothing to undeploy.');
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
this.error(error);
|
|
89
|
-
}
|
|
41
|
+
const adapter = determineIsApp(cliConfig) ? createAppUndeployAdapter(cliConfig) : createStudioUndeployAdapter(cliConfig);
|
|
42
|
+
// An unattended run (--yes, --json, non-TTY) can't answer the confirmation
|
|
43
|
+
// prompt, so it consents up front; machine callers preview with --dry-run.
|
|
44
|
+
const undeployFlags = this.isUnattended() ? {
|
|
45
|
+
...flags,
|
|
46
|
+
yes: true
|
|
47
|
+
} : flags;
|
|
48
|
+
await runUndeploy({
|
|
49
|
+
flags: undeployFlags,
|
|
50
|
+
output: this.output
|
|
51
|
+
}, adapter);
|
|
90
52
|
}
|
|
91
53
|
}
|
|
92
54
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/commands/undeploy.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"sources":["../../src/commands/undeploy.ts"],"sourcesContent":["import {Flags} from '@oclif/core'\nimport {SanityCommand} from '@sanity/cli-core'\n\nimport {\n createAppUndeployAdapter,\n createStudioUndeployAdapter,\n} from '../actions/undeploy/adapters.js'\nimport {runUndeploy} from '../actions/undeploy/runUndeploy.js'\nimport {determineIsApp} from '../util/determineIsApp.js'\n\nexport class UndeployCommand extends SanityCommand<typeof UndeployCommand> {\n static override description = 'Removes the deployed Sanity Studio/App from Sanity hosting'\n\n static override examples = [\n {\n command: '<%= config.bin %> <%= command.id %>',\n description: 'Undeploy the studio or application after confirming',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --dry-run',\n description: 'Report what would be undeployed without deleting anything',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --json --yes',\n description: 'Undeploy without prompting and report the result as JSON',\n },\n ]\n\n static override flags = {\n 'dry-run': Flags.boolean({\n default: false,\n description: 'Report what would be undeployed without deleting anything',\n }),\n json: Flags.boolean({\n char: 'j',\n default: false,\n description: 'Output the result as JSON',\n }),\n yes: Flags.boolean({\n char: 'y',\n default: false,\n description:\n 'Unattended mode, answers \"yes\" to any \"yes/no\" prompt and otherwise uses defaults',\n }),\n }\n\n public async run(): Promise<void> {\n const {flags} = await this.parse(UndeployCommand)\n\n const cliConfig = await this.getCliConfig()\n const adapter = determineIsApp(cliConfig)\n ? createAppUndeployAdapter(cliConfig)\n : createStudioUndeployAdapter(cliConfig)\n\n // An unattended run (--yes, --json, non-TTY) can't answer the confirmation\n // prompt, so it consents up front; machine callers preview with --dry-run.\n const undeployFlags = this.isUnattended() ? {...flags, yes: true} : flags\n await runUndeploy({flags: undeployFlags, output: this.output}, adapter)\n }\n}\n"],"names":["Flags","SanityCommand","createAppUndeployAdapter","createStudioUndeployAdapter","runUndeploy","determineIsApp","UndeployCommand","description","examples","command","flags","boolean","default","json","char","yes","run","parse","cliConfig","getCliConfig","adapter","undeployFlags","isUnattended","output"],"mappings":"AAAA,SAAQA,KAAK,QAAO,cAAa;AACjC,SAAQC,aAAa,QAAO,mBAAkB;AAE9C,SACEC,wBAAwB,EACxBC,2BAA2B,QACtB,kCAAiC;AACxC,SAAQC,WAAW,QAAO,qCAAoC;AAC9D,SAAQC,cAAc,QAAO,4BAA2B;AAExD,OAAO,MAAMC,wBAAwBL;IACnC,OAAgBM,cAAc,6DAA4D;IAE1F,OAAgBC,WAAW;QACzB;YACEC,SAAS;YACTF,aAAa;QACf;QACA;YACEE,SAAS;YACTF,aAAa;QACf;QACA;YACEE,SAAS;YACTF,aAAa;QACf;KACD,CAAA;IAED,OAAgBG,QAAQ;QACtB,WAAWV,MAAMW,OAAO,CAAC;YACvBC,SAAS;YACTL,aAAa;QACf;QACAM,MAAMb,MAAMW,OAAO,CAAC;YAClBG,MAAM;YACNF,SAAS;YACTL,aAAa;QACf;QACAQ,KAAKf,MAAMW,OAAO,CAAC;YACjBG,MAAM;YACNF,SAAS;YACTL,aACE;QACJ;IACF,EAAC;IAED,MAAaS,MAAqB;QAChC,MAAM,EAACN,KAAK,EAAC,GAAG,MAAM,IAAI,CAACO,KAAK,CAACX;QAEjC,MAAMY,YAAY,MAAM,IAAI,CAACC,YAAY;QACzC,MAAMC,UAAUf,eAAea,aAC3BhB,yBAAyBgB,aACzBf,4BAA4Be;QAEhC,2EAA2E;QAC3E,2EAA2E;QAC3E,MAAMG,gBAAgB,IAAI,CAACC,YAAY,KAAK;YAAC,GAAGZ,KAAK;YAAEK,KAAK;QAAI,IAAIL;QACpE,MAAMN,YAAY;YAACM,OAAOW;YAAeE,QAAQ,IAAI,CAACA,MAAM;QAAA,GAAGH;IACjE;AACF"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { debug as cliDebug } from '@sanity/cli-core';
|
|
2
|
+
import { getErrorMessage } from '@sanity/cli-core/errors';
|
|
3
|
+
import { logSymbols } from '@sanity/cli-core/ux';
|
|
4
|
+
export function createFailFastReporter(output) {
|
|
5
|
+
return {
|
|
6
|
+
report (check) {
|
|
7
|
+
// Fixes surface in both modes: appended after the message here, and in the
|
|
8
|
+
// dry-run report, so the problem and its fix never drift apart.
|
|
9
|
+
const text = check.solution ? `${check.message}: ${check.solution}` : check.message;
|
|
10
|
+
if (check.status === 'fail') {
|
|
11
|
+
output.error(text, {
|
|
12
|
+
exit: check.exitCode ?? 1
|
|
13
|
+
});
|
|
14
|
+
} else if (check.status === 'warn') {
|
|
15
|
+
output.warn(text);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export function createCollectingReporter() {
|
|
21
|
+
const results = [];
|
|
22
|
+
return {
|
|
23
|
+
report (check) {
|
|
24
|
+
results.push(check);
|
|
25
|
+
},
|
|
26
|
+
results
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Runs a fallible step and turns a throw into a `fail` check. In a real run
|
|
31
|
+
* that fail exits (aborting the sequence); in a dry run it's recorded and
|
|
32
|
+
* `null` comes back so the caller can skip the rest of the step. `name` labels
|
|
33
|
+
* the step in debug logs.
|
|
34
|
+
*/ export async function runStep(reporter, step) {
|
|
35
|
+
const { debug = cliDebug, formatError = getErrorMessage, name, solution, work } = step;
|
|
36
|
+
try {
|
|
37
|
+
return await work();
|
|
38
|
+
} catch (err) {
|
|
39
|
+
debug(`${name} step failed`, err);
|
|
40
|
+
reporter.report({
|
|
41
|
+
message: formatError(err),
|
|
42
|
+
solution,
|
|
43
|
+
status: 'fail'
|
|
44
|
+
});
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export function checkStatusIcon(status) {
|
|
49
|
+
switch(status){
|
|
50
|
+
case 'fail':
|
|
51
|
+
{
|
|
52
|
+
return logSymbols.error;
|
|
53
|
+
}
|
|
54
|
+
case 'skip':
|
|
55
|
+
{
|
|
56
|
+
return logSymbols.info;
|
|
57
|
+
}
|
|
58
|
+
case 'warn':
|
|
59
|
+
{
|
|
60
|
+
return logSymbols.warning;
|
|
61
|
+
}
|
|
62
|
+
default:
|
|
63
|
+
{
|
|
64
|
+
return logSymbols.success;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Indents continuation lines of a multi-line check message past the status
|
|
70
|
+
* icon so its items nest under the heading.
|
|
71
|
+
*/ export function nestLines(text) {
|
|
72
|
+
return text.replaceAll('\n', '\n ');
|
|
73
|
+
}
|
|
74
|
+
/** Renders problem/warning checks under a title, each with its fix appended. */ export function renderIssues(output, title, checks) {
|
|
75
|
+
if (checks.length === 0) return;
|
|
76
|
+
output.log(`\n${title}`);
|
|
77
|
+
for (const check of checks){
|
|
78
|
+
const fix = check.solution ? `: ${check.solution}` : '';
|
|
79
|
+
output.log(nestLines(` ${checkStatusIcon(check.status)} ${check.message}${fix}`));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
//# sourceMappingURL=checks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/util/checks.ts"],"sourcesContent":["import {debug as cliDebug, type Output} from '@sanity/cli-core'\nimport {getErrorMessage} from '@sanity/cli-core/errors'\nimport {logSymbols} from '@sanity/cli-core/ux'\n\nexport type CheckStatus = 'fail' | 'pass' | 'skip' | 'warn'\n\nexport interface Check {\n message: string\n status: CheckStatus\n\n /** Exit code a real run uses when this check fails; defaults to 1 */\n exitCode?: number\n /** Actionable fix, shown under a failing or warning check */\n solution?: string\n}\n\n/**\n * Where a command's steps send their check outcomes — and the only place the\n * run mode lives. A real run fails fast: a `fail` prints and exits immediately,\n * which aborts the sequence. A dry run collects every outcome and never exits.\n * Steps just call `report`; they never know which mode is running.\n */\nexport interface CheckReporter<TCheck extends Check = Check> {\n report(check: TCheck): void\n}\n\nexport function createFailFastReporter(output: Output): CheckReporter {\n return {\n report(check) {\n // Fixes surface in both modes: appended after the message here, and in the\n // dry-run report, so the problem and its fix never drift apart.\n const text = check.solution ? `${check.message}: ${check.solution}` : check.message\n if (check.status === 'fail') {\n output.error(text, {exit: check.exitCode ?? 1})\n } else if (check.status === 'warn') {\n output.warn(text)\n }\n },\n }\n}\n\nexport function createCollectingReporter<TCheck extends Check = Check>(): CheckReporter<TCheck> & {\n results: TCheck[]\n} {\n const results: TCheck[] = []\n return {\n report(check) {\n results.push(check)\n },\n results,\n }\n}\n\n/**\n * Runs a fallible step and turns a throw into a `fail` check. In a real run\n * that fail exits (aborting the sequence); in a dry run it's recorded and\n * `null` comes back so the caller can skip the rest of the step. `name` labels\n * the step in debug logs.\n */\nexport async function runStep<T>(\n reporter: CheckReporter,\n step: {\n debug?: (message: string, error: unknown) => void\n formatError?: (error: unknown) => string\n\n name: string\n solution?: string\n work: () => Promise<T>\n },\n): Promise<T | null> {\n const {debug = cliDebug, formatError = getErrorMessage, name, solution, work} = step\n try {\n return await work()\n } catch (err) {\n debug(`${name} step failed`, err)\n reporter.report({message: formatError(err), solution, status: 'fail'})\n return null\n }\n}\n\nexport function checkStatusIcon(status: CheckStatus): string {\n switch (status) {\n case 'fail': {\n return logSymbols.error\n }\n case 'skip': {\n return logSymbols.info\n }\n case 'warn': {\n return logSymbols.warning\n }\n default: {\n return logSymbols.success\n }\n }\n}\n\n/**\n * Indents continuation lines of a multi-line check message past the status\n * icon so its items nest under the heading.\n */\nexport function nestLines(text: string): string {\n return text.replaceAll('\\n', '\\n ')\n}\n\n/** Renders problem/warning checks under a title, each with its fix appended. */\nexport function renderIssues(output: Output, title: string, checks: Check[]): void {\n if (checks.length === 0) return\n\n output.log(`\\n${title}`)\n for (const check of checks) {\n const fix = check.solution ? `: ${check.solution}` : ''\n output.log(nestLines(` ${checkStatusIcon(check.status)} ${check.message}${fix}`))\n }\n}\n"],"names":["debug","cliDebug","getErrorMessage","logSymbols","createFailFastReporter","output","report","check","text","solution","message","status","error","exit","exitCode","warn","createCollectingReporter","results","push","runStep","reporter","step","formatError","name","work","err","checkStatusIcon","info","warning","success","nestLines","replaceAll","renderIssues","title","checks","length","log","fix"],"mappings":"AAAA,SAAQA,SAASC,QAAQ,QAAoB,mBAAkB;AAC/D,SAAQC,eAAe,QAAO,0BAAyB;AACvD,SAAQC,UAAU,QAAO,sBAAqB;AAwB9C,OAAO,SAASC,uBAAuBC,MAAc;IACnD,OAAO;QACLC,QAAOC,KAAK;YACV,2EAA2E;YAC3E,gEAAgE;YAChE,MAAMC,OAAOD,MAAME,QAAQ,GAAG,GAAGF,MAAMG,OAAO,CAAC,EAAE,EAAEH,MAAME,QAAQ,EAAE,GAAGF,MAAMG,OAAO;YACnF,IAAIH,MAAMI,MAAM,KAAK,QAAQ;gBAC3BN,OAAOO,KAAK,CAACJ,MAAM;oBAACK,MAAMN,MAAMO,QAAQ,IAAI;gBAAC;YAC/C,OAAO,IAAIP,MAAMI,MAAM,KAAK,QAAQ;gBAClCN,OAAOU,IAAI,CAACP;YACd;QACF;IACF;AACF;AAEA,OAAO,SAASQ;IAGd,MAAMC,UAAoB,EAAE;IAC5B,OAAO;QACLX,QAAOC,KAAK;YACVU,QAAQC,IAAI,CAACX;QACf;QACAU;IACF;AACF;AAEA;;;;;CAKC,GACD,OAAO,eAAeE,QACpBC,QAAuB,EACvBC,IAOC;IAED,MAAM,EAACrB,QAAQC,QAAQ,EAAEqB,cAAcpB,eAAe,EAAEqB,IAAI,EAAEd,QAAQ,EAAEe,IAAI,EAAC,GAAGH;IAChF,IAAI;QACF,OAAO,MAAMG;IACf,EAAE,OAAOC,KAAK;QACZzB,MAAM,GAAGuB,KAAK,YAAY,CAAC,EAAEE;QAC7BL,SAASd,MAAM,CAAC;YAACI,SAASY,YAAYG;YAAMhB;YAAUE,QAAQ;QAAM;QACpE,OAAO;IACT;AACF;AAEA,OAAO,SAASe,gBAAgBf,MAAmB;IACjD,OAAQA;QACN,KAAK;YAAQ;gBACX,OAAOR,WAAWS,KAAK;YACzB;QACA,KAAK;YAAQ;gBACX,OAAOT,WAAWwB,IAAI;YACxB;QACA,KAAK;YAAQ;gBACX,OAAOxB,WAAWyB,OAAO;YAC3B;QACA;YAAS;gBACP,OAAOzB,WAAW0B,OAAO;YAC3B;IACF;AACF;AAEA;;;CAGC,GACD,OAAO,SAASC,UAAUtB,IAAY;IACpC,OAAOA,KAAKuB,UAAU,CAAC,MAAM;AAC/B;AAEA,8EAA8E,GAC9E,OAAO,SAASC,aAAa3B,MAAc,EAAE4B,KAAa,EAAEC,MAAe;IACzE,IAAIA,OAAOC,MAAM,KAAK,GAAG;IAEzB9B,OAAO+B,GAAG,CAAC,CAAC,EAAE,EAAEH,OAAO;IACvB,KAAK,MAAM1B,SAAS2B,OAAQ;QAC1B,MAAMG,MAAM9B,MAAME,QAAQ,GAAG,CAAC,EAAE,EAAEF,MAAME,QAAQ,EAAE,GAAG;QACrDJ,OAAO+B,GAAG,CAACN,UAAU,CAAC,EAAE,EAAEJ,gBAAgBnB,MAAMI,MAAM,EAAE,CAAC,EAAEJ,MAAMG,OAAO,GAAG2B,KAAK;IAClF;AACF"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { format } from 'node:util';
|
|
2
|
+
/**
|
|
3
|
+
* An `Output` whose `log` writes to stderr, for `--json` runs where the payload
|
|
4
|
+
* owns stdout and progress logs must not corrupt it. Spinners are already on
|
|
5
|
+
* stderr.
|
|
6
|
+
*/ export function toStderrOutput(output) {
|
|
7
|
+
return {
|
|
8
|
+
...output,
|
|
9
|
+
log: (message = '', ...args)=>void process.stderr.write(`${format(message, ...args)}\n`)
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
//# sourceMappingURL=toStderrOutput.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/util/toStderrOutput.ts"],"sourcesContent":["import {format} from 'node:util'\n\nimport {type Output} from '@sanity/cli-core'\n\n/**\n * An `Output` whose `log` writes to stderr, for `--json` runs where the payload\n * owns stdout and progress logs must not corrupt it. Spinners are already on\n * stderr.\n */\nexport function toStderrOutput(output: Output): Output {\n return {\n ...output,\n log: (message = '', ...args: unknown[]) =>\n void process.stderr.write(`${format(message, ...args)}\\n`),\n }\n}\n"],"names":["format","toStderrOutput","output","log","message","args","process","stderr","write"],"mappings":"AAAA,SAAQA,MAAM,QAAO,YAAW;AAIhC;;;;CAIC,GACD,OAAO,SAASC,eAAeC,MAAc;IAC3C,OAAO;QACL,GAAGA,MAAM;QACTC,KAAK,CAACC,UAAU,EAAE,EAAE,GAAGC,OACrB,KAAKC,QAAQC,MAAM,CAACC,KAAK,CAAC,GAAGR,OAAOI,YAAYC,MAAM,EAAE,CAAC;IAC7D;AACF"}
|