@sanity/runtime-cli 12.1.0 → 12.3.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.
Files changed (56) hide show
  1. package/README.md +20 -19
  2. package/dist/actions/blueprints/assets.d.ts +3 -4
  3. package/dist/actions/blueprints/assets.js +118 -2
  4. package/dist/actions/blueprints/blueprint.d.ts +3 -2
  5. package/dist/actions/blueprints/stacks.d.ts +8 -4
  6. package/dist/actions/blueprints/stacks.js +6 -32
  7. package/dist/commands/blueprints/doctor.d.ts +1 -0
  8. package/dist/commands/blueprints/doctor.js +4 -0
  9. package/dist/commands/functions/dev.d.ts +2 -2
  10. package/dist/commands/functions/dev.js +3 -2
  11. package/dist/constants.d.ts +7 -0
  12. package/dist/constants.js +7 -0
  13. package/dist/cores/blueprints/deploy.js +29 -12
  14. package/dist/cores/blueprints/doctor.d.ts +1 -0
  15. package/dist/cores/blueprints/doctor.js +36 -5
  16. package/dist/cores/blueprints/info.js +2 -2
  17. package/dist/cores/functions/env/add.js +2 -2
  18. package/dist/cores/functions/env/list.js +2 -2
  19. package/dist/cores/functions/env/remove.js +2 -2
  20. package/dist/cores/functions/logs.js +2 -2
  21. package/dist/cores/functions/test.js +31 -23
  22. package/dist/server/app.js +32 -21
  23. package/dist/server/handlers/invoke.d.ts +2 -2
  24. package/dist/server/handlers/invoke.js +2 -2
  25. package/dist/server/static/components/api-base.js +3 -0
  26. package/dist/server/static/components/app.css +120 -95
  27. package/dist/server/static/components/clear-button.js +1 -1
  28. package/dist/server/static/components/console-panel.js +6 -6
  29. package/dist/server/static/components/fetch-button.js +1 -1
  30. package/dist/server/static/components/filter-api-version.js +3 -3
  31. package/dist/server/static/components/filter-document-id.js +5 -5
  32. package/dist/server/static/components/filter-with-token.js +4 -4
  33. package/dist/server/static/components/filters.js +2 -2
  34. package/dist/server/static/components/function-list.js +14 -5
  35. package/dist/server/static/components/help-button.js +4 -1
  36. package/dist/server/static/components/payload-panel.js +9 -9
  37. package/dist/server/static/components/response-panel.js +8 -8
  38. package/dist/server/static/components/rule-panel.js +4 -4
  39. package/dist/server/static/components/run-panel.js +4 -4
  40. package/dist/server/static/components/select-dropdown.js +5 -25
  41. package/dist/server/static/index.html +9 -9
  42. package/dist/server/static/vendor/m-.css +1 -0
  43. package/dist/server/static/vendor/m-.woff2 +0 -0
  44. package/dist/utils/display/blueprints-formatting.d.ts +3 -2
  45. package/dist/utils/display/blueprints-formatting.js +102 -50
  46. package/dist/utils/display/resources-formatting.d.ts +6 -2
  47. package/dist/utils/display/resources-formatting.js +71 -17
  48. package/dist/utils/find-function.d.ts +2 -2
  49. package/dist/utils/find-function.js +9 -2
  50. package/dist/utils/invoke-local.d.ts +2 -2
  51. package/dist/utils/invoke-local.js +27 -16
  52. package/dist/utils/types.d.ts +66 -24
  53. package/dist/utils/types.js +25 -2
  54. package/dist/utils/validate/resource.js +144 -23
  55. package/oclif.manifest.json +14 -1
  56. package/package.json +2 -2
@@ -5,6 +5,7 @@ import { getStack } from '../../actions/blueprints/stacks.js';
5
5
  import config from '../../config.js';
6
6
  import { capitalize, check, filePathRelativeToCwd, indent, niceId, severe, unsure, } from '../../utils/display/presenters.js';
7
7
  import { validTokenOrErrorMessage } from '../../utils/validated-token.js';
8
+ import { blueprintConfigCore } from './config.js';
8
9
  const diagLookup = {
9
10
  online: 'Online',
10
11
  tokenPresent: 'Auth token present',
@@ -17,12 +18,13 @@ const diagLookup = {
17
18
  userHasAccess: 'User has access to deployed "Stack"',
18
19
  };
19
20
  export async function blueprintDoctorCore(options) {
20
- const { log, token, flags: { verbose: v, path: p }, } = options;
21
+ const { bin, log, token, flags: { verbose: v, path: p, fix }, } = options;
21
22
  const yikes = (s) => {
22
23
  log(chalk.bgRedBright.whiteBright.bold(` ${s} `));
23
24
  };
24
25
  const here = cwd();
25
26
  const path = p || here;
27
+ let tokenOrError;
26
28
  if (v)
27
29
  log(`Checking ${filePathRelativeToCwd(path)}`);
28
30
  // 3 states: null == unknown, true == good, false == bad
@@ -49,7 +51,7 @@ export async function blueprintDoctorCore(options) {
49
51
  // TOKEN
50
52
  if (token) {
51
53
  diagnostics.tokenPresent = true;
52
- const tokenOrError = await validTokenOrErrorMessage(token);
54
+ tokenOrError = await validTokenOrErrorMessage(token);
53
55
  if (tokenOrError.ok) {
54
56
  diagnostics.tokenValid = true;
55
57
  }
@@ -91,7 +93,8 @@ export async function blueprintDoctorCore(options) {
91
93
  diagnostics.blueprintValid = false;
92
94
  }
93
95
  if (blueprint) {
94
- const { configPath, scopeType, scopeId, stackId, projectId } = blueprint;
96
+ const { blueprintConfig, scopeType, scopeId, stackId, projectId } = blueprint;
97
+ const configPath = blueprintConfig?.configPath;
95
98
  // CONFIG file
96
99
  if (configPath) {
97
100
  if (v)
@@ -169,11 +172,39 @@ export async function blueprintDoctorCore(options) {
169
172
  log(severe(`${key} is ${value}`));
170
173
  }
171
174
  }
175
+ const errorMessage = 'One or more checks failed';
172
176
  if (allGood) {
173
177
  log(chalk.bold.green('All checks passed'));
178
+ if (fix)
179
+ log(chalk.bold.yellow('Nothing to fix; --fix flag is ignored'));
174
180
  return { success: true, data: { diagnostics } };
175
181
  }
176
- else {
177
- return { success: false, error: 'One or more checks failed', data: { diagnostics } };
182
+ else if (fix) {
183
+ if (p) {
184
+ return { success: false, error: `${errorMessage}. --fix cannot be used with --path` };
185
+ }
186
+ if (!tokenOrError) {
187
+ return { success: false, error: `${errorMessage}. Unable to fix: Missing authentication token` };
188
+ }
189
+ if (tokenOrError?.ok === false) {
190
+ return {
191
+ success: false,
192
+ error: `${errorMessage}. Unable to fix: ${tokenOrError.error.message}`,
193
+ };
194
+ }
195
+ if (!blueprint) {
196
+ return {
197
+ success: false,
198
+ error: `${errorMessage}. Unable to fix: Blueprint is missing or invalid`,
199
+ };
200
+ }
201
+ return blueprintConfigCore({
202
+ bin,
203
+ log,
204
+ token: tokenOrError.value,
205
+ blueprint,
206
+ flags: { edit: true, verbose: v },
207
+ });
178
208
  }
209
+ return { success: false, error: errorMessage, data: { diagnostics } };
179
210
  }
@@ -1,5 +1,5 @@
1
1
  import { getStack } from '../../actions/blueprints/stacks.js';
2
- import { formatResourceTree, formatStackInfo } from '../../utils/display/blueprints-formatting.js';
2
+ import { formatDeployedResourceTree, formatStackInfo, } from '../../utils/display/blueprints-formatting.js';
3
3
  import { niceId } from '../../utils/display/presenters.js';
4
4
  export async function blueprintInfoCore(options) {
5
5
  const { log, auth, stackId, flags, deployedStack } = options;
@@ -20,7 +20,7 @@ export async function blueprintInfoCore(options) {
20
20
  }
21
21
  log(formatStackInfo(stack, true));
22
22
  if (stack.resources)
23
- log(formatResourceTree(stack.resources));
23
+ log(formatDeployedResourceTree(stack.resources));
24
24
  return { success: true };
25
25
  }
26
26
  catch (error) {
@@ -1,11 +1,11 @@
1
1
  import chalk from 'chalk';
2
2
  import ora from 'ora';
3
3
  import { update } from '../../../actions/functions/env/update.js';
4
- import { findFunctionByName } from '../../../utils/find-function.js';
4
+ import { findFunctionInStack } from '../../../utils/find-function.js';
5
5
  export async function functionEnvAddCore(options) {
6
6
  const args = options.args;
7
7
  const spinner = ora(`Updating "${args.key}" environment variable in "${args.name}"`).start();
8
- const { externalId } = findFunctionByName(options.deployedStack, args.name);
8
+ const { externalId } = findFunctionInStack(options.deployedStack, args.name);
9
9
  const result = await update(externalId, args.key, args.value, options.auth);
10
10
  if (!result.ok) {
11
11
  spinner.fail(`${chalk.red('Failed')} to update ${args.key}`);
@@ -1,10 +1,10 @@
1
1
  import ora from 'ora';
2
2
  import { list } from '../../../actions/functions/env/list.js';
3
- import { findFunctionByName } from '../../../utils/find-function.js';
3
+ import { findFunctionInStack } from '../../../utils/find-function.js';
4
4
  export async function functionEnvListCore(options) {
5
5
  const args = options.args;
6
6
  const spinner = ora(`Listing environment variables for "${args.name}"`).start();
7
- const { externalId } = findFunctionByName(options.deployedStack, args.name);
7
+ const { externalId } = findFunctionInStack(options.deployedStack, args.name);
8
8
  const result = await list(externalId, options.auth);
9
9
  if (!result.ok) {
10
10
  spinner.stop();
@@ -1,11 +1,11 @@
1
1
  import chalk from 'chalk';
2
2
  import ora from 'ora';
3
3
  import { remove } from '../../../actions/functions/env/remove.js';
4
- import { findFunctionByName } from '../../../utils/find-function.js';
4
+ import { findFunctionInStack } from '../../../utils/find-function.js';
5
5
  export async function functionEnvRemoveCore(options) {
6
6
  const args = options.args;
7
7
  const spinner = ora(`Removing "${args.key}" environment variable in "${args.name}"`).start();
8
- const { externalId } = findFunctionByName(options.deployedStack, args.name);
8
+ const { externalId } = findFunctionInStack(options.deployedStack, args.name);
9
9
  const result = await remove(externalId, args.key, options.auth);
10
10
  if (!result.ok) {
11
11
  spinner.fail(`${chalk.red('Failed')} to remove ${args.key}`);
@@ -4,12 +4,12 @@ import ora from 'ora';
4
4
  import { deleteLogs as deleteLogsAction, logs as getLogsAction, streamLogs as streamLogsAction, } from '../../actions/functions/logs.js';
5
5
  import { formatTitle } from '../../utils/display/blueprints-formatting.js';
6
6
  import { niceId } from '../../utils/display/presenters.js';
7
- import { findFunctionByName } from '../../utils/find-function.js';
7
+ import { findFunctionInStack } from '../../utils/find-function.js';
8
8
  export async function functionLogsCore(options) {
9
9
  const { args, flags, log, auth, deployedStack } = options;
10
10
  const { name } = args;
11
11
  const { delete: shouldDelete, watch: shouldWatch, force, limit, json, utc } = flags;
12
- const { externalId } = findFunctionByName(deployedStack, name); // throws if not found
12
+ const { externalId } = findFunctionInStack(deployedStack, name); // throws if not found
13
13
  if (shouldDelete)
14
14
  return deleteLogs({ name, externalId, auth, force, log });
15
15
  if (shouldWatch)
@@ -4,11 +4,12 @@ import { cwd } from 'node:process';
4
4
  import ora from 'ora';
5
5
  import { testAction } from '../../actions/functions/test.js';
6
6
  import config from '../../config.js';
7
+ import { SANITY_FUNCTION_DOCUMENT, SANITY_FUNCTION_MEDIA_LIBRARY_ASSET } from '../../constants.js';
7
8
  import buildPayload from '../../utils/build-payload.js';
8
- import { findFunctionByName } from '../../utils/find-function.js';
9
+ import { findFunctionInBlueprint } from '../../utils/find-function.js';
9
10
  import { fetchAsset, fetchDocument } from '../../utils/functions/fetch-document.js';
10
11
  import { parseJsonObject } from '../../utils/parse-json-object.js';
11
- import { isEventType, } from '../../utils/types.js';
12
+ import { isEventType, isGroqContextOptions, } from '../../utils/types.js';
12
13
  export async function functionTestCore(options) {
13
14
  const { blueprint, log, args, flags } = options;
14
15
  const { name: fnName } = args;
@@ -29,22 +30,25 @@ export async function functionTestCore(options) {
29
30
  };
30
31
  }
31
32
  try {
32
- const resource = findFunctionByName(parsedBlueprint, fnName); // throws if not found
33
- const docFunction = resource.type === 'sanity.function.document';
34
- const contextOptions = {
35
- clientOptions: {
36
- apiVersion: api,
37
- dataset,
38
- projectId,
39
- organizationId,
40
- },
41
- eventResourceType: docFunction ? 'dataset' : 'media-library',
42
- eventResourceId: (docFunction ? `${projectId}.${dataset}` : mediaLibraryId) || '',
43
- functionResourceType: 'project',
44
- functionResourceId: (docFunction ? projectId : organizationId) || '',
45
- };
33
+ const resource = findFunctionInBlueprint(parsedBlueprint, fnName); // throws if not found
34
+ const docFunction = resource.type === SANITY_FUNCTION_DOCUMENT;
35
+ const mediaFunction = resource.type === SANITY_FUNCTION_MEDIA_LIBRARY_ASSET;
36
+ const contextOptions = docFunction || mediaFunction
37
+ ? {
38
+ clientOptions: {
39
+ apiVersion: api,
40
+ dataset,
41
+ projectId,
42
+ organizationId,
43
+ },
44
+ eventResourceType: docFunction ? 'dataset' : 'media-library',
45
+ eventResourceId: (docFunction ? `${projectId}.${dataset}` : mediaLibraryId) || '',
46
+ functionResourceType: 'project',
47
+ functionResourceId: projectId || '',
48
+ }
49
+ : {};
46
50
  // If the user sets the flag to use the real token set it in our options
47
- if (withUserToken) {
51
+ if (withUserToken && isGroqContextOptions(contextOptions)) {
48
52
  contextOptions.clientOptions.token = config.token || undefined;
49
53
  }
50
54
  let before = null;
@@ -115,12 +119,16 @@ export async function functionTestCore(options) {
115
119
  payload = buildPayload({ data, file });
116
120
  }
117
121
  }
118
- const invokeOptions = {
119
- event: eventType,
120
- payload,
121
- before,
122
- after,
123
- };
122
+ const invokeOptions = docFunction || mediaFunction
123
+ ? {
124
+ event: eventType,
125
+ payload,
126
+ before,
127
+ after,
128
+ }
129
+ : {
130
+ event: 'schedule',
131
+ };
124
132
  const spinner = ora('Executing function...').start();
125
133
  const { json, logs, error } = await testAction(resource, invokeOptions, contextOptions, {
126
134
  timeout: timeout ? timeout : resource.timeout,
@@ -5,7 +5,7 @@ import { WebSocketServer } from 'ws';
5
5
  import { readLocalBlueprint } from '../actions/blueprints/blueprint.js';
6
6
  import config from '../config.js';
7
7
  import { isRecord } from '../utils/is-record.js';
8
- import { isEventType, } from '../utils/types.js';
8
+ import { isEventType, isGroqContextOptions, } from '../utils/types.js';
9
9
  import { handleInvokeRequest } from './handlers/invoke.js';
10
10
  const app = (host, port, executionOptions) => {
11
11
  const requestListener = async (req, res) => {
@@ -34,11 +34,13 @@ const app = (host, port, executionOptions) => {
34
34
  const { data, func: functionName, metadata } = parseInvokeRequest(Buffer.concat(body));
35
35
  const { context, event } = data;
36
36
  // replace user token if required
37
- if (context.clientOptions.token) {
38
- context.clientOptions.token = config.token || undefined;
39
- }
40
- else {
41
- delete context.clientOptions.token;
37
+ if (isGroqContextOptions(context)) {
38
+ if (context.clientOptions.token) {
39
+ context.clientOptions.token = config.token || undefined;
40
+ }
41
+ else {
42
+ delete context.clientOptions.token;
43
+ }
42
44
  }
43
45
  const result = await handleInvokeRequest(functionName, event, metadata, context, executionOptions);
44
46
  // Add Server-Timing header
@@ -324,21 +326,30 @@ function parseInvokeRequest(body) {
324
326
  throw new Error('Request body is not valid, `metadata.event` field is missing');
325
327
  }
326
328
  const metadataEvent = metadata.event;
327
- if (typeof metadataEvent !== 'string' || !isEventType(metadataEvent)) {
328
- throw new Error('Request body is not valid, `metadata.event` field is not one of `create`, `update`, or `delete`');
329
- }
330
- if (!('before' in metadata)) {
331
- throw new Error('Request body is not valid, `metadata.before` field is missing');
332
- }
333
- if (!('after' in metadata)) {
334
- throw new Error('Request body is not valid, `metadata.after` field is missing');
335
- }
336
- const { before, after } = metadata;
337
- if (!isRecord(before) && before !== null) {
338
- throw new Error('Request body is not valid, `metadata.before` field is not an object');
329
+ if (typeof metadataEvent !== 'string' ||
330
+ (!isEventType(metadataEvent) && metadataEvent !== 'schedule')) {
331
+ throw new Error('Request body is not valid, `metadata.event` field is not one of `create`, `update`, `delete`, or `schedule`');
339
332
  }
340
- if (!isRecord(after) && after !== null) {
341
- throw new Error('Request body is not valid, `metadata.after` field is not an object');
333
+ let before = null;
334
+ let after = null;
335
+ // Only GROQ-based events (create, update, delete) have before and after fields
336
+ if (metadataEvent !== 'schedule') {
337
+ if (!('before' in metadata)) {
338
+ throw new Error('Request body is not valid, `metadata.before` field is missing');
339
+ }
340
+ if (!('after' in metadata)) {
341
+ throw new Error('Request body is not valid, `metadata.after` field is missing');
342
+ }
343
+ const beforeValue = metadata.before;
344
+ const afterValue = metadata.after;
345
+ if (!isRecord(beforeValue) && beforeValue !== null) {
346
+ throw new Error('Request body is not valid, `metadata.before` field is not an object');
347
+ }
348
+ if (!isRecord(afterValue) && afterValue !== null) {
349
+ throw new Error('Request body is not valid, `metadata.after` field is not an object');
350
+ }
351
+ before = beforeValue;
352
+ after = afterValue;
342
353
  }
343
354
  const clientOptions = {
344
355
  ...context.clientOptions,
@@ -361,7 +372,7 @@ function parseInvokeRequest(body) {
361
372
  },
362
373
  event,
363
374
  },
364
- metadata: { event: metadataEvent, before, after },
375
+ metadata: metadataEvent === 'schedule' ? { event: metadataEvent } : { event: metadataEvent, before, after },
365
376
  };
366
377
  }
367
378
  export { app, buildApiUrl, parseDocumentUrl };
@@ -1,4 +1,4 @@
1
- import type { InvocationResponse, InvokeContextOptions, InvokeExecutionOptions, InvokePayloadOptions } from '../../utils/types.js';
2
- export declare function handleInvokeRequest(functionName: string, event: Record<string, unknown>, metadata: Pick<InvokePayloadOptions, 'event' | 'before' | 'after'>, context: InvokeContextOptions, executionOptions?: Partial<InvokeExecutionOptions>): Promise<InvocationResponse & {
1
+ import type { InvocationResponse, InvokeContextOptions, InvokeExecutionOptions, InvokePayloadMetadata } from '../../utils/types.js';
2
+ export declare function handleInvokeRequest(functionName: string, event: Record<string, unknown>, metadata: InvokePayloadMetadata, context: InvokeContextOptions, executionOptions?: Partial<InvokeExecutionOptions>): Promise<InvocationResponse & {
3
3
  timings: Record<string, number>;
4
4
  }>;
@@ -1,10 +1,10 @@
1
1
  import { readLocalBlueprint } from '../../actions/blueprints/blueprint.js';
2
- import { findFunctionByName } from '../../utils/find-function.js';
2
+ import { findFunctionInBlueprint } from '../../utils/find-function.js';
3
3
  import invoke from '../../utils/invoke-local.js';
4
4
  export async function handleInvokeRequest(functionName, event, metadata, context, executionOptions) {
5
5
  const start = performance.now();
6
6
  const { parsedBlueprint } = await readLocalBlueprint();
7
- const resource = findFunctionByName(parsedBlueprint, functionName);
7
+ const resource = findFunctionInBlueprint(parsedBlueprint, functionName);
8
8
  const readBlueprintTime = performance.now() - start;
9
9
  const payload = {
10
10
  payload: event,
@@ -7,5 +7,8 @@ export class ApiBaseElement extends HTMLElement {
7
7
  constructor() {
8
8
  super()
9
9
  this.api = api
10
+ this.SANITY_FUNCTION_DOCUMENT = 'sanity.function.document'
11
+ this.SANITY_FUNCTION_MEDIA_LIBRARY_ASSET = 'sanity.function.media-library.asset'
12
+ this.SANITY_FUNCTION_SCHEDULE = 'sanity.function.cron'
10
13
  }
11
14
  }