@sanity/runtime-cli 14.7.2 → 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.
- package/README.md +140 -97
- 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 +1 -1
- 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.js +2 -4
- package/dist/commands/blueprints/config.d.ts +1 -1
- package/dist/commands/blueprints/config.js +2 -1
- 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 +2 -1
- 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/plan.d.ts +1 -1
- package/dist/commands/blueprints/plan.js +2 -1
- package/dist/commands/blueprints/promote.js +2 -2
- package/dist/commands/blueprints/stacks.d.ts +1 -1
- package/dist/commands/blueprints/stacks.js +2 -1
- 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 +2 -1
- package/dist/cores/blueprints/config.js +5 -5
- 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.js +4 -1
- package/dist/cores/blueprints/stacks.js +6 -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/functions/fetch-document.d.ts +2 -2
- package/dist/utils/functions/prepare-asset.d.ts +2 -7
- package/dist/utils/functions/prepare-asset.js +2 -2
- 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 +16 -2
- package/dist/utils/types.js +5 -4
- 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 +20 -58
- 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 (
|
|
154
|
+
if (response) {
|
|
155
155
|
log('Response:');
|
|
156
|
-
log(JSON.stringify(
|
|
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 {
|
package/dist/cores/index.d.ts
CHANGED
|
@@ -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:
|
|
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
|
-
/**
|
|
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 & ({
|
package/dist/cores/index.js
CHANGED
|
@@ -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
|
-
|
|
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: {
|
package/dist/server/app.d.ts
CHANGED
|
@@ -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:
|
|
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:
|
|
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:
|
|
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:
|
|
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}
|
|
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({
|
|
@@ -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:
|
|
5
|
-
export declare function fetchAsset(documentId: string, { mediaLibraryId, apiVersion, apiHost, token }: FetchConfig, logger:
|
|
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
|
/**
|
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
|
@@ -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):
|
|
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 {};
|
package/dist/utils/types.js
CHANGED
|
@@ -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:
|
|
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
|
}>>;
|