@sanity/runtime-cli 14.0.1 → 14.1.1
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 +18 -18
- package/dist/actions/blueprints/stacks.d.ts +51 -0
- package/dist/actions/blueprints/stacks.js +18 -0
- package/dist/config.d.ts +1 -0
- package/dist/config.js +2 -0
- package/dist/cores/blueprints/plan.js +56 -21
- package/dist/utils/display/blueprints-formatting.d.ts +3 -1
- package/dist/utils/display/blueprints-formatting.js +137 -23
- package/dist/utils/other/npmjs.js +4 -1
- package/dist/utils/validated-token.js +3 -1
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ $ npm install -g @sanity/runtime-cli
|
|
|
20
20
|
$ sanity-run COMMAND
|
|
21
21
|
running command...
|
|
22
22
|
$ sanity-run (--version)
|
|
23
|
-
@sanity/runtime-cli/14.
|
|
23
|
+
@sanity/runtime-cli/14.1.1 linux-x64 node-v24.13.1
|
|
24
24
|
$ sanity-run --help [COMMAND]
|
|
25
25
|
USAGE
|
|
26
26
|
$ sanity-run COMMAND
|
|
@@ -98,7 +98,7 @@ EXAMPLES
|
|
|
98
98
|
$ sanity-run blueprints add function --name my-function --fn-type document-create --fn-type document-update --lang js
|
|
99
99
|
```
|
|
100
100
|
|
|
101
|
-
_See code: [src/commands/blueprints/add.ts](https://github.com/sanity-io/runtime-cli/blob/v14.
|
|
101
|
+
_See code: [src/commands/blueprints/add.ts](https://github.com/sanity-io/runtime-cli/blob/v14.1.1/src/commands/blueprints/add.ts)_
|
|
102
102
|
|
|
103
103
|
## `sanity-run blueprints config`
|
|
104
104
|
|
|
@@ -133,7 +133,7 @@ EXAMPLES
|
|
|
133
133
|
$ sanity-run blueprints config --edit --project-id <projectId> --stack <name-or-id>
|
|
134
134
|
```
|
|
135
135
|
|
|
136
|
-
_See code: [src/commands/blueprints/config.ts](https://github.com/sanity-io/runtime-cli/blob/v14.
|
|
136
|
+
_See code: [src/commands/blueprints/config.ts](https://github.com/sanity-io/runtime-cli/blob/v14.1.1/src/commands/blueprints/config.ts)_
|
|
137
137
|
|
|
138
138
|
## `sanity-run blueprints deploy`
|
|
139
139
|
|
|
@@ -166,7 +166,7 @@ EXAMPLES
|
|
|
166
166
|
$ sanity-run blueprints deploy --no-wait
|
|
167
167
|
```
|
|
168
168
|
|
|
169
|
-
_See code: [src/commands/blueprints/deploy.ts](https://github.com/sanity-io/runtime-cli/blob/v14.
|
|
169
|
+
_See code: [src/commands/blueprints/deploy.ts](https://github.com/sanity-io/runtime-cli/blob/v14.1.1/src/commands/blueprints/deploy.ts)_
|
|
170
170
|
|
|
171
171
|
## `sanity-run blueprints destroy`
|
|
172
172
|
|
|
@@ -198,7 +198,7 @@ EXAMPLES
|
|
|
198
198
|
$ sanity-run blueprints destroy --stack <name-or-id> --project-id <projectId> --force --no-wait
|
|
199
199
|
```
|
|
200
200
|
|
|
201
|
-
_See code: [src/commands/blueprints/destroy.ts](https://github.com/sanity-io/runtime-cli/blob/v14.
|
|
201
|
+
_See code: [src/commands/blueprints/destroy.ts](https://github.com/sanity-io/runtime-cli/blob/v14.1.1/src/commands/blueprints/destroy.ts)_
|
|
202
202
|
|
|
203
203
|
## `sanity-run blueprints doctor`
|
|
204
204
|
|
|
@@ -224,7 +224,7 @@ DESCRIPTION
|
|
|
224
224
|
issues.
|
|
225
225
|
```
|
|
226
226
|
|
|
227
|
-
_See code: [src/commands/blueprints/doctor.ts](https://github.com/sanity-io/runtime-cli/blob/v14.
|
|
227
|
+
_See code: [src/commands/blueprints/doctor.ts](https://github.com/sanity-io/runtime-cli/blob/v14.1.1/src/commands/blueprints/doctor.ts)_
|
|
228
228
|
|
|
229
229
|
## `sanity-run blueprints info`
|
|
230
230
|
|
|
@@ -254,7 +254,7 @@ EXAMPLES
|
|
|
254
254
|
$ sanity-run blueprints info --stack <name-or-id>
|
|
255
255
|
```
|
|
256
256
|
|
|
257
|
-
_See code: [src/commands/blueprints/info.ts](https://github.com/sanity-io/runtime-cli/blob/v14.
|
|
257
|
+
_See code: [src/commands/blueprints/info.ts](https://github.com/sanity-io/runtime-cli/blob/v14.1.1/src/commands/blueprints/info.ts)_
|
|
258
258
|
|
|
259
259
|
## `sanity-run blueprints init [DIR]`
|
|
260
260
|
|
|
@@ -304,7 +304,7 @@ EXAMPLES
|
|
|
304
304
|
$ sanity-run blueprints init --blueprint-type <json|js|ts> --stack-name <stackName>
|
|
305
305
|
```
|
|
306
306
|
|
|
307
|
-
_See code: [src/commands/blueprints/init.ts](https://github.com/sanity-io/runtime-cli/blob/v14.
|
|
307
|
+
_See code: [src/commands/blueprints/init.ts](https://github.com/sanity-io/runtime-cli/blob/v14.1.1/src/commands/blueprints/init.ts)_
|
|
308
308
|
|
|
309
309
|
## `sanity-run blueprints logs`
|
|
310
310
|
|
|
@@ -333,7 +333,7 @@ EXAMPLES
|
|
|
333
333
|
$ sanity-run blueprints logs --watch
|
|
334
334
|
```
|
|
335
335
|
|
|
336
|
-
_See code: [src/commands/blueprints/logs.ts](https://github.com/sanity-io/runtime-cli/blob/v14.
|
|
336
|
+
_See code: [src/commands/blueprints/logs.ts](https://github.com/sanity-io/runtime-cli/blob/v14.1.1/src/commands/blueprints/logs.ts)_
|
|
337
337
|
|
|
338
338
|
## `sanity-run blueprints plan`
|
|
339
339
|
|
|
@@ -359,7 +359,7 @@ EXAMPLES
|
|
|
359
359
|
$ sanity-run blueprints plan
|
|
360
360
|
```
|
|
361
361
|
|
|
362
|
-
_See code: [src/commands/blueprints/plan.ts](https://github.com/sanity-io/runtime-cli/blob/v14.
|
|
362
|
+
_See code: [src/commands/blueprints/plan.ts](https://github.com/sanity-io/runtime-cli/blob/v14.1.1/src/commands/blueprints/plan.ts)_
|
|
363
363
|
|
|
364
364
|
## `sanity-run blueprints stacks`
|
|
365
365
|
|
|
@@ -388,7 +388,7 @@ EXAMPLES
|
|
|
388
388
|
$ sanity-run blueprints stacks --organization-id <organizationId>
|
|
389
389
|
```
|
|
390
390
|
|
|
391
|
-
_See code: [src/commands/blueprints/stacks.ts](https://github.com/sanity-io/runtime-cli/blob/v14.
|
|
391
|
+
_See code: [src/commands/blueprints/stacks.ts](https://github.com/sanity-io/runtime-cli/blob/v14.1.1/src/commands/blueprints/stacks.ts)_
|
|
392
392
|
|
|
393
393
|
## `sanity-run functions add`
|
|
394
394
|
|
|
@@ -437,7 +437,7 @@ EXAMPLES
|
|
|
437
437
|
$ sanity-run functions add --name my-function --type document-create --type document-update --lang js
|
|
438
438
|
```
|
|
439
439
|
|
|
440
|
-
_See code: [src/commands/functions/add.ts](https://github.com/sanity-io/runtime-cli/blob/v14.
|
|
440
|
+
_See code: [src/commands/functions/add.ts](https://github.com/sanity-io/runtime-cli/blob/v14.1.1/src/commands/functions/add.ts)_
|
|
441
441
|
|
|
442
442
|
## `sanity-run functions dev`
|
|
443
443
|
|
|
@@ -471,7 +471,7 @@ EXAMPLES
|
|
|
471
471
|
$ sanity-run functions dev --timeout 60
|
|
472
472
|
```
|
|
473
473
|
|
|
474
|
-
_See code: [src/commands/functions/dev.ts](https://github.com/sanity-io/runtime-cli/blob/v14.
|
|
474
|
+
_See code: [src/commands/functions/dev.ts](https://github.com/sanity-io/runtime-cli/blob/v14.1.1/src/commands/functions/dev.ts)_
|
|
475
475
|
|
|
476
476
|
## `sanity-run functions env add NAME KEY VALUE`
|
|
477
477
|
|
|
@@ -498,7 +498,7 @@ EXAMPLES
|
|
|
498
498
|
$ sanity-run functions env add MyFunction API_URL https://api.example.com/
|
|
499
499
|
```
|
|
500
500
|
|
|
501
|
-
_See code: [src/commands/functions/env/add.ts](https://github.com/sanity-io/runtime-cli/blob/v14.
|
|
501
|
+
_See code: [src/commands/functions/env/add.ts](https://github.com/sanity-io/runtime-cli/blob/v14.1.1/src/commands/functions/env/add.ts)_
|
|
502
502
|
|
|
503
503
|
## `sanity-run functions env list NAME`
|
|
504
504
|
|
|
@@ -522,7 +522,7 @@ EXAMPLES
|
|
|
522
522
|
$ sanity-run functions env list MyFunction
|
|
523
523
|
```
|
|
524
524
|
|
|
525
|
-
_See code: [src/commands/functions/env/list.ts](https://github.com/sanity-io/runtime-cli/blob/v14.
|
|
525
|
+
_See code: [src/commands/functions/env/list.ts](https://github.com/sanity-io/runtime-cli/blob/v14.1.1/src/commands/functions/env/list.ts)_
|
|
526
526
|
|
|
527
527
|
## `sanity-run functions env remove NAME KEY`
|
|
528
528
|
|
|
@@ -548,7 +548,7 @@ EXAMPLES
|
|
|
548
548
|
$ sanity-run functions env remove MyFunction API_URL
|
|
549
549
|
```
|
|
550
550
|
|
|
551
|
-
_See code: [src/commands/functions/env/remove.ts](https://github.com/sanity-io/runtime-cli/blob/v14.
|
|
551
|
+
_See code: [src/commands/functions/env/remove.ts](https://github.com/sanity-io/runtime-cli/blob/v14.1.1/src/commands/functions/env/remove.ts)_
|
|
552
552
|
|
|
553
553
|
## `sanity-run functions logs [NAME]`
|
|
554
554
|
|
|
@@ -588,7 +588,7 @@ EXAMPLES
|
|
|
588
588
|
$ sanity-run functions logs <name> --delete
|
|
589
589
|
```
|
|
590
590
|
|
|
591
|
-
_See code: [src/commands/functions/logs.ts](https://github.com/sanity-io/runtime-cli/blob/v14.
|
|
591
|
+
_See code: [src/commands/functions/logs.ts](https://github.com/sanity-io/runtime-cli/blob/v14.1.1/src/commands/functions/logs.ts)_
|
|
592
592
|
|
|
593
593
|
## `sanity-run functions test [NAME]`
|
|
594
594
|
|
|
@@ -642,7 +642,7 @@ EXAMPLES
|
|
|
642
642
|
$ sanity-run functions test <name> --event update --data-before '{ "title": "before" }' --data-after '{ "title": "after" }'
|
|
643
643
|
```
|
|
644
644
|
|
|
645
|
-
_See code: [src/commands/functions/test.ts](https://github.com/sanity-io/runtime-cli/blob/v14.
|
|
645
|
+
_See code: [src/commands/functions/test.ts](https://github.com/sanity-io/runtime-cli/blob/v14.1.1/src/commands/functions/test.ts)_
|
|
646
646
|
|
|
647
647
|
## `sanity-run help [COMMAND]`
|
|
648
648
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Blueprint } from '@sanity/blueprints-parser';
|
|
1
2
|
import type { Logger } from '../../utils/logger.js';
|
|
2
3
|
import type { AuthParams, ScopeType, Stack, StackMutation } from '../../utils/types.js';
|
|
3
4
|
export declare const stacksUrl: string;
|
|
@@ -50,6 +51,56 @@ export declare function updateStack({ stackId, stackMutation, auth, logger, }: {
|
|
|
50
51
|
auth: AuthParams;
|
|
51
52
|
logger: ReturnType<typeof Logger>;
|
|
52
53
|
}): Promise<UpdateStackResponse>;
|
|
54
|
+
interface PlanResourceSnapshot {
|
|
55
|
+
name: string;
|
|
56
|
+
type: string;
|
|
57
|
+
parameters: Record<string, unknown>;
|
|
58
|
+
}
|
|
59
|
+
export interface PlanActionDetails {
|
|
60
|
+
name: string;
|
|
61
|
+
type: string;
|
|
62
|
+
parameters?: Record<string, unknown>;
|
|
63
|
+
existingResource?: PlanResourceSnapshot & {
|
|
64
|
+
id: string;
|
|
65
|
+
externalId?: string;
|
|
66
|
+
};
|
|
67
|
+
updatedResource?: PlanResourceSnapshot;
|
|
68
|
+
}
|
|
69
|
+
export interface DeploymentPlanAction {
|
|
70
|
+
type: 'create' | 'update' | 'destroy' | 'skip' | 'attach' | 'detach';
|
|
71
|
+
resourceType: string;
|
|
72
|
+
details: PlanActionDetails | null;
|
|
73
|
+
}
|
|
74
|
+
export interface DeploymentPlan {
|
|
75
|
+
plan: DeploymentPlanAction[];
|
|
76
|
+
summary: {
|
|
77
|
+
createCount: number;
|
|
78
|
+
updateCount: number;
|
|
79
|
+
destroyCount: number;
|
|
80
|
+
skipCount: number;
|
|
81
|
+
attachCount: number;
|
|
82
|
+
detachCount: number;
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
type PlanStackResponse = {
|
|
86
|
+
ok: true;
|
|
87
|
+
error: null;
|
|
88
|
+
problems: null;
|
|
89
|
+
deploymentPlan: DeploymentPlan;
|
|
90
|
+
} | {
|
|
91
|
+
ok: false;
|
|
92
|
+
error: string;
|
|
93
|
+
problems: {
|
|
94
|
+
message: string;
|
|
95
|
+
}[] | null;
|
|
96
|
+
deploymentPlan: null;
|
|
97
|
+
};
|
|
98
|
+
export declare function planStack({ stackId, document, auth, logger, }: {
|
|
99
|
+
stackId: string;
|
|
100
|
+
document: Blueprint;
|
|
101
|
+
auth: AuthParams;
|
|
102
|
+
logger: ReturnType<typeof Logger>;
|
|
103
|
+
}): Promise<PlanStackResponse>;
|
|
53
104
|
interface DestroyStackResponse {
|
|
54
105
|
ok: boolean;
|
|
55
106
|
error: string | null;
|
|
@@ -75,6 +75,24 @@ export async function updateStack({ stackId, stackMutation, auth, logger, }) {
|
|
|
75
75
|
stack: data,
|
|
76
76
|
};
|
|
77
77
|
}
|
|
78
|
+
export async function planStack({ stackId, document, auth, logger, }) {
|
|
79
|
+
const fetchFn = createTracedFetch(logger);
|
|
80
|
+
const response = await fetchFn(`${stacksUrl}/${stackId}/plan`, {
|
|
81
|
+
method: 'POST',
|
|
82
|
+
headers: getHeaders(auth),
|
|
83
|
+
body: JSON.stringify({ document }),
|
|
84
|
+
});
|
|
85
|
+
const data = await response.json();
|
|
86
|
+
if (!response.ok) {
|
|
87
|
+
return {
|
|
88
|
+
ok: false,
|
|
89
|
+
error: data.message || 'Failed to retrieve deployment plan',
|
|
90
|
+
problems: response.status === 400 && Array.isArray(data.problems) ? data.problems : null,
|
|
91
|
+
deploymentPlan: null,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
return { ok: true, error: null, problems: null, deploymentPlan: data };
|
|
95
|
+
}
|
|
78
96
|
export async function resolveStackIdByNameOrId(value, auth, logger) {
|
|
79
97
|
if (value.startsWith('ST-') && value.length === 13)
|
|
80
98
|
return value;
|
package/dist/config.d.ts
CHANGED
package/dist/config.js
CHANGED
|
@@ -21,6 +21,7 @@ const nodeEnv = env.NODE_ENV?.toLowerCase() ?? 'production';
|
|
|
21
21
|
const isTest = nodeEnv === 'test';
|
|
22
22
|
const sanityEnv = env.SANITY_INTERNAL_ENV?.toLowerCase() ?? 'production';
|
|
23
23
|
const sanityProd = sanityEnv === 'production';
|
|
24
|
+
const isLive = sanityProd || sanityEnv === 'staging';
|
|
24
25
|
const isPublishing = env?.PUBLISHING?.toLowerCase() === 'true';
|
|
25
26
|
const apiUrls = {
|
|
26
27
|
production: 'https://api.sanity.io/',
|
|
@@ -52,6 +53,7 @@ function loadToken() {
|
|
|
52
53
|
}
|
|
53
54
|
export default {
|
|
54
55
|
isTest,
|
|
56
|
+
isLive,
|
|
55
57
|
apiUrl,
|
|
56
58
|
get token() {
|
|
57
59
|
return _token === undefined ? loadToken() : _token;
|
|
@@ -1,32 +1,67 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { formatResourceTree,
|
|
1
|
+
import { planStack, resolveStackIdByNameOrId } from '../../actions/blueprints/stacks.js';
|
|
2
|
+
import { formatDeploymentPlan, formatResourceTree, hasActionableChanges, } from '../../utils/display/blueprints-formatting.js';
|
|
3
3
|
import { styleText } from '../../utils/style-text.js';
|
|
4
4
|
export async function blueprintPlanCore(options) {
|
|
5
5
|
const { bin = 'sanity', log, blueprint, token, flags } = options;
|
|
6
6
|
const { verbose: _verbose = false } = flags;
|
|
7
7
|
const { scopeType, scopeId, stackId: blueprintStackId, parsedBlueprint, fileInfo } = blueprint;
|
|
8
|
-
log(`${styleText(['bold', 'blueBright'], 'Blueprint
|
|
8
|
+
log(`${styleText(['bold', 'blueBright'], 'Local Blueprint')} ${styleText('dim', `(${fileInfo.fileName})`)}`);
|
|
9
9
|
log(formatResourceTree(parsedBlueprint.resources));
|
|
10
|
-
if (token
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
10
|
+
if (!token || !scopeType || !scopeId) {
|
|
11
|
+
log(styleText('dim', 'Unable to retrieve live Stack deployment for comparison'));
|
|
12
|
+
const errorMessage = !token
|
|
13
|
+
? `Missing authentication token. Run \`${bin} login\` to authenticate.`
|
|
14
|
+
: `Missing Blueprint configuration. Run \`${bin} blueprints doctor --fix\` to repair.`;
|
|
15
|
+
return {
|
|
16
|
+
success: false,
|
|
17
|
+
error: errorMessage,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
const stackId = flags.stack
|
|
21
|
+
? await resolveStackIdByNameOrId(flags.stack, { token, scopeType, scopeId }, log)
|
|
22
|
+
: blueprintStackId;
|
|
23
|
+
if (!stackId) {
|
|
24
|
+
log(styleText('dim', 'Unable to retrieve live Stack deployment for comparison'));
|
|
25
|
+
return {
|
|
26
|
+
success: false,
|
|
27
|
+
error: `Missing Stack deployment configuration. Run \`${bin} blueprints doctor --fix\` to repair.`,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
const auth = { token, scopeType, scopeId };
|
|
31
|
+
const spinner = log.ora('Generating deployment plan...').start();
|
|
32
|
+
const planResponse = await planStack({
|
|
33
|
+
stackId,
|
|
34
|
+
document: parsedBlueprint,
|
|
35
|
+
auth,
|
|
36
|
+
logger: log,
|
|
37
|
+
});
|
|
38
|
+
spinner.stop().clear();
|
|
39
|
+
if (!planResponse.ok) {
|
|
40
|
+
if (planResponse.problems) {
|
|
41
|
+
log('');
|
|
42
|
+
for (const problem of planResponse.problems) {
|
|
43
|
+
const messages = problem.message
|
|
44
|
+
.split('\n')
|
|
45
|
+
.map((s) => s.trim())
|
|
46
|
+
.filter(Boolean);
|
|
47
|
+
for (const msg of messages) {
|
|
48
|
+
log(` ${styleText(['bold', 'red'], '✘')} ${msg}`);
|
|
49
|
+
}
|
|
27
50
|
}
|
|
51
|
+
log(`\n Fix the issues above before running "${styleText(['bold', 'magenta'], `${bin} blueprints deploy`)}"`);
|
|
28
52
|
}
|
|
53
|
+
else {
|
|
54
|
+
log(styleText('dim', '\nUnable to retrieve deployment plan from server'));
|
|
55
|
+
}
|
|
56
|
+
return { success: false, error: 'Deployment plan has problems' };
|
|
57
|
+
}
|
|
58
|
+
log('');
|
|
59
|
+
log(formatDeploymentPlan(planResponse.deploymentPlan));
|
|
60
|
+
if (hasActionableChanges(planResponse.deploymentPlan)) {
|
|
61
|
+
log(`\n Run "${styleText(['bold', 'magenta'], `${bin} blueprints deploy`)}" to apply these Stack changes`);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
log(`\n ${styleText('dim', `No significant changes to deploy. Run \`${bin} blueprints deploy\` to apply.`)}`);
|
|
29
65
|
}
|
|
30
|
-
log(`\n Run "${styleText(['bold', 'magenta'], `${bin} blueprints deploy`)}" to deploy these Stack changes`);
|
|
31
66
|
return { success: true };
|
|
32
67
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { Blueprint, Resource } from '@sanity/blueprints-parser';
|
|
2
|
+
import type { DeploymentPlan } from '../../actions/blueprints/stacks.js';
|
|
2
3
|
import { type BlueprintResourceRecord, type Stack } from '../types.js';
|
|
3
4
|
export declare function formatTitle(title: string, name: string): string;
|
|
4
5
|
export declare function formatDeployedResourceTree(resources: BlueprintResourceRecord[] | undefined, verbose?: boolean): string;
|
|
5
6
|
export declare function formatResourceTree(resources: Resource[] | undefined, verbose?: boolean): string;
|
|
6
7
|
export declare function formatStackInfo(stack: Stack | Blueprint, isCurrentStack?: boolean): string;
|
|
7
8
|
export declare function formatStacksListing(stacks: Stack[], currentStackId?: string): string;
|
|
8
|
-
export declare function
|
|
9
|
+
export declare function hasActionableChanges(deploymentPlan: DeploymentPlan): boolean;
|
|
10
|
+
export declare function formatDeploymentPlan(deploymentPlan: DeploymentPlan): string;
|
|
@@ -208,29 +208,143 @@ export function formatStacksListing(stacks, currentStackId) {
|
|
|
208
208
|
}
|
|
209
209
|
return output.join('\n');
|
|
210
210
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
if (
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
211
|
+
const IGNORED_PARAMS = {
|
|
212
|
+
[SANITY_FUNCTION_DOCUMENT]: new Set(['src']),
|
|
213
|
+
[SANITY_FUNCTION_MEDIA_LIBRARY_ASSET]: new Set(['src']),
|
|
214
|
+
[SANITY_FUNCTION_SCHEDULE]: new Set(['src']),
|
|
215
|
+
};
|
|
216
|
+
const ASSET_RESOURCE_TYPES = new Set([
|
|
217
|
+
SANITY_FUNCTION_DOCUMENT,
|
|
218
|
+
SANITY_FUNCTION_MEDIA_LIBRARY_ASSET,
|
|
219
|
+
SANITY_FUNCTION_SCHEDULE,
|
|
220
|
+
]);
|
|
221
|
+
function stringifyUnknown(val) {
|
|
222
|
+
if (val === null || val === undefined)
|
|
223
|
+
return JSON.stringify(val);
|
|
224
|
+
if (Array.isArray(val))
|
|
225
|
+
return `[${val.map(stringifyUnknown).join(',')}]`;
|
|
226
|
+
if (typeof val === 'object') {
|
|
227
|
+
const obj = val;
|
|
228
|
+
const sorted = Object.keys(obj)
|
|
229
|
+
.sort()
|
|
230
|
+
.map((k) => `${JSON.stringify(k)}:${stringifyUnknown(obj[k])}`);
|
|
231
|
+
return `{${sorted.join(',')}}`;
|
|
231
232
|
}
|
|
232
|
-
|
|
233
|
-
|
|
233
|
+
return JSON.stringify(val);
|
|
234
|
+
}
|
|
235
|
+
function detectChangedParams(details) {
|
|
236
|
+
const existing = details.existingResource?.parameters;
|
|
237
|
+
const updated = details.updatedResource?.parameters;
|
|
238
|
+
if (!existing || !updated)
|
|
239
|
+
return [];
|
|
240
|
+
const ignored = IGNORED_PARAMS[details.type];
|
|
241
|
+
const keys = new Set([...Object.keys(existing), ...Object.keys(updated)]);
|
|
242
|
+
const changed = [];
|
|
243
|
+
for (const key of keys) {
|
|
244
|
+
if (ignored?.has(key))
|
|
245
|
+
continue;
|
|
246
|
+
if (stringifyUnknown(existing[key]) !== stringifyUnknown(updated[key])) {
|
|
247
|
+
changed.push(key);
|
|
248
|
+
}
|
|
234
249
|
}
|
|
235
|
-
return
|
|
250
|
+
return changed;
|
|
251
|
+
}
|
|
252
|
+
function getAssetSrc(details) {
|
|
253
|
+
const src = details.updatedResource?.parameters?.src ?? details.parameters?.src;
|
|
254
|
+
return typeof src === 'string' && !src.startsWith('AS-') ? src : undefined;
|
|
255
|
+
}
|
|
256
|
+
function actionStyle(type, isUnchanged = false) {
|
|
257
|
+
if (isUnchanged)
|
|
258
|
+
return { icon: '=', color: 'dim' };
|
|
259
|
+
switch (type) {
|
|
260
|
+
case 'create':
|
|
261
|
+
return { icon: '+', color: 'green' };
|
|
262
|
+
case 'update':
|
|
263
|
+
return { icon: '~', color: 'yellow' };
|
|
264
|
+
case 'destroy':
|
|
265
|
+
return { icon: '-', color: 'red' };
|
|
266
|
+
case 'attach':
|
|
267
|
+
return { icon: '←', color: 'blue' };
|
|
268
|
+
case 'detach':
|
|
269
|
+
return { icon: '→', color: 'gray' };
|
|
270
|
+
// case 'skip':
|
|
271
|
+
default:
|
|
272
|
+
return { icon: '=', color: 'dim' };
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
export function hasActionableChanges(deploymentPlan) {
|
|
276
|
+
const { summary } = deploymentPlan;
|
|
277
|
+
if (summary.createCount || summary.destroyCount || summary.attachCount || summary.detachCount) {
|
|
278
|
+
return true;
|
|
279
|
+
}
|
|
280
|
+
return deploymentPlan.plan.some((action) => action.type === 'update' &&
|
|
281
|
+
action.details &&
|
|
282
|
+
(detectChangedParams(action.details).length > 0 ||
|
|
283
|
+
(ASSET_RESOURCE_TYPES.has(action.resourceType) &&
|
|
284
|
+
getAssetSrc(action.details) !== undefined)));
|
|
285
|
+
}
|
|
286
|
+
export function formatDeploymentPlan(deploymentPlan) {
|
|
287
|
+
const lines = [];
|
|
288
|
+
lines.push(styleText('bold', 'Deployment Plan'));
|
|
289
|
+
if (deploymentPlan.plan.length === 0) {
|
|
290
|
+
lines.push(styleText('dim', ' No changes'));
|
|
291
|
+
return lines.join('\n');
|
|
292
|
+
}
|
|
293
|
+
const typePad = Math.max(...deploymentPlan.plan.map((a) => a.type.length));
|
|
294
|
+
const names = deploymentPlan.plan.map((a) => a.details?.name ?? '');
|
|
295
|
+
const namePad = Math.max(...names.map((n) => n.length));
|
|
296
|
+
const enrichedPlan = deploymentPlan.plan.map((action) => {
|
|
297
|
+
const changedParams = action.type === 'update' && action.details ? detectChangedParams(action.details) : [];
|
|
298
|
+
// always represent asset updates as a change; hard to know if the asset has changed
|
|
299
|
+
const assetSrc = action.details && ASSET_RESOURCE_TYPES.has(action.resourceType)
|
|
300
|
+
? getAssetSrc(action.details)
|
|
301
|
+
: undefined;
|
|
302
|
+
const isUnchanged = action.type === 'update' && changedParams.length === 0 && !assetSrc;
|
|
303
|
+
return { action, changedParams, isUnchanged };
|
|
304
|
+
});
|
|
305
|
+
let unchangedCount = 0;
|
|
306
|
+
for (const { action, changedParams, isUnchanged } of enrichedPlan) {
|
|
307
|
+
const name = action.details?.name ?? '';
|
|
308
|
+
if (isUnchanged)
|
|
309
|
+
unchangedCount++;
|
|
310
|
+
const style = actionStyle(action.type, isUnchanged);
|
|
311
|
+
const icon = styleText(['bold', style.color], style.icon);
|
|
312
|
+
const typeLabel = styleText(style.color, action.type.padEnd(typePad));
|
|
313
|
+
const nameLabel = isUnchanged
|
|
314
|
+
? styleText('dim', name.padEnd(namePad))
|
|
315
|
+
: styleText('bold', name.padEnd(namePad));
|
|
316
|
+
const resourceType = styleText('dim', action.resourceType);
|
|
317
|
+
let suffix = '';
|
|
318
|
+
if (changedParams.length > 0) {
|
|
319
|
+
suffix = ` ${styleText('yellow', changedParams.join(', '))}`;
|
|
320
|
+
}
|
|
321
|
+
lines.push(` ${icon} ${typeLabel} ${nameLabel} ${resourceType}${suffix}`);
|
|
322
|
+
}
|
|
323
|
+
const { summary } = deploymentPlan;
|
|
324
|
+
const parts = [];
|
|
325
|
+
if (summary.createCount)
|
|
326
|
+
parts.push(styleText('green', `${summary.createCount} create`));
|
|
327
|
+
if (summary.updateCount) {
|
|
328
|
+
let label = `${summary.updateCount} update`;
|
|
329
|
+
if (unchangedCount > 0 && unchangedCount < summary.updateCount) {
|
|
330
|
+
label += ` (${styleText('yellow', `${summary.updateCount - unchangedCount} changed`)})`;
|
|
331
|
+
}
|
|
332
|
+
else if (unchangedCount === summary.updateCount) {
|
|
333
|
+
label += ` ${styleText('dim', '(no changes)')}`;
|
|
334
|
+
}
|
|
335
|
+
parts.push(label);
|
|
336
|
+
}
|
|
337
|
+
if (summary.destroyCount)
|
|
338
|
+
parts.push(styleText('red', `${summary.destroyCount} destroy`));
|
|
339
|
+
if (summary.skipCount)
|
|
340
|
+
parts.push(styleText('dim', `${summary.skipCount} skip`));
|
|
341
|
+
if (summary.attachCount)
|
|
342
|
+
parts.push(styleText('blue', `${summary.attachCount} attach`));
|
|
343
|
+
if (summary.detachCount)
|
|
344
|
+
parts.push(styleText('dim', `${summary.detachCount} detach`));
|
|
345
|
+
if (parts.length > 0) {
|
|
346
|
+
lines.push('');
|
|
347
|
+
lines.push(` ${parts.join(', ')}`);
|
|
348
|
+
}
|
|
349
|
+
return lines.join('\n');
|
|
236
350
|
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import { env } from 'node:process';
|
|
1
2
|
import { createTracedFetch } from '../traced-fetch.js';
|
|
2
3
|
export async function getLatestNpmVersion(pkg, logger) {
|
|
3
4
|
const fetchFn = createTracedFetch(logger);
|
|
4
|
-
|
|
5
|
+
// allow for registry override; helpful for testing
|
|
6
|
+
const registry = env.SANITY_INTERNAL_NPM_REGISTRY_URL || 'https://registry.npmjs.org';
|
|
7
|
+
const url = `${registry}/${pkg}/latest`;
|
|
5
8
|
try {
|
|
6
9
|
const res = await fetchFn(url);
|
|
7
10
|
if (!res.ok)
|
|
@@ -3,10 +3,12 @@ import { createTracedFetch } from './traced-fetch.js';
|
|
|
3
3
|
export async function validToken(logger, maybeToken) {
|
|
4
4
|
if (config.isTest)
|
|
5
5
|
return maybeToken ?? 'token';
|
|
6
|
-
const fetchFn = createTracedFetch(logger);
|
|
7
6
|
const token = maybeToken ?? config.token;
|
|
8
7
|
if (!token)
|
|
9
8
|
throw new Error('NO_TOKEN');
|
|
9
|
+
if (!config.isLive)
|
|
10
|
+
return token;
|
|
11
|
+
const fetchFn = createTracedFetch(logger);
|
|
10
12
|
const url = `${config.apiUrl}v2025-04-23/users/me`;
|
|
11
13
|
const response = await fetchFn(url, {
|
|
12
14
|
method: 'GET',
|
package/oclif.manifest.json
CHANGED