@sanity/runtime-cli 12.1.0 → 12.2.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 (53) hide show
  1. package/README.md +20 -19
  2. package/dist/actions/blueprints/blueprint.d.ts +3 -2
  3. package/dist/actions/blueprints/stacks.d.ts +8 -4
  4. package/dist/actions/blueprints/stacks.js +6 -32
  5. package/dist/commands/blueprints/doctor.d.ts +1 -0
  6. package/dist/commands/blueprints/doctor.js +4 -0
  7. package/dist/commands/functions/dev.d.ts +2 -2
  8. package/dist/commands/functions/dev.js +3 -2
  9. package/dist/constants.d.ts +7 -0
  10. package/dist/constants.js +7 -0
  11. package/dist/cores/blueprints/doctor.d.ts +1 -0
  12. package/dist/cores/blueprints/doctor.js +36 -5
  13. package/dist/cores/blueprints/info.js +2 -2
  14. package/dist/cores/functions/env/add.js +2 -2
  15. package/dist/cores/functions/env/list.js +2 -2
  16. package/dist/cores/functions/env/remove.js +2 -2
  17. package/dist/cores/functions/logs.js +2 -2
  18. package/dist/cores/functions/test.js +31 -23
  19. package/dist/server/app.js +32 -21
  20. package/dist/server/handlers/invoke.d.ts +2 -2
  21. package/dist/server/handlers/invoke.js +2 -2
  22. package/dist/server/static/components/api-base.js +3 -0
  23. package/dist/server/static/components/app.css +120 -95
  24. package/dist/server/static/components/clear-button.js +1 -1
  25. package/dist/server/static/components/console-panel.js +6 -6
  26. package/dist/server/static/components/fetch-button.js +1 -1
  27. package/dist/server/static/components/filter-api-version.js +3 -3
  28. package/dist/server/static/components/filter-document-id.js +5 -5
  29. package/dist/server/static/components/filter-with-token.js +4 -4
  30. package/dist/server/static/components/filters.js +2 -2
  31. package/dist/server/static/components/function-list.js +14 -5
  32. package/dist/server/static/components/help-button.js +4 -1
  33. package/dist/server/static/components/payload-panel.js +9 -9
  34. package/dist/server/static/components/response-panel.js +8 -8
  35. package/dist/server/static/components/rule-panel.js +4 -4
  36. package/dist/server/static/components/run-panel.js +4 -4
  37. package/dist/server/static/components/select-dropdown.js +5 -25
  38. package/dist/server/static/index.html +9 -9
  39. package/dist/server/static/vendor/m-.css +1 -0
  40. package/dist/server/static/vendor/m-.woff2 +0 -0
  41. package/dist/utils/display/blueprints-formatting.d.ts +3 -2
  42. package/dist/utils/display/blueprints-formatting.js +102 -50
  43. package/dist/utils/display/resources-formatting.d.ts +6 -2
  44. package/dist/utils/display/resources-formatting.js +71 -17
  45. package/dist/utils/find-function.d.ts +2 -2
  46. package/dist/utils/find-function.js +9 -2
  47. package/dist/utils/invoke-local.d.ts +2 -2
  48. package/dist/utils/invoke-local.js +27 -16
  49. package/dist/utils/types.d.ts +46 -22
  50. package/dist/utils/types.js +25 -2
  51. package/dist/utils/validate/resource.js +144 -23
  52. package/oclif.manifest.json +14 -1
  53. package/package.json +2 -2
@@ -11,6 +11,7 @@ import { resolveResourceDependencies } from './functions/resolve-dependencies.js
11
11
  import { shouldAutoResolveDependencies } from './functions/should-auto-resolve-deps.js';
12
12
  import { shouldTranspileFunction } from './functions/should-transpile.js';
13
13
  import { transpileFunction } from './transpile/transpile-function.js';
14
+ import { isDocumentFunctionResource, isGroqContextOptions, isMediaLibraryAssetFunctionResource, } from './types.js';
14
15
  function getChildProcessWrapperPath() {
15
16
  return fileURLToPath(new URL('./child-process-wrapper.js', import.meta.url));
16
17
  }
@@ -34,7 +35,8 @@ function getEvent(rule) {
34
35
  projection: rule.projection || DEFAULT_GROQ_RULE.projection,
35
36
  };
36
37
  }
37
- export async function applyGroqRule(resource, data, before, after, projectId, dataset) {
38
+ export async function applyGroqRule(resource, payload, projectId, dataset) {
39
+ const { before, after, payload: data = null } = payload;
38
40
  // If there is no rule set return everything
39
41
  if (!resource.event)
40
42
  return data;
@@ -64,17 +66,6 @@ export default async function invoke(resource, payload, context, options) {
64
66
  if (!resource.src) {
65
67
  throw new Error(`Function resource "${resource.name}" is missing the 'src' property.`);
66
68
  }
67
- const { before, after, payload: data = null } = payload;
68
- const { forceColor = true, timeout = 10 } = options;
69
- const { projectId, dataset } = context.clientOptions;
70
- const filteredData = await applyGroqRule(resource, data, before, after, projectId, dataset);
71
- if (typeof filteredData === 'undefined') {
72
- return {
73
- logs: `⚠️ Filter "${resource.event?.filter}" returned an empty result. Skipping invoke.`,
74
- error: undefined,
75
- json: undefined,
76
- };
77
- }
78
69
  let cleanupBundle = async () => { };
79
70
  let functionPath = '';
80
71
  let bundleTimings;
@@ -98,6 +89,24 @@ export default async function invoke(resource, payload, context, options) {
98
89
  if (!existingPackageJson) {
99
90
  createTempPackageJson(functionPath);
100
91
  }
92
+ const { forceColor = true, timeout = 10 } = options;
93
+ let filteredData = {};
94
+ if (isDocumentFunctionResource(resource) || isMediaLibraryAssetFunctionResource(resource)) {
95
+ if (!isGroqContextOptions(context)) {
96
+ throw new Error('GROQ-based functions require a context with clientOptions');
97
+ }
98
+ const { projectId, dataset } = context.clientOptions;
99
+ if ('before' in payload) {
100
+ filteredData = await applyGroqRule(resource, payload, projectId, dataset);
101
+ if (typeof filteredData === 'undefined') {
102
+ return {
103
+ logs: `⚠️ Filter "${resource.event?.filter}" returned an empty result. Skipping invoke.`,
104
+ error: undefined,
105
+ json: undefined,
106
+ };
107
+ }
108
+ }
109
+ }
101
110
  return new Promise((resolve, reject) => {
102
111
  let child;
103
112
  let timer;
@@ -167,10 +176,12 @@ export default async function invoke(resource, payload, context, options) {
167
176
  context: {
168
177
  ...context,
169
178
  local: true,
170
- clientOptions: {
171
- ...context.clientOptions,
172
- apiHost: config.apiUrl,
173
- },
179
+ ...(isGroqContextOptions(context) && {
180
+ clientOptions: {
181
+ ...context.clientOptions,
182
+ apiHost: config.apiUrl,
183
+ },
184
+ }),
174
185
  },
175
186
  };
176
187
  child.send(JSON.stringify({ srcPath: functionPath, payload }, null, 2));
@@ -1,4 +1,6 @@
1
+ import { type BlueprintCorsOriginResource, type BlueprintDatasetResource, type BlueprintDocumentWebhookResource, type BlueprintResource, type BlueprintRoleResource } from '@sanity/blueprints';
1
2
  import type { Blueprint } from '@sanity/blueprints-parser';
3
+ import { SANITY_FUNCTION_DOCUMENT, SANITY_FUNCTION_MEDIA_LIBRARY_ASSET, SANITY_FUNCTION_SCHEDULE } from '../constants.js';
2
4
  export type ScopeType = 'organization' | 'project';
3
5
  /** Result utility type */
4
6
  export type Result<T, E = string> = {
@@ -35,45 +37,58 @@ interface GroqRuleMediaLibraryFunction extends GroqRuleBase {
35
37
  id: string;
36
38
  };
37
39
  }
38
- /** @internal */
39
- export interface Resource {
40
- name: string;
41
- type: string;
42
- id?: string;
43
- externalId?: string;
44
- displayName?: string;
40
+ export interface FunctionResourceScheduleExplicitEvent {
41
+ minute: string;
42
+ hour: string;
43
+ dayOfMonth: string;
44
+ month: string;
45
+ dayOfWeek: string;
46
+ }
47
+ interface FunctionResourceScheduleExpressionEvent {
48
+ expression: string;
45
49
  }
46
- export interface DeployedResource extends Resource {
50
+ type FunctionResourceScheduleEvent = FunctionResourceScheduleExplicitEvent | FunctionResourceScheduleExpressionEvent;
51
+ export interface DeployedResource extends BlueprintResource {
47
52
  id: string;
48
53
  externalId: string;
54
+ parameters: Record<string, unknown>;
49
55
  }
50
- export declare function isLocalFunctionResource<T extends Resource>(r: T): r is T & FunctionResourceBase;
51
- export declare function isDocumentFunctionResource<T extends Resource>(r: T): r is T & FunctionResourceDocument;
52
- export declare function isMediaLibraryAssetFunctionResource<T extends Resource>(r: T): r is T & FunctionResourceMediaLibraryAsset;
53
- export declare function isLocalFunctionCollection<T extends Resource>(r: T): r is T & {
56
+ export declare function isLocalFunctionResource(r: BlueprintResource): r is FunctionResourceBase;
57
+ export declare function isDocumentFunctionResource<T extends BlueprintResource>(r: T): r is T & FunctionResourceDocument;
58
+ export declare function isMediaLibraryAssetFunctionResource<T extends BlueprintResource>(r: T): r is T & FunctionResourceMediaLibraryAsset;
59
+ export declare function isScheduleFunctionResource<T extends BlueprintResource>(r: T): r is T & FunctionResourceSchedule;
60
+ export declare function isLocalFunctionCollection<T extends BlueprintResource>(r: T): r is T & {
54
61
  functions: Array<FunctionResourceBase>;
55
62
  };
56
- /** @internal */
57
- export type FunctionResource = FunctionResourceDocument | FunctionResourceMediaLibraryAsset | FunctionResourceBase;
58
- export interface FunctionResourceBase extends Resource {
63
+ export declare function isScheduleEvent(e: unknown): e is FunctionResourceScheduleEvent;
64
+ export declare function isCorsOriginResource(r: unknown): r is BlueprintCorsOriginResource;
65
+ export declare function isRoleResource(r: unknown): r is BlueprintRoleResource;
66
+ export declare function isDatasetResource(r: unknown): r is BlueprintDatasetResource;
67
+ export declare function isWebhookResource(r: unknown): r is BlueprintDocumentWebhookResource;
68
+ /** @internal */
69
+ export type FunctionResource = FunctionResourceDocument | FunctionResourceMediaLibraryAsset | FunctionResourceSchedule | FunctionResourceBase;
70
+ export type FunctionGroqResource = FunctionResourceDocument | FunctionResourceMediaLibraryAsset;
71
+ export interface FunctionResourceBase extends BlueprintResource {
72
+ displayName?: string;
59
73
  src?: string;
60
74
  autoResolveDeps?: boolean;
61
75
  transpile?: boolean;
62
76
  memory?: number;
63
77
  timeout?: number;
64
78
  env?: Record<string, string>;
65
- event?: GroqRuleBase;
79
+ event?: GroqRuleBase | FunctionResourceScheduleEvent;
66
80
  }
67
81
  interface FunctionResourceDocument extends FunctionResourceBase {
68
- type: 'sanity.function.document';
82
+ type: typeof SANITY_FUNCTION_DOCUMENT;
69
83
  event?: GroqRuleDocumentFunction;
70
84
  }
71
85
  interface FunctionResourceMediaLibraryAsset extends FunctionResourceBase {
72
- type: 'sanity.function.media-library.asset';
86
+ type: typeof SANITY_FUNCTION_MEDIA_LIBRARY_ASSET;
73
87
  event: GroqRuleMediaLibraryFunction;
74
88
  }
75
- export interface CorsResource extends Resource {
76
- origin?: string;
89
+ interface FunctionResourceSchedule extends FunctionResourceBase {
90
+ type: typeof SANITY_FUNCTION_SCHEDULE;
91
+ event: FunctionResourceScheduleEvent;
77
92
  }
78
93
  export interface Stack {
79
94
  id: string;
@@ -107,16 +122,22 @@ export interface BuildPayloadOptions {
107
122
  timeout?: number;
108
123
  }
109
124
  /** @internal */
110
- export interface InvokePayloadOptions {
125
+ export interface InvokeGroqPayloadOptions {
111
126
  payload?: Record<string, unknown>;
112
127
  event: EventType;
113
128
  before: Record<string, unknown> | null;
114
129
  after: Record<string, unknown> | null;
115
130
  }
131
+ export interface InvokeSchedulePayloadOptions {
132
+ payload?: Record<string, unknown>;
133
+ event: 'schedule';
134
+ }
135
+ export type InvokePayloadOptions = InvokeGroqPayloadOptions | InvokeSchedulePayloadOptions;
136
+ export type InvokePayloadMetadata = Pick<InvokeGroqPayloadOptions, 'event' | 'before' | 'after'> | Pick<InvokeSchedulePayloadOptions, 'event'>;
116
137
  export type EventType = 'create' | 'update' | 'delete';
117
138
  export declare function isEventType(arg: string): arg is EventType;
118
139
  /** @internal */
119
- export interface InvokeContextOptions {
140
+ export interface InvokeGroqContextOptions {
120
141
  clientOptions: {
121
142
  apiVersion?: string;
122
143
  dataset?: string;
@@ -133,6 +154,9 @@ export interface InvokeContextOptions {
133
154
  /** The resource ID of the function container; resource ID that houses the function. */
134
155
  functionResourceId: string;
135
156
  }
157
+ export type InvokeScheduleContextOptions = Record<string, never>;
158
+ export type InvokeContextOptions = InvokeGroqContextOptions | InvokeScheduleContextOptions;
159
+ export declare function isGroqContextOptions(context: InvokeContextOptions): context is InvokeGroqContextOptions;
136
160
  /** @internal */
137
161
  export interface InvokeExecutionOptions {
138
162
  forceColor?: boolean;
@@ -1,19 +1,42 @@
1
+ import { validateCorsOrigin, validateDataset, validateDocumentWebhook, validateRole, } from '@sanity/blueprints';
2
+ import { SANITY_FUNCTION_DOCUMENT, SANITY_FUNCTION_MEDIA_LIBRARY_ASSET, SANITY_FUNCTION_SCHEDULE, } from '../constants.js';
1
3
  // type narrowing with predicate functions
2
4
  export function isLocalFunctionResource(r) {
3
5
  return r.type.startsWith('sanity.function.');
4
6
  }
5
7
  export function isDocumentFunctionResource(r) {
6
- return r.type === 'sanity.function.document';
8
+ return r.type === SANITY_FUNCTION_DOCUMENT;
7
9
  }
8
10
  export function isMediaLibraryAssetFunctionResource(r) {
9
- return r.type === 'sanity.function.media-library.asset';
11
+ return r.type === SANITY_FUNCTION_MEDIA_LIBRARY_ASSET;
12
+ }
13
+ export function isScheduleFunctionResource(r) {
14
+ return r.type === SANITY_FUNCTION_SCHEDULE;
10
15
  }
11
16
  export function isLocalFunctionCollection(r) {
12
17
  return r.type === 'sanity.experimental.functions-collection';
13
18
  }
19
+ export function isScheduleEvent(e) {
20
+ return e !== null && typeof e === 'object' && ('hour' in e || 'expression' in e);
21
+ }
22
+ export function isCorsOriginResource(r) {
23
+ return validateCorsOrigin(r).length === 0;
24
+ }
25
+ export function isRoleResource(r) {
26
+ return validateRole(r).length === 0;
27
+ }
28
+ export function isDatasetResource(r) {
29
+ return validateDataset(r).length === 0;
30
+ }
31
+ export function isWebhookResource(r) {
32
+ return validateDocumentWebhook(r).length === 0;
33
+ }
14
34
  export function isEventType(arg) {
15
35
  return ['create', 'update', 'delete'].includes(arg);
16
36
  }
37
+ export function isGroqContextOptions(context) {
38
+ return 'clientOptions' in context;
39
+ }
17
40
  /** @internal */
18
41
  export var BlueprintParserErrorType;
19
42
  (function (BlueprintParserErrorType) {
@@ -1,4 +1,4 @@
1
- import { BlueprintParserErrorType, isDocumentFunctionResource, isMediaLibraryAssetFunctionResource, } from '../types.js';
1
+ import { BlueprintParserErrorType, isDocumentFunctionResource, isMediaLibraryAssetFunctionResource, isScheduleEvent, } from '../types.js';
2
2
  export function validateFunctionName(name) {
3
3
  // must be 3+ characters, no special characters, no spaces, allow _ and -
4
4
  return /^[a-zA-Z0-9][a-zA-Z0-9_-]{2,}$/.test(name);
@@ -20,29 +20,68 @@ export function validateFunctionResource(resource) {
20
20
  type: BlueprintParserErrorType.InvalidType,
21
21
  });
22
22
  }
23
- if (!resource.event || !Array.isArray(resource.event.on) || resource.event.on.length === 0) {
24
- errors.push({
25
- message: `${msgPrefix} event.on must be a non-empty array`,
26
- type: BlueprintParserErrorType.MissingRequiredProperty,
27
- });
28
- }
29
- else if (!resource.event.on.every((evt) => validFunctionEventNames.includes(evt))) {
30
- errors.push({
31
- message: `${msgPrefix} event.on values must be one of ${validFunctionEventNames.map((e) => `"${e}"`).join(', ')}`,
32
- type: BlueprintParserErrorType.InvalidValue,
33
- });
34
- }
35
- if (resource.event?.filter && typeof resource.event.filter !== 'string') {
36
- errors.push({
37
- message: `${msgPrefix} event.filter must be a string`,
38
- type: BlueprintParserErrorType.InvalidType,
39
- });
23
+ if (isScheduleEvent(resource.event)) {
24
+ const event = resource.event;
25
+ const hasExpression = 'expression' in event;
26
+ const hasExplicitFields = 'minute' in event ||
27
+ 'hour' in event ||
28
+ 'dayOfMonth' in event ||
29
+ 'month' in event ||
30
+ 'dayOfWeek' in event;
31
+ if (hasExpression && hasExplicitFields) {
32
+ errors.push({
33
+ type: BlueprintParserErrorType.InvalidFormat,
34
+ message: 'Cannot specify both `expression` and explicit cron fields (`minute`, `hour`, `dayOfMonth`, `month`, `dayOfWeek`)',
35
+ });
36
+ }
37
+ else if (!hasExpression && !hasExplicitFields) {
38
+ errors.push({
39
+ type: BlueprintParserErrorType.MissingRequiredProperty,
40
+ message: 'Either `expression` or explicit cron fields (`minute`, `hour`, `dayOfMonth`, `month`, `dayOfWeek`) must be provided',
41
+ });
42
+ }
43
+ else if (hasExpression) {
44
+ const expressionParts = event.expression.split(' ');
45
+ if (expressionParts.length !== 5) {
46
+ errors.push({
47
+ type: BlueprintParserErrorType.InvalidFormat,
48
+ message: 'Invalid `expression` format. Cron fields (`minute`, `hour`, `dayOfMonth`, `month`, `dayOfWeek`) must be provided',
49
+ });
50
+ }
51
+ else {
52
+ const [minute, hour, dayOfWeek, month, dayOfMonth] = expressionParts;
53
+ errors.push(...validateScheduleEvent(msgPrefix, { minute, hour, dayOfWeek, month, dayOfMonth }));
54
+ }
55
+ }
56
+ else if (hasExplicitFields) {
57
+ errors.push(...validateScheduleEvent(msgPrefix, event));
58
+ }
40
59
  }
41
- if (resource.event?.projection && typeof resource.event.projection !== 'string') {
42
- errors.push({
43
- message: `${msgPrefix} event.projection must be a string`,
44
- type: BlueprintParserErrorType.InvalidType,
45
- });
60
+ else {
61
+ if (!resource.event || !Array.isArray(resource.event.on) || resource.event.on.length === 0) {
62
+ errors.push({
63
+ message: `${msgPrefix} event.on must be a non-empty array`,
64
+ type: BlueprintParserErrorType.MissingRequiredProperty,
65
+ });
66
+ }
67
+ else if (!resource.event.on.every((evt) => validFunctionEventNames.includes(evt))) {
68
+ errors.push({
69
+ message: `${msgPrefix} event.on values must be one of ${validFunctionEventNames.map((e) => `"${e}"`).join(', ')}`,
70
+ type: BlueprintParserErrorType.InvalidValue,
71
+ });
72
+ }
73
+ if (resource.event?.filter && typeof resource.event.filter !== 'string') {
74
+ errors.push({
75
+ message: `${msgPrefix} event.filter must be a string`,
76
+ type: BlueprintParserErrorType.InvalidType,
77
+ });
78
+ }
79
+ if (resource.event?.projection && typeof resource.event.projection !== 'string') {
80
+ errors.push({
81
+ message: `${msgPrefix} event.projection must be a string`,
82
+ type: BlueprintParserErrorType.InvalidType,
83
+ });
84
+ }
46
85
  }
47
86
  if (resource.memory !== undefined) {
48
87
  if (!Number.isInteger(resource.memory)) {
@@ -150,3 +189,85 @@ export function validateFunctionResource(resource) {
150
189
  }
151
190
  return errors;
152
191
  }
192
+ function validateScheduleEvent(msgPrefix, event) {
193
+ const errors = [];
194
+ const properties = [
195
+ { name: 'minute', regex: MINUTES },
196
+ { name: 'hour', regex: HOURS },
197
+ { name: 'dayOfMonth', regex: DAY_OF_MONTH },
198
+ { name: 'month', regex: MONTH },
199
+ { name: 'dayOfWeek', regex: DAY_OF_WEEK },
200
+ ];
201
+ properties.forEach((prop) => {
202
+ const { name, regex } = prop;
203
+ if (!(name in event)) {
204
+ errors.push({
205
+ type: BlueprintParserErrorType.MissingRequiredProperty,
206
+ message: `${msgPrefix} '${name}' must be provided`,
207
+ });
208
+ return;
209
+ }
210
+ const value = event[name];
211
+ if (typeof value !== 'string') {
212
+ errors.push({
213
+ type: BlueprintParserErrorType.InvalidType,
214
+ message: `${msgPrefix} '${name}' must be a string`,
215
+ });
216
+ return;
217
+ }
218
+ if (!isValidCronPart(regex, value)) {
219
+ errors.push({
220
+ type: BlueprintParserErrorType.InvalidValue,
221
+ message: `${msgPrefix} ${name} field contains invalid value: ${value}`,
222
+ });
223
+ }
224
+ });
225
+ return errors;
226
+ }
227
+ /**
228
+ * Validates that each cron part adheres to it's rules
229
+ *
230
+ * @param {RegExp} regex the regular expression that corresponds to the part of the cron expression you are testing
231
+ * @param {string} value the string value of part of the cron expression you are testing
232
+ * @returns {boolean} whether or not the cron part is valid
233
+ */
234
+ function isValidCronPart(regex, value) {
235
+ return regex.test(value) && checkAscendingRanges(value) && checkNoZeroStep(value);
236
+ }
237
+ 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))?))*$/;
238
+ 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]))?))*$/;
239
+ 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]))?))*$/;
240
+ 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;
241
+ 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;
242
+ /**
243
+ *
244
+ * @param {string} value the string value of part of the cron expression you are testing
245
+ * @returns {boolean} returns true if the range is valid
246
+ */
247
+ function checkAscendingRanges(value) {
248
+ for (const part of value.split(',')) {
249
+ if (part.includes('-')) {
250
+ const [start, right] = part.split('-');
251
+ const end = right.split('/')[0]; // remove /step
252
+ const s = Number(start);
253
+ const e = Number(end);
254
+ if (s > e)
255
+ return false;
256
+ }
257
+ }
258
+ return true;
259
+ }
260
+ /**
261
+ * @param {string} value the string value of part of the cron expression you are testing
262
+ * @returns {boolean} makes sure we are not trying to divide by zero
263
+ */
264
+ function checkNoZeroStep(value) {
265
+ for (const part of value.split(',')) {
266
+ if (part.includes('/')) {
267
+ const step = Number(part.split('/')[1]);
268
+ if (step === 0)
269
+ return false;
270
+ }
271
+ }
272
+ return true;
273
+ }
@@ -397,6 +397,12 @@
397
397
  "hasDynamicHelp": false,
398
398
  "multiple": false,
399
399
  "type": "option"
400
+ },
401
+ "fix": {
402
+ "description": "Interactively fix configuration issues",
403
+ "name": "fix",
404
+ "allowNo": false,
405
+ "type": "boolean"
400
406
  }
401
407
  },
402
408
  "hasDynamicHelp": false,
@@ -868,6 +874,13 @@
868
874
  "<%= config.bin %> <%= command.id %> --host 127.0.0.1 --port 8974"
869
875
  ],
870
876
  "flags": {
877
+ "verbose": {
878
+ "description": "Verbose output",
879
+ "hidden": true,
880
+ "name": "verbose",
881
+ "allowNo": false,
882
+ "type": "boolean"
883
+ },
871
884
  "host": {
872
885
  "char": "h",
873
886
  "description": "The local network interface at which to listen. [default: \"localhost\"]",
@@ -1416,5 +1429,5 @@
1416
1429
  ]
1417
1430
  }
1418
1431
  },
1419
- "version": "12.1.0"
1432
+ "version": "12.2.0"
1420
1433
  }
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": "12.1.0",
4
+ "version": "12.2.0",
5
5
  "author": "Sanity Runtime Team",
6
6
  "type": "module",
7
7
  "license": "MIT",
@@ -83,6 +83,7 @@
83
83
  "@inquirer/prompts": "^8.0.1",
84
84
  "@oclif/core": "^4.8.0",
85
85
  "@oclif/plugin-help": "^6.2.36",
86
+ "@sanity/blueprints": "^0.7.0",
86
87
  "@sanity/blueprints-parser": "^0.3.0",
87
88
  "@sanity/client": "^7.13.0",
88
89
  "adm-zip": "^0.5.16",
@@ -112,7 +113,6 @@
112
113
  "@oclif/test": "^4.1.15",
113
114
  "@playwright/test": "^1.56.1",
114
115
  "@rollup/plugin-node-resolve": "^16.0.3",
115
- "@sanity/blueprints": "^0.6.0",
116
116
  "@sanity/functions": "^1.1.0",
117
117
  "@types/adm-zip": "^0.5.7",
118
118
  "@types/cardinal": "^2.1.1",