@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.
Files changed (63) hide show
  1. package/README.md +45 -43
  2. package/dist/actions/blueprints/blueprint.d.ts +49 -16
  3. package/dist/actions/blueprints/blueprint.js +97 -139
  4. package/dist/actions/blueprints/resolve.d.ts +51 -0
  5. package/dist/actions/blueprints/resolve.js +52 -0
  6. package/dist/actions/blueprints/resources.js +35 -12
  7. package/dist/actions/functions/dev.d.ts +1 -2
  8. package/dist/actions/functions/dev.js +2 -2
  9. package/dist/baseCommands.d.ts +47 -30
  10. package/dist/baseCommands.js +187 -72
  11. package/dist/commands/blueprints/add.d.ts +3 -2
  12. package/dist/commands/blueprints/add.js +3 -2
  13. package/dist/commands/blueprints/config.d.ts +3 -2
  14. package/dist/commands/blueprints/config.js +3 -2
  15. package/dist/commands/blueprints/deploy.d.ts +3 -2
  16. package/dist/commands/blueprints/deploy.js +4 -3
  17. package/dist/commands/blueprints/destroy.d.ts +3 -2
  18. package/dist/commands/blueprints/destroy.js +3 -2
  19. package/dist/commands/blueprints/doctor.d.ts +0 -1
  20. package/dist/commands/blueprints/doctor.js +2 -3
  21. package/dist/commands/blueprints/info.d.ts +4 -2
  22. package/dist/commands/blueprints/info.js +6 -3
  23. package/dist/commands/blueprints/init.d.ts +0 -1
  24. package/dist/commands/blueprints/init.js +1 -2
  25. package/dist/commands/blueprints/logs.d.ts +3 -2
  26. package/dist/commands/blueprints/logs.js +4 -3
  27. package/dist/commands/blueprints/plan.d.ts +3 -2
  28. package/dist/commands/blueprints/plan.js +5 -3
  29. package/dist/commands/blueprints/promote.d.ts +3 -2
  30. package/dist/commands/blueprints/promote.js +3 -2
  31. package/dist/commands/blueprints/stacks.d.ts +3 -2
  32. package/dist/commands/blueprints/stacks.js +3 -2
  33. package/dist/commands/functions/add.d.ts +3 -2
  34. package/dist/commands/functions/add.js +4 -3
  35. package/dist/commands/functions/build.d.ts +3 -2
  36. package/dist/commands/functions/build.js +3 -2
  37. package/dist/commands/functions/dev.d.ts +3 -2
  38. package/dist/commands/functions/dev.js +3 -2
  39. package/dist/commands/functions/env/add.d.ts +3 -2
  40. package/dist/commands/functions/env/add.js +3 -2
  41. package/dist/commands/functions/env/list.d.ts +3 -2
  42. package/dist/commands/functions/env/list.js +3 -2
  43. package/dist/commands/functions/env/remove.d.ts +3 -2
  44. package/dist/commands/functions/env/remove.js +3 -2
  45. package/dist/commands/functions/logs.d.ts +3 -2
  46. package/dist/commands/functions/logs.js +4 -3
  47. package/dist/commands/functions/test.d.ts +3 -2
  48. package/dist/commands/functions/test.js +3 -2
  49. package/dist/constants.d.ts +15 -2
  50. package/dist/constants.js +14 -10
  51. package/dist/cores/blueprints/config.js +9 -4
  52. package/dist/cores/blueprints/destroy.js +78 -56
  53. package/dist/cores/blueprints/doctor.js +19 -5
  54. package/dist/cores/blueprints/init.js +2 -2
  55. package/dist/cores/functions/add.js +11 -7
  56. package/dist/cores/functions/dev.js +1 -1
  57. package/dist/server/app.d.ts +1 -2
  58. package/dist/server/app.js +16 -8
  59. package/dist/server/handlers/invoke.d.ts +1 -2
  60. package/dist/server/handlers/invoke.js +4 -4
  61. package/dist/server/static/components/rule-panel.js +8 -10
  62. package/oclif.manifest.json +503 -73
  63. 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, env } from 'node:process';
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 { backfillOrganizationId, backfillProjectBasedStackId, readConfigFile, } from './config.js';
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
- * Reads the blueprint file from disk and parses it.
64
- * Overrides config file values with shell environment variables.
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 readLocalBlueprint(logger, validate, blueprintPath) {
73
- const blueprintFile = findBlueprintFile(blueprintPath);
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(foundFilePath, 'utf8').toString();
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 module = await import(foundFilePath);
89
- blueprintModule = module.default;
79
+ const mod = await import(blueprintFilePath);
80
+ blueprintModule = mod.default;
90
81
  break;
91
82
  }
92
83
  case '.ts': {
93
- const jiti = createJiti(dirname(foundFilePath));
94
- const modDefault = await jiti.import(pathToFileURL(foundFilePath).href, { default: true });
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
- const { SANITY_ORGANIZATION_ID: envOrganizationId, SANITY_PROJECT_ID: envProjectId, SANITY_BLUEPRINT_STACK_ID: envStackId, } = env;
135
- const blueprintConfig = readConfigFile(foundFilePath);
136
- // passively heal config on disk by getting organizationId from projectId
137
- if (blueprintConfig?.projectId && !blueprintConfig.organizationId) {
138
- try {
139
- await backfillOrganizationId({
140
- projectId: blueprintConfig.projectId,
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
- // resource validation
226
- if (parsedBlueprint?.resources) {
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
- // Ceremony to cross from parser's Resource type to BlueprintResource
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
- // filter to valid resources typed as BlueprintResource
236
- const validResources = parserResources.filter((r) => !!r.type);
134
+ const resources = parserResources.filter((r) => !!r.type);
237
135
  return {
238
- fileInfo: { blueprintFilePath: foundFilePath, fileName, extension },
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
- resources: validResources,
242
- errors,
243
- scopeType,
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
- // TODO: this substring convention is brittle - consider mapping EVENT_* constants to typeName values explicitly instead of relying on string splitting.
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 (typeName) {
57
- case 'scheduled':
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 (typeName) {
84
- case 'document':
100
+ switch (functionType) {
101
+ case SANITY_FUNCTION_DOCUMENT:
85
102
  resourceJson = {
86
103
  name,
87
104
  src: `functions/${name}`,
88
- type: 'sanity.function.document',
105
+ type: SANITY_FUNCTION_DOCUMENT,
89
106
  event: {
90
107
  on: eventsOn,
91
108
  },
92
109
  };
93
110
  break;
94
- case 'media-library-asset':
111
+ case SANITY_FUNCTION_MEDIA_LIBRARY_ASSET:
95
112
  resourceJson = {
96
113
  name,
97
114
  src: `functions/${name}`,
98
- type: 'sanity.function.media-library.asset',
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 'scheduled':
122
+ case SANITY_FUNCTION_SCHEDULED:
106
123
  resourceJson = {
107
124
  name,
108
125
  src: `functions/${name}`,
109
- type: 'sanity.function.cron',
126
+ type: SANITY_FUNCTION_SCHEDULED,
110
127
  event: {
111
128
  expression: '0 0 * * *',
112
129
  },
113
130
  };
114
131
  break;
115
- // TODO: add sync tag invalidate funx
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, logger: Logger, validateResources: boolean, executionOptions?: Partial<InvokeExecutionOptions>): Promise<void>;
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, logger, validateResources, executionOptions) {
3
- app(host, Number(port), logger, validateResources, executionOptions);
2
+ export async function dev(host, port, validateResources, executionOptions) {
3
+ app(host, Number(port), validateResources, executionOptions);
4
4
  }
@@ -1,10 +1,16 @@
1
1
  import type { Interfaces } from '@oclif/core';
2
2
  import { Command } from '@oclif/core';
3
- import type { ReadBlueprintResult } from './actions/blueprints/blueprint.js';
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 stackFlag: Interfaces.OptionFlag<string | undefined, Interfaces.CustomOptions>;
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
- * Guarantees flags, args, sanityToken, and blueprint.
73
- * Blueprint parser errors are logged and the command exits with an error
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 LocalBlueprintCommand<T extends typeof Command> extends RuntimeCommand<T> {
77
- protected sanityToken: string;
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
- init(): Promise<void>;
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
- static baseFlags: {
100
- stack: Interfaces.OptionFlag<string | undefined, Interfaces.CustomOptions>;
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
  }