@sanity/runtime-cli 17.8.0 → 17.9.0-blueprinit.g14aaebd
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 +28 -22
- package/dist/actions/blueprinit/manifest.d.ts +46 -0
- package/dist/actions/blueprinit/manifest.js +121 -0
- package/dist/actions/blueprinit/quote.d.ts +6 -0
- package/dist/actions/blueprinit/quote.js +8 -0
- package/dist/actions/blueprinit/studio.d.ts +6 -0
- package/dist/actions/blueprinit/studio.js +43 -0
- package/dist/actions/blueprinit/templates.d.ts +73 -0
- package/dist/actions/blueprinit/templates.js +138 -0
- package/dist/actions/blueprinit/versions.d.ts +8 -0
- package/dist/actions/blueprinit/versions.js +11 -0
- package/dist/actions/blueprinit/workspace.d.ts +27 -0
- package/dist/actions/blueprinit/workspace.js +145 -0
- package/dist/actions/blueprints/pre-deploy.js +2 -0
- package/dist/actions/blueprints/stacks.d.ts +3 -0
- package/dist/actions/blueprints/stacks.js +1 -0
- package/dist/actions/sanity/create-project.d.ts +19 -0
- package/dist/actions/sanity/create-project.js +32 -0
- package/dist/actions/sanity/datasets.d.ts +15 -0
- package/dist/actions/sanity/datasets.js +26 -0
- package/dist/actions/sanity/projects.d.ts +11 -0
- package/dist/actions/sanity/projects.js +48 -22
- package/dist/baseCommands.d.ts +6 -0
- package/dist/baseCommands.js +9 -1
- package/dist/commands/blueprints/stacks.d.ts +2 -1
- package/dist/commands/blueprints/stacks.js +14 -6
- package/dist/commands/functions/test.d.ts +1 -0
- package/dist/commands/functions/test.js +5 -0
- package/dist/commands/init.d.ts +21 -0
- package/dist/commands/init.js +82 -0
- package/dist/cores/blueprinit/adopt.d.ts +6 -0
- package/dist/cores/blueprinit/adopt.js +137 -0
- package/dist/cores/blueprinit/columns.d.ts +26 -0
- package/dist/cores/blueprinit/columns.js +27 -0
- package/dist/cores/blueprinit/finish.d.ts +15 -0
- package/dist/cores/blueprinit/finish.js +72 -0
- package/dist/cores/blueprinit/fresh.d.ts +3 -0
- package/dist/cores/blueprinit/fresh.js +219 -0
- package/dist/cores/blueprinit/naming.d.ts +27 -0
- package/dist/cores/blueprinit/naming.js +43 -0
- package/dist/cores/blueprinit/preview.d.ts +34 -0
- package/dist/cores/blueprinit/preview.js +110 -0
- package/dist/cores/blueprinit/project-choice.d.ts +28 -0
- package/dist/cores/blueprinit/project-choice.js +82 -0
- package/dist/cores/blueprinit/prompts.d.ts +13 -0
- package/dist/cores/blueprinit/prompts.js +42 -0
- package/dist/cores/blueprinit/results.d.ts +8 -0
- package/dist/cores/blueprinit/results.js +4 -0
- package/dist/cores/blueprinit/selection.d.ts +15 -0
- package/dist/cores/blueprinit/selection.js +90 -0
- package/dist/cores/blueprinit/stack-name.d.ts +39 -0
- package/dist/cores/blueprinit/stack-name.js +56 -0
- package/dist/cores/blueprinit/target.d.ts +33 -0
- package/dist/cores/blueprinit/target.js +80 -0
- package/dist/cores/blueprinit/types.d.ts +20 -0
- package/dist/cores/blueprinit/types.js +1 -0
- package/dist/cores/blueprinit.d.ts +7 -0
- package/dist/cores/blueprinit.js +64 -0
- package/dist/cores/blueprints/stacks.d.ts +8 -3
- package/dist/cores/blueprints/stacks.js +282 -85
- package/dist/cores/functions/test.d.ts +2 -19
- package/dist/cores/functions/test.js +63 -138
- package/dist/server/app.js +4 -6
- package/dist/server/static/components/payload-panel.js +1 -1
- package/dist/utils/display/intro.d.ts +6 -0
- package/dist/utils/display/intro.js +63 -0
- package/dist/utils/display/prompt.d.ts +4 -1
- package/dist/utils/display/prompt.js +16 -2
- package/dist/utils/functions/child-process-wrapper.js +3 -2
- package/dist/utils/functions/child-runtime/durable.d.ts +2 -2
- package/dist/utils/functions/child-runtime/durable.js +53 -3
- package/dist/utils/functions/durable-logs.d.ts +16 -0
- package/dist/utils/functions/durable-logs.js +52 -0
- package/dist/utils/functions/invoke/local.js +16 -8
- package/dist/utils/functions/invoke/resolve-invoke-type.d.ts +51 -0
- package/dist/utils/functions/invoke/resolve-invoke-type.js +131 -0
- package/dist/utils/types.d.ts +39 -6
- package/oclif.manifest.json +177 -5
- package/package.json +1 -1
- package/skills/blueprints-stacks.md +14 -2
- package/skills/init.md +199 -0
|
@@ -1,34 +1,37 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { dirname, join } from 'node:path';
|
|
3
|
-
import { cwd } from 'node:process';
|
|
1
|
+
import { dirname } from 'node:path';
|
|
4
2
|
import { testAction } from '../../actions/functions/test.js';
|
|
5
3
|
import config from '../../config.js';
|
|
6
|
-
import {
|
|
4
|
+
import { STEP_PHASE_GLYPH } from '../../constants.js';
|
|
7
5
|
import { resolveDeployedResources } from '../../utils/blueprints/resolve-deployed-resources.js';
|
|
8
|
-
import
|
|
9
|
-
import { fetchAsset, fetchDocument } from '../../utils/functions/fetch-document.js';
|
|
6
|
+
import { createDurableJsonCollector } from '../../utils/functions/durable-logs.js';
|
|
10
7
|
import { findFunctionInResources, getFunctionNames } from '../../utils/functions/find.js';
|
|
11
8
|
import { runFunction } from '../../utils/functions/invoke/nested.js';
|
|
12
|
-
import {
|
|
9
|
+
import { buildContext, buildInvokeOptions, collectInvokeInput, defaultInvocationDeps, resolveInvocationPlan, } from '../../utils/functions/invoke/resolve-invoke-type.js';
|
|
13
10
|
import { isEventType, } from '../../utils/types.js';
|
|
14
11
|
/**
|
|
15
12
|
* CLI lines for a durable run, in emission order; bare `start` events dropped.
|
|
16
13
|
* @param events
|
|
17
14
|
*/
|
|
18
15
|
function renderStepTimeline(events) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
const lines = [];
|
|
17
|
+
for (const event of events) {
|
|
18
|
+
if (event.kind === 'log') {
|
|
19
|
+
lines.push(`${event.timestamp} ${event.level.toUpperCase()} ${event.message}`);
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
if (event.phase === 'start')
|
|
23
|
+
continue;
|
|
22
24
|
const glyph = STEP_PHASE_GLYPH[event.phase] ?? '•';
|
|
23
25
|
const duration = typeof event.durationMs === 'number' ? ` (${Math.round(event.durationMs)}ms)` : '';
|
|
24
26
|
const detail = event.phase === 'error' && event.error ? ` ${event.error.message}` : '';
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
+
lines.push(` ${glyph} ${event.name} [${event.kind}]${duration}${detail}`);
|
|
28
|
+
}
|
|
29
|
+
return lines;
|
|
27
30
|
}
|
|
28
31
|
export async function functionTestCore(options) {
|
|
29
32
|
const { blueprint, log, error, args, flags, helpText } = options;
|
|
30
33
|
const { name: fnName } = args;
|
|
31
|
-
const {
|
|
34
|
+
const { event, timeout, api, 'with-user-token': withUserToken, 'no-wait': noWait } = flags;
|
|
32
35
|
const { 'project-id': projectId = blueprint?.projectId, 'organization-id': organizationId = blueprint?.organizationId, } = flags;
|
|
33
36
|
if (!fnName) {
|
|
34
37
|
const functionNames = getFunctionNames(blueprint.resources);
|
|
@@ -54,9 +57,6 @@ export async function functionTestCore(options) {
|
|
|
54
57
|
}
|
|
55
58
|
try {
|
|
56
59
|
const resource = findFunctionInResources(blueprint.resources, fnName); // throws if not found
|
|
57
|
-
const docFunction = resource.type === SANITY_FUNCTION_DOCUMENT;
|
|
58
|
-
const mediaFunction = resource.type === SANITY_FUNCTION_MEDIA_LIBRARY_ASSET;
|
|
59
|
-
const durableFunction = resource.type === SANITY_FUNCTION_DURABLE;
|
|
60
60
|
const resolvedResources = await resolveDeployedResources({
|
|
61
61
|
resources: blueprint.resources,
|
|
62
62
|
stackId: blueprint.stackId,
|
|
@@ -65,112 +65,36 @@ export async function functionTestCore(options) {
|
|
|
65
65
|
: undefined,
|
|
66
66
|
logger: log,
|
|
67
67
|
});
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
resources: resolvedResources,
|
|
85
|
-
blueprintFileDir,
|
|
86
|
-
}
|
|
87
|
-
: { clientOptions: {}, resources: resolvedResources, blueprintFileDir };
|
|
88
|
-
// If the user sets the flag to use the real token set it in our options
|
|
89
|
-
if (withUserToken) {
|
|
90
|
-
contextOptions.clientOptions.token = config.token || undefined;
|
|
91
|
-
}
|
|
92
|
-
let before = null;
|
|
93
|
-
let after = null;
|
|
94
|
-
if (dataBefore && dataAfter) {
|
|
95
|
-
before = parseJsonObject(dataBefore);
|
|
96
|
-
after = parseJsonObject(dataAfter);
|
|
97
|
-
}
|
|
98
|
-
else if (fileBefore && fileAfter) {
|
|
99
|
-
let fileContents = readFileSync(join(cwd(), fileBefore), 'utf8');
|
|
100
|
-
before = parseJsonObject(fileContents);
|
|
101
|
-
fileContents = readFileSync(join(cwd(), fileAfter), 'utf8');
|
|
102
|
-
after = parseJsonObject(fileContents);
|
|
103
|
-
}
|
|
104
|
-
else if (documentIdBefore && documentIdAfter && docFunction) {
|
|
105
|
-
before = await fetchDocument(documentIdBefore, {
|
|
106
|
-
projectId,
|
|
107
|
-
dataset,
|
|
108
|
-
apiVersion: api,
|
|
109
|
-
apiHost: config.apiUrl,
|
|
110
|
-
token: config.token || undefined,
|
|
111
|
-
}, log);
|
|
112
|
-
after = await fetchDocument(documentIdAfter, {
|
|
113
|
-
projectId,
|
|
114
|
-
dataset,
|
|
115
|
-
apiVersion: api,
|
|
116
|
-
apiHost: config.apiUrl,
|
|
117
|
-
token: config.token || undefined,
|
|
118
|
-
}, log);
|
|
119
|
-
}
|
|
120
|
-
else if (documentIdBefore && documentIdAfter) {
|
|
121
|
-
before = await fetchAsset(documentIdBefore, {
|
|
122
|
-
mediaLibraryId,
|
|
123
|
-
apiVersion: api,
|
|
124
|
-
apiHost: config.apiUrl,
|
|
125
|
-
token: config.token || undefined,
|
|
126
|
-
}, log);
|
|
127
|
-
after = await fetchAsset(documentIdAfter, {
|
|
128
|
-
mediaLibraryId,
|
|
129
|
-
apiVersion: api,
|
|
130
|
-
apiHost: config.apiUrl,
|
|
131
|
-
token: config.token || undefined,
|
|
132
|
-
}, log);
|
|
133
|
-
}
|
|
134
|
-
let payload;
|
|
135
|
-
if (after) {
|
|
136
|
-
payload = after;
|
|
137
|
-
}
|
|
138
|
-
else {
|
|
139
|
-
if (documentId && docFunction) {
|
|
140
|
-
payload = await fetchDocument(documentId, {
|
|
141
|
-
projectId,
|
|
142
|
-
dataset,
|
|
143
|
-
apiVersion: api,
|
|
144
|
-
apiHost: config.apiUrl,
|
|
145
|
-
token: config.token || undefined,
|
|
146
|
-
}, log);
|
|
147
|
-
}
|
|
148
|
-
else if (documentId) {
|
|
149
|
-
payload = await fetchAsset(documentId, {
|
|
150
|
-
mediaLibraryId,
|
|
151
|
-
apiVersion: api,
|
|
152
|
-
apiHost: config.apiUrl,
|
|
153
|
-
token: config.token || undefined,
|
|
154
|
-
}, log);
|
|
155
|
-
}
|
|
156
|
-
else {
|
|
157
|
-
payload = buildPayload({ data, file });
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
let invokeOptions;
|
|
161
|
-
if (docFunction || mediaFunction) {
|
|
162
|
-
invokeOptions = { event: eventType, payload, before, after };
|
|
163
|
-
}
|
|
164
|
-
else {
|
|
165
|
-
invokeOptions = { event: durableFunction ? 'durable' : 'scheduled', payload: { data: payload } };
|
|
166
|
-
}
|
|
68
|
+
const input = {
|
|
69
|
+
flags,
|
|
70
|
+
projectId,
|
|
71
|
+
organizationId,
|
|
72
|
+
api,
|
|
73
|
+
apiHost: config.apiUrl,
|
|
74
|
+
token: config.token || undefined,
|
|
75
|
+
withUserToken,
|
|
76
|
+
resources: resolvedResources,
|
|
77
|
+
blueprintFileDir: dirname(blueprint.fileInfo.blueprintFilePath),
|
|
78
|
+
log,
|
|
79
|
+
};
|
|
80
|
+
const plan = resolveInvocationPlan(resource);
|
|
81
|
+
const context = buildContext(plan, input);
|
|
82
|
+
const invokeInput = await collectInvokeInput(plan, input, defaultInvocationDeps);
|
|
83
|
+
const invokeOptions = buildInvokeOptions(plan, invokeInput, eventType);
|
|
167
84
|
// Records each nested context.invoke() so they can be surfaced in order after the spinner resolves.
|
|
168
85
|
const nestedRuns = [];
|
|
169
86
|
let nestedOrder = 0;
|
|
170
|
-
const
|
|
171
|
-
const onStepEvent =
|
|
87
|
+
const durableJson = createDurableJsonCollector();
|
|
88
|
+
const onStepEvent = plan.isDurable
|
|
172
89
|
? (event) => {
|
|
173
|
-
|
|
90
|
+
if (flags.json) {
|
|
91
|
+
durableJson.record(event);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
for (const line of renderStepTimeline([event])) {
|
|
95
|
+
if (line !== null)
|
|
96
|
+
log(line);
|
|
97
|
+
}
|
|
174
98
|
}
|
|
175
99
|
: undefined;
|
|
176
100
|
/**
|
|
@@ -185,7 +109,7 @@ export async function functionTestCore(options) {
|
|
|
185
109
|
const runNested = async (nestedName, nestedPayload, ctx, depth, callStack) => {
|
|
186
110
|
const order = ++nestedOrder;
|
|
187
111
|
const nestedResource = findFunctionInResources(blueprint.resources, nestedName);
|
|
188
|
-
const result = await runFunction(nestedResource, { payload:
|
|
112
|
+
const result = await runFunction(nestedResource, { payload: nestedPayload, event: 'nested' }, { ...ctx, resources: resolvedResources }, { depth, callStack, onNestedInvoke: runNested });
|
|
189
113
|
nestedRuns.push({ order, name: nestedName, logs: result.logs, error: result.error });
|
|
190
114
|
return result;
|
|
191
115
|
};
|
|
@@ -204,12 +128,13 @@ export async function functionTestCore(options) {
|
|
|
204
128
|
}
|
|
205
129
|
};
|
|
206
130
|
const spinner = log.ora('Executing function...').start();
|
|
207
|
-
const { json: response, logs, error, } = await testAction(resource, invokeOptions,
|
|
131
|
+
const { json: response, logs, error, } = await testAction(resource, invokeOptions, context, {
|
|
208
132
|
timeout: timeout ? timeout : resource.timeout,
|
|
209
133
|
onNestedInvoke: runNested,
|
|
210
134
|
depth: 0,
|
|
211
135
|
callStack: [fnName],
|
|
212
136
|
onStepEvent,
|
|
137
|
+
noWait,
|
|
213
138
|
});
|
|
214
139
|
if (error) {
|
|
215
140
|
spinner.fail('Function execution failed.');
|
|
@@ -221,12 +146,6 @@ export async function functionTestCore(options) {
|
|
|
221
146
|
logNestedRuns();
|
|
222
147
|
const errorMessage = error.stack || error.message || error.name;
|
|
223
148
|
const isTimeout = error.message?.includes('Timeout');
|
|
224
|
-
if (durableFunction && stepEvents.length > 0) {
|
|
225
|
-
log('Steps:');
|
|
226
|
-
for (const line of renderStepTimeline(stepEvents)) {
|
|
227
|
-
log(line);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
149
|
return {
|
|
231
150
|
success: false,
|
|
232
151
|
error: errorMessage,
|
|
@@ -237,20 +156,26 @@ export async function functionTestCore(options) {
|
|
|
237
156
|
};
|
|
238
157
|
}
|
|
239
158
|
spinner.succeed('Function execution succeeded.');
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
if (durableFunction && stepEvents.length > 0) {
|
|
248
|
-
log('Steps:');
|
|
249
|
-
for (const line of renderStepTimeline(stepEvents)) {
|
|
250
|
-
log(line);
|
|
159
|
+
if (!plan.isDurable) {
|
|
160
|
+
log('Logs:');
|
|
161
|
+
log(logs || '');
|
|
162
|
+
logNestedRuns();
|
|
163
|
+
if (response) {
|
|
164
|
+
log('Response:');
|
|
165
|
+
log(JSON.stringify(response, null, 2));
|
|
251
166
|
}
|
|
252
167
|
}
|
|
253
|
-
|
|
168
|
+
const durableResult = durableJson.result();
|
|
169
|
+
return {
|
|
170
|
+
success: true,
|
|
171
|
+
json: plan.isDurable
|
|
172
|
+
? {
|
|
173
|
+
response: response ?? null,
|
|
174
|
+
steps: durableResult.steps,
|
|
175
|
+
logs: durableResult.logs,
|
|
176
|
+
}
|
|
177
|
+
: { response, logs },
|
|
178
|
+
};
|
|
254
179
|
}
|
|
255
180
|
catch (error) {
|
|
256
181
|
return {
|
package/dist/server/app.js
CHANGED
|
@@ -28,7 +28,7 @@ const app = (host, port, validateResources, executionOptions) => {
|
|
|
28
28
|
*/
|
|
29
29
|
const nestedInvoke = async (name, payload, ctx, depth, callStack) => {
|
|
30
30
|
const order = ++nestedOrder;
|
|
31
|
-
const result = await handleInvokeRequest(name,
|
|
31
|
+
const result = await handleInvokeRequest(name, payload, { event: 'nested' }, ctx, validateResources, executionOptions, depth, callStack, nestedInvoke);
|
|
32
32
|
broadcast({
|
|
33
33
|
type: 'nested-invoke',
|
|
34
34
|
name,
|
|
@@ -372,13 +372,13 @@ function parseInvokeRequest(body, blueprintFileDir) {
|
|
|
372
372
|
}
|
|
373
373
|
const metadataEvent = metadata.event;
|
|
374
374
|
if (typeof metadataEvent !== 'string' ||
|
|
375
|
-
(!isEventType(metadataEvent) && metadataEvent !== '
|
|
375
|
+
(!isEventType(metadataEvent) && metadataEvent !== 'plain')) {
|
|
376
376
|
throw new Error('Request body is not valid, `metadata.event` field is not one of `create`, `update`, `delete`, or `scheduled`');
|
|
377
377
|
}
|
|
378
378
|
let before = null;
|
|
379
379
|
let after = null;
|
|
380
380
|
// Only GROQ-based events (create, update, delete) have before and after fields
|
|
381
|
-
if (metadataEvent !== '
|
|
381
|
+
if (metadataEvent !== 'plain') {
|
|
382
382
|
if (!('before' in metadata)) {
|
|
383
383
|
throw new Error('Request body is not valid, `metadata.before` field is missing');
|
|
384
384
|
}
|
|
@@ -418,9 +418,7 @@ function parseInvokeRequest(body, blueprintFileDir) {
|
|
|
418
418
|
},
|
|
419
419
|
event,
|
|
420
420
|
},
|
|
421
|
-
metadata: metadataEvent === '
|
|
422
|
-
? { event: metadataEvent }
|
|
423
|
-
: { event: metadataEvent, before, after },
|
|
421
|
+
metadata: metadataEvent === 'plain' ? { event: metadataEvent } : { event: metadataEvent, before, after },
|
|
424
422
|
};
|
|
425
423
|
}
|
|
426
424
|
export { app, buildApiUrl, parseDocumentUrl };
|
|
@@ -127,7 +127,7 @@ class PayloadPanel extends ApiBaseElement {
|
|
|
127
127
|
// for certain other specific function types, set the payload to something specific, or reload whatever was loaded into its document payload before
|
|
128
128
|
switch (selectedFunction.type) {
|
|
129
129
|
case this.SANITY_FUNCTION_SYNC_TAG_INVALIDATE:
|
|
130
|
-
this.#updateCodeView(this.api.store.payload, {
|
|
130
|
+
this.#updateCodeView(this.api.store.payload, {syncTags: ['s1:1234', 's2:5678']})
|
|
131
131
|
break
|
|
132
132
|
default:
|
|
133
133
|
this.#updateCodeView(this.api.store.payload, this.documentCacheByFunction[selectedIndex])
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** A left-to-right reveal of the wordmark; the last frame is the finished line. */
|
|
2
|
+
export declare function introFrames(): string[];
|
|
3
|
+
/** Print the intro, animated when the terminal can show it and static when it cannot. */
|
|
4
|
+
export declare function playIntro({ json }?: {
|
|
5
|
+
json?: boolean;
|
|
6
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { env, stdout } from 'node:process';
|
|
2
|
+
import { onExit } from 'signal-exit';
|
|
3
|
+
import { isInteractive } from '../interactive.js';
|
|
4
|
+
import { styleText } from '../style-text.js';
|
|
5
|
+
const INDENT = ' ';
|
|
6
|
+
const WORDMARK = 'sanity';
|
|
7
|
+
const TAIL = ' with blueprints';
|
|
8
|
+
/** The leading edge of the reveal, densest first. */
|
|
9
|
+
const GRADIENT = ['▓', '▒', '░'];
|
|
10
|
+
const REVEAL_PER_FRAME = 1;
|
|
11
|
+
const FRAME_MS = 45;
|
|
12
|
+
/** Time the finished wordmark holds before the command's own output follows. */
|
|
13
|
+
const HOLD_MS = 250;
|
|
14
|
+
const HIDE_CURSOR = '\x1b[?25l';
|
|
15
|
+
const SHOW_CURSOR = '\x1b[?25h';
|
|
16
|
+
const CLEAR_LINE = '\x1b[2K';
|
|
17
|
+
function style(revealed, edge) {
|
|
18
|
+
const wordmark = revealed.slice(0, WORDMARK.length);
|
|
19
|
+
const tail = revealed.slice(WORDMARK.length);
|
|
20
|
+
return INDENT + styleText(['bold', 'red'], wordmark) + styleText('dim', tail + edge);
|
|
21
|
+
}
|
|
22
|
+
/** A left-to-right reveal of the wordmark; the last frame is the finished line. */
|
|
23
|
+
export function introFrames() {
|
|
24
|
+
const full = WORDMARK + TAIL;
|
|
25
|
+
const frames = [];
|
|
26
|
+
for (let revealed = REVEAL_PER_FRAME; revealed < full.length; revealed += REVEAL_PER_FRAME) {
|
|
27
|
+
const edge = GRADIENT.slice(0, Math.min(GRADIENT.length, full.length - revealed)).join('');
|
|
28
|
+
frames.push(style(full.slice(0, revealed), edge));
|
|
29
|
+
}
|
|
30
|
+
frames.push(style(full, ''));
|
|
31
|
+
return frames;
|
|
32
|
+
}
|
|
33
|
+
function animates() {
|
|
34
|
+
return isInteractive && env.TERM !== 'dumb' && !env.CI;
|
|
35
|
+
}
|
|
36
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
37
|
+
/** Print the intro, animated when the terminal can show it and static when it cannot. */
|
|
38
|
+
export async function playIntro({ json = false } = {}) {
|
|
39
|
+
if (json)
|
|
40
|
+
return;
|
|
41
|
+
const frames = introFrames();
|
|
42
|
+
const finished = frames.at(-1) ?? '';
|
|
43
|
+
if (!animates()) {
|
|
44
|
+
stdout.write(`\n${finished}\n\n`);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
stdout.write(`\n${HIDE_CURSOR}`);
|
|
48
|
+
const cancelExitHook = onExit(() => {
|
|
49
|
+
stdout.write(SHOW_CURSOR);
|
|
50
|
+
});
|
|
51
|
+
try {
|
|
52
|
+
for (const frame of frames) {
|
|
53
|
+
stdout.write(`\r${CLEAR_LINE}${frame}`);
|
|
54
|
+
await sleep(FRAME_MS);
|
|
55
|
+
}
|
|
56
|
+
await sleep(HOLD_MS);
|
|
57
|
+
stdout.write('\n\n');
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
stdout.write(SHOW_CURSOR);
|
|
61
|
+
cancelExitHook();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -24,10 +24,13 @@ export declare function promptForOrganization({ token, knownOrganizationId, logg
|
|
|
24
24
|
/**
|
|
25
25
|
* Asks the user whether to scope a Blueprint to an organization or a project.
|
|
26
26
|
*/
|
|
27
|
-
export declare function promptForScope({ allowBack, hasProjects, }?: {
|
|
27
|
+
export declare function promptForScope({ allowBack, hasProjects, message, }?: {
|
|
28
28
|
allowBack?: boolean;
|
|
29
29
|
hasProjects?: boolean;
|
|
30
|
+
message?: string;
|
|
30
31
|
}): Promise<ScopeType | Back>;
|
|
32
|
+
/** Offers a recovery path after the selected scope cannot be read. */
|
|
33
|
+
export declare function promptForScopeRecovery(): Promise<'exit' | 'retry'>;
|
|
31
34
|
/**
|
|
32
35
|
* Prompts the user to pick a project from a pre-fetched list of projects.
|
|
33
36
|
* Use after promptForOrganization when the user chose project scope.
|
|
@@ -121,7 +121,7 @@ export async function promptForOrganization({ token, knownOrganizationId, logger
|
|
|
121
121
|
/**
|
|
122
122
|
* Asks the user whether to scope a Blueprint to an organization or a project.
|
|
123
123
|
*/
|
|
124
|
-
export async function promptForScope({ allowBack = false, hasProjects = true, } = {}) {
|
|
124
|
+
export async function promptForScope({ allowBack = false, hasProjects = true, message = 'How would you like to scope your Blueprint?', } = {}) {
|
|
125
125
|
const choices = [
|
|
126
126
|
{
|
|
127
127
|
name: 'Organization',
|
|
@@ -136,12 +136,26 @@ export async function promptForScope({ allowBack = false, hasProjects = true, }
|
|
|
136
136
|
},
|
|
137
137
|
];
|
|
138
138
|
const answer = await select({
|
|
139
|
-
message
|
|
139
|
+
message,
|
|
140
140
|
choices: withBackChoice(choices, allowBack),
|
|
141
141
|
default: 'organization',
|
|
142
142
|
});
|
|
143
143
|
return answer;
|
|
144
144
|
}
|
|
145
|
+
/** Offers a recovery path after the selected scope cannot be read. */
|
|
146
|
+
export async function promptForScopeRecovery() {
|
|
147
|
+
return await select({
|
|
148
|
+
message: 'What would you like to do?',
|
|
149
|
+
choices: [
|
|
150
|
+
{
|
|
151
|
+
name: 'Choose another organization or project',
|
|
152
|
+
value: 'retry',
|
|
153
|
+
},
|
|
154
|
+
{ name: 'Exit', value: 'exit' },
|
|
155
|
+
],
|
|
156
|
+
default: 'retry',
|
|
157
|
+
});
|
|
158
|
+
}
|
|
145
159
|
/**
|
|
146
160
|
* Prompts the user to pick a project from a pre-fetched list of projects.
|
|
147
161
|
* Use after promptForOrganization when the user chose project scope.
|
|
@@ -41,7 +41,7 @@ process.on('message', async (data) => {
|
|
|
41
41
|
|
|
42
42
|
// Otherwise this is the initial start message. Support the legacy untyped
|
|
43
43
|
// `{srcPath, payload}` shape as well as the new `{type: 'start', ...}`.
|
|
44
|
-
const {srcPath, payload, isDurable} = jsonData
|
|
44
|
+
const {srcPath, payload, isDurable, noWait} = jsonData
|
|
45
45
|
const {context, ...event} = payload
|
|
46
46
|
|
|
47
47
|
let logs = ''
|
|
@@ -83,9 +83,10 @@ process.on('message', async (data) => {
|
|
|
83
83
|
if (isDurable) {
|
|
84
84
|
json = await runDurable(
|
|
85
85
|
handler,
|
|
86
|
-
{context, event
|
|
86
|
+
{context, event},
|
|
87
87
|
{
|
|
88
88
|
emit: (e) => process.send(JSON.stringify({type: 'step-event', event: e})),
|
|
89
|
+
wait: (ms) => new Promise((resolve) => setTimeout(resolve, noWait ? 0 : ms)),
|
|
89
90
|
},
|
|
90
91
|
)
|
|
91
92
|
} else {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type {DurableContext, DurableHandler, DurableLogger, GenericEvent} from '@sanity/functions'
|
|
2
|
-
import type {
|
|
2
|
+
import type {DurableRuntimeEvent} from '../../types.js'
|
|
3
3
|
|
|
4
4
|
type RunDurableArgs = {
|
|
5
5
|
context: DurableContext
|
|
@@ -8,7 +8,7 @@ type RunDurableArgs = {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
type DurableIO = {
|
|
11
|
-
emit: (event:
|
|
11
|
+
emit: (event: DurableRuntimeEvent) => void
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export function runDurable(
|
|
@@ -1,17 +1,60 @@
|
|
|
1
1
|
import {performance} from 'node:perf_hooks'
|
|
2
|
+
import {inspect} from 'node:util'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
*
|
|
6
|
+
* @param duration
|
|
7
|
+
* @return {number}
|
|
8
|
+
*/
|
|
9
|
+
const durationToMs = (duration = {}) => {
|
|
10
|
+
const {days = 0, hours = 0, minutes = 0, seconds = 0} = duration
|
|
11
|
+
return (((days * 24 + hours) * 60 + minutes) * 60 + seconds) * 1000
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
*
|
|
16
|
+
* @param args
|
|
17
|
+
*/
|
|
18
|
+
const formatArgs = (args) =>
|
|
19
|
+
args.map((a) => (typeof a === 'string' ? a : inspect(a, {depth: null}))).join(' ')
|
|
2
20
|
|
|
3
21
|
/**
|
|
4
22
|
* Runs a durable function handler
|
|
5
23
|
* @param handler - callback being run
|
|
6
24
|
* @param context - data to send to the operation
|
|
7
25
|
* @param event - the step being run
|
|
8
|
-
* @param logger
|
|
9
26
|
* @param io
|
|
10
27
|
*/
|
|
11
|
-
export async function runDurable(handler, {context, event
|
|
28
|
+
export async function runDurable(handler, {context, event}, io) {
|
|
29
|
+
let currentStep = null
|
|
30
|
+
const streamLog = (level, args) => {
|
|
31
|
+
const date = new Date()
|
|
32
|
+
const timestamp = `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`
|
|
33
|
+
|
|
34
|
+
return io.emit({
|
|
35
|
+
timestamp,
|
|
36
|
+
kind: 'log',
|
|
37
|
+
phase: 'log',
|
|
38
|
+
step: currentStep,
|
|
39
|
+
level,
|
|
40
|
+
message: formatArgs(args),
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// @todo: need to throw error for use of `console.log` in a durable function
|
|
45
|
+
const logger = {
|
|
46
|
+
log: (...a) => streamLog('info', a),
|
|
47
|
+
info: (...a) => streamLog('info', a),
|
|
48
|
+
warn: (...a) => streamLog('warn', a),
|
|
49
|
+
error: (...a) => streamLog('error', a),
|
|
50
|
+
}
|
|
51
|
+
|
|
12
52
|
const track = async (name, kind, produce) => {
|
|
13
53
|
io.emit({name, kind, phase: 'start'})
|
|
14
54
|
const startedAt = performance.now()
|
|
55
|
+
const prev = currentStep
|
|
56
|
+
currentStep = name
|
|
57
|
+
|
|
15
58
|
try {
|
|
16
59
|
const value = await produce()
|
|
17
60
|
io.emit({
|
|
@@ -35,11 +78,18 @@ export async function runDurable(handler, {context, event, logger}, io) {
|
|
|
35
78
|
durationMs: performance.now() - startedAt,
|
|
36
79
|
})
|
|
37
80
|
throw err
|
|
81
|
+
} finally {
|
|
82
|
+
currentStep = prev
|
|
38
83
|
}
|
|
39
84
|
}
|
|
40
85
|
|
|
41
86
|
const step = {
|
|
42
|
-
run: ({name, handler: fn}) => track(name, 'run', () => fn({logger, attempt: 1})),
|
|
87
|
+
run: ({name, handler: fn}) => track(name || 'step-run', 'run', () => fn({logger, attempt: 1})),
|
|
88
|
+
wait: ({name, duration}) =>
|
|
89
|
+
track(name, 'wait', async () => {
|
|
90
|
+
await io.wait(durationToMs(duration))
|
|
91
|
+
return undefined
|
|
92
|
+
}),
|
|
43
93
|
}
|
|
44
94
|
|
|
45
95
|
return handler({context, event, step, logger})
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { DurableLogEvent, DurableRuntimeEvent, DurableStepEvent } from '../types.js';
|
|
2
|
+
type DurableLogLine = Pick<DurableLogEvent, 'level' | 'message'>;
|
|
3
|
+
type DurableJsonStep = DurableStepEvent & {
|
|
4
|
+
logs: DurableLogLine[];
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Records a durable log event
|
|
8
|
+
*/
|
|
9
|
+
export declare const createDurableJsonCollector: () => {
|
|
10
|
+
record: (event: DurableRuntimeEvent) => void;
|
|
11
|
+
result: () => {
|
|
12
|
+
steps: DurableJsonStep[];
|
|
13
|
+
logs: DurableLogLine[];
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Records a durable log event
|
|
3
|
+
*/
|
|
4
|
+
export const createDurableJsonCollector = () => {
|
|
5
|
+
const steps = [];
|
|
6
|
+
const logLines = [];
|
|
7
|
+
const activeSteps = new Map();
|
|
8
|
+
function record(event) {
|
|
9
|
+
if (event.kind === 'log') {
|
|
10
|
+
const logLine = {
|
|
11
|
+
level: event.level,
|
|
12
|
+
message: event.message,
|
|
13
|
+
};
|
|
14
|
+
if (event.step === null) {
|
|
15
|
+
logLines.push(logLine);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const step = activeSteps.get(event.step);
|
|
19
|
+
if (step) {
|
|
20
|
+
step.logs.push(logLine);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
logLines.push(logLine);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (event.phase === 'start') {
|
|
27
|
+
const step = {
|
|
28
|
+
...event,
|
|
29
|
+
logs: [],
|
|
30
|
+
};
|
|
31
|
+
steps.push(step);
|
|
32
|
+
activeSteps.set(event.name, step);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const step = activeSteps.get(event.name);
|
|
36
|
+
if (!step)
|
|
37
|
+
return;
|
|
38
|
+
step.phase = event.phase;
|
|
39
|
+
step.durationMs = event.durationMs;
|
|
40
|
+
if (event.phase === 'complete') {
|
|
41
|
+
step.output = event.output;
|
|
42
|
+
}
|
|
43
|
+
if (event.phase === 'error') {
|
|
44
|
+
step.error = event.error;
|
|
45
|
+
}
|
|
46
|
+
activeSteps.delete(event.name);
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
record,
|
|
50
|
+
result: () => ({ steps, logs: logLines }),
|
|
51
|
+
};
|
|
52
|
+
};
|