@sanity/cli 8.22.1 → 8.23.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/dist/actions/context/watchJob.js +9 -3
- package/dist/actions/context/watchJob.js.map +1 -1
- package/dist/actions/deploy/blueprintManaged.js +59 -0
- package/dist/actions/deploy/blueprintManaged.js.map +1 -0
- package/dist/actions/deploy/deployApp.js +117 -1
- package/dist/actions/deploy/deployApp.js.map +1 -1
- package/dist/actions/deploy/deployRunner.js.map +1 -1
- package/dist/actions/deploy/deployStudio.js +6 -31
- package/dist/actions/deploy/deployStudio.js.map +1 -1
- package/dist/actions/exec/runScript.worker.js +6 -2
- package/dist/actions/exec/runScript.worker.js.map +1 -1
- package/dist/actions/init/bootstrapLocalTemplate.js +35 -11
- package/dist/actions/init/bootstrapLocalTemplate.js.map +1 -1
- package/dist/actions/init/createMediaLibraryCliConfig.js +12 -0
- package/dist/actions/init/createMediaLibraryCliConfig.js.map +1 -0
- package/dist/actions/init/determineMediaLibraryTemplate.js +14 -0
- package/dist/actions/init/determineMediaLibraryTemplate.js.map +1 -0
- package/dist/actions/init/initAction.js +35 -6
- package/dist/actions/init/initAction.js.map +1 -1
- package/dist/actions/init/initApp.js +81 -6
- package/dist/actions/init/initApp.js.map +1 -1
- package/dist/actions/init/offerAspectPull.js +81 -0
- package/dist/actions/init/offerAspectPull.js.map +1 -0
- package/dist/actions/init/project/getProjectDetails.js +25 -2
- package/dist/actions/init/project/getProjectDetails.js.map +1 -1
- package/dist/actions/init/templates/index.js +3 -0
- package/dist/actions/init/templates/index.js.map +1 -1
- package/dist/actions/init/templates/mediaLibraryConfig.js +51 -0
- package/dist/actions/init/templates/mediaLibraryConfig.js.map +1 -0
- package/dist/actions/media/aspectName.js +8 -0
- package/dist/actions/media/aspectName.js.map +1 -0
- package/dist/actions/media/aspectSync/contract.js +163 -0
- package/dist/actions/media/aspectSync/contract.js.map +1 -0
- package/dist/actions/media/aspectSync/syncAspects.js +115 -0
- package/dist/actions/media/aspectSync/syncAspects.js.map +1 -0
- package/dist/actions/media/cliConfigFields.js +192 -0
- package/dist/actions/media/cliConfigFields.js.map +1 -0
- package/dist/actions/media/pullAspects.js +225 -0
- package/dist/actions/media/pullAspects.js.map +1 -0
- package/dist/actions/media/resolveMediaLibraryId.js +83 -0
- package/dist/actions/media/resolveMediaLibraryId.js.map +1 -0
- package/dist/commands/context/build.js +2 -2
- package/dist/commands/context/build.js.map +1 -1
- package/dist/commands/context/entries/get.js +83 -0
- package/dist/commands/context/entries/get.js.map +1 -0
- package/dist/commands/context/entries/list.js +82 -0
- package/dist/commands/context/entries/list.js.map +1 -0
- package/dist/commands/context/entries/rebuild.js +95 -0
- package/dist/commands/context/entries/rebuild.js.map +1 -0
- package/dist/commands/context/issues/apply.js +86 -0
- package/dist/commands/context/issues/apply.js.map +1 -0
- package/dist/commands/context/issues/dismiss.js +52 -0
- package/dist/commands/context/issues/dismiss.js.map +1 -0
- package/dist/commands/context/issues/get.js +105 -0
- package/dist/commands/context/issues/get.js.map +1 -0
- package/dist/commands/context/issues/list.js +128 -0
- package/dist/commands/context/issues/list.js.map +1 -0
- package/dist/commands/context/issues/reopen.js +53 -0
- package/dist/commands/context/issues/reopen.js.map +1 -0
- package/dist/commands/context/issues/resolve.js +137 -0
- package/dist/commands/context/issues/resolve.js.map +1 -0
- package/dist/commands/context/jobs/get.js +2 -2
- package/dist/commands/context/jobs/get.js.map +1 -1
- package/dist/commands/context/query.js +92 -0
- package/dist/commands/context/query.js.map +1 -0
- package/dist/commands/deploy.js +13 -0
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/media/prune-aspect-data.js +170 -0
- package/dist/commands/media/prune-aspect-data.js.map +1 -0
- package/dist/commands/media/pull-aspects.js +309 -0
- package/dist/commands/media/pull-aspects.js.map +1 -0
- package/dist/exports/invokeSanityCli/commandPolicies/mcpPolicy.js +17 -0
- package/dist/exports/invokeSanityCli/commandPolicies/mcpPolicy.js.map +1 -1
- package/dist/services/context.js +96 -3
- package/dist/services/context.js.map +1 -1
- package/dist/services/mediaLibraries.js +175 -8
- package/dist/services/mediaLibraries.js.map +1 -1
- package/oclif.manifest.json +2930 -2241
- package/package.json +6 -5
- package/templates/media-library-config/README.md +52 -0
- package/templates/media-library-config/eslint.config.txt +52 -0
- package/templates/media-library-config/src/fields/caption.tsx +29 -0
- package/templates/media-library-config/src/fields/notes.tsx +54 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import { exitCodes, SanityCommand, subdebug } from '@sanity/cli-core';
|
|
3
|
+
import { getErrorMessage } from '@sanity/cli-core/errors';
|
|
4
|
+
import { requiredWhenUnattended } from '@sanity/cli-core/flags';
|
|
5
|
+
import { select } from '@sanity/cli-core/ux';
|
|
6
|
+
import { isHttpError } from '@sanity/client';
|
|
7
|
+
import { formatJobFailure, watchJob } from '../../../actions/context/watchJob.js';
|
|
8
|
+
import { formatKeyValue } from '../../../actions/debug/output.js';
|
|
9
|
+
import { getIssue, resolveIssue } from '../../../services/context.js';
|
|
10
|
+
const resolveIssueDebug = subdebug('context:issues:resolve');
|
|
11
|
+
export class ResolveIssueCommand extends SanityCommand {
|
|
12
|
+
static args = {
|
|
13
|
+
knowledgeBaseId: Args.string({
|
|
14
|
+
description: 'Knowledge base ID',
|
|
15
|
+
required: true
|
|
16
|
+
}),
|
|
17
|
+
// eslint-disable-next-line perfectionist/sort-objects -- positional order: knowledge base first
|
|
18
|
+
issueId: Args.string({
|
|
19
|
+
description: 'Issue ID',
|
|
20
|
+
required: true
|
|
21
|
+
})
|
|
22
|
+
};
|
|
23
|
+
static description = 'Resolve a conflict issue by choosing the correct side\n\nResolving also creates a standing instruction that future builds follow. `sanity context issues reopen` removes it.';
|
|
24
|
+
static examples = [
|
|
25
|
+
{
|
|
26
|
+
command: '<%= config.bin %> <%= command.id %> kb-abc123 issue.def456',
|
|
27
|
+
description: 'Interactively choose the correct side of a conflict'
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
command: '<%= config.bin %> <%= command.id %> kb-abc123 issue.def456 --side 1 --watch',
|
|
31
|
+
description: 'Choose side 1 and wait for the entry to be rewritten'
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
command: '<%= config.bin %> <%= command.id %> kb-abc123 issue.def456 --side 0',
|
|
35
|
+
description: 'Keep the entry as it is (for a conflict on a single entry)'
|
|
36
|
+
}
|
|
37
|
+
];
|
|
38
|
+
static flags = {
|
|
39
|
+
side: requiredWhenUnattended(Flags.integer({
|
|
40
|
+
description: 'Index of the correct side, as listed by `sanity context issues get`',
|
|
41
|
+
min: 0
|
|
42
|
+
})),
|
|
43
|
+
watch: Flags.boolean({
|
|
44
|
+
default: false,
|
|
45
|
+
description: 'Wait for the entry rewrite to finish, exiting non-zero if it fails'
|
|
46
|
+
})
|
|
47
|
+
};
|
|
48
|
+
async run() {
|
|
49
|
+
const { issueId, knowledgeBaseId } = this.args;
|
|
50
|
+
const { side: sideFlag, watch } = this.flags;
|
|
51
|
+
const side = sideFlag ?? await this.promptForSide(knowledgeBaseId, issueId);
|
|
52
|
+
let resolved;
|
|
53
|
+
try {
|
|
54
|
+
resolved = await resolveIssue(knowledgeBaseId, issueId, side);
|
|
55
|
+
} catch (error) {
|
|
56
|
+
resolveIssueDebug('Error resolving issue', error);
|
|
57
|
+
if (isHttpError(error) && error.statusCode === 404) {
|
|
58
|
+
this.error(`Issue "${issueId}" not found on knowledge base "${knowledgeBaseId}"`, {
|
|
59
|
+
exit: exitCodes.RUNTIME_ERROR
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
this.error(`Failed to resolve issue: ${getErrorMessage(error)}`, {
|
|
63
|
+
exit: exitCodes.RUNTIME_ERROR
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
this.log('Issue resolved');
|
|
67
|
+
const { jobId } = resolved;
|
|
68
|
+
if (jobId === null) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
this.log(formatKeyValue('Job ID', jobId));
|
|
72
|
+
this.log(`Track it with: sanity context jobs get ${knowledgeBaseId} ${jobId}`);
|
|
73
|
+
if (!watch) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
let job;
|
|
77
|
+
try {
|
|
78
|
+
job = await watchJob(knowledgeBaseId, jobId, {
|
|
79
|
+
label: 'Rewriting entry',
|
|
80
|
+
successText: 'Entry rewritten'
|
|
81
|
+
});
|
|
82
|
+
} catch (error) {
|
|
83
|
+
resolveIssueDebug('Error watching rewrite job', error);
|
|
84
|
+
this.error(`Failed to watch rewrite job: ${getErrorMessage(error)}`, {
|
|
85
|
+
exit: exitCodes.RUNTIME_ERROR
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
if (job.status !== 'succeeded') {
|
|
89
|
+
this.error(formatJobFailure('Rewrite', job), {
|
|
90
|
+
exit: exitCodes.RUNTIME_ERROR
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
async promptForSide(knowledgeBaseId, issueId) {
|
|
95
|
+
let issue;
|
|
96
|
+
try {
|
|
97
|
+
issue = await getIssue(knowledgeBaseId, issueId);
|
|
98
|
+
} catch (error) {
|
|
99
|
+
resolveIssueDebug('Error getting issue', error);
|
|
100
|
+
if (isHttpError(error) && error.statusCode === 404) {
|
|
101
|
+
this.error(`Knowledge base "${knowledgeBaseId}" not found`, {
|
|
102
|
+
exit: exitCodes.RUNTIME_ERROR
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
this.error(`Failed to get issue: ${getErrorMessage(error)}`, {
|
|
106
|
+
exit: exitCodes.RUNTIME_ERROR
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
if (!issue) {
|
|
110
|
+
this.error(`Issue "${issueId}" not found on knowledge base "${knowledgeBaseId}"`, {
|
|
111
|
+
exit: exitCodes.RUNTIME_ERROR
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
if (issue.status === 'rejected') {
|
|
115
|
+
this.error(`Issue "${issueId}" is rejected (dismissed) and cannot be resolved`, {
|
|
116
|
+
exit: exitCodes.RUNTIME_ERROR
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
const { content } = issue;
|
|
120
|
+
if (content.kind !== 'conflict') {
|
|
121
|
+
this.error(`Only conflict issues can be resolved, "${issueId}" is of kind ${content.kind}`, {
|
|
122
|
+
exit: exitCodes.RUNTIME_ERROR
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
return select({
|
|
126
|
+
choices: content.sides.map((side, index)=>({
|
|
127
|
+
name: `[${index}] ${side.claim}`,
|
|
128
|
+
value: index
|
|
129
|
+
})),
|
|
130
|
+
// Re-resolving keeps the current choice preselected, so Enter can't flip it
|
|
131
|
+
default: issue.resolution ?? content.suggested,
|
|
132
|
+
message: 'Which side is correct?'
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
//# sourceMappingURL=resolve.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/commands/context/issues/resolve.ts"],"sourcesContent":["import {Args, Flags} from '@oclif/core'\nimport {type FlagInput} from '@oclif/core/interfaces'\nimport {exitCodes, SanityCommand, subdebug} from '@sanity/cli-core'\nimport {getErrorMessage} from '@sanity/cli-core/errors'\nimport {requiredWhenUnattended} from '@sanity/cli-core/flags'\nimport {select} from '@sanity/cli-core/ux'\nimport {isHttpError} from '@sanity/client'\n\nimport {formatJobFailure, watchJob} from '../../../actions/context/watchJob.js'\nimport {formatKeyValue} from '../../../actions/debug/output.js'\nimport {getIssue, resolveIssue} from '../../../services/context.js'\n\nconst resolveIssueDebug = subdebug('context:issues:resolve')\n\nexport class ResolveIssueCommand extends SanityCommand<typeof ResolveIssueCommand> {\n static override args = {\n knowledgeBaseId: Args.string({\n description: 'Knowledge base ID',\n required: true,\n }),\n // eslint-disable-next-line perfectionist/sort-objects -- positional order: knowledge base first\n issueId: Args.string({\n description: 'Issue ID',\n required: true,\n }),\n }\n\n static override description =\n 'Resolve a conflict issue by choosing the correct side\\n\\nResolving also creates a standing instruction that future builds follow. `sanity context issues reopen` removes it.'\n\n static override examples = [\n {\n command: '<%= config.bin %> <%= command.id %> kb-abc123 issue.def456',\n description: 'Interactively choose the correct side of a conflict',\n },\n {\n command: '<%= config.bin %> <%= command.id %> kb-abc123 issue.def456 --side 1 --watch',\n description: 'Choose side 1 and wait for the entry to be rewritten',\n },\n {\n command: '<%= config.bin %> <%= command.id %> kb-abc123 issue.def456 --side 0',\n description: 'Keep the entry as it is (for a conflict on a single entry)',\n },\n ]\n\n static override flags = {\n side: requiredWhenUnattended(\n Flags.integer({\n description: 'Index of the correct side, as listed by `sanity context issues get`',\n min: 0,\n }),\n ),\n watch: Flags.boolean({\n default: false,\n description: 'Wait for the entry rewrite to finish, exiting non-zero if it fails',\n }),\n } satisfies FlagInput\n\n public async run(): Promise<void> {\n const {issueId, knowledgeBaseId} = this.args\n const {side: sideFlag, watch} = this.flags\n\n const side = sideFlag ?? (await this.promptForSide(knowledgeBaseId, issueId))\n\n let resolved\n try {\n resolved = await resolveIssue(knowledgeBaseId, issueId, side)\n } catch (error) {\n resolveIssueDebug('Error resolving issue', error)\n if (isHttpError(error) && error.statusCode === 404) {\n this.error(`Issue \"${issueId}\" not found on knowledge base \"${knowledgeBaseId}\"`, {\n exit: exitCodes.RUNTIME_ERROR,\n })\n }\n this.error(`Failed to resolve issue: ${getErrorMessage(error)}`, {\n exit: exitCodes.RUNTIME_ERROR,\n })\n }\n\n this.log('Issue resolved')\n\n const {jobId} = resolved\n if (jobId === null) {\n return\n }\n\n this.log(formatKeyValue('Job ID', jobId))\n this.log(`Track it with: sanity context jobs get ${knowledgeBaseId} ${jobId}`)\n\n if (!watch) {\n return\n }\n\n let job\n try {\n job = await watchJob(knowledgeBaseId, jobId, {\n label: 'Rewriting entry',\n successText: 'Entry rewritten',\n })\n } catch (error) {\n resolveIssueDebug('Error watching rewrite job', error)\n this.error(`Failed to watch rewrite job: ${getErrorMessage(error)}`, {\n exit: exitCodes.RUNTIME_ERROR,\n })\n }\n\n if (job.status !== 'succeeded') {\n this.error(formatJobFailure('Rewrite', job), {\n exit: exitCodes.RUNTIME_ERROR,\n })\n }\n }\n\n private async promptForSide(knowledgeBaseId: string, issueId: string): Promise<number> {\n let issue\n try {\n issue = await getIssue(knowledgeBaseId, issueId)\n } catch (error) {\n resolveIssueDebug('Error getting issue', error)\n if (isHttpError(error) && error.statusCode === 404) {\n this.error(`Knowledge base \"${knowledgeBaseId}\" not found`, {\n exit: exitCodes.RUNTIME_ERROR,\n })\n }\n this.error(`Failed to get issue: ${getErrorMessage(error)}`, {\n exit: exitCodes.RUNTIME_ERROR,\n })\n }\n\n if (!issue) {\n this.error(`Issue \"${issueId}\" not found on knowledge base \"${knowledgeBaseId}\"`, {\n exit: exitCodes.RUNTIME_ERROR,\n })\n }\n\n if (issue.status === 'rejected') {\n this.error(`Issue \"${issueId}\" is rejected (dismissed) and cannot be resolved`, {\n exit: exitCodes.RUNTIME_ERROR,\n })\n }\n\n const {content} = issue\n if (content.kind !== 'conflict') {\n this.error(`Only conflict issues can be resolved, \"${issueId}\" is of kind ${content.kind}`, {\n exit: exitCodes.RUNTIME_ERROR,\n })\n }\n\n return select({\n choices: content.sides.map((side, index) => ({\n name: `[${index}] ${side.claim}`,\n value: index,\n })),\n // Re-resolving keeps the current choice preselected, so Enter can't flip it\n default: issue.resolution ?? content.suggested,\n message: 'Which side is correct?',\n })\n }\n}\n"],"names":["Args","Flags","exitCodes","SanityCommand","subdebug","getErrorMessage","requiredWhenUnattended","select","isHttpError","formatJobFailure","watchJob","formatKeyValue","getIssue","resolveIssue","resolveIssueDebug","ResolveIssueCommand","args","knowledgeBaseId","string","description","required","issueId","examples","command","flags","side","integer","min","watch","boolean","default","run","sideFlag","promptForSide","resolved","error","statusCode","exit","RUNTIME_ERROR","log","jobId","job","label","successText","status","issue","content","kind","choices","sides","map","index","name","claim","value","resolution","suggested","message"],"mappings":"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"}
|
|
@@ -2,7 +2,7 @@ import { Args, Flags } from '@oclif/core';
|
|
|
2
2
|
import { exitCodes, SanityCommand, subdebug } from '@sanity/cli-core';
|
|
3
3
|
import { getErrorMessage, JsonOutputError } from '@sanity/cli-core/errors';
|
|
4
4
|
import { isHttpError } from '@sanity/client';
|
|
5
|
-
import { watchJob } from '../../../actions/context/watchJob.js';
|
|
5
|
+
import { formatJobFailure, watchJob } from '../../../actions/context/watchJob.js';
|
|
6
6
|
import { formatKeyValue } from '../../../actions/debug/output.js';
|
|
7
7
|
import { getJob } from '../../../services/context.js';
|
|
8
8
|
const getJobDebug = subdebug('context:jobs:get');
|
|
@@ -56,7 +56,7 @@ export class GetJobCommand extends SanityCommand {
|
|
|
56
56
|
});
|
|
57
57
|
}
|
|
58
58
|
const jobFailed = watch && job.status !== 'succeeded';
|
|
59
|
-
const failureMessage =
|
|
59
|
+
const failureMessage = formatJobFailure('Job', job);
|
|
60
60
|
if (json) {
|
|
61
61
|
if (jobFailed) throw new JsonOutputError(failureMessage, job, exitCodes.RUNTIME_ERROR);
|
|
62
62
|
this.logJson(job);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/commands/context/jobs/get.ts"],"sourcesContent":["import {Args, Flags} from '@oclif/core'\nimport {type FlagInput} from '@oclif/core/interfaces'\nimport {exitCodes, SanityCommand, subdebug} from '@sanity/cli-core'\nimport {getErrorMessage, JsonOutputError} from '@sanity/cli-core/errors'\nimport {isHttpError} from '@sanity/client'\n\nimport {watchJob} from '../../../actions/context/watchJob.js'\nimport {formatKeyValue} from '../../../actions/debug/output.js'\nimport {getJob} from '../../../services/context.js'\n\nconst getJobDebug = subdebug('context:jobs:get')\n\nexport class GetJobCommand extends SanityCommand<typeof GetJobCommand> {\n static override args = {\n knowledgeBaseId: Args.string({\n description: 'Knowledge base ID',\n required: true,\n }),\n // eslint-disable-next-line perfectionist/sort-objects -- positional order: knowledge base first\n jobId: Args.string({\n description: 'Job ID',\n required: true,\n }),\n }\n\n static override description = 'Get the status of a knowledge base job'\n\n static override enableJsonFlag = true\n\n static override examples = [\n {\n command: '<%= config.bin %> <%= command.id %> kb-abc123 job-def456',\n description: 'Get the current status of a job',\n },\n {\n command: '<%= config.bin %> <%= command.id %> kb-abc123 job-def456 --watch',\n description: 'Wait for the job to finish (non-zero exit if it does not succeed)',\n },\n ]\n\n static override flags = {\n watch: Flags.boolean({\n default: false,\n description: 'Poll until the job reaches a terminal state',\n }),\n } satisfies FlagInput\n\n public async run(): Promise<void> {\n const {jobId, knowledgeBaseId} = this.args\n const {json, watch} = this.flags\n\n let job\n try {\n job = watch\n ? await watchJob(knowledgeBaseId, jobId, {label: 'Waiting for job'})\n : await getJob(knowledgeBaseId, jobId)\n } catch (error) {\n getJobDebug('Error getting job', error)\n if (isHttpError(error) && error.statusCode === 404) {\n this.error(`Job \"${jobId}\" not found on knowledge base \"${knowledgeBaseId}\"`, {\n exit: exitCodes.RUNTIME_ERROR,\n })\n }\n this.error(`Failed to get job: ${getErrorMessage(error)}`, {\n exit: exitCodes.RUNTIME_ERROR,\n })\n }\n\n const jobFailed = watch && job.status !== 'succeeded'\n const failureMessage =
|
|
1
|
+
{"version":3,"sources":["../../../../src/commands/context/jobs/get.ts"],"sourcesContent":["import {Args, Flags} from '@oclif/core'\nimport {type FlagInput} from '@oclif/core/interfaces'\nimport {exitCodes, SanityCommand, subdebug} from '@sanity/cli-core'\nimport {getErrorMessage, JsonOutputError} from '@sanity/cli-core/errors'\nimport {isHttpError} from '@sanity/client'\n\nimport {formatJobFailure, watchJob} from '../../../actions/context/watchJob.js'\nimport {formatKeyValue} from '../../../actions/debug/output.js'\nimport {getJob} from '../../../services/context.js'\n\nconst getJobDebug = subdebug('context:jobs:get')\n\nexport class GetJobCommand extends SanityCommand<typeof GetJobCommand> {\n static override args = {\n knowledgeBaseId: Args.string({\n description: 'Knowledge base ID',\n required: true,\n }),\n // eslint-disable-next-line perfectionist/sort-objects -- positional order: knowledge base first\n jobId: Args.string({\n description: 'Job ID',\n required: true,\n }),\n }\n\n static override description = 'Get the status of a knowledge base job'\n\n static override enableJsonFlag = true\n\n static override examples = [\n {\n command: '<%= config.bin %> <%= command.id %> kb-abc123 job-def456',\n description: 'Get the current status of a job',\n },\n {\n command: '<%= config.bin %> <%= command.id %> kb-abc123 job-def456 --watch',\n description: 'Wait for the job to finish (non-zero exit if it does not succeed)',\n },\n ]\n\n static override flags = {\n watch: Flags.boolean({\n default: false,\n description: 'Poll until the job reaches a terminal state',\n }),\n } satisfies FlagInput\n\n public async run(): Promise<void> {\n const {jobId, knowledgeBaseId} = this.args\n const {json, watch} = this.flags\n\n let job\n try {\n job = watch\n ? await watchJob(knowledgeBaseId, jobId, {label: 'Waiting for job'})\n : await getJob(knowledgeBaseId, jobId)\n } catch (error) {\n getJobDebug('Error getting job', error)\n if (isHttpError(error) && error.statusCode === 404) {\n this.error(`Job \"${jobId}\" not found on knowledge base \"${knowledgeBaseId}\"`, {\n exit: exitCodes.RUNTIME_ERROR,\n })\n }\n this.error(`Failed to get job: ${getErrorMessage(error)}`, {\n exit: exitCodes.RUNTIME_ERROR,\n })\n }\n\n const jobFailed = watch && job.status !== 'succeeded'\n const failureMessage = formatJobFailure('Job', job)\n if (json) {\n if (jobFailed) throw new JsonOutputError(failureMessage, job, exitCodes.RUNTIME_ERROR)\n this.logJson(job)\n } else {\n const padTo = 9 // \"Completed\" is the longest key\n this.log(formatKeyValue('ID', job.id, {padTo}))\n this.log(formatKeyValue('Status', job.status, {padTo}))\n this.log(formatKeyValue('Started', job.startedAt ?? '-', {padTo}))\n this.log(formatKeyValue('Completed', job.completedAt ?? '-', {padTo}))\n this.log(formatKeyValue('Error', job.error ?? '-', {padTo}))\n }\n\n if (jobFailed) {\n this.error(failureMessage, {\n exit: exitCodes.RUNTIME_ERROR,\n })\n }\n }\n}\n"],"names":["Args","Flags","exitCodes","SanityCommand","subdebug","getErrorMessage","JsonOutputError","isHttpError","formatJobFailure","watchJob","formatKeyValue","getJob","getJobDebug","GetJobCommand","args","knowledgeBaseId","string","description","required","jobId","enableJsonFlag","examples","command","flags","watch","boolean","default","run","json","job","label","error","statusCode","exit","RUNTIME_ERROR","jobFailed","status","failureMessage","logJson","padTo","log","id","startedAt","completedAt"],"mappings":"AAAA,SAAQA,IAAI,EAAEC,KAAK,QAAO,cAAa;AAEvC,SAAQC,SAAS,EAAEC,aAAa,EAAEC,QAAQ,QAAO,mBAAkB;AACnE,SAAQC,eAAe,EAAEC,eAAe,QAAO,0BAAyB;AACxE,SAAQC,WAAW,QAAO,iBAAgB;AAE1C,SAAQC,gBAAgB,EAAEC,QAAQ,QAAO,uCAAsC;AAC/E,SAAQC,cAAc,QAAO,mCAAkC;AAC/D,SAAQC,MAAM,QAAO,+BAA8B;AAEnD,MAAMC,cAAcR,SAAS;AAE7B,OAAO,MAAMS,sBAAsBV;IACjC,OAAgBW,OAAO;QACrBC,iBAAiBf,KAAKgB,MAAM,CAAC;YAC3BC,aAAa;YACbC,UAAU;QACZ;QACA,gGAAgG;QAChGC,OAAOnB,KAAKgB,MAAM,CAAC;YACjBC,aAAa;YACbC,UAAU;QACZ;IACF,EAAC;IAED,OAAgBD,cAAc,yCAAwC;IAEtE,OAAgBG,iBAAiB,KAAI;IAErC,OAAgBC,WAAW;QACzB;YACEC,SAAS;YACTL,aAAa;QACf;QACA;YACEK,SAAS;YACTL,aAAa;QACf;KACD,CAAA;IAED,OAAgBM,QAAQ;QACtBC,OAAOvB,MAAMwB,OAAO,CAAC;YACnBC,SAAS;YACTT,aAAa;QACf;IACF,EAAqB;IAErB,MAAaU,MAAqB;QAChC,MAAM,EAACR,KAAK,EAAEJ,eAAe,EAAC,GAAG,IAAI,CAACD,IAAI;QAC1C,MAAM,EAACc,IAAI,EAAEJ,KAAK,EAAC,GAAG,IAAI,CAACD,KAAK;QAEhC,IAAIM;QACJ,IAAI;YACFA,MAAML,QACF,MAAMf,SAASM,iBAAiBI,OAAO;gBAACW,OAAO;YAAiB,KAChE,MAAMnB,OAAOI,iBAAiBI;QACpC,EAAE,OAAOY,OAAO;YACdnB,YAAY,qBAAqBmB;YACjC,IAAIxB,YAAYwB,UAAUA,MAAMC,UAAU,KAAK,KAAK;gBAClD,IAAI,CAACD,KAAK,CAAC,CAAC,KAAK,EAAEZ,MAAM,+BAA+B,EAAEJ,gBAAgB,CAAC,CAAC,EAAE;oBAC5EkB,MAAM/B,UAAUgC,aAAa;gBAC/B;YACF;YACA,IAAI,CAACH,KAAK,CAAC,CAAC,mBAAmB,EAAE1B,gBAAgB0B,QAAQ,EAAE;gBACzDE,MAAM/B,UAAUgC,aAAa;YAC/B;QACF;QAEA,MAAMC,YAAYX,SAASK,IAAIO,MAAM,KAAK;QAC1C,MAAMC,iBAAiB7B,iBAAiB,OAAOqB;QAC/C,IAAID,MAAM;YACR,IAAIO,WAAW,MAAM,IAAI7B,gBAAgB+B,gBAAgBR,KAAK3B,UAAUgC,aAAa;YACrF,IAAI,CAACI,OAAO,CAACT;QACf,OAAO;YACL,MAAMU,QAAQ,EAAE,iCAAiC;;YACjD,IAAI,CAACC,GAAG,CAAC9B,eAAe,MAAMmB,IAAIY,EAAE,EAAE;gBAACF;YAAK;YAC5C,IAAI,CAACC,GAAG,CAAC9B,eAAe,UAAUmB,IAAIO,MAAM,EAAE;gBAACG;YAAK;YACpD,IAAI,CAACC,GAAG,CAAC9B,eAAe,WAAWmB,IAAIa,SAAS,IAAI,KAAK;gBAACH;YAAK;YAC/D,IAAI,CAACC,GAAG,CAAC9B,eAAe,aAAamB,IAAIc,WAAW,IAAI,KAAK;gBAACJ;YAAK;YACnE,IAAI,CAACC,GAAG,CAAC9B,eAAe,SAASmB,IAAIE,KAAK,IAAI,KAAK;gBAACQ;YAAK;QAC3D;QAEA,IAAIJ,WAAW;YACb,IAAI,CAACJ,KAAK,CAACM,gBAAgB;gBACzBJ,MAAM/B,UAAUgC,aAAa;YAC/B;QACF;IACF;AACF"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import { colorizeJson, exitCodes, SanityCommand, subdebug } from '@sanity/cli-core';
|
|
3
|
+
import { getErrorMessage } from '@sanity/cli-core/errors';
|
|
4
|
+
import { isRecord } from '@sanity/cli-core/util';
|
|
5
|
+
import { MissingOrganizationError, resolveOrganizationId } from '../../actions/context/resolveOrganizationId.js';
|
|
6
|
+
import { queryContext } from '../../services/context.js';
|
|
7
|
+
import { getOrganizationFlag } from '../../util/sharedFlags.js';
|
|
8
|
+
import { defineCommandTelemetry } from '../../util/telemetry/commandTelemetry.js';
|
|
9
|
+
const queryContextDebug = subdebug('context:query');
|
|
10
|
+
const flags = {
|
|
11
|
+
...getOrganizationFlag({
|
|
12
|
+
description: 'Organization to query',
|
|
13
|
+
semantics: 'override'
|
|
14
|
+
}),
|
|
15
|
+
params: Flags.custom({
|
|
16
|
+
description: 'Query parameters as a JSON object',
|
|
17
|
+
helpValue: '<json>',
|
|
18
|
+
parse: async (input)=>{
|
|
19
|
+
let parsed;
|
|
20
|
+
try {
|
|
21
|
+
parsed = JSON.parse(input);
|
|
22
|
+
} catch {
|
|
23
|
+
throw new Error('`--params` must be valid JSON');
|
|
24
|
+
}
|
|
25
|
+
if (!isRecord(parsed)) {
|
|
26
|
+
throw new Error('`--params` must be a JSON object, like \'{"type": "issue"}\'');
|
|
27
|
+
}
|
|
28
|
+
return parsed;
|
|
29
|
+
}
|
|
30
|
+
})()
|
|
31
|
+
};
|
|
32
|
+
export class QueryContextCommand extends SanityCommand {
|
|
33
|
+
static args = {
|
|
34
|
+
query: Args.string({
|
|
35
|
+
description: "GROQ query to run against the organization's Context documents",
|
|
36
|
+
required: true
|
|
37
|
+
})
|
|
38
|
+
};
|
|
39
|
+
static description = "Query an organization's Context documents with GROQ";
|
|
40
|
+
static enableJsonFlag = true;
|
|
41
|
+
static examples = [
|
|
42
|
+
{
|
|
43
|
+
command: '<%= config.bin %> <%= command.id %> \'*[_type == "sanity.context.issue" && status == "open"]{_id, knowledgeBaseId}\'',
|
|
44
|
+
description: 'List open issues across all knowledge bases'
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
command: '<%= config.bin %> <%= command.id %> \'*[_type == "sanity.context.entry" && knowledgeBaseId == $kb].path\' --params \'{"kb": "kb-abc123"}\' --json',
|
|
48
|
+
description: 'List the entry paths of one knowledge base as plain JSON'
|
|
49
|
+
}
|
|
50
|
+
];
|
|
51
|
+
static flags = flags;
|
|
52
|
+
static telemetry = defineCommandTelemetry(flags, {
|
|
53
|
+
redact: [
|
|
54
|
+
'params'
|
|
55
|
+
]
|
|
56
|
+
});
|
|
57
|
+
async run() {
|
|
58
|
+
const { query } = this.args;
|
|
59
|
+
const { json, organization, params } = this.flags;
|
|
60
|
+
let organizationId;
|
|
61
|
+
try {
|
|
62
|
+
organizationId = await resolveOrganizationId({
|
|
63
|
+
configuredOrganizationId: (await this.tryGetCliConfig()).app?.organizationId,
|
|
64
|
+
flagOrganizationId: organization,
|
|
65
|
+
unattended: this.isUnattended()
|
|
66
|
+
});
|
|
67
|
+
} catch (error) {
|
|
68
|
+
queryContextDebug('Error resolving organization', error);
|
|
69
|
+
this.error(getErrorMessage(error), {
|
|
70
|
+
exit: error instanceof MissingOrganizationError ? exitCodes.USAGE_ERROR : exitCodes.RUNTIME_ERROR
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
let result;
|
|
74
|
+
try {
|
|
75
|
+
result = await queryContext(organizationId, query, params);
|
|
76
|
+
} catch (error) {
|
|
77
|
+
queryContextDebug('Error running query', error);
|
|
78
|
+
this.error(`Failed to run query: ${getErrorMessage(error)}`, {
|
|
79
|
+
exit: exitCodes.RUNTIME_ERROR
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
// GROQ returns null for no match; guard undefined, which colorizeJson cannot tokenize
|
|
83
|
+
const output = result ?? null;
|
|
84
|
+
if (json) {
|
|
85
|
+
this.logJson(output);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
this.log(colorizeJson(output));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
//# sourceMappingURL=query.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/commands/context/query.ts"],"sourcesContent":["import {Args, Flags} from '@oclif/core'\nimport {type FlagInput} from '@oclif/core/interfaces'\nimport {colorizeJson, exitCodes, SanityCommand, subdebug} from '@sanity/cli-core'\nimport {getErrorMessage} from '@sanity/cli-core/errors'\nimport {isRecord} from '@sanity/cli-core/util'\n\nimport {\n MissingOrganizationError,\n resolveOrganizationId,\n} from '../../actions/context/resolveOrganizationId.js'\nimport {queryContext} from '../../services/context.js'\nimport {getOrganizationFlag} from '../../util/sharedFlags.js'\nimport {defineCommandTelemetry} from '../../util/telemetry/commandTelemetry.js'\n\nconst queryContextDebug = subdebug('context:query')\n\nconst flags = {\n ...getOrganizationFlag({\n description: 'Organization to query',\n semantics: 'override',\n }),\n params: Flags.custom<Record<string, unknown>>({\n description: 'Query parameters as a JSON object',\n helpValue: '<json>',\n parse: async (input: string) => {\n let parsed: unknown\n try {\n parsed = JSON.parse(input)\n } catch {\n throw new Error('`--params` must be valid JSON')\n }\n if (!isRecord(parsed)) {\n throw new Error('`--params` must be a JSON object, like \\'{\"type\": \"issue\"}\\'')\n }\n return parsed\n },\n })(),\n} satisfies FlagInput\n\nexport class QueryContextCommand extends SanityCommand<typeof QueryContextCommand> {\n static override args = {\n query: Args.string({\n description: \"GROQ query to run against the organization's Context documents\",\n required: true,\n }),\n }\n\n static override description = \"Query an organization's Context documents with GROQ\"\n\n static override enableJsonFlag = true\n\n static override examples = [\n {\n command:\n '<%= config.bin %> <%= command.id %> \\'*[_type == \"sanity.context.issue\" && status == \"open\"]{_id, knowledgeBaseId}\\'',\n description: 'List open issues across all knowledge bases',\n },\n {\n command:\n '<%= config.bin %> <%= command.id %> \\'*[_type == \"sanity.context.entry\" && knowledgeBaseId == $kb].path\\' --params \\'{\"kb\": \"kb-abc123\"}\\' --json',\n description: 'List the entry paths of one knowledge base as plain JSON',\n },\n ]\n\n static override flags = flags\n\n static telemetry = defineCommandTelemetry(flags, {redact: ['params']})\n\n public async run(): Promise<void> {\n const {query} = this.args\n const {json, organization, params} = this.flags\n\n let organizationId: string\n try {\n organizationId = await resolveOrganizationId({\n configuredOrganizationId: (await this.tryGetCliConfig()).app?.organizationId,\n flagOrganizationId: organization,\n unattended: this.isUnattended(),\n })\n } catch (error) {\n queryContextDebug('Error resolving organization', error)\n this.error(getErrorMessage(error), {\n exit:\n error instanceof MissingOrganizationError\n ? exitCodes.USAGE_ERROR\n : exitCodes.RUNTIME_ERROR,\n })\n }\n\n let result\n try {\n result = await queryContext(organizationId, query, params)\n } catch (error) {\n queryContextDebug('Error running query', error)\n this.error(`Failed to run query: ${getErrorMessage(error)}`, {\n exit: exitCodes.RUNTIME_ERROR,\n })\n }\n\n // GROQ returns null for no match; guard undefined, which colorizeJson cannot tokenize\n const output = result ?? null\n if (json) {\n this.logJson(output)\n return\n }\n\n this.log(colorizeJson(output))\n }\n}\n"],"names":["Args","Flags","colorizeJson","exitCodes","SanityCommand","subdebug","getErrorMessage","isRecord","MissingOrganizationError","resolveOrganizationId","queryContext","getOrganizationFlag","defineCommandTelemetry","queryContextDebug","flags","description","semantics","params","custom","helpValue","parse","input","parsed","JSON","Error","QueryContextCommand","args","query","string","required","enableJsonFlag","examples","command","telemetry","redact","run","json","organization","organizationId","configuredOrganizationId","tryGetCliConfig","app","flagOrganizationId","unattended","isUnattended","error","exit","USAGE_ERROR","RUNTIME_ERROR","result","output","logJson","log"],"mappings":"AAAA,SAAQA,IAAI,EAAEC,KAAK,QAAO,cAAa;AAEvC,SAAQC,YAAY,EAAEC,SAAS,EAAEC,aAAa,EAAEC,QAAQ,QAAO,mBAAkB;AACjF,SAAQC,eAAe,QAAO,0BAAyB;AACvD,SAAQC,QAAQ,QAAO,wBAAuB;AAE9C,SACEC,wBAAwB,EACxBC,qBAAqB,QAChB,iDAAgD;AACvD,SAAQC,YAAY,QAAO,4BAA2B;AACtD,SAAQC,mBAAmB,QAAO,4BAA2B;AAC7D,SAAQC,sBAAsB,QAAO,2CAA0C;AAE/E,MAAMC,oBAAoBR,SAAS;AAEnC,MAAMS,QAAQ;IACZ,GAAGH,oBAAoB;QACrBI,aAAa;QACbC,WAAW;IACb,EAAE;IACFC,QAAQhB,MAAMiB,MAAM,CAA0B;QAC5CH,aAAa;QACbI,WAAW;QACXC,OAAO,OAAOC;YACZ,IAAIC;YACJ,IAAI;gBACFA,SAASC,KAAKH,KAAK,CAACC;YACtB,EAAE,OAAM;gBACN,MAAM,IAAIG,MAAM;YAClB;YACA,IAAI,CAACjB,SAASe,SAAS;gBACrB,MAAM,IAAIE,MAAM;YAClB;YACA,OAAOF;QACT;IACF;AACF;AAEA,OAAO,MAAMG,4BAA4BrB;IACvC,OAAgBsB,OAAO;QACrBC,OAAO3B,KAAK4B,MAAM,CAAC;YACjBb,aAAa;YACbc,UAAU;QACZ;IACF,EAAC;IAED,OAAgBd,cAAc,sDAAqD;IAEnF,OAAgBe,iBAAiB,KAAI;IAErC,OAAgBC,WAAW;QACzB;YACEC,SACE;YACFjB,aAAa;QACf;QACA;YACEiB,SACE;YACFjB,aAAa;QACf;KACD,CAAA;IAED,OAAgBD,QAAQA,MAAK;IAE7B,OAAOmB,YAAYrB,uBAAuBE,OAAO;QAACoB,QAAQ;YAAC;SAAS;IAAA,GAAE;IAEtE,MAAaC,MAAqB;QAChC,MAAM,EAACR,KAAK,EAAC,GAAG,IAAI,CAACD,IAAI;QACzB,MAAM,EAACU,IAAI,EAAEC,YAAY,EAAEpB,MAAM,EAAC,GAAG,IAAI,CAACH,KAAK;QAE/C,IAAIwB;QACJ,IAAI;YACFA,iBAAiB,MAAM7B,sBAAsB;gBAC3C8B,0BAA0B,AAAC,CAAA,MAAM,IAAI,CAACC,eAAe,EAAC,EAAGC,GAAG,EAAEH;gBAC9DI,oBAAoBL;gBACpBM,YAAY,IAAI,CAACC,YAAY;YAC/B;QACF,EAAE,OAAOC,OAAO;YACdhC,kBAAkB,gCAAgCgC;YAClD,IAAI,CAACA,KAAK,CAACvC,gBAAgBuC,QAAQ;gBACjCC,MACED,iBAAiBrC,2BACbL,UAAU4C,WAAW,GACrB5C,UAAU6C,aAAa;YAC/B;QACF;QAEA,IAAIC;QACJ,IAAI;YACFA,SAAS,MAAMvC,aAAa4B,gBAAgBX,OAAOV;QACrD,EAAE,OAAO4B,OAAO;YACdhC,kBAAkB,uBAAuBgC;YACzC,IAAI,CAACA,KAAK,CAAC,CAAC,qBAAqB,EAAEvC,gBAAgBuC,QAAQ,EAAE;gBAC3DC,MAAM3C,UAAU6C,aAAa;YAC/B;QACF;QAEA,sFAAsF;QACtF,MAAME,SAASD,UAAU;QACzB,IAAIb,MAAM;YACR,IAAI,CAACe,OAAO,CAACD;YACb;QACF;QAEA,IAAI,CAACE,GAAG,CAAClD,aAAagD;IACxB;AACF"}
|
package/dist/commands/deploy.js
CHANGED
|
@@ -86,6 +86,12 @@ export class DeployCommand extends SanityCommand {
|
|
|
86
86
|
default: false,
|
|
87
87
|
description: 'Output the result as JSON'
|
|
88
88
|
}),
|
|
89
|
+
'media-library-id': Flags.string({
|
|
90
|
+
description: 'For a Media Library config deploy: the media library to sync the aspect definitions to. Defaults to the single active library of the configured organization; required when the organization has several',
|
|
91
|
+
// Part of the beta config-app flow, like the hidden media commands it
|
|
92
|
+
// pairs with. A help-listed flag is hard to take back — unhide at GA.
|
|
93
|
+
hidden: true
|
|
94
|
+
}),
|
|
89
95
|
minify: Flags.boolean({
|
|
90
96
|
allowNo: true,
|
|
91
97
|
default: true,
|
|
@@ -120,6 +126,13 @@ export class DeployCommand extends SanityCommand {
|
|
|
120
126
|
const cliConfig = await this.getCliConfig();
|
|
121
127
|
const projectRoot = await this.getProjectRoot();
|
|
122
128
|
const isApp = determineIsApp(cliConfig);
|
|
129
|
+
// `--media-library-id` only steers the aspect-document sync of a Media
|
|
130
|
+
// Library config deploy (see deployApp); studio and app deploys never read
|
|
131
|
+
// it. Warned rather than silently dropped (same courtesy init gives
|
|
132
|
+
// --dashboard on the media template).
|
|
133
|
+
if (flags['media-library-id'] && !resolveWorkbenchConfig(cliConfig)) {
|
|
134
|
+
this.output.warn('--media-library-id is ignored — it only applies when deploying a Media Library config');
|
|
135
|
+
}
|
|
123
136
|
if (flags.create) {
|
|
124
137
|
if (!isApp) {
|
|
125
138
|
return this.output.error('--create is only supported for App SDK apps. For a studio, use --url <hostname>.');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/commands/deploy.ts"],"sourcesContent":["import path from 'node:path'\n\nimport {Args, Flags} from '@oclif/core'\nimport {exitCodes, SanityCommand} from '@sanity/cli-core'\nimport {confirm} from '@sanity/cli-core/ux'\nimport {getWorkbench, resolveWorkbenchConfig} from '@sanity/workbench-cli/deploy'\n\nimport {deployApp} from '../actions/deploy/deployApp.js'\nimport {deployDebug} from '../actions/deploy/deployDebug.js'\nimport {deployStudio} from '../actions/deploy/deployStudio.js'\nimport {getAppId} from '../util/appId.js'\nimport {determineIsApp} from '../util/determineIsApp.js'\nimport {dirIsEmptyOrNonExistent} from '../util/dirIsEmptyOrNonExistent.js'\n\nexport class DeployCommand extends SanityCommand<typeof DeployCommand> {\n static override args = {\n sourceDir: Args.directory({\n description: 'Source directory',\n }),\n }\n\n static override description = 'Builds and deploys Sanity Studio or application to Sanity hosting'\n\n static override enableJsonFlag = true\n\n static override examples = [\n {\n command: '<%= config.bin %> <%= command.id %>',\n description: 'Build and deploy the studio to Sanity hosting',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --no-minify --source-maps',\n description: 'Deploys non-minified build with source maps',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --schema-required',\n description:\n 'Fail fast on schema store fails - for when other services rely on the stored schema',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --external',\n description: 'Register an externally hosted studio (studioHost contains full URL)',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --external --url https://apps.example.com',\n description: 'Register an externally hosted studio, served from the provided origin',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --create --title \"My App\" --yes --json',\n description:\n 'Create and deploy an App SDK app without prompts. Save application.id as deployment.appId, then omit --create on later deploys',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --create --title \"My App\" --dry-run --json',\n description: 'Preview a new App SDK deployment without creating or uploading anything',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --url my-studio --yes',\n description: 'Deploy a studio without prompts using its hostname',\n },\n ]\n\n static override flags = {\n 'auto-updates': Flags.boolean({\n allowNo: true,\n deprecated: true,\n description: 'Automatically update the studio to the latest version',\n }),\n build: Flags.boolean({\n allowNo: true,\n default: true,\n description:\n 'Build the studio before deploying (use --no-build to deploy existing `dist/` output)',\n }),\n create: Flags.boolean({\n description: 'Create a new App SDK app, even if the organization has other apps',\n exclusive: ['external', 'url'],\n }),\n 'dry-run': Flags.boolean({\n default: false,\n description: 'Report what would be deployed without uploading or creating anything',\n }),\n external: Flags.boolean({\n default: false,\n description: 'Register an externally hosted studio',\n exclusive: ['source-maps', 'minify'],\n }),\n json: Flags.boolean({\n char: 'j',\n default: false,\n description: 'Output the result as JSON',\n }),\n minify: Flags.boolean({\n allowNo: true,\n default: true,\n description: 'Minify built JavaScript (use --no-minify to skip for faster builds)',\n }),\n 'schema-required': Flags.boolean({\n default: false,\n description: 'Fail if schema deployment fails',\n }),\n 'source-maps': Flags.boolean({\n default: false,\n description: 'Enable source maps for built bundles (increases size of bundle)',\n }),\n title: Flags.string({\n description:\n 'Title for a newly created application or studio. For apps, use --create to select creation when the organization already has apps',\n }),\n url: Flags.string({\n description:\n 'Studio URL for deployment. For external deploys, the full URL. For hosted studios, the hostname (e.g. \"my-studio\" or \"my-studio.sanity.studio\")',\n }),\n verbose: Flags.boolean({\n default: false,\n description: 'Enable verbose logging',\n }),\n yes: Flags.boolean({\n char: 'y',\n default: false,\n description:\n 'Unattended mode, answers \"yes\" to any \"yes/no\" prompt and otherwise uses defaults',\n }),\n }\n\n public async run(): Promise<void> {\n const {flags} = await this.parse(DeployCommand)\n\n const cliConfig = await this.getCliConfig()\n const projectRoot = await this.getProjectRoot()\n\n const isApp = determineIsApp(cliConfig)\n\n if (flags.create) {\n if (!isApp) {\n return this.output.error(\n '--create is only supported for App SDK apps. For a studio, use --url <hostname>.',\n )\n }\n if (getAppId(cliConfig)) {\n return this.output.error(\n '--create cannot be used with a configured app ID. Omit --create to deploy to that app, or remove deployment.appId and app.id to create a new one.',\n )\n }\n if (getWorkbench(cliConfig) || resolveWorkbenchConfig(cliConfig)) {\n return this.output.error(\n '--create is not supported for this app configuration. Omit --create and run sanity deploy.',\n )\n }\n }\n\n const defaultOutputDir = path.resolve(path.join(projectRoot.directory, 'dist'))\n const sourceDir = path.resolve(process.cwd(), this.args.sourceDir || defaultOutputDir)\n\n // Skip the directory check if the studio is externally hosted\n if (this.args.sourceDir && this.args.sourceDir !== 'dist' && !flags.external) {\n let relativeOutput = path.relative(process.cwd(), sourceDir)\n if (relativeOutput[0] !== '.') {\n relativeOutput = `./${relativeOutput}`\n }\n\n if (!this.isUnattended() && !flags['dry-run']) {\n const isEmpty = await dirIsEmptyOrNonExistent(sourceDir)\n const shouldProceed =\n isEmpty ||\n (await confirm({\n default: false,\n message: `\"${relativeOutput}\" is not empty, do you want to proceed?`,\n }))\n\n if (!shouldProceed) {\n this.output.error('Cancelled.', {exit: exitCodes.RUNTIME_ERROR})\n }\n }\n\n // Keep --json's stdout clean for the payload\n if (!flags.json) this.output.log(`Building to ${relativeOutput}\\n`)\n }\n\n // Force yes downstream: build/app resolution otherwise prompts for prerelease/version choices\n const deployFlags = this.isUnattended() || flags['dry-run'] ? {...flags, yes: true} : flags\n\n if (isApp) {\n deployDebug('Deploying app')\n await deployApp({\n cliConfig,\n flags: deployFlags,\n output: this.output,\n projectRoot,\n sourceDir,\n })\n } else {\n deployDebug('Deploying studio')\n await deployStudio({\n cliConfig,\n flags: deployFlags,\n output: this.output,\n projectRoot,\n sourceDir,\n })\n }\n }\n}\n"],"names":["path","Args","Flags","exitCodes","SanityCommand","confirm","getWorkbench","resolveWorkbenchConfig","deployApp","deployDebug","deployStudio","getAppId","determineIsApp","dirIsEmptyOrNonExistent","DeployCommand","args","sourceDir","directory","description","enableJsonFlag","examples","command","flags","boolean","allowNo","deprecated","build","default","create","exclusive","external","json","char","minify","title","string","url","verbose","yes","run","parse","cliConfig","getCliConfig","projectRoot","getProjectRoot","isApp","output","error","defaultOutputDir","resolve","join","process","cwd","relativeOutput","relative","isUnattended","isEmpty","shouldProceed","message","exit","RUNTIME_ERROR","log","deployFlags"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/commands/deploy.ts"],"sourcesContent":["import path from 'node:path'\n\nimport {Args, Flags} from '@oclif/core'\nimport {exitCodes, SanityCommand} from '@sanity/cli-core'\nimport {confirm} from '@sanity/cli-core/ux'\nimport {getWorkbench, resolveWorkbenchConfig} from '@sanity/workbench-cli/deploy'\n\nimport {deployApp} from '../actions/deploy/deployApp.js'\nimport {deployDebug} from '../actions/deploy/deployDebug.js'\nimport {deployStudio} from '../actions/deploy/deployStudio.js'\nimport {getAppId} from '../util/appId.js'\nimport {determineIsApp} from '../util/determineIsApp.js'\nimport {dirIsEmptyOrNonExistent} from '../util/dirIsEmptyOrNonExistent.js'\n\nexport class DeployCommand extends SanityCommand<typeof DeployCommand> {\n static override args = {\n sourceDir: Args.directory({\n description: 'Source directory',\n }),\n }\n\n static override description = 'Builds and deploys Sanity Studio or application to Sanity hosting'\n\n static override enableJsonFlag = true\n\n static override examples = [\n {\n command: '<%= config.bin %> <%= command.id %>',\n description: 'Build and deploy the studio to Sanity hosting',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --no-minify --source-maps',\n description: 'Deploys non-minified build with source maps',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --schema-required',\n description:\n 'Fail fast on schema store fails - for when other services rely on the stored schema',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --external',\n description: 'Register an externally hosted studio (studioHost contains full URL)',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --external --url https://apps.example.com',\n description: 'Register an externally hosted studio, served from the provided origin',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --create --title \"My App\" --yes --json',\n description:\n 'Create and deploy an App SDK app without prompts. Save application.id as deployment.appId, then omit --create on later deploys',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --create --title \"My App\" --dry-run --json',\n description: 'Preview a new App SDK deployment without creating or uploading anything',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --url my-studio --yes',\n description: 'Deploy a studio without prompts using its hostname',\n },\n ]\n\n static override flags = {\n 'auto-updates': Flags.boolean({\n allowNo: true,\n deprecated: true,\n description: 'Automatically update the studio to the latest version',\n }),\n build: Flags.boolean({\n allowNo: true,\n default: true,\n description:\n 'Build the studio before deploying (use --no-build to deploy existing `dist/` output)',\n }),\n create: Flags.boolean({\n description: 'Create a new App SDK app, even if the organization has other apps',\n exclusive: ['external', 'url'],\n }),\n 'dry-run': Flags.boolean({\n default: false,\n description: 'Report what would be deployed without uploading or creating anything',\n }),\n external: Flags.boolean({\n default: false,\n description: 'Register an externally hosted studio',\n exclusive: ['source-maps', 'minify'],\n }),\n json: Flags.boolean({\n char: 'j',\n default: false,\n description: 'Output the result as JSON',\n }),\n 'media-library-id': Flags.string({\n description:\n 'For a Media Library config deploy: the media library to sync the aspect definitions to. Defaults to the single active library of the configured organization; required when the organization has several',\n // Part of the beta config-app flow, like the hidden media commands it\n // pairs with. A help-listed flag is hard to take back — unhide at GA.\n hidden: true,\n }),\n minify: Flags.boolean({\n allowNo: true,\n default: true,\n description: 'Minify built JavaScript (use --no-minify to skip for faster builds)',\n }),\n 'schema-required': Flags.boolean({\n default: false,\n description: 'Fail if schema deployment fails',\n }),\n 'source-maps': Flags.boolean({\n default: false,\n description: 'Enable source maps for built bundles (increases size of bundle)',\n }),\n title: Flags.string({\n description:\n 'Title for a newly created application or studio. For apps, use --create to select creation when the organization already has apps',\n }),\n url: Flags.string({\n description:\n 'Studio URL for deployment. For external deploys, the full URL. For hosted studios, the hostname (e.g. \"my-studio\" or \"my-studio.sanity.studio\")',\n }),\n verbose: Flags.boolean({\n default: false,\n description: 'Enable verbose logging',\n }),\n yes: Flags.boolean({\n char: 'y',\n default: false,\n description:\n 'Unattended mode, answers \"yes\" to any \"yes/no\" prompt and otherwise uses defaults',\n }),\n }\n\n public async run(): Promise<void> {\n const {flags} = await this.parse(DeployCommand)\n\n const cliConfig = await this.getCliConfig()\n const projectRoot = await this.getProjectRoot()\n\n const isApp = determineIsApp(cliConfig)\n\n // `--media-library-id` only steers the aspect-document sync of a Media\n // Library config deploy (see deployApp); studio and app deploys never read\n // it. Warned rather than silently dropped (same courtesy init gives\n // --dashboard on the media template).\n if (flags['media-library-id'] && !resolveWorkbenchConfig(cliConfig)) {\n this.output.warn(\n '--media-library-id is ignored — it only applies when deploying a Media Library config',\n )\n }\n\n if (flags.create) {\n if (!isApp) {\n return this.output.error(\n '--create is only supported for App SDK apps. For a studio, use --url <hostname>.',\n )\n }\n if (getAppId(cliConfig)) {\n return this.output.error(\n '--create cannot be used with a configured app ID. Omit --create to deploy to that app, or remove deployment.appId and app.id to create a new one.',\n )\n }\n if (getWorkbench(cliConfig) || resolveWorkbenchConfig(cliConfig)) {\n return this.output.error(\n '--create is not supported for this app configuration. Omit --create and run sanity deploy.',\n )\n }\n }\n\n const defaultOutputDir = path.resolve(path.join(projectRoot.directory, 'dist'))\n const sourceDir = path.resolve(process.cwd(), this.args.sourceDir || defaultOutputDir)\n\n // Skip the directory check if the studio is externally hosted\n if (this.args.sourceDir && this.args.sourceDir !== 'dist' && !flags.external) {\n let relativeOutput = path.relative(process.cwd(), sourceDir)\n if (relativeOutput[0] !== '.') {\n relativeOutput = `./${relativeOutput}`\n }\n\n if (!this.isUnattended() && !flags['dry-run']) {\n const isEmpty = await dirIsEmptyOrNonExistent(sourceDir)\n const shouldProceed =\n isEmpty ||\n (await confirm({\n default: false,\n message: `\"${relativeOutput}\" is not empty, do you want to proceed?`,\n }))\n\n if (!shouldProceed) {\n this.output.error('Cancelled.', {exit: exitCodes.RUNTIME_ERROR})\n }\n }\n\n // Keep --json's stdout clean for the payload\n if (!flags.json) this.output.log(`Building to ${relativeOutput}\\n`)\n }\n\n // Force yes downstream: build/app resolution otherwise prompts for prerelease/version choices\n const deployFlags = this.isUnattended() || flags['dry-run'] ? {...flags, yes: true} : flags\n\n if (isApp) {\n deployDebug('Deploying app')\n await deployApp({\n cliConfig,\n flags: deployFlags,\n output: this.output,\n projectRoot,\n sourceDir,\n })\n } else {\n deployDebug('Deploying studio')\n await deployStudio({\n cliConfig,\n flags: deployFlags,\n output: this.output,\n projectRoot,\n sourceDir,\n })\n }\n }\n}\n"],"names":["path","Args","Flags","exitCodes","SanityCommand","confirm","getWorkbench","resolveWorkbenchConfig","deployApp","deployDebug","deployStudio","getAppId","determineIsApp","dirIsEmptyOrNonExistent","DeployCommand","args","sourceDir","directory","description","enableJsonFlag","examples","command","flags","boolean","allowNo","deprecated","build","default","create","exclusive","external","json","char","string","hidden","minify","title","url","verbose","yes","run","parse","cliConfig","getCliConfig","projectRoot","getProjectRoot","isApp","output","warn","error","defaultOutputDir","resolve","join","process","cwd","relativeOutput","relative","isUnattended","isEmpty","shouldProceed","message","exit","RUNTIME_ERROR","log","deployFlags"],"mappings":"AAAA,OAAOA,UAAU,YAAW;AAE5B,SAAQC,IAAI,EAAEC,KAAK,QAAO,cAAa;AACvC,SAAQC,SAAS,EAAEC,aAAa,QAAO,mBAAkB;AACzD,SAAQC,OAAO,QAAO,sBAAqB;AAC3C,SAAQC,YAAY,EAAEC,sBAAsB,QAAO,+BAA8B;AAEjF,SAAQC,SAAS,QAAO,iCAAgC;AACxD,SAAQC,WAAW,QAAO,mCAAkC;AAC5D,SAAQC,YAAY,QAAO,oCAAmC;AAC9D,SAAQC,QAAQ,QAAO,mBAAkB;AACzC,SAAQC,cAAc,QAAO,4BAA2B;AACxD,SAAQC,uBAAuB,QAAO,qCAAoC;AAE1E,OAAO,MAAMC,sBAAsBV;IACjC,OAAgBW,OAAO;QACrBC,WAAWf,KAAKgB,SAAS,CAAC;YACxBC,aAAa;QACf;IACF,EAAC;IAED,OAAgBA,cAAc,oEAAmE;IAEjG,OAAgBC,iBAAiB,KAAI;IAErC,OAAgBC,WAAW;QACzB;YACEC,SAAS;YACTH,aAAa;QACf;QACA;YACEG,SAAS;YACTH,aAAa;QACf;QACA;YACEG,SAAS;YACTH,aACE;QACJ;QACA;YACEG,SAAS;YACTH,aAAa;QACf;QACA;YACEG,SAAS;YACTH,aAAa;QACf;QACA;YACEG,SAAS;YACTH,aACE;QACJ;QACA;YACEG,SAAS;YACTH,aAAa;QACf;QACA;YACEG,SAAS;YACTH,aAAa;QACf;KACD,CAAA;IAED,OAAgBI,QAAQ;QACtB,gBAAgBpB,MAAMqB,OAAO,CAAC;YAC5BC,SAAS;YACTC,YAAY;YACZP,aAAa;QACf;QACAQ,OAAOxB,MAAMqB,OAAO,CAAC;YACnBC,SAAS;YACTG,SAAS;YACTT,aACE;QACJ;QACAU,QAAQ1B,MAAMqB,OAAO,CAAC;YACpBL,aAAa;YACbW,WAAW;gBAAC;gBAAY;aAAM;QAChC;QACA,WAAW3B,MAAMqB,OAAO,CAAC;YACvBI,SAAS;YACTT,aAAa;QACf;QACAY,UAAU5B,MAAMqB,OAAO,CAAC;YACtBI,SAAS;YACTT,aAAa;YACbW,WAAW;gBAAC;gBAAe;aAAS;QACtC;QACAE,MAAM7B,MAAMqB,OAAO,CAAC;YAClBS,MAAM;YACNL,SAAS;YACTT,aAAa;QACf;QACA,oBAAoBhB,MAAM+B,MAAM,CAAC;YAC/Bf,aACE;YACF,sEAAsE;YACtE,sEAAsE;YACtEgB,QAAQ;QACV;QACAC,QAAQjC,MAAMqB,OAAO,CAAC;YACpBC,SAAS;YACTG,SAAS;YACTT,aAAa;QACf;QACA,mBAAmBhB,MAAMqB,OAAO,CAAC;YAC/BI,SAAS;YACTT,aAAa;QACf;QACA,eAAehB,MAAMqB,OAAO,CAAC;YAC3BI,SAAS;YACTT,aAAa;QACf;QACAkB,OAAOlC,MAAM+B,MAAM,CAAC;YAClBf,aACE;QACJ;QACAmB,KAAKnC,MAAM+B,MAAM,CAAC;YAChBf,aACE;QACJ;QACAoB,SAASpC,MAAMqB,OAAO,CAAC;YACrBI,SAAS;YACTT,aAAa;QACf;QACAqB,KAAKrC,MAAMqB,OAAO,CAAC;YACjBS,MAAM;YACNL,SAAS;YACTT,aACE;QACJ;IACF,EAAC;IAED,MAAasB,MAAqB;QAChC,MAAM,EAAClB,KAAK,EAAC,GAAG,MAAM,IAAI,CAACmB,KAAK,CAAC3B;QAEjC,MAAM4B,YAAY,MAAM,IAAI,CAACC,YAAY;QACzC,MAAMC,cAAc,MAAM,IAAI,CAACC,cAAc;QAE7C,MAAMC,QAAQlC,eAAe8B;QAE7B,uEAAuE;QACvE,2EAA2E;QAC3E,oEAAoE;QACpE,sCAAsC;QACtC,IAAIpB,KAAK,CAAC,mBAAmB,IAAI,CAACf,uBAAuBmC,YAAY;YACnE,IAAI,CAACK,MAAM,CAACC,IAAI,CACd;QAEJ;QAEA,IAAI1B,MAAMM,MAAM,EAAE;YAChB,IAAI,CAACkB,OAAO;gBACV,OAAO,IAAI,CAACC,MAAM,CAACE,KAAK,CACtB;YAEJ;YACA,IAAItC,SAAS+B,YAAY;gBACvB,OAAO,IAAI,CAACK,MAAM,CAACE,KAAK,CACtB;YAEJ;YACA,IAAI3C,aAAaoC,cAAcnC,uBAAuBmC,YAAY;gBAChE,OAAO,IAAI,CAACK,MAAM,CAACE,KAAK,CACtB;YAEJ;QACF;QAEA,MAAMC,mBAAmBlD,KAAKmD,OAAO,CAACnD,KAAKoD,IAAI,CAACR,YAAY3B,SAAS,EAAE;QACvE,MAAMD,YAAYhB,KAAKmD,OAAO,CAACE,QAAQC,GAAG,IAAI,IAAI,CAACvC,IAAI,CAACC,SAAS,IAAIkC;QAErE,8DAA8D;QAC9D,IAAI,IAAI,CAACnC,IAAI,CAACC,SAAS,IAAI,IAAI,CAACD,IAAI,CAACC,SAAS,KAAK,UAAU,CAACM,MAAMQ,QAAQ,EAAE;YAC5E,IAAIyB,iBAAiBvD,KAAKwD,QAAQ,CAACH,QAAQC,GAAG,IAAItC;YAClD,IAAIuC,cAAc,CAAC,EAAE,KAAK,KAAK;gBAC7BA,iBAAiB,CAAC,EAAE,EAAEA,gBAAgB;YACxC;YAEA,IAAI,CAAC,IAAI,CAACE,YAAY,MAAM,CAACnC,KAAK,CAAC,UAAU,EAAE;gBAC7C,MAAMoC,UAAU,MAAM7C,wBAAwBG;gBAC9C,MAAM2C,gBACJD,WACC,MAAMrD,QAAQ;oBACbsB,SAAS;oBACTiC,SAAS,CAAC,CAAC,EAAEL,eAAe,uCAAuC,CAAC;gBACtE;gBAEF,IAAI,CAACI,eAAe;oBAClB,IAAI,CAACZ,MAAM,CAACE,KAAK,CAAC,cAAc;wBAACY,MAAM1D,UAAU2D,aAAa;oBAAA;gBAChE;YACF;YAEA,6CAA6C;YAC7C,IAAI,CAACxC,MAAMS,IAAI,EAAE,IAAI,CAACgB,MAAM,CAACgB,GAAG,CAAC,CAAC,YAAY,EAAER,eAAe,EAAE,CAAC;QACpE;QAEA,8FAA8F;QAC9F,MAAMS,cAAc,IAAI,CAACP,YAAY,MAAMnC,KAAK,CAAC,UAAU,GAAG;YAAC,GAAGA,KAAK;YAAEiB,KAAK;QAAI,IAAIjB;QAEtF,IAAIwB,OAAO;YACTrC,YAAY;YACZ,MAAMD,UAAU;gBACdkC;gBACApB,OAAO0C;gBACPjB,QAAQ,IAAI,CAACA,MAAM;gBACnBH;gBACA5B;YACF;QACF,OAAO;YACLP,YAAY;YACZ,MAAMC,aAAa;gBACjBgC;gBACApB,OAAO0C;gBACPjB,QAAQ,IAAI,CAACA,MAAM;gBACnBH;gBACA5B;YACF;QACF;IACF;AACF"}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { styleText } from 'node:util';
|
|
2
|
+
import { Args, Flags } from '@oclif/core';
|
|
3
|
+
import { exitCodes, ProjectRootNotFoundError, SanityCommand, subdebug } from '@sanity/cli-core';
|
|
4
|
+
import { getErrorMessage } from '@sanity/cli-core/errors';
|
|
5
|
+
import { logSymbols, spinner } from '@sanity/cli-core/ux';
|
|
6
|
+
import { resolveWorkbenchConfig } from '@sanity/workbench-cli/deploy';
|
|
7
|
+
import { VALID_ASPECT_NAME } from '../../actions/media/aspectName.js';
|
|
8
|
+
import { resolveMediaLibraryId } from '../../actions/media/resolveMediaLibraryId.js';
|
|
9
|
+
import { getAspectDataCarrierIds, getStoredAspectIds, unsetAspectData } from '../../services/mediaLibraries.js';
|
|
10
|
+
import { pluralize } from '../../util/pluralize.js';
|
|
11
|
+
const pruneAspectDataDebug = subdebug('media:prune-aspect-data');
|
|
12
|
+
const NOT_A_CONFIG_APP = 'This command must be run inside a Media Library config app — a project whose ' + '`sanity.cli.ts` declares `unstable_defineMediaLibrary(...)`. Scaffold one with ' + '`sanity init --template media-library-config`.';
|
|
13
|
+
/** Unset-patch batch size per mutate request. */ const BATCH_SIZE = 100;
|
|
14
|
+
/** How many carrier ids the report lists before truncating. */ const SAMPLE_SIZE = 10;
|
|
15
|
+
/**
|
|
16
|
+
* The deliberate, explicitly-invoked counterpart to the deploy-time aspect
|
|
17
|
+
* sync: deploying a config only mirrors aspect *definitions* — `aspects.<name>`
|
|
18
|
+
* values on assets are never touched and resurface if a matching field comes
|
|
19
|
+
* back. This command removes that data. Irreversible fan-out mutation, so it
|
|
20
|
+
* never asks for confirmation: without `--yes` it only reports what would change.
|
|
21
|
+
*/ export class MediaPruneAspectDataCommand extends SanityCommand {
|
|
22
|
+
static args = {
|
|
23
|
+
aspectName: Args.string({
|
|
24
|
+
description: 'Name of the aspect whose data to remove from all assets',
|
|
25
|
+
required: true
|
|
26
|
+
})
|
|
27
|
+
};
|
|
28
|
+
static description = "Irreversibly remove an aspect's data (aspects.<name> values) from every asset of a media library";
|
|
29
|
+
static examples = [
|
|
30
|
+
{
|
|
31
|
+
command: '<%= config.bin %> <%= command.id %> legacyAspect',
|
|
32
|
+
description: 'Report which assets carry aspects.legacyAspect (changes nothing)'
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
command: '<%= config.bin %> <%= command.id %> legacyAspect --yes',
|
|
36
|
+
description: 'Remove aspects.legacyAspect from every asset that carries it'
|
|
37
|
+
}
|
|
38
|
+
];
|
|
39
|
+
static flags = {
|
|
40
|
+
'media-library-id': Flags.string({
|
|
41
|
+
description: 'The id of the media library to prune (defaults to the single active library of the configured organization)'
|
|
42
|
+
}),
|
|
43
|
+
yes: Flags.boolean({
|
|
44
|
+
char: 'y',
|
|
45
|
+
description: 'Execute the prune. Without this flag the command only reports what would change'
|
|
46
|
+
})
|
|
47
|
+
};
|
|
48
|
+
// Part of the beta config-app flow: the command requires a config app to
|
|
49
|
+
// resolve its organization, so listing it in the general help would only
|
|
50
|
+
// route legacy-flow users into the beta paradigm. The prune itself is
|
|
51
|
+
// paradigm-neutral (it only unsets asset data) — when there is demand from
|
|
52
|
+
// the legacy flow, ungate it from the config app and unhide it.
|
|
53
|
+
static hidden = true;
|
|
54
|
+
async run() {
|
|
55
|
+
const { aspectName } = this.args;
|
|
56
|
+
const { 'media-library-id': mediaLibraryIdFlag, yes = false } = this.flags;
|
|
57
|
+
// The name rule shared with the sync contract; also makes the
|
|
58
|
+
// interpolation into the carrier GROQ filter injection-safe.
|
|
59
|
+
if (!VALID_ASPECT_NAME.test(aspectName)) {
|
|
60
|
+
this.error(`"${aspectName}" is not a valid aspect name (${VALID_ASPECT_NAME})`, {
|
|
61
|
+
exit: exitCodes.USAGE_ERROR
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
let cliConfig;
|
|
65
|
+
try {
|
|
66
|
+
cliConfig = await this.getCliConfig();
|
|
67
|
+
} catch (err) {
|
|
68
|
+
if (err instanceof ProjectRootNotFoundError) {
|
|
69
|
+
this.error(NOT_A_CONFIG_APP, {
|
|
70
|
+
exit: exitCodes.RUNTIME_ERROR
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
throw err;
|
|
74
|
+
}
|
|
75
|
+
const config = resolveWorkbenchConfig(cliConfig);
|
|
76
|
+
if (!config) {
|
|
77
|
+
this.error(NOT_A_CONFIG_APP, {
|
|
78
|
+
exit: exitCodes.RUNTIME_ERROR
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
// Not `isUnattended()`: that treats `--yes` as unattended, but here
|
|
83
|
+
// `--yes` consents to the prune, not to skipping prompts — the report
|
|
84
|
+
// path literally instructs "Re-run with --yes", and that run must still
|
|
85
|
+
// offer the library picker in a multi-library organization. Only a
|
|
86
|
+
// genuinely non-interactive run (CI, piped stdin) must not prompt.
|
|
87
|
+
const mediaLibraryId = await resolveMediaLibraryId({
|
|
88
|
+
mediaLibraryIdFlag,
|
|
89
|
+
organizationId: config.organizationId,
|
|
90
|
+
unattended: !this.resolveIsInteractive()
|
|
91
|
+
});
|
|
92
|
+
const spin = spinner('Finding assets that carry the aspect').start();
|
|
93
|
+
let storedIds;
|
|
94
|
+
let carriers;
|
|
95
|
+
try {
|
|
96
|
+
storedIds = await getStoredAspectIds(mediaLibraryId, 'prune-aspect-data');
|
|
97
|
+
carriers = await getAspectDataCarrierIds(mediaLibraryId, aspectName);
|
|
98
|
+
} finally{
|
|
99
|
+
// Also on failure — a spinner left running garbles the error output.
|
|
100
|
+
spin.stop();
|
|
101
|
+
}
|
|
102
|
+
// Guardrails, not gates: pruning while either is true can be deliberate
|
|
103
|
+
// (e.g. clearing data before re-shaping a field), so warn and continue.
|
|
104
|
+
if (config.fields.some((field)=>field.name === aspectName)) {
|
|
105
|
+
this.warn(`"${aspectName}" is still declared in sanity.cli.ts — its data is likely in active use and will grow back.`);
|
|
106
|
+
}
|
|
107
|
+
if (storedIds.includes(aspectName)) {
|
|
108
|
+
this.warn(`Aspect definition "${aspectName}" still exists in the library — this only removes data, not the definition.`);
|
|
109
|
+
}
|
|
110
|
+
if (carriers.length === 0) {
|
|
111
|
+
this.log(`No assets in media library ${styleText('bold', mediaLibraryId)} carry ${styleText('bold', `aspects.${aspectName}`)} — nothing to prune.`);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const sample = carriers.slice(0, SAMPLE_SIZE);
|
|
115
|
+
// "documents", not "assets": under the raw perspective drafts and
|
|
116
|
+
// release versions are separate ids, so an asset count would overstate.
|
|
117
|
+
this.log(`${styleText('bold', String(carriers.length))} ${pluralize('document', carriers.length)} in media library ${styleText('bold', mediaLibraryId)} ${carriers.length === 1 ? 'carries' : 'carry'} ${styleText('bold', `aspects.${aspectName}`)}:`);
|
|
118
|
+
for (const id of sample){
|
|
119
|
+
this.log(` - ${id}`);
|
|
120
|
+
}
|
|
121
|
+
if (carriers.length > sample.length) {
|
|
122
|
+
this.log(styleText('dim', ` … and ${carriers.length - sample.length} more`));
|
|
123
|
+
}
|
|
124
|
+
if (!yes) {
|
|
125
|
+
this.log('');
|
|
126
|
+
this.log(`This would ${styleText('bold', 'irreversibly')} unset aspects.${aspectName} on all of them. Re-run with ${styleText('bold', '--yes')} to execute.`);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
let pruned = 0;
|
|
130
|
+
for(let offset = 0; offset < carriers.length; offset += BATCH_SIZE){
|
|
131
|
+
const batch = carriers.slice(offset, offset + BATCH_SIZE);
|
|
132
|
+
try {
|
|
133
|
+
await unsetAspectData({
|
|
134
|
+
aspectName,
|
|
135
|
+
assetIds: batch,
|
|
136
|
+
mediaLibraryId
|
|
137
|
+
});
|
|
138
|
+
} catch (batchError) {
|
|
139
|
+
// The progress made so far must survive into the error: re-running
|
|
140
|
+
// is safe (pruned assets no longer match the carrier query), so the
|
|
141
|
+
// message says how far it got and that a retry finishes the rest.
|
|
142
|
+
throw new Error(`Removed aspects.${aspectName} from ${pruned} of ${carriers.length} ${pluralize('document', carriers.length)} before a batch failed: ${getErrorMessage(batchError)}\n` + `Re-running the command with --yes is safe — already-pruned documents no longer ` + `match and only the remaining ${carriers.length - pruned} will be mutated.`, {
|
|
143
|
+
cause: batchError
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
pruned += batch.length;
|
|
147
|
+
this.log(styleText('dim', ` pruned ${pruned}/${carriers.length}`));
|
|
148
|
+
}
|
|
149
|
+
this.log('');
|
|
150
|
+
this.log(`${logSymbols.success} aspects.${aspectName} removed from ${pruned} ${pluralize('document', pruned)}.`);
|
|
151
|
+
} catch (error) {
|
|
152
|
+
const err = error;
|
|
153
|
+
// Ctrl+C in the library prompt and errors that already carry their exit
|
|
154
|
+
// story (`this.error`, CLIError from the resolver) pass through to
|
|
155
|
+
// `SanityCommand.catch` instead of being re-wrapped here.
|
|
156
|
+
if (err.name === 'ExitPromptError' || 'oclif' in err) {
|
|
157
|
+
throw err;
|
|
158
|
+
}
|
|
159
|
+
pruneAspectDataDebug('Failed to prune aspect data', {
|
|
160
|
+
aspectName,
|
|
161
|
+
error: err
|
|
162
|
+
});
|
|
163
|
+
this.error(styleText('bold', 'Failed to prune aspect data') + `\n\n${styleText('red', err.message)}`, {
|
|
164
|
+
exit: exitCodes.RUNTIME_ERROR
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
//# sourceMappingURL=prune-aspect-data.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/commands/media/prune-aspect-data.ts"],"sourcesContent":["import {styleText} from 'node:util'\n\nimport {Args, Flags} from '@oclif/core'\nimport {\n type CliConfig,\n exitCodes,\n ProjectRootNotFoundError,\n SanityCommand,\n subdebug,\n} from '@sanity/cli-core'\nimport {getErrorMessage} from '@sanity/cli-core/errors'\nimport {logSymbols, spinner} from '@sanity/cli-core/ux'\nimport {resolveWorkbenchConfig} from '@sanity/workbench-cli/deploy'\n\nimport {VALID_ASPECT_NAME} from '../../actions/media/aspectName.js'\nimport {resolveMediaLibraryId} from '../../actions/media/resolveMediaLibraryId.js'\nimport {\n getAspectDataCarrierIds,\n getStoredAspectIds,\n unsetAspectData,\n} from '../../services/mediaLibraries.js'\nimport {pluralize} from '../../util/pluralize.js'\n\nconst pruneAspectDataDebug = subdebug('media:prune-aspect-data')\n\nconst NOT_A_CONFIG_APP =\n 'This command must be run inside a Media Library config app — a project whose ' +\n '`sanity.cli.ts` declares `unstable_defineMediaLibrary(...)`. Scaffold one with ' +\n '`sanity init --template media-library-config`.'\n\n/** Unset-patch batch size per mutate request. */\nconst BATCH_SIZE = 100\n\n/** How many carrier ids the report lists before truncating. */\nconst SAMPLE_SIZE = 10\n\n/**\n * The deliberate, explicitly-invoked counterpart to the deploy-time aspect\n * sync: deploying a config only mirrors aspect *definitions* — `aspects.<name>`\n * values on assets are never touched and resurface if a matching field comes\n * back. This command removes that data. Irreversible fan-out mutation, so it\n * never asks for confirmation: without `--yes` it only reports what would change.\n */\nexport class MediaPruneAspectDataCommand extends SanityCommand<typeof MediaPruneAspectDataCommand> {\n static override args = {\n aspectName: Args.string({\n description: 'Name of the aspect whose data to remove from all assets',\n required: true,\n }),\n }\n\n static override description =\n \"Irreversibly remove an aspect's data (aspects.<name> values) from every asset of a media library\"\n\n static override examples = [\n {\n command: '<%= config.bin %> <%= command.id %> legacyAspect',\n description: 'Report which assets carry aspects.legacyAspect (changes nothing)',\n },\n {\n command: '<%= config.bin %> <%= command.id %> legacyAspect --yes',\n description: 'Remove aspects.legacyAspect from every asset that carries it',\n },\n ]\n\n static override flags = {\n 'media-library-id': Flags.string({\n description:\n 'The id of the media library to prune (defaults to the single active library of the configured organization)',\n }),\n yes: Flags.boolean({\n char: 'y',\n description:\n 'Execute the prune. Without this flag the command only reports what would change',\n }),\n }\n\n // Part of the beta config-app flow: the command requires a config app to\n // resolve its organization, so listing it in the general help would only\n // route legacy-flow users into the beta paradigm. The prune itself is\n // paradigm-neutral (it only unsets asset data) — when there is demand from\n // the legacy flow, ungate it from the config app and unhide it.\n static override hidden = true\n\n public async run(): Promise<void> {\n const {aspectName} = this.args\n const {'media-library-id': mediaLibraryIdFlag, yes = false} = this.flags\n\n // The name rule shared with the sync contract; also makes the\n // interpolation into the carrier GROQ filter injection-safe.\n if (!VALID_ASPECT_NAME.test(aspectName)) {\n this.error(`\"${aspectName}\" is not a valid aspect name (${VALID_ASPECT_NAME})`, {\n exit: exitCodes.USAGE_ERROR,\n })\n }\n\n let cliConfig: CliConfig\n try {\n cliConfig = await this.getCliConfig()\n } catch (err) {\n if (err instanceof ProjectRootNotFoundError) {\n this.error(NOT_A_CONFIG_APP, {exit: exitCodes.RUNTIME_ERROR})\n }\n throw err\n }\n\n const config = resolveWorkbenchConfig(cliConfig)\n if (!config) {\n this.error(NOT_A_CONFIG_APP, {exit: exitCodes.RUNTIME_ERROR})\n }\n\n try {\n // Not `isUnattended()`: that treats `--yes` as unattended, but here\n // `--yes` consents to the prune, not to skipping prompts — the report\n // path literally instructs \"Re-run with --yes\", and that run must still\n // offer the library picker in a multi-library organization. Only a\n // genuinely non-interactive run (CI, piped stdin) must not prompt.\n const mediaLibraryId = await resolveMediaLibraryId({\n mediaLibraryIdFlag,\n organizationId: config.organizationId,\n unattended: !this.resolveIsInteractive(),\n })\n\n const spin = spinner('Finding assets that carry the aspect').start()\n let storedIds: string[]\n let carriers: string[]\n try {\n storedIds = await getStoredAspectIds(mediaLibraryId, 'prune-aspect-data')\n carriers = await getAspectDataCarrierIds(mediaLibraryId, aspectName)\n } finally {\n // Also on failure — a spinner left running garbles the error output.\n spin.stop()\n }\n\n // Guardrails, not gates: pruning while either is true can be deliberate\n // (e.g. clearing data before re-shaping a field), so warn and continue.\n if (config.fields.some((field) => field.name === aspectName)) {\n this.warn(\n `\"${aspectName}\" is still declared in sanity.cli.ts — its data is likely in active use and will grow back.`,\n )\n }\n if (storedIds.includes(aspectName)) {\n this.warn(\n `Aspect definition \"${aspectName}\" still exists in the library — this only removes data, not the definition.`,\n )\n }\n\n if (carriers.length === 0) {\n this.log(\n `No assets in media library ${styleText('bold', mediaLibraryId)} carry ${styleText(\n 'bold',\n `aspects.${aspectName}`,\n )} — nothing to prune.`,\n )\n return\n }\n\n const sample = carriers.slice(0, SAMPLE_SIZE)\n // \"documents\", not \"assets\": under the raw perspective drafts and\n // release versions are separate ids, so an asset count would overstate.\n this.log(\n `${styleText('bold', String(carriers.length))} ${pluralize(\n 'document',\n carriers.length,\n )} in media library ${styleText('bold', mediaLibraryId)} ${\n carriers.length === 1 ? 'carries' : 'carry'\n } ${styleText('bold', `aspects.${aspectName}`)}:`,\n )\n for (const id of sample) {\n this.log(` - ${id}`)\n }\n if (carriers.length > sample.length) {\n this.log(styleText('dim', ` … and ${carriers.length - sample.length} more`))\n }\n\n if (!yes) {\n this.log('')\n this.log(\n `This would ${styleText(\n 'bold',\n 'irreversibly',\n )} unset aspects.${aspectName} on all of them. Re-run with ${styleText(\n 'bold',\n '--yes',\n )} to execute.`,\n )\n return\n }\n\n let pruned = 0\n for (let offset = 0; offset < carriers.length; offset += BATCH_SIZE) {\n const batch = carriers.slice(offset, offset + BATCH_SIZE)\n try {\n await unsetAspectData({aspectName, assetIds: batch, mediaLibraryId})\n } catch (batchError) {\n // The progress made so far must survive into the error: re-running\n // is safe (pruned assets no longer match the carrier query), so the\n // message says how far it got and that a retry finishes the rest.\n throw new Error(\n `Removed aspects.${aspectName} from ${pruned} of ${carriers.length} ${pluralize(\n 'document',\n carriers.length,\n )} before a batch failed: ${getErrorMessage(batchError)}\\n` +\n `Re-running the command with --yes is safe — already-pruned documents no longer ` +\n `match and only the remaining ${carriers.length - pruned} will be mutated.`,\n {cause: batchError},\n )\n }\n pruned += batch.length\n this.log(styleText('dim', ` pruned ${pruned}/${carriers.length}`))\n }\n\n this.log('')\n this.log(\n `${logSymbols.success} aspects.${aspectName} removed from ${pruned} ${pluralize(\n 'document',\n pruned,\n )}.`,\n )\n } catch (error) {\n const err = error as Error\n // Ctrl+C in the library prompt and errors that already carry their exit\n // story (`this.error`, CLIError from the resolver) pass through to\n // `SanityCommand.catch` instead of being re-wrapped here.\n if (err.name === 'ExitPromptError' || 'oclif' in err) {\n throw err\n }\n pruneAspectDataDebug('Failed to prune aspect data', {\n aspectName,\n error: err,\n })\n this.error(\n styleText('bold', 'Failed to prune aspect data') + `\\n\\n${styleText('red', err.message)}`,\n {exit: exitCodes.RUNTIME_ERROR},\n )\n }\n }\n}\n"],"names":["styleText","Args","Flags","exitCodes","ProjectRootNotFoundError","SanityCommand","subdebug","getErrorMessage","logSymbols","spinner","resolveWorkbenchConfig","VALID_ASPECT_NAME","resolveMediaLibraryId","getAspectDataCarrierIds","getStoredAspectIds","unsetAspectData","pluralize","pruneAspectDataDebug","NOT_A_CONFIG_APP","BATCH_SIZE","SAMPLE_SIZE","MediaPruneAspectDataCommand","args","aspectName","string","description","required","examples","command","flags","yes","boolean","char","hidden","run","mediaLibraryIdFlag","test","error","exit","USAGE_ERROR","cliConfig","getCliConfig","err","RUNTIME_ERROR","config","mediaLibraryId","organizationId","unattended","resolveIsInteractive","spin","start","storedIds","carriers","stop","fields","some","field","name","warn","includes","length","log","sample","slice","String","id","pruned","offset","batch","assetIds","batchError","Error","cause","success","message"],"mappings":"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"}
|