@sanity/runtime-cli 14.7.2 → 14.8.1
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.
- package/README.md +150 -99
- package/dist/actions/blueprints/assets.d.ts +4 -3
- package/dist/actions/blueprints/assets.js +69 -101
- package/dist/actions/blueprints/blueprint.d.ts +2 -2
- package/dist/actions/blueprints/blueprint.js +1 -8
- package/dist/actions/blueprints/config.d.ts +2 -2
- package/dist/actions/blueprints/logs-streaming.d.ts +2 -2
- package/dist/actions/blueprints/logs.d.ts +2 -4
- package/dist/actions/blueprints/resources.d.ts +3 -3
- package/dist/actions/blueprints/resources.js +30 -10
- package/dist/actions/blueprints/stacks.d.ts +15 -25
- package/dist/actions/functions/dev.d.ts +1 -1
- package/dist/actions/functions/env/list.d.ts +1 -1
- package/dist/actions/functions/env/remove.d.ts +1 -1
- package/dist/actions/functions/env/update.d.ts +1 -1
- package/dist/actions/functions/logs.d.ts +3 -3
- package/dist/actions/node.d.ts +1 -1
- package/dist/actions/sanity/examples.d.ts +2 -2
- package/dist/actions/sanity/projects.d.ts +7 -13
- package/dist/baseCommands.d.ts +8 -0
- package/dist/baseCommands.js +10 -4
- package/dist/commands/blueprints/config.d.ts +1 -1
- package/dist/commands/blueprints/config.js +5 -13
- package/dist/commands/blueprints/deploy.d.ts +1 -1
- package/dist/commands/blueprints/deploy.js +2 -1
- package/dist/commands/blueprints/destroy.d.ts +1 -1
- package/dist/commands/blueprints/destroy.js +5 -7
- package/dist/commands/blueprints/doctor.js +2 -2
- package/dist/commands/blueprints/info.d.ts +1 -1
- package/dist/commands/blueprints/info.js +2 -1
- package/dist/commands/blueprints/init.js +3 -11
- package/dist/commands/blueprints/plan.d.ts +1 -1
- package/dist/commands/blueprints/plan.js +2 -1
- package/dist/commands/blueprints/promote.d.ts +0 -1
- package/dist/commands/blueprints/promote.js +2 -6
- package/dist/commands/blueprints/stacks.d.ts +1 -1
- package/dist/commands/blueprints/stacks.js +5 -13
- package/dist/commands/functions/build.d.ts +1 -1
- package/dist/commands/functions/build.js +2 -1
- package/dist/commands/functions/env/add.d.ts +1 -1
- package/dist/commands/functions/env/add.js +2 -1
- package/dist/commands/functions/env/list.d.ts +1 -1
- package/dist/commands/functions/env/list.js +2 -1
- package/dist/commands/functions/env/remove.d.ts +1 -1
- package/dist/commands/functions/env/remove.js +2 -1
- package/dist/commands/functions/logs.d.ts +0 -1
- package/dist/commands/functions/logs.js +0 -5
- package/dist/commands/functions/test.d.ts +1 -1
- package/dist/commands/functions/test.js +5 -13
- package/dist/constants.d.ts +2 -1
- package/dist/cores/blueprints/config.js +11 -7
- package/dist/cores/blueprints/deploy.js +62 -74
- package/dist/cores/blueprints/destroy.js +3 -3
- package/dist/cores/blueprints/doctor.js +5 -1
- package/dist/cores/blueprints/info.js +1 -1
- package/dist/cores/blueprints/init.d.ts +4 -3
- package/dist/cores/blueprints/plan.js +7 -1
- package/dist/cores/blueprints/promote.d.ts +0 -1
- package/dist/cores/blueprints/promote.js +4 -4
- package/dist/cores/blueprints/stacks.js +6 -2
- package/dist/cores/functions/add.d.ts +1 -0
- package/dist/cores/functions/add.js +2 -2
- package/dist/cores/functions/build.js +2 -2
- package/dist/cores/functions/env/add.js +1 -1
- package/dist/cores/functions/env/list.js +1 -1
- package/dist/cores/functions/env/remove.js +1 -1
- package/dist/cores/functions/test.js +4 -4
- package/dist/cores/index.d.ts +9 -2
- package/dist/cores/index.js +3 -2
- package/dist/server/app.d.ts +1 -1
- package/dist/server/handlers/invoke.d.ts +1 -1
- package/dist/utils/display/prompt.d.ts +2 -2
- package/dist/utils/display/prompt.js +1 -1
- package/dist/utils/display/resources-formatting.d.ts +2 -2
- package/dist/utils/display/resources-formatting.js +1 -1
- package/dist/utils/functions/fetch-document.d.ts +2 -2
- package/dist/utils/functions/prepare-asset.d.ts +3 -8
- package/dist/utils/functions/prepare-asset.js +2 -2
- package/dist/utils/functions/should-auto-resolve-deps.js +1 -1
- package/dist/utils/functions/should-transpile.js +1 -1
- package/dist/utils/invoke-local.d.ts +1 -7
- package/dist/utils/invoke-local.js +5 -24
- package/dist/utils/logger.d.ts +2 -0
- package/dist/utils/logger.js +2 -0
- package/dist/utils/other/github.d.ts +1 -1
- package/dist/utils/other/npmjs.d.ts +1 -1
- package/dist/utils/traced-fetch.d.ts +1 -1
- package/dist/utils/types.d.ts +21 -80
- package/dist/utils/types.js +6 -14
- package/dist/utils/validate/resource.d.ts +0 -3
- package/dist/utils/validate/resource.js +0 -270
- package/dist/utils/validated-token.d.ts +2 -2
- package/oclif.manifest.json +36 -90
- package/package.json +2 -2
|
@@ -11,30 +11,13 @@ 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 {
|
|
14
|
+
import { isGroqContextOptions, } from './types.js';
|
|
15
15
|
function getChildProcessWrapperPath() {
|
|
16
16
|
return fileURLToPath(new URL('./child-process-wrapper.js', import.meta.url));
|
|
17
17
|
}
|
|
18
18
|
export function sanitizeLogs(logs) {
|
|
19
19
|
return logs.replace(/([a-zA-Z0-9]{10})[a-zA-Z0-9]{65,}/g, '$1**********');
|
|
20
20
|
}
|
|
21
|
-
export const DEFAULT_GROQ_RULE = { on: ['publish'], filter: '', projection: '' };
|
|
22
|
-
export function isDefaultGROQRule(rule) {
|
|
23
|
-
if (!rule)
|
|
24
|
-
return true;
|
|
25
|
-
return (Array.isArray(rule.on) &&
|
|
26
|
-
rule.on.length === DEFAULT_GROQ_RULE.on.length &&
|
|
27
|
-
rule.on.every((v) => DEFAULT_GROQ_RULE.on.includes(v)) &&
|
|
28
|
-
rule.filter === DEFAULT_GROQ_RULE.filter &&
|
|
29
|
-
rule.projection === DEFAULT_GROQ_RULE.projection);
|
|
30
|
-
}
|
|
31
|
-
function getEvent(rule) {
|
|
32
|
-
return {
|
|
33
|
-
on: rule.on || DEFAULT_GROQ_RULE.on,
|
|
34
|
-
filter: rule.filter || DEFAULT_GROQ_RULE.filter,
|
|
35
|
-
projection: rule.projection || DEFAULT_GROQ_RULE.projection,
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
21
|
export async function applyGroqRule(resource, payload, projectId, dataset) {
|
|
39
22
|
const { before, after, payload: data = null } = payload;
|
|
40
23
|
// If there is no rule set return everything
|
|
@@ -42,11 +25,8 @@ export async function applyGroqRule(resource, payload, projectId, dataset) {
|
|
|
42
25
|
return data;
|
|
43
26
|
// default groq rule is: gimme full doc content. otherwise, parse + eval custom rule
|
|
44
27
|
// applying the GROQ filter may result in a slimmer set of documents
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const hasProjection = event.projection?.length;
|
|
48
|
-
const projection = hasProjection ? `${event?.projection}` : '';
|
|
49
|
-
const query = `*[${event?.filter}]${projection}`;
|
|
28
|
+
if (resource.event.filter || resource.event.projection) {
|
|
29
|
+
const query = `*[${resource.event.filter || ''}]${resource.event.projection || ''}`;
|
|
50
30
|
try {
|
|
51
31
|
const rule = groq.parse(query, { mode: 'delta' });
|
|
52
32
|
const sanity = projectId && dataset ? { projectId, dataset } : undefined;
|
|
@@ -91,7 +71,8 @@ export default async function invoke(resource, payload, context, options) {
|
|
|
91
71
|
}
|
|
92
72
|
const { forceColor = true, timeout = 10 } = options;
|
|
93
73
|
let filteredData = {};
|
|
94
|
-
if (
|
|
74
|
+
if (resource.type === 'sanity.function.document' ||
|
|
75
|
+
resource.type === 'sanity.function.media-library.asset') {
|
|
95
76
|
if (!isGroqContextOptions(context)) {
|
|
96
77
|
throw new Error('GROQ-based functions require a context with clientOptions');
|
|
97
78
|
}
|
package/dist/utils/logger.d.ts
CHANGED
|
@@ -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>;
|
package/dist/utils/logger.js
CHANGED
|
@@ -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:
|
|
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:
|
|
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:
|
|
35
|
+
export declare function createTracedFetch(logger: Logger, options?: TracedFetchOptions): typeof fetch;
|
package/dist/utils/types.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { type BlueprintCorsOriginResource, type BlueprintDatasetResource, type BlueprintDocumentWebhookResource, type BlueprintProjectResource, type BlueprintResource, type BlueprintRobotResource, type BlueprintRoleResource } from '@sanity/blueprints';
|
|
1
|
+
import { type BlueprintCorsOriginResource, type BlueprintDatasetResource, type BlueprintDocumentFunctionResource, type BlueprintDocumentWebhookResource, type BlueprintMediaLibraryAssetFunctionResource, type BlueprintProjectResource, type BlueprintResource, type BlueprintRobotResource, type BlueprintRoleResource, type BlueprintScheduledFunctionResource } from '@sanity/blueprints';
|
|
2
2
|
import type { Blueprint } from '@sanity/blueprints-parser';
|
|
3
|
-
import { SANITY_FUNCTION_DOCUMENT, SANITY_FUNCTION_MEDIA_LIBRARY_ASSET, SANITY_FUNCTION_SCHEDULED } from '../constants.js';
|
|
4
3
|
export type ScopeType = 'organization' | 'project';
|
|
5
4
|
/** Result utility type */
|
|
6
5
|
export type Result<T, E = string> = {
|
|
@@ -10,6 +9,14 @@ export type Result<T, E = string> = {
|
|
|
10
9
|
ok: false;
|
|
11
10
|
error: E;
|
|
12
11
|
};
|
|
12
|
+
/** Base shape for API action responses */
|
|
13
|
+
export interface ActionResponse {
|
|
14
|
+
ok: boolean;
|
|
15
|
+
error: string | null;
|
|
16
|
+
}
|
|
17
|
+
/** @internal */
|
|
18
|
+
export type FunctionGroqResource = BlueprintDocumentFunctionResource | BlueprintMediaLibraryAssetFunctionResource;
|
|
19
|
+
export type FunctionResource = FunctionGroqResource | BlueprintScheduledFunctionResource;
|
|
13
20
|
/** @internal */
|
|
14
21
|
export interface AuthParams {
|
|
15
22
|
token: string;
|
|
@@ -17,37 +24,6 @@ export interface AuthParams {
|
|
|
17
24
|
scopeId: string;
|
|
18
25
|
}
|
|
19
26
|
/** @internal */
|
|
20
|
-
export type GroqRule = GroqRuleDocumentFunction | GroqRuleMediaLibraryFunction;
|
|
21
|
-
export interface GroqRuleBase {
|
|
22
|
-
on: Array<string>;
|
|
23
|
-
filter?: string;
|
|
24
|
-
projection?: string;
|
|
25
|
-
}
|
|
26
|
-
interface GroqRuleDocumentFunction extends GroqRuleBase {
|
|
27
|
-
includeDrafts?: boolean;
|
|
28
|
-
includeAllVersions?: boolean;
|
|
29
|
-
resource?: {
|
|
30
|
-
type: 'dataset';
|
|
31
|
-
id: string;
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
interface GroqRuleMediaLibraryFunction extends GroqRuleBase {
|
|
35
|
-
resource: {
|
|
36
|
-
type: 'media-library';
|
|
37
|
-
id: string;
|
|
38
|
-
};
|
|
39
|
-
}
|
|
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;
|
|
49
|
-
}
|
|
50
|
-
type FunctionResourceScheduleEvent = FunctionResourceScheduleExplicitEvent | FunctionResourceScheduleExpressionEvent;
|
|
51
27
|
/** The Blueprint resource as represented in the Blueprints API database */
|
|
52
28
|
export interface BlueprintResourceRecord extends BlueprintResource {
|
|
53
29
|
id: string;
|
|
@@ -58,60 +34,16 @@ export interface BlueprintResourceRecord extends BlueprintResource {
|
|
|
58
34
|
export interface DeployedResource extends BlueprintResourceRecord {
|
|
59
35
|
externalId: string;
|
|
60
36
|
}
|
|
61
|
-
export declare function isLocalFunctionResource(r: BlueprintResource): r is
|
|
62
|
-
export declare function
|
|
63
|
-
export declare function isMediaLibraryAssetFunctionResource<T extends BlueprintResource>(r: T): r is T & FunctionResourceMediaLibraryAsset;
|
|
64
|
-
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
|
-
export declare function isScheduleEvent(e: unknown): e is FunctionResourceScheduleEvent;
|
|
37
|
+
export declare function isLocalFunctionResource(r: BlueprintResource): r is FunctionResource;
|
|
38
|
+
export declare function isScheduleEvent(e: unknown): e is BlueprintScheduledFunctionResource['event'];
|
|
67
39
|
export declare function isCorsOriginResource(r: unknown): r is BlueprintCorsOriginResource;
|
|
68
40
|
export declare function isProjectResource(r: unknown): r is BlueprintProjectResource;
|
|
69
41
|
export declare function isRobotResource(r: unknown): r is BlueprintRobotResource;
|
|
70
42
|
export declare function isRoleResource(r: unknown): r is BlueprintRoleResource;
|
|
71
43
|
export declare function isDatasetResource(r: unknown): r is BlueprintDatasetResource;
|
|
44
|
+
export declare function isStudioResource(r: unknown): boolean;
|
|
72
45
|
export declare function isWebhookResource(r: unknown): r is BlueprintDocumentWebhookResource;
|
|
73
46
|
/** @internal */
|
|
74
|
-
export type FunctionResource = FunctionResourceDocument | FunctionResourceMediaLibraryAsset | FunctionResourceSchedule | FunctionResourceBase;
|
|
75
|
-
export type FunctionGroqResource = FunctionResourceDocument | FunctionResourceMediaLibraryAsset;
|
|
76
|
-
export interface FunctionResourceBase extends BlueprintResource {
|
|
77
|
-
displayName?: string;
|
|
78
|
-
src?: string;
|
|
79
|
-
autoResolveDeps?: boolean;
|
|
80
|
-
transpile?: boolean;
|
|
81
|
-
memory?: number;
|
|
82
|
-
timeout?: number;
|
|
83
|
-
env?: Record<string, string>;
|
|
84
|
-
event?: GroqRuleBase | FunctionResourceScheduleEvent;
|
|
85
|
-
}
|
|
86
|
-
interface FunctionResourceDocument extends FunctionResourceBase {
|
|
87
|
-
type: typeof SANITY_FUNCTION_DOCUMENT;
|
|
88
|
-
event?: GroqRuleDocumentFunction;
|
|
89
|
-
}
|
|
90
|
-
interface FunctionResourceMediaLibraryAsset extends FunctionResourceBase {
|
|
91
|
-
type: typeof SANITY_FUNCTION_MEDIA_LIBRARY_ASSET;
|
|
92
|
-
event: GroqRuleMediaLibraryFunction;
|
|
93
|
-
}
|
|
94
|
-
interface FunctionResourceSchedule extends FunctionResourceBase {
|
|
95
|
-
type: typeof SANITY_FUNCTION_SCHEDULED;
|
|
96
|
-
event: FunctionResourceScheduleEvent;
|
|
97
|
-
}
|
|
98
|
-
export interface CollectionFunction {
|
|
99
|
-
type: typeof SANITY_FUNCTION_DOCUMENT | typeof SANITY_FUNCTION_SCHEDULED | typeof SANITY_FUNCTION_MEDIA_LIBRARY_ASSET;
|
|
100
|
-
src: string;
|
|
101
|
-
name: string;
|
|
102
|
-
displayName?: string;
|
|
103
|
-
event?: GroqRuleBase | FunctionResourceScheduleEvent;
|
|
104
|
-
memory?: number;
|
|
105
|
-
timeout?: number;
|
|
106
|
-
env?: Record<string, string>;
|
|
107
|
-
}
|
|
108
|
-
/** @internal */
|
|
109
|
-
export interface FunctionsCollection extends BlueprintResource {
|
|
110
|
-
type: 'sanity.experimental.functions-collection';
|
|
111
|
-
name: string;
|
|
112
|
-
src?: string;
|
|
113
|
-
functions: Array<FunctionResourceBase>;
|
|
114
|
-
}
|
|
115
47
|
export interface CorsResource extends BlueprintResource {
|
|
116
48
|
origin?: string;
|
|
117
49
|
}
|
|
@@ -258,4 +190,13 @@ export declare interface FetchConfig {
|
|
|
258
190
|
}
|
|
259
191
|
export declare const INSTALLER_OPTIONS: readonly ["npm", "pnpm", "yarn"];
|
|
260
192
|
export type InstallerType = 'npm' | 'pnpm' | 'yarn';
|
|
193
|
+
export type AssetPrepResult = {
|
|
194
|
+
success: true;
|
|
195
|
+
outputPath: string;
|
|
196
|
+
cleanup: () => Promise<void>;
|
|
197
|
+
} | {
|
|
198
|
+
success: false;
|
|
199
|
+
cleanup: () => Promise<void>;
|
|
200
|
+
error: string;
|
|
201
|
+
};
|
|
261
202
|
export {};
|
package/dist/utils/types.js
CHANGED
|
@@ -1,21 +1,9 @@
|
|
|
1
1
|
import { validateCorsOrigin, validateDataset, validateDocumentWebhook, validateProject, validateRobot, validateRole, } from '@sanity/blueprints';
|
|
2
|
-
import {
|
|
3
|
-
// type narrowing with predicate functions
|
|
2
|
+
import { SANITY_FUNCTION_PREFIX } from '../constants.js';
|
|
4
3
|
export function isLocalFunctionResource(r) {
|
|
5
4
|
return r.type.startsWith(SANITY_FUNCTION_PREFIX);
|
|
6
5
|
}
|
|
7
|
-
|
|
8
|
-
return r.type === SANITY_FUNCTION_DOCUMENT;
|
|
9
|
-
}
|
|
10
|
-
export function isMediaLibraryAssetFunctionResource(r) {
|
|
11
|
-
return r.type === SANITY_FUNCTION_MEDIA_LIBRARY_ASSET;
|
|
12
|
-
}
|
|
13
|
-
export function isScheduleFunctionResource(r) {
|
|
14
|
-
return r.type === SANITY_FUNCTION_SCHEDULED;
|
|
15
|
-
}
|
|
16
|
-
export function isLocalFunctionCollection(r) {
|
|
17
|
-
return r.type === 'sanity.experimental.functions-collection';
|
|
18
|
-
}
|
|
6
|
+
// type narrowing with predicate functions
|
|
19
7
|
export function isScheduleEvent(e) {
|
|
20
8
|
return e !== null && typeof e === 'object' && ('hour' in e || 'expression' in e);
|
|
21
9
|
}
|
|
@@ -34,6 +22,10 @@ export function isRoleResource(r) {
|
|
|
34
22
|
export function isDatasetResource(r) {
|
|
35
23
|
return validateDataset(r).length === 0;
|
|
36
24
|
}
|
|
25
|
+
// TODO: update to use blueprints helpers once available
|
|
26
|
+
export function isStudioResource(r) {
|
|
27
|
+
return !!r && typeof r === 'object' && 'type' in r && r.type === 'sanity.studio';
|
|
28
|
+
}
|
|
37
29
|
export function isWebhookResource(r) {
|
|
38
30
|
return validateDocumentWebhook(r).length === 0;
|
|
39
31
|
}
|
|
@@ -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:
|
|
4
|
-
export declare function validTokenOrErrorMessage(logger:
|
|
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
|
}>>;
|