@sanity/cli 7.5.0 → 7.7.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 +6 -2
- package/dist/actions/build/buildApp.js +7 -183
- package/dist/actions/build/buildApp.js.map +1 -1
- package/dist/actions/build/buildStudio.js +6 -213
- package/dist/actions/build/buildStudio.js.map +1 -1
- package/dist/actions/build/shouldAutoUpdate.js +100 -22
- package/dist/actions/build/shouldAutoUpdate.js.map +1 -1
- package/dist/actions/deploy/{createStudioUserApplication.js → createUserApplication.js} +56 -5
- package/dist/actions/deploy/createUserApplication.js.map +1 -0
- package/dist/actions/deploy/deployApp.js +253 -183
- package/dist/actions/deploy/deployApp.js.map +1 -1
- package/dist/actions/deploy/deployChecks.js +284 -0
- package/dist/actions/deploy/deployChecks.js.map +1 -0
- package/dist/actions/deploy/deployRunner.js +78 -0
- package/dist/actions/deploy/deployRunner.js.map +1 -0
- package/dist/actions/deploy/deployStudio.js +200 -197
- package/dist/actions/deploy/deployStudio.js.map +1 -1
- package/dist/actions/deploy/deployStudioSchemasAndManifests.js +2 -3
- package/dist/actions/deploy/deployStudioSchemasAndManifests.js.map +1 -1
- package/dist/actions/deploy/deploymentPlan.js +117 -0
- package/dist/actions/deploy/deploymentPlan.js.map +1 -0
- package/dist/actions/deploy/findUserApplication.js +209 -0
- package/dist/actions/deploy/findUserApplication.js.map +1 -0
- package/dist/actions/deploy/resolveDeployTarget.js +185 -0
- package/dist/actions/deploy/resolveDeployTarget.js.map +1 -0
- package/dist/actions/deploy/urlUtils.js +4 -0
- package/dist/actions/deploy/urlUtils.js.map +1 -1
- package/dist/actions/dev/devAction.js +1 -1
- package/dist/actions/dev/devAction.js.map +1 -1
- package/dist/actions/dev/servers/getDevServerConfig.js +18 -6
- package/dist/actions/dev/servers/getDevServerConfig.js.map +1 -1
- package/dist/actions/dev/servers/startAppDevServer.js +1 -1
- package/dist/actions/dev/servers/startAppDevServer.js.map +1 -1
- package/dist/actions/dev/servers/startStudioDevServer.js +2 -1
- package/dist/actions/dev/servers/startStudioDevServer.js.map +1 -1
- package/dist/actions/manifest/extractCoreAppManifest.js +15 -1
- package/dist/actions/manifest/extractCoreAppManifest.js.map +1 -1
- package/dist/commands/deploy.js +31 -12
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/dev.js +2 -1
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/init.js +2 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/exports/index.d.ts +9 -0
- package/dist/exports/index.js +1 -8
- package/dist/exports/index.js.map +1 -1
- package/dist/server/devServer.js +27 -3
- package/dist/server/devServer.js.map +1 -1
- package/dist/services/userApplications.js.map +1 -1
- package/dist/util/appId.js +13 -5
- package/dist/util/appId.js.map +1 -1
- package/dist/util/compareDependencyVersions.js.map +1 -1
- package/dist/util/determineIsApp.js +1 -1
- package/dist/util/determineIsApp.js.map +1 -1
- package/dist/util/errorMessages.js +2 -0
- package/dist/util/errorMessages.js.map +1 -1
- package/oclif.manifest.json +223 -203
- package/package.json +8 -8
- package/dist/actions/build/handlePrereleaseVersions.js +0 -44
- package/dist/actions/build/handlePrereleaseVersions.js.map +0 -1
- package/dist/actions/deploy/createStudioUserApplication.js.map +0 -1
- package/dist/actions/deploy/createUserApplicationForApp.js +0 -56
- package/dist/actions/deploy/createUserApplicationForApp.js.map +0 -1
- package/dist/actions/deploy/findUserApplicationForApp.js +0 -111
- package/dist/actions/deploy/findUserApplicationForApp.js.map +0 -1
- package/dist/actions/deploy/findUserApplicationForStudio.js +0 -172
- package/dist/actions/deploy/findUserApplicationForStudio.js.map +0 -1
- package/dist/actions/deploy/viewDeployment.js +0 -32
- package/dist/actions/deploy/viewDeployment.js.map +0 -1
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import { exitCodes, getLocalPackageVersion } from '@sanity/cli-core';
|
|
2
|
+
import { spinner } from '@sanity/cli-core/ux';
|
|
3
|
+
import { checkBuiltOutput } from '@sanity/workbench-cli/deploy';
|
|
4
|
+
import { resolveAppIdIssue } from '../../util/appId.js';
|
|
5
|
+
import { APP_ID_NOT_FOUND_IN_ORGANIZATION } from '../../util/errorMessages.js';
|
|
6
|
+
import { getErrorMessage } from '../../util/getErrorMessage.js';
|
|
7
|
+
import { getAutoUpdateIssueMessage, getAutoUpdateMigrationHint, resolveAutoUpdates } from '../build/shouldAutoUpdate.js';
|
|
8
|
+
import { checkDir } from './checkDir.js';
|
|
9
|
+
import { deployDebug } from './deployDebug.js';
|
|
10
|
+
import { resolveAppDeployTarget, resolveStudioDeployTarget } from './resolveDeployTarget.js';
|
|
11
|
+
import { getCoreAppUrl } from './urlUtils.js';
|
|
12
|
+
export function createFailFastReporter(output) {
|
|
13
|
+
return {
|
|
14
|
+
report (check) {
|
|
15
|
+
// Fixes surface in both modes: appended after the message here, and in the
|
|
16
|
+
// dry-run report, so the problem and its fix never drift apart.
|
|
17
|
+
const text = check.solution ? `${check.message}: ${check.solution}` : check.message;
|
|
18
|
+
if (check.status === 'fail') {
|
|
19
|
+
output.error(text, {
|
|
20
|
+
exit: check.exitCode ?? 1
|
|
21
|
+
});
|
|
22
|
+
} else if (check.status === 'warn') {
|
|
23
|
+
output.warn(text);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export function createCollectingReporter() {
|
|
29
|
+
const results = [];
|
|
30
|
+
return {
|
|
31
|
+
report (check) {
|
|
32
|
+
results.push(check);
|
|
33
|
+
},
|
|
34
|
+
results
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Runs a fallible step and turns a throw into a `fail` check. In a real deploy
|
|
39
|
+
* that fail exits (aborting the run); in a dry run it's recorded and `null`
|
|
40
|
+
* comes back so the caller can skip the rest of the step. `name` labels the
|
|
41
|
+
* step in debug logs.
|
|
42
|
+
*/ export async function runStep(reporter, name, work, formatError = getErrorMessage, solution) {
|
|
43
|
+
try {
|
|
44
|
+
return await work();
|
|
45
|
+
} catch (err) {
|
|
46
|
+
deployDebug(`${name} step failed`, err);
|
|
47
|
+
reporter.report({
|
|
48
|
+
message: formatError(err),
|
|
49
|
+
solution,
|
|
50
|
+
status: 'fail'
|
|
51
|
+
});
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export async function checkPackageVersion(reporter, { moduleName, workDir }) {
|
|
56
|
+
const version = await getLocalPackageVersion(moduleName, workDir);
|
|
57
|
+
reporter.report(version ? {
|
|
58
|
+
message: `Using ${moduleName} ${version}`,
|
|
59
|
+
status: 'pass',
|
|
60
|
+
version
|
|
61
|
+
} : {
|
|
62
|
+
message: `Failed to find installed ${moduleName} version`,
|
|
63
|
+
solution: `Install ${moduleName} in this project`,
|
|
64
|
+
status: 'fail'
|
|
65
|
+
});
|
|
66
|
+
return version;
|
|
67
|
+
}
|
|
68
|
+
export function checkAutoUpdates(reporter, { cliConfig, flags }) {
|
|
69
|
+
const { enabled, issue } = resolveAutoUpdates({
|
|
70
|
+
cliConfig,
|
|
71
|
+
flags
|
|
72
|
+
});
|
|
73
|
+
if (issue) {
|
|
74
|
+
reporter.report({
|
|
75
|
+
message: getAutoUpdateIssueMessage(issue),
|
|
76
|
+
// A config conflict makes a real deploy refuse to run; deprecations only warn
|
|
77
|
+
status: issue.type === 'conflicting-config' ? 'fail' : 'warn'
|
|
78
|
+
});
|
|
79
|
+
// The deprecated top-level config also gets the styled migration edit, the
|
|
80
|
+
// same second warning the build/dev path prints through shouldAutoUpdate.
|
|
81
|
+
if (issue.type === 'deprecated-config') {
|
|
82
|
+
reporter.report({
|
|
83
|
+
message: getAutoUpdateMigrationHint(cliConfig.autoUpdates),
|
|
84
|
+
status: 'warn'
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return enabled;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* The dry-run form of the `app.id` config check a real deploy runs in
|
|
92
|
+
* `findUserApplication`: a conflict fails (both `app.id` and `deployment.appId`
|
|
93
|
+
* set), the deprecated config alone warns.
|
|
94
|
+
*/ export function checkAppId(reporter, { cliConfig }) {
|
|
95
|
+
const issue = resolveAppIdIssue(cliConfig);
|
|
96
|
+
if (issue === 'conflicting-config') {
|
|
97
|
+
reporter.report({
|
|
98
|
+
message: 'Both `app.id` (deprecated) and `deployment.appId` are set',
|
|
99
|
+
solution: 'Remove `app.id` from sanity.cli.ts',
|
|
100
|
+
status: 'fail'
|
|
101
|
+
});
|
|
102
|
+
} else if (issue === 'deprecated-config') {
|
|
103
|
+
reporter.report({
|
|
104
|
+
message: 'The `app.id` config is deprecated',
|
|
105
|
+
solution: 'Move it to `deployment.appId` in sanity.cli.ts',
|
|
106
|
+
status: 'warn'
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
export async function checkBuild(reporter, { build, skipReason, successMessage }) {
|
|
111
|
+
if (skipReason) {
|
|
112
|
+
reporter.report({
|
|
113
|
+
message: skipReason,
|
|
114
|
+
status: 'skip'
|
|
115
|
+
});
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
await runStep(reporter, 'build', async ()=>{
|
|
119
|
+
await build();
|
|
120
|
+
reporter.report({
|
|
121
|
+
message: successMessage,
|
|
122
|
+
status: 'pass'
|
|
123
|
+
});
|
|
124
|
+
}, (err)=>`Build failed: ${getErrorMessage(err)}`, 'Fix the build error above, then retry');
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* The deploy directory must exist and hold the right build output: a federation
|
|
128
|
+
* remote for a workbench app, an `index.html` SPA otherwise.
|
|
129
|
+
*/ export async function verifyOutputDir({ isWorkbenchApp, reporter, sourceDir }) {
|
|
130
|
+
const spin = spinner('Verifying local content...').start();
|
|
131
|
+
const verifyBuild = isWorkbenchApp ? checkBuiltOutput : checkDir;
|
|
132
|
+
try {
|
|
133
|
+
await verifyBuild(sourceDir);
|
|
134
|
+
spin.succeed();
|
|
135
|
+
} catch (err) {
|
|
136
|
+
spin.fail();
|
|
137
|
+
deployDebug('Error checking directory', err);
|
|
138
|
+
reporter.report({
|
|
139
|
+
message: getErrorMessage(err),
|
|
140
|
+
solution: 'Run the build first, or check the output directory',
|
|
141
|
+
status: 'fail'
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* The single diagnosis for each app deploy-target verdict, shared by the
|
|
147
|
+
* dry-run report and the real deploy's unattended error paths so message,
|
|
148
|
+
* fix, and exit code can't drift between the two.
|
|
149
|
+
*/ export function describeAppTarget(resolution, { title } = {}) {
|
|
150
|
+
switch(resolution.type){
|
|
151
|
+
case 'blocked':
|
|
152
|
+
{
|
|
153
|
+
return {
|
|
154
|
+
message: `Deploy target not resolved — ${resolution.message}`,
|
|
155
|
+
status: 'skip'
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
case 'found':
|
|
159
|
+
{
|
|
160
|
+
const { application } = resolution;
|
|
161
|
+
const title = application.title ?? application.appHost;
|
|
162
|
+
const url = getCoreAppUrl(application.organizationId, application.id);
|
|
163
|
+
return {
|
|
164
|
+
message: `Deploys to existing application "${title}" at ${url}`,
|
|
165
|
+
status: 'pass',
|
|
166
|
+
target: {
|
|
167
|
+
applicationId: application.id,
|
|
168
|
+
title: application.title ?? null,
|
|
169
|
+
url
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
case 'invalid':
|
|
174
|
+
{
|
|
175
|
+
return {
|
|
176
|
+
message: APP_ID_NOT_FOUND_IN_ORGANIZATION,
|
|
177
|
+
solution: 'Check `deployment.appId` matches an app in your organization',
|
|
178
|
+
status: 'fail'
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
case 'needs-input':
|
|
182
|
+
{
|
|
183
|
+
return {
|
|
184
|
+
exitCode: exitCodes.USAGE_ERROR,
|
|
185
|
+
message: `No \`deployment.appId\` configured (${resolution.existing.length} existing ${resolution.existing.length === 1 ? 'application' : 'applications'} to choose from)`,
|
|
186
|
+
solution: 'Add `deployment.appId` to sanity.cli.ts',
|
|
187
|
+
status: 'fail'
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
// Without --title, creating an app needs a prompt no unattended run can answer
|
|
191
|
+
case 'would-create':
|
|
192
|
+
{
|
|
193
|
+
if (title) {
|
|
194
|
+
return {
|
|
195
|
+
message: `Would create a new application "${title}"`,
|
|
196
|
+
status: 'pass'
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
return {
|
|
200
|
+
exitCode: exitCodes.USAGE_ERROR,
|
|
201
|
+
message: 'No application to deploy to — creating one needs a title',
|
|
202
|
+
solution: 'Pass `--title "<name>"` or set `app.title` in sanity.cli.ts to create one, or set `deployment.appId` to deploy to an existing app',
|
|
203
|
+
status: 'fail'
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
export function describeAppTargetError(err, organizationId) {
|
|
209
|
+
return err?.statusCode === 403 ? `You don’t have permission to view applications for the configured organization ID ("${organizationId}"). Verify the organization ID, or ask your organization’s admin for access.` : `Failed to resolve deploy target: ${getErrorMessage(err)}`;
|
|
210
|
+
}
|
|
211
|
+
export async function checkAppTarget(reporter, { appId, organizationId, title }) {
|
|
212
|
+
await runStep(reporter, 'target', async ()=>reporter.report(describeAppTarget(await resolveAppDeployTarget({
|
|
213
|
+
appId,
|
|
214
|
+
organizationId
|
|
215
|
+
}), {
|
|
216
|
+
title
|
|
217
|
+
})), (err)=>describeAppTargetError(err, organizationId));
|
|
218
|
+
}
|
|
219
|
+
/** Same contract as {@link describeAppTarget}, for the studio verdicts. */ export function describeStudioTarget(resolution, { isExternal, title }) {
|
|
220
|
+
const studioUrl = (host)=>isExternal ? host : `https://${host}.sanity.studio`;
|
|
221
|
+
switch(resolution.type){
|
|
222
|
+
case 'blocked':
|
|
223
|
+
{
|
|
224
|
+
return {
|
|
225
|
+
message: `Deploy target not resolved — ${resolution.message}`,
|
|
226
|
+
status: 'skip'
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
case 'found':
|
|
230
|
+
{
|
|
231
|
+
const url = studioUrl(resolution.application.appHost);
|
|
232
|
+
return {
|
|
233
|
+
message: `Deploys to existing studio ${url}`,
|
|
234
|
+
status: 'pass',
|
|
235
|
+
target: {
|
|
236
|
+
applicationId: resolution.application.id,
|
|
237
|
+
title: resolution.application.title ?? null,
|
|
238
|
+
url
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
case 'invalid':
|
|
243
|
+
{
|
|
244
|
+
return {
|
|
245
|
+
// A bad host is a usage error; other invalid targets exit 1
|
|
246
|
+
exitCode: resolution.reason === 'invalid-host' ? exitCodes.USAGE_ERROR : 1,
|
|
247
|
+
message: resolution.message,
|
|
248
|
+
solution: 'Check `studioHost` and `deployment.appId` in sanity.cli.ts',
|
|
249
|
+
status: 'fail'
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
case 'needs-input':
|
|
253
|
+
{
|
|
254
|
+
return {
|
|
255
|
+
exitCode: exitCodes.USAGE_ERROR,
|
|
256
|
+
message: isExternal ? 'No external studio URL configured' : 'No studio hostname configured',
|
|
257
|
+
solution: isExternal ? 'Set `studioHost` in sanity.cli.ts, or pass the full URL with --url' : 'Set `studioHost` in sanity.cli.ts, or pass a hostname with --url',
|
|
258
|
+
status: 'fail'
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
case 'would-create':
|
|
262
|
+
{
|
|
263
|
+
const url = studioUrl(resolution.appHost);
|
|
264
|
+
const titled = title ? ` titled "${title}"` : '';
|
|
265
|
+
return {
|
|
266
|
+
message: isExternal ? `Would register external studio at ${resolution.appHost}${titled}` : `Would create studio hostname ${url}${titled} (name availability is checked on deploy)`,
|
|
267
|
+
status: 'pass',
|
|
268
|
+
target: {
|
|
269
|
+
applicationId: null,
|
|
270
|
+
title: null,
|
|
271
|
+
url
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
export async function checkStudioTarget(reporter, options) {
|
|
278
|
+
await runStep(reporter, 'target', async ()=>reporter.report(describeStudioTarget(await resolveStudioDeployTarget(options), {
|
|
279
|
+
isExternal: options.isExternal,
|
|
280
|
+
title: options.title
|
|
281
|
+
})), (err)=>`Failed to resolve deploy target: ${getErrorMessage(err)}`);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
//# sourceMappingURL=deployChecks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/deploy/deployChecks.ts"],"sourcesContent":["import {type CliConfig, exitCodes, getLocalPackageVersion, type Output} from '@sanity/cli-core'\nimport {spinner} from '@sanity/cli-core/ux'\nimport {checkBuiltOutput} from '@sanity/workbench-cli/deploy'\n\nimport {resolveAppIdIssue} from '../../util/appId.js'\nimport {APP_ID_NOT_FOUND_IN_ORGANIZATION} from '../../util/errorMessages.js'\nimport {getErrorMessage} from '../../util/getErrorMessage.js'\nimport {\n getAutoUpdateIssueMessage,\n getAutoUpdateMigrationHint,\n resolveAutoUpdates,\n} from '../build/shouldAutoUpdate.js'\nimport {checkDir} from './checkDir.js'\nimport {deployDebug} from './deployDebug.js'\nimport {\n type AppDeployTargetResolution,\n resolveAppDeployTarget,\n resolveStudioDeployTarget,\n type StudioDeployTargetResolution,\n} from './resolveDeployTarget.js'\nimport {type DeployFlags} from './types.js'\nimport {getCoreAppUrl} from './urlUtils.js'\n\ntype DeployCheckStatus = 'fail' | 'pass' | 'skip' | 'warn'\n\n/**\n * Where a deploy resolves to, computed once from the deploy-target verdict. The\n * dry-run report and its JSON both read this, so the human and machine outputs\n * can't drift.\n */\nexport interface DeployTarget {\n /** The application the deploy targets; `null` when a deploy would create one. */\n applicationId: string | null\n /** The application's title; `null` when it has none (or isn't created yet). */\n title: string | null\n /** Where the deployed studio/app is reachable; `null` when it can't be resolved yet. */\n url: string | null\n}\n\nexport interface DeployCheck {\n message: string\n status: DeployCheckStatus\n\n /** Exit code a real deploy uses when this check fails; defaults to 1 */\n exitCode?: number\n /** Actionable fix, shown under a failing or warning check */\n solution?: string\n /** Set on the deploy-target check with the resolved target both reporters read. */\n target?: DeployTarget\n /** Set on the package-version check with the version both reporters read. */\n version?: string\n}\n\n/**\n * Where deploy steps send their check outcomes — and the only place the deploy\n * mode lives. A real deploy fails fast: a `fail` prints and exits immediately,\n * which aborts the sequence. A dry run collects every outcome and never exits.\n * Steps just call `report`; they never know which mode is running.\n */\nexport interface CheckReporter {\n report(check: DeployCheck): void\n}\n\nexport function createFailFastReporter(output: Output): CheckReporter {\n return {\n report(check) {\n // Fixes surface in both modes: appended after the message here, and in the\n // dry-run report, so the problem and its fix never drift apart.\n const text = check.solution ? `${check.message}: ${check.solution}` : check.message\n if (check.status === 'fail') {\n output.error(text, {exit: check.exitCode ?? 1})\n } else if (check.status === 'warn') {\n output.warn(text)\n }\n },\n }\n}\n\nexport function createCollectingReporter(): CheckReporter & {results: DeployCheck[]} {\n const results: DeployCheck[] = []\n return {\n report(check) {\n results.push(check)\n },\n results,\n }\n}\n\n/**\n * Runs a fallible step and turns a throw into a `fail` check. In a real deploy\n * that fail exits (aborting the run); in a dry run it's recorded and `null`\n * comes back so the caller can skip the rest of the step. `name` labels the\n * step in debug logs.\n */\nexport async function runStep<T>(\n reporter: CheckReporter,\n name: string,\n work: () => Promise<T>,\n formatError: (err: unknown) => string = getErrorMessage,\n solution?: string,\n): Promise<T | null> {\n try {\n return await work()\n } catch (err) {\n deployDebug(`${name} step failed`, err)\n reporter.report({message: formatError(err), solution, status: 'fail'})\n return null\n }\n}\n\nexport async function checkPackageVersion(\n reporter: CheckReporter,\n {moduleName, workDir}: {moduleName: string; workDir: string},\n): Promise<string | null> {\n const version = await getLocalPackageVersion(moduleName, workDir)\n reporter.report(\n version\n ? {message: `Using ${moduleName} ${version}`, status: 'pass', version}\n : {\n message: `Failed to find installed ${moduleName} version`,\n solution: `Install ${moduleName} in this project`,\n status: 'fail',\n },\n )\n return version\n}\n\nexport function checkAutoUpdates(\n reporter: CheckReporter,\n {cliConfig, flags}: {cliConfig: CliConfig; flags: DeployFlags},\n): boolean {\n const {enabled, issue} = resolveAutoUpdates({cliConfig, flags})\n\n if (issue) {\n reporter.report({\n message: getAutoUpdateIssueMessage(issue),\n // A config conflict makes a real deploy refuse to run; deprecations only warn\n status: issue.type === 'conflicting-config' ? 'fail' : 'warn',\n })\n\n // The deprecated top-level config also gets the styled migration edit, the\n // same second warning the build/dev path prints through shouldAutoUpdate.\n if (issue.type === 'deprecated-config') {\n reporter.report({\n message: getAutoUpdateMigrationHint(cliConfig.autoUpdates),\n status: 'warn',\n })\n }\n }\n\n return enabled\n}\n\n/**\n * The dry-run form of the `app.id` config check a real deploy runs in\n * `findUserApplication`: a conflict fails (both `app.id` and `deployment.appId`\n * set), the deprecated config alone warns.\n */\nexport function checkAppId(reporter: CheckReporter, {cliConfig}: {cliConfig: CliConfig}): void {\n const issue = resolveAppIdIssue(cliConfig)\n if (issue === 'conflicting-config') {\n reporter.report({\n message: 'Both `app.id` (deprecated) and `deployment.appId` are set',\n solution: 'Remove `app.id` from sanity.cli.ts',\n status: 'fail',\n })\n } else if (issue === 'deprecated-config') {\n reporter.report({\n message: 'The `app.id` config is deprecated',\n solution: 'Move it to `deployment.appId` in sanity.cli.ts',\n status: 'warn',\n })\n }\n}\n\nexport async function checkBuild(\n reporter: CheckReporter,\n {\n build,\n skipReason,\n successMessage,\n }: {build: () => Promise<void>; skipReason: string | undefined; successMessage: string},\n): Promise<void> {\n if (skipReason) {\n reporter.report({message: skipReason, status: 'skip'})\n return\n }\n\n await runStep(\n reporter,\n 'build',\n async () => {\n await build()\n reporter.report({message: successMessage, status: 'pass'})\n },\n (err) => `Build failed: ${getErrorMessage(err)}`,\n 'Fix the build error above, then retry',\n )\n}\n\n/**\n * The deploy directory must exist and hold the right build output: a federation\n * remote for a workbench app, an `index.html` SPA otherwise.\n */\nexport async function verifyOutputDir({\n isWorkbenchApp,\n reporter,\n sourceDir,\n}: {\n isWorkbenchApp: boolean\n reporter: CheckReporter\n sourceDir: string\n}): Promise<void> {\n const spin = spinner('Verifying local content...').start()\n const verifyBuild = isWorkbenchApp ? checkBuiltOutput : checkDir\n try {\n await verifyBuild(sourceDir)\n spin.succeed()\n } catch (err) {\n spin.fail()\n deployDebug('Error checking directory', err)\n reporter.report({\n message: getErrorMessage(err),\n solution: 'Run the build first, or check the output directory',\n status: 'fail',\n })\n }\n}\n\n/**\n * The single diagnosis for each app deploy-target verdict, shared by the\n * dry-run report and the real deploy's unattended error paths so message,\n * fix, and exit code can't drift between the two.\n */\nexport function describeAppTarget(\n resolution: AppDeployTargetResolution,\n {title}: {title?: string} = {},\n): DeployCheck {\n switch (resolution.type) {\n case 'blocked': {\n return {message: `Deploy target not resolved — ${resolution.message}`, status: 'skip'}\n }\n case 'found': {\n const {application} = resolution\n const title = application.title ?? application.appHost\n const url = getCoreAppUrl(application.organizationId, application.id)\n return {\n message: `Deploys to existing application \"${title}\" at ${url}`,\n status: 'pass',\n target: {applicationId: application.id, title: application.title ?? null, url},\n }\n }\n case 'invalid': {\n return {\n message: APP_ID_NOT_FOUND_IN_ORGANIZATION,\n solution: 'Check `deployment.appId` matches an app in your organization',\n status: 'fail',\n }\n }\n case 'needs-input': {\n return {\n exitCode: exitCodes.USAGE_ERROR,\n message: `No \\`deployment.appId\\` configured (${resolution.existing.length} existing ${resolution.existing.length === 1 ? 'application' : 'applications'} to choose from)`,\n solution: 'Add `deployment.appId` to sanity.cli.ts',\n status: 'fail',\n }\n }\n // Without --title, creating an app needs a prompt no unattended run can answer\n case 'would-create': {\n if (title) {\n return {message: `Would create a new application \"${title}\"`, status: 'pass'}\n }\n return {\n exitCode: exitCodes.USAGE_ERROR,\n message: 'No application to deploy to — creating one needs a title',\n solution:\n 'Pass `--title \"<name>\"` or set `app.title` in sanity.cli.ts to create one, or set `deployment.appId` to deploy to an existing app',\n status: 'fail',\n }\n }\n }\n}\n\nexport function describeAppTargetError(err: unknown, organizationId: string | undefined): string {\n return (err as {statusCode?: number})?.statusCode === 403\n ? `You don’t have permission to view applications for the configured organization ID (\"${organizationId}\"). Verify the organization ID, or ask your organization’s admin for access.`\n : `Failed to resolve deploy target: ${getErrorMessage(err)}`\n}\n\nexport async function checkAppTarget(\n reporter: CheckReporter,\n {\n appId,\n organizationId,\n title,\n }: {appId: string | undefined; organizationId: string | undefined; title?: string},\n): Promise<void> {\n await runStep(\n reporter,\n 'target',\n async () =>\n reporter.report(\n describeAppTarget(await resolveAppDeployTarget({appId, organizationId}), {title}),\n ),\n (err) => describeAppTargetError(err, organizationId),\n )\n}\n\n/** Same contract as {@link describeAppTarget}, for the studio verdicts. */\nexport function describeStudioTarget(\n resolution: StudioDeployTargetResolution,\n {isExternal, title}: {isExternal: boolean; title?: string},\n): DeployCheck {\n const studioUrl = (host: string) => (isExternal ? host : `https://${host}.sanity.studio`)\n\n switch (resolution.type) {\n case 'blocked': {\n return {message: `Deploy target not resolved — ${resolution.message}`, status: 'skip'}\n }\n case 'found': {\n const url = studioUrl(resolution.application.appHost)\n return {\n message: `Deploys to existing studio ${url}`,\n status: 'pass',\n target: {\n applicationId: resolution.application.id,\n title: resolution.application.title ?? null,\n url,\n },\n }\n }\n case 'invalid': {\n return {\n // A bad host is a usage error; other invalid targets exit 1\n exitCode: resolution.reason === 'invalid-host' ? exitCodes.USAGE_ERROR : 1,\n message: resolution.message,\n solution: 'Check `studioHost` and `deployment.appId` in sanity.cli.ts',\n status: 'fail',\n }\n }\n case 'needs-input': {\n return {\n exitCode: exitCodes.USAGE_ERROR,\n message: isExternal ? 'No external studio URL configured' : 'No studio hostname configured',\n solution: isExternal\n ? 'Set `studioHost` in sanity.cli.ts, or pass the full URL with --url'\n : 'Set `studioHost` in sanity.cli.ts, or pass a hostname with --url',\n status: 'fail',\n }\n }\n case 'would-create': {\n const url = studioUrl(resolution.appHost)\n const titled = title ? ` titled \"${title}\"` : ''\n return {\n message: isExternal\n ? `Would register external studio at ${resolution.appHost}${titled}`\n : `Would create studio hostname ${url}${titled} (name availability is checked on deploy)`,\n status: 'pass',\n target: {applicationId: null, title: null, url},\n }\n }\n }\n}\n\nexport async function checkStudioTarget(\n reporter: CheckReporter,\n options: {\n appId: string | undefined\n isExternal: boolean\n projectId: string | undefined\n studioHost: string | undefined\n title: string | undefined\n urlFlag: string | undefined\n },\n): Promise<void> {\n await runStep(\n reporter,\n 'target',\n async () =>\n reporter.report(\n describeStudioTarget(await resolveStudioDeployTarget(options), {\n isExternal: options.isExternal,\n title: options.title,\n }),\n ),\n (err) => `Failed to resolve deploy target: ${getErrorMessage(err)}`,\n )\n}\n"],"names":["exitCodes","getLocalPackageVersion","spinner","checkBuiltOutput","resolveAppIdIssue","APP_ID_NOT_FOUND_IN_ORGANIZATION","getErrorMessage","getAutoUpdateIssueMessage","getAutoUpdateMigrationHint","resolveAutoUpdates","checkDir","deployDebug","resolveAppDeployTarget","resolveStudioDeployTarget","getCoreAppUrl","createFailFastReporter","output","report","check","text","solution","message","status","error","exit","exitCode","warn","createCollectingReporter","results","push","runStep","reporter","name","work","formatError","err","checkPackageVersion","moduleName","workDir","version","checkAutoUpdates","cliConfig","flags","enabled","issue","type","autoUpdates","checkAppId","checkBuild","build","skipReason","successMessage","verifyOutputDir","isWorkbenchApp","sourceDir","spin","start","verifyBuild","succeed","fail","describeAppTarget","resolution","title","application","appHost","url","organizationId","id","target","applicationId","USAGE_ERROR","existing","length","describeAppTargetError","statusCode","checkAppTarget","appId","describeStudioTarget","isExternal","studioUrl","host","reason","titled","checkStudioTarget","options"],"mappings":"AAAA,SAAwBA,SAAS,EAAEC,sBAAsB,QAAoB,mBAAkB;AAC/F,SAAQC,OAAO,QAAO,sBAAqB;AAC3C,SAAQC,gBAAgB,QAAO,+BAA8B;AAE7D,SAAQC,iBAAiB,QAAO,sBAAqB;AACrD,SAAQC,gCAAgC,QAAO,8BAA6B;AAC5E,SAAQC,eAAe,QAAO,gCAA+B;AAC7D,SACEC,yBAAyB,EACzBC,0BAA0B,EAC1BC,kBAAkB,QACb,+BAA8B;AACrC,SAAQC,QAAQ,QAAO,gBAAe;AACtC,SAAQC,WAAW,QAAO,mBAAkB;AAC5C,SAEEC,sBAAsB,EACtBC,yBAAyB,QAEpB,2BAA0B;AAEjC,SAAQC,aAAa,QAAO,gBAAe;AA0C3C,OAAO,SAASC,uBAAuBC,MAAc;IACnD,OAAO;QACLC,QAAOC,KAAK;YACV,2EAA2E;YAC3E,gEAAgE;YAChE,MAAMC,OAAOD,MAAME,QAAQ,GAAG,GAAGF,MAAMG,OAAO,CAAC,EAAE,EAAEH,MAAME,QAAQ,EAAE,GAAGF,MAAMG,OAAO;YACnF,IAAIH,MAAMI,MAAM,KAAK,QAAQ;gBAC3BN,OAAOO,KAAK,CAACJ,MAAM;oBAACK,MAAMN,MAAMO,QAAQ,IAAI;gBAAC;YAC/C,OAAO,IAAIP,MAAMI,MAAM,KAAK,QAAQ;gBAClCN,OAAOU,IAAI,CAACP;YACd;QACF;IACF;AACF;AAEA,OAAO,SAASQ;IACd,MAAMC,UAAyB,EAAE;IACjC,OAAO;QACLX,QAAOC,KAAK;YACVU,QAAQC,IAAI,CAACX;QACf;QACAU;IACF;AACF;AAEA;;;;;CAKC,GACD,OAAO,eAAeE,QACpBC,QAAuB,EACvBC,IAAY,EACZC,IAAsB,EACtBC,cAAwC5B,eAAe,EACvDc,QAAiB;IAEjB,IAAI;QACF,OAAO,MAAMa;IACf,EAAE,OAAOE,KAAK;QACZxB,YAAY,GAAGqB,KAAK,YAAY,CAAC,EAAEG;QACnCJ,SAASd,MAAM,CAAC;YAACI,SAASa,YAAYC;YAAMf;YAAUE,QAAQ;QAAM;QACpE,OAAO;IACT;AACF;AAEA,OAAO,eAAec,oBACpBL,QAAuB,EACvB,EAACM,UAAU,EAAEC,OAAO,EAAwC;IAE5D,MAAMC,UAAU,MAAMtC,uBAAuBoC,YAAYC;IACzDP,SAASd,MAAM,CACbsB,UACI;QAAClB,SAAS,CAAC,MAAM,EAAEgB,WAAW,CAAC,EAAEE,SAAS;QAAEjB,QAAQ;QAAQiB;IAAO,IACnE;QACElB,SAAS,CAAC,yBAAyB,EAAEgB,WAAW,QAAQ,CAAC;QACzDjB,UAAU,CAAC,QAAQ,EAAEiB,WAAW,gBAAgB,CAAC;QACjDf,QAAQ;IACV;IAEN,OAAOiB;AACT;AAEA,OAAO,SAASC,iBACdT,QAAuB,EACvB,EAACU,SAAS,EAAEC,KAAK,EAA6C;IAE9D,MAAM,EAACC,OAAO,EAAEC,KAAK,EAAC,GAAGnC,mBAAmB;QAACgC;QAAWC;IAAK;IAE7D,IAAIE,OAAO;QACTb,SAASd,MAAM,CAAC;YACdI,SAASd,0BAA0BqC;YACnC,8EAA8E;YAC9EtB,QAAQsB,MAAMC,IAAI,KAAK,uBAAuB,SAAS;QACzD;QAEA,2EAA2E;QAC3E,0EAA0E;QAC1E,IAAID,MAAMC,IAAI,KAAK,qBAAqB;YACtCd,SAASd,MAAM,CAAC;gBACdI,SAASb,2BAA2BiC,UAAUK,WAAW;gBACzDxB,QAAQ;YACV;QACF;IACF;IAEA,OAAOqB;AACT;AAEA;;;;CAIC,GACD,OAAO,SAASI,WAAWhB,QAAuB,EAAE,EAACU,SAAS,EAAyB;IACrF,MAAMG,QAAQxC,kBAAkBqC;IAChC,IAAIG,UAAU,sBAAsB;QAClCb,SAASd,MAAM,CAAC;YACdI,SAAS;YACTD,UAAU;YACVE,QAAQ;QACV;IACF,OAAO,IAAIsB,UAAU,qBAAqB;QACxCb,SAASd,MAAM,CAAC;YACdI,SAAS;YACTD,UAAU;YACVE,QAAQ;QACV;IACF;AACF;AAEA,OAAO,eAAe0B,WACpBjB,QAAuB,EACvB,EACEkB,KAAK,EACLC,UAAU,EACVC,cAAc,EACuE;IAEvF,IAAID,YAAY;QACdnB,SAASd,MAAM,CAAC;YAACI,SAAS6B;YAAY5B,QAAQ;QAAM;QACpD;IACF;IAEA,MAAMQ,QACJC,UACA,SACA;QACE,MAAMkB;QACNlB,SAASd,MAAM,CAAC;YAACI,SAAS8B;YAAgB7B,QAAQ;QAAM;IAC1D,GACA,CAACa,MAAQ,CAAC,cAAc,EAAE7B,gBAAgB6B,MAAM,EAChD;AAEJ;AAEA;;;CAGC,GACD,OAAO,eAAeiB,gBAAgB,EACpCC,cAAc,EACdtB,QAAQ,EACRuB,SAAS,EAKV;IACC,MAAMC,OAAOrD,QAAQ,8BAA8BsD,KAAK;IACxD,MAAMC,cAAcJ,iBAAiBlD,mBAAmBO;IACxD,IAAI;QACF,MAAM+C,YAAYH;QAClBC,KAAKG,OAAO;IACd,EAAE,OAAOvB,KAAK;QACZoB,KAAKI,IAAI;QACThD,YAAY,4BAA4BwB;QACxCJ,SAASd,MAAM,CAAC;YACdI,SAASf,gBAAgB6B;YACzBf,UAAU;YACVE,QAAQ;QACV;IACF;AACF;AAEA;;;;CAIC,GACD,OAAO,SAASsC,kBACdC,UAAqC,EACrC,EAACC,KAAK,EAAmB,GAAG,CAAC,CAAC;IAE9B,OAAQD,WAAWhB,IAAI;QACrB,KAAK;YAAW;gBACd,OAAO;oBAACxB,SAAS,CAAC,6BAA6B,EAAEwC,WAAWxC,OAAO,EAAE;oBAAEC,QAAQ;gBAAM;YACvF;QACA,KAAK;YAAS;gBACZ,MAAM,EAACyC,WAAW,EAAC,GAAGF;gBACtB,MAAMC,QAAQC,YAAYD,KAAK,IAAIC,YAAYC,OAAO;gBACtD,MAAMC,MAAMnD,cAAciD,YAAYG,cAAc,EAAEH,YAAYI,EAAE;gBACpE,OAAO;oBACL9C,SAAS,CAAC,iCAAiC,EAAEyC,MAAM,KAAK,EAAEG,KAAK;oBAC/D3C,QAAQ;oBACR8C,QAAQ;wBAACC,eAAeN,YAAYI,EAAE;wBAAEL,OAAOC,YAAYD,KAAK,IAAI;wBAAMG;oBAAG;gBAC/E;YACF;QACA,KAAK;YAAW;gBACd,OAAO;oBACL5C,SAAShB;oBACTe,UAAU;oBACVE,QAAQ;gBACV;YACF;QACA,KAAK;YAAe;gBAClB,OAAO;oBACLG,UAAUzB,UAAUsE,WAAW;oBAC/BjD,SAAS,CAAC,oCAAoC,EAAEwC,WAAWU,QAAQ,CAACC,MAAM,CAAC,UAAU,EAAEX,WAAWU,QAAQ,CAACC,MAAM,KAAK,IAAI,gBAAgB,eAAe,gBAAgB,CAAC;oBAC1KpD,UAAU;oBACVE,QAAQ;gBACV;YACF;QACA,+EAA+E;QAC/E,KAAK;YAAgB;gBACnB,IAAIwC,OAAO;oBACT,OAAO;wBAACzC,SAAS,CAAC,gCAAgC,EAAEyC,MAAM,CAAC,CAAC;wBAAExC,QAAQ;oBAAM;gBAC9E;gBACA,OAAO;oBACLG,UAAUzB,UAAUsE,WAAW;oBAC/BjD,SAAS;oBACTD,UACE;oBACFE,QAAQ;gBACV;YACF;IACF;AACF;AAEA,OAAO,SAASmD,uBAAuBtC,GAAY,EAAE+B,cAAkC;IACrF,OAAO,AAAC/B,KAA+BuC,eAAe,MAClD,CAAC,oFAAoF,EAAER,eAAe,4EAA4E,CAAC,GACnL,CAAC,iCAAiC,EAAE5D,gBAAgB6B,MAAM;AAChE;AAEA,OAAO,eAAewC,eACpB5C,QAAuB,EACvB,EACE6C,KAAK,EACLV,cAAc,EACdJ,KAAK,EAC2E;IAElF,MAAMhC,QACJC,UACA,UACA,UACEA,SAASd,MAAM,CACb2C,kBAAkB,MAAMhD,uBAAuB;YAACgE;YAAOV;QAAc,IAAI;YAACJ;QAAK,KAEnF,CAAC3B,MAAQsC,uBAAuBtC,KAAK+B;AAEzC;AAEA,yEAAyE,GACzE,OAAO,SAASW,qBACdhB,UAAwC,EACxC,EAACiB,UAAU,EAAEhB,KAAK,EAAwC;IAE1D,MAAMiB,YAAY,CAACC,OAAkBF,aAAaE,OAAO,CAAC,QAAQ,EAAEA,KAAK,cAAc,CAAC;IAExF,OAAQnB,WAAWhB,IAAI;QACrB,KAAK;YAAW;gBACd,OAAO;oBAACxB,SAAS,CAAC,6BAA6B,EAAEwC,WAAWxC,OAAO,EAAE;oBAAEC,QAAQ;gBAAM;YACvF;QACA,KAAK;YAAS;gBACZ,MAAM2C,MAAMc,UAAUlB,WAAWE,WAAW,CAACC,OAAO;gBACpD,OAAO;oBACL3C,SAAS,CAAC,2BAA2B,EAAE4C,KAAK;oBAC5C3C,QAAQ;oBACR8C,QAAQ;wBACNC,eAAeR,WAAWE,WAAW,CAACI,EAAE;wBACxCL,OAAOD,WAAWE,WAAW,CAACD,KAAK,IAAI;wBACvCG;oBACF;gBACF;YACF;QACA,KAAK;YAAW;gBACd,OAAO;oBACL,4DAA4D;oBAC5DxC,UAAUoC,WAAWoB,MAAM,KAAK,iBAAiBjF,UAAUsE,WAAW,GAAG;oBACzEjD,SAASwC,WAAWxC,OAAO;oBAC3BD,UAAU;oBACVE,QAAQ;gBACV;YACF;QACA,KAAK;YAAe;gBAClB,OAAO;oBACLG,UAAUzB,UAAUsE,WAAW;oBAC/BjD,SAASyD,aAAa,sCAAsC;oBAC5D1D,UAAU0D,aACN,uEACA;oBACJxD,QAAQ;gBACV;YACF;QACA,KAAK;YAAgB;gBACnB,MAAM2C,MAAMc,UAAUlB,WAAWG,OAAO;gBACxC,MAAMkB,SAASpB,QAAQ,CAAC,SAAS,EAAEA,MAAM,CAAC,CAAC,GAAG;gBAC9C,OAAO;oBACLzC,SAASyD,aACL,CAAC,kCAAkC,EAAEjB,WAAWG,OAAO,GAAGkB,QAAQ,GAClE,CAAC,6BAA6B,EAAEjB,MAAMiB,OAAO,yCAAyC,CAAC;oBAC3F5D,QAAQ;oBACR8C,QAAQ;wBAACC,eAAe;wBAAMP,OAAO;wBAAMG;oBAAG;gBAChD;YACF;IACF;AACF;AAEA,OAAO,eAAekB,kBACpBpD,QAAuB,EACvBqD,OAOC;IAED,MAAMtD,QACJC,UACA,UACA,UACEA,SAASd,MAAM,CACb4D,qBAAqB,MAAMhE,0BAA0BuE,UAAU;YAC7DN,YAAYM,QAAQN,UAAU;YAC9BhB,OAAOsB,QAAQtB,KAAK;QACtB,KAEJ,CAAC3B,MAAQ,CAAC,iCAAiC,EAAE7B,gBAAgB6B,MAAM;AAEvE"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { format } from 'node:util';
|
|
2
|
+
import { CLIError } from '@oclif/core/errors';
|
|
3
|
+
import { createCollectingReporter, createFailFastReporter } from './deployChecks.js';
|
|
4
|
+
import { deployDebug } from './deployDebug.js';
|
|
5
|
+
import { deploymentPlanToJson, isDeployable, renderDeploymentPlan } from './deploymentPlan.js';
|
|
6
|
+
/**
|
|
7
|
+
* Runs a deploy in the mode the flags select: a real deploy fails fast and
|
|
8
|
+
* mutates, `--dry-run` drives the same `run` sequence read-only and renders a
|
|
9
|
+
* plan, and `--json` emits the same information as machine-readable JSON.
|
|
10
|
+
*/ export async function runDeploy(options, spec) {
|
|
11
|
+
const { output } = options;
|
|
12
|
+
const json = !!options.flags.json;
|
|
13
|
+
// The JSON payload owns stdout, so the run's progress logs go to stderr; only
|
|
14
|
+
// the final JSON.stringify writes to stdout. Spinners are already on stderr.
|
|
15
|
+
const runOptions = json ? {
|
|
16
|
+
...options,
|
|
17
|
+
output: {
|
|
18
|
+
...output,
|
|
19
|
+
log: (message = '', ...args)=>void process.stderr.write(`${format(message, ...args)}\n`)
|
|
20
|
+
}
|
|
21
|
+
} : options;
|
|
22
|
+
try {
|
|
23
|
+
if (options.flags['dry-run']) {
|
|
24
|
+
const plan = await collectPlan(runOptions, spec);
|
|
25
|
+
if (json) output.log(JSON.stringify(deploymentPlanToJson(plan), null, 2));
|
|
26
|
+
else renderDeploymentPlan(plan, output);
|
|
27
|
+
exitIfBlocked(plan, output);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const result = await spec.run(runOptions, createFailFastReporter(runOptions.output));
|
|
31
|
+
if (json && result) output.log(JSON.stringify({
|
|
32
|
+
deployed: true,
|
|
33
|
+
...result
|
|
34
|
+
}, null, 2));
|
|
35
|
+
} catch (error) {
|
|
36
|
+
// Failures signal via exit code and stderr, like every other command — no JSON on stdout.
|
|
37
|
+
normalizeDeployError(error, output, spec.type);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/** Runs the step sequence read-only and gathers the plan a dry run reports. */ async function collectPlan(options, spec) {
|
|
41
|
+
const reporter = createCollectingReporter();
|
|
42
|
+
await spec.run(options, reporter);
|
|
43
|
+
const plan = {
|
|
44
|
+
checks: reporter.results,
|
|
45
|
+
files: [],
|
|
46
|
+
target: reporter.results.find((check)=>check.target)?.target ?? null,
|
|
47
|
+
type: spec.type,
|
|
48
|
+
version: reporter.results.find((check)=>check.version)?.version ?? null
|
|
49
|
+
};
|
|
50
|
+
// A blocked deploy uploads nothing, so only enumerate files for a deployable plan.
|
|
51
|
+
if (isDeployable(plan)) plan.files = await spec.listFiles(options);
|
|
52
|
+
return plan;
|
|
53
|
+
}
|
|
54
|
+
/** Exits like a real (fail-fast) deploy would, on the first failing check's exit code. */ function exitIfBlocked(plan, output) {
|
|
55
|
+
if (isDeployable(plan)) return;
|
|
56
|
+
const failed = plan.checks.find((check)=>check.status === 'fail');
|
|
57
|
+
output.error('Deploy blocked by failing checks.', {
|
|
58
|
+
exit: failed?.exitCode ?? 1
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
function normalizeDeployError(error, output, type) {
|
|
62
|
+
const noun = type === 'coreApp' ? 'application' : 'studio';
|
|
63
|
+
// Ctrl+C on an interactive prompt isn't a real failure
|
|
64
|
+
if (error instanceof Error && error.name === 'ExitPromptError') {
|
|
65
|
+
output.error('Deployment cancelled by user', {
|
|
66
|
+
exit: 1
|
|
67
|
+
});
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
// A failed check already carries its own message and exit code; rethrow untouched
|
|
71
|
+
if (error instanceof CLIError) throw error;
|
|
72
|
+
deployDebug(`Error deploying ${noun}`, error);
|
|
73
|
+
output.error(`Error deploying ${noun}: ${error}`, {
|
|
74
|
+
exit: 1
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
//# sourceMappingURL=deployRunner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/deploy/deployRunner.ts"],"sourcesContent":["import {format} from 'node:util'\n\nimport {CLIError} from '@oclif/core/errors'\nimport {type Output} from '@sanity/cli-core'\n\nimport {\n type CheckReporter,\n createCollectingReporter,\n createFailFastReporter,\n type DeployTarget,\n} from './deployChecks.js'\nimport {deployDebug} from './deployDebug.js'\nimport {\n type DeploymentFile,\n type DeploymentPlan,\n deploymentPlanToJson,\n isDeployable,\n renderDeploymentPlan,\n} from './deploymentPlan.js'\nimport {type DeployAppOptions} from './types.js'\n\n/** What a real deploy produced — the payload `--json` reports. */\nexport interface DeployResult {\n applicationType: 'coreApp' | 'studio'\n /** Installed framework version the deploy used (`sanity` or `@sanity/sdk-react`). */\n applicationVersion: string\n /**\n * The deployed application/studio, in the same shape the dry-run plan reports\n * so the two modes can't drift; `null` for a config-only singleton deploy.\n */\n target: DeployTarget | null\n\n /** Set when a media-library singleton deployed its installation config. */\n installationId?: string\n}\n\n/**\n * The parts of a deploy that differ between core apps and studios. The shared\n * sequence — mode selection, error handling, the dry-run plan, `--json` — lives\n * in `runDeploy`.\n */\nexport interface DeploySpec {\n /** Files a real deploy would upload, listed only for the dry-run plan. */\n listFiles: (options: DeployAppOptions) => Promise<DeploymentFile[]>\n /** The step sequence; every step reports through `reporter`. */\n run: (options: DeployAppOptions, reporter: CheckReporter) => Promise<DeployResult | void>\n type: 'coreApp' | 'studio'\n}\n\n/**\n * Runs a deploy in the mode the flags select: a real deploy fails fast and\n * mutates, `--dry-run` drives the same `run` sequence read-only and renders a\n * plan, and `--json` emits the same information as machine-readable JSON.\n */\nexport async function runDeploy(options: DeployAppOptions, spec: DeploySpec): Promise<void> {\n const {output} = options\n const json = !!options.flags.json\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. Spinners are already on stderr.\n const runOptions = json\n ? {\n ...options,\n output: {\n ...output,\n log: (message = '', ...args: unknown[]) =>\n void process.stderr.write(`${format(message, ...args)}\\n`),\n },\n }\n : options\n\n try {\n if (options.flags['dry-run']) {\n const plan = await collectPlan(runOptions, spec)\n if (json) output.log(JSON.stringify(deploymentPlanToJson(plan), null, 2))\n else renderDeploymentPlan(plan, output)\n exitIfBlocked(plan, output)\n return\n }\n\n const result = await spec.run(runOptions, createFailFastReporter(runOptions.output))\n if (json && result) output.log(JSON.stringify({deployed: true, ...result}, null, 2))\n } catch (error) {\n // Failures signal via exit code and stderr, like every other command — no JSON on stdout.\n normalizeDeployError(error, output, spec.type)\n }\n}\n\n/** Runs the step sequence read-only and gathers the plan a dry run reports. */\nasync function collectPlan(options: DeployAppOptions, spec: DeploySpec): Promise<DeploymentPlan> {\n const reporter = createCollectingReporter()\n await spec.run(options, reporter)\n const plan: DeploymentPlan = {\n checks: reporter.results,\n files: [],\n target: reporter.results.find((check) => check.target)?.target ?? null,\n type: spec.type,\n version: reporter.results.find((check) => check.version)?.version ?? null,\n }\n // A blocked deploy uploads nothing, so only enumerate files for a deployable plan.\n if (isDeployable(plan)) plan.files = await spec.listFiles(options)\n return plan\n}\n\n/** Exits like a real (fail-fast) deploy would, on the first failing check's exit code. */\nfunction exitIfBlocked(plan: DeploymentPlan, output: Output): void {\n if (isDeployable(plan)) return\n const failed = plan.checks.find((check) => check.status === 'fail')\n output.error('Deploy blocked by failing checks.', {exit: failed?.exitCode ?? 1})\n}\n\nfunction normalizeDeployError(error: unknown, output: Output, type: 'coreApp' | 'studio'): void {\n const noun = type === 'coreApp' ? 'application' : 'studio'\n\n // Ctrl+C on an interactive prompt isn't a real failure\n if (error instanceof Error && error.name === 'ExitPromptError') {\n output.error('Deployment cancelled by user', {exit: 1})\n return\n }\n // A failed check already carries its own message and exit code; rethrow untouched\n if (error instanceof CLIError) throw error\n deployDebug(`Error deploying ${noun}`, error)\n output.error(`Error deploying ${noun}: ${error}`, {exit: 1})\n}\n"],"names":["format","CLIError","createCollectingReporter","createFailFastReporter","deployDebug","deploymentPlanToJson","isDeployable","renderDeploymentPlan","runDeploy","options","spec","output","json","flags","runOptions","log","message","args","process","stderr","write","plan","collectPlan","JSON","stringify","exitIfBlocked","result","run","deployed","error","normalizeDeployError","type","reporter","checks","results","files","target","find","check","version","listFiles","failed","status","exit","exitCode","noun","Error","name"],"mappings":"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"}
|