@sanity/cli 7.8.0 → 7.10.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 +132 -0
- package/dist/actions/auth/login/getProvider.js +7 -2
- package/dist/actions/auth/login/getProvider.js.map +1 -1
- package/dist/actions/dataset/create.js +2 -2
- package/dist/actions/dataset/create.js.map +1 -1
- package/dist/actions/deploy/createUserApplication.js +8 -5
- package/dist/actions/deploy/createUserApplication.js.map +1 -1
- package/dist/actions/deploy/deployApp.js +43 -20
- package/dist/actions/deploy/deployApp.js.map +1 -1
- package/dist/actions/deploy/deployChecks.js +9 -8
- package/dist/actions/deploy/deployChecks.js.map +1 -1
- package/dist/actions/deploy/deployStudio.js +12 -9
- package/dist/actions/deploy/deployStudio.js.map +1 -1
- package/dist/actions/deploy/resolveDeployTarget.js +3 -2
- package/dist/actions/deploy/resolveDeployTarget.js.map +1 -1
- package/dist/actions/deploy/urlUtils.js +0 -4
- package/dist/actions/deploy/urlUtils.js.map +1 -1
- package/dist/actions/documents/types.js.map +1 -1
- package/dist/actions/documents/validate.js +2 -1
- package/dist/actions/documents/validate.js.map +1 -1
- package/dist/actions/manifest/extractCoreAppManifest.js +6 -1
- package/dist/actions/manifest/extractCoreAppManifest.js.map +1 -1
- package/dist/actions/organizations/getOrganization.js +7 -3
- package/dist/actions/organizations/getOrganization.js.map +1 -1
- package/dist/actions/organizations/validateOrganizationSlug.js +20 -0
- package/dist/actions/organizations/validateOrganizationSlug.js.map +1 -0
- package/dist/actions/undeploy/adapters.js +3 -3
- package/dist/actions/undeploy/adapters.js.map +1 -1
- package/dist/actions/undeploy/runUndeploy.js +20 -6
- package/dist/actions/undeploy/runUndeploy.js.map +1 -1
- package/dist/actions/undeploy/undeployPlan.js +18 -3
- package/dist/actions/undeploy/undeployPlan.js.map +1 -1
- package/dist/commands/documents/validate.js +18 -17
- package/dist/commands/documents/validate.js.map +1 -1
- package/dist/commands/hooks/create.js +4 -0
- package/dist/commands/hooks/create.js.map +1 -1
- package/dist/commands/hooks/delete.js +5 -0
- package/dist/commands/hooks/delete.js.map +1 -1
- package/dist/commands/hooks/logs.js +5 -0
- package/dist/commands/hooks/logs.js.map +1 -1
- package/dist/commands/install.js +1 -1
- package/dist/commands/install.js.map +1 -1
- package/dist/commands/organizations/create.js +79 -0
- package/dist/commands/organizations/create.js.map +1 -0
- package/dist/commands/organizations/delete.js +100 -0
- package/dist/commands/organizations/delete.js.map +1 -0
- package/dist/commands/organizations/get.js +48 -0
- package/dist/commands/organizations/get.js.map +1 -0
- package/dist/commands/organizations/list.js +60 -0
- package/dist/commands/organizations/list.js.map +1 -0
- package/dist/commands/organizations/update.js +115 -0
- package/dist/commands/organizations/update.js.map +1 -0
- package/dist/commands/projects/create.js +2 -1
- package/dist/commands/projects/create.js.map +1 -1
- package/dist/commands/undeploy.js +13 -1
- package/dist/commands/undeploy.js.map +1 -1
- package/dist/prompts/promptForOrganizationName.js +2 -1
- package/dist/prompts/promptForOrganizationName.js.map +1 -1
- package/dist/server/devServer.js.map +1 -1
- package/dist/services/organizations.js +45 -2
- package/dist/services/organizations.js.map +1 -1
- package/dist/services/userApplications.js.map +1 -1
- package/dist/topicAliases.js +7 -0
- package/dist/topicAliases.js.map +1 -1
- package/dist/util/organizationAliases.js +12 -0
- package/dist/util/organizationAliases.js.map +1 -0
- package/oclif.config.js +1 -0
- package/oclif.manifest.json +1342 -1094
- package/package.json +27 -27
|
@@ -34,6 +34,9 @@ export async function getOrganization({ isUnattended, output, requestedId, user
|
|
|
34
34
|
// If the user has no organizations, prompt them to create one with the same name as
|
|
35
35
|
// their user, but allow them to customize it if they want
|
|
36
36
|
if (organizations.length === 0) {
|
|
37
|
+
if (isUnattended) {
|
|
38
|
+
throw new Error('No organizations are available. Create an organization at https://www.sanity.io/manage, then pass its ID or slug with --organization.');
|
|
39
|
+
}
|
|
37
40
|
output.log('You need to create an organization to create projects.');
|
|
38
41
|
return promptAndCreateNewOrganization(user);
|
|
39
42
|
}
|
|
@@ -45,10 +48,11 @@ export async function getOrganization({ isUnattended, output, requestedId, user
|
|
|
45
48
|
const withAttach = withGrantInfo.filter(({ hasAttachGrant })=>hasAttachGrant);
|
|
46
49
|
debug('User has attach access to %d organizations.', withAttach.length);
|
|
47
50
|
const organizationChoices = getOrganizationChoices(withGrantInfo);
|
|
48
|
-
// In unattended mode use defaults without prompting
|
|
49
51
|
if (isUnattended) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
+
if (withAttach.length > 1) {
|
|
53
|
+
throw new Error('Multiple organizations are available. Select one with --organization <slug|id>.');
|
|
54
|
+
}
|
|
55
|
+
return withAttach[0]?.organization;
|
|
52
56
|
}
|
|
53
57
|
// If the user only has a single organization (and they have attach access to it),
|
|
54
58
|
// we'll default to that one. Otherwise, we'll default to the organization with the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/organizations/getOrganization.ts"],"sourcesContent":["import {subdebug} from '@sanity/cli-core/debug'\nimport {type Output, type SanityOrgUser} from '@sanity/cli-core/types'\nimport {select, spinner} from '@sanity/cli-core/ux'\n\nimport {promptForOrganizationName} from '../../prompts/promptForOrganizationName.js'\nimport {\n createOrganization,\n listOrganizations,\n type ProjectOrganization,\n} from '../../services/organizations.js'\nimport {findOrganizationByUserName} from './findOrganizationByUserName.js'\nimport {getOrganizationChoices} from './getOrganizationChoices.js'\nimport {getOrganizationsWithAttachGrantInfo} from './getOrganizationsWithAttachGrantInfo.js'\n\nconst debug = subdebug('getOrganizationId')\n\ninterface GetOrganizationOptions {\n isUnattended: boolean\n output: Output\n requestedId: string | undefined\n user: SanityOrgUser\n}\n\nconst promptAndCreateNewOrganization = async (user: SanityOrgUser) => {\n const organizationName = await promptForOrganizationName(user)\n const spin = spinner('Creating organization').start()\n const newOrganization = await createOrganization(organizationName)\n spin.succeed()\n return newOrganization\n}\n\nexport async function getOrganization({\n isUnattended,\n output,\n requestedId,\n user,\n}: GetOrganizationOptions) {\n // Get available organizations\n const spin = spinner('Loading organizations').start()\n let organizations: ProjectOrganization[]\n try {\n organizations = await listOrganizations()\n spin.succeed()\n } catch (error) {\n spin.fail()\n debug('Error retrieving organization list', error)\n throw error\n }\n\n // If organization is specified, validate it\n if (requestedId) {\n const org = organizations.find((o) => o.id === requestedId || o.slug === requestedId)\n if (org) return org\n\n throw new Error(`Organization \"${requestedId}\" not found or you don't have access to it`)\n }\n\n // If the user has no organizations, prompt them to create one with the same name as\n // their user, but allow them to customize it if they want\n if (organizations.length === 0) {\n output.log('You need to create an organization to create projects.')\n return promptAndCreateNewOrganization(user)\n }\n\n // If the user has organizations, let them choose from them, but also allow them to\n // create a new one in case they do not have access to any of them, or they want to\n // create a personal/other organization.\n debug(`User has ${organizations.length} organization(s), checking attach access`)\n const withGrantInfo = await getOrganizationsWithAttachGrantInfo(organizations)\n const withAttach = withGrantInfo.filter(({hasAttachGrant}) => hasAttachGrant)\n\n debug('User has attach access to %d organizations.', withAttach.length)\n const organizationChoices = getOrganizationChoices(withGrantInfo)\n\n
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/organizations/getOrganization.ts"],"sourcesContent":["import {subdebug} from '@sanity/cli-core/debug'\nimport {type Output, type SanityOrgUser} from '@sanity/cli-core/types'\nimport {select, spinner} from '@sanity/cli-core/ux'\n\nimport {promptForOrganizationName} from '../../prompts/promptForOrganizationName.js'\nimport {\n createOrganization,\n listOrganizations,\n type ProjectOrganization,\n} from '../../services/organizations.js'\nimport {findOrganizationByUserName} from './findOrganizationByUserName.js'\nimport {getOrganizationChoices} from './getOrganizationChoices.js'\nimport {getOrganizationsWithAttachGrantInfo} from './getOrganizationsWithAttachGrantInfo.js'\n\nconst debug = subdebug('getOrganizationId')\n\ninterface GetOrganizationOptions {\n isUnattended: boolean\n output: Output\n requestedId: string | undefined\n user: SanityOrgUser\n}\n\nconst promptAndCreateNewOrganization = async (user: SanityOrgUser) => {\n const organizationName = await promptForOrganizationName(user)\n const spin = spinner('Creating organization').start()\n const newOrganization = await createOrganization(organizationName)\n spin.succeed()\n return newOrganization\n}\n\nexport async function getOrganization({\n isUnattended,\n output,\n requestedId,\n user,\n}: GetOrganizationOptions) {\n // Get available organizations\n const spin = spinner('Loading organizations').start()\n let organizations: ProjectOrganization[]\n try {\n organizations = await listOrganizations()\n spin.succeed()\n } catch (error) {\n spin.fail()\n debug('Error retrieving organization list', error)\n throw error\n }\n\n // If organization is specified, validate it\n if (requestedId) {\n const org = organizations.find((o) => o.id === requestedId || o.slug === requestedId)\n if (org) return org\n\n throw new Error(`Organization \"${requestedId}\" not found or you don't have access to it`)\n }\n\n // If the user has no organizations, prompt them to create one with the same name as\n // their user, but allow them to customize it if they want\n if (organizations.length === 0) {\n if (isUnattended) {\n throw new Error(\n 'No organizations are available. Create an organization at https://www.sanity.io/manage, then pass its ID or slug with --organization.',\n )\n }\n\n output.log('You need to create an organization to create projects.')\n return promptAndCreateNewOrganization(user)\n }\n\n // If the user has organizations, let them choose from them, but also allow them to\n // create a new one in case they do not have access to any of them, or they want to\n // create a personal/other organization.\n debug(`User has ${organizations.length} organization(s), checking attach access`)\n const withGrantInfo = await getOrganizationsWithAttachGrantInfo(organizations)\n const withAttach = withGrantInfo.filter(({hasAttachGrant}) => hasAttachGrant)\n\n debug('User has attach access to %d organizations.', withAttach.length)\n const organizationChoices = getOrganizationChoices(withGrantInfo)\n\n if (isUnattended) {\n if (withAttach.length > 1) {\n throw new Error(\n 'Multiple organizations are available. Select one with --organization <slug|id>.',\n )\n }\n\n return withAttach[0]?.organization\n }\n\n // If the user only has a single organization (and they have attach access to it),\n // we'll default to that one. Otherwise, we'll default to the organization with the\n // same name as the user if it exists.\n const defaultOrganizationId =\n withAttach.length === 1\n ? withAttach[0].organization.id\n : findOrganizationByUserName(organizations, user)\n\n const chosenOrg = await select({\n choices: organizationChoices,\n default: defaultOrganizationId || undefined,\n message: 'Select organization:',\n })\n\n if (chosenOrg === '-new-') {\n return promptAndCreateNewOrganization(user)\n }\n\n return organizations.find((org) => org.id === chosenOrg)\n}\n"],"names":["subdebug","select","spinner","promptForOrganizationName","createOrganization","listOrganizations","findOrganizationByUserName","getOrganizationChoices","getOrganizationsWithAttachGrantInfo","debug","promptAndCreateNewOrganization","user","organizationName","spin","start","newOrganization","succeed","getOrganization","isUnattended","output","requestedId","organizations","error","fail","org","find","o","id","slug","Error","length","log","withGrantInfo","withAttach","filter","hasAttachGrant","organizationChoices","organization","defaultOrganizationId","chosenOrg","choices","default","undefined","message"],"mappings":"AAAA,SAAQA,QAAQ,QAAO,yBAAwB;AAE/C,SAAQC,MAAM,EAAEC,OAAO,QAAO,sBAAqB;AAEnD,SAAQC,yBAAyB,QAAO,6CAA4C;AACpF,SACEC,kBAAkB,EAClBC,iBAAiB,QAEZ,kCAAiC;AACxC,SAAQC,0BAA0B,QAAO,kCAAiC;AAC1E,SAAQC,sBAAsB,QAAO,8BAA6B;AAClE,SAAQC,mCAAmC,QAAO,2CAA0C;AAE5F,MAAMC,QAAQT,SAAS;AASvB,MAAMU,iCAAiC,OAAOC;IAC5C,MAAMC,mBAAmB,MAAMT,0BAA0BQ;IACzD,MAAME,OAAOX,QAAQ,yBAAyBY,KAAK;IACnD,MAAMC,kBAAkB,MAAMX,mBAAmBQ;IACjDC,KAAKG,OAAO;IACZ,OAAOD;AACT;AAEA,OAAO,eAAeE,gBAAgB,EACpCC,YAAY,EACZC,MAAM,EACNC,WAAW,EACXT,IAAI,EACmB;IACvB,8BAA8B;IAC9B,MAAME,OAAOX,QAAQ,yBAAyBY,KAAK;IACnD,IAAIO;IACJ,IAAI;QACFA,gBAAgB,MAAMhB;QACtBQ,KAAKG,OAAO;IACd,EAAE,OAAOM,OAAO;QACdT,KAAKU,IAAI;QACTd,MAAM,sCAAsCa;QAC5C,MAAMA;IACR;IAEA,4CAA4C;IAC5C,IAAIF,aAAa;QACf,MAAMI,MAAMH,cAAcI,IAAI,CAAC,CAACC,IAAMA,EAAEC,EAAE,KAAKP,eAAeM,EAAEE,IAAI,KAAKR;QACzE,IAAII,KAAK,OAAOA;QAEhB,MAAM,IAAIK,MAAM,CAAC,cAAc,EAAET,YAAY,0CAA0C,CAAC;IAC1F;IAEA,oFAAoF;IACpF,0DAA0D;IAC1D,IAAIC,cAAcS,MAAM,KAAK,GAAG;QAC9B,IAAIZ,cAAc;YAChB,MAAM,IAAIW,MACR;QAEJ;QAEAV,OAAOY,GAAG,CAAC;QACX,OAAOrB,+BAA+BC;IACxC;IAEA,mFAAmF;IACnF,mFAAmF;IACnF,wCAAwC;IACxCF,MAAM,CAAC,SAAS,EAAEY,cAAcS,MAAM,CAAC,wCAAwC,CAAC;IAChF,MAAME,gBAAgB,MAAMxB,oCAAoCa;IAChE,MAAMY,aAAaD,cAAcE,MAAM,CAAC,CAAC,EAACC,cAAc,EAAC,GAAKA;IAE9D1B,MAAM,+CAA+CwB,WAAWH,MAAM;IACtE,MAAMM,sBAAsB7B,uBAAuByB;IAEnD,IAAId,cAAc;QAChB,IAAIe,WAAWH,MAAM,GAAG,GAAG;YACzB,MAAM,IAAID,MACR;QAEJ;QAEA,OAAOI,UAAU,CAAC,EAAE,EAAEI;IACxB;IAEA,kFAAkF;IAClF,mFAAmF;IACnF,sCAAsC;IACtC,MAAMC,wBACJL,WAAWH,MAAM,KAAK,IAClBG,UAAU,CAAC,EAAE,CAACI,YAAY,CAACV,EAAE,GAC7BrB,2BAA2Be,eAAeV;IAEhD,MAAM4B,YAAY,MAAMtC,OAAO;QAC7BuC,SAASJ;QACTK,SAASH,yBAAyBI;QAClCC,SAAS;IACX;IAEA,IAAIJ,cAAc,SAAS;QACzB,OAAO7B,+BAA+BC;IACxC;IAEA,OAAOU,cAAcI,IAAI,CAAC,CAACD,MAAQA,IAAIG,EAAE,KAAKY;AAChD"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export function validateOrganizationSlug(input) {
|
|
2
|
+
if (!input || input.trim() === '') {
|
|
3
|
+
return 'Organization slug cannot be empty';
|
|
4
|
+
}
|
|
5
|
+
if (input !== input.toLowerCase()) {
|
|
6
|
+
return 'Organization slug must be lowercase';
|
|
7
|
+
}
|
|
8
|
+
if (/\s/.test(input)) {
|
|
9
|
+
return 'Organization slug cannot contain spaces';
|
|
10
|
+
}
|
|
11
|
+
if (!/^[a-z0-9-]+$/.test(input)) {
|
|
12
|
+
return 'Organization slug may only contain lowercase letters, numbers, and dashes';
|
|
13
|
+
}
|
|
14
|
+
if (input.startsWith('-') || input.endsWith('-')) {
|
|
15
|
+
return 'Organization slug cannot start or end with a dash';
|
|
16
|
+
}
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
//# sourceMappingURL=validateOrganizationSlug.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/organizations/validateOrganizationSlug.ts"],"sourcesContent":["export function validateOrganizationSlug(input: string): string | true {\n if (!input || input.trim() === '') {\n return 'Organization slug cannot be empty'\n }\n if (input !== input.toLowerCase()) {\n return 'Organization slug must be lowercase'\n }\n if (/\\s/.test(input)) {\n return 'Organization slug cannot contain spaces'\n }\n if (!/^[a-z0-9-]+$/.test(input)) {\n return 'Organization slug may only contain lowercase letters, numbers, and dashes'\n }\n if (input.startsWith('-') || input.endsWith('-')) {\n return 'Organization slug cannot start or end with a dash'\n }\n return true\n}\n"],"names":["validateOrganizationSlug","input","trim","toLowerCase","test","startsWith","endsWith"],"mappings":"AAAA,OAAO,SAASA,yBAAyBC,KAAa;IACpD,IAAI,CAACA,SAASA,MAAMC,IAAI,OAAO,IAAI;QACjC,OAAO;IACT;IACA,IAAID,UAAUA,MAAME,WAAW,IAAI;QACjC,OAAO;IACT;IACA,IAAI,KAAKC,IAAI,CAACH,QAAQ;QACpB,OAAO;IACT;IACA,IAAI,CAAC,eAAeG,IAAI,CAACH,QAAQ;QAC/B,OAAO;IACT;IACA,IAAIA,MAAMI,UAAU,CAAC,QAAQJ,MAAMK,QAAQ,CAAC,MAAM;QAChD,OAAO;IACT;IACA,OAAO;AACT"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { getCoreAppUrl } from '@sanity/cli-core/util';
|
|
1
2
|
import { deleteUserApplication, getUserApplication } from '../../services/userApplications.js';
|
|
2
3
|
import { getAppId } from '../../util/appId.js';
|
|
3
4
|
import { NO_PROJECT_ID } from '../../util/errorMessages.js';
|
|
4
|
-
import { getCoreAppUrl } from '../deploy/urlUtils.js';
|
|
5
5
|
export function createAppUndeployAdapter(cliConfig) {
|
|
6
6
|
return {
|
|
7
7
|
async resolveTarget () {
|
|
@@ -77,11 +77,11 @@ function toUndeployTarget(application, type) {
|
|
|
77
77
|
return {
|
|
78
78
|
activeDeployment: application.activeDeployment ? {
|
|
79
79
|
deployedAt: application.activeDeployment.deployedAt,
|
|
80
|
-
deployedBy: application.activeDeployment.deployedBy
|
|
81
|
-
version: application.activeDeployment.version
|
|
80
|
+
deployedBy: application.activeDeployment.deployedBy
|
|
82
81
|
} : null,
|
|
83
82
|
appHost: application.appHost ?? null,
|
|
84
83
|
createdAt: application.createdAt ?? null,
|
|
84
|
+
deletes: 'application',
|
|
85
85
|
id: application.id,
|
|
86
86
|
organizationId: application.organizationId ?? null,
|
|
87
87
|
projectId: application.projectId ?? null,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/undeploy/adapters.ts"],"sourcesContent":["import {type CliConfig} from '@sanity/cli-core'\
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/undeploy/adapters.ts"],"sourcesContent":["import {type CliConfig} from '@sanity/cli-core'\nimport {\n type UndeployAdapter,\n type UndeployApplicationTarget,\n type UndeployTarget,\n} from '@sanity/cli-core/undeploy'\nimport {getCoreAppUrl} from '@sanity/cli-core/util'\n\nimport {\n deleteUserApplication,\n getUserApplication,\n type UserApplication,\n} from '../../services/userApplications.js'\nimport {getAppId} from '../../util/appId.js'\nimport {NO_PROJECT_ID} from '../../util/errorMessages.js'\n\nexport function createAppUndeployAdapter(\n cliConfig: CliConfig,\n): UndeployAdapter<UndeployApplicationTarget> {\n return {\n async resolveTarget() {\n const appId = getAppId(cliConfig)\n if (!appId) {\n return {\n message: 'No `deployment.appId` configured',\n solution: 'Add `deployment.appId` to sanity.cli.ts',\n type: 'none',\n }\n }\n\n const application = await getUserApplication({appId, isSdkApp: true})\n if (!application) {\n return {message: 'Application with the given ID does not exist', type: 'none'}\n }\n\n return {target: toUndeployTarget(application, 'coreApp'), type: 'found'}\n },\n type: 'coreApp',\n undeploy: ({id}) => deleteUserApplication({applicationId: id, appType: 'coreApp'}),\n }\n}\n\nexport function createStudioUndeployAdapter(\n cliConfig: CliConfig,\n): UndeployAdapter<UndeployApplicationTarget> {\n return {\n async resolveTarget() {\n const appId = cliConfig.deployment?.appId\n const studioHost = cliConfig.studioHost\n if (!appId && !studioHost) {\n return {\n message: 'No studio hostname configured',\n solution: 'Set `studioHost` in sanity.cli.ts',\n type: 'none',\n }\n }\n\n const projectId = cliConfig.api?.projectId\n if (!projectId) throw new Error(NO_PROJECT_ID)\n\n const application = await getUserApplication({\n appHost: studioHost,\n appId,\n isSdkApp: false,\n projectId,\n })\n if (!application) {\n return {\n message:\n 'Your project has not been assigned an app ID or a studio hostname, or the `appId` or `studioHost` provided does not exist',\n type: 'none',\n }\n }\n\n return {target: toUndeployTarget(application, 'studio'), type: 'found'}\n },\n type: 'studio',\n undeploy: ({id}) => deleteUserApplication({applicationId: id, appType: 'studio'}),\n }\n}\n\nfunction toUndeployTarget(\n application: UserApplication,\n type: UndeployTarget['type'],\n): UndeployApplicationTarget {\n return {\n activeDeployment: application.activeDeployment\n ? {\n deployedAt: application.activeDeployment.deployedAt,\n deployedBy: application.activeDeployment.deployedBy,\n }\n : null,\n appHost: application.appHost ?? null,\n createdAt: application.createdAt ?? null,\n deletes: 'application',\n id: application.id,\n organizationId: application.organizationId ?? null,\n projectId: application.projectId ?? null,\n title: application.title ?? null,\n type,\n url: resolveTargetUrl(application, type),\n }\n}\n\nfunction resolveTargetUrl(\n application: UserApplication,\n type: UndeployTarget['type'],\n): string | null {\n if (type === 'coreApp') {\n return application.organizationId\n ? getCoreAppUrl(application.organizationId, application.id)\n : null\n }\n if (!application.appHost) return null\n return application.urlType === 'external'\n ? application.appHost\n : `https://${application.appHost}.sanity.studio`\n}\n"],"names":["getCoreAppUrl","deleteUserApplication","getUserApplication","getAppId","NO_PROJECT_ID","createAppUndeployAdapter","cliConfig","resolveTarget","appId","message","solution","type","application","isSdkApp","target","toUndeployTarget","undeploy","id","applicationId","appType","createStudioUndeployAdapter","deployment","studioHost","projectId","api","Error","appHost","activeDeployment","deployedAt","deployedBy","createdAt","deletes","organizationId","title","url","resolveTargetUrl","urlType"],"mappings":"AAMA,SAAQA,aAAa,QAAO,wBAAuB;AAEnD,SACEC,qBAAqB,EACrBC,kBAAkB,QAEb,qCAAoC;AAC3C,SAAQC,QAAQ,QAAO,sBAAqB;AAC5C,SAAQC,aAAa,QAAO,8BAA6B;AAEzD,OAAO,SAASC,yBACdC,SAAoB;IAEpB,OAAO;QACL,MAAMC;YACJ,MAAMC,QAAQL,SAASG;YACvB,IAAI,CAACE,OAAO;gBACV,OAAO;oBACLC,SAAS;oBACTC,UAAU;oBACVC,MAAM;gBACR;YACF;YAEA,MAAMC,cAAc,MAAMV,mBAAmB;gBAACM;gBAAOK,UAAU;YAAI;YACnE,IAAI,CAACD,aAAa;gBAChB,OAAO;oBAACH,SAAS;oBAAgDE,MAAM;gBAAM;YAC/E;YAEA,OAAO;gBAACG,QAAQC,iBAAiBH,aAAa;gBAAYD,MAAM;YAAO;QACzE;QACAA,MAAM;QACNK,UAAU,CAAC,EAACC,EAAE,EAAC,GAAKhB,sBAAsB;gBAACiB,eAAeD;gBAAIE,SAAS;YAAS;IAClF;AACF;AAEA,OAAO,SAASC,4BACdd,SAAoB;IAEpB,OAAO;QACL,MAAMC;YACJ,MAAMC,QAAQF,UAAUe,UAAU,EAAEb;YACpC,MAAMc,aAAahB,UAAUgB,UAAU;YACvC,IAAI,CAACd,SAAS,CAACc,YAAY;gBACzB,OAAO;oBACLb,SAAS;oBACTC,UAAU;oBACVC,MAAM;gBACR;YACF;YAEA,MAAMY,YAAYjB,UAAUkB,GAAG,EAAED;YACjC,IAAI,CAACA,WAAW,MAAM,IAAIE,MAAMrB;YAEhC,MAAMQ,cAAc,MAAMV,mBAAmB;gBAC3CwB,SAASJ;gBACTd;gBACAK,UAAU;gBACVU;YACF;YACA,IAAI,CAACX,aAAa;gBAChB,OAAO;oBACLH,SACE;oBACFE,MAAM;gBACR;YACF;YAEA,OAAO;gBAACG,QAAQC,iBAAiBH,aAAa;gBAAWD,MAAM;YAAO;QACxE;QACAA,MAAM;QACNK,UAAU,CAAC,EAACC,EAAE,EAAC,GAAKhB,sBAAsB;gBAACiB,eAAeD;gBAAIE,SAAS;YAAQ;IACjF;AACF;AAEA,SAASJ,iBACPH,WAA4B,EAC5BD,IAA4B;IAE5B,OAAO;QACLgB,kBAAkBf,YAAYe,gBAAgB,GAC1C;YACEC,YAAYhB,YAAYe,gBAAgB,CAACC,UAAU;YACnDC,YAAYjB,YAAYe,gBAAgB,CAACE,UAAU;QACrD,IACA;QACJH,SAASd,YAAYc,OAAO,IAAI;QAChCI,WAAWlB,YAAYkB,SAAS,IAAI;QACpCC,SAAS;QACTd,IAAIL,YAAYK,EAAE;QAClBe,gBAAgBpB,YAAYoB,cAAc,IAAI;QAC9CT,WAAWX,YAAYW,SAAS,IAAI;QACpCU,OAAOrB,YAAYqB,KAAK,IAAI;QAC5BtB;QACAuB,KAAKC,iBAAiBvB,aAAaD;IACrC;AACF;AAEA,SAASwB,iBACPvB,WAA4B,EAC5BD,IAA4B;IAE5B,IAAIA,SAAS,WAAW;QACtB,OAAOC,YAAYoB,cAAc,GAC7BhC,cAAcY,YAAYoB,cAAc,EAAEpB,YAAYK,EAAE,IACxD;IACN;IACA,IAAI,CAACL,YAAYc,OAAO,EAAE,OAAO;IACjC,OAAOd,YAAYwB,OAAO,KAAK,aAC3BxB,YAAYc,OAAO,GACnB,CAAC,QAAQ,EAAEd,YAAYc,OAAO,CAAC,cAAc,CAAC;AACpD"}
|
|
@@ -5,7 +5,7 @@ import { getErrorMessage } from '@sanity/cli-core/errors';
|
|
|
5
5
|
import { confirm, spinner } from '@sanity/cli-core/ux';
|
|
6
6
|
import { createCollectingReporter, createFailFastReporter } from '../../util/checks.js';
|
|
7
7
|
import { toStderrOutput } from '../../util/toStderrOutput.js';
|
|
8
|
-
import { describeUndeployTarget, renderUndeployPlan, undeployPlanToJson } from './undeployPlan.js';
|
|
8
|
+
import { describeUndeployTarget, renderUndeployPlan, undeployLabel, undeployPlanToJson } from './undeployPlan.js';
|
|
9
9
|
const undeployDebug = subdebug('undeploy');
|
|
10
10
|
/**
|
|
11
11
|
* Runs an undeploy in the mode the flags select: a real undeploy fails fast,
|
|
@@ -15,7 +15,8 @@ const undeployDebug = subdebug('undeploy');
|
|
|
15
15
|
*/ export async function runUndeploy(options, adapter) {
|
|
16
16
|
const { flags, output } = options;
|
|
17
17
|
const json = !!flags.json;
|
|
18
|
-
|
|
18
|
+
// The report-only `summary` lines never enter the payload
|
|
19
|
+
const emitJson = (payload)=>output.log(JSON.stringify(payload, (key, value)=>key === 'summary' ? undefined : value, 2));
|
|
19
20
|
// The JSON payload owns stdout, so the run's progress logs go to stderr; only
|
|
20
21
|
// the final JSON.stringify writes to stdout.
|
|
21
22
|
const runOptions = json ? {
|
|
@@ -82,15 +83,20 @@ const undeployDebug = subdebug('undeploy');
|
|
|
82
83
|
});
|
|
83
84
|
if (!shouldUndeploy) return undefined;
|
|
84
85
|
}
|
|
85
|
-
const
|
|
86
|
+
const label = undeployLabel(target, adapter.type);
|
|
87
|
+
const spin = spinner(`Undeploying ${label}`).start();
|
|
86
88
|
try {
|
|
87
89
|
await adapter.undeploy(target);
|
|
88
90
|
} catch (error) {
|
|
89
91
|
spin.fail();
|
|
90
|
-
|
|
92
|
+
undeployDebug(`Error undeploying ${label}`, error);
|
|
93
|
+
// Labeled here, where the target is known — `normalizeFailure` only sees the adapter type.
|
|
94
|
+
throw new CLIError(`Error undeploying ${label}: ${getErrorMessage(error)}`, {
|
|
95
|
+
exit: 1
|
|
96
|
+
});
|
|
91
97
|
}
|
|
92
98
|
spin.succeed();
|
|
93
|
-
|
|
99
|
+
printUndeployed(target, output);
|
|
94
100
|
return {
|
|
95
101
|
application: target,
|
|
96
102
|
undeployed: true
|
|
@@ -118,6 +124,10 @@ const undeployDebug = subdebug('undeploy');
|
|
|
118
124
|
return resolution;
|
|
119
125
|
}
|
|
120
126
|
function confirmUndeployMessage(target) {
|
|
127
|
+
if (target.deletes === 'config') {
|
|
128
|
+
return `This will delete the deployed config for ${styleText('yellow', target.title ?? 'this app')}.
|
|
129
|
+
Are you ${styleText('red', 'sure')} you want to undeploy?`;
|
|
130
|
+
}
|
|
121
131
|
if (target.type === 'coreApp') {
|
|
122
132
|
return `This will undeploy the following application:
|
|
123
133
|
|
|
@@ -131,7 +141,11 @@ Are you ${styleText('red', 'sure')} you want to undeploy?`;
|
|
|
131
141
|
return `This will undeploy ${styleText('yellow', target.url ?? target.id)} and make it unavailable for your users.
|
|
132
142
|
Are you ${styleText('red', 'sure')} you want to undeploy?`;
|
|
133
143
|
}
|
|
134
|
-
function
|
|
144
|
+
function printUndeployed(target, output) {
|
|
145
|
+
if (target.deletes === 'config') {
|
|
146
|
+
output.log(`\n${styleText('bold', 'Config deleted.')}`);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
135
149
|
if (target.type === 'coreApp') {
|
|
136
150
|
output.log(`\n${styleText('bold', '⏱️ Application undeploy scheduled.')} It might be a few minutes until ${target.title ? styleText('italic', `'${target.title}'`) : 'your application'} is unavailable.`);
|
|
137
151
|
output.log(`\n${styleText('bold', 'Remember to remove `deployment.appId` from your application configuration')} to avoid errors when redeploying.`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/undeploy/runUndeploy.ts"],"sourcesContent":["import {styleText} from 'node:util'\n\nimport {CLIError} from '@oclif/core/errors'\nimport {type Output, subdebug} from '@sanity/cli-core'\nimport {getErrorMessage} from '@sanity/cli-core/errors'\nimport {confirm, spinner} from '@sanity/cli-core/ux'\n\nimport {type UndeployCommand} from '../../commands/undeploy.js'\nimport {\n type CheckReporter,\n createCollectingReporter,\n createFailFastReporter,\n} from '../../util/checks.js'\nimport {toStderrOutput} from '../../util/toStderrOutput.js'\nimport {\n describeUndeployTarget,\n renderUndeployPlan,\n type UndeployPlan,\n undeployPlanToJson,\n type UndeployTarget,\n type UndeployTargetResolution,\n} from './undeployPlan.js'\n\nconst undeployDebug = subdebug('undeploy')\n\ntype UndeployFlags = UndeployCommand['flags']\n\nexport interface UndeployOptions {\n flags: UndeployFlags\n output: Output\n}\n\n/**\n * The parts of an undeploy that differ per application backend. The shared\n * sequence — mode selection, target reporting, confirmation, error handling —\n * lives in `runUndeploy`; adapters only resolve and delete.\n */\nexport interface UndeployAdapter {\n /** Resolves what an undeploy would delete; read-only. */\n resolveTarget(): Promise<UndeployTargetResolution>\n type: 'coreApp' | 'studio'\n /** Deletes the target — the only mutating step, never run on a dry run. */\n undeploy(target: UndeployTarget): Promise<void>\n}\n\n/** What a real undeploy produced — the payload `--json` reports. */\ntype UndeployResult =\n | {application: UndeployTarget; undeployed: true}\n | {reason: string; undeployed: false}\n\n/**\n * Runs an undeploy in the mode the flags select: a real undeploy fails fast,\n * confirms, and deletes; `--dry-run` drives the same target resolution\n * read-only and renders a plan instead. `--json` emits the same information as\n * machine-readable JSON.\n */\nexport async function runUndeploy(\n options: UndeployOptions,\n adapter: UndeployAdapter,\n): Promise<void> {\n const {flags, output} = options\n const json = !!flags.json\n const emitJson = (payload: unknown) => output.log(JSON.stringify(payload, null, 2))\n\n // The JSON payload owns stdout, so the run's progress logs go to stderr; only\n // the final JSON.stringify writes to stdout.\n const runOptions = json ? {...options, output: toStderrOutput(output)} : options\n\n try {\n if (flags['dry-run']) {\n const reporter = createCollectingReporter()\n const resolution = await resolveTarget(adapter, reporter)\n const plan: UndeployPlan = {\n checks: reporter.results,\n reason: resolution?.type === 'none' ? resolution.message : null,\n target: resolution?.type === 'found' ? resolution.target : null,\n type: adapter.type,\n }\n if (json) emitJson(undeployPlanToJson(plan))\n else renderUndeployPlan(plan, output)\n // A blocked plan exits like a real (fail-fast) undeploy would.\n const failed = plan.checks.find((check) => check.status === 'fail')\n if (failed) output.error('Undeploy blocked by failing checks.', {exit: failed.exitCode ?? 1})\n return\n }\n\n const result = await undeployApp(runOptions, adapter)\n if (json && result) emitJson(result)\n } catch (error) {\n const failure = normalizeFailure(error, adapter.type)\n // A blocked dry run reaches this catch too (its exit throws) and already\n // printed its plan, so only a real undeploy adds the {undeployed: false} envelope.\n if (json && !flags['dry-run']) {\n emitJson({error: {message: failure.message}, undeployed: false})\n }\n output.error(failure.message, {exit: failure.exit})\n }\n}\n\n/** The real run: resolve the target, confirm, delete, report. */\nasync function undeployApp(\n options: UndeployOptions,\n adapter: UndeployAdapter,\n): Promise<UndeployResult | undefined> {\n const {flags, output} = options\n\n const resolution = await resolveTarget(adapter, createFailFastReporter(output))\n // A resolve failure never lands here: the fail-fast reporter already exited.\n if (!resolution) return undefined\n if (resolution.type === 'none') {\n output.log(`${resolution.message}.`)\n if (resolution.solution) output.log(`${resolution.solution}.`)\n output.log('Nothing to undeploy.')\n return {reason: resolution.message, undeployed: false}\n }\n\n const {target} = resolution\n if (!flags.yes) {\n const shouldUndeploy = await confirm({\n default: false,\n message: confirmUndeployMessage(target),\n })\n if (!shouldUndeploy) return undefined\n }\n\n const spin = spinner(\n `Undeploying ${adapter.type === 'coreApp' ? 'application' : 'studio'}`,\n ).start()\n try {\n await adapter.undeploy(target)\n } catch (error) {\n spin.fail()\n throw error\n }\n spin.succeed()\n\n printUndeployScheduled(target, output)\n return {application: target, undeployed: true}\n}\n\n/**\n * Resolves the undeploy target and reports the verdict as a check, shared by\n * both modes so a real run and a dry run can't diverge.\n */\nasync function resolveTarget(\n adapter: UndeployAdapter,\n reporter: CheckReporter,\n): Promise<UndeployTargetResolution | null> {\n const spin = spinner('Checking application info').start()\n\n let resolution: UndeployTargetResolution\n try {\n resolution = await adapter.resolveTarget()\n } catch (error) {\n spin.fail()\n undeployDebug('Failed to resolve undeploy target', error)\n reporter.report({\n message: `Failed to resolve undeploy target: ${getErrorMessage(error)}`,\n status: 'fail',\n })\n return null\n }\n\n spin[resolution.type === 'found' ? 'succeed' : 'fail']()\n reporter.report(describeUndeployTarget(resolution))\n return resolution\n}\n\nfunction confirmUndeployMessage(target: UndeployTarget): string {\n if (target.type === 'coreApp') {\n return `This will undeploy the following application:\n\n Title: ${styleText('yellow', target.title || '(untitled application)')}\n ID: ${styleText('yellow', target.id)}\n\nThe application will no longer be available for any of your users if you proceed.\n\nAre you ${styleText('red', 'sure')} you want to undeploy?`\n }\n\n return `This will undeploy ${styleText('yellow', target.url ?? target.id)} and make it unavailable for your users.\nAre you ${styleText('red', 'sure')} you want to undeploy?`\n}\n\nfunction printUndeployScheduled(target: UndeployTarget, output: Output): void {\n if (target.type === 'coreApp') {\n output.log(\n `\\n${styleText('bold', '⏱️ Application undeploy scheduled.')} It might be a few minutes until ${\n target.title ? styleText('italic', `'${target.title}'`) : 'your application'\n } is unavailable.`,\n )\n output.log(\n `\\n${styleText('bold', 'Remember to remove `deployment.appId` from your application configuration')} to avoid errors when redeploying.`,\n )\n return\n }\n\n output.log(\n `\\nStudio undeploy scheduled. It might be a few minutes until ${target.url ?? 'your studio'} is unavailable.`,\n )\n}\n\n/** The one failure diagnosis both the stderr message and the `--json` envelope read. */\nfunction normalizeFailure(\n error: unknown,\n type: UndeployAdapter['type'],\n): {exit: number; message: string} {\n // Ctrl+C on the confirmation prompt isn't a real failure\n if (error instanceof Error && error.name === 'ExitPromptError') {\n return {exit: 1, message: 'Undeploy cancelled by user'}\n }\n // A failed check already carries its own message and exit code\n if (error instanceof CLIError) {\n return {exit: error.oclif?.exit ?? 1, message: error.message}\n }\n const label = type === 'coreApp' ? 'application' : 'studio'\n undeployDebug(`Error undeploying ${label}`, error)\n return {exit: 1, message: `Error undeploying ${label}: ${getErrorMessage(error)}`}\n}\n"],"names":["styleText","CLIError","subdebug","getErrorMessage","confirm","spinner","createCollectingReporter","createFailFastReporter","toStderrOutput","describeUndeployTarget","renderUndeployPlan","undeployPlanToJson","undeployDebug","runUndeploy","options","adapter","flags","output","json","emitJson","payload","log","JSON","stringify","runOptions","reporter","resolution","resolveTarget","plan","checks","results","reason","type","message","target","failed","find","check","status","error","exit","exitCode","result","undeployApp","failure","normalizeFailure","undeployed","undefined","solution","yes","shouldUndeploy","default","confirmUndeployMessage","spin","start","undeploy","fail","succeed","printUndeployScheduled","application","report","title","id","url","Error","name","oclif","label"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/undeploy/runUndeploy.ts"],"sourcesContent":["import {styleText} from 'node:util'\n\nimport {CLIError} from '@oclif/core/errors'\nimport {type Output, subdebug} from '@sanity/cli-core'\nimport {getErrorMessage} from '@sanity/cli-core/errors'\nimport {\n type UndeployAdapter,\n type UndeployTarget,\n type UndeployTargetResolution,\n} from '@sanity/cli-core/undeploy'\nimport {confirm, spinner} from '@sanity/cli-core/ux'\n\nimport {type UndeployCommand} from '../../commands/undeploy.js'\nimport {\n type CheckReporter,\n createCollectingReporter,\n createFailFastReporter,\n} from '../../util/checks.js'\nimport {toStderrOutput} from '../../util/toStderrOutput.js'\nimport {\n describeUndeployTarget,\n renderUndeployPlan,\n undeployLabel,\n type UndeployPlan,\n undeployPlanToJson,\n} from './undeployPlan.js'\n\nconst undeployDebug = subdebug('undeploy')\n\ntype UndeployFlags = UndeployCommand['flags']\n\nexport interface UndeployOptions {\n flags: UndeployFlags\n output: Output\n}\n\n/** What a real undeploy produced — the payload `--json` reports. */\ntype UndeployResult =\n | {application: UndeployTarget; undeployed: true}\n | {reason: string; undeployed: false}\n\n/**\n * Runs an undeploy in the mode the flags select: a real undeploy fails fast,\n * confirms, and deletes; `--dry-run` drives the same target resolution\n * read-only and renders a plan instead. `--json` emits the same information as\n * machine-readable JSON.\n */\nexport async function runUndeploy(\n options: UndeployOptions,\n adapter: UndeployAdapter,\n): Promise<void> {\n const {flags, output} = options\n const json = !!flags.json\n // The report-only `summary` lines never enter the payload\n const emitJson = (payload: unknown) =>\n output.log(JSON.stringify(payload, (key, value) => (key === 'summary' ? undefined : value), 2))\n\n // The JSON payload owns stdout, so the run's progress logs go to stderr; only\n // the final JSON.stringify writes to stdout.\n const runOptions = json ? {...options, output: toStderrOutput(output)} : options\n\n try {\n if (flags['dry-run']) {\n const reporter = createCollectingReporter()\n const resolution = await resolveTarget(adapter, reporter)\n const plan: UndeployPlan = {\n checks: reporter.results,\n reason: resolution?.type === 'none' ? resolution.message : null,\n target: resolution?.type === 'found' ? resolution.target : null,\n type: adapter.type,\n }\n if (json) emitJson(undeployPlanToJson(plan))\n else renderUndeployPlan(plan, output)\n // A blocked plan exits like a real (fail-fast) undeploy would.\n const failed = plan.checks.find((check) => check.status === 'fail')\n if (failed) output.error('Undeploy blocked by failing checks.', {exit: failed.exitCode ?? 1})\n return\n }\n\n const result = await undeployApp(runOptions, adapter)\n if (json && result) emitJson(result)\n } catch (error) {\n const failure = normalizeFailure(error, adapter.type)\n // A blocked dry run reaches this catch too (its exit throws) and already\n // printed its plan, so only a real undeploy adds the {undeployed: false} envelope.\n if (json && !flags['dry-run']) {\n emitJson({error: {message: failure.message}, undeployed: false})\n }\n output.error(failure.message, {exit: failure.exit})\n }\n}\n\n/** The real run: resolve the target, confirm, delete, report. */\nasync function undeployApp(\n options: UndeployOptions,\n adapter: UndeployAdapter,\n): Promise<UndeployResult | undefined> {\n const {flags, output} = options\n\n const resolution = await resolveTarget(adapter, createFailFastReporter(output))\n // A resolve failure never lands here: the fail-fast reporter already exited.\n if (!resolution) return undefined\n if (resolution.type === 'none') {\n output.log(`${resolution.message}.`)\n if (resolution.solution) output.log(`${resolution.solution}.`)\n output.log('Nothing to undeploy.')\n return {reason: resolution.message, undeployed: false}\n }\n\n const {target} = resolution\n if (!flags.yes) {\n const shouldUndeploy = await confirm({\n default: false,\n message: confirmUndeployMessage(target),\n })\n if (!shouldUndeploy) return undefined\n }\n\n const label = undeployLabel(target, adapter.type)\n const spin = spinner(`Undeploying ${label}`).start()\n try {\n await adapter.undeploy(target)\n } catch (error) {\n spin.fail()\n undeployDebug(`Error undeploying ${label}`, error)\n // Labeled here, where the target is known — `normalizeFailure` only sees the adapter type.\n throw new CLIError(`Error undeploying ${label}: ${getErrorMessage(error)}`, {exit: 1})\n }\n spin.succeed()\n\n printUndeployed(target, output)\n return {application: target, undeployed: true}\n}\n\n/**\n * Resolves the undeploy target and reports the verdict as a check, shared by\n * both modes so a real run and a dry run can't diverge.\n */\nasync function resolveTarget(\n adapter: UndeployAdapter,\n reporter: CheckReporter,\n): Promise<UndeployTargetResolution | null> {\n const spin = spinner('Checking application info').start()\n\n let resolution: UndeployTargetResolution\n try {\n resolution = await adapter.resolveTarget()\n } catch (error) {\n spin.fail()\n undeployDebug('Failed to resolve undeploy target', error)\n reporter.report({\n message: `Failed to resolve undeploy target: ${getErrorMessage(error)}`,\n status: 'fail',\n })\n return null\n }\n\n spin[resolution.type === 'found' ? 'succeed' : 'fail']()\n reporter.report(describeUndeployTarget(resolution))\n return resolution\n}\n\nfunction confirmUndeployMessage(target: UndeployTarget): string {\n if (target.deletes === 'config') {\n return `This will delete the deployed config for ${styleText(\n 'yellow',\n target.title ?? 'this app',\n )}.\nAre you ${styleText('red', 'sure')} you want to undeploy?`\n }\n\n if (target.type === 'coreApp') {\n return `This will undeploy the following application:\n\n Title: ${styleText('yellow', target.title || '(untitled application)')}\n ID: ${styleText('yellow', target.id)}\n\nThe application will no longer be available for any of your users if you proceed.\n\nAre you ${styleText('red', 'sure')} you want to undeploy?`\n }\n\n return `This will undeploy ${styleText('yellow', target.url ?? target.id)} and make it unavailable for your users.\nAre you ${styleText('red', 'sure')} you want to undeploy?`\n}\n\nfunction printUndeployed(target: UndeployTarget, output: Output): void {\n if (target.deletes === 'config') {\n output.log(`\\n${styleText('bold', 'Config deleted.')}`)\n return\n }\n\n if (target.type === 'coreApp') {\n output.log(\n `\\n${styleText('bold', '⏱️ Application undeploy scheduled.')} It might be a few minutes until ${\n target.title ? styleText('italic', `'${target.title}'`) : 'your application'\n } is unavailable.`,\n )\n output.log(\n `\\n${styleText('bold', 'Remember to remove `deployment.appId` from your application configuration')} to avoid errors when redeploying.`,\n )\n return\n }\n\n output.log(\n `\\nStudio undeploy scheduled. It might be a few minutes until ${target.url ?? 'your studio'} is unavailable.`,\n )\n}\n\n/** The one failure diagnosis both the stderr message and the `--json` envelope read. */\nfunction normalizeFailure(\n error: unknown,\n type: UndeployAdapter['type'],\n): {exit: number; message: string} {\n // Ctrl+C on the confirmation prompt isn't a real failure\n if (error instanceof Error && error.name === 'ExitPromptError') {\n return {exit: 1, message: 'Undeploy cancelled by user'}\n }\n // A failed check already carries its own message and exit code\n if (error instanceof CLIError) {\n return {exit: error.oclif?.exit ?? 1, message: error.message}\n }\n const label = type === 'coreApp' ? 'application' : 'studio'\n undeployDebug(`Error undeploying ${label}`, error)\n return {exit: 1, message: `Error undeploying ${label}: ${getErrorMessage(error)}`}\n}\n"],"names":["styleText","CLIError","subdebug","getErrorMessage","confirm","spinner","createCollectingReporter","createFailFastReporter","toStderrOutput","describeUndeployTarget","renderUndeployPlan","undeployLabel","undeployPlanToJson","undeployDebug","runUndeploy","options","adapter","flags","output","json","emitJson","payload","log","JSON","stringify","key","value","undefined","runOptions","reporter","resolution","resolveTarget","plan","checks","results","reason","type","message","target","failed","find","check","status","error","exit","exitCode","result","undeployApp","failure","normalizeFailure","undeployed","solution","yes","shouldUndeploy","default","confirmUndeployMessage","label","spin","start","undeploy","fail","succeed","printUndeployed","application","report","deletes","title","id","url","Error","name","oclif"],"mappings":"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"}
|
|
@@ -3,6 +3,12 @@ import { checkStatusIcon, renderIssues } from '../../util/checks.js';
|
|
|
3
3
|
export function canUndeploy(plan) {
|
|
4
4
|
return plan.target !== null && plan.checks.every((check)=>check.status !== 'fail');
|
|
5
5
|
}
|
|
6
|
+
/** What an undeploy deletes, phrased for the human output. */ export function undeployLabel(target, type) {
|
|
7
|
+
if (target?.deletes === 'config') {
|
|
8
|
+
return `installation config${target.title ? ` for "${target.title}"` : ''}`;
|
|
9
|
+
}
|
|
10
|
+
return type === 'coreApp' ? 'application' : 'studio';
|
|
11
|
+
}
|
|
6
12
|
/**
|
|
7
13
|
* A machine-readable projection of the plan: blocking problems mapped to their
|
|
8
14
|
* fix, warnings as messages. Derived from the same checks and target the human
|
|
@@ -34,6 +40,12 @@ export function canUndeploy(plan) {
|
|
|
34
40
|
};
|
|
35
41
|
}
|
|
36
42
|
const { target } = resolution;
|
|
43
|
+
if (target.deletes === 'config') {
|
|
44
|
+
return {
|
|
45
|
+
message: target.title ? `Undeploys the installation config for "${target.title}"` : 'Undeploys the installation config',
|
|
46
|
+
status: 'pass'
|
|
47
|
+
};
|
|
48
|
+
}
|
|
37
49
|
if (target.type === 'studio') {
|
|
38
50
|
return {
|
|
39
51
|
message: `Undeploys studio ${target.url ?? target.id}`,
|
|
@@ -62,8 +74,8 @@ export function renderUndeployPlan(plan, output) {
|
|
|
62
74
|
if (plan.target) renderTarget(plan.target, output);
|
|
63
75
|
if (!canUndeploy(plan)) {
|
|
64
76
|
if (problems.length > 0) {
|
|
65
|
-
const label = plan.type
|
|
66
|
-
output.log(styleText('red', `\n${checkStatusIcon('fail')} ${label} can not be undeployed.`));
|
|
77
|
+
const label = undeployLabel(plan.target, plan.type);
|
|
78
|
+
output.log(styleText('red', `\n${checkStatusIcon('fail')} ${label[0].toUpperCase()}${label.slice(1)} can not be undeployed.`));
|
|
67
79
|
} else {
|
|
68
80
|
output.log('\nNothing to undeploy.');
|
|
69
81
|
}
|
|
@@ -94,11 +106,14 @@ function renderTarget(target, output) {
|
|
|
94
106
|
for (const [key, value] of rows){
|
|
95
107
|
if (value) output.log(` ${key.padEnd(8)} ${styleText('yellow', value)}`);
|
|
96
108
|
}
|
|
109
|
+
// Adapter-authored lines about what gets deleted (interfaces, config snapshots, …)
|
|
110
|
+
for (const entry of target.summary ?? []){
|
|
111
|
+
for (const line of entry.split('\n'))output.log(` ${line}`);
|
|
112
|
+
}
|
|
97
113
|
}
|
|
98
114
|
function formatDeployment(deployment) {
|
|
99
115
|
if (!deployment) return null;
|
|
100
116
|
const parts = [
|
|
101
|
-
deployment.version ? `version ${deployment.version}` : null,
|
|
102
117
|
deployment.deployedAt ? `at ${deployment.deployedAt}` : null,
|
|
103
118
|
deployment.deployedBy ? `by ${deployment.deployedBy}` : null
|
|
104
119
|
].filter(Boolean);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/undeploy/undeployPlan.ts"],"sourcesContent":["import {styleText} from 'node:util'\n\nimport {type Output} from '@sanity/cli-core'\
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/undeploy/undeployPlan.ts"],"sourcesContent":["import {styleText} from 'node:util'\n\nimport {type Output} from '@sanity/cli-core'\nimport {type UndeployTarget, type UndeployTargetResolution} from '@sanity/cli-core/undeploy'\n\nimport {type Check, checkStatusIcon, renderIssues} from '../../util/checks.js'\n\n/** What a `--dry-run` undeploy would do: the real undeploy sequence with the deletion gated off. */\nexport interface UndeployPlan {\n checks: Check[]\n /** Why there is nothing to undeploy; `null` when a target resolved. */\n reason: string | null\n /** What would be deleted; `null` when there is nothing to undeploy. */\n target: UndeployTarget | null\n type: 'coreApp' | 'studio'\n}\n\nexport function canUndeploy(plan: UndeployPlan): boolean {\n return plan.target !== null && plan.checks.every((check) => check.status !== 'fail')\n}\n\n/** What an undeploy deletes, phrased for the human output. */\nexport function undeployLabel(target: UndeployTarget | null, type: 'coreApp' | 'studio'): string {\n if (target?.deletes === 'config') {\n return `installation config${target.title ? ` for \"${target.title}\"` : ''}`\n }\n return type === 'coreApp' ? 'application' : 'studio'\n}\n\n/**\n * A machine-readable projection of the plan: blocking problems mapped to their\n * fix, warnings as messages. Derived from the same checks and target the human\n * report renders, so the two can't drift.\n */\nexport function undeployPlanToJson(plan: UndeployPlan): {\n application: UndeployTarget | null\n canUndeploy: boolean\n errors: Record<string, string | null>\n reason: string | null\n warnings: string[]\n} {\n const errors: Record<string, string | null> = {}\n const warnings: string[] = []\n for (const check of plan.checks) {\n if (check.status === 'fail') errors[check.message] = check.solution ?? null\n else if (check.status === 'warn') warnings.push(check.message)\n }\n\n return {\n application: plan.target,\n canUndeploy: canUndeploy(plan),\n errors,\n reason: plan.reason,\n warnings,\n }\n}\n\n/**\n * The single diagnosis for each undeploy-target verdict, shared by the dry-run\n * report and the real run so message and fix can't drift between the two.\n */\nexport function describeUndeployTarget(resolution: UndeployTargetResolution): Check {\n if (resolution.type === 'none') {\n return {message: resolution.message, solution: resolution.solution, status: 'skip'}\n }\n\n const {target} = resolution\n if (target.deletes === 'config') {\n return {\n message: target.title\n ? `Undeploys the installation config for \"${target.title}\"`\n : 'Undeploys the installation config',\n status: 'pass',\n }\n }\n if (target.type === 'studio') {\n return {message: `Undeploys studio ${target.url ?? target.id}`, status: 'pass'}\n }\n const name = target.title ? `\"${target.title}\" (${target.id})` : target.id\n return {message: `Undeploys application ${name}`, status: 'pass'}\n}\n\nexport function renderUndeployPlan(plan: UndeployPlan, output: Output): void {\n const problems = plan.checks.filter((check) => check.status === 'fail')\n const warnings = plan.checks.filter((check) => check.status === 'warn')\n\n output.log('\\nDry run — no changes made.\\n')\n\n // Only pass/skip here; problems and warnings render below with their fixes.\n // A passing target check already says what gets undeployed, so an\n // undeployable plan needs no extra verdict line.\n for (const check of plan.checks) {\n if (check.status === 'pass' || check.status === 'skip') {\n const fix = check.solution ? `: ${check.solution}` : ''\n output.log(` ${checkStatusIcon(check.status)} ${check.message}${fix}`)\n }\n }\n\n if (plan.target) renderTarget(plan.target, output)\n\n if (!canUndeploy(plan)) {\n if (problems.length > 0) {\n const label = undeployLabel(plan.target, plan.type)\n output.log(\n styleText(\n 'red',\n `\\n${checkStatusIcon('fail')} ${label[0].toUpperCase()}${label.slice(1)} can not be undeployed.`,\n ),\n )\n } else {\n output.log('\\nNothing to undeploy.')\n }\n }\n\n renderIssues(output, 'Problems to fix:', problems)\n renderIssues(output, 'Warnings:', warnings)\n}\n\nfunction renderTarget(target: UndeployTarget, output: Output): void {\n const rows: [string, string | null][] = [\n ['Title', target.title],\n ['ID', target.id],\n ['URL', target.url],\n ['Deployed', formatDeployment(target.activeDeployment)],\n ]\n\n output.log('')\n for (const [key, value] of rows) {\n if (value) output.log(` ${key.padEnd(8)} ${styleText('yellow', value)}`)\n }\n\n // Adapter-authored lines about what gets deleted (interfaces, config snapshots, …)\n for (const entry of target.summary ?? []) {\n for (const line of entry.split('\\n')) output.log(` ${line}`)\n }\n}\n\nfunction formatDeployment(deployment: UndeployTarget['activeDeployment']): string | null {\n if (!deployment) return null\n const parts = [\n deployment.deployedAt ? `at ${deployment.deployedAt}` : null,\n deployment.deployedBy ? `by ${deployment.deployedBy}` : null,\n ].filter(Boolean)\n return parts.length > 0 ? parts.join(' ') : null\n}\n"],"names":["styleText","checkStatusIcon","renderIssues","canUndeploy","plan","target","checks","every","check","status","undeployLabel","type","deletes","title","undeployPlanToJson","errors","warnings","message","solution","push","application","reason","describeUndeployTarget","resolution","url","id","name","renderUndeployPlan","output","problems","filter","log","fix","renderTarget","length","label","toUpperCase","slice","rows","formatDeployment","activeDeployment","key","value","padEnd","entry","summary","line","split","deployment","parts","deployedAt","deployedBy","Boolean","join"],"mappings":"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"}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { stat } from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { styleText } from 'node:util';
|
|
4
4
|
import { Flags } from '@oclif/core';
|
|
5
|
-
import { ProjectRootNotFoundError
|
|
5
|
+
import { ProjectRootNotFoundError } from '@sanity/cli-core/errors';
|
|
6
|
+
import { SanityCommand } from '@sanity/cli-core/SanityCommand';
|
|
6
7
|
import { confirm, logSymbols } from '@sanity/cli-core/ux';
|
|
7
8
|
import { validateDocuments } from '../../actions/documents/validate.js';
|
|
8
9
|
import { reporters } from '../../actions/documents/validation/reporters/index.js';
|
|
@@ -87,28 +88,28 @@ export class ValidateDocumentsCommand extends SanityCommand {
|
|
|
87
88
|
cliConfig = await this.getCliConfig();
|
|
88
89
|
} catch (err) {
|
|
89
90
|
if (err instanceof ProjectRootNotFoundError) {
|
|
90
|
-
this.error('This command must be run from within a Sanity project directory (requires studio schema for validation)', {
|
|
91
|
+
return this.output.error('This command must be run from within a Sanity project directory (requires studio schema for validation)', {
|
|
91
92
|
exit: 1
|
|
92
93
|
});
|
|
93
94
|
}
|
|
94
95
|
throw err;
|
|
95
96
|
}
|
|
96
97
|
if (!unattendedMode) {
|
|
97
|
-
this.log(`${styleText('yellow', `${logSymbols.warning} Warning:`)} This command ${file ? 'reads all documents from your input file' : 'downloads all documents from your dataset'} and processes them through your local schema within a ` + `simulated browser environment.\n`);
|
|
98
|
-
this.log(`Potential pitfalls:\n`);
|
|
99
|
-
this.log(`- Processes all documents locally (excluding assets). Large datasets may require more resources.`);
|
|
100
|
-
this.log(`- Executes all custom validation functions. Some functions may need to be refactored for compatibility.`);
|
|
101
|
-
this.log(`- Not all standard browser features are available and may cause issues while loading your Studio.`);
|
|
102
|
-
this.log(`- Adheres to document permissions. Ensure this account can see all desired documents.`);
|
|
98
|
+
this.output.log(`${styleText('yellow', `${logSymbols.warning} Warning:`)} This command ${file ? 'reads all documents from your input file' : 'downloads all documents from your dataset'} and processes them through your local schema within a ` + `simulated browser environment.\n`);
|
|
99
|
+
this.output.log(`Potential pitfalls:\n`);
|
|
100
|
+
this.output.log(`- Processes all documents locally (excluding assets). Large datasets may require more resources.`);
|
|
101
|
+
this.output.log(`- Executes all custom validation functions. Some functions may need to be refactored for compatibility.`);
|
|
102
|
+
this.output.log(`- Not all standard browser features are available and may cause issues while loading your Studio.`);
|
|
103
|
+
this.output.log(`- Adheres to document permissions. Ensure this account can see all desired documents.`);
|
|
103
104
|
if (file) {
|
|
104
|
-
this.log(`- Checks for missing document references against the live dataset if not found in your file.`);
|
|
105
|
+
this.output.log(`- Checks for missing document references against the live dataset if not found in your file.`);
|
|
105
106
|
}
|
|
106
107
|
const confirmed = await confirm({
|
|
107
108
|
default: true,
|
|
108
109
|
message: `Are you sure you want to continue?`
|
|
109
110
|
});
|
|
110
111
|
if (!confirmed) {
|
|
111
|
-
this.error('User aborted', {
|
|
112
|
+
return this.output.error('User aborted', {
|
|
112
113
|
exit: 1
|
|
113
114
|
});
|
|
114
115
|
}
|
|
@@ -118,16 +119,16 @@ export class ValidateDocumentsCommand extends SanityCommand {
|
|
|
118
119
|
style: 'long',
|
|
119
120
|
type: 'conjunction'
|
|
120
121
|
});
|
|
121
|
-
this.error(`Did not recognize format '${format}'. Available formats are ${formatter.format(Object.keys(reporters).map((key)=>`'${key}'`))}`, {
|
|
122
|
+
return this.output.error(`Did not recognize format '${format}'. Available formats are ${formatter.format(Object.keys(reporters).map((key)=>`'${key}'`))}`, {
|
|
122
123
|
exit: 1
|
|
123
124
|
});
|
|
124
125
|
}
|
|
125
126
|
let ndjsonFilePath;
|
|
126
127
|
if (file) {
|
|
127
128
|
const filePath = path.resolve(workDir, file);
|
|
128
|
-
const
|
|
129
|
-
if (!
|
|
130
|
-
this.error(`'--file' must point to a valid ndjson file or tarball`, {
|
|
129
|
+
const st = await stat(filePath);
|
|
130
|
+
if (!st.isFile()) {
|
|
131
|
+
return this.output.error(`'--file' must point to a valid ndjson file or tarball`, {
|
|
131
132
|
exit: 1
|
|
132
133
|
});
|
|
133
134
|
}
|
|
@@ -154,10 +155,10 @@ export class ValidateDocumentsCommand extends SanityCommand {
|
|
|
154
155
|
workspace
|
|
155
156
|
});
|
|
156
157
|
if (overallLevel === 'error') {
|
|
157
|
-
this.exit(1);
|
|
158
|
+
return this.exit(1);
|
|
158
159
|
}
|
|
159
160
|
} catch (err) {
|
|
160
|
-
this.error(err instanceof Error ? err.message : String(err), {
|
|
161
|
+
return this.output.error(err instanceof Error ? err.message : String(err), {
|
|
161
162
|
exit: 1
|
|
162
163
|
});
|
|
163
164
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/commands/documents/validate.ts"],"sourcesContent":["import fs from 'node:fs'\nimport path from 'node:path'\nimport {styleText} from 'node:util'\n\nimport {Flags} from '@oclif/core'\nimport {type CliConfig, ProjectRootNotFoundError, SanityCommand} from '@sanity/cli-core'\nimport {confirm, logSymbols} from '@sanity/cli-core/ux'\n\nimport {type Level} from '../../actions/documents/types.js'\nimport {validateDocuments} from '../../actions/documents/validate.js'\nimport {reporters} from '../../actions/documents/validation/reporters/index.js'\nimport {getDatasetFlag, getProjectIdFlag} from '../../util/sharedFlags.js'\n\nexport class ValidateDocumentsCommand extends SanityCommand<typeof ValidateDocumentsCommand> {\n static description = 'Validate documents in a dataset against the studio schema'\n\n static examples = [\n {\n command: '<%= config.bin %> <%= command.id %> --workspace default',\n description: 'Validates all documents in a Sanity project with more than one workspace',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --workspace default --dataset staging',\n description: 'Override the dataset specified in the workspace',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --yes > report.txt',\n description: 'Save the results of the report into a file',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --level info',\n description: 'Report out info level validation markers too',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --project-id abc123 --dataset production',\n description: 'Validate documents in a specific project and dataset',\n },\n ]\n\n static flags = {\n ...getProjectIdFlag({\n description:\n 'Override the project ID used. By default, this is derived from the given workspace',\n semantics: 'specify',\n }),\n ...getDatasetFlag({\n description:\n 'Override the dataset used. By default, this is derived from the given workspace',\n semantics: 'specify',\n }),\n file: Flags.string({\n description:\n 'Provide a path to either an .ndjson file or a tarball containing an .ndjson file',\n }),\n format: Flags.string({\n description:\n 'The output format used to print the found validation markers and report progress',\n }),\n level: Flags.custom<Level>({\n default: 'warning',\n description: 'The minimum level reported. Defaults to warning',\n options: ['error', 'warning', 'info'],\n })(),\n 'max-custom-validation-concurrency': Flags.integer({\n default: 5,\n description: 'Specify how many custom validators can run concurrently',\n }),\n 'max-fetch-concurrency': Flags.integer({\n default: 25,\n description: 'Specify how many `client.fetch` requests are allowed to run concurrently',\n }),\n workspace: Flags.string({\n description: 'The name of the workspace to use when downloading and validating all documents',\n }),\n yes: Flags.boolean({\n char: 'y',\n default: false,\n description: 'Skips the first confirmation prompt',\n }),\n }\n\n static override hiddenAliases: string[] = ['document:validate']\n\n public async run(): Promise<void> {\n const {flags} = await this.parse(ValidateDocumentsCommand)\n const {\n dataset,\n file,\n format,\n level,\n 'max-custom-validation-concurrency': maxCustomValidationConcurrency,\n 'max-fetch-concurrency': maxFetchConcurrency,\n 'project-id': projectId,\n workspace,\n } = flags\n const unattendedMode = Boolean(flags.yes)\n\n let workDir: string\n let cliConfig: CliConfig\n try {\n const root = await this.getProjectRoot()\n workDir = root.directory\n cliConfig = await this.getCliConfig()\n } catch (err) {\n if (err instanceof ProjectRootNotFoundError) {\n this.error(\n 'This command must be run from within a Sanity project directory (requires studio schema for validation)',\n {exit: 1},\n )\n }\n throw err\n }\n\n if (!unattendedMode) {\n this.log(\n `${styleText('yellow', `${logSymbols.warning} Warning:`)} This command ${\n file\n ? 'reads all documents from your input file'\n : 'downloads all documents from your dataset'\n } and processes them through your local schema within a ` +\n `simulated browser environment.\\n`,\n )\n this.log(`Potential pitfalls:\\n`)\n this.log(\n `- Processes all documents locally (excluding assets). Large datasets may require more resources.`,\n )\n this.log(\n `- Executes all custom validation functions. Some functions may need to be refactored for compatibility.`,\n )\n this.log(\n `- Not all standard browser features are available and may cause issues while loading your Studio.`,\n )\n this.log(\n `- Adheres to document permissions. Ensure this account can see all desired documents.`,\n )\n if (file) {\n this.log(\n `- Checks for missing document references against the live dataset if not found in your file.`,\n )\n }\n\n const confirmed = await confirm({\n default: true,\n message: `Are you sure you want to continue?`,\n })\n\n if (!confirmed) {\n this.error('User aborted', {exit: 1})\n }\n }\n\n if (format && !(format in reporters)) {\n const formatter = new Intl.ListFormat('en-US', {\n style: 'long',\n type: 'conjunction',\n })\n this.error(\n `Did not recognize format '${format}'. Available formats are ${formatter.format(\n Object.keys(reporters).map((key) => `'${key}'`),\n )}`,\n {exit: 1},\n )\n }\n\n let ndjsonFilePath\n if (file) {\n const filePath = path.resolve(workDir, file)\n\n const stat = await fs.promises.stat(filePath)\n if (!stat.isFile()) {\n this.error(`'--file' must point to a valid ndjson file or tarball`, {exit: 1})\n }\n\n ndjsonFilePath = filePath\n }\n\n try {\n const overallLevel = await validateDocuments({\n dataset,\n level,\n maxCustomValidationConcurrency,\n maxFetchConcurrency,\n ndjsonFilePath,\n projectId,\n reporter: (worker) => {\n const reporter =\n format && format in reporters\n ? reporters[format as keyof typeof reporters]\n : reporters.pretty\n\n return reporter({flags, output: this.output, worker})\n },\n studioHost: cliConfig.studioHost,\n workDir,\n workspace,\n })\n\n if (overallLevel === 'error') {\n this.exit(1)\n }\n } catch (err) {\n this.error(err instanceof Error ? err.message : String(err), {exit: 1})\n }\n }\n}\n"],"names":["fs","path","styleText","Flags","ProjectRootNotFoundError","SanityCommand","confirm","logSymbols","validateDocuments","reporters","getDatasetFlag","getProjectIdFlag","ValidateDocumentsCommand","description","examples","command","flags","semantics","file","string","format","level","custom","default","options","integer","workspace","yes","boolean","char","hiddenAliases","run","parse","dataset","maxCustomValidationConcurrency","maxFetchConcurrency","projectId","unattendedMode","Boolean","workDir","cliConfig","root","getProjectRoot","directory","getCliConfig","err","error","exit","log","warning","confirmed","message","formatter","Intl","ListFormat","style","type","Object","keys","map","key","ndjsonFilePath","filePath","resolve","stat","promises","isFile","overallLevel","reporter","worker","pretty","output","studioHost","Error","String"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/commands/documents/validate.ts"],"sourcesContent":["import {stat} from 'node:fs/promises'\nimport path from 'node:path'\nimport {styleText} from 'node:util'\n\nimport {Flags} from '@oclif/core'\nimport {ProjectRootNotFoundError} from '@sanity/cli-core/errors'\nimport {SanityCommand} from '@sanity/cli-core/SanityCommand'\nimport {type CliConfig} from '@sanity/cli-core/types'\nimport {confirm, logSymbols} from '@sanity/cli-core/ux'\n\nimport {type Level} from '../../actions/documents/types.js'\nimport {validateDocuments} from '../../actions/documents/validate.js'\nimport {reporters} from '../../actions/documents/validation/reporters/index.js'\nimport {getDatasetFlag, getProjectIdFlag} from '../../util/sharedFlags.js'\n\nexport class ValidateDocumentsCommand extends SanityCommand<typeof ValidateDocumentsCommand> {\n static description = 'Validate documents in a dataset against the studio schema'\n\n static examples = [\n {\n command: '<%= config.bin %> <%= command.id %> --workspace default',\n description: 'Validates all documents in a Sanity project with more than one workspace',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --workspace default --dataset staging',\n description: 'Override the dataset specified in the workspace',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --yes > report.txt',\n description: 'Save the results of the report into a file',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --level info',\n description: 'Report out info level validation markers too',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --project-id abc123 --dataset production',\n description: 'Validate documents in a specific project and dataset',\n },\n ]\n\n static flags = {\n ...getProjectIdFlag({\n description:\n 'Override the project ID used. By default, this is derived from the given workspace',\n semantics: 'specify',\n }),\n ...getDatasetFlag({\n description:\n 'Override the dataset used. By default, this is derived from the given workspace',\n semantics: 'specify',\n }),\n file: Flags.string({\n description:\n 'Provide a path to either an .ndjson file or a tarball containing an .ndjson file',\n }),\n format: Flags.string({\n description:\n 'The output format used to print the found validation markers and report progress',\n }),\n level: Flags.custom<Level>({\n default: 'warning',\n description: 'The minimum level reported. Defaults to warning',\n options: ['error', 'warning', 'info'],\n })(),\n 'max-custom-validation-concurrency': Flags.integer({\n default: 5,\n description: 'Specify how many custom validators can run concurrently',\n }),\n 'max-fetch-concurrency': Flags.integer({\n default: 25,\n description: 'Specify how many `client.fetch` requests are allowed to run concurrently',\n }),\n workspace: Flags.string({\n description: 'The name of the workspace to use when downloading and validating all documents',\n }),\n yes: Flags.boolean({\n char: 'y',\n default: false,\n description: 'Skips the first confirmation prompt',\n }),\n }\n\n static override hiddenAliases: string[] = ['document:validate']\n\n public async run(): Promise<void> {\n const {flags} = await this.parse(ValidateDocumentsCommand)\n const {\n dataset,\n file,\n format,\n level,\n 'max-custom-validation-concurrency': maxCustomValidationConcurrency,\n 'max-fetch-concurrency': maxFetchConcurrency,\n 'project-id': projectId,\n workspace,\n } = flags\n const unattendedMode = Boolean(flags.yes)\n\n let workDir: string\n let cliConfig: CliConfig\n try {\n const root = await this.getProjectRoot()\n workDir = root.directory\n cliConfig = await this.getCliConfig()\n } catch (err) {\n if (err instanceof ProjectRootNotFoundError) {\n return this.output.error(\n 'This command must be run from within a Sanity project directory (requires studio schema for validation)',\n {exit: 1},\n )\n }\n throw err\n }\n\n if (!unattendedMode) {\n this.output.log(\n `${styleText('yellow', `${logSymbols.warning} Warning:`)} This command ${\n file\n ? 'reads all documents from your input file'\n : 'downloads all documents from your dataset'\n } and processes them through your local schema within a ` +\n `simulated browser environment.\\n`,\n )\n this.output.log(`Potential pitfalls:\\n`)\n this.output.log(\n `- Processes all documents locally (excluding assets). Large datasets may require more resources.`,\n )\n this.output.log(\n `- Executes all custom validation functions. Some functions may need to be refactored for compatibility.`,\n )\n this.output.log(\n `- Not all standard browser features are available and may cause issues while loading your Studio.`,\n )\n this.output.log(\n `- Adheres to document permissions. Ensure this account can see all desired documents.`,\n )\n if (file) {\n this.output.log(\n `- Checks for missing document references against the live dataset if not found in your file.`,\n )\n }\n\n const confirmed = await confirm({\n default: true,\n message: `Are you sure you want to continue?`,\n })\n\n if (!confirmed) {\n return this.output.error('User aborted', {exit: 1})\n }\n }\n\n if (format && !(format in reporters)) {\n const formatter = new Intl.ListFormat('en-US', {\n style: 'long',\n type: 'conjunction',\n })\n return this.output.error(\n `Did not recognize format '${format}'. Available formats are ${formatter.format(\n Object.keys(reporters).map((key) => `'${key}'`),\n )}`,\n {exit: 1},\n )\n }\n\n let ndjsonFilePath\n if (file) {\n const filePath = path.resolve(workDir, file)\n\n const st = await stat(filePath)\n if (!st.isFile()) {\n return this.output.error(`'--file' must point to a valid ndjson file or tarball`, {exit: 1})\n }\n\n ndjsonFilePath = filePath\n }\n\n try {\n const overallLevel = await validateDocuments({\n dataset,\n level,\n maxCustomValidationConcurrency,\n maxFetchConcurrency,\n ndjsonFilePath,\n projectId,\n reporter: (worker) => {\n const reporter =\n format && format in reporters\n ? reporters[format as keyof typeof reporters]\n : reporters.pretty\n\n return reporter({flags, output: this.output, worker})\n },\n studioHost: cliConfig.studioHost,\n workDir,\n workspace,\n })\n\n if (overallLevel === 'error') {\n return this.exit(1)\n }\n } catch (err) {\n return this.output.error(err instanceof Error ? err.message : String(err), {exit: 1})\n }\n }\n}\n"],"names":["stat","path","styleText","Flags","ProjectRootNotFoundError","SanityCommand","confirm","logSymbols","validateDocuments","reporters","getDatasetFlag","getProjectIdFlag","ValidateDocumentsCommand","description","examples","command","flags","semantics","file","string","format","level","custom","default","options","integer","workspace","yes","boolean","char","hiddenAliases","run","parse","dataset","maxCustomValidationConcurrency","maxFetchConcurrency","projectId","unattendedMode","Boolean","workDir","cliConfig","root","getProjectRoot","directory","getCliConfig","err","output","error","exit","log","warning","confirmed","message","formatter","Intl","ListFormat","style","type","Object","keys","map","key","ndjsonFilePath","filePath","resolve","st","isFile","overallLevel","reporter","worker","pretty","studioHost","Error","String"],"mappings":"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"}
|
|
@@ -48,6 +48,10 @@ export class CreateHookCommand extends SanityCommand {
|
|
|
48
48
|
}
|
|
49
49
|
const organizationId = projectInfo.organizationId || 'personal';
|
|
50
50
|
const manageUrl = getSanityUrl(`/organizations/${organizationId}/project/${projectId}/api/webhooks/new`);
|
|
51
|
+
if (this.isUnattended()) {
|
|
52
|
+
this.log(`Create a webhook at ${manageUrl}`);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
51
55
|
this.log(`Opening ${manageUrl}`);
|
|
52
56
|
try {
|
|
53
57
|
await open(manageUrl);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/commands/hooks/create.ts"],"sourcesContent":["import {getSanityUrl, SanityCommand, subdebug} from '@sanity/cli-core'\nimport open from 'open'\n\nimport {promptForProject} from '../../prompts/promptForProject.js'\nimport {getProjectById} from '../../services/projects.js'\nimport {getProjectIdFlag} from '../../util/sharedFlags.js'\n\nconst createHookDebug = subdebug('hook:create')\n\nexport class CreateHookCommand extends SanityCommand<typeof CreateHookCommand> {\n static override description = 'Create a new webhook for the project'\n static override examples = [\n {\n command: '<%= config.bin %> <%= command.id %>',\n description: 'Create a new webhook for the project',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --project-id abc123',\n description: 'Create a webhook for a specific project',\n },\n ]\n\n static override flags = {\n ...getProjectIdFlag({\n description: 'Project ID to create webhook for',\n semantics: 'override',\n }),\n }\n\n static override hiddenAliases: string[] = ['hook:create']\n\n public async run() {\n const projectId = await this.getProjectId({\n fallback: () =>\n promptForProject({requiredPermissions: [{grant: 'read', permission: 'sanity.project'}]}),\n })\n\n let projectInfo: {organizationId?: string | null}\n try {\n projectInfo = await getProjectById(projectId)\n } catch (error) {\n const err = error as Error\n createHookDebug(`Error fetching project info for project ${projectId}`, err)\n this.error(`Failed to fetch project information:\\n${err.message}`, {exit: 1})\n }\n\n const organizationId = projectInfo.organizationId || 'personal'\n const manageUrl = getSanityUrl(\n `/organizations/${organizationId}/project/${projectId}/api/webhooks/new`,\n )\n\n this.log(`Opening ${manageUrl}`)\n\n try {\n await open(manageUrl)\n } catch (error) {\n const err = error as Error\n createHookDebug('Error opening browser', err)\n this.error(`Failed to open browser:\\n${err.message}`, {exit: 1})\n }\n }\n}\n"],"names":["getSanityUrl","SanityCommand","subdebug","open","promptForProject","getProjectById","getProjectIdFlag","createHookDebug","CreateHookCommand","description","examples","command","flags","semantics","hiddenAliases","run","projectId","getProjectId","fallback","requiredPermissions","grant","permission","projectInfo","error","err","message","exit","organizationId","manageUrl","log"],"mappings":"AAAA,SAAQA,YAAY,EAAEC,aAAa,EAAEC,QAAQ,QAAO,mBAAkB;AACtE,OAAOC,UAAU,OAAM;AAEvB,SAAQC,gBAAgB,QAAO,oCAAmC;AAClE,SAAQC,cAAc,QAAO,6BAA4B;AACzD,SAAQC,gBAAgB,QAAO,4BAA2B;AAE1D,MAAMC,kBAAkBL,SAAS;AAEjC,OAAO,MAAMM,0BAA0BP;IACrC,OAAgBQ,cAAc,uCAAsC;IACpE,OAAgBC,WAAW;QACzB;YACEC,SAAS;YACTF,aAAa;QACf;QACA;YACEE,SAAS;YACTF,aAAa;QACf;KACD,CAAA;IAED,OAAgBG,QAAQ;QACtB,GAAGN,iBAAiB;YAClBG,aAAa;YACbI,WAAW;QACb,EAAE;IACJ,EAAC;IAED,OAAgBC,gBAA0B;QAAC;KAAc,CAAA;IAEzD,MAAaC,MAAM;QACjB,MAAMC,YAAY,MAAM,IAAI,CAACC,YAAY,CAAC;YACxCC,UAAU,IACRd,iBAAiB;oBAACe,qBAAqB;wBAAC;4BAACC,OAAO;4BAAQC,YAAY;wBAAgB;qBAAE;gBAAA;QAC1F;QAEA,IAAIC;QACJ,IAAI;YACFA,cAAc,MAAMjB,eAAeW;QACrC,EAAE,OAAOO,OAAO;YACd,MAAMC,MAAMD;YACZhB,gBAAgB,CAAC,wCAAwC,EAAES,WAAW,EAAEQ;YACxE,IAAI,CAACD,KAAK,CAAC,CAAC,sCAAsC,EAAEC,IAAIC,OAAO,EAAE,EAAE;gBAACC,MAAM;YAAC;QAC7E;QAEA,MAAMC,iBAAiBL,YAAYK,cAAc,IAAI;QACrD,MAAMC,YAAY5B,aAChB,CAAC,eAAe,EAAE2B,eAAe,SAAS,EAAEX,UAAU,iBAAiB,CAAC;QAG1E,IAAI,CAACa,GAAG,CAAC,CAAC,QAAQ,
|
|
1
|
+
{"version":3,"sources":["../../../src/commands/hooks/create.ts"],"sourcesContent":["import {getSanityUrl, SanityCommand, subdebug} from '@sanity/cli-core'\nimport open from 'open'\n\nimport {promptForProject} from '../../prompts/promptForProject.js'\nimport {getProjectById} from '../../services/projects.js'\nimport {getProjectIdFlag} from '../../util/sharedFlags.js'\n\nconst createHookDebug = subdebug('hook:create')\n\nexport class CreateHookCommand extends SanityCommand<typeof CreateHookCommand> {\n static override description = 'Create a new webhook for the project'\n static override examples = [\n {\n command: '<%= config.bin %> <%= command.id %>',\n description: 'Create a new webhook for the project',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --project-id abc123',\n description: 'Create a webhook for a specific project',\n },\n ]\n\n static override flags = {\n ...getProjectIdFlag({\n description: 'Project ID to create webhook for',\n semantics: 'override',\n }),\n }\n\n static override hiddenAliases: string[] = ['hook:create']\n\n public async run() {\n const projectId = await this.getProjectId({\n fallback: () =>\n promptForProject({requiredPermissions: [{grant: 'read', permission: 'sanity.project'}]}),\n })\n\n let projectInfo: {organizationId?: string | null}\n try {\n projectInfo = await getProjectById(projectId)\n } catch (error) {\n const err = error as Error\n createHookDebug(`Error fetching project info for project ${projectId}`, err)\n this.error(`Failed to fetch project information:\\n${err.message}`, {exit: 1})\n }\n\n const organizationId = projectInfo.organizationId || 'personal'\n const manageUrl = getSanityUrl(\n `/organizations/${organizationId}/project/${projectId}/api/webhooks/new`,\n )\n\n if (this.isUnattended()) {\n this.log(`Create a webhook at ${manageUrl}`)\n return\n }\n\n this.log(`Opening ${manageUrl}`)\n\n try {\n await open(manageUrl)\n } catch (error) {\n const err = error as Error\n createHookDebug('Error opening browser', err)\n this.error(`Failed to open browser:\\n${err.message}`, {exit: 1})\n }\n }\n}\n"],"names":["getSanityUrl","SanityCommand","subdebug","open","promptForProject","getProjectById","getProjectIdFlag","createHookDebug","CreateHookCommand","description","examples","command","flags","semantics","hiddenAliases","run","projectId","getProjectId","fallback","requiredPermissions","grant","permission","projectInfo","error","err","message","exit","organizationId","manageUrl","isUnattended","log"],"mappings":"AAAA,SAAQA,YAAY,EAAEC,aAAa,EAAEC,QAAQ,QAAO,mBAAkB;AACtE,OAAOC,UAAU,OAAM;AAEvB,SAAQC,gBAAgB,QAAO,oCAAmC;AAClE,SAAQC,cAAc,QAAO,6BAA4B;AACzD,SAAQC,gBAAgB,QAAO,4BAA2B;AAE1D,MAAMC,kBAAkBL,SAAS;AAEjC,OAAO,MAAMM,0BAA0BP;IACrC,OAAgBQ,cAAc,uCAAsC;IACpE,OAAgBC,WAAW;QACzB;YACEC,SAAS;YACTF,aAAa;QACf;QACA;YACEE,SAAS;YACTF,aAAa;QACf;KACD,CAAA;IAED,OAAgBG,QAAQ;QACtB,GAAGN,iBAAiB;YAClBG,aAAa;YACbI,WAAW;QACb,EAAE;IACJ,EAAC;IAED,OAAgBC,gBAA0B;QAAC;KAAc,CAAA;IAEzD,MAAaC,MAAM;QACjB,MAAMC,YAAY,MAAM,IAAI,CAACC,YAAY,CAAC;YACxCC,UAAU,IACRd,iBAAiB;oBAACe,qBAAqB;wBAAC;4BAACC,OAAO;4BAAQC,YAAY;wBAAgB;qBAAE;gBAAA;QAC1F;QAEA,IAAIC;QACJ,IAAI;YACFA,cAAc,MAAMjB,eAAeW;QACrC,EAAE,OAAOO,OAAO;YACd,MAAMC,MAAMD;YACZhB,gBAAgB,CAAC,wCAAwC,EAAES,WAAW,EAAEQ;YACxE,IAAI,CAACD,KAAK,CAAC,CAAC,sCAAsC,EAAEC,IAAIC,OAAO,EAAE,EAAE;gBAACC,MAAM;YAAC;QAC7E;QAEA,MAAMC,iBAAiBL,YAAYK,cAAc,IAAI;QACrD,MAAMC,YAAY5B,aAChB,CAAC,eAAe,EAAE2B,eAAe,SAAS,EAAEX,UAAU,iBAAiB,CAAC;QAG1E,IAAI,IAAI,CAACa,YAAY,IAAI;YACvB,IAAI,CAACC,GAAG,CAAC,CAAC,oBAAoB,EAAEF,WAAW;YAC3C;QACF;QAEA,IAAI,CAACE,GAAG,CAAC,CAAC,QAAQ,EAAEF,WAAW;QAE/B,IAAI;YACF,MAAMzB,KAAKyB;QACb,EAAE,OAAOL,OAAO;YACd,MAAMC,MAAMD;YACZhB,gBAAgB,yBAAyBiB;YACzC,IAAI,CAACD,KAAK,CAAC,CAAC,yBAAyB,EAAEC,IAAIC,OAAO,EAAE,EAAE;gBAACC,MAAM;YAAC;QAChE;IACF;AACF"}
|
|
@@ -38,6 +38,11 @@ export class Delete extends SanityCommand {
|
|
|
38
38
|
];
|
|
39
39
|
async run() {
|
|
40
40
|
const { args } = await this.parse(Delete);
|
|
41
|
+
if (!args.name && this.isUnattended()) {
|
|
42
|
+
this.error('Webhook name is required. Pass the name as an argument.', {
|
|
43
|
+
exit: 2
|
|
44
|
+
});
|
|
45
|
+
}
|
|
41
46
|
const projectId = await this.getProjectId({
|
|
42
47
|
fallback: ()=>promptForProject({
|
|
43
48
|
requiredPermissions: [
|