@sanity/cli 7.7.1 → 7.9.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 +128 -26
- package/dist/actions/deploy/deployApp.js.map +1 -1
- package/dist/actions/deploy/deployChecks.js +97 -72
- 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 +129 -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 +44 -0
- 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/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/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/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 +179 -0
- package/dist/actions/undeploy/runUndeploy.js.map +1 -0
- package/dist/actions/undeploy/undeployPlan.js +123 -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/documents/validate.js +18 -17
- package/dist/commands/documents/validate.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 +47 -73
- package/dist/commands/undeploy.js.map +1 -1
- package/dist/server/devServer.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 +28 -1
- 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 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/schema/validateAction.ts"],"sourcesContent":["import {writeFileSync} from 'node:fs'\n\nimport {formatSchemaValidation, getAggregatedSeverity} from '@sanity/cli-build/_internal/extract'\nimport {
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/schema/validateAction.ts"],"sourcesContent":["import {writeFileSync} from 'node:fs'\n\nimport {formatSchemaValidation, getAggregatedSeverity} from '@sanity/cli-build/_internal/extract'\nimport {studioWorkerTask} from '@sanity/cli-core/tasks'\nimport {type Output} from '@sanity/cli-core/types'\nimport {logSymbols, spinner} from '@sanity/cli-core/ux'\n\nimport {generateMetafile} from './metafile.js'\nimport {\n type ValidateSchemaWorkerData,\n type ValidateSchemaWorkerResult,\n} from './validateSchema.worker.js'\n\ninterface Options {\n output: Output\n workDir: string\n\n debugMetafilePath?: string\n format?: string\n level?: 'error' | 'warning'\n workspace?: string\n}\n\nexport async function validateAction(options: Options): Promise<void> {\n const {debugMetafilePath, format, level, output, workDir, workspace} = options\n\n let spin\n\n if (format === 'pretty') {\n spin = spinner(\n workspace ? `Validating schema from workspace '${workspace}'…` : 'Validating schema…',\n ).start()\n }\n\n const {serializedDebug, validation} = await studioWorkerTask<ValidateSchemaWorkerResult>(\n new URL('validateSchema.worker.js', import.meta.url),\n {\n name: 'validateSchema',\n studioRootPath: workDir,\n workerData: {\n debugSerialize: Boolean(debugMetafilePath),\n level,\n workDir,\n workspace: workspace,\n } satisfies ValidateSchemaWorkerData,\n },\n )\n\n const problems = validation.flatMap((group) => group.problems)\n const errorCount = problems.filter((problem) => problem.severity === 'error').length\n const warningCount = problems.filter((problem) => problem.severity === 'warning').length\n\n const overallSeverity = getAggregatedSeverity(validation)\n const didFail = overallSeverity === 'error'\n\n if (debugMetafilePath && !didFail) {\n if (!serializedDebug) throw new Error('serializedDebug should always be produced')\n const metafile = generateMetafile(serializedDebug)\n writeFileSync(debugMetafilePath, JSON.stringify(metafile), 'utf8')\n }\n\n switch (format) {\n case 'json': {\n output.log(JSON.stringify(validation))\n break\n }\n case 'ndjson': {\n for (const group of validation) {\n output.log(JSON.stringify(group))\n }\n break\n }\n default: {\n spin?.succeed('Validated schema')\n output.log(`\\nValidation results:`)\n output.log(\n `${logSymbols.error} Errors: ${errorCount.toLocaleString('en-US')} error${\n errorCount === 1 ? '' : 's'\n }`,\n )\n if (level !== 'error') {\n output.log(\n `${logSymbols.warning} Warnings: ${warningCount.toLocaleString('en-US')} warning${\n warningCount === 1 ? '' : 's'\n }`,\n )\n }\n output.log()\n\n output.log(formatSchemaValidation(validation))\n\n if (debugMetafilePath) {\n output.log()\n if (didFail) {\n output.log(`${logSymbols.info} Metafile not written due to validation errors`)\n } else {\n output.log(`${logSymbols.info} Metafile written to: ${debugMetafilePath}`)\n output.log(` This can be analyzed at https://esbuild.github.io/analyze/`)\n }\n }\n }\n }\n\n if (didFail) {\n throw new Error('Schema validation failed')\n }\n}\n"],"names":["writeFileSync","formatSchemaValidation","getAggregatedSeverity","studioWorkerTask","logSymbols","spinner","generateMetafile","validateAction","options","debugMetafilePath","format","level","output","workDir","workspace","spin","start","serializedDebug","validation","URL","url","name","studioRootPath","workerData","debugSerialize","Boolean","problems","flatMap","group","errorCount","filter","problem","severity","length","warningCount","overallSeverity","didFail","Error","metafile","JSON","stringify","log","succeed","error","toLocaleString","warning","info"],"mappings":"AAAA,SAAQA,aAAa,QAAO,UAAS;AAErC,SAAQC,sBAAsB,EAAEC,qBAAqB,QAAO,sCAAqC;AACjG,SAAQC,gBAAgB,QAAO,yBAAwB;AAEvD,SAAQC,UAAU,EAAEC,OAAO,QAAO,sBAAqB;AAEvD,SAAQC,gBAAgB,QAAO,gBAAe;AAgB9C,OAAO,eAAeC,eAAeC,OAAgB;IACnD,MAAM,EAACC,iBAAiB,EAAEC,MAAM,EAAEC,KAAK,EAAEC,MAAM,EAAEC,OAAO,EAAEC,SAAS,EAAC,GAAGN;IAEvE,IAAIO;IAEJ,IAAIL,WAAW,UAAU;QACvBK,OAAOV,QACLS,YAAY,CAAC,kCAAkC,EAAEA,UAAU,EAAE,CAAC,GAAG,sBACjEE,KAAK;IACT;IAEA,MAAM,EAACC,eAAe,EAAEC,UAAU,EAAC,GAAG,MAAMf,iBAC1C,IAAIgB,IAAI,4BAA4B,YAAYC,GAAG,GACnD;QACEC,MAAM;QACNC,gBAAgBT;QAChBU,YAAY;YACVC,gBAAgBC,QAAQhB;YACxBE;YACAE;YACAC,WAAWA;QACb;IACF;IAGF,MAAMY,WAAWR,WAAWS,OAAO,CAAC,CAACC,QAAUA,MAAMF,QAAQ;IAC7D,MAAMG,aAAaH,SAASI,MAAM,CAAC,CAACC,UAAYA,QAAQC,QAAQ,KAAK,SAASC,MAAM;IACpF,MAAMC,eAAeR,SAASI,MAAM,CAAC,CAACC,UAAYA,QAAQC,QAAQ,KAAK,WAAWC,MAAM;IAExF,MAAME,kBAAkBjC,sBAAsBgB;IAC9C,MAAMkB,UAAUD,oBAAoB;IAEpC,IAAI1B,qBAAqB,CAAC2B,SAAS;QACjC,IAAI,CAACnB,iBAAiB,MAAM,IAAIoB,MAAM;QACtC,MAAMC,WAAWhC,iBAAiBW;QAClCjB,cAAcS,mBAAmB8B,KAAKC,SAAS,CAACF,WAAW;IAC7D;IAEA,OAAQ5B;QACN,KAAK;YAAQ;gBACXE,OAAO6B,GAAG,CAACF,KAAKC,SAAS,CAACtB;gBAC1B;YACF;QACA,KAAK;YAAU;gBACb,KAAK,MAAMU,SAASV,WAAY;oBAC9BN,OAAO6B,GAAG,CAACF,KAAKC,SAAS,CAACZ;gBAC5B;gBACA;YACF;QACA;YAAS;gBACPb,MAAM2B,QAAQ;gBACd9B,OAAO6B,GAAG,CAAC,CAAC,qBAAqB,CAAC;gBAClC7B,OAAO6B,GAAG,CACR,GAAGrC,WAAWuC,KAAK,CAAC,WAAW,EAAEd,WAAWe,cAAc,CAAC,SAAS,MAAM,EACxEf,eAAe,IAAI,KAAK,KACxB;gBAEJ,IAAIlB,UAAU,SAAS;oBACrBC,OAAO6B,GAAG,CACR,GAAGrC,WAAWyC,OAAO,CAAC,WAAW,EAAEX,aAAaU,cAAc,CAAC,SAAS,QAAQ,EAC9EV,iBAAiB,IAAI,KAAK,KAC1B;gBAEN;gBACAtB,OAAO6B,GAAG;gBAEV7B,OAAO6B,GAAG,CAACxC,uBAAuBiB;gBAElC,IAAIT,mBAAmB;oBACrBG,OAAO6B,GAAG;oBACV,IAAIL,SAAS;wBACXxB,OAAO6B,GAAG,CAAC,GAAGrC,WAAW0C,IAAI,CAAC,8CAA8C,CAAC;oBAC/E,OAAO;wBACLlC,OAAO6B,GAAG,CAAC,GAAGrC,WAAW0C,IAAI,CAAC,sBAAsB,EAAErC,mBAAmB;wBACzEG,OAAO6B,GAAG,CAAC,CAAC,4DAA4D,CAAC;oBAC3E;gBACF;YACF;IACF;IAEA,IAAIL,SAAS;QACX,MAAM,IAAIC,MAAM;IAClB;AACF"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { fileURLToPath } from 'node:url';
|
|
2
2
|
import { styleText } from 'node:util';
|
|
3
3
|
import { subdebug } from '@sanity/cli-core';
|
|
4
|
+
import { getErrorMessage, toError } from '@sanity/cli-core/errors';
|
|
4
5
|
import { logSymbols, spinner } from '@sanity/cli-core/ux';
|
|
5
6
|
import { execa } from 'execa';
|
|
6
|
-
import { getErrorMessage, toError } from '../../util/getErrorMessage.js';
|
|
7
7
|
import { getSkillsCliAgentDisplayNameById, getSkillsCliAgentSkillsDir, UNIVERSAL_SKILLS_DIR } from '../mcp/editorConfigs.js';
|
|
8
8
|
const skillsDebug = subdebug('skills:setup');
|
|
9
9
|
/** Source repo for the bundled `skills` CLI. See https://www.sanity.io/docs/ai/skills. */ export const SANITY_SKILLS_REPO = 'sanity-io/agent-toolkit';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/skills/setupSkills.ts"],"sourcesContent":["import {fileURLToPath} from 'node:url'\nimport {styleText} from 'node:util'\n\nimport {type Output, subdebug} from '@sanity/cli-core'\nimport {
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/skills/setupSkills.ts"],"sourcesContent":["import {fileURLToPath} from 'node:url'\nimport {styleText} from 'node:util'\n\nimport {type Output, subdebug} from '@sanity/cli-core'\nimport {getErrorMessage, toError} from '@sanity/cli-core/errors'\nimport {logSymbols, spinner} from '@sanity/cli-core/ux'\nimport {execa} from 'execa'\n\nimport {\n getSkillsCliAgentDisplayNameById,\n getSkillsCliAgentSkillsDir,\n UNIVERSAL_SKILLS_DIR,\n} from '../mcp/editorConfigs.js'\n\nconst skillsDebug = subdebug('skills:setup')\n\n/** Source repo for the bundled `skills` CLI. See https://www.sanity.io/docs/ai/skills. */\nexport const SANITY_SKILLS_REPO = 'sanity-io/agent-toolkit'\n\n/** Names of the skills we install — must match the entries in the source repo. */\nexport const SANITY_SKILL_NAMES = ['sanity-best-practices', 'sanity-migration']\n\n/**\n * Absolute path to the bundled `skills` CLI bin. Resolved once at module load\n * via `import.meta.resolve` so we run the version pinned in our package.json\n * instead of paying the `npx -y` registry lookup at runtime.\n */\nexport const SKILLS_BIN_PATH = fileURLToPath(\n import.meta.resolve('skills/bin/cli.mjs', import.meta.url),\n)\n\ninterface SetupSkillsOptions {\n /** Skills-CLI agent IDs (e.g. 'cursor', 'claude-code') to install for. */\n agents: string[]\n\n /**\n * Output to use for user-facing messages, so they go through the calling\n * command rather than directly to stdout/stderr.\n */\n output: Output\n}\n\ninterface SetupSkillsResult {\n /** Deduplicated `--agent` values passed to `skills add`. */\n installedAgents: string[]\n skipped: boolean\n\n error?: Error\n}\n\n/**\n * Prints a short summary beneath the success line: the skills that were\n * installed, grouped by where they live. Universal agents share\n * `~/.agents/skills`, so they're listed together under one header; any agent\n * with its own directory (e.g. Claude Code) is listed separately with its\n * location. Global (`-g`) installs are home-anchored, so locations show `~/`.\n */\nfunction printInstallSummary(agents: string[], output: Output): void {\n const universal: string[] = []\n const additional: {dir: string; name: string}[] = []\n\n for (const agent of agents) {\n const name = getSkillsCliAgentDisplayNameById(agent) ?? agent\n const dir = getSkillsCliAgentSkillsDir(agent)\n if (dir && dir !== UNIVERSAL_SKILLS_DIR) {\n additional.push({dir, name})\n } else {\n universal.push(name)\n }\n }\n\n if (universal.length > 0) {\n output.log('')\n output.log(styleText('dim', ` Universal (~/${UNIVERSAL_SKILLS_DIR})`))\n output.log(styleText('dim', ` ${universal.join(', ')}`))\n }\n\n if (additional.length > 0) {\n output.log('')\n output.log(styleText('dim', ' Additional agents'))\n for (const {dir, name} of additional) {\n output.log(styleText('dim', ` ${name} (~/${dir})`))\n }\n }\n output.log('')\n}\n\n/**\n * Runs the bundled `skills add` globally for the given agents. Failures are\n * surfaced as warnings and never throw — skills install is best-effort and\n * must not abort `sanity init`.\n */\nexport async function setupSkills(options: SetupSkillsOptions): Promise<SetupSkillsResult> {\n const {output} = options\n const uniqueAgents = [...new Set(options.agents)]\n\n if (uniqueAgents.length === 0) {\n skillsDebug('No agents passed — skipping skills install')\n return {installedAgents: [], skipped: true}\n }\n\n const args = [\n SKILLS_BIN_PATH,\n 'add',\n SANITY_SKILLS_REPO,\n '--skill',\n ...SANITY_SKILL_NAMES,\n '-g',\n ...uniqueAgents.flatMap((agent) => ['-a', agent]),\n '-y',\n ]\n\n skillsDebug('Running: %s %s', process.execPath, args.join(' '))\n\n const spin = spinner('Installing Sanity agent skills').start()\n\n try {\n const result = await execa(process.execPath, args, {stdio: 'pipe', timeout: 90_000})\n skillsDebug('skills stdout: %s', result.stdout)\n skillsDebug('skills stderr: %s', result.stderr)\n spin.stop()\n output.log(\n `${logSymbols.success} Sanity agent skills installed: [${SANITY_SKILL_NAMES.join(', ')}]`,\n )\n printInstallSummary(uniqueAgents, output)\n return {installedAgents: uniqueAgents, skipped: false}\n } catch (error) {\n skillsDebug('Error installing skills %O', error)\n const err = toError(error)\n spin.stop()\n output.warn(`Could not install Sanity agent skills: ${getErrorMessage(error)}`)\n if (error && typeof error === 'object') {\n const {stderr, stdout} = error as {stderr?: string; stdout?: string}\n if (stdout) output.warn(stdout)\n if (stderr) output.warn(stderr)\n }\n return {error: err, installedAgents: [], skipped: false}\n }\n}\n"],"names":["fileURLToPath","styleText","subdebug","getErrorMessage","toError","logSymbols","spinner","execa","getSkillsCliAgentDisplayNameById","getSkillsCliAgentSkillsDir","UNIVERSAL_SKILLS_DIR","skillsDebug","SANITY_SKILLS_REPO","SANITY_SKILL_NAMES","SKILLS_BIN_PATH","resolve","url","printInstallSummary","agents","output","universal","additional","agent","name","dir","push","length","log","join","setupSkills","options","uniqueAgents","Set","installedAgents","skipped","args","flatMap","process","execPath","spin","start","result","stdio","timeout","stdout","stderr","stop","success","error","err","warn"],"mappings":"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"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { getCoreAppUrl } from '@sanity/cli-core/util';
|
|
2
|
+
import { deleteUserApplication, getUserApplication } from '../../services/userApplications.js';
|
|
3
|
+
import { getAppId } from '../../util/appId.js';
|
|
4
|
+
import { NO_PROJECT_ID } from '../../util/errorMessages.js';
|
|
5
|
+
export function createAppUndeployAdapter(cliConfig) {
|
|
6
|
+
return {
|
|
7
|
+
async resolveTarget () {
|
|
8
|
+
const appId = getAppId(cliConfig);
|
|
9
|
+
if (!appId) {
|
|
10
|
+
return {
|
|
11
|
+
message: 'No `deployment.appId` configured',
|
|
12
|
+
solution: 'Add `deployment.appId` to sanity.cli.ts',
|
|
13
|
+
type: 'none'
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
const application = await getUserApplication({
|
|
17
|
+
appId,
|
|
18
|
+
isSdkApp: true
|
|
19
|
+
});
|
|
20
|
+
if (!application) {
|
|
21
|
+
return {
|
|
22
|
+
message: 'Application with the given ID does not exist',
|
|
23
|
+
type: 'none'
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
target: toUndeployTarget(application, 'coreApp'),
|
|
28
|
+
type: 'found'
|
|
29
|
+
};
|
|
30
|
+
},
|
|
31
|
+
type: 'coreApp',
|
|
32
|
+
undeploy: ({ id })=>deleteUserApplication({
|
|
33
|
+
applicationId: id,
|
|
34
|
+
appType: 'coreApp'
|
|
35
|
+
})
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
export function createStudioUndeployAdapter(cliConfig) {
|
|
39
|
+
return {
|
|
40
|
+
async resolveTarget () {
|
|
41
|
+
const appId = cliConfig.deployment?.appId;
|
|
42
|
+
const studioHost = cliConfig.studioHost;
|
|
43
|
+
if (!appId && !studioHost) {
|
|
44
|
+
return {
|
|
45
|
+
message: 'No studio hostname configured',
|
|
46
|
+
solution: 'Set `studioHost` in sanity.cli.ts',
|
|
47
|
+
type: 'none'
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
const projectId = cliConfig.api?.projectId;
|
|
51
|
+
if (!projectId) throw new Error(NO_PROJECT_ID);
|
|
52
|
+
const application = await getUserApplication({
|
|
53
|
+
appHost: studioHost,
|
|
54
|
+
appId,
|
|
55
|
+
isSdkApp: false,
|
|
56
|
+
projectId
|
|
57
|
+
});
|
|
58
|
+
if (!application) {
|
|
59
|
+
return {
|
|
60
|
+
message: 'Your project has not been assigned an app ID or a studio hostname, or the `appId` or `studioHost` provided does not exist',
|
|
61
|
+
type: 'none'
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
target: toUndeployTarget(application, 'studio'),
|
|
66
|
+
type: 'found'
|
|
67
|
+
};
|
|
68
|
+
},
|
|
69
|
+
type: 'studio',
|
|
70
|
+
undeploy: ({ id })=>deleteUserApplication({
|
|
71
|
+
applicationId: id,
|
|
72
|
+
appType: 'studio'
|
|
73
|
+
})
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function toUndeployTarget(application, type) {
|
|
77
|
+
return {
|
|
78
|
+
activeDeployment: application.activeDeployment ? {
|
|
79
|
+
deployedAt: application.activeDeployment.deployedAt,
|
|
80
|
+
deployedBy: application.activeDeployment.deployedBy
|
|
81
|
+
} : null,
|
|
82
|
+
appHost: application.appHost ?? null,
|
|
83
|
+
createdAt: application.createdAt ?? null,
|
|
84
|
+
deletes: 'application',
|
|
85
|
+
id: application.id,
|
|
86
|
+
organizationId: application.organizationId ?? null,
|
|
87
|
+
projectId: application.projectId ?? null,
|
|
88
|
+
title: application.title ?? null,
|
|
89
|
+
type,
|
|
90
|
+
url: resolveTargetUrl(application, type)
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
function resolveTargetUrl(application, type) {
|
|
94
|
+
if (type === 'coreApp') {
|
|
95
|
+
return application.organizationId ? getCoreAppUrl(application.organizationId, application.id) : null;
|
|
96
|
+
}
|
|
97
|
+
if (!application.appHost) return null;
|
|
98
|
+
return application.urlType === 'external' ? application.appHost : `https://${application.appHost}.sanity.studio`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
//# sourceMappingURL=adapters.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { styleText } from 'node:util';
|
|
2
|
+
import { CLIError } from '@oclif/core/errors';
|
|
3
|
+
import { subdebug } from '@sanity/cli-core';
|
|
4
|
+
import { getErrorMessage } from '@sanity/cli-core/errors';
|
|
5
|
+
import { confirm, spinner } from '@sanity/cli-core/ux';
|
|
6
|
+
import { createCollectingReporter, createFailFastReporter } from '../../util/checks.js';
|
|
7
|
+
import { toStderrOutput } from '../../util/toStderrOutput.js';
|
|
8
|
+
import { describeUndeployTarget, renderUndeployPlan, undeployLabel, undeployPlanToJson } from './undeployPlan.js';
|
|
9
|
+
const undeployDebug = subdebug('undeploy');
|
|
10
|
+
/**
|
|
11
|
+
* Runs an undeploy in the mode the flags select: a real undeploy fails fast,
|
|
12
|
+
* confirms, and deletes; `--dry-run` drives the same target resolution
|
|
13
|
+
* read-only and renders a plan instead. `--json` emits the same information as
|
|
14
|
+
* machine-readable JSON.
|
|
15
|
+
*/ export async function runUndeploy(options, adapter) {
|
|
16
|
+
const { flags, output } = options;
|
|
17
|
+
const json = !!flags.json;
|
|
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));
|
|
20
|
+
// The JSON payload owns stdout, so the run's progress logs go to stderr; only
|
|
21
|
+
// the final JSON.stringify writes to stdout.
|
|
22
|
+
const runOptions = json ? {
|
|
23
|
+
...options,
|
|
24
|
+
output: toStderrOutput(output)
|
|
25
|
+
} : options;
|
|
26
|
+
try {
|
|
27
|
+
if (flags['dry-run']) {
|
|
28
|
+
const reporter = createCollectingReporter();
|
|
29
|
+
const resolution = await resolveTarget(adapter, reporter);
|
|
30
|
+
const plan = {
|
|
31
|
+
checks: reporter.results,
|
|
32
|
+
reason: resolution?.type === 'none' ? resolution.message : null,
|
|
33
|
+
target: resolution?.type === 'found' ? resolution.target : null,
|
|
34
|
+
type: adapter.type
|
|
35
|
+
};
|
|
36
|
+
if (json) emitJson(undeployPlanToJson(plan));
|
|
37
|
+
else renderUndeployPlan(plan, output);
|
|
38
|
+
// A blocked plan exits like a real (fail-fast) undeploy would.
|
|
39
|
+
const failed = plan.checks.find((check)=>check.status === 'fail');
|
|
40
|
+
if (failed) output.error('Undeploy blocked by failing checks.', {
|
|
41
|
+
exit: failed.exitCode ?? 1
|
|
42
|
+
});
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const result = await undeployApp(runOptions, adapter);
|
|
46
|
+
if (json && result) emitJson(result);
|
|
47
|
+
} catch (error) {
|
|
48
|
+
const failure = normalizeFailure(error, adapter.type);
|
|
49
|
+
// A blocked dry run reaches this catch too (its exit throws) and already
|
|
50
|
+
// printed its plan, so only a real undeploy adds the {undeployed: false} envelope.
|
|
51
|
+
if (json && !flags['dry-run']) {
|
|
52
|
+
emitJson({
|
|
53
|
+
error: {
|
|
54
|
+
message: failure.message
|
|
55
|
+
},
|
|
56
|
+
undeployed: false
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
output.error(failure.message, {
|
|
60
|
+
exit: failure.exit
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/** The real run: resolve the target, confirm, delete, report. */ async function undeployApp(options, adapter) {
|
|
65
|
+
const { flags, output } = options;
|
|
66
|
+
const resolution = await resolveTarget(adapter, createFailFastReporter(output));
|
|
67
|
+
// A resolve failure never lands here: the fail-fast reporter already exited.
|
|
68
|
+
if (!resolution) return undefined;
|
|
69
|
+
if (resolution.type === 'none') {
|
|
70
|
+
output.log(`${resolution.message}.`);
|
|
71
|
+
if (resolution.solution) output.log(`${resolution.solution}.`);
|
|
72
|
+
output.log('Nothing to undeploy.');
|
|
73
|
+
return {
|
|
74
|
+
reason: resolution.message,
|
|
75
|
+
undeployed: false
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
const { target } = resolution;
|
|
79
|
+
if (!flags.yes) {
|
|
80
|
+
const shouldUndeploy = await confirm({
|
|
81
|
+
default: false,
|
|
82
|
+
message: confirmUndeployMessage(target)
|
|
83
|
+
});
|
|
84
|
+
if (!shouldUndeploy) return undefined;
|
|
85
|
+
}
|
|
86
|
+
const label = undeployLabel(target, adapter.type);
|
|
87
|
+
const spin = spinner(`Undeploying ${label}`).start();
|
|
88
|
+
try {
|
|
89
|
+
await adapter.undeploy(target);
|
|
90
|
+
} catch (error) {
|
|
91
|
+
spin.fail();
|
|
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
|
+
});
|
|
97
|
+
}
|
|
98
|
+
spin.succeed();
|
|
99
|
+
printUndeployed(target, output);
|
|
100
|
+
return {
|
|
101
|
+
application: target,
|
|
102
|
+
undeployed: true
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Resolves the undeploy target and reports the verdict as a check, shared by
|
|
107
|
+
* both modes so a real run and a dry run can't diverge.
|
|
108
|
+
*/ async function resolveTarget(adapter, reporter) {
|
|
109
|
+
const spin = spinner('Checking application info').start();
|
|
110
|
+
let resolution;
|
|
111
|
+
try {
|
|
112
|
+
resolution = await adapter.resolveTarget();
|
|
113
|
+
} catch (error) {
|
|
114
|
+
spin.fail();
|
|
115
|
+
undeployDebug('Failed to resolve undeploy target', error);
|
|
116
|
+
reporter.report({
|
|
117
|
+
message: `Failed to resolve undeploy target: ${getErrorMessage(error)}`,
|
|
118
|
+
status: 'fail'
|
|
119
|
+
});
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
spin[resolution.type === 'found' ? 'succeed' : 'fail']();
|
|
123
|
+
reporter.report(describeUndeployTarget(resolution));
|
|
124
|
+
return resolution;
|
|
125
|
+
}
|
|
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
|
+
}
|
|
131
|
+
if (target.type === 'coreApp') {
|
|
132
|
+
return `This will undeploy the following application:
|
|
133
|
+
|
|
134
|
+
Title: ${styleText('yellow', target.title || '(untitled application)')}
|
|
135
|
+
ID: ${styleText('yellow', target.id)}
|
|
136
|
+
|
|
137
|
+
The application will no longer be available for any of your users if you proceed.
|
|
138
|
+
|
|
139
|
+
Are you ${styleText('red', 'sure')} you want to undeploy?`;
|
|
140
|
+
}
|
|
141
|
+
return `This will undeploy ${styleText('yellow', target.url ?? target.id)} and make it unavailable for your users.
|
|
142
|
+
Are you ${styleText('red', 'sure')} you want to undeploy?`;
|
|
143
|
+
}
|
|
144
|
+
function printUndeployed(target, output) {
|
|
145
|
+
if (target.deletes === 'config') {
|
|
146
|
+
output.log(`\n${styleText('bold', 'Config deleted.')}`);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (target.type === 'coreApp') {
|
|
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.`);
|
|
151
|
+
output.log(`\n${styleText('bold', 'Remember to remove `deployment.appId` from your application configuration')} to avoid errors when redeploying.`);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
output.log(`\nStudio undeploy scheduled. It might be a few minutes until ${target.url ?? 'your studio'} is unavailable.`);
|
|
155
|
+
}
|
|
156
|
+
/** The one failure diagnosis both the stderr message and the `--json` envelope read. */ function normalizeFailure(error, type) {
|
|
157
|
+
// Ctrl+C on the confirmation prompt isn't a real failure
|
|
158
|
+
if (error instanceof Error && error.name === 'ExitPromptError') {
|
|
159
|
+
return {
|
|
160
|
+
exit: 1,
|
|
161
|
+
message: 'Undeploy cancelled by user'
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
// A failed check already carries its own message and exit code
|
|
165
|
+
if (error instanceof CLIError) {
|
|
166
|
+
return {
|
|
167
|
+
exit: error.oclif?.exit ?? 1,
|
|
168
|
+
message: error.message
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
const label = type === 'coreApp' ? 'application' : 'studio';
|
|
172
|
+
undeployDebug(`Error undeploying ${label}`, error);
|
|
173
|
+
return {
|
|
174
|
+
exit: 1,
|
|
175
|
+
message: `Error undeploying ${label}: ${getErrorMessage(error)}`
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
//# sourceMappingURL=runUndeploy.js.map
|
|
@@ -0,0 +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 {\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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"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { styleText } from 'node:util';
|
|
2
|
+
import { checkStatusIcon, renderIssues } from '../../util/checks.js';
|
|
3
|
+
export function canUndeploy(plan) {
|
|
4
|
+
return plan.target !== null && plan.checks.every((check)=>check.status !== 'fail');
|
|
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
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* A machine-readable projection of the plan: blocking problems mapped to their
|
|
14
|
+
* fix, warnings as messages. Derived from the same checks and target the human
|
|
15
|
+
* report renders, so the two can't drift.
|
|
16
|
+
*/ export function undeployPlanToJson(plan) {
|
|
17
|
+
const errors = {};
|
|
18
|
+
const warnings = [];
|
|
19
|
+
for (const check of plan.checks){
|
|
20
|
+
if (check.status === 'fail') errors[check.message] = check.solution ?? null;
|
|
21
|
+
else if (check.status === 'warn') warnings.push(check.message);
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
application: plan.target,
|
|
25
|
+
canUndeploy: canUndeploy(plan),
|
|
26
|
+
errors,
|
|
27
|
+
reason: plan.reason,
|
|
28
|
+
warnings
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The single diagnosis for each undeploy-target verdict, shared by the dry-run
|
|
33
|
+
* report and the real run so message and fix can't drift between the two.
|
|
34
|
+
*/ export function describeUndeployTarget(resolution) {
|
|
35
|
+
if (resolution.type === 'none') {
|
|
36
|
+
return {
|
|
37
|
+
message: resolution.message,
|
|
38
|
+
solution: resolution.solution,
|
|
39
|
+
status: 'skip'
|
|
40
|
+
};
|
|
41
|
+
}
|
|
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
|
+
}
|
|
49
|
+
if (target.type === 'studio') {
|
|
50
|
+
return {
|
|
51
|
+
message: `Undeploys studio ${target.url ?? target.id}`,
|
|
52
|
+
status: 'pass'
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
const name = target.title ? `"${target.title}" (${target.id})` : target.id;
|
|
56
|
+
return {
|
|
57
|
+
message: `Undeploys application ${name}`,
|
|
58
|
+
status: 'pass'
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
export function renderUndeployPlan(plan, output) {
|
|
62
|
+
const problems = plan.checks.filter((check)=>check.status === 'fail');
|
|
63
|
+
const warnings = plan.checks.filter((check)=>check.status === 'warn');
|
|
64
|
+
output.log('\nDry run — no changes made.\n');
|
|
65
|
+
// Only pass/skip here; problems and warnings render below with their fixes.
|
|
66
|
+
// A passing target check already says what gets undeployed, so an
|
|
67
|
+
// undeployable plan needs no extra verdict line.
|
|
68
|
+
for (const check of plan.checks){
|
|
69
|
+
if (check.status === 'pass' || check.status === 'skip') {
|
|
70
|
+
const fix = check.solution ? `: ${check.solution}` : '';
|
|
71
|
+
output.log(` ${checkStatusIcon(check.status)} ${check.message}${fix}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (plan.target) renderTarget(plan.target, output);
|
|
75
|
+
if (!canUndeploy(plan)) {
|
|
76
|
+
if (problems.length > 0) {
|
|
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.`));
|
|
79
|
+
} else {
|
|
80
|
+
output.log('\nNothing to undeploy.');
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
renderIssues(output, 'Problems to fix:', problems);
|
|
84
|
+
renderIssues(output, 'Warnings:', warnings);
|
|
85
|
+
}
|
|
86
|
+
function renderTarget(target, output) {
|
|
87
|
+
const rows = [
|
|
88
|
+
[
|
|
89
|
+
'Title',
|
|
90
|
+
target.title
|
|
91
|
+
],
|
|
92
|
+
[
|
|
93
|
+
'ID',
|
|
94
|
+
target.id
|
|
95
|
+
],
|
|
96
|
+
[
|
|
97
|
+
'URL',
|
|
98
|
+
target.url
|
|
99
|
+
],
|
|
100
|
+
[
|
|
101
|
+
'Deployed',
|
|
102
|
+
formatDeployment(target.activeDeployment)
|
|
103
|
+
]
|
|
104
|
+
];
|
|
105
|
+
output.log('');
|
|
106
|
+
for (const [key, value] of rows){
|
|
107
|
+
if (value) output.log(` ${key.padEnd(8)} ${styleText('yellow', value)}`);
|
|
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
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function formatDeployment(deployment) {
|
|
115
|
+
if (!deployment) return null;
|
|
116
|
+
const parts = [
|
|
117
|
+
deployment.deployedAt ? `at ${deployment.deployedAt}` : null,
|
|
118
|
+
deployment.deployedBy ? `by ${deployment.deployedBy}` : null
|
|
119
|
+
].filter(Boolean);
|
|
120
|
+
return parts.length > 0 ? parts.join(' ') : null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
//# sourceMappingURL=undeployPlan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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":"AAAA,SAAQA,SAAS,QAAO,YAAW;AAKnC,SAAoBC,eAAe,EAAEC,YAAY,QAAO,uBAAsB;AAY9E,OAAO,SAASC,YAAYC,IAAkB;IAC5C,OAAOA,KAAKC,MAAM,KAAK,QAAQD,KAAKE,MAAM,CAACC,KAAK,CAAC,CAACC,QAAUA,MAAMC,MAAM,KAAK;AAC/E;AAEA,4DAA4D,GAC5D,OAAO,SAASC,cAAcL,MAA6B,EAAEM,IAA0B;IACrF,IAAIN,QAAQO,YAAY,UAAU;QAChC,OAAO,CAAC,mBAAmB,EAAEP,OAAOQ,KAAK,GAAG,CAAC,MAAM,EAAER,OAAOQ,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI;IAC7E;IACA,OAAOF,SAAS,YAAY,gBAAgB;AAC9C;AAEA;;;;CAIC,GACD,OAAO,SAASG,mBAAmBV,IAAkB;IAOnD,MAAMW,SAAwC,CAAC;IAC/C,MAAMC,WAAqB,EAAE;IAC7B,KAAK,MAAMR,SAASJ,KAAKE,MAAM,CAAE;QAC/B,IAAIE,MAAMC,MAAM,KAAK,QAAQM,MAAM,CAACP,MAAMS,OAAO,CAAC,GAAGT,MAAMU,QAAQ,IAAI;aAClE,IAAIV,MAAMC,MAAM,KAAK,QAAQO,SAASG,IAAI,CAACX,MAAMS,OAAO;IAC/D;IAEA,OAAO;QACLG,aAAahB,KAAKC,MAAM;QACxBF,aAAaA,YAAYC;QACzBW;QACAM,QAAQjB,KAAKiB,MAAM;QACnBL;IACF;AACF;AAEA;;;CAGC,GACD,OAAO,SAASM,uBAAuBC,UAAoC;IACzE,IAAIA,WAAWZ,IAAI,KAAK,QAAQ;QAC9B,OAAO;YAACM,SAASM,WAAWN,OAAO;YAAEC,UAAUK,WAAWL,QAAQ;YAAET,QAAQ;QAAM;IACpF;IAEA,MAAM,EAACJ,MAAM,EAAC,GAAGkB;IACjB,IAAIlB,OAAOO,OAAO,KAAK,UAAU;QAC/B,OAAO;YACLK,SAASZ,OAAOQ,KAAK,GACjB,CAAC,uCAAuC,EAAER,OAAOQ,KAAK,CAAC,CAAC,CAAC,GACzD;YACJJ,QAAQ;QACV;IACF;IACA,IAAIJ,OAAOM,IAAI,KAAK,UAAU;QAC5B,OAAO;YAACM,SAAS,CAAC,iBAAiB,EAAEZ,OAAOmB,GAAG,IAAInB,OAAOoB,EAAE,EAAE;YAAEhB,QAAQ;QAAM;IAChF;IACA,MAAMiB,OAAOrB,OAAOQ,KAAK,GAAG,CAAC,CAAC,EAAER,OAAOQ,KAAK,CAAC,GAAG,EAAER,OAAOoB,EAAE,CAAC,CAAC,CAAC,GAAGpB,OAAOoB,EAAE;IAC1E,OAAO;QAACR,SAAS,CAAC,sBAAsB,EAAES,MAAM;QAAEjB,QAAQ;IAAM;AAClE;AAEA,OAAO,SAASkB,mBAAmBvB,IAAkB,EAAEwB,MAAc;IACnE,MAAMC,WAAWzB,KAAKE,MAAM,CAACwB,MAAM,CAAC,CAACtB,QAAUA,MAAMC,MAAM,KAAK;IAChE,MAAMO,WAAWZ,KAAKE,MAAM,CAACwB,MAAM,CAAC,CAACtB,QAAUA,MAAMC,MAAM,KAAK;IAEhEmB,OAAOG,GAAG,CAAC;IAEX,4EAA4E;IAC5E,kEAAkE;IAClE,iDAAiD;IACjD,KAAK,MAAMvB,SAASJ,KAAKE,MAAM,CAAE;QAC/B,IAAIE,MAAMC,MAAM,KAAK,UAAUD,MAAMC,MAAM,KAAK,QAAQ;YACtD,MAAMuB,MAAMxB,MAAMU,QAAQ,GAAG,CAAC,EAAE,EAAEV,MAAMU,QAAQ,EAAE,GAAG;YACrDU,OAAOG,GAAG,CAAC,CAAC,EAAE,EAAE9B,gBAAgBO,MAAMC,MAAM,EAAE,CAAC,EAAED,MAAMS,OAAO,GAAGe,KAAK;QACxE;IACF;IAEA,IAAI5B,KAAKC,MAAM,EAAE4B,aAAa7B,KAAKC,MAAM,EAAEuB;IAE3C,IAAI,CAACzB,YAAYC,OAAO;QACtB,IAAIyB,SAASK,MAAM,GAAG,GAAG;YACvB,MAAMC,QAAQzB,cAAcN,KAAKC,MAAM,EAAED,KAAKO,IAAI;YAClDiB,OAAOG,GAAG,CACR/B,UACE,OACA,CAAC,EAAE,EAAEC,gBAAgB,QAAQ,CAAC,EAAEkC,KAAK,CAAC,EAAE,CAACC,WAAW,KAAKD,MAAME,KAAK,CAAC,GAAG,uBAAuB,CAAC;QAGtG,OAAO;YACLT,OAAOG,GAAG,CAAC;QACb;IACF;IAEA7B,aAAa0B,QAAQ,oBAAoBC;IACzC3B,aAAa0B,QAAQ,aAAaZ;AACpC;AAEA,SAASiB,aAAa5B,MAAsB,EAAEuB,MAAc;IAC1D,MAAMU,OAAkC;QACtC;YAAC;YAASjC,OAAOQ,KAAK;SAAC;QACvB;YAAC;YAAMR,OAAOoB,EAAE;SAAC;QACjB;YAAC;YAAOpB,OAAOmB,GAAG;SAAC;QACnB;YAAC;YAAYe,iBAAiBlC,OAAOmC,gBAAgB;SAAE;KACxD;IAEDZ,OAAOG,GAAG,CAAC;IACX,KAAK,MAAM,CAACU,KAAKC,MAAM,IAAIJ,KAAM;QAC/B,IAAII,OAAOd,OAAOG,GAAG,CAAC,CAAC,IAAI,EAAEU,IAAIE,MAAM,CAAC,GAAG,CAAC,EAAE3C,UAAU,UAAU0C,QAAQ;IAC5E;IAEA,mFAAmF;IACnF,KAAK,MAAME,SAASvC,OAAOwC,OAAO,IAAI,EAAE,CAAE;QACxC,KAAK,MAAMC,QAAQF,MAAMG,KAAK,CAAC,MAAOnB,OAAOG,GAAG,CAAC,CAAC,IAAI,EAAEe,MAAM;IAChE;AACF;AAEA,SAASP,iBAAiBS,UAA8C;IACtE,IAAI,CAACA,YAAY,OAAO;IACxB,MAAMC,QAAQ;QACZD,WAAWE,UAAU,GAAG,CAAC,GAAG,EAAEF,WAAWE,UAAU,EAAE,GAAG;QACxDF,WAAWG,UAAU,GAAG,CAAC,GAAG,EAAEH,WAAWG,UAAU,EAAE,GAAG;KACzD,CAACrB,MAAM,CAACsB;IACT,OAAOH,MAAMf,MAAM,GAAG,IAAIe,MAAMI,IAAI,CAAC,OAAO;AAC9C"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { styleText } from 'node:util';
|
|
2
2
|
import { Args, Flags } from '@oclif/core';
|
|
3
3
|
import { exit } from '@oclif/core/errors';
|
|
4
|
-
import {
|
|
4
|
+
import { subdebug } from '@sanity/cli-core/debug';
|
|
5
|
+
import { SanityCommand } from '@sanity/cli-core/SanityCommand';
|
|
5
6
|
import { spinner } from '@sanity/cli-core/ux';
|
|
6
7
|
import { Table } from 'console-table-printer';
|
|
7
8
|
import { formatDistance } from 'date-fns/formatDistance';
|