@sanity/runtime-cli 17.7.1 → 17.8.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 +19 -19
- package/dist/constants.d.ts +2 -0
- package/dist/constants.js +5 -0
- package/dist/cores/functions/test.js +48 -14
- package/dist/utils/functions/child-process-wrapper.js +18 -6
- package/dist/utils/functions/child-runtime/durable.d.ts +18 -0
- package/dist/utils/functions/child-runtime/durable.js +46 -0
- package/dist/utils/functions/invoke/local.js +12 -2
- package/dist/utils/progress.js +6 -3
- package/dist/utils/types.d.ts +26 -1
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -90,7 +90,7 @@ EXAMPLES
|
|
|
90
90
|
$ sanity-run blueprints add function --name my-function --fn-type document-create --fn-type document-update --lang js
|
|
91
91
|
```
|
|
92
92
|
|
|
93
|
-
_See code: [src/commands/blueprints/add.ts](https://github.com/sanity-io/runtime-cli/blob/v17.
|
|
93
|
+
_See code: [src/commands/blueprints/add.ts](https://github.com/sanity-io/runtime-cli/blob/v17.8.0/src/commands/blueprints/add.ts)_
|
|
94
94
|
|
|
95
95
|
## `sanity-run blueprints config`
|
|
96
96
|
|
|
@@ -127,7 +127,7 @@ EXAMPLES
|
|
|
127
127
|
$ sanity-run blueprints config --edit --project-id <projectId> --stack <name-or-id>
|
|
128
128
|
```
|
|
129
129
|
|
|
130
|
-
_See code: [src/commands/blueprints/config.ts](https://github.com/sanity-io/runtime-cli/blob/v17.
|
|
130
|
+
_See code: [src/commands/blueprints/config.ts](https://github.com/sanity-io/runtime-cli/blob/v17.8.0/src/commands/blueprints/config.ts)_
|
|
131
131
|
|
|
132
132
|
## `sanity-run blueprints deploy`
|
|
133
133
|
|
|
@@ -183,7 +183,7 @@ EXAMPLES
|
|
|
183
183
|
$ sanity-run blueprints deploy --new-stack-name <new-name>
|
|
184
184
|
```
|
|
185
185
|
|
|
186
|
-
_See code: [src/commands/blueprints/deploy.ts](https://github.com/sanity-io/runtime-cli/blob/v17.
|
|
186
|
+
_See code: [src/commands/blueprints/deploy.ts](https://github.com/sanity-io/runtime-cli/blob/v17.8.0/src/commands/blueprints/deploy.ts)_
|
|
187
187
|
|
|
188
188
|
## `sanity-run blueprints destroy`
|
|
189
189
|
|
|
@@ -221,7 +221,7 @@ EXAMPLES
|
|
|
221
221
|
$ sanity-run blueprints destroy --stack <name-or-id> --project-id <projectId> --force --no-wait
|
|
222
222
|
```
|
|
223
223
|
|
|
224
|
-
_See code: [src/commands/blueprints/destroy.ts](https://github.com/sanity-io/runtime-cli/blob/v17.
|
|
224
|
+
_See code: [src/commands/blueprints/destroy.ts](https://github.com/sanity-io/runtime-cli/blob/v17.8.0/src/commands/blueprints/destroy.ts)_
|
|
225
225
|
|
|
226
226
|
## `sanity-run blueprints doctor`
|
|
227
227
|
|
|
@@ -253,7 +253,7 @@ EXAMPLES
|
|
|
253
253
|
$ sanity-run blueprints doctor --fix
|
|
254
254
|
```
|
|
255
255
|
|
|
256
|
-
_See code: [src/commands/blueprints/doctor.ts](https://github.com/sanity-io/runtime-cli/blob/v17.
|
|
256
|
+
_See code: [src/commands/blueprints/doctor.ts](https://github.com/sanity-io/runtime-cli/blob/v17.8.0/src/commands/blueprints/doctor.ts)_
|
|
257
257
|
|
|
258
258
|
## `sanity-run blueprints info`
|
|
259
259
|
|
|
@@ -291,7 +291,7 @@ EXAMPLES
|
|
|
291
291
|
$ sanity-run blueprints info --organization-id <orgId> --stack <name-or-id>
|
|
292
292
|
```
|
|
293
293
|
|
|
294
|
-
_See code: [src/commands/blueprints/info.ts](https://github.com/sanity-io/runtime-cli/blob/v17.
|
|
294
|
+
_See code: [src/commands/blueprints/info.ts](https://github.com/sanity-io/runtime-cli/blob/v17.8.0/src/commands/blueprints/info.ts)_
|
|
295
295
|
|
|
296
296
|
## `sanity-run blueprints init [DIR]`
|
|
297
297
|
|
|
@@ -355,7 +355,7 @@ EXAMPLES
|
|
|
355
355
|
$ sanity-run blueprints init old-stack --type <json|js|ts> --project-id <projectId> --stack-id <existingStackId>
|
|
356
356
|
```
|
|
357
357
|
|
|
358
|
-
_See code: [src/commands/blueprints/init.ts](https://github.com/sanity-io/runtime-cli/blob/v17.
|
|
358
|
+
_See code: [src/commands/blueprints/init.ts](https://github.com/sanity-io/runtime-cli/blob/v17.8.0/src/commands/blueprints/init.ts)_
|
|
359
359
|
|
|
360
360
|
## `sanity-run blueprints logs`
|
|
361
361
|
|
|
@@ -401,7 +401,7 @@ EXAMPLES
|
|
|
401
401
|
$ sanity-run blueprints logs --before 2026-05-01T00:00:00Z
|
|
402
402
|
```
|
|
403
403
|
|
|
404
|
-
_See code: [src/commands/blueprints/logs.ts](https://github.com/sanity-io/runtime-cli/blob/v17.
|
|
404
|
+
_See code: [src/commands/blueprints/logs.ts](https://github.com/sanity-io/runtime-cli/blob/v17.8.0/src/commands/blueprints/logs.ts)_
|
|
405
405
|
|
|
406
406
|
## `sanity-run blueprints mint-deploy-token`
|
|
407
407
|
|
|
@@ -446,7 +446,7 @@ EXAMPLES
|
|
|
446
446
|
$ sanity-run blueprints mint-deploy-token --organization-id <orgId>
|
|
447
447
|
```
|
|
448
448
|
|
|
449
|
-
_See code: [src/commands/blueprints/mint-deploy-token.ts](https://github.com/sanity-io/runtime-cli/blob/v17.
|
|
449
|
+
_See code: [src/commands/blueprints/mint-deploy-token.ts](https://github.com/sanity-io/runtime-cli/blob/v17.8.0/src/commands/blueprints/mint-deploy-token.ts)_
|
|
450
450
|
|
|
451
451
|
## `sanity-run blueprints plan`
|
|
452
452
|
|
|
@@ -479,7 +479,7 @@ EXAMPLES
|
|
|
479
479
|
$ sanity-run blueprints plan --organization-id <orgId> --stack <name-or-id>
|
|
480
480
|
```
|
|
481
481
|
|
|
482
|
-
_See code: [src/commands/blueprints/plan.ts](https://github.com/sanity-io/runtime-cli/blob/v17.
|
|
482
|
+
_See code: [src/commands/blueprints/plan.ts](https://github.com/sanity-io/runtime-cli/blob/v17.8.0/src/commands/blueprints/plan.ts)_
|
|
483
483
|
|
|
484
484
|
## `sanity-run blueprints promote`
|
|
485
485
|
|
|
@@ -515,7 +515,7 @@ EXAMPLES
|
|
|
515
515
|
$ sanity-run blueprints promote --new-stack-name <new-name>
|
|
516
516
|
```
|
|
517
517
|
|
|
518
|
-
_See code: [src/commands/blueprints/promote.ts](https://github.com/sanity-io/runtime-cli/blob/v17.
|
|
518
|
+
_See code: [src/commands/blueprints/promote.ts](https://github.com/sanity-io/runtime-cli/blob/v17.8.0/src/commands/blueprints/promote.ts)_
|
|
519
519
|
|
|
520
520
|
## `sanity-run blueprints stacks`
|
|
521
521
|
|
|
@@ -551,7 +551,7 @@ EXAMPLES
|
|
|
551
551
|
$ sanity-run blueprints stacks --organization-id <organizationId> --include-projects
|
|
552
552
|
```
|
|
553
553
|
|
|
554
|
-
_See code: [src/commands/blueprints/stacks.ts](https://github.com/sanity-io/runtime-cli/blob/v17.
|
|
554
|
+
_See code: [src/commands/blueprints/stacks.ts](https://github.com/sanity-io/runtime-cli/blob/v17.8.0/src/commands/blueprints/stacks.ts)_
|
|
555
555
|
|
|
556
556
|
## `sanity-run functions add`
|
|
557
557
|
|
|
@@ -603,7 +603,7 @@ EXAMPLES
|
|
|
603
603
|
$ sanity-run functions add --name my-function --type document-create --type document-update --lang js
|
|
604
604
|
```
|
|
605
605
|
|
|
606
|
-
_See code: [src/commands/functions/add.ts](https://github.com/sanity-io/runtime-cli/blob/v17.
|
|
606
|
+
_See code: [src/commands/functions/add.ts](https://github.com/sanity-io/runtime-cli/blob/v17.8.0/src/commands/functions/add.ts)_
|
|
607
607
|
|
|
608
608
|
## `sanity-run functions dev`
|
|
609
609
|
|
|
@@ -638,7 +638,7 @@ EXAMPLES
|
|
|
638
638
|
$ sanity-run functions dev --timeout 60
|
|
639
639
|
```
|
|
640
640
|
|
|
641
|
-
_See code: [src/commands/functions/dev.ts](https://github.com/sanity-io/runtime-cli/blob/v17.
|
|
641
|
+
_See code: [src/commands/functions/dev.ts](https://github.com/sanity-io/runtime-cli/blob/v17.8.0/src/commands/functions/dev.ts)_
|
|
642
642
|
|
|
643
643
|
## `sanity-run functions env add NAME KEY VALUE`
|
|
644
644
|
|
|
@@ -671,7 +671,7 @@ EXAMPLES
|
|
|
671
671
|
$ sanity-run functions env add --stack <name-or-id> MyFunction API_URL https://api.example.com/
|
|
672
672
|
```
|
|
673
673
|
|
|
674
|
-
_See code: [src/commands/functions/env/add.ts](https://github.com/sanity-io/runtime-cli/blob/v17.
|
|
674
|
+
_See code: [src/commands/functions/env/add.ts](https://github.com/sanity-io/runtime-cli/blob/v17.8.0/src/commands/functions/env/add.ts)_
|
|
675
675
|
|
|
676
676
|
## `sanity-run functions env list NAME`
|
|
677
677
|
|
|
@@ -701,7 +701,7 @@ EXAMPLES
|
|
|
701
701
|
$ sanity-run functions env list --stack <name-or-id> MyFunction
|
|
702
702
|
```
|
|
703
703
|
|
|
704
|
-
_See code: [src/commands/functions/env/list.ts](https://github.com/sanity-io/runtime-cli/blob/v17.
|
|
704
|
+
_See code: [src/commands/functions/env/list.ts](https://github.com/sanity-io/runtime-cli/blob/v17.8.0/src/commands/functions/env/list.ts)_
|
|
705
705
|
|
|
706
706
|
## `sanity-run functions env remove NAME KEY`
|
|
707
707
|
|
|
@@ -733,7 +733,7 @@ EXAMPLES
|
|
|
733
733
|
$ sanity-run functions env remove --stack <name-or-id> MyFunction API_URL
|
|
734
734
|
```
|
|
735
735
|
|
|
736
|
-
_See code: [src/commands/functions/env/remove.ts](https://github.com/sanity-io/runtime-cli/blob/v17.
|
|
736
|
+
_See code: [src/commands/functions/env/remove.ts](https://github.com/sanity-io/runtime-cli/blob/v17.8.0/src/commands/functions/env/remove.ts)_
|
|
737
737
|
|
|
738
738
|
## `sanity-run functions logs [NAME]`
|
|
739
739
|
|
|
@@ -773,7 +773,7 @@ EXAMPLES
|
|
|
773
773
|
$ sanity-run functions logs <name> --delete
|
|
774
774
|
```
|
|
775
775
|
|
|
776
|
-
_See code: [src/commands/functions/logs.ts](https://github.com/sanity-io/runtime-cli/blob/v17.
|
|
776
|
+
_See code: [src/commands/functions/logs.ts](https://github.com/sanity-io/runtime-cli/blob/v17.8.0/src/commands/functions/logs.ts)_
|
|
777
777
|
|
|
778
778
|
## `sanity-run functions test [NAME]`
|
|
779
779
|
|
|
@@ -830,7 +830,7 @@ EXAMPLES
|
|
|
830
830
|
$ sanity-run functions test <name> --event update --data-before '{ "title": "before" }' --data-after '{ "title": "after" }'
|
|
831
831
|
```
|
|
832
832
|
|
|
833
|
-
_See code: [src/commands/functions/test.ts](https://github.com/sanity-io/runtime-cli/blob/v17.
|
|
833
|
+
_See code: [src/commands/functions/test.ts](https://github.com/sanity-io/runtime-cli/blob/v17.8.0/src/commands/functions/test.ts)_
|
|
834
834
|
|
|
835
835
|
## `sanity-run help [COMMAND]`
|
|
836
836
|
|
package/dist/constants.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { DurableStepPhase } from './utils/types.js';
|
|
1
2
|
export declare const SANITY_ACCESS_ROLE = "sanity.access.role";
|
|
2
3
|
export declare const SANITY_ACCESS_ROBOT = "sanity.access.robot";
|
|
3
4
|
export declare const SANITY_FUNCTION_PREFIX = "sanity.function.";
|
|
@@ -50,3 +51,4 @@ export declare const LABEL_PUBSUB = "PubSub";
|
|
|
50
51
|
export declare const LABEL_SCHEDULED = "Scheduled";
|
|
51
52
|
export declare const LABEL_SYNC_TAG_INVALIDATE = "Sync Tag Invalidate";
|
|
52
53
|
export declare const CI_SETUP_DOCS_URL = "https://www.sanity.io/docs/blueprints/deploy-blueprints-from-ci";
|
|
54
|
+
export declare const STEP_PHASE_GLYPH: Record<DurableStepPhase, string>;
|
package/dist/constants.js
CHANGED
|
@@ -56,3 +56,8 @@ export const LABEL_PUBSUB = 'PubSub';
|
|
|
56
56
|
export const LABEL_SCHEDULED = 'Scheduled';
|
|
57
57
|
export const LABEL_SYNC_TAG_INVALIDATE = 'Sync Tag Invalidate';
|
|
58
58
|
export const CI_SETUP_DOCS_URL = 'https://www.sanity.io/docs/blueprints/deploy-blueprints-from-ci';
|
|
59
|
+
export const STEP_PHASE_GLYPH = {
|
|
60
|
+
start: '▶',
|
|
61
|
+
complete: '✔',
|
|
62
|
+
error: '✖',
|
|
63
|
+
};
|
|
@@ -3,7 +3,7 @@ import { dirname, join } from 'node:path';
|
|
|
3
3
|
import { cwd } from 'node:process';
|
|
4
4
|
import { testAction } from '../../actions/functions/test.js';
|
|
5
5
|
import config from '../../config.js';
|
|
6
|
-
import { SANITY_FUNCTION_DOCUMENT, SANITY_FUNCTION_MEDIA_LIBRARY_ASSET } from '../../constants.js';
|
|
6
|
+
import { SANITY_FUNCTION_DOCUMENT, SANITY_FUNCTION_DURABLE, SANITY_FUNCTION_MEDIA_LIBRARY_ASSET, STEP_PHASE_GLYPH, } from '../../constants.js';
|
|
7
7
|
import { resolveDeployedResources } from '../../utils/blueprints/resolve-deployed-resources.js';
|
|
8
8
|
import buildPayload from '../../utils/functions/build-payload.js';
|
|
9
9
|
import { fetchAsset, fetchDocument } from '../../utils/functions/fetch-document.js';
|
|
@@ -11,6 +11,20 @@ import { findFunctionInResources, getFunctionNames } from '../../utils/functions
|
|
|
11
11
|
import { runFunction } from '../../utils/functions/invoke/nested.js';
|
|
12
12
|
import { parseJsonObject } from '../../utils/parse-json-object.js';
|
|
13
13
|
import { isEventType, } from '../../utils/types.js';
|
|
14
|
+
/**
|
|
15
|
+
* CLI lines for a durable run, in emission order; bare `start` events dropped.
|
|
16
|
+
* @param events
|
|
17
|
+
*/
|
|
18
|
+
function renderStepTimeline(events) {
|
|
19
|
+
return events
|
|
20
|
+
.filter((event) => event.phase !== 'start')
|
|
21
|
+
.map((event) => {
|
|
22
|
+
const glyph = STEP_PHASE_GLYPH[event.phase] ?? '•';
|
|
23
|
+
const duration = typeof event.durationMs === 'number' ? ` (${Math.round(event.durationMs)}ms)` : '';
|
|
24
|
+
const detail = event.phase === 'error' && event.error ? ` ${event.error.message}` : '';
|
|
25
|
+
return ` ${glyph} ${event.name} [${event.kind}]${duration}${detail}`;
|
|
26
|
+
});
|
|
27
|
+
}
|
|
14
28
|
export async function functionTestCore(options) {
|
|
15
29
|
const { blueprint, log, error, args, flags, helpText } = options;
|
|
16
30
|
const { name: fnName } = args;
|
|
@@ -42,6 +56,7 @@ export async function functionTestCore(options) {
|
|
|
42
56
|
const resource = findFunctionInResources(blueprint.resources, fnName); // throws if not found
|
|
43
57
|
const docFunction = resource.type === SANITY_FUNCTION_DOCUMENT;
|
|
44
58
|
const mediaFunction = resource.type === SANITY_FUNCTION_MEDIA_LIBRARY_ASSET;
|
|
59
|
+
const durableFunction = resource.type === SANITY_FUNCTION_DURABLE;
|
|
45
60
|
const resolvedResources = await resolveDeployedResources({
|
|
46
61
|
resources: blueprint.resources,
|
|
47
62
|
stackId: blueprint.stackId,
|
|
@@ -51,7 +66,10 @@ export async function functionTestCore(options) {
|
|
|
51
66
|
logger: log,
|
|
52
67
|
});
|
|
53
68
|
const blueprintFileDir = dirname(blueprint.fileInfo.blueprintFilePath);
|
|
54
|
-
const
|
|
69
|
+
const isMediaSource = mediaFunction || (durableFunction && resource.event?.type === 'media-library');
|
|
70
|
+
const eventResourceType = isMediaSource ? 'media-library' : 'dataset';
|
|
71
|
+
const eventResourceId = (isMediaSource ? mediaLibraryId : `${projectId}.${dataset}`) || '';
|
|
72
|
+
const contextOptions = docFunction || mediaFunction || durableFunction
|
|
55
73
|
? {
|
|
56
74
|
clientOptions: {
|
|
57
75
|
apiVersion: api,
|
|
@@ -59,8 +77,8 @@ export async function functionTestCore(options) {
|
|
|
59
77
|
projectId,
|
|
60
78
|
organizationId,
|
|
61
79
|
},
|
|
62
|
-
eventResourceType
|
|
63
|
-
eventResourceId
|
|
80
|
+
eventResourceType,
|
|
81
|
+
eventResourceId,
|
|
64
82
|
functionResourceType: 'project',
|
|
65
83
|
functionResourceId: projectId || '',
|
|
66
84
|
resources: resolvedResources,
|
|
@@ -139,19 +157,22 @@ export async function functionTestCore(options) {
|
|
|
139
157
|
payload = buildPayload({ data, file });
|
|
140
158
|
}
|
|
141
159
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
: {
|
|
150
|
-
event: 'scheduled',
|
|
151
|
-
};
|
|
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
|
+
}
|
|
152
167
|
// Records each nested context.invoke() so they can be surfaced in order after the spinner resolves.
|
|
153
168
|
const nestedRuns = [];
|
|
154
169
|
let nestedOrder = 0;
|
|
170
|
+
const stepEvents = [];
|
|
171
|
+
const onStepEvent = durableFunction
|
|
172
|
+
? (event) => {
|
|
173
|
+
stepEvents.push(event);
|
|
174
|
+
}
|
|
175
|
+
: undefined;
|
|
155
176
|
/**
|
|
156
177
|
* Re-entrant callback that resolves a nested context.invoke(name, payload):
|
|
157
178
|
* it finds the named function in the same blueprint, reuses the already
|
|
@@ -188,6 +209,7 @@ export async function functionTestCore(options) {
|
|
|
188
209
|
onNestedInvoke: runNested,
|
|
189
210
|
depth: 0,
|
|
190
211
|
callStack: [fnName],
|
|
212
|
+
onStepEvent,
|
|
191
213
|
});
|
|
192
214
|
if (error) {
|
|
193
215
|
spinner.fail('Function execution failed.');
|
|
@@ -199,6 +221,12 @@ export async function functionTestCore(options) {
|
|
|
199
221
|
logNestedRuns();
|
|
200
222
|
const errorMessage = error.stack || error.message || error.name;
|
|
201
223
|
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
|
+
}
|
|
202
230
|
return {
|
|
203
231
|
success: false,
|
|
204
232
|
error: errorMessage,
|
|
@@ -216,6 +244,12 @@ export async function functionTestCore(options) {
|
|
|
216
244
|
log('Response:');
|
|
217
245
|
log(JSON.stringify(response, null, 2));
|
|
218
246
|
}
|
|
247
|
+
if (durableFunction && stepEvents.length > 0) {
|
|
248
|
+
log('Steps:');
|
|
249
|
+
for (const line of renderStepTimeline(stepEvents)) {
|
|
250
|
+
log(line);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
219
253
|
return { success: true, json: { response, logs } };
|
|
220
254
|
}
|
|
221
255
|
catch (error) {
|
|
@@ -3,6 +3,7 @@ import {isAbsolute, join} from 'node:path'
|
|
|
3
3
|
import process from 'node:process'
|
|
4
4
|
import {pathToFileURL} from 'node:url'
|
|
5
5
|
import {installConsole} from './child-runtime/console.js'
|
|
6
|
+
import {runDurable} from './child-runtime/durable.js'
|
|
6
7
|
import {createInvoker} from './child-runtime/invoke.js'
|
|
7
8
|
import {createResourcesApi} from './child-runtime/resources.js'
|
|
8
9
|
|
|
@@ -40,7 +41,7 @@ process.on('message', async (data) => {
|
|
|
40
41
|
|
|
41
42
|
// Otherwise this is the initial start message. Support the legacy untyped
|
|
42
43
|
// `{srcPath, payload}` shape as well as the new `{type: 'start', ...}`.
|
|
43
|
-
const {srcPath, payload} = jsonData
|
|
44
|
+
const {srcPath, payload, isDurable} = jsonData
|
|
44
45
|
const {context, ...event} = payload
|
|
45
46
|
|
|
46
47
|
let logs = ''
|
|
@@ -78,11 +79,22 @@ process.on('message', async (data) => {
|
|
|
78
79
|
|
|
79
80
|
// Allow the handler to invoke other blueprint functions by name.
|
|
80
81
|
context.invoke = invoker.invoke
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
82
|
+
|
|
83
|
+
if (isDurable) {
|
|
84
|
+
json = await runDurable(
|
|
85
|
+
handler,
|
|
86
|
+
{context, event, logger: console},
|
|
87
|
+
{
|
|
88
|
+
emit: (e) => process.send(JSON.stringify({type: 'step-event', event: e})),
|
|
89
|
+
},
|
|
90
|
+
)
|
|
91
|
+
} else {
|
|
92
|
+
json = await handler({
|
|
93
|
+
context,
|
|
94
|
+
event,
|
|
95
|
+
done: async () => new Response(null, {status: 204}),
|
|
96
|
+
})
|
|
97
|
+
}
|
|
86
98
|
|
|
87
99
|
// Restore streams
|
|
88
100
|
process.stdout.write = originalStdoutWrite
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type {DurableContext, DurableHandler, DurableLogger, GenericEvent} from '@sanity/functions'
|
|
2
|
+
import type {DurableStepEvent} from '../../types.js'
|
|
3
|
+
|
|
4
|
+
type RunDurableArgs = {
|
|
5
|
+
context: DurableContext
|
|
6
|
+
event: GenericEvent
|
|
7
|
+
logger: DurableLogger
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
type DurableIO = {
|
|
11
|
+
emit: (event: DurableStepEvent) => void
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function runDurable(
|
|
15
|
+
handler: DurableHandler,
|
|
16
|
+
args: RunDurableArgs,
|
|
17
|
+
io: DurableIO,
|
|
18
|
+
): Promise<unknown>
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import {performance} from 'node:perf_hooks'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Runs a durable function handler
|
|
5
|
+
* @param handler - callback being run
|
|
6
|
+
* @param context - data to send to the operation
|
|
7
|
+
* @param event - the step being run
|
|
8
|
+
* @param logger
|
|
9
|
+
* @param io
|
|
10
|
+
*/
|
|
11
|
+
export async function runDurable(handler, {context, event, logger}, io) {
|
|
12
|
+
const track = async (name, kind, produce) => {
|
|
13
|
+
io.emit({name, kind, phase: 'start'})
|
|
14
|
+
const startedAt = performance.now()
|
|
15
|
+
try {
|
|
16
|
+
const value = await produce()
|
|
17
|
+
io.emit({
|
|
18
|
+
name,
|
|
19
|
+
kind,
|
|
20
|
+
phase: 'complete',
|
|
21
|
+
output: value,
|
|
22
|
+
durationMs: performance.now() - startedAt,
|
|
23
|
+
})
|
|
24
|
+
return value
|
|
25
|
+
} catch (err) {
|
|
26
|
+
io.emit({
|
|
27
|
+
name,
|
|
28
|
+
kind,
|
|
29
|
+
phase: 'error',
|
|
30
|
+
error: {
|
|
31
|
+
name: err?.name ?? 'Error',
|
|
32
|
+
message: err?.message ?? String(err),
|
|
33
|
+
stack: err?.stack ?? '',
|
|
34
|
+
},
|
|
35
|
+
durationMs: performance.now() - startedAt,
|
|
36
|
+
})
|
|
37
|
+
throw err
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const step = {
|
|
42
|
+
run: ({name, handler: fn}) => track(name, 'run', () => fn({logger, attempt: 1})),
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return handler({context, event, step, logger})
|
|
46
|
+
}
|
|
@@ -5,6 +5,7 @@ import { setTimeout } from 'node:timers';
|
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
import * as groq from 'groq-js';
|
|
7
7
|
import config from '../../../config.js';
|
|
8
|
+
import { SANITY_FUNCTION_DURABLE } from '../../../constants.js';
|
|
8
9
|
import { groupBlueprintResources } from '../../blueprints/group-resources.js';
|
|
9
10
|
import { isGroqContextOptions, } from '../../types.js';
|
|
10
11
|
import { acquireFunctionBuild } from './prepare.js';
|
|
@@ -42,7 +43,7 @@ export default async function invoke(resource, payload, context, options) {
|
|
|
42
43
|
if (!resource.src) {
|
|
43
44
|
throw new Error(`Function resource "${resource.name}" is missing the 'src' property.`);
|
|
44
45
|
}
|
|
45
|
-
const { forceColor = true, timeout = 10, onNestedInvoke, depth = 0, callStack = [] } = options;
|
|
46
|
+
const { forceColor = true, timeout = 10, onNestedInvoke, depth = 0, callStack = [], onStepEvent = () => { }, } = options;
|
|
46
47
|
// Apply the GROQ rule before preparing a build so a filtered-out event skips
|
|
47
48
|
// the (potentially expensive) transpile entirely.
|
|
48
49
|
let filteredData = payload?.payload?.data || {};
|
|
@@ -208,6 +209,10 @@ export default async function invoke(resource, payload, context, options) {
|
|
|
208
209
|
catch {
|
|
209
210
|
return;
|
|
210
211
|
}
|
|
212
|
+
if (msg?.type === 'step-event') {
|
|
213
|
+
onStepEvent(msg.event);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
211
216
|
// Nested invoke request — handle without tearing down the child.
|
|
212
217
|
if (msg?.type === 'invoke-request') {
|
|
213
218
|
handleNestedInvoke({ id: msg.id, name: msg.name, payload: msg.payload });
|
|
@@ -289,7 +294,12 @@ export default async function invoke(resource, payload, context, options) {
|
|
|
289
294
|
}),
|
|
290
295
|
},
|
|
291
296
|
};
|
|
292
|
-
const startMessage = {
|
|
297
|
+
const startMessage = {
|
|
298
|
+
type: 'start',
|
|
299
|
+
srcPath: functionPath,
|
|
300
|
+
payload,
|
|
301
|
+
isDurable: resource.type === SANITY_FUNCTION_DURABLE,
|
|
302
|
+
};
|
|
293
303
|
child.send(JSON.stringify(startMessage));
|
|
294
304
|
}
|
|
295
305
|
function shutdown() {
|
package/dist/utils/progress.js
CHANGED
|
@@ -2,6 +2,9 @@ import { stderr } from 'node:process';
|
|
|
2
2
|
import { onExit } from 'signal-exit';
|
|
3
3
|
const ST = '\x1b\\';
|
|
4
4
|
export const HEARTBEAT_INTERVAL_MS = 5_000;
|
|
5
|
+
// Ora clobbers stderr.write and messes with OSC output.
|
|
6
|
+
// Keep the original writer so progress sequences do not collide.
|
|
7
|
+
const writeToStderr = stderr.write.bind(stderr);
|
|
5
8
|
const STATES = {
|
|
6
9
|
clear: 0,
|
|
7
10
|
normal: 1,
|
|
@@ -24,7 +27,7 @@ function startHeartbeat(sequence) {
|
|
|
24
27
|
return;
|
|
25
28
|
heartbeat = setInterval(() => {
|
|
26
29
|
if (stderr.isTTY && heartbeatSequence)
|
|
27
|
-
|
|
30
|
+
writeToStderr(heartbeatSequence);
|
|
28
31
|
}, HEARTBEAT_INTERVAL_MS);
|
|
29
32
|
heartbeat.unref();
|
|
30
33
|
}
|
|
@@ -37,7 +40,7 @@ function write(state, value = 0) {
|
|
|
37
40
|
exitHookRegistered = true;
|
|
38
41
|
function clear() {
|
|
39
42
|
stopHeartbeat();
|
|
40
|
-
|
|
43
|
+
writeToStderr(`\x1b]9;4;0;0${ST}`);
|
|
41
44
|
}
|
|
42
45
|
// arc/utils SIGINT handler clobbers our signal-exit; luckily it then calls process.exit()
|
|
43
46
|
process.once('exit', clear); // triggered by arc/utils SIGINT handler
|
|
@@ -47,7 +50,7 @@ function write(state, value = 0) {
|
|
|
47
50
|
const active = state === 'normal' || state === 'paused' || state === 'busy';
|
|
48
51
|
if (!active)
|
|
49
52
|
stopHeartbeat();
|
|
50
|
-
|
|
53
|
+
writeToStderr(sequence);
|
|
51
54
|
if (active)
|
|
52
55
|
startHeartbeat(sequence);
|
|
53
56
|
}
|
package/dist/utils/types.d.ts
CHANGED
|
@@ -111,7 +111,7 @@ export interface InvokeNonGroqPayloadOptions {
|
|
|
111
111
|
export type InvokePayloadOptions = InvokeGroqPayloadOptions | InvokeNonGroqPayloadOptions;
|
|
112
112
|
export type InvokePayloadMetadata = Pick<InvokeGroqPayloadOptions, 'event' | 'before' | 'after'> | Pick<InvokeNonGroqPayloadOptions, 'event'>;
|
|
113
113
|
export type EventType = 'create' | 'update' | 'delete';
|
|
114
|
-
export type NonGroqEventType = 'scheduled' | 'nested';
|
|
114
|
+
export type NonGroqEventType = 'scheduled' | 'nested' | 'durable';
|
|
115
115
|
export declare function isEventType(arg: string): arg is EventType;
|
|
116
116
|
/** @internal */
|
|
117
117
|
export interface InvokeGroqContextOptions {
|
|
@@ -161,6 +161,8 @@ export interface InvokeExecutionOptions {
|
|
|
161
161
|
depth?: number;
|
|
162
162
|
/** Function names already on the invoke call stack (innermost last), used to detect cycles. @internal */
|
|
163
163
|
callStack?: string[];
|
|
164
|
+
/** Stream a durable step transition to the caller. @internal */
|
|
165
|
+
onStepEvent?: (event: DurableStepEvent) => void;
|
|
164
166
|
}
|
|
165
167
|
/**
|
|
166
168
|
* Re-entrant callback that executes a named function from the blueprint on behalf
|
|
@@ -178,6 +180,7 @@ export type ParentToChildMessage = {
|
|
|
178
180
|
type: 'start';
|
|
179
181
|
srcPath: string;
|
|
180
182
|
payload: unknown;
|
|
183
|
+
isDurable?: boolean;
|
|
181
184
|
} | {
|
|
182
185
|
type: 'invoke-response';
|
|
183
186
|
id: string;
|
|
@@ -190,6 +193,9 @@ export type ParentToChildMessage = {
|
|
|
190
193
|
id: string;
|
|
191
194
|
ok: false;
|
|
192
195
|
error: InvocationError;
|
|
196
|
+
} | {
|
|
197
|
+
type: 'step-event';
|
|
198
|
+
event: DurableStepEvent;
|
|
193
199
|
};
|
|
194
200
|
/**
|
|
195
201
|
* IPC messages sent from a function child process back up to the parent.
|
|
@@ -202,12 +208,16 @@ export type ChildToParentMessage = {
|
|
|
202
208
|
id: string;
|
|
203
209
|
name: string;
|
|
204
210
|
payload: Record<string, unknown>;
|
|
211
|
+
isDurable?: boolean;
|
|
205
212
|
} | {
|
|
206
213
|
type: 'result';
|
|
207
214
|
json?: JsonInvocationResponse;
|
|
208
215
|
logs?: string;
|
|
209
216
|
errorLogs?: string;
|
|
210
217
|
error?: InvocationError;
|
|
218
|
+
} | {
|
|
219
|
+
type: 'step-event';
|
|
220
|
+
event: DurableStepEvent;
|
|
211
221
|
};
|
|
212
222
|
/** @internal */
|
|
213
223
|
export interface InvocationResponse {
|
|
@@ -290,4 +300,19 @@ export type Warning = {
|
|
|
290
300
|
code: string;
|
|
291
301
|
suggestions: string[];
|
|
292
302
|
};
|
|
303
|
+
/** @internal */
|
|
304
|
+
export type DurableOperationSteps = 'run';
|
|
305
|
+
/** @internal */
|
|
306
|
+
export type DurableStepPhase = 'start' | 'complete' | 'error';
|
|
307
|
+
/**
|
|
308
|
+
* Represents a single step in a durable operation
|
|
309
|
+
*/
|
|
310
|
+
export interface DurableStepEvent {
|
|
311
|
+
name: string;
|
|
312
|
+
kind: DurableOperationSteps;
|
|
313
|
+
phase: DurableStepPhase;
|
|
314
|
+
output?: unknown;
|
|
315
|
+
error?: InvocationError;
|
|
316
|
+
durationMs?: number;
|
|
317
|
+
}
|
|
293
318
|
export {};
|
package/oclif.manifest.json
CHANGED