@sanity/runtime-cli 14.7.1 → 14.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.
Files changed (81) hide show
  1. package/README.md +140 -97
  2. package/dist/actions/blueprints/assets.d.ts +4 -3
  3. package/dist/actions/blueprints/assets.js +69 -101
  4. package/dist/actions/blueprints/blueprint.d.ts +2 -2
  5. package/dist/actions/blueprints/blueprint.js +1 -8
  6. package/dist/actions/blueprints/config.d.ts +2 -2
  7. package/dist/actions/blueprints/logs-streaming.d.ts +2 -2
  8. package/dist/actions/blueprints/logs.d.ts +2 -4
  9. package/dist/actions/blueprints/resources.d.ts +1 -1
  10. package/dist/actions/blueprints/stacks.d.ts +15 -25
  11. package/dist/actions/functions/dev.d.ts +1 -1
  12. package/dist/actions/functions/env/list.d.ts +1 -1
  13. package/dist/actions/functions/env/remove.d.ts +1 -1
  14. package/dist/actions/functions/env/update.d.ts +1 -1
  15. package/dist/actions/functions/logs.d.ts +3 -3
  16. package/dist/actions/node.d.ts +1 -1
  17. package/dist/actions/sanity/examples.d.ts +2 -2
  18. package/dist/actions/sanity/projects.d.ts +7 -13
  19. package/dist/baseCommands.js +2 -4
  20. package/dist/commands/blueprints/config.d.ts +1 -1
  21. package/dist/commands/blueprints/config.js +2 -1
  22. package/dist/commands/blueprints/deploy.d.ts +1 -1
  23. package/dist/commands/blueprints/deploy.js +2 -1
  24. package/dist/commands/blueprints/destroy.d.ts +1 -1
  25. package/dist/commands/blueprints/destroy.js +2 -1
  26. package/dist/commands/blueprints/doctor.js +2 -2
  27. package/dist/commands/blueprints/info.d.ts +1 -1
  28. package/dist/commands/blueprints/info.js +2 -1
  29. package/dist/commands/blueprints/plan.d.ts +1 -1
  30. package/dist/commands/blueprints/plan.js +2 -1
  31. package/dist/commands/blueprints/promote.js +2 -2
  32. package/dist/commands/blueprints/stacks.d.ts +1 -1
  33. package/dist/commands/blueprints/stacks.js +2 -1
  34. package/dist/commands/functions/build.d.ts +1 -1
  35. package/dist/commands/functions/build.js +2 -1
  36. package/dist/commands/functions/env/add.d.ts +1 -1
  37. package/dist/commands/functions/env/add.js +2 -1
  38. package/dist/commands/functions/env/list.d.ts +1 -1
  39. package/dist/commands/functions/env/list.js +2 -1
  40. package/dist/commands/functions/env/remove.d.ts +1 -1
  41. package/dist/commands/functions/env/remove.js +2 -1
  42. package/dist/commands/functions/logs.d.ts +0 -1
  43. package/dist/commands/functions/logs.js +0 -5
  44. package/dist/commands/functions/test.d.ts +1 -1
  45. package/dist/commands/functions/test.js +2 -1
  46. package/dist/cores/blueprints/config.js +5 -5
  47. package/dist/cores/blueprints/deploy.js +62 -74
  48. package/dist/cores/blueprints/destroy.js +3 -3
  49. package/dist/cores/blueprints/doctor.js +5 -1
  50. package/dist/cores/blueprints/info.js +1 -1
  51. package/dist/cores/blueprints/init.d.ts +4 -3
  52. package/dist/cores/blueprints/plan.js +7 -1
  53. package/dist/cores/blueprints/promote.js +4 -1
  54. package/dist/cores/blueprints/stacks.js +6 -2
  55. package/dist/cores/functions/build.js +2 -2
  56. package/dist/cores/functions/env/add.js +1 -1
  57. package/dist/cores/functions/env/list.js +1 -1
  58. package/dist/cores/functions/env/remove.js +1 -1
  59. package/dist/cores/functions/test.js +4 -4
  60. package/dist/cores/index.d.ts +9 -2
  61. package/dist/cores/index.js +3 -2
  62. package/dist/server/app.d.ts +1 -1
  63. package/dist/server/handlers/invoke.d.ts +1 -1
  64. package/dist/utils/display/prompt.d.ts +2 -2
  65. package/dist/utils/display/prompt.js +1 -1
  66. package/dist/utils/functions/detect-native-modules.js +4 -1
  67. package/dist/utils/functions/fetch-document.d.ts +2 -2
  68. package/dist/utils/functions/prepare-asset.d.ts +2 -7
  69. package/dist/utils/functions/prepare-asset.js +2 -2
  70. package/dist/utils/logger.d.ts +2 -0
  71. package/dist/utils/logger.js +2 -0
  72. package/dist/utils/other/github.d.ts +1 -1
  73. package/dist/utils/other/npmjs.d.ts +1 -1
  74. package/dist/utils/traced-fetch.d.ts +1 -1
  75. package/dist/utils/types.d.ts +16 -2
  76. package/dist/utils/types.js +5 -4
  77. package/dist/utils/validate/resource.d.ts +0 -3
  78. package/dist/utils/validate/resource.js +0 -270
  79. package/dist/utils/validated-token.d.ts +2 -2
  80. package/oclif.manifest.json +20 -58
  81. package/package.json +2 -2
@@ -138,7 +138,7 @@ export async function functionTestCore(options) {
138
138
  event: 'scheduled',
139
139
  };
140
140
  const spinner = log.ora('Executing function...').start();
141
- const { json, logs, error } = await testAction(resource, invokeOptions, contextOptions, {
141
+ const { json: response, logs, error, } = await testAction(resource, invokeOptions, contextOptions, {
142
142
  timeout: timeout ? timeout : resource.timeout,
143
143
  });
144
144
  if (error) {
@@ -151,11 +151,11 @@ export async function functionTestCore(options) {
151
151
  spinner.succeed('Function execution succeeded.');
152
152
  log('Logs:');
153
153
  log(logs || '');
154
- if (json) {
154
+ if (response) {
155
155
  log('Response:');
156
- log(JSON.stringify(json, null, 2));
156
+ log(JSON.stringify(response, null, 2));
157
157
  }
158
- return { success: true };
158
+ return { success: true, json: { response, logs } };
159
159
  }
160
160
  catch (error) {
161
161
  return {
@@ -7,7 +7,7 @@ export interface CoreConfig {
7
7
  /** The CLI binary name. */
8
8
  bin: string;
9
9
  /** The log output function. */
10
- log: ReturnType<typeof Logger>;
10
+ log: Logger;
11
11
  /** Enable resource validation during parsing */
12
12
  validateResources?: boolean;
13
13
  /** Path to a specific Blueprint file or directory */
@@ -25,7 +25,10 @@ export interface DeployedBlueprintConfig extends BlueprintConfig {
25
25
  deployedStack: Stack;
26
26
  }
27
27
  export type CoreResult = {
28
- /** Arbitrary data for isolated testing */
28
+ /**
29
+ * Internal data for isolated testing and debugging.
30
+ * Not returned to users! Use `json` for structured output via oclif's `--json` flag.
31
+ */
29
32
  data?: Record<string, unknown>;
30
33
  } & ({
31
34
  /** Something went wrong. */
@@ -40,6 +43,10 @@ export type CoreResult = {
40
43
  /** The streaming function, if the operation is streaming. */
41
44
  streaming?: Promise<void>;
42
45
  error?: never;
46
+ /**
47
+ * Structured data returned to the user when `--json` is passed.
48
+ * Commands should return this value from `run()` for oclif to serialize.
49
+ */
43
50
  json?: Record<string, unknown>;
44
51
  });
45
52
  type InitBlueprintConfigParams = CoreConfig & ({
@@ -1,7 +1,6 @@
1
1
  import { readLocalBlueprint } from '../actions/blueprints/blueprint.js';
2
2
  import { getStack, resolveStackIdByNameOrId } from '../actions/blueprints/stacks.js';
3
3
  import { presentBlueprintParserErrors } from '../utils/display/errors.js';
4
- import { niceId } from '../utils/display/presenters.js';
5
4
  import { validTokenOrErrorMessage } from '../utils/validated-token.js';
6
5
  export * as blueprintsCores from './blueprints/index.js';
7
6
  export * as functionsCores from './functions/index.js';
@@ -55,12 +54,14 @@ export async function initDeployedBlueprintConfig(config) {
55
54
  config.log(`Incomplete configuration. Run \`${config.bin} blueprints doctor\` for diagnostics.`);
56
55
  return { ok: false, error: 'Missing Stack deployment configuration for Blueprint' };
57
56
  }
57
+ const spinner = config.log.ora('Loading Stack deployment...').start();
58
58
  const stackResponse = await getStack({ stackId, auth, logger: config.log });
59
59
  if (!stackResponse.ok) {
60
- config.log(`Could not retrieve Stack deployment info for ${niceId(stackId)}.`);
60
+ spinner.fail('Could not load Stack deployment');
61
61
  config.log(`Run \`${config.bin} blueprints doctor\` for diagnostics.`);
62
62
  return { ok: false, error: 'Missing Stack deployment' };
63
63
  }
64
+ spinner.stop().clear();
64
65
  return {
65
66
  ok: true,
66
67
  value: {
@@ -1,6 +1,6 @@
1
1
  import type { Logger } from '../utils/logger.js';
2
2
  import { type InvokeExecutionOptions } from '../utils/types.js';
3
- declare const app: (host: string, port: number, logger: ReturnType<typeof Logger>, validateResources: boolean, executionOptions?: Partial<InvokeExecutionOptions>) => void;
3
+ declare const app: (host: string, port: number, logger: Logger, validateResources: boolean, executionOptions?: Partial<InvokeExecutionOptions>) => void;
4
4
  declare function parseDocumentUrl(url: string): {
5
5
  projectId: string;
6
6
  dataset: string;
@@ -1,5 +1,5 @@
1
1
  import type { Logger } from '../../utils/logger.js';
2
2
  import type { InvocationResponse, InvokeContextOptions, InvokeExecutionOptions, InvokePayloadMetadata } from '../../utils/types.js';
3
- export declare function handleInvokeRequest(functionName: string, event: Record<string, unknown>, metadata: InvokePayloadMetadata, context: InvokeContextOptions, logger: ReturnType<typeof Logger>, validateResources: boolean, executionOptions?: Partial<InvokeExecutionOptions>): Promise<InvocationResponse & {
3
+ export declare function handleInvokeRequest(functionName: string, event: Record<string, unknown>, metadata: InvokePayloadMetadata, context: InvokeContextOptions, logger: Logger, validateResources: boolean, executionOptions?: Partial<InvokeExecutionOptions>): Promise<InvocationResponse & {
4
4
  timings: Record<string, number>;
5
5
  }>;
@@ -10,7 +10,7 @@ export declare function promptForProject({ token, knownOrganizationId, knownProj
10
10
  token: string;
11
11
  knownOrganizationId?: string;
12
12
  knownProjectId?: string;
13
- logger: ReturnType<typeof Logger>;
13
+ logger: Logger;
14
14
  }): Promise<{
15
15
  projectId: string;
16
16
  displayName: string;
@@ -26,7 +26,7 @@ export declare function promptForProject({ token, knownOrganizationId, knownProj
26
26
  export declare function promptForStack({ projectId, token, logger, }: {
27
27
  projectId: string;
28
28
  token: string;
29
- logger: ReturnType<typeof Logger>;
29
+ logger: Logger;
30
30
  }): Promise<{
31
31
  stackId: string;
32
32
  name: string;
@@ -95,7 +95,7 @@ export async function promptForStack({ projectId, token, logger, }) {
95
95
  stackChoices.push({ name: styleText('bold', 'New Stack ✨'), value: NEW_STACK_ID });
96
96
  stackChoices.push(new Separator(styleText('underline', 'Use an existing Stack:')));
97
97
  stackChoices.push(...stacks.map((s) => ({
98
- name: `"${s.name}" ${niceId(s.id)} ${styleText('dim', `(${s.resourceCount} res)`)}`,
98
+ name: `"${s.name}" ${niceId(s.id)} ${styleText('dim', `(${s.resourceCount} ${s.resourceCount === 1 ? 'resource' : 'resources'})`)}`,
99
99
  value: s.id,
100
100
  })));
101
101
  pickedStackId = await select({
@@ -62,8 +62,11 @@ export const detectNativeModulesForBundle = (resource) => {
62
62
  const stats = statSync(sourcePath);
63
63
  const entryDir = stats.isFile() ? path.dirname(sourcePath) : sourcePath;
64
64
  const hasPackageJson = existsSync(path.join(entryDir, 'package.json'));
65
+ // @todo: Can this just move to vite https://linear.app/sanity/issue/RUN-1221/can-we-use-a-vite-plugin-at-transpile-to-determine-native-modules
65
66
  if (!hasPackageJson) {
66
- throw new Error('pnpm workspace detected but no package.json found in function src');
67
+ // No package.json means we cant run the native module check
68
+ // Assuming safe to skip native module detection.
69
+ return [];
67
70
  }
68
71
  // gets package.json dependencies of the function. We use these function deps to compare against the workspace root modules.
69
72
  const pkgJsonPath = path.join(entryDir, 'package.json');
@@ -1,5 +1,5 @@
1
1
  import { type ClientConfig } from '@sanity/client';
2
2
  import type { Logger } from '../logger.js';
3
3
  import type { FetchConfig } from '../types.js';
4
- export declare function fetchDocument(documentId: string, { projectId, dataset, useCdn, apiVersion, apiHost, token }: ClientConfig, logger: ReturnType<typeof Logger>): Promise<Record<string, unknown>>;
5
- export declare function fetchAsset(documentId: string, { mediaLibraryId, apiVersion, apiHost, token }: FetchConfig, logger: ReturnType<typeof Logger>): Promise<Record<string, unknown>>;
4
+ export declare function fetchDocument(documentId: string, { projectId, dataset, useCdn, apiVersion, apiHost, token }: ClientConfig, logger: Logger): Promise<Record<string, unknown>>;
5
+ export declare function fetchAsset(documentId: string, { mediaLibraryId, apiVersion, apiHost, token }: FetchConfig, logger: Logger): Promise<Record<string, unknown>>;
@@ -1,11 +1,6 @@
1
- import type { CollectionFunction, FunctionResource, InstallerType } from '../types.js';
1
+ import type { AssetPrepResult, CollectionFunction, FunctionResource, InstallerType } from '../types.js';
2
2
  export declare function prepareAsset({ resource, }: {
3
3
  resource: FunctionResource | CollectionFunction;
4
4
  }, { installer }?: {
5
5
  installer?: InstallerType;
6
- }): Promise<{
7
- success: boolean;
8
- outputPath?: string;
9
- cleanup?: () => Promise<void>;
10
- error?: string;
11
- }>;
6
+ }): Promise<AssetPrepResult>;
@@ -29,7 +29,7 @@ export async function prepareAsset({ resource, }, { installer } = {}) {
29
29
  wasBundled = result.bundled;
30
30
  }
31
31
  catch (err) {
32
- return { success: false, error: err instanceof Error ? err.message : `${err}` };
32
+ return { success: false, cleanup, error: err instanceof Error ? err.message : `${err}` };
33
33
  }
34
34
  }
35
35
  const shouldResolveDependencies = await shouldAutoResolveDependencies(resource);
@@ -45,7 +45,7 @@ export async function prepareAsset({ resource, }, { installer } = {}) {
45
45
  return { success: true, outputPath: functionPath, cleanup };
46
46
  }
47
47
  catch (err) {
48
- return { success: false, error: err instanceof Error ? err.message : `${err}` };
48
+ return { success: false, cleanup, error: err instanceof Error ? err.message : `${err}` };
49
49
  }
50
50
  }
51
51
  /**
@@ -2,6 +2,7 @@ import ora from 'ora';
2
2
  export declare function Logger(log: (msg: string) => void, flags?: {
3
3
  verbose?: boolean;
4
4
  trace?: boolean;
5
+ json?: boolean;
5
6
  }): {
6
7
  (msg: string): void;
7
8
  trace(formatter: unknown, ...args: unknown[]): false | void;
@@ -11,3 +12,4 @@ export declare function Logger(log: (msg: string) => void, flags?: {
11
12
  error(formatter: unknown, ...args: unknown[]): false | void;
12
13
  ora: typeof ora;
13
14
  };
15
+ export type Logger = ReturnType<typeof Logger>;
@@ -19,6 +19,8 @@ export function Logger(log, flags = {}) {
19
19
  logger.warn = (formatter, ...args) => level <= LogLevel.WARN && logger(format(formatter, ...args));
20
20
  logger.error = (formatter, ...args) => level <= LogLevel.ERROR && logger(format(formatter, ...args));
21
21
  const oraWrapper = (opts) => {
22
+ if (flags.json)
23
+ return createOraLineLoggingWrapper(opts, logger);
22
24
  if (level >= LogLevel.INFO)
23
25
  return ora(opts);
24
26
  return createOraLineLoggingWrapper(opts, logger);
@@ -1,3 +1,3 @@
1
1
  import type { Logger } from '../logger.js';
2
2
  export declare const GITHUB_API_URL = "https://api.github.com";
3
- export declare function gitHubRequest(path: string, logger: ReturnType<typeof Logger>): Promise<Response>;
3
+ export declare function gitHubRequest(path: string, logger: Logger): Promise<Response>;
@@ -1,2 +1,2 @@
1
1
  import type { Logger } from '../logger.js';
2
- export declare function getLatestNpmVersion(pkg: string, logger: ReturnType<typeof Logger>): Promise<string>;
2
+ export declare function getLatestNpmVersion(pkg: string, logger: Logger): Promise<string>;
@@ -32,4 +32,4 @@ export interface TracedFetchOptions {
32
32
  * const response = await tracedFetch('https://api.example.com/data')
33
33
  * ```
34
34
  */
35
- export declare function createTracedFetch(logger: ReturnType<typeof Logger>, options?: TracedFetchOptions): typeof fetch;
35
+ export declare function createTracedFetch(logger: Logger, options?: TracedFetchOptions): typeof fetch;
@@ -10,6 +10,11 @@ export type Result<T, E = string> = {
10
10
  ok: false;
11
11
  error: E;
12
12
  };
13
+ /** Base shape for API action responses */
14
+ export interface ActionResponse {
15
+ ok: boolean;
16
+ error: string | null;
17
+ }
13
18
  /** @internal */
14
19
  export interface AuthParams {
15
20
  token: string;
@@ -58,17 +63,17 @@ export interface BlueprintResourceRecord extends BlueprintResource {
58
63
  export interface DeployedResource extends BlueprintResourceRecord {
59
64
  externalId: string;
60
65
  }
61
- export declare function isLocalFunctionResource(r: BlueprintResource): r is FunctionResourceBase;
66
+ export declare function isLocalFunctionResource(r: BlueprintResource): boolean;
62
67
  export declare function isDocumentFunctionResource<T extends BlueprintResource>(r: T): r is T & FunctionResourceDocument;
63
68
  export declare function isMediaLibraryAssetFunctionResource<T extends BlueprintResource>(r: T): r is T & FunctionResourceMediaLibraryAsset;
64
69
  export declare function isScheduleFunctionResource<T extends BlueprintResource>(r: T): r is T & FunctionResourceSchedule;
65
- export declare function isLocalFunctionCollection<T extends BlueprintResource>(r: T): r is T & FunctionsCollection;
66
70
  export declare function isScheduleEvent(e: unknown): e is FunctionResourceScheduleEvent;
67
71
  export declare function isCorsOriginResource(r: unknown): r is BlueprintCorsOriginResource;
68
72
  export declare function isProjectResource(r: unknown): r is BlueprintProjectResource;
69
73
  export declare function isRobotResource(r: unknown): r is BlueprintRobotResource;
70
74
  export declare function isRoleResource(r: unknown): r is BlueprintRoleResource;
71
75
  export declare function isDatasetResource(r: unknown): r is BlueprintDatasetResource;
76
+ export declare function isStudioResource(r: unknown): boolean;
72
77
  export declare function isWebhookResource(r: unknown): r is BlueprintDocumentWebhookResource;
73
78
  /** @internal */
74
79
  export type FunctionResource = FunctionResourceDocument | FunctionResourceMediaLibraryAsset | FunctionResourceSchedule | FunctionResourceBase;
@@ -258,4 +263,13 @@ export declare interface FetchConfig {
258
263
  }
259
264
  export declare const INSTALLER_OPTIONS: readonly ["npm", "pnpm", "yarn"];
260
265
  export type InstallerType = 'npm' | 'pnpm' | 'yarn';
266
+ export type AssetPrepResult = {
267
+ success: true;
268
+ outputPath: string;
269
+ cleanup: () => Promise<void>;
270
+ } | {
271
+ success: false;
272
+ cleanup: () => Promise<void>;
273
+ error: string;
274
+ };
261
275
  export {};
@@ -1,9 +1,9 @@
1
1
  import { validateCorsOrigin, validateDataset, validateDocumentWebhook, validateProject, validateRobot, validateRole, } from '@sanity/blueprints';
2
2
  import { SANITY_FUNCTION_DOCUMENT, SANITY_FUNCTION_MEDIA_LIBRARY_ASSET, SANITY_FUNCTION_PREFIX, SANITY_FUNCTION_SCHEDULED, } from '../constants.js';
3
- // type narrowing with predicate functions
4
3
  export function isLocalFunctionResource(r) {
5
4
  return r.type.startsWith(SANITY_FUNCTION_PREFIX);
6
5
  }
6
+ // type narrowing with predicate functions
7
7
  export function isDocumentFunctionResource(r) {
8
8
  return r.type === SANITY_FUNCTION_DOCUMENT;
9
9
  }
@@ -13,9 +13,6 @@ export function isMediaLibraryAssetFunctionResource(r) {
13
13
  export function isScheduleFunctionResource(r) {
14
14
  return r.type === SANITY_FUNCTION_SCHEDULED;
15
15
  }
16
- export function isLocalFunctionCollection(r) {
17
- return r.type === 'sanity.experimental.functions-collection';
18
- }
19
16
  export function isScheduleEvent(e) {
20
17
  return e !== null && typeof e === 'object' && ('hour' in e || 'expression' in e);
21
18
  }
@@ -34,6 +31,10 @@ export function isRoleResource(r) {
34
31
  export function isDatasetResource(r) {
35
32
  return validateDataset(r).length === 0;
36
33
  }
34
+ // TODO: update to use blueprints helpers once available
35
+ export function isStudioResource(r) {
36
+ return !!r && typeof r === 'object' && 'type' in r && r.type === 'sanity.studio';
37
+ }
37
38
  export function isWebhookResource(r) {
38
39
  return validateDocumentWebhook(r).length === 0;
39
40
  }
@@ -1,4 +1 @@
1
- import type { FunctionResource } from '../types.js';
2
- import { type BlueprintParserError } from '../types.js';
3
1
  export declare function validateFunctionName(name: string): boolean;
4
- export declare function validateFunctionResource(resource: FunctionResource): BlueprintParserError[];
@@ -1,274 +1,4 @@
1
- import { SANITY_FUNCTION_PREFIX } from '../../constants.js';
2
- import { BlueprintParserErrorType, isDocumentFunctionResource, isMediaLibraryAssetFunctionResource, isScheduleEvent, } from '../types.js';
3
1
  export function validateFunctionName(name) {
4
2
  // must be 3+ characters, no special characters, no spaces, allow _ and -
5
3
  return /^[a-zA-Z0-9][a-zA-Z0-9_-]{2,}$/.test(name);
6
4
  }
7
- const validFunctionEventNames = ['publish', 'create', 'update', 'delete'];
8
- export function validateFunctionResource(resource) {
9
- const { name: fnName } = resource;
10
- const msgPrefix = `Function "${fnName}":`;
11
- const errors = [];
12
- if (!validateFunctionName(resource.name)) {
13
- errors.push({
14
- message: `${msgPrefix} Function name must be at least 3 characters, start with a letter or number, and only contain letters, numbers, _ or -`,
15
- type: BlueprintParserErrorType.InvalidProperty,
16
- });
17
- }
18
- if (!resource.type.startsWith(SANITY_FUNCTION_PREFIX)) {
19
- errors.push({
20
- message: `${msgPrefix} Resource type must start with "${SANITY_FUNCTION_PREFIX}"`,
21
- type: BlueprintParserErrorType.InvalidType,
22
- });
23
- }
24
- if (isScheduleEvent(resource.event)) {
25
- const event = resource.event;
26
- const hasExpression = 'expression' in event;
27
- const hasExplicitFields = 'minute' in event ||
28
- 'hour' in event ||
29
- 'dayOfMonth' in event ||
30
- 'month' in event ||
31
- 'dayOfWeek' in event;
32
- if (hasExpression && hasExplicitFields) {
33
- errors.push({
34
- type: BlueprintParserErrorType.InvalidFormat,
35
- message: 'Cannot specify both `expression` and explicit cron fields (`minute`, `hour`, `dayOfMonth`, `month`, `dayOfWeek`)',
36
- });
37
- }
38
- else if (!hasExpression && !hasExplicitFields) {
39
- errors.push({
40
- type: BlueprintParserErrorType.MissingRequiredProperty,
41
- message: 'Either `expression` or explicit cron fields (`minute`, `hour`, `dayOfMonth`, `month`, `dayOfWeek`) must be provided',
42
- });
43
- }
44
- else if (hasExpression) {
45
- const expressionParts = event.expression.split(' ');
46
- if (expressionParts.length !== 5) {
47
- errors.push({
48
- type: BlueprintParserErrorType.InvalidFormat,
49
- message: 'Invalid `expression` format. Cron fields (`minute`, `hour`, `dayOfMonth`, `month`, `dayOfWeek`) must be provided',
50
- });
51
- }
52
- else {
53
- const [minute, hour, dayOfWeek, month, dayOfMonth] = expressionParts;
54
- errors.push(...validateScheduleEvent(msgPrefix, { minute, hour, dayOfWeek, month, dayOfMonth }));
55
- }
56
- }
57
- else if (hasExplicitFields) {
58
- errors.push(...validateScheduleEvent(msgPrefix, event));
59
- }
60
- }
61
- else {
62
- if (!resource.event || !Array.isArray(resource.event.on) || resource.event.on.length === 0) {
63
- errors.push({
64
- message: `${msgPrefix} event.on must be a non-empty array`,
65
- type: BlueprintParserErrorType.MissingRequiredProperty,
66
- });
67
- }
68
- else if (!resource.event.on.every((evt) => validFunctionEventNames.includes(evt))) {
69
- errors.push({
70
- message: `${msgPrefix} event.on values must be one of ${validFunctionEventNames.map((e) => `"${e}"`).join(', ')}`,
71
- type: BlueprintParserErrorType.InvalidValue,
72
- });
73
- }
74
- if (resource.event?.filter && typeof resource.event.filter !== 'string') {
75
- errors.push({
76
- message: `${msgPrefix} event.filter must be a string`,
77
- type: BlueprintParserErrorType.InvalidType,
78
- });
79
- }
80
- if (resource.event?.projection && typeof resource.event.projection !== 'string') {
81
- errors.push({
82
- message: `${msgPrefix} event.projection must be a string`,
83
- type: BlueprintParserErrorType.InvalidType,
84
- });
85
- }
86
- }
87
- if (resource.memory !== undefined) {
88
- if (!Number.isInteger(resource.memory)) {
89
- errors.push({
90
- message: `${msgPrefix} memory must be an integer`,
91
- type: BlueprintParserErrorType.InvalidType,
92
- });
93
- }
94
- else if (resource.memory < 1 || resource.memory > 10) {
95
- errors.push({
96
- message: `${msgPrefix} memory must be between 1 and 10 (GB)`,
97
- type: BlueprintParserErrorType.InvalidValue,
98
- });
99
- }
100
- }
101
- if (resource.timeout !== undefined) {
102
- if (!Number.isInteger(resource.timeout)) {
103
- errors.push({
104
- message: `${msgPrefix} timeout must be an integer`,
105
- type: BlueprintParserErrorType.InvalidType,
106
- });
107
- }
108
- else if (resource.timeout < 1 || resource.timeout > 900) {
109
- errors.push({
110
- message: `${msgPrefix} timeout must be between 1 and 900 (seconds)`,
111
- type: BlueprintParserErrorType.InvalidValue,
112
- });
113
- }
114
- }
115
- if (resource.env !== undefined) {
116
- if (typeof resource.env !== 'object') {
117
- errors.push({
118
- message: `${msgPrefix} env must be an object`,
119
- type: BlueprintParserErrorType.InvalidType,
120
- });
121
- }
122
- else {
123
- if (!Object.keys(resource.env).every((key) => typeof key === 'string')) {
124
- errors.push({
125
- message: `${msgPrefix} All env keys must be strings`,
126
- type: BlueprintParserErrorType.InvalidFormat,
127
- });
128
- }
129
- if (!Object.values(resource.env).every((value) => typeof value === 'string')) {
130
- errors.push({
131
- message: `${msgPrefix} All env values must be strings`,
132
- type: BlueprintParserErrorType.InvalidFormat,
133
- });
134
- }
135
- }
136
- }
137
- if (resource.event && 'resource' in resource.event && resource.event.resource) {
138
- if (!resource.event.resource.type) {
139
- errors.push({
140
- message: `${msgPrefix} event.resource.type must be defined`,
141
- type: BlueprintParserErrorType.MissingRequiredProperty,
142
- });
143
- }
144
- if (!resource.event.resource.id) {
145
- errors.push({
146
- message: `${msgPrefix} event.resource.id must be defined`,
147
- type: BlueprintParserErrorType.MissingRequiredProperty,
148
- });
149
- }
150
- }
151
- if (isMediaLibraryAssetFunctionResource(resource)) {
152
- if (resource.event.resource.type !== 'media-library') {
153
- errors.push({
154
- message: `${msgPrefix} event.resource.type must be "media-library"`,
155
- type: BlueprintParserErrorType.InvalidType,
156
- });
157
- }
158
- }
159
- if (isDocumentFunctionResource(resource) && resource.event) {
160
- if (resource.event.includeDrafts && typeof resource.event.includeDrafts !== 'boolean') {
161
- errors.push({
162
- message: `${msgPrefix} event.includeDrafts must be a boolean`,
163
- type: BlueprintParserErrorType.InvalidType,
164
- });
165
- }
166
- if (resource.event.includeAllVersions &&
167
- typeof resource.event.includeAllVersions !== 'boolean') {
168
- errors.push({
169
- message: `${msgPrefix} event.includeAllVersions must be a boolean`,
170
- type: BlueprintParserErrorType.InvalidType,
171
- });
172
- }
173
- if (resource.event.resource) {
174
- if (!resource.event.resource.type || resource.event.resource.type !== 'dataset') {
175
- errors.push({
176
- message: `${msgPrefix} event.resource.type must be "dataset"`,
177
- type: BlueprintParserErrorType.InvalidType,
178
- });
179
- }
180
- // Ensure exactly one period is passed into the ID to conform to the format <projectId>.<datasetName>
181
- if (!resource.event.resource.id || resource.event.resource.id.split('.').length !== 2) {
182
- errors.push({
183
- message: `${msgPrefix} event.resource.id must be of the form <projectId>.<datasetName>. <datasetName> can be "*" to signify "all datasets in project with ID <projectId>."`,
184
- type: BlueprintParserErrorType.InvalidFormat,
185
- });
186
- }
187
- // TODO: validate the project ID and dataset names exist by making an API call?
188
- // NB: this is done by the functions service
189
- }
190
- }
191
- return errors;
192
- }
193
- function validateScheduleEvent(msgPrefix, event) {
194
- const errors = [];
195
- const properties = [
196
- { name: 'minute', regex: MINUTES },
197
- { name: 'hour', regex: HOURS },
198
- { name: 'dayOfMonth', regex: DAY_OF_MONTH },
199
- { name: 'month', regex: MONTH },
200
- { name: 'dayOfWeek', regex: DAY_OF_WEEK },
201
- ];
202
- properties.forEach((prop) => {
203
- const { name, regex } = prop;
204
- if (!(name in event)) {
205
- errors.push({
206
- type: BlueprintParserErrorType.MissingRequiredProperty,
207
- message: `${msgPrefix} '${name}' must be provided`,
208
- });
209
- return;
210
- }
211
- const value = event[name];
212
- if (typeof value !== 'string') {
213
- errors.push({
214
- type: BlueprintParserErrorType.InvalidType,
215
- message: `${msgPrefix} '${name}' must be a string`,
216
- });
217
- return;
218
- }
219
- if (!isValidCronPart(regex, value)) {
220
- errors.push({
221
- type: BlueprintParserErrorType.InvalidValue,
222
- message: `${msgPrefix} ${name} field contains invalid value: ${value}`,
223
- });
224
- }
225
- });
226
- return errors;
227
- }
228
- /**
229
- * Validates that each cron part adheres to it's rules
230
- *
231
- * @param {RegExp} regex the regular expression that corresponds to the part of the cron expression you are testing
232
- * @param {string} value the string value of part of the cron expression you are testing
233
- * @returns {boolean} whether or not the cron part is valid
234
- */
235
- function isValidCronPart(regex, value) {
236
- return regex.test(value) && checkAscendingRanges(value) && checkNoZeroStep(value);
237
- }
238
- const MINUTES = /^(\*(\/([1-5]?\d))?|([0-5]?\d)(-[0-5]?\d)?(\/([1-5]?\d))?)(,(\*(\/([1-5]?\d))?|([0-5]?\d)(-[0-5]?\d)?(\/([1-5]?\d))?))*$/;
239
- const HOURS = /^(\*(\/([1-9]|1\d|2[0-3]))?|([01]?\d|2[0-3])(-([01]?\d|2[0-3]))?(\/([1-9]|1\d|2[0-3]))?)(,(\*(\/([1-9]|1\d|2[0-3]))?|([01]?\d|2[0-3])(-([01]?\d|2[0-3]))?(\/([1-9]|1\d|2[0-3]))?))*$/;
240
- const DAY_OF_MONTH = /^(\*(\/([1-9]|[12]\d|3[01]))?|([1-9]|[12]\d|3[01])(-([1-9]|[12]\d|3[01]))?(\/([1-9]|[12]\d|3[01]))?)(,(\*(\/([1-9]|[12]\d|3[01]))?|([1-9]|[12]\d|3[01])(-([1-9]|[12]\d|3[01]))?(\/([1-9]|[12]\d|3[01]))?))*$/;
241
- const MONTH = /^(\*(\/([1-9]|1[0-2]))?|([1-9]|1[0-2]|JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)(-([1-9]|1[0-2]|JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))?(\/([1-9]|1[0-2]))?)(,(\*(\/([1-9]|1[0-2]))?|([1-9]|1[0-2]|JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)(-([1-9]|1[0-2]|JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))?(\/([1-9]|1[0-2]))?))*$/i;
242
- const DAY_OF_WEEK = /^(\*(\/([0-7]))?|([0-7]|SUN|MON|TUE|WED|THU|FRI|SAT)(-([0-7]|SUN|MON|TUE|WED|THU|FRI|SAT))?(\/([0-7]))?)(,(\*(\/([0-7]))?|([0-7]|SUN|MON|TUE|WED|THU|FRI|SAT)(-([0-7]|SUN|MON|TUE|WED|THU|FRI|SAT))?(\/([0-7]))?))*$/i;
243
- /**
244
- *
245
- * @param {string} value the string value of part of the cron expression you are testing
246
- * @returns {boolean} returns true if the range is valid
247
- */
248
- function checkAscendingRanges(value) {
249
- for (const part of value.split(',')) {
250
- if (part.includes('-')) {
251
- const [start, right] = part.split('-');
252
- const end = right.split('/')[0]; // remove /step
253
- const s = Number(start);
254
- const e = Number(end);
255
- if (s > e)
256
- return false;
257
- }
258
- }
259
- return true;
260
- }
261
- /**
262
- * @param {string} value the string value of part of the cron expression you are testing
263
- * @returns {boolean} makes sure we are not trying to divide by zero
264
- */
265
- function checkNoZeroStep(value) {
266
- for (const part of value.split(',')) {
267
- if (part.includes('/')) {
268
- const step = Number(part.split('/')[1]);
269
- if (step === 0)
270
- return false;
271
- }
272
- }
273
- return true;
274
- }
@@ -1,7 +1,7 @@
1
1
  import type { Logger } from './logger.js';
2
2
  import type { Result } from './types.js';
3
- export declare function validToken(logger: ReturnType<typeof Logger>, maybeToken?: string): Promise<string>;
4
- export declare function validTokenOrErrorMessage(logger: ReturnType<typeof Logger>, maybeToken?: string): Promise<Result<string, {
3
+ export declare function validToken(logger: Logger, maybeToken?: string): Promise<string>;
4
+ export declare function validTokenOrErrorMessage(logger: Logger, maybeToken?: string): Promise<Result<string, {
5
5
  e: Error | unknown;
6
6
  message: string;
7
7
  }>>;