@sanity/runtime-cli 14.11.0 → 14.12.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 +45 -43
- package/dist/actions/blueprints/blueprint.d.ts +49 -16
- package/dist/actions/blueprints/blueprint.js +97 -139
- package/dist/actions/blueprints/resolve.d.ts +51 -0
- package/dist/actions/blueprints/resolve.js +52 -0
- package/dist/actions/blueprints/resources.js +35 -12
- package/dist/actions/functions/dev.d.ts +1 -2
- package/dist/actions/functions/dev.js +2 -2
- package/dist/baseCommands.d.ts +47 -30
- package/dist/baseCommands.js +187 -72
- package/dist/commands/blueprints/add.d.ts +3 -2
- package/dist/commands/blueprints/add.js +3 -2
- package/dist/commands/blueprints/config.d.ts +3 -2
- package/dist/commands/blueprints/config.js +3 -2
- package/dist/commands/blueprints/deploy.d.ts +3 -2
- package/dist/commands/blueprints/deploy.js +4 -3
- package/dist/commands/blueprints/destroy.d.ts +3 -2
- package/dist/commands/blueprints/destroy.js +3 -2
- package/dist/commands/blueprints/doctor.d.ts +0 -1
- package/dist/commands/blueprints/doctor.js +2 -3
- package/dist/commands/blueprints/info.d.ts +4 -2
- package/dist/commands/blueprints/info.js +6 -3
- package/dist/commands/blueprints/init.d.ts +0 -1
- package/dist/commands/blueprints/init.js +1 -2
- package/dist/commands/blueprints/logs.d.ts +3 -2
- package/dist/commands/blueprints/logs.js +4 -3
- package/dist/commands/blueprints/plan.d.ts +3 -2
- package/dist/commands/blueprints/plan.js +5 -3
- package/dist/commands/blueprints/promote.d.ts +3 -2
- package/dist/commands/blueprints/promote.js +3 -2
- package/dist/commands/blueprints/stacks.d.ts +3 -2
- package/dist/commands/blueprints/stacks.js +3 -2
- package/dist/commands/functions/add.d.ts +3 -2
- package/dist/commands/functions/add.js +4 -3
- package/dist/commands/functions/build.d.ts +3 -2
- package/dist/commands/functions/build.js +3 -2
- package/dist/commands/functions/dev.d.ts +3 -2
- package/dist/commands/functions/dev.js +3 -2
- package/dist/commands/functions/env/add.d.ts +3 -2
- package/dist/commands/functions/env/add.js +3 -2
- package/dist/commands/functions/env/list.d.ts +3 -2
- package/dist/commands/functions/env/list.js +3 -2
- package/dist/commands/functions/env/remove.d.ts +3 -2
- package/dist/commands/functions/env/remove.js +3 -2
- package/dist/commands/functions/logs.d.ts +3 -2
- package/dist/commands/functions/logs.js +4 -3
- package/dist/commands/functions/test.d.ts +3 -2
- package/dist/commands/functions/test.js +3 -2
- package/dist/constants.d.ts +15 -2
- package/dist/constants.js +14 -10
- package/dist/cores/blueprints/config.js +9 -4
- package/dist/cores/blueprints/destroy.js +78 -56
- package/dist/cores/blueprints/doctor.js +19 -5
- package/dist/cores/blueprints/init.js +2 -2
- package/dist/cores/functions/add.js +11 -7
- package/dist/cores/functions/dev.js +1 -1
- package/dist/server/app.d.ts +1 -2
- package/dist/server/app.js +16 -8
- package/dist/server/handlers/invoke.d.ts +1 -2
- package/dist/server/handlers/invoke.js +4 -4
- package/dist/server/static/components/rule-panel.js +8 -10
- package/oclif.manifest.json +503 -73
- package/package.json +2 -2
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { basename, dirname, extname } from 'node:path';
|
|
3
|
-
import { cwd
|
|
3
|
+
import { cwd } from 'node:process';
|
|
4
4
|
import { pathToFileURL } from 'node:url';
|
|
5
5
|
import blueprintParserValidator from '@sanity/blueprints-parser';
|
|
6
6
|
import * as find from 'empathic/find';
|
|
7
7
|
import { createJiti } from 'jiti';
|
|
8
8
|
import { SANITY_FUNCTION_DOCUMENT, SANITY_FUNCTION_MEDIA_LIBRARY_ASSET, SANITY_FUNCTION_SCHEDULED, } from '../../constants.js';
|
|
9
9
|
import { validateResources } from '../../utils/validate/index.js';
|
|
10
|
-
import {
|
|
10
|
+
import { backfillProjectBasedStackId, readConfigFile, } from './config.js';
|
|
11
|
+
import { resolveIds } from './resolve.js';
|
|
11
12
|
const SUPPORTED_FILE_EXTENSIONS = ['.json', '.js', '.mjs', '.ts'];
|
|
12
13
|
let SUPPORTED_FILE_NAMES = SUPPORTED_FILE_EXTENSIONS.map((ext) => `blueprint${ext}`);
|
|
13
14
|
SUPPORTED_FILE_NAMES = [
|
|
@@ -29,7 +30,6 @@ export default defineBlueprint({
|
|
|
29
30
|
`.trim();
|
|
30
31
|
/**
|
|
31
32
|
* Finds the blueprint file in the given path or current working directory
|
|
32
|
-
* @param blueprintPath - The path of the blueprint file or directory
|
|
33
33
|
* @returns The path, file name, and extension of the blueprint file
|
|
34
34
|
*/
|
|
35
35
|
export function findBlueprintFile(blueprintPath) {
|
|
@@ -60,38 +60,29 @@ export function findBlueprintFile(blueprintPath) {
|
|
|
60
60
|
};
|
|
61
61
|
}
|
|
62
62
|
/**
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
* Can infer stackId from projectId if no stackId is provided and legacy ST-<projectId> stacks from launch are used.
|
|
66
|
-
*
|
|
67
|
-
* @param logger The logger instance
|
|
68
|
-
* @param validate Validation options
|
|
69
|
-
* @param blueprintPath - The path of the blueprint file or directory- will search up the directory tree!
|
|
70
|
-
* @returns Known information about the Blueprint, config, and Stack
|
|
63
|
+
* Load a blueprint file from disk: read JSON or import+execute a JS/TS module.
|
|
64
|
+
* Returns the raw blueprint data and the module reference (if dynamic).
|
|
71
65
|
*/
|
|
72
|
-
export async function
|
|
73
|
-
const
|
|
74
|
-
if (!blueprintFile)
|
|
75
|
-
throw Error('Could not find Blueprint file! Use the `blueprints init` command.');
|
|
76
|
-
const { blueprintFilePath: foundFilePath, fileName, extension } = blueprintFile;
|
|
66
|
+
export async function loadBlueprintFile(fileInfo) {
|
|
67
|
+
const { blueprintFilePath, fileName, extension } = fileInfo;
|
|
77
68
|
let rawBlueprint;
|
|
78
69
|
let blueprintModule;
|
|
79
70
|
try {
|
|
80
71
|
switch (extension) {
|
|
81
72
|
case '.json': {
|
|
82
|
-
const blueprintString = readFileSync(
|
|
73
|
+
const blueprintString = readFileSync(blueprintFilePath, 'utf8').toString();
|
|
83
74
|
rawBlueprint = JSON.parse(blueprintString);
|
|
84
75
|
break;
|
|
85
76
|
}
|
|
86
77
|
case '.js':
|
|
87
78
|
case '.mjs': {
|
|
88
|
-
const
|
|
89
|
-
blueprintModule =
|
|
79
|
+
const mod = await import(blueprintFilePath);
|
|
80
|
+
blueprintModule = mod.default;
|
|
90
81
|
break;
|
|
91
82
|
}
|
|
92
83
|
case '.ts': {
|
|
93
|
-
const jiti = createJiti(dirname(
|
|
94
|
-
const modDefault = await jiti.import(pathToFileURL(
|
|
84
|
+
const jiti = createJiti(dirname(blueprintFilePath));
|
|
85
|
+
const modDefault = await jiti.import(pathToFileURL(blueprintFilePath).href, { default: true });
|
|
95
86
|
blueprintModule = modDefault;
|
|
96
87
|
break;
|
|
97
88
|
}
|
|
@@ -102,22 +93,10 @@ export async function readLocalBlueprint(logger, validate, blueprintPath) {
|
|
|
102
93
|
catch (err) {
|
|
103
94
|
throw Error(`Unable to parse Blueprint file: ${fileName}\n${err}`);
|
|
104
95
|
}
|
|
105
|
-
|
|
106
|
-
* Org, Project, Stack IDs can be set...
|
|
107
|
-
* - in the environment,
|
|
108
|
-
* - on the blueprint module,
|
|
109
|
-
* - and in the config file.
|
|
110
|
-
* The precedence is: environment > blueprint module > config file
|
|
111
|
-
*/
|
|
112
|
-
let moduleOrganizationId;
|
|
113
|
-
let moduleProjectId;
|
|
114
|
-
let moduleStackId;
|
|
96
|
+
// Dynamic modules: extract attached properties, then execute to get raw blueprint
|
|
115
97
|
if (blueprintModule) {
|
|
116
98
|
if (typeof blueprintModule === 'function') {
|
|
117
99
|
try {
|
|
118
|
-
moduleOrganizationId = blueprintModule.organizationId;
|
|
119
|
-
moduleProjectId = blueprintModule.projectId;
|
|
120
|
-
moduleStackId = blueprintModule.stackId;
|
|
121
100
|
rawBlueprint = blueprintModule();
|
|
122
101
|
}
|
|
123
102
|
catch {
|
|
@@ -131,89 +110,14 @@ export async function readLocalBlueprint(logger, validate, blueprintPath) {
|
|
|
131
110
|
if (typeof rawBlueprint === 'undefined') {
|
|
132
111
|
throw Error('Unable to read Blueprint');
|
|
133
112
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
blueprintFilePath: foundFilePath,
|
|
142
|
-
logger,
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
catch { }
|
|
146
|
-
}
|
|
147
|
-
const sources = {};
|
|
148
|
-
let organizationId;
|
|
149
|
-
if (envOrganizationId) {
|
|
150
|
-
organizationId = envOrganizationId;
|
|
151
|
-
sources.organizationId = 'env';
|
|
152
|
-
}
|
|
153
|
-
else if (moduleOrganizationId) {
|
|
154
|
-
organizationId = moduleOrganizationId;
|
|
155
|
-
sources.organizationId = 'module';
|
|
156
|
-
}
|
|
157
|
-
else if (blueprintConfig?.organizationId) {
|
|
158
|
-
organizationId = blueprintConfig.organizationId;
|
|
159
|
-
sources.organizationId = 'config';
|
|
160
|
-
}
|
|
161
|
-
let projectId;
|
|
162
|
-
if (envProjectId) {
|
|
163
|
-
projectId = envProjectId;
|
|
164
|
-
sources.projectId = 'env';
|
|
165
|
-
}
|
|
166
|
-
else if (moduleProjectId) {
|
|
167
|
-
projectId = moduleProjectId;
|
|
168
|
-
sources.projectId = 'module';
|
|
169
|
-
}
|
|
170
|
-
else if (blueprintConfig?.projectId) {
|
|
171
|
-
projectId = blueprintConfig.projectId;
|
|
172
|
-
sources.projectId = 'config';
|
|
173
|
-
}
|
|
174
|
-
let stackId;
|
|
175
|
-
if (envStackId) {
|
|
176
|
-
stackId = envStackId;
|
|
177
|
-
sources.stackId = 'env';
|
|
178
|
-
}
|
|
179
|
-
else if (moduleStackId) {
|
|
180
|
-
stackId = moduleStackId;
|
|
181
|
-
sources.stackId = 'module';
|
|
182
|
-
}
|
|
183
|
-
else if (blueprintConfig?.stackId) {
|
|
184
|
-
stackId = blueprintConfig.stackId;
|
|
185
|
-
sources.stackId = 'config';
|
|
186
|
-
}
|
|
187
|
-
// LAUNCH LIMIT: 1 Stack per Project - infer stackId from projectId
|
|
188
|
-
// this code path exists to handle project-based stacks from initial Blueprints/Functions launch
|
|
189
|
-
if (!stackId && projectId) {
|
|
190
|
-
try {
|
|
191
|
-
// try to assume project-based stack if no stackId is provided
|
|
192
|
-
stackId = await backfillProjectBasedStackId({
|
|
193
|
-
blueprintFilePath: foundFilePath,
|
|
194
|
-
projectId,
|
|
195
|
-
logger,
|
|
196
|
-
});
|
|
197
|
-
if (stackId)
|
|
198
|
-
sources.stackId = 'inferred';
|
|
199
|
-
}
|
|
200
|
-
catch {
|
|
201
|
-
// our assumption was wrong, so we'll leave stackId undefined
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
let scopeType;
|
|
205
|
-
let scopeId;
|
|
206
|
-
// Scope is as specific as possible; project > organization
|
|
207
|
-
if (projectId) {
|
|
208
|
-
scopeType = 'project';
|
|
209
|
-
scopeId = projectId;
|
|
210
|
-
}
|
|
211
|
-
else if (organizationId) {
|
|
212
|
-
scopeType = 'organization';
|
|
213
|
-
scopeId = organizationId;
|
|
214
|
-
}
|
|
113
|
+
return { rawBlueprint, module: blueprintModule };
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Run the blueprint parser/validator on raw blueprint data.
|
|
117
|
+
* Optionally validates function resources.
|
|
118
|
+
*/
|
|
119
|
+
export function parseBlueprintContent(rawBlueprint, options) {
|
|
215
120
|
const parserResult = blueprintParserValidator(rawBlueprint, {
|
|
216
|
-
// Prevents the following resources from being referenced.
|
|
217
121
|
invalidReferenceTypes: [
|
|
218
122
|
SANITY_FUNCTION_DOCUMENT,
|
|
219
123
|
SANITY_FUNCTION_MEDIA_LIBRARY_ASSET,
|
|
@@ -222,34 +126,88 @@ export async function readLocalBlueprint(logger, validate, blueprintPath) {
|
|
|
222
126
|
});
|
|
223
127
|
const parsedBlueprint = parserResult.result === 'valid' ? parserResult.blueprint : undefined;
|
|
224
128
|
const errors = parserResult.result !== 'valid' ? parserResult.errors : [];
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
if (validate?.resources) {
|
|
228
|
-
// validate function resources
|
|
229
|
-
errors.push(...validateResources(parsedBlueprint.resources));
|
|
230
|
-
}
|
|
129
|
+
if (parsedBlueprint?.resources && options.validateResources) {
|
|
130
|
+
errors.push(...validateResources(parsedBlueprint.resources));
|
|
231
131
|
}
|
|
232
|
-
//
|
|
233
|
-
// widen from the parser's Resource type assignable to BlueprintResource
|
|
132
|
+
// Widen from the parser's Resource type to BlueprintResource
|
|
234
133
|
const parserResources = parsedBlueprint?.resources ?? [];
|
|
235
|
-
|
|
236
|
-
const validResources = parserResources.filter((r) => !!r.type);
|
|
134
|
+
const resources = parserResources.filter((r) => !!r.type);
|
|
237
135
|
return {
|
|
238
|
-
|
|
136
|
+
parsedBlueprint: parsedBlueprint || rawBlueprint,
|
|
137
|
+
errors,
|
|
138
|
+
resources,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Find, load, and parse a local blueprint file in one step.
|
|
143
|
+
* Does not read the config file or resolve IDs -- callers that need
|
|
144
|
+
* scope/stack information should use `readConfigFile` + `resolveIds` separately.
|
|
145
|
+
*
|
|
146
|
+
* @param blueprintPath Path to a blueprint file or directory containing one
|
|
147
|
+
* @param options.validateResources Whether to validate function resources
|
|
148
|
+
*/
|
|
149
|
+
export async function loadAndParseBlueprint(blueprintPath, options = { validateResources: true }) {
|
|
150
|
+
const fileInfo = findBlueprintFile(blueprintPath);
|
|
151
|
+
if (!fileInfo)
|
|
152
|
+
throw Error('Could not find Blueprint file! Use the `blueprints init` command.');
|
|
153
|
+
const loaded = await loadBlueprintFile(fileInfo);
|
|
154
|
+
const parsed = parseBlueprintContent(loaded.rawBlueprint, options);
|
|
155
|
+
return {
|
|
156
|
+
fileInfo,
|
|
157
|
+
rawBlueprint: loaded.rawBlueprint,
|
|
158
|
+
module: loaded.module,
|
|
159
|
+
...parsed,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Reads the blueprint file from disk and parses it.
|
|
164
|
+
* Resolves IDs from environment > blueprint module > config file.
|
|
165
|
+
* Can infer stackId from projectId for legacy ST-<projectId> stacks.
|
|
166
|
+
* @see {@link loadBlueprintFile} {@link readConfigFile} {@link resolveIds} {@link parseBlueprintContent}
|
|
167
|
+
* @todo deprecate in favor of decomposed functions
|
|
168
|
+
* @returns Known information about the Blueprint, config, and Stack
|
|
169
|
+
*/
|
|
170
|
+
export async function readLocalBlueprint(logger, validate, blueprintPath) {
|
|
171
|
+
const fileInfo = findBlueprintFile(blueprintPath);
|
|
172
|
+
if (!fileInfo)
|
|
173
|
+
throw Error('Could not find Blueprint file! Use the `blueprints init` command.');
|
|
174
|
+
// 1. Load blueprint from disk
|
|
175
|
+
const loaded = await loadBlueprintFile(fileInfo);
|
|
176
|
+
const { rawBlueprint } = loaded;
|
|
177
|
+
// 2. Read the config file
|
|
178
|
+
const blueprintConfig = readConfigFile(fileInfo.blueprintFilePath);
|
|
179
|
+
// 3. Resolve IDs: env > module > config
|
|
180
|
+
const resolved = resolveIds({
|
|
181
|
+
module: loaded.module,
|
|
182
|
+
config: blueprintConfig,
|
|
183
|
+
});
|
|
184
|
+
// 4. Legacy stack ID inference: ST-<projectId>
|
|
185
|
+
let { stackId } = resolved;
|
|
186
|
+
if (!stackId && resolved.projectId) {
|
|
187
|
+
try {
|
|
188
|
+
stackId = await backfillProjectBasedStackId({
|
|
189
|
+
blueprintFilePath: fileInfo.blueprintFilePath,
|
|
190
|
+
projectId: resolved.projectId,
|
|
191
|
+
logger,
|
|
192
|
+
});
|
|
193
|
+
if (stackId)
|
|
194
|
+
resolved.sources.stackId = 'inferred';
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
// assumption was wrong; leave stackId undefined
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
// 5. Parse + validate blueprint content
|
|
201
|
+
const parsed = parseBlueprintContent(rawBlueprint, {
|
|
202
|
+
validateResources: validate.resources,
|
|
203
|
+
});
|
|
204
|
+
return {
|
|
205
|
+
fileInfo,
|
|
239
206
|
blueprintConfig,
|
|
240
207
|
rawBlueprint,
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
scopeId,
|
|
245
|
-
organizationId,
|
|
246
|
-
projectId,
|
|
247
|
-
stackId,
|
|
248
|
-
sources,
|
|
249
|
-
/** @deprecated */
|
|
250
|
-
configPath: blueprintConfig?.configPath,
|
|
251
|
-
// fallback to the raw blueprint if the parser found errors
|
|
252
|
-
parsedBlueprint: parsedBlueprint || rawBlueprint,
|
|
208
|
+
...parsed,
|
|
209
|
+
...resolved,
|
|
210
|
+
stackId: stackId ?? resolved.stackId,
|
|
253
211
|
};
|
|
254
212
|
}
|
|
255
213
|
export function writeBlueprintToDisk({ blueprintFilePath, jsonContent = JSON_BLUEPRINT_CONTENT, }) {
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { ScopeType } from '../../utils/types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Source of a resolved config value.
|
|
4
|
+
* - flags: from CLI flags like `--project-id`
|
|
5
|
+
* - env: from the environment - usually CLI var like `SANITY_ORGANIZATION_ID`
|
|
6
|
+
* - module: from the blueprint module - undocumented escape hatch
|
|
7
|
+
* - config: from the config file - .sanity/blueprint.config.json most common
|
|
8
|
+
* - inferred: legacy `ST-<projectId>` stacks from launch
|
|
9
|
+
*/
|
|
10
|
+
export type ConfigSource = 'flags' | 'env' | 'module' | 'config' | 'inferred';
|
|
11
|
+
/** A set of optional IDs from a single source. */
|
|
12
|
+
export interface IdValues {
|
|
13
|
+
organizationId?: string;
|
|
14
|
+
projectId?: string;
|
|
15
|
+
stackId?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Sources to resolve IDs from, in descending priority.
|
|
19
|
+
* Each key maps to a source; the first non-empty value wins.
|
|
20
|
+
*
|
|
21
|
+
* - `env` defaults to reading SANITY_* process.env vars
|
|
22
|
+
* - `module` and `config` accept any object with `{organizationId?, projectId?, stackId?}`
|
|
23
|
+
*/
|
|
24
|
+
export interface IdSources {
|
|
25
|
+
flags?: IdValues | null;
|
|
26
|
+
env?: IdValues | null;
|
|
27
|
+
module?: IdValues | null;
|
|
28
|
+
config?: IdValues | null;
|
|
29
|
+
}
|
|
30
|
+
export interface ResolvedIds {
|
|
31
|
+
organizationId?: string;
|
|
32
|
+
projectId?: string;
|
|
33
|
+
stackId?: string;
|
|
34
|
+
scopeType?: ScopeType;
|
|
35
|
+
scopeId?: string;
|
|
36
|
+
sources: {
|
|
37
|
+
organizationId?: ConfigSource;
|
|
38
|
+
projectId?: ConfigSource;
|
|
39
|
+
stackId?: ConfigSource;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Resolve organization, project, and stack IDs from a prioritized set of sources.
|
|
44
|
+
* Precedence: flags > env > module > config.
|
|
45
|
+
* Derives scopeType/scopeId from the resolved IDs (project > organization).
|
|
46
|
+
*
|
|
47
|
+
* Env vars are read from process.env by default (SANITY_ORGANIZATION_ID,
|
|
48
|
+
* SANITY_PROJECT_ID, SANITY_BLUEPRINT_STACK_ID). Pass `env` explicitly to
|
|
49
|
+
* override, or `null` to skip.
|
|
50
|
+
*/
|
|
51
|
+
export declare function resolveIds(sources?: IdSources): ResolvedIds;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { env as processEnv } from 'node:process';
|
|
2
|
+
/**
|
|
3
|
+
* Resolve organization, project, and stack IDs from a prioritized set of sources.
|
|
4
|
+
* Precedence: flags > env > module > config.
|
|
5
|
+
* Derives scopeType/scopeId from the resolved IDs (project > organization).
|
|
6
|
+
*
|
|
7
|
+
* Env vars are read from process.env by default (SANITY_ORGANIZATION_ID,
|
|
8
|
+
* SANITY_PROJECT_ID, SANITY_BLUEPRINT_STACK_ID). Pass `env` explicitly to
|
|
9
|
+
* override, or `null` to skip.
|
|
10
|
+
*/
|
|
11
|
+
export function resolveIds(sources = {}) {
|
|
12
|
+
const envValues = sources.env !== undefined
|
|
13
|
+
? sources.env
|
|
14
|
+
: {
|
|
15
|
+
organizationId: processEnv.SANITY_ORGANIZATION_ID,
|
|
16
|
+
projectId: processEnv.SANITY_PROJECT_ID,
|
|
17
|
+
stackId: processEnv.SANITY_BLUEPRINT_STACK_ID,
|
|
18
|
+
};
|
|
19
|
+
const ordered = [
|
|
20
|
+
{ source: 'flags', values: sources.flags },
|
|
21
|
+
{ source: 'env', values: envValues },
|
|
22
|
+
{ source: 'module', values: sources.module },
|
|
23
|
+
{ source: 'config', values: sources.config },
|
|
24
|
+
];
|
|
25
|
+
const result = { sources: {} };
|
|
26
|
+
for (const { source, values } of ordered) {
|
|
27
|
+
if (values === null || values === undefined)
|
|
28
|
+
continue;
|
|
29
|
+
if (!result.organizationId && values.organizationId) {
|
|
30
|
+
result.organizationId = values.organizationId;
|
|
31
|
+
result.sources.organizationId = source;
|
|
32
|
+
}
|
|
33
|
+
if (!result.projectId && values.projectId) {
|
|
34
|
+
result.projectId = values.projectId;
|
|
35
|
+
result.sources.projectId = source;
|
|
36
|
+
}
|
|
37
|
+
if (!result.stackId && values.stackId) {
|
|
38
|
+
result.stackId = values.stackId;
|
|
39
|
+
result.sources.stackId = source;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// Scope is as specific as possible; project > organization
|
|
43
|
+
if (result.projectId) {
|
|
44
|
+
result.scopeType = 'project';
|
|
45
|
+
result.scopeId = result.projectId;
|
|
46
|
+
}
|
|
47
|
+
else if (result.organizationId) {
|
|
48
|
+
result.scopeType = 'organization';
|
|
49
|
+
result.scopeId = result.organizationId;
|
|
50
|
+
}
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
@@ -2,6 +2,7 @@ import { spawn } from 'node:child_process';
|
|
|
2
2
|
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { dirname, join } from 'node:path';
|
|
4
4
|
import { cwd } from 'node:process';
|
|
5
|
+
import { MAP_EVENT_TO_FUNCTION_TYPE, SANITY_FUNCTION_DOCUMENT, SANITY_FUNCTION_MEDIA_LIBRARY_ASSET, SANITY_FUNCTION_SCHEDULED, SANITY_FUNCTION_SYNC_TAG_INVALIDATE, } from '../../constants.js';
|
|
5
6
|
import { styleText } from '../../utils/style-text.js';
|
|
6
7
|
import { writeOrUpdateNodeDependency } from '../node.js';
|
|
7
8
|
import { addResourceToBlueprint } from './blueprint.js';
|
|
@@ -21,6 +22,20 @@ export const handler = scheduledEventHandler(async ({ context }) => {
|
|
|
21
22
|
const time = new Date().toLocaleTimeString()
|
|
22
23
|
console.log(\`Your scheduled Sanity Function was called at \${time}\`)
|
|
23
24
|
})`;
|
|
25
|
+
const DEFAULT_SYNC_TAG_HELPER_FUNCTION_TEMPLATE = /*ts*/ `import { syncTagInvalidateEventHandler } from '@sanity/functions'
|
|
26
|
+
|
|
27
|
+
export const handler = syncTagInvalidateEventHandler(async ({ context, event, done }) => {
|
|
28
|
+
const time = new Date().toLocaleTimeString()
|
|
29
|
+
console.log(\`Your sync tag invalidate Sanity Function was called at \${time}\`)
|
|
30
|
+
// TODO: add code to do something with the invalidated sync tags provided to you in \`event.data.syncTags\`
|
|
31
|
+
try {
|
|
32
|
+
// notify Sanity that you have completed invalidation
|
|
33
|
+
const response = await done(event.data.syncTags)
|
|
34
|
+
console.log('Invalidation complete, Sanity responded with an HTTP', response.status)
|
|
35
|
+
} catch (e) {
|
|
36
|
+
console.error('Error invoking Sanity invalidation done endpoint!', e)
|
|
37
|
+
}
|
|
38
|
+
})`;
|
|
24
39
|
/**
|
|
25
40
|
* Creates a new function resource file and adds it to the blueprint
|
|
26
41
|
*/
|
|
@@ -47,16 +62,18 @@ export async function createFunctionResource(options, logger) {
|
|
|
47
62
|
throw Error(`Unsupported language: ${lang}`);
|
|
48
63
|
// type looks like 'document-publish', 'media-library-asset-delete' or 'scheduled-function'
|
|
49
64
|
// and we are guaranteed to have the same leading words (typeName below) for all provided type strings (via guards in the call site for this method).
|
|
50
|
-
|
|
51
|
-
const typeName = type[0].substring(0, type[0].lastIndexOf('-'));
|
|
65
|
+
const functionType = MAP_EVENT_TO_FUNCTION_TYPE[type[0]];
|
|
52
66
|
// Create index.<lang> with default template
|
|
53
67
|
const indexPath = join(functionDir, `index.${lang}`);
|
|
54
68
|
let template = DEFAULT_FUNCTION_TEMPLATE;
|
|
55
69
|
if (addHelpers) {
|
|
56
|
-
switch (
|
|
57
|
-
case
|
|
70
|
+
switch (functionType) {
|
|
71
|
+
case SANITY_FUNCTION_SCHEDULED:
|
|
58
72
|
template = DEFAULT_SCHEDULED_HELPER_FUNCTION_TEMPLATE;
|
|
59
73
|
break;
|
|
74
|
+
case SANITY_FUNCTION_SYNC_TAG_INVALIDATE:
|
|
75
|
+
template = DEFAULT_SYNC_TAG_HELPER_FUNCTION_TEMPLATE;
|
|
76
|
+
break;
|
|
60
77
|
default:
|
|
61
78
|
template = DEFAULT_HELPER_FUNCTION_TEMPLATE;
|
|
62
79
|
break;
|
|
@@ -80,39 +97,45 @@ export async function createFunctionResource(options, logger) {
|
|
|
80
97
|
const eventsOn = type.map((t) => t.substring(t.lastIndexOf('-') + 1));
|
|
81
98
|
// Create resource definition
|
|
82
99
|
let resourceJson;
|
|
83
|
-
switch (
|
|
84
|
-
case
|
|
100
|
+
switch (functionType) {
|
|
101
|
+
case SANITY_FUNCTION_DOCUMENT:
|
|
85
102
|
resourceJson = {
|
|
86
103
|
name,
|
|
87
104
|
src: `functions/${name}`,
|
|
88
|
-
type:
|
|
105
|
+
type: SANITY_FUNCTION_DOCUMENT,
|
|
89
106
|
event: {
|
|
90
107
|
on: eventsOn,
|
|
91
108
|
},
|
|
92
109
|
};
|
|
93
110
|
break;
|
|
94
|
-
case
|
|
111
|
+
case SANITY_FUNCTION_MEDIA_LIBRARY_ASSET:
|
|
95
112
|
resourceJson = {
|
|
96
113
|
name,
|
|
97
114
|
src: `functions/${name}`,
|
|
98
|
-
type:
|
|
115
|
+
type: SANITY_FUNCTION_MEDIA_LIBRARY_ASSET,
|
|
99
116
|
event: {
|
|
100
117
|
on: eventsOn,
|
|
101
118
|
resource: { type: 'media-library', id: 'my-media-library-id' },
|
|
102
119
|
},
|
|
103
120
|
};
|
|
104
121
|
break;
|
|
105
|
-
case
|
|
122
|
+
case SANITY_FUNCTION_SCHEDULED:
|
|
106
123
|
resourceJson = {
|
|
107
124
|
name,
|
|
108
125
|
src: `functions/${name}`,
|
|
109
|
-
type:
|
|
126
|
+
type: SANITY_FUNCTION_SCHEDULED,
|
|
110
127
|
event: {
|
|
111
128
|
expression: '0 0 * * *',
|
|
112
129
|
},
|
|
113
130
|
};
|
|
114
131
|
break;
|
|
115
|
-
|
|
132
|
+
case SANITY_FUNCTION_SYNC_TAG_INVALIDATE:
|
|
133
|
+
resourceJson = {
|
|
134
|
+
name,
|
|
135
|
+
src: `functions/${name}`,
|
|
136
|
+
type: SANITY_FUNCTION_SYNC_TAG_INVALIDATE,
|
|
137
|
+
};
|
|
138
|
+
break;
|
|
116
139
|
}
|
|
117
140
|
if (!resourceJson) {
|
|
118
141
|
throw new Error('Could not create function resource based on selections');
|
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
import type { Logger } from '../../utils/logger.js';
|
|
2
1
|
import type { InvokeExecutionOptions } from '../../utils/types.js';
|
|
3
|
-
export declare function dev(host: string, port: number,
|
|
2
|
+
export declare function dev(host: string, port: number, validateResources: boolean, executionOptions?: Partial<InvokeExecutionOptions>): Promise<void>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { app } from '../../server/app.js';
|
|
2
|
-
export async function dev(host, port,
|
|
3
|
-
app(host, Number(port),
|
|
2
|
+
export async function dev(host, port, validateResources, executionOptions) {
|
|
3
|
+
app(host, Number(port), validateResources, executionOptions);
|
|
4
4
|
}
|
package/dist/baseCommands.d.ts
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import type { Interfaces } from '@oclif/core';
|
|
2
2
|
import { Command } from '@oclif/core';
|
|
3
|
-
import type
|
|
3
|
+
import { type ReadBlueprintResult } from './actions/blueprints/blueprint.js';
|
|
4
4
|
import type { CoreResult } from './cores/index.js';
|
|
5
5
|
import type { AuthParams, ScopeType, Stack } from './utils/types.js';
|
|
6
6
|
export type Flags<T extends typeof Command> = Interfaces.InferredFlags<(typeof RuntimeCommand)['baseFlags'] & T['flags']>;
|
|
7
7
|
export type Args<T extends typeof Command> = Interfaces.InferredArgs<T['args']>;
|
|
8
|
+
export declare const pathFlagConfig: {
|
|
9
|
+
description: string;
|
|
10
|
+
env: string;
|
|
11
|
+
aliases: string[];
|
|
12
|
+
char: "p";
|
|
13
|
+
};
|
|
8
14
|
export declare const baseFlags: {
|
|
9
15
|
json: Interfaces.BooleanFlag<boolean>;
|
|
10
16
|
path: Interfaces.OptionFlag<string | undefined, Interfaces.CustomOptions>;
|
|
@@ -12,7 +18,9 @@ export declare const baseFlags: {
|
|
|
12
18
|
'validate-resources': Interfaces.BooleanFlag<boolean>;
|
|
13
19
|
verbose: Interfaces.BooleanFlag<boolean>;
|
|
14
20
|
};
|
|
15
|
-
export declare const
|
|
21
|
+
export declare const stackFlagConfig: {
|
|
22
|
+
description: string;
|
|
23
|
+
};
|
|
16
24
|
export declare const projectIdFlagConfig: {
|
|
17
25
|
description: string;
|
|
18
26
|
aliases: string[];
|
|
@@ -21,11 +29,6 @@ export declare const organizationIdFlagConfig: {
|
|
|
21
29
|
description: string;
|
|
22
30
|
aliases: string[];
|
|
23
31
|
};
|
|
24
|
-
/**
|
|
25
|
-
* Unhides a flag by setting its hidden property to false
|
|
26
|
-
* Also makes oclif's types happy when destructuring the flag
|
|
27
|
-
*/
|
|
28
|
-
export declare function unhide<T>(flag: T): T;
|
|
29
32
|
/**
|
|
30
33
|
* Guarantees flags and args.
|
|
31
34
|
* Also centralizes baseFlags and enables oclif's built-in --json for all subclasses.
|
|
@@ -69,40 +72,54 @@ export declare abstract class RuntimeCommand<T extends typeof Command> extends C
|
|
|
69
72
|
}): Promise<unknown>;
|
|
70
73
|
}
|
|
71
74
|
/**
|
|
72
|
-
*
|
|
73
|
-
*
|
|
75
|
+
* Context a command can declare it needs.
|
|
76
|
+
* The base class resolves each from the best available source during init().
|
|
77
|
+
* Dependencies are resolved implicitly -- declaring 'deployedStack' implies
|
|
78
|
+
* token, scope, and stackId without listing them.
|
|
79
|
+
*
|
|
80
|
+
* - token: Authenticated API token
|
|
81
|
+
* - blueprint: Parsed local blueprint file (filesystem)
|
|
82
|
+
* - scope: scopeType + scopeId (from flags, env, config file, or blueprint)
|
|
83
|
+
* - stackId: stackId (from flags, env, config file, blueprint, or inferred)
|
|
84
|
+
* - deployedStack: Remote Stack object fetched from the API (implies token, scope, stackId)
|
|
85
|
+
*/
|
|
86
|
+
export type Need = 'token' | 'blueprint' | 'scope' | 'stackId' | 'deployedStack';
|
|
87
|
+
/**
|
|
88
|
+
* Base command that resolves context declaratively from a `static needs` array.
|
|
89
|
+
* Commands declare what they *directly use*; the base class resolves transitive
|
|
90
|
+
* dependencies automatically (e.g. 'deployedStack' implies token, scope, stackId).
|
|
91
|
+
*
|
|
92
|
+
* When a command needs only remote context (e.g. `['deployedStack']`),
|
|
93
|
+
* no local blueprint file is required -- flags and config are sufficient.
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* ```ts
|
|
97
|
+
* class InfoCommand extends ResolvedCommand<typeof InfoCommand> {
|
|
98
|
+
* static needs = ['deployedStack'] as const
|
|
99
|
+
* // token, scope, stackId resolved automatically
|
|
100
|
+
* }
|
|
101
|
+
* ```
|
|
102
|
+
*
|
|
74
103
|
* @extends RuntimeCommand
|
|
75
104
|
*/
|
|
76
|
-
export declare abstract class
|
|
77
|
-
|
|
78
|
-
protected blueprint: ReadBlueprintResult;
|
|
105
|
+
export declare abstract class ResolvedCommand<T extends typeof Command> extends RuntimeCommand<T> {
|
|
106
|
+
static needs: readonly Need[];
|
|
79
107
|
static baseFlags: {
|
|
108
|
+
stack: Interfaces.OptionFlag<string | undefined, Interfaces.CustomOptions>;
|
|
109
|
+
'project-id': Interfaces.OptionFlag<string | undefined, Interfaces.CustomOptions>;
|
|
110
|
+
'organization-id': Interfaces.OptionFlag<string | undefined, Interfaces.CustomOptions>;
|
|
80
111
|
json: Interfaces.BooleanFlag<boolean>;
|
|
81
112
|
path: Interfaces.OptionFlag<string | undefined, Interfaces.CustomOptions>;
|
|
82
113
|
trace: Interfaces.BooleanFlag<boolean>;
|
|
83
114
|
'validate-resources': Interfaces.BooleanFlag<boolean>;
|
|
84
115
|
verbose: Interfaces.BooleanFlag<boolean>;
|
|
85
116
|
};
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Guarantees flags, args, sanityToken, blueprint, scopeType, scopeId, stackId, auth, and deployedStack.
|
|
90
|
-
* If scope or stack is missing, the command exits with an error
|
|
91
|
-
* @extends LocalBlueprintCommand
|
|
92
|
-
*/
|
|
93
|
-
export declare abstract class DeployedStackCommand<T extends typeof Command> extends LocalBlueprintCommand<T> {
|
|
94
|
-
protected auth: AuthParams;
|
|
95
|
-
protected deployedStack: Stack;
|
|
117
|
+
protected sanityToken: string;
|
|
118
|
+
protected blueprint: ReadBlueprintResult;
|
|
96
119
|
protected scopeType: ScopeType;
|
|
97
120
|
protected scopeId: string;
|
|
98
121
|
protected stackId: string;
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
json: Interfaces.BooleanFlag<boolean>;
|
|
102
|
-
path: Interfaces.OptionFlag<string | undefined, Interfaces.CustomOptions>;
|
|
103
|
-
trace: Interfaces.BooleanFlag<boolean>;
|
|
104
|
-
'validate-resources': Interfaces.BooleanFlag<boolean>;
|
|
105
|
-
verbose: Interfaces.BooleanFlag<boolean>;
|
|
106
|
-
};
|
|
122
|
+
protected auth: AuthParams;
|
|
123
|
+
protected deployedStack: Stack;
|
|
107
124
|
init(): Promise<void>;
|
|
108
125
|
}
|