@sanity/runtime-cli 12.2.0 → 12.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -20,7 +20,7 @@ $ npm install -g @sanity/runtime-cli
20
20
  $ sanity-run COMMAND
21
21
  running command...
22
22
  $ sanity-run (--version)
23
- @sanity/runtime-cli/12.2.0 linux-x64 node-v24.11.1
23
+ @sanity/runtime-cli/12.3.0 linux-x64 node-v24.11.1
24
24
  $ sanity-run --help [COMMAND]
25
25
  USAGE
26
26
  $ sanity-run COMMAND
@@ -92,7 +92,7 @@ EXAMPLES
92
92
  $ sanity-run blueprints add function --name my-function --fn-type document-create --fn-type document-update --lang js
93
93
  ```
94
94
 
95
- _See code: [src/commands/blueprints/add.ts](https://github.com/sanity-io/runtime-cli/blob/v12.2.0/src/commands/blueprints/add.ts)_
95
+ _See code: [src/commands/blueprints/add.ts](https://github.com/sanity-io/runtime-cli/blob/v12.3.0/src/commands/blueprints/add.ts)_
96
96
 
97
97
  ## `sanity-run blueprints config`
98
98
 
@@ -121,7 +121,7 @@ EXAMPLES
121
121
  $ sanity-run blueprints config --edit --project-id <projectId> --stack-id <stackId>
122
122
  ```
123
123
 
124
- _See code: [src/commands/blueprints/config.ts](https://github.com/sanity-io/runtime-cli/blob/v12.2.0/src/commands/blueprints/config.ts)_
124
+ _See code: [src/commands/blueprints/config.ts](https://github.com/sanity-io/runtime-cli/blob/v12.3.0/src/commands/blueprints/config.ts)_
125
125
 
126
126
  ## `sanity-run blueprints deploy`
127
127
 
@@ -143,7 +143,7 @@ EXAMPLES
143
143
  $ sanity-run blueprints deploy --no-wait
144
144
  ```
145
145
 
146
- _See code: [src/commands/blueprints/deploy.ts](https://github.com/sanity-io/runtime-cli/blob/v12.2.0/src/commands/blueprints/deploy.ts)_
146
+ _See code: [src/commands/blueprints/deploy.ts](https://github.com/sanity-io/runtime-cli/blob/v12.3.0/src/commands/blueprints/deploy.ts)_
147
147
 
148
148
  ## `sanity-run blueprints destroy`
149
149
 
@@ -170,7 +170,7 @@ EXAMPLES
170
170
  $ sanity-run blueprints destroy --stack-id <stackId> --project-id <projectId> --force --no-wait
171
171
  ```
172
172
 
173
- _See code: [src/commands/blueprints/destroy.ts](https://github.com/sanity-io/runtime-cli/blob/v12.2.0/src/commands/blueprints/destroy.ts)_
173
+ _See code: [src/commands/blueprints/destroy.ts](https://github.com/sanity-io/runtime-cli/blob/v12.3.0/src/commands/blueprints/destroy.ts)_
174
174
 
175
175
  ## `sanity-run blueprints doctor`
176
176
 
@@ -189,7 +189,7 @@ DESCRIPTION
189
189
  Diagnose potential issues with Blueprint configuration
190
190
  ```
191
191
 
192
- _See code: [src/commands/blueprints/doctor.ts](https://github.com/sanity-io/runtime-cli/blob/v12.2.0/src/commands/blueprints/doctor.ts)_
192
+ _See code: [src/commands/blueprints/doctor.ts](https://github.com/sanity-io/runtime-cli/blob/v12.3.0/src/commands/blueprints/doctor.ts)_
193
193
 
194
194
  ## `sanity-run blueprints info`
195
195
 
@@ -211,7 +211,7 @@ EXAMPLES
211
211
  $ sanity-run blueprints info --id <stackId>
212
212
  ```
213
213
 
214
- _See code: [src/commands/blueprints/info.ts](https://github.com/sanity-io/runtime-cli/blob/v12.2.0/src/commands/blueprints/info.ts)_
214
+ _See code: [src/commands/blueprints/info.ts](https://github.com/sanity-io/runtime-cli/blob/v12.3.0/src/commands/blueprints/info.ts)_
215
215
 
216
216
  ## `sanity-run blueprints init [DIR]`
217
217
 
@@ -251,7 +251,7 @@ EXAMPLES
251
251
  $ sanity-run blueprints init --blueprint-type <json|js|ts> --stack-name <stackName>
252
252
  ```
253
253
 
254
- _See code: [src/commands/blueprints/init.ts](https://github.com/sanity-io/runtime-cli/blob/v12.2.0/src/commands/blueprints/init.ts)_
254
+ _See code: [src/commands/blueprints/init.ts](https://github.com/sanity-io/runtime-cli/blob/v12.3.0/src/commands/blueprints/init.ts)_
255
255
 
256
256
  ## `sanity-run blueprints logs`
257
257
 
@@ -273,7 +273,7 @@ EXAMPLES
273
273
  $ sanity-run blueprints logs --watch
274
274
  ```
275
275
 
276
- _See code: [src/commands/blueprints/logs.ts](https://github.com/sanity-io/runtime-cli/blob/v12.2.0/src/commands/blueprints/logs.ts)_
276
+ _See code: [src/commands/blueprints/logs.ts](https://github.com/sanity-io/runtime-cli/blob/v12.3.0/src/commands/blueprints/logs.ts)_
277
277
 
278
278
  ## `sanity-run blueprints plan`
279
279
 
@@ -290,7 +290,7 @@ EXAMPLES
290
290
  $ sanity-run blueprints plan
291
291
  ```
292
292
 
293
- _See code: [src/commands/blueprints/plan.ts](https://github.com/sanity-io/runtime-cli/blob/v12.2.0/src/commands/blueprints/plan.ts)_
293
+ _See code: [src/commands/blueprints/plan.ts](https://github.com/sanity-io/runtime-cli/blob/v12.3.0/src/commands/blueprints/plan.ts)_
294
294
 
295
295
  ## `sanity-run blueprints stacks`
296
296
 
@@ -315,7 +315,7 @@ EXAMPLES
315
315
  $ sanity-run blueprints stacks --organization-id <organizationId>
316
316
  ```
317
317
 
318
- _See code: [src/commands/blueprints/stacks.ts](https://github.com/sanity-io/runtime-cli/blob/v12.2.0/src/commands/blueprints/stacks.ts)_
318
+ _See code: [src/commands/blueprints/stacks.ts](https://github.com/sanity-io/runtime-cli/blob/v12.3.0/src/commands/blueprints/stacks.ts)_
319
319
 
320
320
  ## `sanity-run functions add`
321
321
 
@@ -357,7 +357,7 @@ EXAMPLES
357
357
  $ sanity-run functions add --name my-function --type document-create --type document-update --lang js
358
358
  ```
359
359
 
360
- _See code: [src/commands/functions/add.ts](https://github.com/sanity-io/runtime-cli/blob/v12.2.0/src/commands/functions/add.ts)_
360
+ _See code: [src/commands/functions/add.ts](https://github.com/sanity-io/runtime-cli/blob/v12.3.0/src/commands/functions/add.ts)_
361
361
 
362
362
  ## `sanity-run functions dev`
363
363
 
@@ -380,7 +380,7 @@ EXAMPLES
380
380
  $ sanity-run functions dev --host 127.0.0.1 --port 8974
381
381
  ```
382
382
 
383
- _See code: [src/commands/functions/dev.ts](https://github.com/sanity-io/runtime-cli/blob/v12.2.0/src/commands/functions/dev.ts)_
383
+ _See code: [src/commands/functions/dev.ts](https://github.com/sanity-io/runtime-cli/blob/v12.3.0/src/commands/functions/dev.ts)_
384
384
 
385
385
  ## `sanity-run functions env add NAME KEY VALUE`
386
386
 
@@ -402,7 +402,7 @@ EXAMPLES
402
402
  $ sanity-run functions env add MyFunction API_URL https://api.example.com/
403
403
  ```
404
404
 
405
- _See code: [src/commands/functions/env/add.ts](https://github.com/sanity-io/runtime-cli/blob/v12.2.0/src/commands/functions/env/add.ts)_
405
+ _See code: [src/commands/functions/env/add.ts](https://github.com/sanity-io/runtime-cli/blob/v12.3.0/src/commands/functions/env/add.ts)_
406
406
 
407
407
  ## `sanity-run functions env list NAME`
408
408
 
@@ -422,7 +422,7 @@ EXAMPLES
422
422
  $ sanity-run functions env list MyFunction
423
423
  ```
424
424
 
425
- _See code: [src/commands/functions/env/list.ts](https://github.com/sanity-io/runtime-cli/blob/v12.2.0/src/commands/functions/env/list.ts)_
425
+ _See code: [src/commands/functions/env/list.ts](https://github.com/sanity-io/runtime-cli/blob/v12.3.0/src/commands/functions/env/list.ts)_
426
426
 
427
427
  ## `sanity-run functions env remove NAME KEY`
428
428
 
@@ -443,7 +443,7 @@ EXAMPLES
443
443
  $ sanity-run functions env remove MyFunction API_URL
444
444
  ```
445
445
 
446
- _See code: [src/commands/functions/env/remove.ts](https://github.com/sanity-io/runtime-cli/blob/v12.2.0/src/commands/functions/env/remove.ts)_
446
+ _See code: [src/commands/functions/env/remove.ts](https://github.com/sanity-io/runtime-cli/blob/v12.3.0/src/commands/functions/env/remove.ts)_
447
447
 
448
448
  ## `sanity-run functions logs NAME`
449
449
 
@@ -477,7 +477,7 @@ EXAMPLES
477
477
  $ sanity-run functions logs <name> --delete
478
478
  ```
479
479
 
480
- _See code: [src/commands/functions/logs.ts](https://github.com/sanity-io/runtime-cli/blob/v12.2.0/src/commands/functions/logs.ts)_
480
+ _See code: [src/commands/functions/logs.ts](https://github.com/sanity-io/runtime-cli/blob/v12.3.0/src/commands/functions/logs.ts)_
481
481
 
482
482
  ## `sanity-run functions test NAME`
483
483
 
@@ -526,7 +526,7 @@ EXAMPLES
526
526
  $ sanity-run functions test <name> --event update --data-before '{ "title": "before" }' --data-after '{ "title": "after" }'
527
527
  ```
528
528
 
529
- _See code: [src/commands/functions/test.ts](https://github.com/sanity-io/runtime-cli/blob/v12.2.0/src/commands/functions/test.ts)_
529
+ _See code: [src/commands/functions/test.ts](https://github.com/sanity-io/runtime-cli/blob/v12.3.0/src/commands/functions/test.ts)_
530
530
 
531
531
  ## `sanity-run help [COMMAND]`
532
532
 
@@ -1,16 +1,15 @@
1
1
  import AdmZip from 'adm-zip';
2
- import type { AuthParams, FunctionResource } from '../../utils/types.js';
2
+ import type { AuthParams, CollectionFunction, FunctionResource } from '../../utils/types.js';
3
3
  export declare const ASSET_CHECK_URL: string;
4
4
  export declare const ASSET_STASH_URL: string;
5
5
  export declare function stashAsset({ resource, auth, }: {
6
- resource: FunctionResource;
6
+ resource: FunctionResource | CollectionFunction;
7
7
  auth: AuthParams;
8
8
  }): Promise<{
9
9
  success: boolean;
10
+ assetId?: string;
10
11
  exists?: boolean;
11
12
  hash?: string;
12
- assetId?: string;
13
- outputPath?: string;
14
13
  error?: string;
15
14
  }>;
16
15
  export declare function hashBuffer(buffer: Buffer): string;
@@ -1,5 +1,6 @@
1
1
  import crypto from 'node:crypto';
2
2
  import fs from 'node:fs';
3
+ import os from 'node:os';
3
4
  import path from 'node:path';
4
5
  import { cwd } from 'node:process';
5
6
  import AdmZip from 'adm-zip';
@@ -10,12 +11,68 @@ import { shouldAutoResolveDependencies } from '../../utils/functions/should-auto
10
11
  import { shouldTranspileFunction } from '../../utils/functions/should-transpile.js';
11
12
  import getHeaders from '../../utils/get-headers.js';
12
13
  import { transpileFunction } from '../../utils/transpile/transpile-function.js';
14
+ import { isLocalFunctionCollection } from '../../utils/types.js';
13
15
  const { apiUrl } = config;
14
16
  const ASSETS_URL = `${apiUrl}vX/blueprints/assets`;
15
17
  export const ASSET_CHECK_URL = `${ASSETS_URL}/check`;
16
18
  export const ASSET_STASH_URL = `${ASSETS_URL}/stash`;
17
19
  const MAX_ASSET_SIZE = 209_715_200; // 200 MB in bytes
18
20
  export async function stashAsset({ resource, auth, }) {
21
+ const isCollection = isLocalFunctionCollection(resource);
22
+ const functions = isCollection ? resource.functions : [resource];
23
+ const combinedAsset = [];
24
+ try {
25
+ for (const func of functions) {
26
+ const prepResult = await prepareAsset({ resource: func, auth });
27
+ if (prepResult.success && prepResult.outputPath && prepResult.cleanup) {
28
+ combinedAsset.push({
29
+ name: func.name,
30
+ partAsset: { outputPath: prepResult.outputPath, cleanup: prepResult.cleanup },
31
+ });
32
+ }
33
+ }
34
+ if (combinedAsset.length !== functions.length) {
35
+ const processedNames = new Set(combinedAsset.map((asset) => asset.name));
36
+ const failedFunctions = functions.filter((func) => !processedNames.has(func.name));
37
+ return {
38
+ success: false,
39
+ error: `Failed to prepare ${failedFunctions.length} function(s) in collection: ${failedFunctions.map((f) => f.name).join(', ')}`,
40
+ };
41
+ }
42
+ let result = { success: false };
43
+ if (isCollection) {
44
+ const combinedResult = await combineAssets(combinedAsset);
45
+ if (combinedResult.combinedPath && combinedResult.cleanup) {
46
+ result = await stashSingleAsset({
47
+ outputPath: combinedResult.combinedPath,
48
+ cleanup: combinedResult.cleanup,
49
+ resource: resource,
50
+ auth,
51
+ collectionInfo: {
52
+ name: resource.name,
53
+ functionCount: resource.functions.length,
54
+ },
55
+ });
56
+ }
57
+ else if (combinedResult.error) {
58
+ result = { success: false, error: combinedResult.error };
59
+ }
60
+ }
61
+ else {
62
+ result = await stashSingleAsset({
63
+ outputPath: combinedAsset[0].partAsset.outputPath,
64
+ cleanup: combinedAsset[0].partAsset.cleanup,
65
+ resource: resource,
66
+ auth,
67
+ });
68
+ }
69
+ return result;
70
+ }
71
+ catch (err) {
72
+ return { success: false, error: err instanceof Error ? err.message : `${err}` };
73
+ }
74
+ }
75
+ async function prepareAsset({ resource, }) {
19
76
  if (!resource.src)
20
77
  throw new Error('Resource src is required');
21
78
  let functionPath = path.join(cwd(), resource.src);
@@ -41,7 +98,60 @@ export async function stashAsset({ resource, auth, }) {
41
98
  if (size > MAX_ASSET_SIZE) {
42
99
  throw new Error('Resource is larger than max asset size of 200 MB.');
43
100
  }
44
- const { b64, hash } = await pathToB64ZipHash(functionPath);
101
+ return { success: true, outputPath: functionPath, cleanup };
102
+ }
103
+ catch (err) {
104
+ return { success: false, error: err instanceof Error ? err.message : `${err}` };
105
+ }
106
+ }
107
+ async function combineAssets(allAssets) {
108
+ const combinedPath = path.join(os.tmpdir(), `assets-${Date.now()}`);
109
+ await fs.promises.mkdir(combinedPath, { recursive: true });
110
+ try {
111
+ for (const asset of allAssets) {
112
+ try {
113
+ await fs.promises.mkdir(path.join(combinedPath, asset.name), { recursive: true });
114
+ await fs.promises.cp(asset.partAsset.outputPath, path.join(combinedPath, asset.name), {
115
+ recursive: true,
116
+ });
117
+ await asset.partAsset.cleanup();
118
+ }
119
+ catch (err) {
120
+ // Clean up any partial work
121
+ try {
122
+ await fs.promises.rm(combinedPath, { recursive: true, force: true });
123
+ }
124
+ catch {
125
+ // Ignore cleanup errors
126
+ }
127
+ // Clean up remaining individual assets
128
+ await Promise.allSettled(allAssets.map((a) => a.partAsset.cleanup()));
129
+ return {
130
+ error: `Failed to combine asset for function '${asset.name}': ${err instanceof Error ? err.message : String(err)}`,
131
+ };
132
+ }
133
+ }
134
+ const cleanupCombined = async () => {
135
+ await fs.promises.rm(combinedPath, { recursive: true, force: true });
136
+ };
137
+ return { combinedPath, cleanup: cleanupCombined };
138
+ }
139
+ catch (err) {
140
+ // Clean up any partial work
141
+ try {
142
+ await fs.promises.rm(combinedPath, { recursive: true, force: true });
143
+ }
144
+ catch {
145
+ // Ignore cleanup errors
146
+ }
147
+ // Clean up individual assets that were successfully prepared
148
+ await Promise.allSettled(allAssets.map((asset) => asset.partAsset.cleanup()));
149
+ return { error: err instanceof Error ? err.message : `${err}` };
150
+ }
151
+ }
152
+ async function stashSingleAsset({ outputPath, cleanup, resource, auth, collectionInfo, }) {
153
+ try {
154
+ const { b64, hash } = await pathToB64ZipHash(outputPath);
45
155
  try {
46
156
  const checkResponse = await fetch(`${ASSET_CHECK_URL}/${hash}`, {
47
157
  method: 'GET',
@@ -66,7 +176,13 @@ export async function stashAsset({ resource, auth, }) {
66
176
  if (assetResponse.ok) {
67
177
  return { success: true, assetId: assetJson.id, exists: false, hash };
68
178
  }
69
- return { success: false, error: assetJson.message || 'Unknown error' };
179
+ const contextMsg = collectionInfo
180
+ ? ` (part of collection '${collectionInfo.name}' with ${collectionInfo.functionCount} functions)`
181
+ : '';
182
+ return {
183
+ success: false,
184
+ error: `Failed to stash asset for ${resource.name}${contextMsg}: ${assetJson.message || 'Unknown error'}`,
185
+ };
70
186
  }
71
187
  catch (err) {
72
188
  let error = '';
@@ -14,23 +14,37 @@ export async function blueprintDeployCore(options) {
14
14
  try {
15
15
  const { resources } = blueprint.parsedBlueprint;
16
16
  const validResources = resources?.filter((r) => r.type) || [];
17
- let functionResources = validResources.filter(isLocalFunctionResource);
18
- const experimentalFunctionCollections = validResources.filter(isLocalFunctionCollection);
19
- if (experimentalFunctionCollections.length > 0) {
20
- const allFunctionResources = experimentalFunctionCollections.flatMap((r) => r.functions);
21
- functionResources = [...functionResources, ...allFunctionResources];
22
- }
17
+ const functionResources = validResources.filter(isLocalFunctionResource);
18
+ const functionCollections = validResources.filter(isLocalFunctionCollection);
19
+ const allFunctionResources = [...functionResources, ...functionCollections];
23
20
  // First stash all function assets
24
- if (functionResources.length > 0) {
21
+ if (allFunctionResources.length > 0) {
25
22
  log('Processing function assets...');
26
- for (const resource of functionResources) {
23
+ for (const resource of allFunctionResources) {
27
24
  const fnSpinner = ora({ text: `Processing ${resource.name}...`, prefixText: ' ' }).start();
28
25
  const result = await stashAsset({ resource, auth });
29
26
  if (result.success && result.assetId) {
30
- const src = resource.src;
31
27
  resource.src = result.assetId;
32
- fnSpinner.succeed(`${resource.name} ${niceId(result.assetId)}`);
33
- log(` Source: ${src}`);
28
+ if (isLocalFunctionCollection(resource)) {
29
+ try {
30
+ for (const func of resource.functions) {
31
+ func.src = func.name;
32
+ }
33
+ fnSpinner.succeed(`${resource.name} collection ${niceId(result.assetId)} (${resource.functions.length} functions)`);
34
+ log(` Functions: ${resource.functions.map((f) => f.name).join(', ')}`);
35
+ }
36
+ catch (err) {
37
+ fnSpinner.fail(`Failed to update function collection ${resource.name}`);
38
+ return {
39
+ success: false,
40
+ error: `Error updating function collection '${resource.name}': ${err instanceof Error ? err.message : String(err)}`,
41
+ };
42
+ }
43
+ }
44
+ else {
45
+ fnSpinner.succeed(`${resource.name} ${niceId(result.assetId)}`);
46
+ log(` Source: ${resource.src}`);
47
+ }
34
48
  if (verbose) {
35
49
  if (result.hash) {
36
50
  if (result.hash.length > 24) {
@@ -45,7 +59,10 @@ export async function blueprintDeployCore(options) {
45
59
  }
46
60
  }
47
61
  else {
48
- fnSpinner.fail(`Failed uploading ${resource.name} asset, deploy has stopped`);
62
+ const errorMsg = isLocalFunctionCollection(resource)
63
+ ? `Failed uploading function collection ${resource.name} (${resource.functions?.length || 0} functions), deploy has stopped`
64
+ : `Failed uploading ${resource.name} asset, deploy has stopped`;
65
+ fnSpinner.fail(errorMsg);
49
66
  log(` Error: ${result.error}`);
50
67
  return { success: false, error: result.error || 'Failed to process function resource' };
51
68
  }
@@ -57,9 +57,7 @@ export declare function isLocalFunctionResource(r: BlueprintResource): r is Func
57
57
  export declare function isDocumentFunctionResource<T extends BlueprintResource>(r: T): r is T & FunctionResourceDocument;
58
58
  export declare function isMediaLibraryAssetFunctionResource<T extends BlueprintResource>(r: T): r is T & FunctionResourceMediaLibraryAsset;
59
59
  export declare function isScheduleFunctionResource<T extends BlueprintResource>(r: T): r is T & FunctionResourceSchedule;
60
- export declare function isLocalFunctionCollection<T extends BlueprintResource>(r: T): r is T & {
61
- functions: Array<FunctionResourceBase>;
62
- };
60
+ export declare function isLocalFunctionCollection<T extends BlueprintResource>(r: T): r is T & FunctionsCollection;
63
61
  export declare function isScheduleEvent(e: unknown): e is FunctionResourceScheduleEvent;
64
62
  export declare function isCorsOriginResource(r: unknown): r is BlueprintCorsOriginResource;
65
63
  export declare function isRoleResource(r: unknown): r is BlueprintRoleResource;
@@ -90,6 +88,26 @@ interface FunctionResourceSchedule extends FunctionResourceBase {
90
88
  type: typeof SANITY_FUNCTION_SCHEDULE;
91
89
  event: FunctionResourceScheduleEvent;
92
90
  }
91
+ export interface CollectionFunction {
92
+ type: typeof SANITY_FUNCTION_DOCUMENT | typeof SANITY_FUNCTION_SCHEDULE | typeof SANITY_FUNCTION_MEDIA_LIBRARY_ASSET;
93
+ src: string;
94
+ name: string;
95
+ displayName?: string;
96
+ event?: GroqRuleBase | FunctionResourceScheduleEvent;
97
+ memory?: number;
98
+ timeout?: number;
99
+ env?: Record<string, string>;
100
+ }
101
+ /** @internal */
102
+ export interface FunctionsCollection extends BlueprintResource {
103
+ type: 'sanity.experimental.functions-collection';
104
+ name: string;
105
+ src?: string;
106
+ functions: Array<FunctionResourceBase>;
107
+ }
108
+ export interface CorsResource extends BlueprintResource {
109
+ origin?: string;
110
+ }
93
111
  export interface Stack {
94
112
  id: string;
95
113
  name: string;
@@ -1429,5 +1429,5 @@
1429
1429
  ]
1430
1430
  }
1431
1431
  },
1432
- "version": "12.2.0"
1432
+ "version": "12.3.0"
1433
1433
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sanity/runtime-cli",
3
3
  "description": "Sanity's Runtime CLI for Blueprints and Functions",
4
- "version": "12.2.0",
4
+ "version": "12.3.0",
5
5
  "author": "Sanity Runtime Team",
6
6
  "type": "module",
7
7
  "license": "MIT",