@sanity/runtime-cli 15.1.3 → 15.1.5

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 (65) hide show
  1. package/README.md +26 -20
  2. package/dist/actions/blueprints/blueprint.js +1 -1
  3. package/dist/actions/blueprints/operations.d.ts +30 -0
  4. package/dist/actions/blueprints/operations.js +50 -0
  5. package/dist/actions/blueprints/stacks.d.ts +2 -2
  6. package/dist/actions/blueprints/wait-for-operation.d.ts +30 -0
  7. package/dist/actions/blueprints/wait-for-operation.js +88 -0
  8. package/dist/actions/functions/test.js +1 -1
  9. package/dist/actions/node.js +1 -1
  10. package/dist/actions/sanity/examples.js +1 -1
  11. package/dist/baseCommands.js +1 -0
  12. package/dist/commands/blueprints/deploy.js +3 -1
  13. package/dist/commands/blueprints/destroy.js +3 -1
  14. package/dist/cores/blueprints/deploy.js +43 -78
  15. package/dist/cores/blueprints/destroy.js +41 -57
  16. package/dist/cores/functions/add.js +1 -1
  17. package/dist/cores/functions/build.js +1 -1
  18. package/dist/cores/functions/env/add.js +2 -2
  19. package/dist/cores/functions/env/list.js +2 -2
  20. package/dist/cores/functions/env/remove.js +2 -2
  21. package/dist/cores/functions/logs.js +2 -2
  22. package/dist/cores/functions/test.js +13 -3
  23. package/dist/cores/index.d.ts +2 -0
  24. package/dist/server/handlers/invoke.js +17 -5
  25. package/dist/utils/blueprints/group-resources.d.ts +8 -0
  26. package/dist/utils/blueprints/group-resources.js +29 -0
  27. package/dist/utils/blueprints/resolve-deployed-resources.d.ts +17 -0
  28. package/dist/utils/blueprints/resolve-deployed-resources.js +29 -0
  29. package/dist/utils/{validate/index.d.ts → blueprints/validate-resources.d.ts} +0 -1
  30. package/dist/utils/{validate/index.js → blueprints/validate-resources.js} +0 -1
  31. package/dist/utils/exit-codes.d.ts +15 -0
  32. package/dist/utils/exit-codes.js +15 -0
  33. package/dist/utils/{build-payload.d.ts → functions/build-payload.d.ts} +1 -1
  34. package/dist/utils/{build-payload.js → functions/build-payload.js} +1 -1
  35. package/dist/utils/{child-process-wrapper.js → functions/child-process-wrapper.js} +63 -0
  36. package/dist/utils/{find-function.d.ts → functions/find.d.ts} +1 -1
  37. package/dist/utils/{find-function.js → functions/find.js} +2 -2
  38. package/dist/utils/{invoke-local.d.ts → functions/invoke-local.d.ts} +1 -1
  39. package/dist/utils/{invoke-local.js → functions/invoke-local.js} +10 -8
  40. package/dist/utils/functions/prepare-asset.js +2 -2
  41. package/dist/utils/{transpile/transpile-function.d.ts → functions/transpile/function.d.ts} +1 -1
  42. package/dist/utils/{transpile/transpile-function.js → functions/transpile/function.js} +3 -3
  43. package/dist/utils/types.d.ts +4 -0
  44. package/oclif.manifest.json +3 -3
  45. package/package.json +2 -2
  46. /package/dist/utils/{other → external}/github.d.ts +0 -0
  47. /package/dist/utils/{other → external}/github.js +0 -0
  48. /package/dist/utils/{other → external}/npmjs.d.ts +0 -0
  49. /package/dist/utils/{other → external}/npmjs.js +0 -0
  50. /package/dist/utils/functions/{getFolderSize.d.ts → get-folder-size.d.ts} +0 -0
  51. /package/dist/utils/functions/{getFolderSize.js → get-folder-size.js} +0 -0
  52. /package/dist/utils/functions/{packageJsonUtils.d.ts → package-json.d.ts} +0 -0
  53. /package/dist/utils/functions/{packageJsonUtils.js → package-json.js} +0 -0
  54. /package/dist/utils/functions/{resolve-function-auth.d.ts → resolve-auth.d.ts} +0 -0
  55. /package/dist/utils/functions/{resolve-function-auth.js → resolve-auth.js} +0 -0
  56. /package/dist/utils/{transpile → functions/transpile}/cleanup-source-maps.d.ts +0 -0
  57. /package/dist/utils/{transpile → functions/transpile}/cleanup-source-maps.js +0 -0
  58. /package/dist/utils/{transpile → functions/transpile}/find-up.d.ts +0 -0
  59. /package/dist/utils/{transpile → functions/transpile}/find-up.js +0 -0
  60. /package/dist/utils/{pnpm.d.ts → functions/transpile/pnpm.d.ts} +0 -0
  61. /package/dist/utils/{pnpm.js → functions/transpile/pnpm.js} +0 -0
  62. /package/dist/utils/{transpile → functions/transpile}/verify-handler.d.ts +0 -0
  63. /package/dist/utils/{transpile → functions/transpile}/verify-handler.js +0 -0
  64. /package/dist/utils/{validate/resource.d.ts → functions/validate-name.d.ts} +0 -0
  65. /package/dist/utils/{validate/resource.js → functions/validate-name.js} +0 -0
@@ -1,14 +1,14 @@
1
1
  import { setTimeout as sleep } from 'node:timers/promises';
2
2
  import { confirm } from '@inquirer/prompts';
3
3
  import { patchConfigFile } from '../../actions/blueprints/config.js';
4
- import { setupLogPolling } from '../../actions/blueprints/logs-polling.js';
5
4
  import { destroyStack, getStack, resolveStackIdByNameOrId } from '../../actions/blueprints/stacks.js';
6
- import { createHintCollector } from '../../utils/blueprints/hints.js';
5
+ import { waitForOperation } from '../../actions/blueprints/wait-for-operation.js';
7
6
  import { niceId } from '../../utils/display/presenters.js';
7
+ import { CODE_OPERATION_UNCONFIRMED, EXIT_OPERATION_UNCONFIRMED } from '../../utils/exit-codes.js';
8
8
  import { styleText } from '../../utils/style-text.js';
9
9
  export async function blueprintDestroyCore(options) {
10
10
  const { bin = 'sanity', log, token, blueprint, flags } = options;
11
- const { force = false, 'project-id': flagProjectId, 'organization-id': flagOrganizationId, stack: flagStack, 'no-wait': noWait = false, verbose: _verbose = false, } = flags;
11
+ const { force = false, 'project-id': flagProjectId, 'organization-id': flagOrganizationId, stack: flagStack, 'no-wait': noWait = false, verbose = false, } = flags;
12
12
  // 3-flag combo: destroy without needing a local blueprint config
13
13
  if ((flagProjectId || flagOrganizationId) && flagStack && force) {
14
14
  let scopeType;
@@ -45,6 +45,7 @@ export async function blueprintDestroyCore(options) {
45
45
  auth,
46
46
  log,
47
47
  bin,
48
+ verbose,
48
49
  });
49
50
  }
50
51
  const { scopeType, scopeId, stackId } = blueprint;
@@ -126,6 +127,7 @@ export async function blueprintDestroyCore(options) {
126
127
  auth,
127
128
  log,
128
129
  bin,
130
+ verbose,
129
131
  });
130
132
  }
131
133
  catch (error) {
@@ -147,62 +149,44 @@ function clearLocalStackIdFromConfig(blueprint, destroyedStackId, log) {
147
149
  }
148
150
  }
149
151
  async function waitForDestruction(options) {
150
- const { stackId, stackName, operationId, auth, log, bin } = options;
152
+ const { stackId, stackName, operationId, auth, log, bin, verbose = false } = options;
151
153
  log(styleText('dim', 'Stack destruction progress:'));
152
154
  log('');
153
- const logHints = createHintCollector(bin);
154
- let logStreamCleanup = null;
155
- try {
156
- let lastLogAt = Date.now();
157
- let idleMessageShown = false;
158
- logStreamCleanup = setupLogPolling({
159
- stackId,
160
- operationId,
161
- auth,
162
- log,
163
- onActivity: () => {
164
- lastLogAt = Date.now();
165
- },
166
- onLogEntry: (logEntry) => logHints.inspectLog(logEntry),
167
- });
168
- while (true) {
169
- const { ok, stack: currentStack } = await getStack({ stackId, auth, logger: log });
170
- const operation = currentStack?.recentOperation;
171
- if (!ok || !operation || operation?.status === 'COMPLETED') {
172
- // Operation is also marked destroyed when stack is deleted;
173
- // it's possible that the operation is "gone" or available and "COMPLETED"
174
- if (logStreamCleanup)
175
- logStreamCleanup();
176
- log('');
177
- log(styleText(['bold', 'magenta'], 'Stack destruction completed!'));
178
- return { success: true, json: { stackId, stackName } };
179
- }
180
- if (operation.status === 'FAILED') {
181
- if (logStreamCleanup)
182
- logStreamCleanup();
183
- log('');
184
- return {
185
- success: false,
186
- error: 'Stack destruction failed',
187
- suggestions: [
188
- ...logHints.getSuggestions(),
189
- 'Review the destruction output above for error details.',
190
- `Run \`npx ${bin} blueprints logs --verbose\` for more context.`,
191
- `Run \`npx ${bin} blueprints info\` to view current Stack status.`,
192
- ],
193
- };
194
- }
195
- if (!idleMessageShown && Date.now() - lastLogAt > 60_000) {
196
- log(`No new activity for 60 seconds. The destruction is still running on Sanity servers.`);
197
- log(`You can safely exit and check status later with \`npx ${bin} blueprints info\`.`);
198
- idleMessageShown = true;
199
- }
200
- await sleep(1500);
201
- }
155
+ const outcome = await waitForOperation({
156
+ stackId,
157
+ operationId,
158
+ auth,
159
+ log,
160
+ bin,
161
+ verbose,
162
+ includeDestroyed: true,
163
+ progressNoun: 'destruction',
164
+ });
165
+ if (outcome.type === 'completed') {
166
+ log(styleText(['bold', 'magenta'], 'Stack destruction completed!'));
167
+ return { success: true, json: { stackId, stackName } };
202
168
  }
203
- catch (error) {
204
- if (logStreamCleanup)
205
- logStreamCleanup();
206
- throw error;
169
+ if (outcome.type === 'unconfirmed') {
170
+ return {
171
+ success: false,
172
+ error: `Stack destruction was accepted but completion could not be confirmed${outcome.error ? ` (${outcome.error})` : ''}.`,
173
+ code: CODE_OPERATION_UNCONFIRMED,
174
+ exitCode: EXIT_OPERATION_UNCONFIRMED,
175
+ suggestions: [
176
+ 'The destruction may or may not have finished on Sanity servers.',
177
+ `Run \`npx ${bin} blueprints info\` to view current Stack status.`,
178
+ `Run \`npx ${bin} blueprints logs --watch\` to keep streaming logs.`,
179
+ ],
180
+ };
207
181
  }
182
+ return {
183
+ success: false,
184
+ error: 'Stack destruction failed',
185
+ suggestions: [
186
+ ...outcome.logHints,
187
+ 'Review the destruction output above for error details.',
188
+ `Run \`npx ${bin} blueprints logs --verbose\` for more context.`,
189
+ `Run \`npx ${bin} blueprints info\` to view current Stack status.`,
190
+ ],
191
+ };
208
192
  }
@@ -7,8 +7,8 @@ import { createFunctionResource } from '../../actions/blueprints/resources.js';
7
7
  import { verifyExampleExists, writeExample } from '../../actions/sanity/examples.js';
8
8
  import { EVENT_DOCUMENT_CREATE, EVENT_DOCUMENT_DELETE, EVENT_DOCUMENT_UPDATE, EVENT_MEDIA_LIBRARY_ASSET_CREATE, EVENT_MEDIA_LIBRARY_ASSET_DELETE, EVENT_MEDIA_LIBRARY_ASSET_UPDATE, EVENT_SCHEDULED, EVENT_SYNC_TAG_INVALIDATE, FUNCTION_TYPES, LABEL_DOCUMENT_CREATE, LABEL_DOCUMENT_DELETE, LABEL_DOCUMENT_UPDATE, LABEL_MEDIA_LIBRARY_ASSET_CREATE, LABEL_MEDIA_LIBRARY_ASSET_DELETE, LABEL_MEDIA_LIBRARY_ASSET_UPDATE, LABEL_SCHEDULED, LABEL_SYNC_TAG_INVALIDATE, MAP_EVENT_TO_FUNCTION_TYPE, SANITY_FUNCTION_DOCUMENT, SANITY_FUNCTION_MEDIA_LIBRARY_ASSET, SANITY_FUNCTION_SCHEDULED, SANITY_FUNCTION_SYNC_TAG_INVALIDATE, } from '../../constants.js';
9
9
  import { check, indent, warn } from '../../utils/display/presenters.js';
10
+ import { validateFunctionName } from '../../utils/functions/validate-name.js';
10
11
  import { styleText } from '../../utils/style-text.js';
11
- import { validateFunctionName } from '../../utils/validate/resource.js';
12
12
  export const generateFunctionBlueprintResourceTemplate = (fnName, eventNames) => {
13
13
  const functionType = MAP_EVENT_TO_FUNCTION_TYPE[eventNames[0]];
14
14
  let definer = '';
@@ -1,7 +1,7 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { pathToZip } from '../../actions/blueprints/assets.js';
4
- import { findFunctionInResources } from '../../utils/find-function.js';
4
+ import { findFunctionInResources } from '../../utils/functions/find.js';
5
5
  import { prepareAsset } from '../../utils/functions/prepare-asset.js';
6
6
  import { isLocalFunctionResource } from '../../utils/types.js';
7
7
  export async function functionBuildCore(options) {
@@ -1,6 +1,6 @@
1
1
  import { update } from '../../../actions/functions/env/update.js';
2
- import { findFunctionInStack } from '../../../utils/find-function.js';
3
- import { resolveFunctionAuth } from '../../../utils/functions/resolve-function-auth.js';
2
+ import { findFunctionInStack } from '../../../utils/functions/find.js';
3
+ import { resolveFunctionAuth } from '../../../utils/functions/resolve-auth.js';
4
4
  import { styleText } from '../../../utils/style-text.js';
5
5
  export async function functionEnvAddCore(options) {
6
6
  const { args, log } = options;
@@ -1,6 +1,6 @@
1
1
  import { list } from '../../../actions/functions/env/list.js';
2
- import { findFunctionInStack } from '../../../utils/find-function.js';
3
- import { resolveFunctionAuth } from '../../../utils/functions/resolve-function-auth.js';
2
+ import { findFunctionInStack } from '../../../utils/functions/find.js';
3
+ import { resolveFunctionAuth } from '../../../utils/functions/resolve-auth.js';
4
4
  export async function functionEnvListCore(options) {
5
5
  const { args, log } = options;
6
6
  const spinner = log.ora(`Listing environment variables for "${args.name}"`).start();
@@ -1,6 +1,6 @@
1
1
  import { remove } from '../../../actions/functions/env/remove.js';
2
- import { findFunctionInStack } from '../../../utils/find-function.js';
3
- import { resolveFunctionAuth } from '../../../utils/functions/resolve-function-auth.js';
2
+ import { findFunctionInStack } from '../../../utils/functions/find.js';
3
+ import { resolveFunctionAuth } from '../../../utils/functions/resolve-auth.js';
4
4
  import { styleText } from '../../../utils/style-text.js';
5
5
  export async function functionEnvRemoveCore(options) {
6
6
  const { args, log } = options;
@@ -2,8 +2,8 @@ import { confirm } from '@inquirer/prompts';
2
2
  import { deleteLogs as deleteLogsAction, logs as getLogsAction, streamLogs as streamLogsAction, } from '../../actions/functions/logs.js';
3
3
  import { formatTitle } from '../../utils/display/blueprints-formatting.js';
4
4
  import { niceId } from '../../utils/display/presenters.js';
5
- import { findFunctionInStack, getFunctionNames } from '../../utils/find-function.js';
6
- import { resolveFunctionAuth } from '../../utils/functions/resolve-function-auth.js';
5
+ import { findFunctionInStack, getFunctionNames } from '../../utils/functions/find.js';
6
+ import { resolveFunctionAuth } from '../../utils/functions/resolve-auth.js';
7
7
  import { styleText } from '../../utils/style-text.js';
8
8
  export async function functionLogsCore(options) {
9
9
  const { args, flags, log, error, deployedStack, blueprint, helpText } = options;
@@ -4,9 +4,10 @@ import { cwd } from 'node:process';
4
4
  import { testAction } from '../../actions/functions/test.js';
5
5
  import config from '../../config.js';
6
6
  import { SANITY_FUNCTION_DOCUMENT, SANITY_FUNCTION_MEDIA_LIBRARY_ASSET } from '../../constants.js';
7
- import buildPayload from '../../utils/build-payload.js';
8
- import { findFunctionInResources, getFunctionNames } from '../../utils/find-function.js';
7
+ import { resolveDeployedResources } from '../../utils/blueprints/resolve-deployed-resources.js';
8
+ import buildPayload from '../../utils/functions/build-payload.js';
9
9
  import { fetchAsset, fetchDocument } from '../../utils/functions/fetch-document.js';
10
+ import { findFunctionInResources, getFunctionNames } from '../../utils/functions/find.js';
10
11
  import { parseJsonObject } from '../../utils/parse-json-object.js';
11
12
  import { isEventType, } from '../../utils/types.js';
12
13
  export async function functionTestCore(options) {
@@ -40,6 +41,14 @@ export async function functionTestCore(options) {
40
41
  const resource = findFunctionInResources(blueprint.resources, fnName); // throws if not found
41
42
  const docFunction = resource.type === SANITY_FUNCTION_DOCUMENT;
42
43
  const mediaFunction = resource.type === SANITY_FUNCTION_MEDIA_LIBRARY_ASSET;
44
+ const resolvedResources = await resolveDeployedResources({
45
+ resources: blueprint.resources,
46
+ stackId: blueprint.stackId,
47
+ auth: config.token && blueprint.scopeType && blueprint.scopeId
48
+ ? { token: config.token, scopeType: blueprint.scopeType, scopeId: blueprint.scopeId }
49
+ : undefined,
50
+ logger: log,
51
+ });
43
52
  const contextOptions = docFunction || mediaFunction
44
53
  ? {
45
54
  clientOptions: {
@@ -52,8 +61,9 @@ export async function functionTestCore(options) {
52
61
  eventResourceId: (docFunction ? `${projectId}.${dataset}` : mediaLibraryId) || '',
53
62
  functionResourceType: 'project',
54
63
  functionResourceId: projectId || '',
64
+ resources: resolvedResources,
55
65
  }
56
- : { clientOptions: {} };
66
+ : { clientOptions: {}, resources: resolvedResources };
57
67
  // If the user sets the flag to use the real token set it in our options
58
68
  if (withUserToken) {
59
69
  contextOptions.clientOptions.token = config.token || undefined;
@@ -40,6 +40,8 @@ export type CoreResult = {
40
40
  ref?: string;
41
41
  /** A machine-readable error code. */
42
42
  code?: string;
43
+ /** Overrides the process exit code (defaults to oclif's 2). */
44
+ exitCode?: number;
43
45
  streaming?: never;
44
46
  json?: never;
45
47
  } | {
@@ -1,16 +1,28 @@
1
- import { loadAndParseBlueprint } from '../../actions/blueprints/blueprint.js';
2
- import { findFunctionInResources } from '../../utils/find-function.js';
3
- import invoke from '../../utils/invoke-local.js';
1
+ import { readLocalBlueprint } from '../../actions/blueprints/blueprint.js';
2
+ import config from '../../config.js';
3
+ import { resolveDeployedResources } from '../../utils/blueprints/resolve-deployed-resources.js';
4
+ import { findFunctionInResources } from '../../utils/functions/find.js';
5
+ import invoke from '../../utils/functions/invoke-local.js';
6
+ import { Logger } from '../../utils/logger.js';
7
+ const silentLogger = Logger(() => { });
4
8
  export async function handleInvokeRequest(functionName, event, metadata, context, validateResources, executionOptions) {
5
9
  const start = performance.now();
6
- const { resources } = await loadAndParseBlueprint(undefined, { validateResources });
10
+ const { resources, stackId, scopeType, scopeId } = await readLocalBlueprint(silentLogger, {
11
+ resources: validateResources,
12
+ });
7
13
  const resource = findFunctionInResources(resources, functionName);
8
14
  const readBlueprintTime = performance.now() - start;
15
+ const resolvedResources = await resolveDeployedResources({
16
+ resources,
17
+ stackId,
18
+ auth: config.token && scopeType && scopeId ? { token: config.token, scopeType, scopeId } : undefined,
19
+ logger: silentLogger,
20
+ });
9
21
  const payload = {
10
22
  payload: event,
11
23
  ...metadata,
12
24
  };
13
- const response = await invoke(resource, payload, context, {
25
+ const response = await invoke(resource, payload, { ...context, resources: resolvedResources }, {
14
26
  forceColor: executionOptions?.forceColor ?? false,
15
27
  timeout: executionOptions?.timeout ?? resource.timeout,
16
28
  });
@@ -0,0 +1,8 @@
1
+ import type { BlueprintResource } from '@sanity/blueprints';
2
+ export type GroupedBlueprintResource = {
3
+ id: string;
4
+ name: string;
5
+ type: string;
6
+ };
7
+ export type GroupedBlueprintResources = Record<string, GroupedBlueprintResource[]>;
8
+ export declare function groupBlueprintResources(blueprintResources: BlueprintResource[]): GroupedBlueprintResources;
@@ -0,0 +1,29 @@
1
+ const BLUEPRINT_TYPE_TO_KEY = {
2
+ 'sanity.project.webhook': 'webhook',
3
+ 'sanity.project.cors': 'cors',
4
+ 'sanity.project.origin': 'cors',
5
+ 'sanity.access.role': 'role',
6
+ 'sanity.project.dataset': 'dataset',
7
+ 'sanity.project': 'project',
8
+ };
9
+ export function groupBlueprintResources(blueprintResources) {
10
+ const grouped = {};
11
+ for (const resource of blueprintResources) {
12
+ if (resource.type === 'sanity.access.robot')
13
+ continue;
14
+ const key = (resource.type?.startsWith('sanity.function.')
15
+ ? 'function'
16
+ : BLUEPRINT_TYPE_TO_KEY[resource.type]) ?? resource.type?.split('.').pop();
17
+ if (!key)
18
+ continue;
19
+ if (!grouped[key])
20
+ grouped[key] = [];
21
+ const externalId = resource.externalId;
22
+ grouped[key].push({
23
+ id: externalId || 'not-deployed',
24
+ name: resource.name,
25
+ type: resource.type,
26
+ });
27
+ }
28
+ return grouped;
29
+ }
@@ -0,0 +1,17 @@
1
+ import type { BlueprintResource } from '@sanity/blueprints';
2
+ import type { Logger } from '../logger.js';
3
+ import type { AuthParams } from '../types.js';
4
+ interface ResolveOptions {
5
+ resources: BlueprintResource[];
6
+ stackId?: string;
7
+ auth?: AuthParams;
8
+ logger: Logger;
9
+ }
10
+ /**
11
+ * Returns the local blueprint resources, annotated with externalIds from the
12
+ * deployed stack where a match is found (by name + type). Locally-added
13
+ * resources that aren't yet deployed are preserved without an externalId.
14
+ * Any failure to fetch the stack falls back to the raw local resources.
15
+ */
16
+ export declare function resolveDeployedResources({ resources, stackId, auth, logger, }: ResolveOptions): Promise<BlueprintResource[]>;
17
+ export {};
@@ -0,0 +1,29 @@
1
+ import { getStack } from '../../actions/blueprints/stacks.js';
2
+ /**
3
+ * Returns the local blueprint resources, annotated with externalIds from the
4
+ * deployed stack where a match is found (by name + type). Locally-added
5
+ * resources that aren't yet deployed are preserved without an externalId.
6
+ * Any failure to fetch the stack falls back to the raw local resources.
7
+ */
8
+ export async function resolveDeployedResources({ resources, stackId, auth, logger, }) {
9
+ if (!stackId || !auth?.token || !auth.scopeType || !auth.scopeId)
10
+ return resources;
11
+ try {
12
+ const response = await getStack({ stackId, auth, logger });
13
+ if (!response.ok || !Array.isArray(response.stack?.resources))
14
+ return resources;
15
+ const deployedByKey = new Map();
16
+ for (const deployed of response.stack.resources) {
17
+ if (deployed.externalId) {
18
+ deployedByKey.set(`${deployed.type}:${deployed.name}`, deployed.externalId);
19
+ }
20
+ }
21
+ return resources.map((resource) => {
22
+ const externalId = deployedByKey.get(`${resource.type}:${resource.name}`);
23
+ return externalId ? { ...resource, externalId } : resource;
24
+ });
25
+ }
26
+ catch {
27
+ return resources;
28
+ }
29
+ }
@@ -1,4 +1,3 @@
1
1
  import { type BlueprintResource } from '@sanity/blueprints';
2
2
  import type { BlueprintParserError } from '../types.js';
3
- export * as validate from './resource.js';
4
3
  export declare function validateResources(resources: BlueprintResource[]): BlueprintParserError[];
@@ -1,6 +1,5 @@
1
1
  import { validateCorsOrigin, validateDataset, validateDocumentFunction, validateDocumentWebhook, validateMediaLibraryAssetFunction, validateProject, validateRole, validateScheduledFunction, } from '@sanity/blueprints';
2
2
  import { SANITY_ACCESS_ROLE, SANITY_FUNCTION_DOCUMENT, SANITY_FUNCTION_MEDIA_LIBRARY_ASSET, SANITY_FUNCTION_SCHEDULED, SANITY_PROJECT, SANITY_PROJECT_CORS, SANITY_PROJECT_DATASET, SANITY_PROJECT_WEBHOOK, } from '../../constants.js';
3
- export * as validate from './resource.js';
4
3
  const RESOURCE_VALIDATORS = {
5
4
  [SANITY_ACCESS_ROLE]: {
6
5
  validate: validateRole,
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Process exit codes used by the CLI.
3
+ *
4
+ * oclif exits 0 on success and 2 on error by default. Codes defined here are
5
+ * deliberate, documented signals for scripts/CI to branch on.
6
+ */
7
+ /**
8
+ * A deploy/destroy was accepted but the CLI could not confirm completion
9
+ * (e.g. the operation status endpoint was unavailable). The operation may or
10
+ * may not have finished; rerun `blueprints info` to check. Mirrors EX_TEMPFAIL
11
+ * from sysexits.h ("temporary failure; the user is invited to retry").
12
+ */
13
+ export declare const EXIT_OPERATION_UNCONFIRMED = 75;
14
+ /** Machine-readable error code paired with {@link EXIT_OPERATION_UNCONFIRMED}. */
15
+ export declare const CODE_OPERATION_UNCONFIRMED = "OPERATION_UNCONFIRMED";
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Process exit codes used by the CLI.
3
+ *
4
+ * oclif exits 0 on success and 2 on error by default. Codes defined here are
5
+ * deliberate, documented signals for scripts/CI to branch on.
6
+ */
7
+ /**
8
+ * A deploy/destroy was accepted but the CLI could not confirm completion
9
+ * (e.g. the operation status endpoint was unavailable). The operation may or
10
+ * may not have finished; rerun `blueprints info` to check. Mirrors EX_TEMPFAIL
11
+ * from sysexits.h ("temporary failure; the user is invited to retry").
12
+ */
13
+ export const EXIT_OPERATION_UNCONFIRMED = 75;
14
+ /** Machine-readable error code paired with {@link EXIT_OPERATION_UNCONFIRMED}. */
15
+ export const CODE_OPERATION_UNCONFIRMED = 'OPERATION_UNCONFIRMED';
@@ -1,2 +1,2 @@
1
- import type { BuildPayloadOptions } from './types.js';
1
+ import type { BuildPayloadOptions } from '../types.js';
2
2
  export default function buildPayload(options: BuildPayloadOptions): Record<string, unknown>;
@@ -1,7 +1,7 @@
1
1
  import { readFileSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import { cwd } from 'node:process';
4
- import { parseJsonObject } from './parse-json-object.js';
4
+ import { parseJsonObject } from '../parse-json-object.js';
5
5
  export default function buildPayload(options) {
6
6
  const { data, file } = options;
7
7
  let payload = {};
@@ -117,6 +117,9 @@ process.on('message', async (data) => {
117
117
  )
118
118
  }
119
119
 
120
+ // Replace resources JSON with Resources API
121
+ context.resources = createResourcesApi(context.resources)
122
+
120
123
  json = await eventHandler({
121
124
  context,
122
125
  event,
@@ -149,3 +152,63 @@ process.on('message', async (data) => {
149
152
  process.exit(1)
150
153
  }
151
154
  })
155
+
156
+ /**
157
+ * The returned proxy wraps a function so the API itself is callable.
158
+ * @param {Record<string, ResourceEntry[]>} resources
159
+ * @returns {ResourcesApi}
160
+ */
161
+ export function createResourcesApi(resources) {
162
+ const findByName = (name) => {
163
+ for (const group of Object.values(resources)) {
164
+ const found = group.find((r) => r.name === name)
165
+ if (found) return found
166
+ }
167
+ return undefined
168
+ }
169
+ let allCache
170
+ const all = () => (allCache ??= Object.values(resources).flat())
171
+
172
+ return /** @type {ResourcesApi} */ (
173
+ /** @type {unknown} */ (
174
+ /*
175
+ * The Proxy target is `findByName`, so invoking the API as a function —
176
+ * `context.resources('my-proj')` — bypasses the `get` trap entirely and
177
+ * calls `findByName` directly for a cross-type lookup by name.
178
+ */
179
+ new Proxy(findByName, {
180
+ get: (_target, prop) => {
181
+ // context.resources.all() — flat array of every resource across types
182
+ if (prop === 'all') return all
183
+ // [...context.resources] / for (const r of context.resources) — iterate all
184
+ if (prop === Symbol.iterator) {
185
+ return function* () {
186
+ yield* all()
187
+ }
188
+ }
189
+
190
+ /**
191
+ * Symbol property access (other than iterator above) and `then`
192
+ * lookups return undefined. The `then` guard prevents the API from
193
+ * looking like a thenable, so `await context.resources` resolves to
194
+ * the proxy itself rather than hanging or unwrapping unexpectedly.
195
+ */
196
+ if (typeof prop !== 'string' || prop === 'then') {
197
+ return undefined
198
+ }
199
+
200
+ /**
201
+ * context.resources.<type>(name) — per-type lookup,
202
+ * e.g. context.resources.project('my-proj').
203
+ * Returns a function so the caller supplies the name;
204
+ * unknown types yield a function that always returns undefined.
205
+ */
206
+ return (/** @type {string} */ name) =>
207
+ Object.hasOwn(resources, prop)
208
+ ? resources[prop].find((r) => r.name === name)
209
+ : undefined
210
+ },
211
+ })
212
+ )
213
+ )
214
+ }
@@ -1,5 +1,5 @@
1
1
  import type { BlueprintResource } from '@sanity/blueprints';
2
- import { type DeployedResource, type FunctionResource, type Stack } from './types.js';
2
+ import { type DeployedResource, type FunctionResource, type Stack } from '../types.js';
3
3
  export declare function getFunctionNames(resources: BlueprintResource[] | undefined): string[];
4
4
  export declare function findFunctionInResources(resources: BlueprintResource[], name: string): FunctionResource;
5
5
  /** @deprecated Use findFunctionInResources instead */
@@ -1,5 +1,5 @@
1
- import { SANITY_FUNCTION_PREFIX } from '../constants.js';
2
- import { isLocalFunctionResource, } from './types.js';
1
+ import { SANITY_FUNCTION_PREFIX } from '../../constants.js';
2
+ import { isLocalFunctionResource, } from '../types.js';
3
3
  export function getFunctionNames(resources) {
4
4
  return (resources
5
5
  ?.filter((r) => r.type.startsWith(SANITY_FUNCTION_PREFIX))
@@ -1,4 +1,4 @@
1
- import { type FunctionGroqResource, type FunctionResource, type InvocationResponse, type InvokeContextOptions, type InvokeExecutionOptions, type InvokeGroqPayloadOptions, type InvokePayloadOptions } from './types.js';
1
+ import { type FunctionGroqResource, type FunctionResource, type InvocationResponse, type InvokeContextOptions, type InvokeExecutionOptions, type InvokeGroqPayloadOptions, type InvokePayloadOptions } from '../types.js';
2
2
  export declare function sanitizeLogs(logs: string): string;
3
3
  export declare function applyGroqRule(resource: FunctionGroqResource, payload: InvokeGroqPayloadOptions, projectId: string | undefined, dataset: string | undefined): Promise<any>;
4
4
  export default function invoke(resource: FunctionResource, payload: InvokePayloadOptions, context: InvokeContextOptions, options: InvokeExecutionOptions): Promise<InvocationResponse>;
@@ -4,14 +4,15 @@ import { cwd } from 'node:process';
4
4
  import { setTimeout } from 'node:timers';
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import * as groq from 'groq-js';
7
- import config from '../config.js';
8
- import { findFunctionEntryPoint } from './functions/find-entry-point.js';
9
- import { cleanupTempPackageJson, createTempPackageJson, doesPackageJsonExists, } from './functions/packageJsonUtils.js';
10
- import { resolveResourceDependencies } from './functions/resolve-dependencies.js';
11
- import { shouldAutoResolveDependencies } from './functions/should-auto-resolve-deps.js';
12
- import { shouldTranspileFunction } from './functions/should-transpile.js';
13
- import { transpileFunction } from './transpile/transpile-function.js';
14
- import { isGroqContextOptions, } from './types.js';
7
+ import config from '../../config.js';
8
+ import { groupBlueprintResources } from '../blueprints/group-resources.js';
9
+ import { isGroqContextOptions, } from '../types.js';
10
+ import { findFunctionEntryPoint } from './find-entry-point.js';
11
+ import { cleanupTempPackageJson, createTempPackageJson, doesPackageJsonExists, } from './package-json.js';
12
+ import { resolveResourceDependencies } from './resolve-dependencies.js';
13
+ import { shouldAutoResolveDependencies } from './should-auto-resolve-deps.js';
14
+ import { shouldTranspileFunction } from './should-transpile.js';
15
+ import { transpileFunction } from './transpile/function.js';
15
16
  function getChildProcessWrapperPath() {
16
17
  return fileURLToPath(new URL('./child-process-wrapper.js', import.meta.url));
17
18
  }
@@ -157,6 +158,7 @@ export default async function invoke(resource, payload, context, options) {
157
158
  context: {
158
159
  ...context,
159
160
  local: true,
161
+ resources: groupBlueprintResources(context.resources ?? []),
160
162
  ...(isGroqContextOptions(context) && {
161
163
  clientOptions: {
162
164
  ...context.clientOptions,
@@ -3,12 +3,12 @@ import { rm, stat } from 'node:fs/promises';
3
3
  import path from 'node:path';
4
4
  import { cwd } from 'node:process';
5
5
  import { MAX_ASSET_SIZE } from '../../constants.js';
6
- import { transpileFunction } from '../transpile/transpile-function.js';
7
6
  import { detectNativeModules, errorMessage } from './detect-native-modules.js';
8
- import { getFolderSize } from './getFolderSize.js';
7
+ import { getFolderSize } from './get-folder-size.js';
9
8
  import { resolveResourceDependencies } from './resolve-dependencies.js';
10
9
  import { shouldAutoResolveDependencies } from './should-auto-resolve-deps.js';
11
10
  import { shouldTranspileFunction } from './should-transpile.js';
11
+ import { transpileFunction } from './transpile/function.js';
12
12
  export async function prepareAsset({ resource, }, { installer } = {}) {
13
13
  if (!resource.src)
14
14
  throw new Error('Resource src is required');
@@ -1,4 +1,4 @@
1
- import type { FunctionResource, InstallerType } from '../types.js';
1
+ import type { FunctionResource, InstallerType } from '../../types.js';
2
2
  export declare const resolveViteRoot: (entry: string) => string;
3
3
  export declare function transpileFunction(resource: FunctionResource, { installer }?: {
4
4
  installer?: InstallerType;
@@ -5,10 +5,10 @@ import { cwd } from 'node:process';
5
5
  import * as find from 'empathic/find';
6
6
  import { build as viteBuild } from 'vite';
7
7
  import tsConfigPaths from 'vite-tsconfig-paths';
8
- import { detectNativeModulesPlugin } from '../functions/detect-native-modules.js';
9
- import { findFunctionEntryPoint } from '../functions/find-entry-point.js';
10
- import { createPnpmRequire } from '../pnpm.js';
8
+ import { detectNativeModulesPlugin } from '../detect-native-modules.js';
9
+ import { findFunctionEntryPoint } from '../find-entry-point.js';
11
10
  import { cleanupSourceMaps } from './cleanup-source-maps.js';
11
+ import { createPnpmRequire } from './pnpm.js';
12
12
  import { verifyHandler } from './verify-handler.js';
13
13
  export const resolveViteRoot = (entry) => {
14
14
  const nodeModulesDir = find.dir('node_modules', { cwd: entry });
@@ -123,12 +123,16 @@ export interface InvokeGroqContextOptions {
123
123
  functionResourceType: string;
124
124
  /** The resource ID of the function container; resource ID that houses the function. */
125
125
  functionResourceId: string;
126
+ /** All resources declared by the blueprint, used to populate `context.resources` at invoke time. */
127
+ resources?: BlueprintResource[];
126
128
  }
127
129
  /** @internal */
128
130
  export interface InvokeScheduleContextOptions {
129
131
  clientOptions: {
130
132
  token?: string;
131
133
  };
134
+ /** All resources declared by the blueprint, used to populate `context.resources` at invoke time. */
135
+ resources?: BlueprintResource[];
132
136
  }
133
137
  export type InvokeContextOptions = InvokeGroqContextOptions | InvokeScheduleContextOptions;
134
138
  export declare function isGroqContextOptions(context: InvokeContextOptions): context is InvokeGroqContextOptions;