@sanity/runtime-cli 12.0.1 → 12.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/README.md +25 -24
  2. package/dist/actions/blueprints/blueprint.d.ts +5 -4
  3. package/dist/actions/blueprints/config.d.ts +7 -6
  4. package/dist/actions/blueprints/config.js +8 -6
  5. package/dist/actions/blueprints/stacks.d.ts +8 -4
  6. package/dist/actions/blueprints/stacks.js +6 -32
  7. package/dist/commands/blueprints/destroy.js +2 -0
  8. package/dist/commands/blueprints/doctor.d.ts +1 -0
  9. package/dist/commands/blueprints/doctor.js +4 -0
  10. package/dist/commands/blueprints/info.js +1 -1
  11. package/dist/commands/blueprints/init.js +1 -1
  12. package/dist/commands/blueprints/stacks.js +1 -1
  13. package/dist/commands/functions/dev.d.ts +2 -2
  14. package/dist/commands/functions/dev.js +3 -2
  15. package/dist/config.js +12 -2
  16. package/dist/constants.d.ts +7 -0
  17. package/dist/constants.js +7 -0
  18. package/dist/cores/blueprints/config.js +36 -39
  19. package/dist/cores/blueprints/doctor.d.ts +1 -0
  20. package/dist/cores/blueprints/doctor.js +36 -5
  21. package/dist/cores/blueprints/info.js +2 -2
  22. package/dist/cores/blueprints/init.d.ts +2 -0
  23. package/dist/cores/blueprints/init.js +38 -14
  24. package/dist/cores/functions/env/add.js +2 -2
  25. package/dist/cores/functions/env/list.js +2 -2
  26. package/dist/cores/functions/env/remove.js +2 -2
  27. package/dist/cores/functions/logs.js +2 -2
  28. package/dist/cores/functions/test.js +31 -23
  29. package/dist/server/app.js +32 -21
  30. package/dist/server/handlers/invoke.d.ts +2 -2
  31. package/dist/server/handlers/invoke.js +2 -2
  32. package/dist/server/static/components/api-base.js +3 -0
  33. package/dist/server/static/components/app.css +120 -95
  34. package/dist/server/static/components/clear-button.js +1 -1
  35. package/dist/server/static/components/console-panel.js +6 -6
  36. package/dist/server/static/components/fetch-button.js +1 -1
  37. package/dist/server/static/components/filter-api-version.js +3 -3
  38. package/dist/server/static/components/filter-document-id.js +5 -5
  39. package/dist/server/static/components/filter-with-token.js +4 -4
  40. package/dist/server/static/components/filters.js +2 -2
  41. package/dist/server/static/components/function-list.js +14 -5
  42. package/dist/server/static/components/help-button.js +4 -1
  43. package/dist/server/static/components/payload-panel.js +9 -9
  44. package/dist/server/static/components/response-panel.js +8 -8
  45. package/dist/server/static/components/rule-panel.js +4 -4
  46. package/dist/server/static/components/run-panel.js +4 -4
  47. package/dist/server/static/components/select-dropdown.js +5 -25
  48. package/dist/server/static/index.html +9 -9
  49. package/dist/server/static/vendor/m-.css +1 -0
  50. package/dist/server/static/vendor/m-.woff2 +0 -0
  51. package/dist/utils/display/blueprints-formatting.d.ts +3 -2
  52. package/dist/utils/display/blueprints-formatting.js +102 -50
  53. package/dist/utils/display/presenters.d.ts +1 -0
  54. package/dist/utils/display/presenters.js +3 -0
  55. package/dist/utils/display/prompt.js +10 -9
  56. package/dist/utils/display/resources-formatting.d.ts +6 -2
  57. package/dist/utils/display/resources-formatting.js +71 -17
  58. package/dist/utils/find-function.d.ts +2 -2
  59. package/dist/utils/find-function.js +9 -2
  60. package/dist/utils/invoke-local.d.ts +2 -2
  61. package/dist/utils/invoke-local.js +27 -16
  62. package/dist/utils/types.d.ts +46 -22
  63. package/dist/utils/types.js +25 -2
  64. package/dist/utils/validate/resource.js +144 -23
  65. package/dist/utils/validated-token.js +1 -1
  66. package/oclif.manifest.json +23 -4
  67. package/package.json +7 -4
@@ -1,15 +1,13 @@
1
- import { highlight } from 'cardinal';
2
1
  import chalk from 'chalk';
3
2
  import { patchConfigFile, writeConfigFile, } from '../../actions/blueprints/config.js';
4
- import { BLUEPRINT_CONFIG_DIR, BLUEPRINT_CONFIG_FILE } from '../../config.js';
5
- import { capitalize, filePathRelativeToCwd, niceId, warn } from '../../utils/display/presenters.js';
3
+ import { filePathRelativeToCwd, labeledId, warn } from '../../utils/display/presenters.js';
6
4
  import { promptForProject, promptForStack } from '../../utils/display/prompt.js';
7
5
  export async function blueprintConfigCore(options) {
8
6
  const { bin = 'sanity', blueprint, log, token, flags } = options;
9
7
  const { edit: editConfig = false, 'project-id': flagProjectId, 'organization-id': flagOrganizationId, 'stack-id': flagStackId, verbose: v = false, } = flags;
8
+ const providedConfigFlag = [flagProjectId, flagStackId, flagOrganizationId].some(Boolean);
10
9
  const { stackId: configStackId, scopeType: configScopeType, scopeId: configScopeId, blueprintConfig, fileInfo, } = blueprint;
11
10
  const blueprintFilePath = fileInfo.blueprintFilePath;
12
- const configPath = blueprintConfig?.configPath;
13
11
  if (!configStackId && !configScopeType && !configScopeId) {
14
12
  log(warn('Incomplete configuration.'));
15
13
  if (!editConfig) {
@@ -18,19 +16,10 @@ export async function blueprintConfigCore(options) {
18
16
  return { success: true }; // not necessarily fatal
19
17
  }
20
18
  }
21
- log(chalk.bold('Current Blueprint configuration:'));
22
- if (configPath) {
23
- if (v)
24
- log(` File: ${filePathRelativeToCwd(configPath)}`);
25
- }
26
- log(` Deployment: ${chalk.blue('Stack')} ${niceId(configStackId || 'unknown')}`);
27
- log(` Scoped to: ${chalk.blue(capitalize(configScopeType || 'unknown'))} ${niceId(configScopeId || 'unknown')}`);
28
- if (blueprintConfig?.updatedAt) {
29
- if (v)
30
- log(` Updated: ${new Date(blueprintConfig.updatedAt).toLocaleString()}`);
31
- }
19
+ if (blueprintConfig)
20
+ printConfig({ configLabel: 'Current', log, config: blueprintConfig, v });
32
21
  // passing new config without --edit flag is not allowed
33
- if ((flagProjectId || flagOrganizationId || flagStackId) && !editConfig) {
22
+ if (providedConfigFlag && !editConfig) {
34
23
  log('To update the configuration, use the --edit flag.');
35
24
  return { success: true };
36
25
  }
@@ -39,10 +28,9 @@ export async function blueprintConfigCore(options) {
39
28
  return { success: true };
40
29
  }
41
30
  else {
42
- // if a config property flag was passed, set the value and return success
43
- // do not try to validate correctness of combined flags; this should not be interactive
44
- const providedConfigFlag = [flagProjectId, flagStackId, flagOrganizationId].some(Boolean);
45
31
  if (providedConfigFlag) {
32
+ // if a config property flag was passed, set the value and return success
33
+ // do not try to validate correctness of combined flags; this should not be interactive
46
34
  const configUpdate = {};
47
35
  if (flagProjectId)
48
36
  configUpdate.projectId = flagProjectId;
@@ -51,19 +39,16 @@ export async function blueprintConfigCore(options) {
51
39
  if (flagOrganizationId)
52
40
  configUpdate.organizationId = flagOrganizationId;
53
41
  try {
54
- patchConfigFile(blueprintFilePath, configUpdate);
42
+ const newConfig = patchConfigFile(blueprintFilePath, configUpdate);
43
+ printConfig({ configLabel: 'Updated', log, config: newConfig, v });
44
+ return { success: true };
55
45
  }
56
46
  catch {
57
47
  return { success: false, error: 'Unable to update configuration.' };
58
48
  }
59
- log('\nConfiguration updated.');
60
- return { success: true };
61
- }
62
- // organization-based Blueprints are not yet supported for interactive editing
63
- if (configScopeType === 'organization') {
64
- return { success: false, error: 'Only project-based Blueprints are supported.' };
65
49
  }
66
- // otherwise, prompt for values interactively
50
+ // prompt for values interactively
51
+ // do not yet offer organization as scope option
67
52
  let updatedProjectId = flagProjectId;
68
53
  if (!updatedProjectId) {
69
54
  const pickedProject = await promptForProject({
@@ -83,21 +68,33 @@ export async function blueprintConfigCore(options) {
83
68
  return { success: false, error: 'Stack is required.' };
84
69
  try {
85
70
  // update or create config JSON
86
- writeConfigFile({ blueprintFilePath, projectId: updatedProjectId, stackId: updatedStackId });
87
- log(`\n${chalk.bold('New configuration:')}`);
88
- log(` Deployment: ${chalk.blue('Stack')} ${niceId(updatedStackId)}`);
89
- log(` Scoped to: ${chalk.blue('Project')} ${niceId(updatedProjectId)}`);
90
- log('');
91
- log('Configuration updated.');
71
+ const newConfig = await writeConfigFile(blueprintFilePath, {
72
+ projectId: updatedProjectId,
73
+ stackId: updatedStackId,
74
+ });
75
+ printConfig({ configLabel: 'Updated', log, config: newConfig, v });
92
76
  return { success: true };
93
77
  }
94
78
  catch {
95
- log(`Unable to update config. These values should be set in ${BLUEPRINT_CONFIG_DIR}/${BLUEPRINT_CONFIG_FILE}`);
96
- log(highlight(JSON.stringify({ metadata: { projectId: updatedProjectId, stackId: updatedStackId } }, null, 2)));
97
- return {
98
- success: false,
99
- error: `Be sure to update your ${BLUEPRINT_CONFIG_DIR}/${BLUEPRINT_CONFIG_FILE}`,
100
- };
79
+ return { success: false, error: 'Unable to update configuration!' };
101
80
  }
102
81
  }
103
82
  }
83
+ function printConfig(options) {
84
+ const { configLabel, log, config, v = false } = options;
85
+ const { projectId, organizationId, stackId, updatedAt } = config;
86
+ const scopeType = projectId ? 'project' : 'organization';
87
+ const scopeId = projectId ? projectId : organizationId;
88
+ if (v)
89
+ log(JSON.stringify(config));
90
+ log(`${chalk.bold(`${configLabel} configuration:`)}`);
91
+ log(` Deployment: ${labeledId('stack', stackId)}`);
92
+ log(` Scoped to: ${labeledId(scopeType, scopeId)}`);
93
+ if (updatedAt)
94
+ log(` Updated: ${new Date(updatedAt).toLocaleString()}`);
95
+ if ('configPath' in config) {
96
+ const { configPath } = config;
97
+ if (configPath)
98
+ log(` File: ${filePathRelativeToCwd(configPath)}`);
99
+ }
100
+ }
@@ -4,6 +4,7 @@ export interface BlueprintDoctorOptions extends CoreConfig {
4
4
  flags: {
5
5
  verbose?: boolean;
6
6
  path?: string;
7
+ fix?: boolean;
7
8
  };
8
9
  }
9
10
  export declare function blueprintDoctorCore(options: BlueprintDoctorOptions): Promise<CoreResult>;
@@ -5,6 +5,7 @@ import { getStack } from '../../actions/blueprints/stacks.js';
5
5
  import config from '../../config.js';
6
6
  import { capitalize, check, filePathRelativeToCwd, indent, niceId, severe, unsure, } from '../../utils/display/presenters.js';
7
7
  import { validTokenOrErrorMessage } from '../../utils/validated-token.js';
8
+ import { blueprintConfigCore } from './config.js';
8
9
  const diagLookup = {
9
10
  online: 'Online',
10
11
  tokenPresent: 'Auth token present',
@@ -17,12 +18,13 @@ const diagLookup = {
17
18
  userHasAccess: 'User has access to deployed "Stack"',
18
19
  };
19
20
  export async function blueprintDoctorCore(options) {
20
- const { log, token, flags: { verbose: v, path: p }, } = options;
21
+ const { bin, log, token, flags: { verbose: v, path: p, fix }, } = options;
21
22
  const yikes = (s) => {
22
23
  log(chalk.bgRedBright.whiteBright.bold(` ${s} `));
23
24
  };
24
25
  const here = cwd();
25
26
  const path = p || here;
27
+ let tokenOrError;
26
28
  if (v)
27
29
  log(`Checking ${filePathRelativeToCwd(path)}`);
28
30
  // 3 states: null == unknown, true == good, false == bad
@@ -49,7 +51,7 @@ export async function blueprintDoctorCore(options) {
49
51
  // TOKEN
50
52
  if (token) {
51
53
  diagnostics.tokenPresent = true;
52
- const tokenOrError = await validTokenOrErrorMessage(token);
54
+ tokenOrError = await validTokenOrErrorMessage(token);
53
55
  if (tokenOrError.ok) {
54
56
  diagnostics.tokenValid = true;
55
57
  }
@@ -91,7 +93,8 @@ export async function blueprintDoctorCore(options) {
91
93
  diagnostics.blueprintValid = false;
92
94
  }
93
95
  if (blueprint) {
94
- const { configPath, scopeType, scopeId, stackId, projectId } = blueprint;
96
+ const { blueprintConfig, scopeType, scopeId, stackId, projectId } = blueprint;
97
+ const configPath = blueprintConfig?.configPath;
95
98
  // CONFIG file
96
99
  if (configPath) {
97
100
  if (v)
@@ -169,11 +172,39 @@ export async function blueprintDoctorCore(options) {
169
172
  log(severe(`${key} is ${value}`));
170
173
  }
171
174
  }
175
+ const errorMessage = 'One or more checks failed';
172
176
  if (allGood) {
173
177
  log(chalk.bold.green('All checks passed'));
178
+ if (fix)
179
+ log(chalk.bold.yellow('Nothing to fix; --fix flag is ignored'));
174
180
  return { success: true, data: { diagnostics } };
175
181
  }
176
- else {
177
- return { success: false, error: 'One or more checks failed', data: { diagnostics } };
182
+ else if (fix) {
183
+ if (p) {
184
+ return { success: false, error: `${errorMessage}. --fix cannot be used with --path` };
185
+ }
186
+ if (!tokenOrError) {
187
+ return { success: false, error: `${errorMessage}. Unable to fix: Missing authentication token` };
188
+ }
189
+ if (tokenOrError?.ok === false) {
190
+ return {
191
+ success: false,
192
+ error: `${errorMessage}. Unable to fix: ${tokenOrError.error.message}`,
193
+ };
194
+ }
195
+ if (!blueprint) {
196
+ return {
197
+ success: false,
198
+ error: `${errorMessage}. Unable to fix: Blueprint is missing or invalid`,
199
+ };
200
+ }
201
+ return blueprintConfigCore({
202
+ bin,
203
+ log,
204
+ token: tokenOrError.value,
205
+ blueprint,
206
+ flags: { edit: true, verbose: v },
207
+ });
178
208
  }
209
+ return { success: false, error: errorMessage, data: { diagnostics } };
179
210
  }
@@ -1,5 +1,5 @@
1
1
  import { getStack } from '../../actions/blueprints/stacks.js';
2
- import { formatResourceTree, formatStackInfo } from '../../utils/display/blueprints-formatting.js';
2
+ import { formatDeployedResourceTree, formatStackInfo, } from '../../utils/display/blueprints-formatting.js';
3
3
  import { niceId } from '../../utils/display/presenters.js';
4
4
  export async function blueprintInfoCore(options) {
5
5
  const { log, auth, stackId, flags, deployedStack } = options;
@@ -20,7 +20,7 @@ export async function blueprintInfoCore(options) {
20
20
  }
21
21
  log(formatStackInfo(stack, true));
22
22
  if (stack.resources)
23
- log(formatResourceTree(stack.resources));
23
+ log(formatDeployedResourceTree(stack.resources));
24
24
  return { success: true };
25
25
  }
26
26
  catch (error) {
@@ -33,11 +33,13 @@ export declare function resolveScopeAndStack(params: {
33
33
  organizationId: string | undefined;
34
34
  stackId: string | undefined;
35
35
  stackName: string | undefined;
36
+ log: (message: string) => void;
36
37
  token: string;
37
38
  }): Promise<ResolvedScope>;
38
39
  export declare function determineBlueprintExtension(params: {
39
40
  requestedType: string | undefined;
40
41
  blueprintDir: string;
42
+ log: (message: string) => void;
41
43
  }): Promise<string>;
42
44
  export declare function createBlueprintFiles(params: {
43
45
  blueprintDir: string;
@@ -9,7 +9,7 @@ import { writeGitignoreFile } from '../../actions/git.js';
9
9
  import { writeOrUpdateNodeDependency } from '../../actions/node.js';
10
10
  import { verifyExampleExists, writeExample } from '../../actions/sanity/examples.js';
11
11
  import { BLUEPRINT_CONFIG_DIR, BLUEPRINT_CONFIG_FILE } from '../../config.js';
12
- import { check, filePathRelativeToCwd, warn } from '../../utils/display/presenters.js';
12
+ import { check, filePathRelativeToCwd, labeledId, warn } from '../../utils/display/presenters.js';
13
13
  import { promptForBlueprintType, promptForProject, promptForStack, } from '../../utils/display/prompt.js';
14
14
  import { blueprintConfigCore } from './config.js';
15
15
  const SCOPE_PROJECT = 'project';
@@ -20,6 +20,7 @@ export async function blueprintInitCore(options) {
20
20
  const { dir: argDir } = args;
21
21
  const userProvidedDirName = argDir || flagDir;
22
22
  const blueprintDir = userProvidedDirName || '.';
23
+ const flagProvidedConfig = [flagProjectId, flagOrganizationId, flagStackId].some(Boolean);
23
24
  const validationError = validateFlags({
24
25
  stackId: flagStackId,
25
26
  stackName: flagStackName,
@@ -29,15 +30,30 @@ export async function blueprintInitCore(options) {
29
30
  if (validationError)
30
31
  return validationError;
31
32
  try {
33
+ // look for existing blueprint file and maybe re-configure it
32
34
  const existingBlueprintFile = findBlueprintFile(blueprintDir);
33
35
  if (existingBlueprintFile) {
34
36
  log(warn(`Existing Blueprint found: ${filePathRelativeToCwd(existingBlueprintFile.blueprintFilePath)}`));
35
- const overwrite = await confirm({
36
- message: 'Would you like to re-configure deployment for this Blueprint?',
37
- default: true,
38
- });
39
- if (!overwrite)
40
- return { success: false, error: 'Initialization cancelled.' };
37
+ if (flagExample) {
38
+ return {
39
+ success: false,
40
+ error: 'Flag --example cannot be used with an existing Blueprint.',
41
+ };
42
+ }
43
+ if (flagStackName) {
44
+ return {
45
+ success: false,
46
+ error: 'Flag --stack-name cannot be used with an existing Blueprint.',
47
+ };
48
+ }
49
+ if (!flagProvidedConfig) {
50
+ const overwrite = await confirm({
51
+ message: 'Would you like to re-configure deployment for this Blueprint?',
52
+ default: true,
53
+ });
54
+ if (!overwrite)
55
+ return { success: false, error: 'Initialization cancelled.' };
56
+ }
41
57
  const existingBlueprint = await readLocalBlueprint(existingBlueprintFile.blueprintFilePath);
42
58
  return blueprintConfigCore({
43
59
  blueprint: existingBlueprint,
@@ -68,11 +84,13 @@ export async function blueprintInitCore(options) {
68
84
  organizationId: flagOrganizationId,
69
85
  stackId: flagStackId,
70
86
  stackName: flagStackName,
87
+ log,
71
88
  token,
72
89
  });
73
90
  const blueprintExtension = await determineBlueprintExtension({
74
91
  requestedType: flagBlueprintType,
75
92
  blueprintDir,
93
+ log,
76
94
  });
77
95
  return createBlueprintFiles({
78
96
  blueprintDir,
@@ -87,7 +105,6 @@ export async function blueprintInitCore(options) {
87
105
  }
88
106
  catch (error) {
89
107
  const errorMessage = error instanceof Error ? error.message : String(error);
90
- log(`Error: ${errorMessage}`);
91
108
  return { success: false, error: errorMessage };
92
109
  }
93
110
  }
@@ -136,7 +153,7 @@ async function handleExampleInitialization(options) {
136
153
  return { success: false, error: 'Failed to find blueprint file.' };
137
154
  }
138
155
  const { blueprintFilePath } = discoveredBlueprint;
139
- writeConfigFile({ blueprintFilePath, projectId: resolvedProjectId, stackId: stack.id });
156
+ writeConfigFile(blueprintFilePath, { projectId: resolvedProjectId, stackId: stack.id });
140
157
  log(check(`${chalk.bold('Configured:')} ${exampleDir}/${BLUEPRINT_CONFIG_DIR}/${BLUEPRINT_CONFIG_FILE}`));
141
158
  log(`\n Run "${chalk.bold.magenta(`cd ${exampleDir} && npm i`)}" and check out the README`);
142
159
  if (instructions) {
@@ -146,7 +163,7 @@ async function handleExampleInitialization(options) {
146
163
  return { success: true };
147
164
  }
148
165
  export async function resolveScopeAndStack(params) {
149
- const { projectId, organizationId, stackId, stackName, token } = params;
166
+ const { projectId, organizationId, stackId, stackName, log, token } = params;
150
167
  let scopeType = SCOPE_PROJECT;
151
168
  let scopeId;
152
169
  if (projectId) {
@@ -159,7 +176,9 @@ export async function resolveScopeAndStack(params) {
159
176
  }
160
177
  let resolvedStackId = stackId;
161
178
  if (!resolvedStackId && stackName && scopeType && scopeId) {
179
+ // sending stackName will assume you want to create a new stack
162
180
  // essentially the only way to create an org-scoped stack
181
+ log(`\nCreating new Stack "${stackName}" scoped to ${labeledId(scopeType, scopeId)}`);
163
182
  const stack = await createEmptyStack({
164
183
  token,
165
184
  scopeType,
@@ -169,11 +188,13 @@ export async function resolveScopeAndStack(params) {
169
188
  resolvedStackId = stack.id;
170
189
  }
171
190
  if (!scopeId) {
191
+ log('\nBlueprints are associated with a Sanity Project. Please select one:');
172
192
  const pickedProject = await promptForProject({ token });
173
193
  scopeType = SCOPE_PROJECT;
174
194
  scopeId = pickedProject.projectId;
175
195
  }
176
196
  if (!resolvedStackId) {
197
+ log('\nBlueprints are deployed to a "Stack".');
177
198
  const { stackId } = await promptForStack({ projectId: scopeId, token });
178
199
  resolvedStackId = stackId;
179
200
  }
@@ -184,11 +205,14 @@ export async function resolveScopeAndStack(params) {
184
205
  };
185
206
  }
186
207
  export async function determineBlueprintExtension(params) {
187
- const { requestedType, blueprintDir } = params;
208
+ const { requestedType, blueprintDir, log } = params;
188
209
  let extension = requestedType;
189
- if (!extension)
210
+ if (!extension) {
211
+ log('\nBlueprint files are authored locally to create resources in your Stack.');
190
212
  extension = await promptForBlueprintType();
213
+ }
191
214
  if (extension === 'js') {
215
+ // do we need to use .mjs or .js?
192
216
  const packageJsonPath = join(blueprintDir, 'package.json');
193
217
  const packageExists = existsSync(packageJsonPath);
194
218
  if (packageExists) {
@@ -209,6 +233,7 @@ export async function createBlueprintFiles(params) {
209
233
  if (!blueprintExtension) {
210
234
  return { success: false, error: 'Blueprint type is required.' };
211
235
  }
236
+ log(`\nCreating local Blueprint setup...`);
212
237
  const blueprintFileName = `sanity.blueprint.${blueprintExtension}`;
213
238
  const blueprintFilePath = join(blueprintDir, blueprintFileName);
214
239
  writeBlueprintToDisk({ blueprintFilePath });
@@ -217,8 +242,7 @@ export async function createBlueprintFiles(params) {
217
242
  }
218
243
  const displayPath = userProvidedDirName || '.';
219
244
  log(check(`${chalk.bold('Created Blueprint:')} ${displayPath}/${blueprintFileName}`));
220
- writeConfigFile({
221
- blueprintFilePath,
245
+ writeConfigFile(blueprintFilePath, {
222
246
  stackId,
223
247
  ...(scopeType === SCOPE_ORGANIZATION ? { organizationId: scopeId } : { projectId: scopeId }),
224
248
  });
@@ -1,11 +1,11 @@
1
1
  import chalk from 'chalk';
2
2
  import ora from 'ora';
3
3
  import { update } from '../../../actions/functions/env/update.js';
4
- import { findFunctionByName } from '../../../utils/find-function.js';
4
+ import { findFunctionInStack } from '../../../utils/find-function.js';
5
5
  export async function functionEnvAddCore(options) {
6
6
  const args = options.args;
7
7
  const spinner = ora(`Updating "${args.key}" environment variable in "${args.name}"`).start();
8
- const { externalId } = findFunctionByName(options.deployedStack, args.name);
8
+ const { externalId } = findFunctionInStack(options.deployedStack, args.name);
9
9
  const result = await update(externalId, args.key, args.value, options.auth);
10
10
  if (!result.ok) {
11
11
  spinner.fail(`${chalk.red('Failed')} to update ${args.key}`);
@@ -1,10 +1,10 @@
1
1
  import ora from 'ora';
2
2
  import { list } from '../../../actions/functions/env/list.js';
3
- import { findFunctionByName } from '../../../utils/find-function.js';
3
+ import { findFunctionInStack } from '../../../utils/find-function.js';
4
4
  export async function functionEnvListCore(options) {
5
5
  const args = options.args;
6
6
  const spinner = ora(`Listing environment variables for "${args.name}"`).start();
7
- const { externalId } = findFunctionByName(options.deployedStack, args.name);
7
+ const { externalId } = findFunctionInStack(options.deployedStack, args.name);
8
8
  const result = await list(externalId, options.auth);
9
9
  if (!result.ok) {
10
10
  spinner.stop();
@@ -1,11 +1,11 @@
1
1
  import chalk from 'chalk';
2
2
  import ora from 'ora';
3
3
  import { remove } from '../../../actions/functions/env/remove.js';
4
- import { findFunctionByName } from '../../../utils/find-function.js';
4
+ import { findFunctionInStack } from '../../../utils/find-function.js';
5
5
  export async function functionEnvRemoveCore(options) {
6
6
  const args = options.args;
7
7
  const spinner = ora(`Removing "${args.key}" environment variable in "${args.name}"`).start();
8
- const { externalId } = findFunctionByName(options.deployedStack, args.name);
8
+ const { externalId } = findFunctionInStack(options.deployedStack, args.name);
9
9
  const result = await remove(externalId, args.key, options.auth);
10
10
  if (!result.ok) {
11
11
  spinner.fail(`${chalk.red('Failed')} to remove ${args.key}`);
@@ -4,12 +4,12 @@ import ora from 'ora';
4
4
  import { deleteLogs as deleteLogsAction, logs as getLogsAction, streamLogs as streamLogsAction, } from '../../actions/functions/logs.js';
5
5
  import { formatTitle } from '../../utils/display/blueprints-formatting.js';
6
6
  import { niceId } from '../../utils/display/presenters.js';
7
- import { findFunctionByName } from '../../utils/find-function.js';
7
+ import { findFunctionInStack } from '../../utils/find-function.js';
8
8
  export async function functionLogsCore(options) {
9
9
  const { args, flags, log, auth, deployedStack } = options;
10
10
  const { name } = args;
11
11
  const { delete: shouldDelete, watch: shouldWatch, force, limit, json, utc } = flags;
12
- const { externalId } = findFunctionByName(deployedStack, name); // throws if not found
12
+ const { externalId } = findFunctionInStack(deployedStack, name); // throws if not found
13
13
  if (shouldDelete)
14
14
  return deleteLogs({ name, externalId, auth, force, log });
15
15
  if (shouldWatch)
@@ -4,11 +4,12 @@ import { cwd } from 'node:process';
4
4
  import ora from 'ora';
5
5
  import { testAction } from '../../actions/functions/test.js';
6
6
  import config from '../../config.js';
7
+ import { SANITY_FUNCTION_DOCUMENT, SANITY_FUNCTION_MEDIA_LIBRARY_ASSET } from '../../constants.js';
7
8
  import buildPayload from '../../utils/build-payload.js';
8
- import { findFunctionByName } from '../../utils/find-function.js';
9
+ import { findFunctionInBlueprint } from '../../utils/find-function.js';
9
10
  import { fetchAsset, fetchDocument } from '../../utils/functions/fetch-document.js';
10
11
  import { parseJsonObject } from '../../utils/parse-json-object.js';
11
- import { isEventType, } from '../../utils/types.js';
12
+ import { isEventType, isGroqContextOptions, } from '../../utils/types.js';
12
13
  export async function functionTestCore(options) {
13
14
  const { blueprint, log, args, flags } = options;
14
15
  const { name: fnName } = args;
@@ -29,22 +30,25 @@ export async function functionTestCore(options) {
29
30
  };
30
31
  }
31
32
  try {
32
- const resource = findFunctionByName(parsedBlueprint, fnName); // throws if not found
33
- const docFunction = resource.type === 'sanity.function.document';
34
- const contextOptions = {
35
- clientOptions: {
36
- apiVersion: api,
37
- dataset,
38
- projectId,
39
- organizationId,
40
- },
41
- eventResourceType: docFunction ? 'dataset' : 'media-library',
42
- eventResourceId: (docFunction ? `${projectId}.${dataset}` : mediaLibraryId) || '',
43
- functionResourceType: 'project',
44
- functionResourceId: (docFunction ? projectId : organizationId) || '',
45
- };
33
+ const resource = findFunctionInBlueprint(parsedBlueprint, fnName); // throws if not found
34
+ const docFunction = resource.type === SANITY_FUNCTION_DOCUMENT;
35
+ const mediaFunction = resource.type === SANITY_FUNCTION_MEDIA_LIBRARY_ASSET;
36
+ const contextOptions = docFunction || mediaFunction
37
+ ? {
38
+ clientOptions: {
39
+ apiVersion: api,
40
+ dataset,
41
+ projectId,
42
+ organizationId,
43
+ },
44
+ eventResourceType: docFunction ? 'dataset' : 'media-library',
45
+ eventResourceId: (docFunction ? `${projectId}.${dataset}` : mediaLibraryId) || '',
46
+ functionResourceType: 'project',
47
+ functionResourceId: projectId || '',
48
+ }
49
+ : {};
46
50
  // If the user sets the flag to use the real token set it in our options
47
- if (withUserToken) {
51
+ if (withUserToken && isGroqContextOptions(contextOptions)) {
48
52
  contextOptions.clientOptions.token = config.token || undefined;
49
53
  }
50
54
  let before = null;
@@ -115,12 +119,16 @@ export async function functionTestCore(options) {
115
119
  payload = buildPayload({ data, file });
116
120
  }
117
121
  }
118
- const invokeOptions = {
119
- event: eventType,
120
- payload,
121
- before,
122
- after,
123
- };
122
+ const invokeOptions = docFunction || mediaFunction
123
+ ? {
124
+ event: eventType,
125
+ payload,
126
+ before,
127
+ after,
128
+ }
129
+ : {
130
+ event: 'schedule',
131
+ };
124
132
  const spinner = ora('Executing function...').start();
125
133
  const { json, logs, error } = await testAction(resource, invokeOptions, contextOptions, {
126
134
  timeout: timeout ? timeout : resource.timeout,
@@ -5,7 +5,7 @@ import { WebSocketServer } from 'ws';
5
5
  import { readLocalBlueprint } from '../actions/blueprints/blueprint.js';
6
6
  import config from '../config.js';
7
7
  import { isRecord } from '../utils/is-record.js';
8
- import { isEventType, } from '../utils/types.js';
8
+ import { isEventType, isGroqContextOptions, } from '../utils/types.js';
9
9
  import { handleInvokeRequest } from './handlers/invoke.js';
10
10
  const app = (host, port, executionOptions) => {
11
11
  const requestListener = async (req, res) => {
@@ -34,11 +34,13 @@ const app = (host, port, executionOptions) => {
34
34
  const { data, func: functionName, metadata } = parseInvokeRequest(Buffer.concat(body));
35
35
  const { context, event } = data;
36
36
  // replace user token if required
37
- if (context.clientOptions.token) {
38
- context.clientOptions.token = config.token || undefined;
39
- }
40
- else {
41
- delete context.clientOptions.token;
37
+ if (isGroqContextOptions(context)) {
38
+ if (context.clientOptions.token) {
39
+ context.clientOptions.token = config.token || undefined;
40
+ }
41
+ else {
42
+ delete context.clientOptions.token;
43
+ }
42
44
  }
43
45
  const result = await handleInvokeRequest(functionName, event, metadata, context, executionOptions);
44
46
  // Add Server-Timing header
@@ -324,21 +326,30 @@ function parseInvokeRequest(body) {
324
326
  throw new Error('Request body is not valid, `metadata.event` field is missing');
325
327
  }
326
328
  const metadataEvent = metadata.event;
327
- if (typeof metadataEvent !== 'string' || !isEventType(metadataEvent)) {
328
- throw new Error('Request body is not valid, `metadata.event` field is not one of `create`, `update`, or `delete`');
329
- }
330
- if (!('before' in metadata)) {
331
- throw new Error('Request body is not valid, `metadata.before` field is missing');
332
- }
333
- if (!('after' in metadata)) {
334
- throw new Error('Request body is not valid, `metadata.after` field is missing');
335
- }
336
- const { before, after } = metadata;
337
- if (!isRecord(before) && before !== null) {
338
- throw new Error('Request body is not valid, `metadata.before` field is not an object');
329
+ if (typeof metadataEvent !== 'string' ||
330
+ (!isEventType(metadataEvent) && metadataEvent !== 'schedule')) {
331
+ throw new Error('Request body is not valid, `metadata.event` field is not one of `create`, `update`, `delete`, or `schedule`');
339
332
  }
340
- if (!isRecord(after) && after !== null) {
341
- throw new Error('Request body is not valid, `metadata.after` field is not an object');
333
+ let before = null;
334
+ let after = null;
335
+ // Only GROQ-based events (create, update, delete) have before and after fields
336
+ if (metadataEvent !== 'schedule') {
337
+ if (!('before' in metadata)) {
338
+ throw new Error('Request body is not valid, `metadata.before` field is missing');
339
+ }
340
+ if (!('after' in metadata)) {
341
+ throw new Error('Request body is not valid, `metadata.after` field is missing');
342
+ }
343
+ const beforeValue = metadata.before;
344
+ const afterValue = metadata.after;
345
+ if (!isRecord(beforeValue) && beforeValue !== null) {
346
+ throw new Error('Request body is not valid, `metadata.before` field is not an object');
347
+ }
348
+ if (!isRecord(afterValue) && afterValue !== null) {
349
+ throw new Error('Request body is not valid, `metadata.after` field is not an object');
350
+ }
351
+ before = beforeValue;
352
+ after = afterValue;
342
353
  }
343
354
  const clientOptions = {
344
355
  ...context.clientOptions,
@@ -361,7 +372,7 @@ function parseInvokeRequest(body) {
361
372
  },
362
373
  event,
363
374
  },
364
- metadata: { event: metadataEvent, before, after },
375
+ metadata: metadataEvent === 'schedule' ? { event: metadataEvent } : { event: metadataEvent, before, after },
365
376
  };
366
377
  }
367
378
  export { app, buildApiUrl, parseDocumentUrl };
@@ -1,4 +1,4 @@
1
- import type { InvocationResponse, InvokeContextOptions, InvokeExecutionOptions, InvokePayloadOptions } from '../../utils/types.js';
2
- export declare function handleInvokeRequest(functionName: string, event: Record<string, unknown>, metadata: Pick<InvokePayloadOptions, 'event' | 'before' | 'after'>, context: InvokeContextOptions, executionOptions?: Partial<InvokeExecutionOptions>): Promise<InvocationResponse & {
1
+ import type { InvocationResponse, InvokeContextOptions, InvokeExecutionOptions, InvokePayloadMetadata } from '../../utils/types.js';
2
+ export declare function handleInvokeRequest(functionName: string, event: Record<string, unknown>, metadata: InvokePayloadMetadata, context: InvokeContextOptions, executionOptions?: Partial<InvokeExecutionOptions>): Promise<InvocationResponse & {
3
3
  timings: Record<string, number>;
4
4
  }>;
@@ -1,10 +1,10 @@
1
1
  import { readLocalBlueprint } from '../../actions/blueprints/blueprint.js';
2
- import { findFunctionByName } from '../../utils/find-function.js';
2
+ import { findFunctionInBlueprint } from '../../utils/find-function.js';
3
3
  import invoke from '../../utils/invoke-local.js';
4
4
  export async function handleInvokeRequest(functionName, event, metadata, context, executionOptions) {
5
5
  const start = performance.now();
6
6
  const { parsedBlueprint } = await readLocalBlueprint();
7
- const resource = findFunctionByName(parsedBlueprint, functionName);
7
+ const resource = findFunctionInBlueprint(parsedBlueprint, functionName);
8
8
  const readBlueprintTime = performance.now() - start;
9
9
  const payload = {
10
10
  payload: event,
@@ -7,5 +7,8 @@ export class ApiBaseElement extends HTMLElement {
7
7
  constructor() {
8
8
  super()
9
9
  this.api = api
10
+ this.SANITY_FUNCTION_DOCUMENT = 'sanity.function.document'
11
+ this.SANITY_FUNCTION_MEDIA_LIBRARY_ASSET = 'sanity.function.media-library.asset'
12
+ this.SANITY_FUNCTION_SCHEDULE = 'sanity.function.cron'
10
13
  }
11
14
  }