@sanity/runtime-cli 17.13.1 → 17.14.0-next.g792820f
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 +19 -19
- package/dist/actions/blueprints/pre-deploy.d.ts +1 -0
- package/dist/actions/blueprints/pre-deploy.js +21 -4
- package/dist/actions/blueprints/stacks.d.ts +6 -5
- package/dist/actions/blueprints/stacks.js +25 -12
- package/dist/baseCommands.js +7 -2
- package/dist/constants.d.ts +2 -0
- package/dist/constants.js +2 -0
- package/dist/cores/blueprints/deploy.js +10 -5
- package/dist/cores/blueprints/destroy.js +28 -17
- package/dist/cores/blueprints/plan.js +6 -2
- package/dist/cores/index.d.ts +2 -0
- package/dist/utils/bundles/prepare-asset.d.ts +38 -0
- package/dist/utils/bundles/prepare-asset.js +271 -0
- package/dist/utils/bundles/stamp-config-version.d.ts +5 -0
- package/dist/utils/bundles/stamp-config-version.js +45 -0
- package/dist/utils/display/blueprints-formatting.d.ts +5 -1
- package/dist/utils/display/blueprints-formatting.js +15 -2
- package/dist/utils/{blueprints → functions}/extract-durable-config.d.ts +2 -2
- package/dist/utils/functions/extract-durable-config.js +58 -0
- package/dist/utils/request-id.d.ts +4 -0
- package/dist/utils/request-id.js +19 -0
- package/dist/utils/types.d.ts +15 -0
- package/oclif.manifest.json +1 -1
- package/package.json +2 -2
- package/dist/utils/blueprints/extract-durable-config.js +0 -60
- package/dist/utils/studios/prepare-asset.d.ts +0 -6
- package/dist/utils/studios/prepare-asset.js +0 -166
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { BlueprintApplicationResource, BlueprintInstallationConfigResource, BlueprintStudioResource } from '@sanity/blueprints';
|
|
2
|
+
import { type BuildStudioEventListener } from '@sanity/cli-build/_internal/build';
|
|
3
|
+
import type { Output } from '@sanity/cli-core';
|
|
4
|
+
import type { AssetPrepResult } from '../types.js';
|
|
5
|
+
export type BundleResource = BlueprintStudioResource | BlueprintApplicationResource | BlueprintInstallationConfigResource;
|
|
6
|
+
type BuildContext<R extends BundleResource> = {
|
|
7
|
+
resource: R;
|
|
8
|
+
appId: string | undefined;
|
|
9
|
+
workDir: string;
|
|
10
|
+
outDir: string;
|
|
11
|
+
output: Output;
|
|
12
|
+
eventListener: Partial<BuildStudioEventListener>;
|
|
13
|
+
};
|
|
14
|
+
type BuildResult = {
|
|
15
|
+
mismatchedPackages?: string[];
|
|
16
|
+
};
|
|
17
|
+
export declare const BUNDLE_SPECS: {
|
|
18
|
+
'sanity.studio': {
|
|
19
|
+
requiresAppId: true;
|
|
20
|
+
build: typeof buildStudioBundle;
|
|
21
|
+
};
|
|
22
|
+
'sanity.application': {
|
|
23
|
+
requiresAppId: true;
|
|
24
|
+
build: typeof buildApplicationBundle;
|
|
25
|
+
};
|
|
26
|
+
'sanity.installation.config': {
|
|
27
|
+
requiresAppId: false;
|
|
28
|
+
build: typeof buildInstallationConfigBundle;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
export declare function prepareBundleAsset<R extends BundleResource>({ blueprintFileDir, resource, }: {
|
|
32
|
+
blueprintFileDir: string;
|
|
33
|
+
resource: R;
|
|
34
|
+
}): Promise<AssetPrepResult>;
|
|
35
|
+
declare function buildStudioBundle({ resource, appId, workDir, outDir, output, eventListener, }: BuildContext<BlueprintStudioResource>): Promise<BuildResult>;
|
|
36
|
+
declare function buildApplicationBundle({ resource, appId, workDir, outDir, output, eventListener, }: BuildContext<BlueprintApplicationResource>): Promise<BuildResult>;
|
|
37
|
+
declare function buildInstallationConfigBundle({ resource, workDir, outDir, output, eventListener, }: BuildContext<BlueprintInstallationConfigResource>): Promise<BuildResult>;
|
|
38
|
+
export {};
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { createWriteStream } from 'node:fs';
|
|
2
|
+
import { mkdtemp, readFile, rm, stat } from 'node:fs/promises';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import path, { basename, dirname, join } from 'node:path';
|
|
5
|
+
import { pipeline } from 'node:stream/promises';
|
|
6
|
+
import { createGzip } from 'node:zlib';
|
|
7
|
+
import { buildApp, buildStudio, compareDependencyVersions, } from '@sanity/cli-build/_internal/build';
|
|
8
|
+
import { getLocalPackageVersion } from '@sanity/cli-core';
|
|
9
|
+
import { pack } from 'tar-fs';
|
|
10
|
+
export const BUNDLE_SPECS = {
|
|
11
|
+
'sanity.studio': { requiresAppId: true, build: buildStudioBundle },
|
|
12
|
+
'sanity.application': { requiresAppId: true, build: buildApplicationBundle },
|
|
13
|
+
'sanity.installation.config': { requiresAppId: false, build: buildInstallationConfigBundle },
|
|
14
|
+
};
|
|
15
|
+
export async function prepareBundleAsset({ blueprintFileDir, resource, }) {
|
|
16
|
+
// The lookup can't prove it yields the spec matching this R, so it's asserted once here.
|
|
17
|
+
const spec = BUNDLE_SPECS[resource.type];
|
|
18
|
+
const appId = getAppId(resource);
|
|
19
|
+
if (spec.requiresAppId && !appId) {
|
|
20
|
+
return {
|
|
21
|
+
success: false,
|
|
22
|
+
async cleanup() { },
|
|
23
|
+
error: `Unable to determine appId for ${resource.type} resource '${resource.name}'`,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
let cleanup = async () => { };
|
|
27
|
+
const assetPath = String(resource.root);
|
|
28
|
+
const workDir = path.resolve(blueprintFileDir, assetPath);
|
|
29
|
+
// Defence in depth alongside blueprints-node's own check: an absolute path or one
|
|
30
|
+
// that climbs above the blueprint dir would bundle files outside the project.
|
|
31
|
+
if (path.isAbsolute(assetPath) || path.relative(blueprintFileDir, workDir).startsWith('..')) {
|
|
32
|
+
return {
|
|
33
|
+
success: false,
|
|
34
|
+
cleanup,
|
|
35
|
+
error: `Property 'root' on resource with type '${resource.type}' must be under the blueprint directory`,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
const workDirStat = await stat(workDir);
|
|
40
|
+
if (!workDirStat.isDirectory()) {
|
|
41
|
+
return {
|
|
42
|
+
success: false,
|
|
43
|
+
cleanup,
|
|
44
|
+
error: `Property 'root' on resource with type '${resource.type}' must be a directory`,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
50
|
+
return {
|
|
51
|
+
success: false,
|
|
52
|
+
cleanup,
|
|
53
|
+
error: `Unable to read given 'root' on resource with type '${resource.type}': ${msg}`,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
const outDir = await mkdtemp(join(tmpdir(), `bundle-`));
|
|
58
|
+
const outFile = `${outDir}.tar.gz`;
|
|
59
|
+
cleanup = async () => {
|
|
60
|
+
await rm(outDir, { force: true, recursive: true });
|
|
61
|
+
await rm(outFile, { force: true });
|
|
62
|
+
};
|
|
63
|
+
let mismatchedPackages;
|
|
64
|
+
try {
|
|
65
|
+
;
|
|
66
|
+
({ mismatchedPackages } = await spec.build({
|
|
67
|
+
resource,
|
|
68
|
+
appId,
|
|
69
|
+
workDir,
|
|
70
|
+
outDir,
|
|
71
|
+
output: createSilentOutput(),
|
|
72
|
+
eventListener,
|
|
73
|
+
}));
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
77
|
+
return {
|
|
78
|
+
success: false,
|
|
79
|
+
cleanup,
|
|
80
|
+
error: message,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
await pipeline(pack(dirname(outDir), { entries: [basename(outDir)] }), createGzip(), createWriteStream(outFile));
|
|
84
|
+
const buffer = await readFile(outFile);
|
|
85
|
+
return {
|
|
86
|
+
success: true,
|
|
87
|
+
cleanup,
|
|
88
|
+
outputBuffer: buffer,
|
|
89
|
+
fileExtension: 'tar.gz',
|
|
90
|
+
assetParameter: 'root',
|
|
91
|
+
metadata: {
|
|
92
|
+
sanityVersion: await getInstalledSanityVersion(workDir),
|
|
93
|
+
mismatchedPackages,
|
|
94
|
+
assetBytes: buffer.length,
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
return {
|
|
100
|
+
success: false,
|
|
101
|
+
cleanup,
|
|
102
|
+
error: error instanceof Error ? error.message : String(error),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
async function buildStudioBundle({ resource, appId, workDir, outDir, output, eventListener, }) {
|
|
107
|
+
let mismatchedPackages;
|
|
108
|
+
await buildStudio({
|
|
109
|
+
appId,
|
|
110
|
+
autoUpdatesEnabled: resource.autoUpdates.enabled,
|
|
111
|
+
checkAppId() { },
|
|
112
|
+
async compareDependencyVersions(packages) {
|
|
113
|
+
const result = await compareDependencyVersions(packages, workDir, { appId });
|
|
114
|
+
mismatchedPackages = result.mismatched.map((m) => m.pkg);
|
|
115
|
+
return result;
|
|
116
|
+
},
|
|
117
|
+
determineBasePath() {
|
|
118
|
+
return resource.basePath ?? '/';
|
|
119
|
+
},
|
|
120
|
+
// a studio is a standalone app, not a workbench module-federation app
|
|
121
|
+
isApp: false,
|
|
122
|
+
isBlueprints: true,
|
|
123
|
+
isWorkbenchApp: false,
|
|
124
|
+
minify: resource.minify ?? true,
|
|
125
|
+
outDir,
|
|
126
|
+
output,
|
|
127
|
+
reactCompiler: resource.reactCompiler || false,
|
|
128
|
+
schemaExtraction: undefined,
|
|
129
|
+
sourceMap: resource.sourceMap ?? false,
|
|
130
|
+
stats: false,
|
|
131
|
+
unattendedMode: true,
|
|
132
|
+
// TODO pass in vite config from the studio resource
|
|
133
|
+
vite: undefined,
|
|
134
|
+
workDir,
|
|
135
|
+
eventListener,
|
|
136
|
+
});
|
|
137
|
+
return { mismatchedPackages };
|
|
138
|
+
}
|
|
139
|
+
async function buildApplicationBundle({ resource, appId, workDir, outDir, output, eventListener, }) {
|
|
140
|
+
const views = resource.views ?? [];
|
|
141
|
+
const [window, extraWindow] = views.filter((v) => v.type === 'app');
|
|
142
|
+
if (extraWindow) {
|
|
143
|
+
throw new Error(`Application resource '${resource.name}' declares more than one window view; at most one is allowed`);
|
|
144
|
+
}
|
|
145
|
+
await buildApp({
|
|
146
|
+
...sharedBuildAppOptions({ appId, workDir, outDir, output, eventListener }),
|
|
147
|
+
appId,
|
|
148
|
+
appTitle: resource.title,
|
|
149
|
+
entry: window?.src,
|
|
150
|
+
workbenchAppId: appId,
|
|
151
|
+
exposes: {
|
|
152
|
+
views: views.map(toWorkbenchView),
|
|
153
|
+
webWorkers: resource.webWorkers,
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
// a workbench app never auto-updates, so no dependency mismatches are compared
|
|
157
|
+
return {};
|
|
158
|
+
}
|
|
159
|
+
async function buildInstallationConfigBundle({ resource, workDir, outDir, output, eventListener, }) {
|
|
160
|
+
await buildApp({
|
|
161
|
+
...sharedBuildAppOptions({ appId: undefined, workDir, outDir, output, eventListener }),
|
|
162
|
+
appId: undefined,
|
|
163
|
+
appTitle: undefined,
|
|
164
|
+
entry: undefined,
|
|
165
|
+
workbenchAppId: undefined,
|
|
166
|
+
exposes: { config: { appType: resource.appType, fields: resource.fields } },
|
|
167
|
+
});
|
|
168
|
+
return {};
|
|
169
|
+
}
|
|
170
|
+
// Options a workbench build (application or installation config) always sets the
|
|
171
|
+
// same way; each caller adds only what differs (identity, entry, exposes).
|
|
172
|
+
function sharedBuildAppOptions({ appId, workDir, outDir, output, eventListener, }) {
|
|
173
|
+
return {
|
|
174
|
+
autoUpdatesEnabled: false,
|
|
175
|
+
checkAppId() { },
|
|
176
|
+
async compareDependencyVersions(packages) {
|
|
177
|
+
return compareDependencyVersions(packages, workDir, { appId });
|
|
178
|
+
},
|
|
179
|
+
determineBasePath() {
|
|
180
|
+
return '/';
|
|
181
|
+
},
|
|
182
|
+
isWorkbenchApp: true,
|
|
183
|
+
isBlueprints: true,
|
|
184
|
+
minify: true,
|
|
185
|
+
outDir,
|
|
186
|
+
output,
|
|
187
|
+
reactCompiler: false,
|
|
188
|
+
schemaExtraction: undefined,
|
|
189
|
+
sourceMap: false,
|
|
190
|
+
stats: false,
|
|
191
|
+
unattendedMode: true,
|
|
192
|
+
vite: undefined,
|
|
193
|
+
workDir,
|
|
194
|
+
eventListener,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
// blueprints-node emits `type` (window views as `app`); workbench-cli discriminates on `surface`.
|
|
198
|
+
function toWorkbenchView(view) {
|
|
199
|
+
switch (view.type) {
|
|
200
|
+
case 'app': {
|
|
201
|
+
const { type: _type, ...rest } = view;
|
|
202
|
+
return { ...rest, surface: 'window' };
|
|
203
|
+
}
|
|
204
|
+
case 'panel': {
|
|
205
|
+
const { type, ...rest } = view;
|
|
206
|
+
return { ...rest, surface: type };
|
|
207
|
+
}
|
|
208
|
+
case 'asset_source': {
|
|
209
|
+
const { type, ...rest } = view;
|
|
210
|
+
return { ...rest, surface: type };
|
|
211
|
+
}
|
|
212
|
+
case 'tile': {
|
|
213
|
+
const { type, ...rest } = view;
|
|
214
|
+
return { ...rest, surface: type };
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
const eventListener = {
|
|
219
|
+
onBuildFail({ message }) {
|
|
220
|
+
throw new Error(message);
|
|
221
|
+
},
|
|
222
|
+
onInvalidStyledComponentsVersionRange({ message }) {
|
|
223
|
+
throw new Error(message);
|
|
224
|
+
},
|
|
225
|
+
onNoDeclaredStyledComponentsVersion({ message }) {
|
|
226
|
+
throw new Error(message);
|
|
227
|
+
},
|
|
228
|
+
onNoInstalledSanityVersion({ message }) {
|
|
229
|
+
throw new Error(message);
|
|
230
|
+
},
|
|
231
|
+
onNoInstalledStyledComponentsVersion({ message }) {
|
|
232
|
+
throw new Error(message);
|
|
233
|
+
},
|
|
234
|
+
onPreReleaseInNonInteractiveAutoUpdate({ message }) {
|
|
235
|
+
throw new Error(message);
|
|
236
|
+
},
|
|
237
|
+
};
|
|
238
|
+
function createSilentOutput() {
|
|
239
|
+
return {
|
|
240
|
+
error(input, options) {
|
|
241
|
+
if (typeof options?.exit === 'number') {
|
|
242
|
+
if (input instanceof Error) {
|
|
243
|
+
throw input;
|
|
244
|
+
}
|
|
245
|
+
throw new Error(input);
|
|
246
|
+
}
|
|
247
|
+
return undefined;
|
|
248
|
+
},
|
|
249
|
+
log() { },
|
|
250
|
+
warn(input) {
|
|
251
|
+
return input;
|
|
252
|
+
},
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
async function getInstalledSanityVersion(workDir) {
|
|
256
|
+
try {
|
|
257
|
+
return (await getLocalPackageVersion('sanity', workDir)) ?? undefined;
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
return undefined;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
function getAppId(resource) {
|
|
264
|
+
if ('externalId' in resource && typeof resource.externalId === 'string' && resource.externalId) {
|
|
265
|
+
return resource.externalId;
|
|
266
|
+
}
|
|
267
|
+
else if (resource.lifecycle?.ownershipAction?.type === 'attach') {
|
|
268
|
+
return resource.lifecycle.ownershipAction.id;
|
|
269
|
+
}
|
|
270
|
+
return undefined;
|
|
271
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { BlueprintResource } from '@sanity/blueprints';
|
|
2
|
+
/**
|
|
3
|
+
* Stamps the installed `sanity` version onto each media-library config resource and studio resource.
|
|
4
|
+
*/
|
|
5
|
+
export declare const stampConfigVersion: (resource: BlueprintResource, blueprintFileDir: string) => Promise<void>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
import { getLocalPackageVersion } from '@sanity/cli-core/package-manager';
|
|
3
|
+
import { SANITY_INSTALLATION_CONFIG, SANITY_STUDIO } from '../../constants.js';
|
|
4
|
+
/**
|
|
5
|
+
* Stamps the installed `sanity` version onto each media-library config resource and studio resource.
|
|
6
|
+
*/
|
|
7
|
+
export const stampConfigVersion = async (resource, blueprintFileDir) => {
|
|
8
|
+
let update;
|
|
9
|
+
let root;
|
|
10
|
+
if (resource.type === SANITY_INSTALLATION_CONFIG) {
|
|
11
|
+
const config = resource;
|
|
12
|
+
if (config.appType !== 'media-library')
|
|
13
|
+
return;
|
|
14
|
+
// Leave values included in the blueprint.
|
|
15
|
+
if (config.version !== undefined)
|
|
16
|
+
return;
|
|
17
|
+
root = config.root;
|
|
18
|
+
update = (version) => {
|
|
19
|
+
config.version = version;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
else if (resource.type === SANITY_STUDIO) {
|
|
23
|
+
const config = resource;
|
|
24
|
+
// Leave values included in the blueprint.
|
|
25
|
+
if (config.autoUpdates?.version !== undefined)
|
|
26
|
+
return;
|
|
27
|
+
root = config.root;
|
|
28
|
+
update = (version) => {
|
|
29
|
+
if (config.autoUpdates) {
|
|
30
|
+
config.autoUpdates.version = version;
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
config.autoUpdates = { enabled: true, version };
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
if (update && root) {
|
|
38
|
+
const workDir = resolve(blueprintFileDir, root);
|
|
39
|
+
const version = await getLocalPackageVersion('sanity', workDir);
|
|
40
|
+
if (!version) {
|
|
41
|
+
throw new Error(`Unable to determine the installed 'sanity' version for resource '${resource.name}'; is 'sanity' installed in '${root}'?`);
|
|
42
|
+
}
|
|
43
|
+
update(version);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
@@ -4,7 +4,11 @@ import type { DeploymentPlan } from '../../actions/blueprints/stacks.js';
|
|
|
4
4
|
import { type BlueprintResourceRecord, type Stack, type StackSummary } from '../types.js';
|
|
5
5
|
export declare function formatTitle(title: string, name: string): string;
|
|
6
6
|
export declare function formatDeployedResourceTree(resources: BlueprintResourceRecord[] | undefined, verbose?: boolean): string;
|
|
7
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Returns hosted Studio URLs, one line per Studio from resource-ish shapes
|
|
9
|
+
* Searches `parameters`, `resolvedParameters` and root for `studioHost`
|
|
10
|
+
*/
|
|
11
|
+
export declare function formatStudioUrls(resources: (BlueprintResource | BlueprintResourceRecord)[] | undefined): string[];
|
|
8
12
|
export declare function formatResourceTree(resources: BlueprintResource[] | undefined, verbose?: boolean): string;
|
|
9
13
|
export declare function formatStackInfo(stack: Stack | StackSummary | Blueprint, isCurrentStack?: boolean): string;
|
|
10
14
|
export declare function formatStacksListing(stacks: StackSummary[], currentStackId?: string): string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { formatDateTime, formatElapsed } from '@sanity/cli-core/dates';
|
|
2
2
|
import { treeify } from 'array-treeify';
|
|
3
|
-
import { SANITY_ACCESS_ROBOT, SANITY_ACCESS_ROLE, SANITY_FUNCTION_DOCUMENT, SANITY_FUNCTION_DURABLE, SANITY_FUNCTION_MEDIA_LIBRARY_ASSET, SANITY_FUNCTION_PUBSUB, SANITY_FUNCTION_SCHEDULED, SANITY_FUNCTION_SYNC_TAG_INVALIDATE, SANITY_PROJECT, SANITY_PROJECT_CORS, SANITY_PROJECT_DATASET, SANITY_PROJECT_WEBHOOK, SANITY_STUDIO, } from '../../constants.js';
|
|
3
|
+
import { SANITY_ACCESS_ROBOT, SANITY_ACCESS_ROLE, SANITY_APPLICATION, SANITY_FUNCTION_DOCUMENT, SANITY_FUNCTION_DURABLE, SANITY_FUNCTION_MEDIA_LIBRARY_ASSET, SANITY_FUNCTION_PUBSUB, SANITY_FUNCTION_SCHEDULED, SANITY_FUNCTION_SYNC_TAG_INVALIDATE, SANITY_INSTALLATION_CONFIG, SANITY_PROJECT, SANITY_PROJECT_CORS, SANITY_PROJECT_DATASET, SANITY_PROJECT_WEBHOOK, SANITY_STUDIO, } from '../../constants.js';
|
|
4
4
|
import { styleText } from '../style-text.js';
|
|
5
5
|
import { isCorsOriginResource, isDatasetResource, isRobotResource, isRoleResource, isStudioResource, isWebhookResource, } from '../types.js';
|
|
6
6
|
import { capitalize, niceId, renderSection } from './presenters.js';
|
|
@@ -65,6 +65,14 @@ const RESOURCE_CATEGORIES = {
|
|
|
65
65
|
return isStudioResource(res) ? arrayifyStudio(res) : [];
|
|
66
66
|
},
|
|
67
67
|
},
|
|
68
|
+
[SANITY_APPLICATION]: {
|
|
69
|
+
label: 'Applications',
|
|
70
|
+
displayNameAttribute: 'title',
|
|
71
|
+
},
|
|
72
|
+
[SANITY_INSTALLATION_CONFIG]: {
|
|
73
|
+
label: 'Installation configs',
|
|
74
|
+
displayNameAttribute: 'name',
|
|
75
|
+
},
|
|
68
76
|
};
|
|
69
77
|
const categoryByLabel = Object.values(RESOURCE_CATEGORIES).reduce((acc, curr) => {
|
|
70
78
|
acc[curr.label] = curr;
|
|
@@ -165,6 +173,10 @@ export function formatDeployedResourceTree(resources, verbose = false) {
|
|
|
165
173
|
(res) => ({ name: res.name, type: res.type, ...(res.resolvedParameters ?? res.parameters) }), verbose);
|
|
166
174
|
return `${treeify(output)}\n`;
|
|
167
175
|
}
|
|
176
|
+
/**
|
|
177
|
+
* Returns hosted Studio URLs, one line per Studio from resource-ish shapes
|
|
178
|
+
* Searches `parameters`, `resolvedParameters` and root for `studioHost`
|
|
179
|
+
*/
|
|
168
180
|
export function formatStudioUrls(resources) {
|
|
169
181
|
if (!resources)
|
|
170
182
|
return [];
|
|
@@ -172,7 +184,8 @@ export function formatStudioUrls(resources) {
|
|
|
172
184
|
for (const res of resources) {
|
|
173
185
|
if (res.type !== SANITY_STUDIO)
|
|
174
186
|
continue;
|
|
175
|
-
const
|
|
187
|
+
const { resolvedParameters, parameters } = res;
|
|
188
|
+
const host = studioHost(resolvedParameters ?? parameters ?? res);
|
|
176
189
|
if (host)
|
|
177
190
|
lines.push(`Studio ${styleText(['bold', 'green'], res.name)} ${studioUrl(host)}`);
|
|
178
191
|
}
|
|
@@ -2,7 +2,7 @@ import { type BlueprintResource } from '@sanity/blueprints';
|
|
|
2
2
|
/**
|
|
3
3
|
* Extracts the blueprint config from the function source code and mutates it to the resource object.
|
|
4
4
|
* This is used for durable functions that may not have a config defined in the blueprint.
|
|
5
|
-
* @param
|
|
5
|
+
* @param resource
|
|
6
6
|
* @param blueprintFileDir
|
|
7
7
|
*/
|
|
8
|
-
export declare const extractDurableConfig: (
|
|
8
|
+
export declare const extractDurableConfig: (resource: BlueprintResource, blueprintFileDir: string) => Promise<void>;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
import { pathToFileURL } from 'node:url';
|
|
3
|
+
import { validateDurableFunction, } from '@sanity/blueprints';
|
|
4
|
+
import { SANITY_FUNCTION_DURABLE } from '../../constants.js';
|
|
5
|
+
import { isLocalFunctionResource } from '../types.js';
|
|
6
|
+
import { findFunctionEntryPoint } from './find-entry-point.js';
|
|
7
|
+
import { transpileFunction } from './transpile/function.js';
|
|
8
|
+
const DURABLE_CONFIG_KEYS = ['event', 'concurrency', 'debounce', 'debounceKey'];
|
|
9
|
+
/**
|
|
10
|
+
* Extracts the blueprint config from the function source code and mutates it to the resource object.
|
|
11
|
+
* This is used for durable functions that may not have a config defined in the blueprint.
|
|
12
|
+
* @param resource
|
|
13
|
+
* @param blueprintFileDir
|
|
14
|
+
*/
|
|
15
|
+
export const extractDurableConfig = async (resource, blueprintFileDir) => {
|
|
16
|
+
if (!isLocalFunctionResource(resource))
|
|
17
|
+
return;
|
|
18
|
+
// skip if not a durable function
|
|
19
|
+
if (resource.type !== SANITY_FUNCTION_DURABLE)
|
|
20
|
+
return;
|
|
21
|
+
// Check if source needs to be transpiled before attempting to get config
|
|
22
|
+
const resourceSrc = resolve(blueprintFileDir, resource.src);
|
|
23
|
+
const entryPoint = await findFunctionEntryPoint(resourceSrc, resource.name);
|
|
24
|
+
let importPath = entryPoint;
|
|
25
|
+
let cleanup = async () => { };
|
|
26
|
+
// need to transpile and grab the config from the built output if the entry point is a TypeScript file
|
|
27
|
+
if (entryPoint.endsWith('.ts')) {
|
|
28
|
+
const built = await transpileFunction({ resource, functionPath: resourceSrc, entryPoint });
|
|
29
|
+
importPath = await findFunctionEntryPoint(built.outputDir, resource.name);
|
|
30
|
+
cleanup = built.cleanup;
|
|
31
|
+
}
|
|
32
|
+
let config = {};
|
|
33
|
+
try {
|
|
34
|
+
const mod = await import(pathToFileURL(importPath).href);
|
|
35
|
+
config = (mod.handler || mod.default)?.config;
|
|
36
|
+
}
|
|
37
|
+
finally {
|
|
38
|
+
await cleanup();
|
|
39
|
+
}
|
|
40
|
+
// Update values not included in the blueprint.
|
|
41
|
+
if (resource.event === undefined && config?.event !== undefined) {
|
|
42
|
+
resource.event = config.event;
|
|
43
|
+
}
|
|
44
|
+
if (resource.concurrency === undefined && config?.concurrency !== undefined) {
|
|
45
|
+
resource.concurrency = config.concurrency;
|
|
46
|
+
}
|
|
47
|
+
if (resource.debounce === undefined && config?.debounce !== undefined) {
|
|
48
|
+
resource.debounce = config.debounce;
|
|
49
|
+
}
|
|
50
|
+
if (resource.debounceKey === undefined && config?.debounceKey !== undefined) {
|
|
51
|
+
resource.debounceKey = config.debounceKey;
|
|
52
|
+
}
|
|
53
|
+
// Validate the resource after merging the config values
|
|
54
|
+
const errors = validateDurableFunction(resource);
|
|
55
|
+
if (errors.length > 0) {
|
|
56
|
+
throw new Error(`Invalid durable resource "${resource.name}": ${errors.map((error) => error.message).join('; ')}`);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const REQUEST_ID_HEADER = "x-request-id";
|
|
2
|
+
/** Stack PUT/POST/DELETE success bodies also carry `requestId`; error bodies do not. */
|
|
3
|
+
export declare function getRequestId(response?: Response, body?: unknown): string | undefined;
|
|
4
|
+
export declare function withRequestId(message: string, requestId?: string): string;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export const REQUEST_ID_HEADER = 'x-request-id';
|
|
2
|
+
/** Stack PUT/POST/DELETE success bodies also carry `requestId`; error bodies do not. */
|
|
3
|
+
export function getRequestId(response, body) {
|
|
4
|
+
const fromHeader = response?.headers.get(REQUEST_ID_HEADER);
|
|
5
|
+
if (fromHeader)
|
|
6
|
+
return fromHeader;
|
|
7
|
+
if (body && typeof body === 'object' && 'requestId' in body) {
|
|
8
|
+
const fromBody = body.requestId;
|
|
9
|
+
if (typeof fromBody === 'string' && fromBody)
|
|
10
|
+
return fromBody;
|
|
11
|
+
}
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
export function withRequestId(message, requestId) {
|
|
15
|
+
if (!requestId)
|
|
16
|
+
return message;
|
|
17
|
+
const suffix = `(request ID ${requestId})`;
|
|
18
|
+
return message.endsWith('.') ? `${message.slice(0, -1)} ${suffix}.` : `${message} ${suffix}`;
|
|
19
|
+
}
|
package/dist/utils/types.d.ts
CHANGED
|
@@ -65,6 +65,15 @@ interface StackBase {
|
|
|
65
65
|
export interface Stack extends StackBase {
|
|
66
66
|
resources: Array<BlueprintResourceRecord>;
|
|
67
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* Shape returned by `PUT /stacks/{id}`
|
|
70
|
+
* The authored document is echoed by the API: resources without `parameters` or `resolvedParameters`.
|
|
71
|
+
*/
|
|
72
|
+
export interface UpdatedStack extends StackBase {
|
|
73
|
+
resources: BlueprintResource[];
|
|
74
|
+
operationId: string;
|
|
75
|
+
changeCount?: number;
|
|
76
|
+
}
|
|
68
77
|
export interface StackSummary extends StackBase {
|
|
69
78
|
resourceCount: number;
|
|
70
79
|
}
|
|
@@ -329,11 +338,17 @@ export type PrepAssetFn<R extends BlueprintResource> = (options: {
|
|
|
329
338
|
blueprintFileDir: string;
|
|
330
339
|
installer?: InstallerType;
|
|
331
340
|
}) => Promise<AssetPrepResult>;
|
|
341
|
+
export type PrepResourceFn<R extends BlueprintResource> = (options: {
|
|
342
|
+
resource: R;
|
|
343
|
+
blueprintFileDir: string;
|
|
344
|
+
}) => Promise<void>;
|
|
332
345
|
export type PreDeployConfig = {
|
|
333
346
|
/** The resource type this config applies to or a function that returns true if this config applies to the given type. */
|
|
334
347
|
resourceType: string | ((type: string) => boolean);
|
|
335
348
|
/** Function that takes a resource and returns a buffer to be sent to blueprints. */
|
|
336
349
|
prepareAsset?: PrepAssetFn<BlueprintResource>;
|
|
350
|
+
/** Function that mutates the resource before the pre-deploy is executed. */
|
|
351
|
+
prepareResource?: PrepResourceFn<BlueprintResource>;
|
|
337
352
|
};
|
|
338
353
|
export type AssetResource<R extends BlueprintResource> = {
|
|
339
354
|
resource: R;
|
package/oclif.manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanity/runtime-cli",
|
|
3
3
|
"description": "Sanity's Runtime CLI for Blueprints and Functions",
|
|
4
|
-
"version": "17.
|
|
4
|
+
"version": "17.14.0-next.g792820f",
|
|
5
5
|
"author": "Sanity Runtime Team",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"license": "MIT",
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
"@oclif/core": "^4.14.0",
|
|
69
69
|
"@oclif/plugin-help": "^6.3.0",
|
|
70
70
|
"@sanity-labs/oclif-plugin-skills-flag": "^0.2.1",
|
|
71
|
-
"@sanity/blueprints": "^0.
|
|
71
|
+
"@sanity/blueprints": "^0.27.0",
|
|
72
72
|
"@sanity/blueprints-parser": "^0.5.0",
|
|
73
73
|
"@sanity/cli-build": "^6.3.0",
|
|
74
74
|
"@sanity/cli-core": "^3.7.0",
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { resolve } from 'node:path';
|
|
2
|
-
import { pathToFileURL } from 'node:url';
|
|
3
|
-
import { validateDurableFunction, } from '@sanity/blueprints';
|
|
4
|
-
import { SANITY_FUNCTION_DURABLE } from '../../constants.js';
|
|
5
|
-
import { findFunctionEntryPoint } from '../functions/find-entry-point.js';
|
|
6
|
-
import { transpileFunction } from '../functions/transpile/function.js';
|
|
7
|
-
import { isLocalFunctionResource } from '../types.js';
|
|
8
|
-
const DURABLE_CONFIG_KEYS = ['event', 'concurrency', 'debounce', 'debounceKey'];
|
|
9
|
-
/**
|
|
10
|
-
* Extracts the blueprint config from the function source code and mutates it to the resource object.
|
|
11
|
-
* This is used for durable functions that may not have a config defined in the blueprint.
|
|
12
|
-
* @param resources
|
|
13
|
-
* @param blueprintFileDir
|
|
14
|
-
*/
|
|
15
|
-
export const extractDurableConfig = async (resources, blueprintFileDir) => {
|
|
16
|
-
for (const resource of resources) {
|
|
17
|
-
if (!isLocalFunctionResource(resource))
|
|
18
|
-
continue;
|
|
19
|
-
// skip if not a durable function
|
|
20
|
-
if (resource.type !== SANITY_FUNCTION_DURABLE)
|
|
21
|
-
continue;
|
|
22
|
-
// Check if source needs to be transpiled before attempting to get config
|
|
23
|
-
const resourceSrc = resolve(blueprintFileDir, resource.src);
|
|
24
|
-
const entryPoint = await findFunctionEntryPoint(resourceSrc, resource.name);
|
|
25
|
-
let importPath = entryPoint;
|
|
26
|
-
let cleanup = async () => { };
|
|
27
|
-
// need to transpile and grab the config from the built output if the entry point is a TypeScript file
|
|
28
|
-
if (entryPoint.endsWith('.ts')) {
|
|
29
|
-
const built = await transpileFunction({ resource, functionPath: resourceSrc, entryPoint });
|
|
30
|
-
importPath = await findFunctionEntryPoint(built.outputDir, resource.name);
|
|
31
|
-
cleanup = built.cleanup;
|
|
32
|
-
}
|
|
33
|
-
let config = {};
|
|
34
|
-
try {
|
|
35
|
-
const mod = await import(pathToFileURL(importPath).href);
|
|
36
|
-
config = (mod.handler || mod.default)?.config;
|
|
37
|
-
}
|
|
38
|
-
finally {
|
|
39
|
-
await cleanup();
|
|
40
|
-
}
|
|
41
|
-
// Update values not included in the blueprint.
|
|
42
|
-
if (resource.event === undefined && config?.event !== undefined) {
|
|
43
|
-
resource.event = config.event;
|
|
44
|
-
}
|
|
45
|
-
if (resource.concurrency === undefined && config?.concurrency !== undefined) {
|
|
46
|
-
resource.concurrency = config.concurrency;
|
|
47
|
-
}
|
|
48
|
-
if (resource.debounce === undefined && config?.debounce !== undefined) {
|
|
49
|
-
resource.debounce = config.debounce;
|
|
50
|
-
}
|
|
51
|
-
if (resource.debounceKey === undefined && config?.debounceKey !== undefined) {
|
|
52
|
-
resource.debounceKey = config.debounceKey;
|
|
53
|
-
}
|
|
54
|
-
// Validate the resource after merging the config values
|
|
55
|
-
const errors = validateDurableFunction(resource);
|
|
56
|
-
if (errors.length > 0) {
|
|
57
|
-
throw new Error(`Invalid durable resource "${resource.name}": ${errors.map((error) => error.message).join('; ')}`);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
};
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import type { BlueprintStudioResource } from '@sanity/blueprints';
|
|
2
|
-
import type { AssetPrepResult } from '../types.js';
|
|
3
|
-
export declare function prepareStudioAsset({ blueprintFileDir, resource, }: {
|
|
4
|
-
blueprintFileDir: string;
|
|
5
|
-
resource: BlueprintStudioResource;
|
|
6
|
-
}): Promise<AssetPrepResult>;
|