@sanity/runtime-cli 14.7.2 → 14.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/README.md +150 -99
  2. package/dist/actions/blueprints/assets.d.ts +4 -3
  3. package/dist/actions/blueprints/assets.js +69 -101
  4. package/dist/actions/blueprints/blueprint.d.ts +2 -2
  5. package/dist/actions/blueprints/blueprint.js +1 -8
  6. package/dist/actions/blueprints/config.d.ts +2 -2
  7. package/dist/actions/blueprints/logs-streaming.d.ts +2 -2
  8. package/dist/actions/blueprints/logs.d.ts +2 -4
  9. package/dist/actions/blueprints/resources.d.ts +3 -3
  10. package/dist/actions/blueprints/resources.js +30 -10
  11. package/dist/actions/blueprints/stacks.d.ts +15 -25
  12. package/dist/actions/functions/dev.d.ts +1 -1
  13. package/dist/actions/functions/env/list.d.ts +1 -1
  14. package/dist/actions/functions/env/remove.d.ts +1 -1
  15. package/dist/actions/functions/env/update.d.ts +1 -1
  16. package/dist/actions/functions/logs.d.ts +3 -3
  17. package/dist/actions/node.d.ts +1 -1
  18. package/dist/actions/sanity/examples.d.ts +2 -2
  19. package/dist/actions/sanity/projects.d.ts +7 -13
  20. package/dist/baseCommands.d.ts +8 -0
  21. package/dist/baseCommands.js +10 -4
  22. package/dist/commands/blueprints/config.d.ts +1 -1
  23. package/dist/commands/blueprints/config.js +5 -13
  24. package/dist/commands/blueprints/deploy.d.ts +1 -1
  25. package/dist/commands/blueprints/deploy.js +2 -1
  26. package/dist/commands/blueprints/destroy.d.ts +1 -1
  27. package/dist/commands/blueprints/destroy.js +5 -7
  28. package/dist/commands/blueprints/doctor.js +2 -2
  29. package/dist/commands/blueprints/info.d.ts +1 -1
  30. package/dist/commands/blueprints/info.js +2 -1
  31. package/dist/commands/blueprints/init.js +3 -11
  32. package/dist/commands/blueprints/plan.d.ts +1 -1
  33. package/dist/commands/blueprints/plan.js +2 -1
  34. package/dist/commands/blueprints/promote.d.ts +0 -1
  35. package/dist/commands/blueprints/promote.js +2 -6
  36. package/dist/commands/blueprints/stacks.d.ts +1 -1
  37. package/dist/commands/blueprints/stacks.js +5 -13
  38. package/dist/commands/functions/build.d.ts +1 -1
  39. package/dist/commands/functions/build.js +2 -1
  40. package/dist/commands/functions/env/add.d.ts +1 -1
  41. package/dist/commands/functions/env/add.js +2 -1
  42. package/dist/commands/functions/env/list.d.ts +1 -1
  43. package/dist/commands/functions/env/list.js +2 -1
  44. package/dist/commands/functions/env/remove.d.ts +1 -1
  45. package/dist/commands/functions/env/remove.js +2 -1
  46. package/dist/commands/functions/logs.d.ts +0 -1
  47. package/dist/commands/functions/logs.js +0 -5
  48. package/dist/commands/functions/test.d.ts +1 -1
  49. package/dist/commands/functions/test.js +5 -13
  50. package/dist/constants.d.ts +2 -1
  51. package/dist/cores/blueprints/config.js +11 -7
  52. package/dist/cores/blueprints/deploy.js +62 -74
  53. package/dist/cores/blueprints/destroy.js +3 -3
  54. package/dist/cores/blueprints/doctor.js +5 -1
  55. package/dist/cores/blueprints/info.js +1 -1
  56. package/dist/cores/blueprints/init.d.ts +4 -3
  57. package/dist/cores/blueprints/plan.js +7 -1
  58. package/dist/cores/blueprints/promote.d.ts +0 -1
  59. package/dist/cores/blueprints/promote.js +4 -4
  60. package/dist/cores/blueprints/stacks.js +6 -2
  61. package/dist/cores/functions/add.d.ts +1 -0
  62. package/dist/cores/functions/add.js +2 -2
  63. package/dist/cores/functions/build.js +2 -2
  64. package/dist/cores/functions/env/add.js +1 -1
  65. package/dist/cores/functions/env/list.js +1 -1
  66. package/dist/cores/functions/env/remove.js +1 -1
  67. package/dist/cores/functions/test.js +4 -4
  68. package/dist/cores/index.d.ts +9 -2
  69. package/dist/cores/index.js +3 -2
  70. package/dist/server/app.d.ts +1 -1
  71. package/dist/server/handlers/invoke.d.ts +1 -1
  72. package/dist/utils/display/prompt.d.ts +2 -2
  73. package/dist/utils/display/prompt.js +1 -1
  74. package/dist/utils/display/resources-formatting.d.ts +2 -2
  75. package/dist/utils/display/resources-formatting.js +1 -1
  76. package/dist/utils/functions/fetch-document.d.ts +2 -2
  77. package/dist/utils/functions/prepare-asset.d.ts +3 -8
  78. package/dist/utils/functions/prepare-asset.js +2 -2
  79. package/dist/utils/functions/should-auto-resolve-deps.js +1 -1
  80. package/dist/utils/functions/should-transpile.js +1 -1
  81. package/dist/utils/invoke-local.d.ts +1 -7
  82. package/dist/utils/invoke-local.js +5 -24
  83. package/dist/utils/logger.d.ts +2 -0
  84. package/dist/utils/logger.js +2 -0
  85. package/dist/utils/other/github.d.ts +1 -1
  86. package/dist/utils/other/npmjs.d.ts +1 -1
  87. package/dist/utils/traced-fetch.d.ts +1 -1
  88. package/dist/utils/types.d.ts +21 -80
  89. package/dist/utils/types.js +6 -14
  90. package/dist/utils/validate/resource.d.ts +0 -3
  91. package/dist/utils/validate/resource.js +0 -270
  92. package/dist/utils/validated-token.d.ts +2 -2
  93. package/oclif.manifest.json +36 -90
  94. package/package.json +2 -2
@@ -4,7 +4,7 @@ import { setupLogStreaming } from '../../actions/blueprints/logs-streaming.js';
4
4
  import { getStack, updateStack } from '../../actions/blueprints/stacks.js';
5
5
  import { niceId } from '../../utils/display/presenters.js';
6
6
  import { styleText } from '../../utils/style-text.js';
7
- import { isLocalFunctionCollection, isLocalFunctionResource } from '../../utils/types.js';
7
+ import { isLocalFunctionResource, isStudioResource } from '../../utils/types.js';
8
8
  const DEFAULT_ASSET_TIMEOUT = 60;
9
9
  const assetTimeoutS = Number(process.env.SANITY_ASSET_TIMEOUT) || DEFAULT_ASSET_TIMEOUT;
10
10
  const assetTimeoutMs = assetTimeoutS * 1000;
@@ -30,77 +30,13 @@ export async function blueprintDeployCore(options) {
30
30
  }
31
31
  const { resources } = blueprint.parsedBlueprint;
32
32
  const validResources = resources?.filter((r) => r.type) || [];
33
- const functionResources = validResources.filter(isLocalFunctionResource);
34
- const functionCollections = validResources.filter(isLocalFunctionCollection);
35
- const allFunctionResources = [...functionResources, ...functionCollections];
36
- // First stash all function assets
37
- if (allFunctionResources.length > 0) {
38
- log('Processing function assets...');
39
- for (const resource of allFunctionResources) {
40
- const fnSpinner = log.ora({ text: `Processing ${resource.name}...`, prefixText: ' ' }).start();
41
- const warnTimer = setTimeout(() => {
42
- fnSpinner.text = `Still processing ${resource.name}, this can take a moment...`;
43
- }, warnTimeoutMs);
44
- let assetTimeoutTimer;
45
- let result;
46
- try {
47
- result = await Promise.race([
48
- stashAsset({ resource, auth, logger: log, installer }),
49
- new Promise((_, reject) => {
50
- assetTimeoutTimer = setTimeout(() => {
51
- reject(new Error(`Processing ${resource.name} timed out after ${assetTimeoutS}s`));
52
- }, assetTimeoutMs);
53
- }),
54
- ]);
55
- }
56
- catch (err) {
57
- const msg = err instanceof Error ? err.message : String(err);
58
- fnSpinner.fail(msg);
59
- return { success: false, error: msg };
60
- }
61
- finally {
62
- clearTimeout(warnTimer);
63
- clearTimeout(assetTimeoutTimer);
64
- }
65
- if (result.success && result.assetId) {
66
- resource.src = result.assetId;
67
- if (isLocalFunctionCollection(resource)) {
68
- try {
69
- for (const func of resource.functions) {
70
- func.src = func.name;
71
- }
72
- fnSpinner.succeed(`${resource.name} collection ${niceId(result.assetId)} (${resource.functions.length} functions)`);
73
- log(` Functions: ${resource.functions.map((f) => f.name).join(', ')}`);
74
- }
75
- catch (err) {
76
- fnSpinner.fail(`Failed to update function collection ${resource.name}`);
77
- return {
78
- success: false,
79
- error: `Error updating function collection '${resource.name}': ${err instanceof Error ? err.message : String(err)}`,
80
- };
81
- }
82
- }
83
- else {
84
- fnSpinner.succeed(`${resource.name} ${niceId(result.assetId)}`);
85
- log(` Source: ${resource.src}`);
86
- }
87
- if (result.hash) {
88
- if (result.hash.length > 24) {
89
- log.verbose(` Hash: ${result.hash.slice(0, 8)}...${result.hash.slice(-12)}`);
90
- }
91
- else {
92
- log.verbose(` Hash: ${result.hash}`);
93
- }
94
- }
95
- if (result.exists)
96
- log.verbose(' Asset unchanged');
97
- }
98
- else {
99
- const errorMsg = isLocalFunctionCollection(resource)
100
- ? `Failed uploading function collection ${resource.name} (${resource.functions?.length || 0} functions), deploy has stopped`
101
- : `Failed uploading ${resource.name} asset, deploy has stopped`;
102
- fnSpinner.fail(errorMsg);
103
- return { success: false, error: result.error || 'Failed to process function resource' };
33
+ const preDeployResources = validResources.filter(isPreDeployResource);
34
+ if (preDeployResources.length > 0) {
35
+ log('Processing assets...');
36
+ for (const resource of preDeployResources) {
37
+ const preDeployResult = await preDeploy(resource, { auth, installer, log });
38
+ if (preDeployResult.success === false) {
39
+ return preDeployResult;
104
40
  }
105
41
  }
106
42
  }
@@ -125,7 +61,7 @@ export async function blueprintDeployCore(options) {
125
61
  if (noWait) {
126
62
  log(styleText(['bold', 'green'], 'Stack deployment started!'));
127
63
  log(`Use \`npx ${bin} blueprints info\` to check status`);
128
- return { success: true, data: { resources } };
64
+ return { success: true, json: { stackId: stack.id, resources }, data: { resources } };
129
65
  }
130
66
  log(styleText('dim', 'Stack deployment progress:'));
131
67
  let logStreamCleanup = null;
@@ -164,7 +100,7 @@ export async function blueprintDeployCore(options) {
164
100
  if (logStreamCleanup)
165
101
  logStreamCleanup();
166
102
  log(styleText(['bold', 'green'], 'Stack deployment completed!'));
167
- return { success: true, data: { resources } };
103
+ return { success: true, json: { stackId: stack.id, resources }, data: { resources } };
168
104
  }
169
105
  if (!idleMessageShown && Date.now() - lastLogAt > 60_000) {
170
106
  log(`No new activity for 60 seconds. The deployment is still running on Sanity servers.`);
@@ -185,3 +121,55 @@ export async function blueprintDeployCore(options) {
185
121
  return { success: false, error: errorMessage };
186
122
  }
187
123
  }
124
+ function isPreDeployResource(r) {
125
+ return isLocalFunctionResource(r) || isStudioResource(r);
126
+ }
127
+ async function preDeploy(resource, options) {
128
+ const { auth, installer, log } = options;
129
+ const fnSpinner = log.ora({ text: `Processing ${resource.name}...`, prefixText: ' ' }).start();
130
+ const warnTimer = setTimeout(() => {
131
+ fnSpinner.text = `Still processing ${resource.name}, this can take a moment...`;
132
+ }, warnTimeoutMs);
133
+ let assetTimeoutTimer;
134
+ let result;
135
+ try {
136
+ result = await Promise.race([
137
+ stashAsset({ resource, auth, logger: log, installer }),
138
+ new Promise((_, reject) => {
139
+ assetTimeoutTimer = setTimeout(() => {
140
+ reject(new Error(`Processing ${resource.name} timed out after ${assetTimeoutS}s`));
141
+ }, assetTimeoutMs);
142
+ }),
143
+ ]);
144
+ }
145
+ catch (err) {
146
+ const msg = err instanceof Error ? err.message : String(err);
147
+ fnSpinner.fail(msg);
148
+ return { success: false, error: msg };
149
+ }
150
+ finally {
151
+ clearTimeout(warnTimer);
152
+ clearTimeout(assetTimeoutTimer);
153
+ }
154
+ if (result.success && result.assetId) {
155
+ resource.src = result.assetId;
156
+ fnSpinner.succeed(`${resource.name} ${niceId(result.assetId)}`);
157
+ log(` Source: ${resource.src}`);
158
+ if (result.hash) {
159
+ if (result.hash.length > 24) {
160
+ log.verbose(` Hash: ${result.hash.slice(0, 8)}...${result.hash.slice(-12)}`);
161
+ }
162
+ else {
163
+ log.verbose(` Hash: ${result.hash}`);
164
+ }
165
+ }
166
+ if (result.exists)
167
+ log.verbose(' Asset unchanged');
168
+ }
169
+ else {
170
+ const errorMsg = `Failed uploading ${resource.name} asset, deploy has stopped`;
171
+ fnSpinner.fail(errorMsg);
172
+ return { success: false, error: result.error || 'Failed to process asset' };
173
+ }
174
+ return { success: true };
175
+ }
@@ -32,7 +32,7 @@ export async function blueprintDestroyCore(options) {
32
32
  if (!ok)
33
33
  return { success: false, error: error || 'Failed to destroy Stack deployment' };
34
34
  log(`Stack deployment "${stack.name}" ${niceId(stack.id)} destroyed`);
35
- return { success: true };
35
+ return { success: true, json: { stackId: stack.id, stackName: stack.name } };
36
36
  }
37
37
  const { scopeType, scopeId, stackId } = blueprint;
38
38
  if (!scopeType || !scopeId)
@@ -92,7 +92,7 @@ export async function blueprintDestroyCore(options) {
92
92
  destroySpinner.stop().clear();
93
93
  if (noWait) {
94
94
  log(styleText(['bold', 'magenta'], 'Stack destruction started!'));
95
- return { success: true };
95
+ return { success: true, json: { stackId: stack.id, stackName: stack.name } };
96
96
  }
97
97
  log(styleText('dim', 'Stack destruction progress:'));
98
98
  let logStreamCleanup = null;
@@ -117,7 +117,7 @@ export async function blueprintDestroyCore(options) {
117
117
  if (logStreamCleanup)
118
118
  logStreamCleanup();
119
119
  log(styleText(['bold', 'magenta'], 'Stack destruction completed!'));
120
- return { success: true };
120
+ return { success: true, json: { stackId: stack.id, stackName: stack.name } };
121
121
  }
122
122
  if (operation.status === 'FAILED') {
123
123
  if (logStreamCleanup)
@@ -273,7 +273,11 @@ export async function blueprintDoctorCore(options) {
273
273
  log(styleText(['bold', 'green'], 'All checks passed'));
274
274
  if (fix)
275
275
  log(styleText(['bold', 'yellow'], 'Nothing to fix; --fix flag is ignored'));
276
- return { success: true, data: { diagnostics: flatDiagnostics } };
276
+ return {
277
+ success: true,
278
+ json: { diagnostics: flatDiagnostics },
279
+ data: { diagnostics: flatDiagnostics },
280
+ };
277
281
  }
278
282
  if (fix) {
279
283
  if (p) {
@@ -6,7 +6,7 @@ export async function blueprintInfoCore(options) {
6
6
  log(formatStackInfo(deployedStack, true));
7
7
  if (deployedStack.resources)
8
8
  log(formatDeployedResourceTree(deployedStack.resources, verbose));
9
- return { success: true };
9
+ return { success: true, json: { stack: deployedStack } };
10
10
  }
11
11
  catch (error) {
12
12
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -1,3 +1,4 @@
1
+ import type { Logger } from '../../utils/logger.js';
1
2
  import type { ScopeType } from '../../utils/types.js';
2
3
  import type { CoreConfig, CoreResult } from '../index.js';
3
4
  export interface BlueprintInitOptions extends CoreConfig {
@@ -35,13 +36,13 @@ export declare function resolveScopeAndStack(params: {
35
36
  stackId: string | undefined;
36
37
  stackName: string | undefined;
37
38
  knownProjectId?: string;
38
- log: CoreConfig['log'];
39
+ log: Logger;
39
40
  token: string;
40
41
  }): Promise<ResolvedScope>;
41
42
  export declare function determineBlueprintExtension(params: {
42
43
  requestedType: string | undefined;
43
44
  blueprintDir: string;
44
- log: CoreConfig['log'];
45
+ log: Logger;
45
46
  }): Promise<string>;
46
47
  export declare function createBlueprintFiles(params: {
47
48
  blueprintDir: string;
@@ -51,6 +52,6 @@ export declare function createBlueprintFiles(params: {
51
52
  scopeId: string;
52
53
  stackId: string | undefined;
53
54
  bin: string;
54
- log: CoreConfig['log'];
55
+ log: Logger;
55
56
  }): Promise<CoreResult>;
56
57
  export {};
@@ -63,5 +63,11 @@ export async function blueprintPlanCore(options) {
63
63
  else {
64
64
  log(`\n ${styleText('dim', `No significant changes to deploy. Run \`npx ${bin} blueprints deploy\` to apply.`)}`);
65
65
  }
66
- return { success: true };
66
+ return {
67
+ success: true,
68
+ json: {
69
+ resources: parsedBlueprint.resources,
70
+ plan: planResponse.deploymentPlan,
71
+ },
72
+ };
67
73
  }
@@ -1,7 +1,6 @@
1
1
  import type { CoreResult, DeployedBlueprintConfig } from '../index.js';
2
2
  export interface BlueprintPromoteOptions extends DeployedBlueprintConfig {
3
3
  flags: {
4
- 'i-know-what-im-doing': boolean;
5
4
  force?: boolean;
6
5
  verbose?: boolean;
7
6
  };
@@ -4,9 +4,6 @@ import { promoteStack } from '../../actions/blueprints/stacks.js';
4
4
  import { niceId } from '../../utils/display/presenters.js';
5
5
  export async function blueprintPromoteCore(options) {
6
6
  const { log, stackId, auth, flags, deployedStack, blueprint } = options;
7
- if (!flags['i-know-what-im-doing']) {
8
- return { success: false, error: 'Seems you do not know what you are doing.' };
9
- }
10
7
  let message = `"${deployedStack.name}" ${niceId(deployedStack.id)}`;
11
8
  if (deployedStack.scopeType === 'organization') {
12
9
  message = `Stack ${message} is already org-scoped. Promote again?`;
@@ -21,10 +18,13 @@ export async function blueprintPromoteCore(options) {
21
18
  }
22
19
  }
23
20
  try {
21
+ const spinner = log.ora('Promoting Stack...').start();
24
22
  const { ok, error, stack } = await promoteStack({ stackId, auth, logger: log });
25
23
  if (!ok) {
24
+ spinner.fail('Failed to promote Stack');
26
25
  return { success: false, error: error || 'Failed to promote Stack' };
27
26
  }
27
+ spinner.stop().clear();
28
28
  log(`Stack "${stack.name}" ${niceId(stack.id)} promoted successfully`);
29
29
  const { blueprintFilePath } = blueprint.fileInfo;
30
30
  try {
@@ -41,7 +41,7 @@ export async function blueprintPromoteCore(options) {
41
41
  error: 'Stack promoted successfully but failed to update local Blueprint configuration. No config file found.',
42
42
  };
43
43
  }
44
- return { success: true, data: { stack } };
44
+ return { success: true, json: { stack }, data: { stack } };
45
45
  }
46
46
  catch (error) {
47
47
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -25,16 +25,20 @@ export async function blueprintStacksCore(options) {
25
25
  return { success: false, error: 'Unable to determine scope for Blueprint Stacks' };
26
26
  }
27
27
  try {
28
+ const spinner = log.ora('Fetching Stacks...').start();
28
29
  const { ok, stacks, error } = await listStacks({ token, scopeType, scopeId }, log);
29
- if (!ok)
30
+ if (!ok) {
31
+ spinner.fail('Failed to list Stacks');
30
32
  return { success: false, error: error || 'Failed to list stacks' };
33
+ }
34
+ spinner.stop().clear();
31
35
  if (!stacks || stacks.length === 0) {
32
36
  log('No stacks found');
33
37
  return { success: true };
34
38
  }
35
39
  log(`${styleText('bold', capitalize(scopeType))} ${niceId(scopeId)} ${styleText('bold', 'Stacks')}:\n`);
36
40
  log(formatStacksListing(stacks, blueprintStackId));
37
- return { success: true };
41
+ return { success: true, json: { stacks, scopeType, scopeId } };
38
42
  }
39
43
  catch (error) {
40
44
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -13,4 +13,5 @@ export interface FunctionAddOptions extends CoreConfig {
13
13
  install?: boolean;
14
14
  };
15
15
  }
16
+ export declare const generateFunctionBlueprintResourceTemplate: (fnName: string, eventNames: string[]) => string;
16
17
  export declare function functionAddCore(options: FunctionAddOptions): Promise<CoreResult>;
@@ -11,7 +11,7 @@ FUNCTION_TYPES, LABEL_DOCUMENT_CREATE, LABEL_DOCUMENT_DELETE, LABEL_DOCUMENT_UPD
11
11
  import { check, indent, warn } from '../../utils/display/presenters.js';
12
12
  import { styleText } from '../../utils/style-text.js';
13
13
  import { validateFunctionName } from '../../utils/validate/resource.js';
14
- const generateFunctionBlueprintResourceTemplate = (fnName, eventNames) => {
14
+ export const generateFunctionBlueprintResourceTemplate = (fnName, eventNames) => {
15
15
  const functionType = eventNames[0].substring(0, eventNames[0].lastIndexOf('-'));
16
16
  let definer = '';
17
17
  const eventOns = eventNames.map((e) => `'${e.substring(e.lastIndexOf('-') + 1)}'`);
@@ -19,7 +19,7 @@ const generateFunctionBlueprintResourceTemplate = (fnName, eventNames) => {
19
19
  case 'document':
20
20
  definer = `defineDocumentFunction({name: '${fnName}', event: {on: [${eventOns.join(', ')}]}}), // ← add this line`;
21
21
  break;
22
- case 'media':
22
+ case 'media-library-asset':
23
23
  definer = `defineMediaLibraryAssetFunction({name: '${fnName}', event: {on: [${eventOns.join(', ')}], resource: {type: 'media-library', id: 'my-media-library-id'}}}), // ← add this line`;
24
24
  break;
25
25
  case 'scheduled':
@@ -28,7 +28,7 @@ export async function functionBuildCore(options) {
28
28
  for (const resource of functions) {
29
29
  const spinner = log.ora(`Building ${resource.name}...`).start();
30
30
  const prepResult = await prepareAsset({ resource }, { installer });
31
- if (!prepResult.success || !prepResult.outputPath) {
31
+ if (prepResult.success === false) {
32
32
  spinner.fail(`Failed to build ${resource.name}`);
33
33
  errors.push({ name: resource.name, error: prepResult.error ?? 'Unknown error' });
34
34
  continue;
@@ -58,5 +58,5 @@ export async function functionBuildCore(options) {
58
58
  log.warn(`${errors.length} function(s) failed to build`);
59
59
  }
60
60
  log(`\nBuild complete: ${built.length} function(s)`);
61
- return { success: true };
61
+ return { success: true, json: { built, errors, outDir } };
62
62
  }
@@ -17,5 +17,5 @@ export async function functionEnvAddCore(options) {
17
17
  };
18
18
  }
19
19
  spinner.succeed(`Update of ${args.key} succeeded`);
20
- return { success: true };
20
+ return { success: true, json: { name: args.name, key: args.key, value: args.value } };
21
21
  }
@@ -16,5 +16,5 @@ export async function functionEnvListCore(options) {
16
16
  for (const key of result.envvars) {
17
17
  options.log(key);
18
18
  }
19
- return { success: true };
19
+ return { success: true, json: { name: args.name, envvars: result.envvars } };
20
20
  }
@@ -14,5 +14,5 @@ export async function functionEnvRemoveCore(options) {
14
14
  return { success: false, error: result.error || 'Unknown error' };
15
15
  }
16
16
  spinner.succeed(`Removal of ${args.key} succeeded`);
17
- return { success: true };
17
+ return { success: true, json: { name: args.name, key: args.key } };
18
18
  }
@@ -138,7 +138,7 @@ export async function functionTestCore(options) {
138
138
  event: 'scheduled',
139
139
  };
140
140
  const spinner = log.ora('Executing function...').start();
141
- const { json, logs, error } = await testAction(resource, invokeOptions, contextOptions, {
141
+ const { json: response, logs, error, } = await testAction(resource, invokeOptions, contextOptions, {
142
142
  timeout: timeout ? timeout : resource.timeout,
143
143
  });
144
144
  if (error) {
@@ -151,11 +151,11 @@ export async function functionTestCore(options) {
151
151
  spinner.succeed('Function execution succeeded.');
152
152
  log('Logs:');
153
153
  log(logs || '');
154
- if (json) {
154
+ if (response) {
155
155
  log('Response:');
156
- log(JSON.stringify(json, null, 2));
156
+ log(JSON.stringify(response, null, 2));
157
157
  }
158
- return { success: true };
158
+ return { success: true, json: { response, logs } };
159
159
  }
160
160
  catch (error) {
161
161
  return {
@@ -7,7 +7,7 @@ export interface CoreConfig {
7
7
  /** The CLI binary name. */
8
8
  bin: string;
9
9
  /** The log output function. */
10
- log: ReturnType<typeof Logger>;
10
+ log: Logger;
11
11
  /** Enable resource validation during parsing */
12
12
  validateResources?: boolean;
13
13
  /** Path to a specific Blueprint file or directory */
@@ -25,7 +25,10 @@ export interface DeployedBlueprintConfig extends BlueprintConfig {
25
25
  deployedStack: Stack;
26
26
  }
27
27
  export type CoreResult = {
28
- /** Arbitrary data for isolated testing */
28
+ /**
29
+ * Internal data for isolated testing and debugging.
30
+ * Not returned to users! Use `json` for structured output via oclif's `--json` flag.
31
+ */
29
32
  data?: Record<string, unknown>;
30
33
  } & ({
31
34
  /** Something went wrong. */
@@ -40,6 +43,10 @@ export type CoreResult = {
40
43
  /** The streaming function, if the operation is streaming. */
41
44
  streaming?: Promise<void>;
42
45
  error?: never;
46
+ /**
47
+ * Structured data returned to the user when `--json` is passed.
48
+ * Commands should return this value from `run()` for oclif to serialize.
49
+ */
43
50
  json?: Record<string, unknown>;
44
51
  });
45
52
  type InitBlueprintConfigParams = CoreConfig & ({
@@ -1,7 +1,6 @@
1
1
  import { readLocalBlueprint } from '../actions/blueprints/blueprint.js';
2
2
  import { getStack, resolveStackIdByNameOrId } from '../actions/blueprints/stacks.js';
3
3
  import { presentBlueprintParserErrors } from '../utils/display/errors.js';
4
- import { niceId } from '../utils/display/presenters.js';
5
4
  import { validTokenOrErrorMessage } from '../utils/validated-token.js';
6
5
  export * as blueprintsCores from './blueprints/index.js';
7
6
  export * as functionsCores from './functions/index.js';
@@ -55,12 +54,14 @@ export async function initDeployedBlueprintConfig(config) {
55
54
  config.log(`Incomplete configuration. Run \`${config.bin} blueprints doctor\` for diagnostics.`);
56
55
  return { ok: false, error: 'Missing Stack deployment configuration for Blueprint' };
57
56
  }
57
+ const spinner = config.log.ora('Loading Stack deployment...').start();
58
58
  const stackResponse = await getStack({ stackId, auth, logger: config.log });
59
59
  if (!stackResponse.ok) {
60
- config.log(`Could not retrieve Stack deployment info for ${niceId(stackId)}.`);
60
+ spinner.fail('Could not load Stack deployment');
61
61
  config.log(`Run \`${config.bin} blueprints doctor\` for diagnostics.`);
62
62
  return { ok: false, error: 'Missing Stack deployment' };
63
63
  }
64
+ spinner.stop().clear();
64
65
  return {
65
66
  ok: true,
66
67
  value: {
@@ -1,6 +1,6 @@
1
1
  import type { Logger } from '../utils/logger.js';
2
2
  import { type InvokeExecutionOptions } from '../utils/types.js';
3
- declare const app: (host: string, port: number, logger: ReturnType<typeof Logger>, validateResources: boolean, executionOptions?: Partial<InvokeExecutionOptions>) => void;
3
+ declare const app: (host: string, port: number, logger: Logger, validateResources: boolean, executionOptions?: Partial<InvokeExecutionOptions>) => void;
4
4
  declare function parseDocumentUrl(url: string): {
5
5
  projectId: string;
6
6
  dataset: string;
@@ -1,5 +1,5 @@
1
1
  import type { Logger } from '../../utils/logger.js';
2
2
  import type { InvocationResponse, InvokeContextOptions, InvokeExecutionOptions, InvokePayloadMetadata } from '../../utils/types.js';
3
- export declare function handleInvokeRequest(functionName: string, event: Record<string, unknown>, metadata: InvokePayloadMetadata, context: InvokeContextOptions, logger: ReturnType<typeof Logger>, validateResources: boolean, executionOptions?: Partial<InvokeExecutionOptions>): Promise<InvocationResponse & {
3
+ export declare function handleInvokeRequest(functionName: string, event: Record<string, unknown>, metadata: InvokePayloadMetadata, context: InvokeContextOptions, logger: Logger, validateResources: boolean, executionOptions?: Partial<InvokeExecutionOptions>): Promise<InvocationResponse & {
4
4
  timings: Record<string, number>;
5
5
  }>;
@@ -10,7 +10,7 @@ export declare function promptForProject({ token, knownOrganizationId, knownProj
10
10
  token: string;
11
11
  knownOrganizationId?: string;
12
12
  knownProjectId?: string;
13
- logger: ReturnType<typeof Logger>;
13
+ logger: Logger;
14
14
  }): Promise<{
15
15
  projectId: string;
16
16
  displayName: string;
@@ -26,7 +26,7 @@ export declare function promptForProject({ token, knownOrganizationId, knownProj
26
26
  export declare function promptForStack({ projectId, token, logger, }: {
27
27
  projectId: string;
28
28
  token: string;
29
- logger: ReturnType<typeof Logger>;
29
+ logger: Logger;
30
30
  }): Promise<{
31
31
  stackId: string;
32
32
  name: string;
@@ -95,7 +95,7 @@ export async function promptForStack({ projectId, token, logger, }) {
95
95
  stackChoices.push({ name: styleText('bold', 'New Stack ✨'), value: NEW_STACK_ID });
96
96
  stackChoices.push(new Separator(styleText('underline', 'Use an existing Stack:')));
97
97
  stackChoices.push(...stacks.map((s) => ({
98
- name: `"${s.name}" ${niceId(s.id)} ${styleText('dim', `(${s.resourceCount} res)`)}`,
98
+ name: `"${s.name}" ${niceId(s.id)} ${styleText('dim', `(${s.resourceCount} ${s.resourceCount === 1 ? 'resource' : 'resources'})`)}`,
99
99
  value: s.id,
100
100
  })));
101
101
  pickedStackId = await select({
@@ -1,7 +1,7 @@
1
1
  import type { BlueprintCorsOriginResource, BlueprintDatasetResource, BlueprintDocumentWebhookResource, BlueprintRobotResource, BlueprintRoleResource } from '@sanity/blueprints';
2
+ import type { Resource } from '@sanity/blueprints-parser';
2
3
  import type { TreeInput } from 'array-treeify';
3
- import { type FunctionResourceBase } from '../../utils/types.js';
4
- export declare function arrayifyFunction(fn: FunctionResourceBase): TreeInput;
4
+ export declare function arrayifyFunction(fn: Resource): TreeInput;
5
5
  export declare function arrayifyCors(resource: BlueprintCorsOriginResource): TreeInput;
6
6
  export declare function arrayifyRobot(resource: BlueprintRobotResource): TreeInput;
7
7
  export declare function arrayifyRole(resource: BlueprintRoleResource): TreeInput;
@@ -1,4 +1,4 @@
1
- import { isScheduleEvent, } from '../../utils/types.js';
1
+ import { isScheduleEvent } from '../../utils/types.js';
2
2
  import { styleText } from '../style-text.js';
3
3
  function formatLabel(label) {
4
4
  return styleText('dim', `${label}:`);
@@ -1,5 +1,5 @@
1
1
  import { type ClientConfig } from '@sanity/client';
2
2
  import type { Logger } from '../logger.js';
3
3
  import type { FetchConfig } from '../types.js';
4
- export declare function fetchDocument(documentId: string, { projectId, dataset, useCdn, apiVersion, apiHost, token }: ClientConfig, logger: ReturnType<typeof Logger>): Promise<Record<string, unknown>>;
5
- export declare function fetchAsset(documentId: string, { mediaLibraryId, apiVersion, apiHost, token }: FetchConfig, logger: ReturnType<typeof Logger>): Promise<Record<string, unknown>>;
4
+ export declare function fetchDocument(documentId: string, { projectId, dataset, useCdn, apiVersion, apiHost, token }: ClientConfig, logger: Logger): Promise<Record<string, unknown>>;
5
+ export declare function fetchAsset(documentId: string, { mediaLibraryId, apiVersion, apiHost, token }: FetchConfig, logger: Logger): Promise<Record<string, unknown>>;
@@ -1,11 +1,6 @@
1
- import type { CollectionFunction, FunctionResource, InstallerType } from '../types.js';
1
+ import type { AssetPrepResult, FunctionResource, InstallerType } from '../types.js';
2
2
  export declare function prepareAsset({ resource, }: {
3
- resource: FunctionResource | CollectionFunction;
3
+ resource: FunctionResource;
4
4
  }, { installer }?: {
5
5
  installer?: InstallerType;
6
- }): Promise<{
7
- success: boolean;
8
- outputPath?: string;
9
- cleanup?: () => Promise<void>;
10
- error?: string;
11
- }>;
6
+ }): Promise<AssetPrepResult>;
@@ -29,7 +29,7 @@ export async function prepareAsset({ resource, }, { installer } = {}) {
29
29
  wasBundled = result.bundled;
30
30
  }
31
31
  catch (err) {
32
- return { success: false, error: err instanceof Error ? err.message : `${err}` };
32
+ return { success: false, cleanup, error: err instanceof Error ? err.message : `${err}` };
33
33
  }
34
34
  }
35
35
  const shouldResolveDependencies = await shouldAutoResolveDependencies(resource);
@@ -45,7 +45,7 @@ export async function prepareAsset({ resource, }, { installer } = {}) {
45
45
  return { success: true, outputPath: functionPath, cleanup };
46
46
  }
47
47
  catch (err) {
48
- return { success: false, error: err instanceof Error ? err.message : `${err}` };
48
+ return { success: false, cleanup, error: err instanceof Error ? err.message : `${err}` };
49
49
  }
50
50
  }
51
51
  /**
@@ -1,5 +1,5 @@
1
1
  export async function shouldAutoResolveDependencies(resource) {
2
- if (typeof resource.autoResolveDeps === 'boolean') {
2
+ if ('autoResolveDeps' in resource && typeof resource.autoResolveDeps === 'boolean') {
3
3
  return resource.autoResolveDeps;
4
4
  }
5
5
  // otherwise hydrate is the default
@@ -1,6 +1,6 @@
1
1
  import { findFunctionEntryPoint } from './find-entry-point.js';
2
2
  export async function shouldTranspileFunction(resource) {
3
- if (typeof resource.transpile === 'boolean') {
3
+ if ('transpile' in resource && typeof resource.transpile === 'boolean') {
4
4
  return resource.transpile;
5
5
  }
6
6
  if (!resource.src) {
@@ -1,10 +1,4 @@
1
- import { type FunctionGroqResource, type FunctionResource, type GroqRuleBase, type InvocationResponse, type InvokeContextOptions, type InvokeExecutionOptions, type InvokeGroqPayloadOptions, type InvokePayloadOptions } from './types.js';
1
+ import { type FunctionGroqResource, type FunctionResource, type InvocationResponse, type InvokeContextOptions, type InvokeExecutionOptions, type InvokeGroqPayloadOptions, type InvokePayloadOptions } from './types.js';
2
2
  export declare function sanitizeLogs(logs: string): string;
3
- export declare const DEFAULT_GROQ_RULE: {
4
- on: string[];
5
- filter: string;
6
- projection: string;
7
- };
8
- export declare function isDefaultGROQRule(rule: GroqRuleBase | undefined): boolean;
9
3
  export declare function applyGroqRule(resource: FunctionGroqResource, payload: InvokeGroqPayloadOptions, projectId: string | undefined, dataset: string | undefined): Promise<any>;
10
4
  export default function invoke(resource: FunctionResource, payload: InvokePayloadOptions, context: InvokeContextOptions, options: InvokeExecutionOptions): Promise<InvocationResponse>;