@sanity/runtime-cli 17.15.0 → 17.16.0-next.g8d59d6f

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.
Files changed (37) hide show
  1. package/README.md +19 -19
  2. package/dist/actions/blueprints/assets.d.ts +3 -3
  3. package/dist/actions/blueprints/assets.js +3 -3
  4. package/dist/actions/blueprints/blueprint.d.ts +20 -9
  5. package/dist/actions/blueprints/blueprint.js +25 -79
  6. package/dist/actions/blueprints/config.d.ts +1 -0
  7. package/dist/actions/blueprints/config.js +7 -1
  8. package/dist/actions/blueprints/pre-deploy.js +3 -3
  9. package/dist/actions/blueprints/process-assets.js +3 -2
  10. package/dist/baseCommands.js +3 -3
  11. package/dist/config.d.ts +1 -2
  12. package/dist/config.js +1 -2
  13. package/dist/cores/blueprints/doctor.js +2 -2
  14. package/dist/cores/blueprints/init.js +11 -2
  15. package/dist/cores/functions/test.js +29 -6
  16. package/dist/server/app.js +4 -4
  17. package/dist/server/static/components/app.css +8 -1
  18. package/dist/utils/blueprints/group-resources.d.ts +2 -1
  19. package/dist/utils/blueprints/group-resources.js +3 -3
  20. package/dist/utils/blueprints/resolve-deployed-resources.d.ts +2 -2
  21. package/dist/utils/blueprints/resolve-deployed-resources.js +4 -4
  22. package/dist/utils/bundles/prepare-asset.d.ts +3 -3
  23. package/dist/utils/bundles/prepare-asset.js +8 -7
  24. package/dist/utils/functions/child-process-wrapper.js +1 -1
  25. package/dist/utils/functions/child-runtime/invoke.d.ts +1 -0
  26. package/dist/utils/functions/child-runtime/invoke.js +26 -9
  27. package/dist/utils/functions/extract-durable-config.js +1 -4
  28. package/dist/utils/functions/invoke/local.js +11 -5
  29. package/dist/utils/functions/invoke/nested.js +2 -1
  30. package/dist/utils/functions/invoke/resolve-invoke-type.d.ts +2 -2
  31. package/dist/utils/functions/invoke/resolve-invoke-type.js +2 -2
  32. package/dist/utils/types.d.ts +20 -13
  33. package/dist/utils/types.js +1 -1
  34. package/oclif.manifest.json +1 -1
  35. package/package.json +2 -2
  36. package/dist/actions/blueprints/resolve.d.ts +0 -51
  37. package/dist/actions/blueprints/resolve.js +0 -52
@@ -1,7 +1,7 @@
1
1
  import { dirname } from 'node:path';
2
2
  import { testAction } from '../../actions/functions/test.js';
3
3
  import config from '../../config.js';
4
- import { STEP_PHASE_GLYPH } from '../../constants.js';
4
+ import { SANITY_FUNCTION_DURABLE, STEP_PHASE_GLYPH } from '../../constants.js';
5
5
  import { resolveDeployedResources } from '../../utils/blueprints/resolve-deployed-resources.js';
6
6
  import { createDurableJsonCollector } from '../../utils/functions/durable-logs.js';
7
7
  import { findFunctionInResources, getFunctionNames } from '../../utils/functions/find.js';
@@ -55,7 +55,7 @@ export async function functionTestCore(options) {
55
55
  apiHost: config.apiUrl,
56
56
  token: config.token || undefined,
57
57
  withUserToken,
58
- resources: resolvedResources,
58
+ resolvedResources,
59
59
  blueprintFileDir: dirname(blueprint.fileInfo.blueprintFilePath),
60
60
  log,
61
61
  };
@@ -88,15 +88,38 @@ export async function functionTestCore(options) {
88
88
  * surfaced after the spinner resolves, including failed ones — a downstream
89
89
  * error is fire-and-forget (it doesn't reject the caller) but is still shown.
90
90
  */
91
- const runNested = async (nestedName, nestedPayload, ctx, depth, callStack) => {
91
+ const runNested = async (nestedName, nestedPayload, ctx, depth, callStack, metadata) => {
92
92
  const order = ++nestedOrder;
93
93
  const nestedResource = findFunctionInResources(blueprint.resources, nestedName);
94
- const result = await runFunction(nestedResource, { payload: nestedPayload, event: 'nested' }, { ...ctx, resources: resolvedResources }, { depth, callStack, onNestedInvoke: runNested });
95
- nestedRuns.push({ order, name: nestedName, logs: result.logs, error: result.error });
96
- return result;
94
+ const isLiveDurable = metadata?.origin === 'step.delegate' && nestedResource.type === SANITY_FUNCTION_DURABLE;
95
+ if (isLiveDurable) {
96
+ log(`↳ BEGIN: invoke('${nestedName}')`);
97
+ }
98
+ try {
99
+ const result = await runFunction(nestedResource, { payload: nestedPayload, event: 'nested' }, { ...ctx, resources: resolvedResources }, { depth, callStack, onNestedInvoke: runNested, onStepEvent });
100
+ if (result.error && isLiveDurable) {
101
+ log(result.error.stack || result.error.message);
102
+ }
103
+ nestedRuns.push({
104
+ order,
105
+ name: nestedName,
106
+ logs: result.logs,
107
+ error: result.error,
108
+ isLiveDurable,
109
+ });
110
+ return result;
111
+ }
112
+ finally {
113
+ if (isLiveDurable) {
114
+ log(`↳ END: invoke('${nestedName}')`);
115
+ }
116
+ }
97
117
  };
98
118
  const logNestedRuns = () => {
99
119
  for (const run of [...nestedRuns].sort((a, b) => a.order - b.order)) {
120
+ // Skip if is Durable so log ordering is retained
121
+ if (run.isLiveDurable)
122
+ continue;
100
123
  log(`↳ BEGIN: invoke('${run.name}')`);
101
124
  // Show any logs received
102
125
  if (run.logs?.trim()) {
@@ -1,14 +1,14 @@
1
1
  import { existsSync, readFileSync, watchFile } from 'node:fs';
2
2
  import * as http from 'node:http';
3
3
  import { dirname } from 'node:path';
4
+ import { resolveStackConfig } from '@sanity/blueprints/resolve';
4
5
  import { default as mime } from 'mime-types';
5
6
  import { WebSocket, WebSocketServer } from 'ws';
6
7
  import { findBlueprintFile, loadAndParseBlueprint } from '../actions/blueprints/blueprint.js';
7
8
  import { readConfigFile } from '../actions/blueprints/config.js';
8
- import { resolveIds } from '../actions/blueprints/resolve.js';
9
9
  import config from '../config.js';
10
10
  import { isRecord } from '../utils/is-record.js';
11
- import { isEventType, isGroqContextOptions, } from '../utils/types.js';
11
+ import { isEventType, } from '../utils/types.js';
12
12
  import { handleInvokeRequest } from './handlers/invoke.js';
13
13
  const app = (host, port, validateResources, executionOptions) => {
14
14
  // Send a JSON envelope to every connected UI client.
@@ -47,7 +47,7 @@ const app = (host, port, validateResources, executionOptions) => {
47
47
  const blueprint = await loadAndParseBlueprint(undefined, { validateResources });
48
48
  const { parsedBlueprint, fileInfo, module: blueprintModule } = blueprint;
49
49
  const blueprintConfig = readConfigFile(fileInfo.blueprintFilePath);
50
- const { projectId, organizationId } = resolveIds({
50
+ const { projectId, organizationId } = resolveStackConfig({
51
51
  module: blueprintModule,
52
52
  config: blueprintConfig,
53
53
  });
@@ -75,7 +75,7 @@ const app = (host, port, validateResources, executionOptions) => {
75
75
  const { data, func: functionName, metadata, } = parseInvokeRequest(Buffer.concat(body), dirname(fileInfo.blueprintFilePath));
76
76
  const { context, event } = data;
77
77
  // replace user token if required
78
- if (isGroqContextOptions(context)) {
78
+ if ('clientOptions' in context) {
79
79
  if (context.clientOptions.token) {
80
80
  context.clientOptions.token = config.token || undefined;
81
81
  }
@@ -191,7 +191,7 @@ body {
191
191
  .border-right {
192
192
  border-right-width: 1px;
193
193
  border-right-style: solid;
194
- border-top-color: var(--card-border-color);
194
+ border-right-color: var(--card-border-color);
195
195
  }
196
196
 
197
197
  .hr-border {
@@ -413,6 +413,13 @@ ol[type='content'] li:hover:not(.selected:not(.selected)) {
413
413
  border-right: none;
414
414
  }
415
415
 
416
+ fieldset {
417
+ & :is(input:not([role='switch']), select, textarea) {
418
+ border: 1px solid light-dark(var(--gray-200), var(--gray-800)) !important;
419
+ border-radius: var(--radius-2);
420
+ }
421
+ }
422
+
416
423
  & :is(input:not([is='switch']), select, textarea) {
417
424
  border: 1px solid light-dark(var(--gray-200), var(--gray-800)) !important;
418
425
  border-radius: var(--radius-2);
@@ -1,8 +1,9 @@
1
1
  import type { BlueprintResource } from '@sanity/blueprints';
2
+ import type { ResolvedResource } from '../types.js';
2
3
  export type GroupedBlueprintResource = {
3
4
  id: string;
4
5
  name: string;
5
6
  type: string;
6
7
  };
7
8
  export type GroupedBlueprintResources = Record<string, GroupedBlueprintResource[]>;
8
- export declare function groupBlueprintResources(blueprintResources: BlueprintResource[]): GroupedBlueprintResources;
9
+ export declare function groupBlueprintResources(blueprintResources: ResolvedResource<BlueprintResource>[]): GroupedBlueprintResources;
@@ -8,7 +8,8 @@ const BLUEPRINT_TYPE_TO_KEY = {
8
8
  };
9
9
  export function groupBlueprintResources(blueprintResources) {
10
10
  const grouped = {};
11
- for (const resource of blueprintResources) {
11
+ for (const resolvedResource of blueprintResources) {
12
+ const resource = resolvedResource.resource;
12
13
  if (resource.type === 'sanity.access.robot')
13
14
  continue;
14
15
  const key = (resource.type?.startsWith('sanity.function.')
@@ -18,9 +19,8 @@ export function groupBlueprintResources(blueprintResources) {
18
19
  continue;
19
20
  if (!grouped[key])
20
21
  grouped[key] = [];
21
- const externalId = resource.externalId;
22
22
  grouped[key].push({
23
- id: externalId || 'not-deployed',
23
+ id: resolvedResource.externalId || 'not-deployed',
24
24
  name: resource.name,
25
25
  type: resource.type,
26
26
  });
@@ -1,5 +1,5 @@
1
1
  import type { BlueprintResource } from '@sanity/blueprints';
2
- import type { AuthParams } from '../types.js';
2
+ import type { AuthParams, ResolvedResource } from '../types.js';
3
3
  interface ResolveOptions {
4
4
  resources: BlueprintResource[];
5
5
  stackId?: string;
@@ -12,5 +12,5 @@ interface ResolveOptions {
12
12
  * resources that aren't yet deployed are preserved without an externalId.
13
13
  * Any failure to fetch the stack falls back to the raw local resources.
14
14
  */
15
- export declare function resolveDeployedResources({ resources, stackId, auth, fetchFn, }: ResolveOptions): Promise<BlueprintResource[]>;
15
+ export declare function resolveDeployedResources({ resources, stackId, auth, fetchFn, }: ResolveOptions): Promise<ResolvedResource<BlueprintResource>[]>;
16
16
  export {};
@@ -7,11 +7,11 @@ import { getStack } from '../../actions/blueprints/stacks.js';
7
7
  */
8
8
  export async function resolveDeployedResources({ resources, stackId, auth, fetchFn, }) {
9
9
  if (!stackId || !auth?.token || !auth.scopeType || !auth.scopeId)
10
- return resources;
10
+ return resources.map((r) => ({ resource: r }));
11
11
  try {
12
12
  const response = await getStack({ stackId, auth, fetchFn });
13
13
  if (!response.ok || !Array.isArray(response.stack?.resources))
14
- return resources;
14
+ return resources.map((r) => ({ resource: r }));
15
15
  const deployedByKey = new Map();
16
16
  for (const deployed of response.stack.resources) {
17
17
  if (deployed.externalId) {
@@ -20,10 +20,10 @@ export async function resolveDeployedResources({ resources, stackId, auth, fetch
20
20
  }
21
21
  return resources.map((resource) => {
22
22
  const externalId = deployedByKey.get(`${resource.type}:${resource.name}`);
23
- return externalId ? { ...resource, externalId } : resource;
23
+ return { resource, externalId };
24
24
  });
25
25
  }
26
26
  catch {
27
- return resources;
27
+ return resources.map((r) => ({ resource: r }));
28
28
  }
29
29
  }
@@ -1,7 +1,7 @@
1
1
  import type { BlueprintApplicationResource, BlueprintInstallationConfigResource, BlueprintStudioResource } from '@sanity/blueprints';
2
2
  import { type BuildStudioEventListener } from '@sanity/cli-build/_internal/build';
3
3
  import type { Output } from '@sanity/cli-core';
4
- import type { AssetPrepResult } from '../types.js';
4
+ import type { AssetPrepResult, ResolvedResource } from '../types.js';
5
5
  export type BundleResource = BlueprintStudioResource | BlueprintApplicationResource | BlueprintInstallationConfigResource;
6
6
  type BuildContext<R extends BundleResource> = {
7
7
  resource: R;
@@ -28,9 +28,9 @@ export declare const BUNDLE_SPECS: {
28
28
  build: typeof buildInstallationConfigBundle;
29
29
  };
30
30
  };
31
- export declare function prepareBundleAsset<R extends BundleResource>({ blueprintFileDir, resource, }: {
31
+ export declare function prepareBundleAsset<R extends BundleResource>({ blueprintFileDir, resolvedResource, }: {
32
32
  blueprintFileDir: string;
33
- resource: R;
33
+ resolvedResource: ResolvedResource<R>;
34
34
  }): Promise<AssetPrepResult>;
35
35
  declare function buildStudioBundle({ resource, appId, workDir, outDir, output, eventListener, }: BuildContext<BlueprintStudioResource>): Promise<BuildResult>;
36
36
  declare function buildApplicationBundle({ resource, appId, workDir, outDir, output, eventListener, }: BuildContext<BlueprintApplicationResource>): Promise<BuildResult>;
@@ -12,10 +12,11 @@ export const BUNDLE_SPECS = {
12
12
  'sanity.application': { requiresAppId: true, build: buildApplicationBundle },
13
13
  'sanity.installation.config': { requiresAppId: false, build: buildInstallationConfigBundle },
14
14
  };
15
- export async function prepareBundleAsset({ blueprintFileDir, resource, }) {
15
+ export async function prepareBundleAsset({ blueprintFileDir, resolvedResource, }) {
16
+ const resource = resolvedResource.resource;
16
17
  // The lookup can't prove it yields the spec matching this R, so it's asserted once here.
17
18
  const spec = BUNDLE_SPECS[resource.type];
18
- const appId = getAppId(resource);
19
+ const appId = getAppId(resolvedResource);
19
20
  if (spec.requiresAppId && !appId) {
20
21
  return {
21
22
  success: false,
@@ -260,12 +261,12 @@ async function getInstalledSanityVersion(workDir) {
260
261
  return undefined;
261
262
  }
262
263
  }
263
- function getAppId(resource) {
264
- if ('externalId' in resource && typeof resource.externalId === 'string' && resource.externalId) {
265
- return resource.externalId;
264
+ function getAppId(resolvedResource) {
265
+ if (resolvedResource.externalId) {
266
+ return resolvedResource.externalId;
266
267
  }
267
- else if (resource.lifecycle?.ownershipAction?.type === 'attach') {
268
- return resource.lifecycle.ownershipAction.id;
268
+ else if (resolvedResource.resource.lifecycle?.ownershipAction?.type === 'attach') {
269
+ return resolvedResource.resource.lifecycle.ownershipAction.id;
269
270
  }
270
271
  return undefined;
271
272
  }
@@ -86,7 +86,7 @@ process.on('message', async (data) => {
86
86
  {
87
87
  emit: (e) => process.send(JSON.stringify({type: 'step-event', event: e})),
88
88
  wait: (ms) => new Promise((resolve) => setTimeout(resolve, noWait ? 0 : ms)),
89
- invoke: invoker.invoke,
89
+ invoke: invoker.delegate,
90
90
  },
91
91
  )
92
92
  } else {
@@ -1,4 +1,5 @@
1
1
  export function createInvoker(): {
2
2
  invoke: (name: string, payload?: Record<string, unknown>) => Promise<unknown>
3
+ delegate: (name: string, payload?: Record<string, unknown>) => Promise<unknown>
3
4
  handleResponse: (msg: unknown) => void
4
5
  }
@@ -10,6 +10,17 @@ export function createInvoker() {
10
10
  const pending = new Map()
11
11
  let invokeSeq = 0
12
12
 
13
+ function sendInvokeRequest(name, payload = {}, metadata = {}) {
14
+ if (typeof name !== 'string' || name.length === 0) {
15
+ return Promise.reject(new Error('context.invoke requires a function name'))
16
+ }
17
+ const id = String(++invokeSeq)
18
+ return new Promise((resolve, reject) => {
19
+ pending.set(id, {resolve, reject})
20
+ process.send(JSON.stringify({type: 'invoke-request', id, name, payload, metadata}))
21
+ })
22
+ }
23
+
13
24
  /**
14
25
  * `context.invoke(name, payload)` — ask the parent to execute another blueprint
15
26
  * function and resolve with its return value. Sent over IPC and correlated by id.
@@ -18,14 +29,20 @@ export function createInvoker() {
18
29
  * @returns {Promise<unknown>}
19
30
  */
20
31
  function invoke(name, payload = {}) {
21
- if (typeof name !== 'string' || name.length === 0) {
22
- return Promise.reject(new Error('context.invoke requires a function name'))
23
- }
24
- const id = String(++invokeSeq)
25
- return new Promise((resolve, reject) => {
26
- pending.set(id, {resolve, reject})
27
- process.send(JSON.stringify({type: 'invoke-request', id, name, payload}))
28
- })
32
+ return sendInvokeRequest(name, payload)
33
+ }
34
+
35
+ /**
36
+ * durable `step.delegate(name, payload)` — ask the parent durable to execute another blueprint function
37
+ * and resolve with its return value. Sent over IPC and correlated by id. Adds an `origin` field to the message so the parent can
38
+ * distinguish between a normal invoke and a durable delegate for logging purposes.
39
+ * @param {string} name
40
+ * @param {Record<string, unknown>} [payload]
41
+ * @returns {Promise<unknown>}
42
+ */
43
+ function delegate(name, payload = {}) {
44
+ const metadata = {origin: 'step.delegate'}
45
+ return sendInvokeRequest(name, payload, metadata)
29
46
  }
30
47
 
31
48
  function handleResponse(msg) {
@@ -43,5 +60,5 @@ export function createInvoker() {
43
60
  }
44
61
  }
45
62
 
46
- return {invoke, handleResponse}
63
+ return {delegate, invoke, handleResponse}
47
64
  }
@@ -5,7 +5,7 @@ import { SANITY_FUNCTION_DURABLE } from '../../constants.js';
5
5
  import { isLocalFunctionResource } from '../types.js';
6
6
  import { findFunctionEntryPoint } from './find-entry-point.js';
7
7
  import { transpileFunction } from './transpile/function.js';
8
- const DURABLE_CONFIG_KEYS = ['event', 'concurrency', 'debounce', 'debounceKey'];
8
+ const DURABLE_CONFIG_KEYS = ['event', 'concurrency', 'debounce'];
9
9
  /**
10
10
  * Extracts the blueprint config from the function source code and mutates it to the resource object.
11
11
  * This is used for durable functions that may not have a config defined in the blueprint.
@@ -47,9 +47,6 @@ export const extractDurableConfig = async (resource, blueprintFileDir) => {
47
47
  if (resource.debounce === undefined && config?.debounce !== undefined) {
48
48
  resource.debounce = config.debounce;
49
49
  }
50
- if (resource.debounceKey === undefined && config?.debounceKey !== undefined) {
51
- resource.debounceKey = config.debounceKey;
52
- }
53
50
  // Validate the resource after merging the config values
54
51
  const errors = validateDurableFunction(resource);
55
52
  if (errors.length > 0) {
@@ -64,10 +64,11 @@ export default async function invoke(resource, payload, context, options) {
64
64
  // Apply the GROQ rule before preparing a build so a filtered-out event skips
65
65
  // the (potentially expensive) transpile entirely.
66
66
  if (isGroqInvocation(resource)) {
67
- if (!isGroqContextOptions(context)) {
67
+ if (!('clientOptions' in context)) {
68
68
  throw new Error('GROQ-based functions require a context with clientOptions');
69
69
  }
70
- const { projectId, dataset } = context.clientOptions;
70
+ const projectId = 'projectId' in context.clientOptions ? context.clientOptions.projectId : undefined;
71
+ const dataset = 'dataset' in context.clientOptions ? context.clientOptions.dataset : undefined;
71
72
  if ('before' in payload) {
72
73
  filteredData = await applyGroqRule(resource, payload, projectId, dataset);
73
74
  const filter = resource.event && 'filter' in resource.event ? resource.event.filter : '';
@@ -156,14 +157,14 @@ export default async function invoke(resource, payload, context, options) {
156
157
  // Resolve a nested `context.invoke(name, payload)` by re-entering the invoke
157
158
  // pipeline via the supplied callback, then hand the result back to the child.
158
159
  async function handleNestedInvoke(msg) {
159
- const { id, name, payload: nestedPayload } = msg;
160
+ const { id, name, payload: nestedPayload, metadata } = msg;
160
161
  pauseTimer();
161
162
  const nestedStart = performance.now();
162
163
  try {
163
164
  if (!onNestedInvoke) {
164
165
  throw new Error('Nested context.invoke is not supported in this context');
165
166
  }
166
- const nested = await onNestedInvoke(name, nestedPayload ?? {}, context, depth + 1, callStack);
167
+ const nested = await onNestedInvoke(name, nestedPayload ?? {}, context, depth + 1, callStack, metadata);
167
168
  nestedTimings[`invoke:${name}`] =
168
169
  (nestedTimings[`invoke:${name}`] ?? 0) + (performance.now() - nestedStart);
169
170
  // Fire-and-forget contract: in production `context.invoke` returns once the
@@ -231,7 +232,12 @@ export default async function invoke(resource, payload, context, options) {
231
232
  }
232
233
  // Nested invoke request — handle without tearing down the child.
233
234
  if (msg?.type === 'invoke-request') {
234
- handleNestedInvoke({ id: msg.id, name: msg.name, payload: msg.payload });
235
+ handleNestedInvoke({
236
+ id: msg.id,
237
+ name: msg.name,
238
+ payload: msg.payload,
239
+ metadata: msg.metadata,
240
+ });
235
241
  return;
236
242
  }
237
243
  // Terminal result message.
@@ -28,7 +28,7 @@ export function assertNestedInvokeAllowed(name, depth, callStack) {
28
28
  * stack extended by this function, and normalize timings.
29
29
  */
30
30
  export async function runFunction(resource, payload, context, options) {
31
- const { forceColor, timeout, depth = 0, callStack = [], onNestedInvoke } = options;
31
+ const { forceColor, timeout, depth = 0, callStack = [], onNestedInvoke, onStepEvent } = options;
32
32
  assertNestedInvokeAllowed(resource.name, depth, callStack);
33
33
  const response = await invoke(resource, payload, context, {
34
34
  forceColor,
@@ -36,6 +36,7 @@ export async function runFunction(resource, payload, context, options) {
36
36
  onNestedInvoke,
37
37
  depth,
38
38
  callStack: [...callStack, resource.name],
39
+ onStepEvent,
39
40
  });
40
41
  return { ...response, timings: response.timings ?? {} };
41
42
  }
@@ -1,6 +1,6 @@
1
1
  import type { BlueprintResource } from '@sanity/blueprints';
2
2
  import type { Logger } from '../../logger.js';
3
- import type { EventType, FunctionGroqResource, FunctionResource, FunctionTestFlags, InvokeContextOptions, InvokePayloadOptions } from '../../types.js';
3
+ import type { EventType, FunctionGroqResource, FunctionResource, FunctionTestFlags, InvokeContextOptions, InvokePayloadOptions, ResolvedResource } from '../../types.js';
4
4
  import buildPayload from '../build-payload.js';
5
5
  import { fetchAsset, fetchDocument } from '../fetch-document.js';
6
6
  export type InvocationPlan = {
@@ -23,7 +23,7 @@ export type InputContext = {
23
23
  apiHost: string;
24
24
  token?: string;
25
25
  withUserToken?: boolean;
26
- resources: BlueprintResource[];
26
+ resolvedResources: ResolvedResource<BlueprintResource>[];
27
27
  blueprintFileDir: string;
28
28
  log: Logger;
29
29
  };
@@ -51,7 +51,7 @@ export const buildContext = (plan, input) => {
51
51
  if (plan.kind === 'plain') {
52
52
  return {
53
53
  clientOptions: { ...withToken },
54
- resources: input.resources,
54
+ resources: input.resolvedResources,
55
55
  blueprintFileDir: input.blueprintFileDir,
56
56
  };
57
57
  }
@@ -69,7 +69,7 @@ export const buildContext = (plan, input) => {
69
69
  '',
70
70
  functionResourceType: 'project',
71
71
  functionResourceId: input.projectId || '',
72
- resources: input.resources,
72
+ resources: input.resolvedResources,
73
73
  blueprintFileDir: input.blueprintFileDir,
74
74
  };
75
75
  };
@@ -40,6 +40,11 @@ export interface BlueprintResourceRecord extends BlueprintResource {
40
40
  export interface DeployedResource extends BlueprintResourceRecord {
41
41
  externalId: string;
42
42
  }
43
+ /** Wrap a resource and its externalId if it has one. */
44
+ export interface ResolvedResource<R extends BlueprintResource> {
45
+ resource: R;
46
+ externalId?: string;
47
+ }
43
48
  export declare function isLocalFunctionResource(r: BlueprintResource): r is FunctionResource;
44
49
  export declare function hasEventSource(r: FunctionResource): r is EventFunctionResource;
45
50
  export declare function isScheduleEvent(e: unknown): e is BlueprintScheduledFunctionResource['event'];
@@ -134,8 +139,14 @@ export type InvokePayloadMetadata = Pick<InvokeGroqPayloadOptions, 'event' | 'be
134
139
  export type EventType = 'create' | 'update' | 'delete';
135
140
  export type NonGroqEventType = 'plain' | 'nested' | 'durable';
136
141
  export declare function isEventType(arg: string): arg is EventType;
142
+ interface BaseContextOptions {
143
+ /** All resources declared by the blueprint, used to populate `context.resources` at invoke time. */
144
+ resources?: ResolvedResource<BlueprintResource>[];
145
+ /** The directory where the blueprint manifest is located. */
146
+ blueprintFileDir: string;
147
+ }
137
148
  /** @internal */
138
- export interface InvokeGroqContextOptions {
149
+ export interface InvokeGroqContextOptions extends BaseContextOptions {
139
150
  clientOptions: {
140
151
  apiVersion?: string;
141
152
  dataset?: string;
@@ -151,20 +162,12 @@ export interface InvokeGroqContextOptions {
151
162
  functionResourceType: string;
152
163
  /** The resource ID of the function container; resource ID that houses the function. */
153
164
  functionResourceId: string;
154
- /** All resources declared by the blueprint, used to populate `context.resources` at invoke time. */
155
- resources?: BlueprintResource[];
156
- /** The directory where the blueprint manifest is located. */
157
- blueprintFileDir: string;
158
165
  }
159
166
  /** @internal */
160
- export interface InvokeScheduleContextOptions {
167
+ export interface InvokeScheduleContextOptions extends BaseContextOptions {
161
168
  clientOptions: {
162
169
  token?: string;
163
170
  };
164
- /** All resources declared by the blueprint, used to populate `context.resources` at invoke time. */
165
- resources?: BlueprintResource[];
166
- /** The directory where the blueprint manifest is located. */
167
- blueprintFileDir: string;
168
171
  }
169
172
  export type InvokeContextOptions = InvokeGroqContextOptions | InvokeScheduleContextOptions;
170
173
  export declare function isGroqContextOptions(context: InvokeContextOptions): context is InvokeGroqContextOptions;
@@ -187,12 +190,15 @@ export interface InvokeExecutionOptions {
187
190
  /** Don't wait the full duration during a wait durable event @internal */
188
191
  noWait?: boolean;
189
192
  }
193
+ export type NestedInvokeMetadata = {
194
+ origin?: 'step.delegate';
195
+ };
190
196
  /**
191
197
  * Re-entrant callback that executes a named function from the blueprint on behalf
192
198
  * of a nested `context.invoke(name, payload)` and returns its invocation result.
193
199
  * @internal
194
200
  */
195
- export type NestedInvoke = (name: string, payload: Record<string, unknown>, context: InvokeContextOptions, depth: number, callStack: string[]) => Promise<InvocationResponse & {
201
+ export type NestedInvoke = (name: string, payload: Record<string, unknown>, context: InvokeContextOptions, depth: number, callStack: string[], metadata?: NestedInvokeMetadata) => Promise<InvocationResponse & {
196
202
  timings: Record<string, number>;
197
203
  }>;
198
204
  /**
@@ -233,6 +239,7 @@ export type ChildToParentMessage = {
233
239
  name: string;
234
240
  payload: Record<string, unknown>;
235
241
  isDurable?: boolean;
242
+ metadata?: NestedInvokeMetadata;
236
243
  } | {
237
244
  type: 'result';
238
245
  json?: JsonInvocationResponse;
@@ -334,7 +341,7 @@ export type AssetPrepResult = {
334
341
  error: string;
335
342
  };
336
343
  export type PrepAssetFn<R extends BlueprintResource> = (options: {
337
- resource: R;
344
+ resolvedResource: ResolvedResource<R>;
338
345
  blueprintFileDir: string;
339
346
  installer?: InstallerType;
340
347
  }) => Promise<AssetPrepResult>;
@@ -351,7 +358,7 @@ export type PreDeployConfig = {
351
358
  prepareResource?: PrepResourceFn<BlueprintResource>;
352
359
  };
353
360
  export type AssetResource<R extends BlueprintResource> = {
354
- resource: R;
361
+ resource: ResolvedResource<R>;
355
362
  prepareAsset: PrepAssetFn<R>;
356
363
  };
357
364
  export type Warning = {
@@ -38,7 +38,7 @@ export function isEventType(arg) {
38
38
  return ['create', 'update', 'delete'].includes(arg);
39
39
  }
40
40
  export function isGroqContextOptions(context) {
41
- return 'clientOptions' in context;
41
+ return 'eventResourceType' in context;
42
42
  }
43
43
  /** @internal */
44
44
  export var BlueprintParserErrorType;
@@ -3148,5 +3148,5 @@
3148
3148
  ]
3149
3149
  }
3150
3150
  },
3151
- "version": "17.15.0"
3151
+ "version": "17.16.0-next.g8d59d6f"
3152
3152
  }
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.15.0",
4
+ "version": "17.16.0-next.g8d59d6f",
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.27.0",
71
+ "@sanity/blueprints": "^0.28.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,51 +0,0 @@
1
- import type { ScopeType } from '../../utils/types.js';
2
- /**
3
- * Source of a resolved config value.
4
- * - flags: from CLI flags like `--project-id`
5
- * - env: from the environment - usually CLI var like `SANITY_ORGANIZATION_ID`
6
- * - module: from the blueprint module - undocumented escape hatch
7
- * - config: from the config file - .sanity/blueprint.config.json most common
8
- * - inferred: legacy `ST-<projectId>` stacks from launch
9
- */
10
- export type ConfigSource = 'flags' | 'env' | 'module' | 'config' | 'inferred';
11
- /** A set of optional IDs from a single source. */
12
- export interface IdValues {
13
- organizationId?: string;
14
- projectId?: string;
15
- stackId?: string;
16
- }
17
- /**
18
- * Sources to resolve IDs from, in descending priority.
19
- * Each key maps to a source; the first non-empty value wins.
20
- *
21
- * - `env` defaults to reading SANITY_* process.env vars
22
- * - `module` and `config` accept any object with `{organizationId?, projectId?, stackId?}`
23
- */
24
- export interface IdSources {
25
- flags?: IdValues | null;
26
- env?: IdValues | null;
27
- module?: IdValues | null;
28
- config?: IdValues | null;
29
- }
30
- export interface ResolvedIds {
31
- organizationId?: string;
32
- projectId?: string;
33
- stackId?: string;
34
- scopeType?: ScopeType;
35
- scopeId?: string;
36
- sources: {
37
- organizationId?: ConfigSource;
38
- projectId?: ConfigSource;
39
- stackId?: ConfigSource;
40
- };
41
- }
42
- /**
43
- * Resolve organization, project, and stack IDs from a prioritized set of sources.
44
- * Precedence: flags > env > module > config.
45
- * Derives scopeType/scopeId from the resolved IDs (project > organization).
46
- *
47
- * Env vars are read from process.env by default (SANITY_ORGANIZATION_ID,
48
- * SANITY_PROJECT_ID, SANITY_BLUEPRINT_STACK_ID). Pass `env` explicitly to
49
- * override, or `null` to skip.
50
- */
51
- export declare function resolveIds(sources?: IdSources): ResolvedIds;