@sanity/runtime-cli 14.5.1 → 14.6.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/14.5.1 linux-x64 node-v24.14.0
23
+ @sanity/runtime-cli/14.6.0 linux-x64 node-v24.14.0
24
24
  $ sanity-run --help [COMMAND]
25
25
  USAGE
26
26
  $ sanity-run COMMAND
@@ -98,7 +98,7 @@ EXAMPLES
98
98
  $ sanity-run blueprints add function --name my-function --fn-type document-create --fn-type document-update --lang js
99
99
  ```
100
100
 
101
- _See code: [src/commands/blueprints/add.ts](https://github.com/sanity-io/runtime-cli/blob/v14.5.1/src/commands/blueprints/add.ts)_
101
+ _See code: [src/commands/blueprints/add.ts](https://github.com/sanity-io/runtime-cli/blob/v14.6.0/src/commands/blueprints/add.ts)_
102
102
 
103
103
  ## `sanity-run blueprints config`
104
104
 
@@ -133,7 +133,7 @@ EXAMPLES
133
133
  $ sanity-run blueprints config --edit --project-id <projectId> --stack <name-or-id>
134
134
  ```
135
135
 
136
- _See code: [src/commands/blueprints/config.ts](https://github.com/sanity-io/runtime-cli/blob/v14.5.1/src/commands/blueprints/config.ts)_
136
+ _See code: [src/commands/blueprints/config.ts](https://github.com/sanity-io/runtime-cli/blob/v14.6.0/src/commands/blueprints/config.ts)_
137
137
 
138
138
  ## `sanity-run blueprints deploy`
139
139
 
@@ -170,7 +170,7 @@ EXAMPLES
170
170
  $ sanity-run blueprints deploy --fn-installer npm
171
171
  ```
172
172
 
173
- _See code: [src/commands/blueprints/deploy.ts](https://github.com/sanity-io/runtime-cli/blob/v14.5.1/src/commands/blueprints/deploy.ts)_
173
+ _See code: [src/commands/blueprints/deploy.ts](https://github.com/sanity-io/runtime-cli/blob/v14.6.0/src/commands/blueprints/deploy.ts)_
174
174
 
175
175
  ## `sanity-run blueprints destroy`
176
176
 
@@ -202,7 +202,7 @@ EXAMPLES
202
202
  $ sanity-run blueprints destroy --stack <name-or-id> --project-id <projectId> --force --no-wait
203
203
  ```
204
204
 
205
- _See code: [src/commands/blueprints/destroy.ts](https://github.com/sanity-io/runtime-cli/blob/v14.5.1/src/commands/blueprints/destroy.ts)_
205
+ _See code: [src/commands/blueprints/destroy.ts](https://github.com/sanity-io/runtime-cli/blob/v14.6.0/src/commands/blueprints/destroy.ts)_
206
206
 
207
207
  ## `sanity-run blueprints doctor`
208
208
 
@@ -228,7 +228,7 @@ DESCRIPTION
228
228
  issues.
229
229
  ```
230
230
 
231
- _See code: [src/commands/blueprints/doctor.ts](https://github.com/sanity-io/runtime-cli/blob/v14.5.1/src/commands/blueprints/doctor.ts)_
231
+ _See code: [src/commands/blueprints/doctor.ts](https://github.com/sanity-io/runtime-cli/blob/v14.6.0/src/commands/blueprints/doctor.ts)_
232
232
 
233
233
  ## `sanity-run blueprints info`
234
234
 
@@ -258,7 +258,7 @@ EXAMPLES
258
258
  $ sanity-run blueprints info --stack <name-or-id>
259
259
  ```
260
260
 
261
- _See code: [src/commands/blueprints/info.ts](https://github.com/sanity-io/runtime-cli/blob/v14.5.1/src/commands/blueprints/info.ts)_
261
+ _See code: [src/commands/blueprints/info.ts](https://github.com/sanity-io/runtime-cli/blob/v14.6.0/src/commands/blueprints/info.ts)_
262
262
 
263
263
  ## `sanity-run blueprints init [DIR]`
264
264
 
@@ -308,7 +308,7 @@ EXAMPLES
308
308
  $ sanity-run blueprints init --blueprint-type <json|js|ts> --stack-name <stackName>
309
309
  ```
310
310
 
311
- _See code: [src/commands/blueprints/init.ts](https://github.com/sanity-io/runtime-cli/blob/v14.5.1/src/commands/blueprints/init.ts)_
311
+ _See code: [src/commands/blueprints/init.ts](https://github.com/sanity-io/runtime-cli/blob/v14.6.0/src/commands/blueprints/init.ts)_
312
312
 
313
313
  ## `sanity-run blueprints logs`
314
314
 
@@ -337,7 +337,7 @@ EXAMPLES
337
337
  $ sanity-run blueprints logs --watch
338
338
  ```
339
339
 
340
- _See code: [src/commands/blueprints/logs.ts](https://github.com/sanity-io/runtime-cli/blob/v14.5.1/src/commands/blueprints/logs.ts)_
340
+ _See code: [src/commands/blueprints/logs.ts](https://github.com/sanity-io/runtime-cli/blob/v14.6.0/src/commands/blueprints/logs.ts)_
341
341
 
342
342
  ## `sanity-run blueprints plan`
343
343
 
@@ -363,7 +363,7 @@ EXAMPLES
363
363
  $ sanity-run blueprints plan
364
364
  ```
365
365
 
366
- _See code: [src/commands/blueprints/plan.ts](https://github.com/sanity-io/runtime-cli/blob/v14.5.1/src/commands/blueprints/plan.ts)_
366
+ _See code: [src/commands/blueprints/plan.ts](https://github.com/sanity-io/runtime-cli/blob/v14.6.0/src/commands/blueprints/plan.ts)_
367
367
 
368
368
  ## `sanity-run blueprints stacks`
369
369
 
@@ -392,7 +392,7 @@ EXAMPLES
392
392
  $ sanity-run blueprints stacks --organization-id <organizationId>
393
393
  ```
394
394
 
395
- _See code: [src/commands/blueprints/stacks.ts](https://github.com/sanity-io/runtime-cli/blob/v14.5.1/src/commands/blueprints/stacks.ts)_
395
+ _See code: [src/commands/blueprints/stacks.ts](https://github.com/sanity-io/runtime-cli/blob/v14.6.0/src/commands/blueprints/stacks.ts)_
396
396
 
397
397
  ## `sanity-run functions add`
398
398
 
@@ -441,7 +441,7 @@ EXAMPLES
441
441
  $ sanity-run functions add --name my-function --type document-create --type document-update --lang js
442
442
  ```
443
443
 
444
- _See code: [src/commands/functions/add.ts](https://github.com/sanity-io/runtime-cli/blob/v14.5.1/src/commands/functions/add.ts)_
444
+ _See code: [src/commands/functions/add.ts](https://github.com/sanity-io/runtime-cli/blob/v14.6.0/src/commands/functions/add.ts)_
445
445
 
446
446
  ## `sanity-run functions dev`
447
447
 
@@ -475,7 +475,7 @@ EXAMPLES
475
475
  $ sanity-run functions dev --timeout 60
476
476
  ```
477
477
 
478
- _See code: [src/commands/functions/dev.ts](https://github.com/sanity-io/runtime-cli/blob/v14.5.1/src/commands/functions/dev.ts)_
478
+ _See code: [src/commands/functions/dev.ts](https://github.com/sanity-io/runtime-cli/blob/v14.6.0/src/commands/functions/dev.ts)_
479
479
 
480
480
  ## `sanity-run functions env add NAME KEY VALUE`
481
481
 
@@ -502,7 +502,7 @@ EXAMPLES
502
502
  $ sanity-run functions env add MyFunction API_URL https://api.example.com/
503
503
  ```
504
504
 
505
- _See code: [src/commands/functions/env/add.ts](https://github.com/sanity-io/runtime-cli/blob/v14.5.1/src/commands/functions/env/add.ts)_
505
+ _See code: [src/commands/functions/env/add.ts](https://github.com/sanity-io/runtime-cli/blob/v14.6.0/src/commands/functions/env/add.ts)_
506
506
 
507
507
  ## `sanity-run functions env list NAME`
508
508
 
@@ -526,7 +526,7 @@ EXAMPLES
526
526
  $ sanity-run functions env list MyFunction
527
527
  ```
528
528
 
529
- _See code: [src/commands/functions/env/list.ts](https://github.com/sanity-io/runtime-cli/blob/v14.5.1/src/commands/functions/env/list.ts)_
529
+ _See code: [src/commands/functions/env/list.ts](https://github.com/sanity-io/runtime-cli/blob/v14.6.0/src/commands/functions/env/list.ts)_
530
530
 
531
531
  ## `sanity-run functions env remove NAME KEY`
532
532
 
@@ -552,7 +552,7 @@ EXAMPLES
552
552
  $ sanity-run functions env remove MyFunction API_URL
553
553
  ```
554
554
 
555
- _See code: [src/commands/functions/env/remove.ts](https://github.com/sanity-io/runtime-cli/blob/v14.5.1/src/commands/functions/env/remove.ts)_
555
+ _See code: [src/commands/functions/env/remove.ts](https://github.com/sanity-io/runtime-cli/blob/v14.6.0/src/commands/functions/env/remove.ts)_
556
556
 
557
557
  ## `sanity-run functions logs [NAME]`
558
558
 
@@ -592,7 +592,7 @@ EXAMPLES
592
592
  $ sanity-run functions logs <name> --delete
593
593
  ```
594
594
 
595
- _See code: [src/commands/functions/logs.ts](https://github.com/sanity-io/runtime-cli/blob/v14.5.1/src/commands/functions/logs.ts)_
595
+ _See code: [src/commands/functions/logs.ts](https://github.com/sanity-io/runtime-cli/blob/v14.6.0/src/commands/functions/logs.ts)_
596
596
 
597
597
  ## `sanity-run functions test [NAME]`
598
598
 
@@ -646,7 +646,7 @@ EXAMPLES
646
646
  $ sanity-run functions test <name> --event update --data-before '{ "title": "before" }' --data-after '{ "title": "after" }'
647
647
  ```
648
648
 
649
- _See code: [src/commands/functions/test.ts](https://github.com/sanity-io/runtime-cli/blob/v14.5.1/src/commands/functions/test.ts)_
649
+ _See code: [src/commands/functions/test.ts](https://github.com/sanity-io/runtime-cli/blob/v14.6.0/src/commands/functions/test.ts)_
650
650
 
651
651
  ## `sanity-run help [COMMAND]`
652
652
 
@@ -666,5 +666,5 @@ DESCRIPTION
666
666
  Display help for sanity-run.
667
667
  ```
668
668
 
669
- _See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v6.2.37/src/commands/help.ts)_
669
+ _See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/6.2.38/src/commands/help.ts)_
670
670
  <!-- commandsstop -->
@@ -29,17 +29,22 @@ export declare function writeConfigFile(blueprintFilePath: string, options: {
29
29
  organizationId?: string;
30
30
  projectId?: string;
31
31
  }): BlueprintsConfig;
32
+ export interface ConfigPatch {
33
+ organizationId?: string | null;
34
+ projectId?: string | null;
35
+ stackId?: string;
36
+ }
32
37
  /**
33
38
  * Update the config file with the given properties.
34
39
  * Config file must already exist.
35
40
  * Adds timestamp. No validation or transformation is performed.
36
41
  * @param blueprintFilePath - the path to the blueprint file
37
42
  * @param updateableProperties - the properties to update
38
- * @param updateableProperties.organizationId - the organization ID
39
- * @param updateableProperties.projectId - the project ID
43
+ * @param updateableProperties.organizationId - the organization ID (null to remove)
44
+ * @param updateableProperties.projectId - the project ID (null to remove)
40
45
  * @param updateableProperties.stackId - the stack ID
41
46
  */
42
- export declare function patchConfigFile(blueprintFilePath: string, updateableProperties: ConfigUpdate): BlueprintsConfig;
47
+ export declare function patchConfigFile(blueprintFilePath: string, updateableProperties: ConfigPatch): BlueprintsConfig;
43
48
  /**
44
49
  * Find and write an organizationId to the config file by getting it from the projectId
45
50
  * @throws {Error} if unable to fetch project
@@ -57,8 +57,8 @@ export function writeConfigFile(blueprintFilePath, options) {
57
57
  * Adds timestamp. No validation or transformation is performed.
58
58
  * @param blueprintFilePath - the path to the blueprint file
59
59
  * @param updateableProperties - the properties to update
60
- * @param updateableProperties.organizationId - the organization ID
61
- * @param updateableProperties.projectId - the project ID
60
+ * @param updateableProperties.organizationId - the organization ID (null to remove)
61
+ * @param updateableProperties.projectId - the project ID (null to remove)
62
62
  * @param updateableProperties.stackId - the stack ID
63
63
  */
64
64
  export function patchConfigFile(blueprintFilePath, updateableProperties) {
@@ -66,13 +66,15 @@ export function patchConfigFile(blueprintFilePath, updateableProperties) {
66
66
  if (!existingConfig)
67
67
  throw new Error('No config file found');
68
68
  const { configPath, ...existingConfigProperties } = existingConfig;
69
- const newConfig = {
69
+ const merged = {
70
70
  blueprintConfigVersion: BLUEPRINT_CONFIG_VERSION, // patch doesn't overwrite versions
71
71
  runtimeCliVersion: RUNTIME_CLI_VERSION,
72
72
  ...existingConfigProperties,
73
73
  ...updateableProperties,
74
74
  updatedAt: Date.now(),
75
75
  };
76
+ // remove value if set to null
77
+ const newConfig = Object.fromEntries(Object.entries(merged).filter(([_, v]) => v !== null));
76
78
  writeFileSync(configPath, JSON.stringify(newConfig, null, 2));
77
79
  return newConfig;
78
80
  }
@@ -107,6 +107,18 @@ interface DestroyStackResponse {
107
107
  stack: Stack;
108
108
  }
109
109
  export declare function resolveStackIdByNameOrId(value: string, auth: AuthParams, logger: ReturnType<typeof Logger>): Promise<string>;
110
+ interface PromoteStackResponse {
111
+ ok: boolean;
112
+ error: string | null;
113
+ stack: Stack & {
114
+ alreadyPromoted: boolean;
115
+ };
116
+ }
117
+ export declare function promoteStack({ stackId, auth, logger, }: {
118
+ stackId: string;
119
+ auth: AuthParams;
120
+ logger: ReturnType<typeof Logger>;
121
+ }): Promise<PromoteStackResponse>;
110
122
  export declare function destroyStack({ stackId, auth, logger, }: {
111
123
  stackId: string;
112
124
  auth: AuthParams;
@@ -94,8 +94,9 @@ export async function planStack({ stackId, document, auth, logger, }) {
94
94
  return { ok: true, error: null, problems: null, deploymentPlan: data };
95
95
  }
96
96
  export async function resolveStackIdByNameOrId(value, auth, logger) {
97
- if (value.startsWith('ST-') && value.length === 13)
97
+ if (value.startsWith('ST-') && (value.length === 13 || value.length === 11))
98
98
  return value;
99
+ // ID prefixed w 'ST-' standard IDs: 13 old project IDs: 11
99
100
  const result = await listStacks(auth, logger);
100
101
  if (!result.ok)
101
102
  throw new Error(result.error || 'Failed to list stacks');
@@ -104,6 +105,19 @@ export async function resolveStackIdByNameOrId(value, auth, logger) {
104
105
  throw new Error(`No stack found with name "${value}"`);
105
106
  return match.id;
106
107
  }
108
+ export async function promoteStack({ stackId, auth, logger, }) {
109
+ const fetchFn = createTracedFetch(logger);
110
+ const response = await fetchFn(`${stacksUrl}/${stackId}/promote`, {
111
+ method: 'POST',
112
+ headers: getHeaders(auth),
113
+ });
114
+ const data = await response.json();
115
+ return {
116
+ ok: response.ok,
117
+ error: response.ok ? null : data.message,
118
+ stack: data,
119
+ };
120
+ }
107
121
  export async function destroyStack({ stackId, auth, logger, }) {
108
122
  const fetchFn = createTracedFetch(logger);
109
123
  const response = await fetchFn(`${stacksUrl}/${stackId}`, {
@@ -7,5 +7,5 @@ export default class LogsCommand extends DeployedStackCommand<typeof LogsCommand
7
7
  stack: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
8
  watch: import("@oclif/core/interfaces").BooleanFlag<boolean>;
9
9
  };
10
- run(): Promise<void>;
10
+ run(): Promise<void | Record<string, unknown>>;
11
11
  }
@@ -22,7 +22,7 @@ If you're not seeing expected logs, verify your Stack is deployed with 'blueprin
22
22
  }),
23
23
  };
24
24
  async run() {
25
- const { success, streaming, error } = await blueprintLogsCore({
25
+ const { success, streaming, error, json } = await blueprintLogsCore({
26
26
  bin: this.config.bin,
27
27
  log: Logger(this.log.bind(this), this.flags),
28
28
  auth: this.auth,
@@ -35,5 +35,6 @@ If you're not seeing expected logs, verify your Stack is deployed with 'blueprin
35
35
  return streaming;
36
36
  if (!success)
37
37
  this.error(error);
38
+ return json;
38
39
  }
39
40
  }
@@ -0,0 +1,13 @@
1
+ import { DeployedStackCommand } from '../../baseCommands.js';
2
+ export default class PromoteCommand extends DeployedStackCommand<typeof PromoteCommand> {
3
+ static summary: string;
4
+ static description: string;
5
+ static hidden: boolean;
6
+ static examples: string[];
7
+ static flags: {
8
+ stack: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
+ 'i-know-what-im-doing': import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
+ force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
+ };
12
+ run(): Promise<Record<string, unknown> | undefined>;
13
+ }
@@ -0,0 +1,45 @@
1
+ import { Flags } from '@oclif/core';
2
+ import { DeployedStackCommand } from '../../baseCommands.js';
3
+ import { blueprintPromoteCore } from '../../cores/blueprints/promote.js';
4
+ import { Logger } from '../../utils/logger.js';
5
+ export default class PromoteCommand extends DeployedStackCommand {
6
+ static summary = 'Promote a Stack deployment to a broader scope';
7
+ static description = `EXPERIMENTAL! Promotes a Stack, changing its scope from project to organization. This is a one-way trip.`;
8
+ static hidden = true;
9
+ static examples = [
10
+ '<%= config.bin %> <%= command.id %>',
11
+ '<%= config.bin %> <%= command.id %> --stack <name-or-id>',
12
+ ];
13
+ static flags = {
14
+ stack: Flags.string({
15
+ description: 'Stack name or ID to promote',
16
+ aliases: ['id'],
17
+ }),
18
+ 'i-know-what-im-doing': Flags.boolean({
19
+ description: 'Must be set',
20
+ default: false,
21
+ }),
22
+ force: Flags.boolean({
23
+ description: 'Skip confirmation prompt',
24
+ default: false,
25
+ }),
26
+ };
27
+ async run() {
28
+ const { success, error, data } = await blueprintPromoteCore({
29
+ bin: this.config.bin,
30
+ log: Logger(this.log.bind(this), this.flags),
31
+ token: this.sanityToken,
32
+ blueprint: this.blueprint,
33
+ stackId: this.stackId,
34
+ scopeType: this.scopeType,
35
+ scopeId: this.scopeId,
36
+ deployedStack: this.deployedStack,
37
+ auth: this.auth,
38
+ validateResources: this.flags['validate-resources'],
39
+ flags: this.flags,
40
+ });
41
+ if (!success)
42
+ this.error(error);
43
+ return data;
44
+ }
45
+ }
@@ -15,5 +15,5 @@ export default class LogsCommand extends DeployedStackCommand<typeof LogsCommand
15
15
  force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
16
16
  watch: import("@oclif/core/interfaces").BooleanFlag<boolean>;
17
17
  };
18
- run(): Promise<void>;
18
+ run(): Promise<Record<string, unknown> | undefined>;
19
19
  }
@@ -53,7 +53,7 @@ Use --watch (-w) to stream logs in real-time. Use --delete to clear all logs for
53
53
  }),
54
54
  };
55
55
  async run() {
56
- const { success, error } = await functionLogsCore({
56
+ const { success, error, json } = await functionLogsCore({
57
57
  bin: this.config.bin,
58
58
  log: Logger(this.log.bind(this), this.flags),
59
59
  error: (msg, options) => this.error(msg, options),
@@ -71,5 +71,6 @@ Use --watch (-w) to stream logs in real-time. Use --delete to clear all logs for
71
71
  });
72
72
  if (!success)
73
73
  this.error(error);
74
+ return json;
74
75
  }
75
76
  }
@@ -17,6 +17,8 @@ export declare const EVENT_MEDIA_LIBRARY_ASSET_UPDATE = "media-library-asset-upd
17
17
  export declare const EVENT_MEDIA_LIBRARY_ASSET_DELETE = "media-library-asset-delete";
18
18
  export declare const EVENT_SCHEDULED = "scheduled-function";
19
19
  export declare const FUNCTION_TYPES: string[];
20
+ export declare const PROJECT_SCOPED_FUNCTION_TYPES: ReadonlySet<string>;
21
+ export declare const ORGANIZATION_SCOPED_FUNCTION_TYPES: ReadonlySet<string>;
20
22
  export declare const MAX_ASSET_SIZE = 209715200;
21
23
  export declare const CONVERT_BYTES_TO_MB = 1048576;
22
24
  export declare const LABEL_DOCUMENT_CREATE = "Document Create";
package/dist/constants.js CHANGED
@@ -26,6 +26,13 @@ export const FUNCTION_TYPES = [
26
26
  EVENT_MEDIA_LIBRARY_ASSET_DELETE,
27
27
  EVENT_SCHEDULED,
28
28
  ];
29
+ export const PROJECT_SCOPED_FUNCTION_TYPES = new Set([
30
+ SANITY_FUNCTION_DOCUMENT,
31
+ SANITY_FUNCTION_MEDIA_LIBRARY_ASSET,
32
+ ]);
33
+ export const ORGANIZATION_SCOPED_FUNCTION_TYPES = new Set([
34
+ SANITY_FUNCTION_SCHEDULED,
35
+ ]);
29
36
  export const MAX_ASSET_SIZE = 209_715_200; // 200 MB in bytes
30
37
  export const CONVERT_BYTES_TO_MB = 1_048_576; // Used to convert bytes to megabytes (1024 * 1024)
31
38
  export const LABEL_DOCUMENT_CREATE = 'Document Create';
@@ -14,5 +14,7 @@ export type { BlueprintLogsOptions } from './logs.js';
14
14
  export { blueprintLogsCore } from './logs.js';
15
15
  export type { BlueprintPlanOptions } from './plan.js';
16
16
  export { blueprintPlanCore } from './plan.js';
17
+ export type { BlueprintPromoteOptions } from './promote.js';
18
+ export { blueprintPromoteCore } from './promote.js';
17
19
  export type { BlueprintStacksOptions } from './stacks.js';
18
20
  export { blueprintStacksCore } from './stacks.js';
@@ -6,4 +6,5 @@ export { blueprintInfoCore } from './info.js';
6
6
  export { blueprintInitCore } from './init.js';
7
7
  export { blueprintLogsCore } from './logs.js';
8
8
  export { blueprintPlanCore } from './plan.js';
9
+ export { blueprintPromoteCore } from './promote.js';
9
10
  export { blueprintStacksCore } from './stacks.js';
@@ -58,7 +58,7 @@ export async function blueprintLogsCore(options) {
58
58
  spinner.succeed(`${formatTitle('Blueprint', deployedStack.name)} Logs`);
59
59
  log(`Found ${styleText('bold', logs.length.toString())} log entries for Stack deployment ${niceId(stackId)}\n`);
60
60
  log(formatLogs(logs, verbose));
61
- return { success: true };
61
+ return { success: true, json: { logs } };
62
62
  }
63
63
  catch (err) {
64
64
  spinner.fail('Failed to retrieve Stack deployment logs');
@@ -0,0 +1,9 @@
1
+ import type { CoreResult, DeployedBlueprintConfig } from '../index.js';
2
+ export interface BlueprintPromoteOptions extends DeployedBlueprintConfig {
3
+ flags: {
4
+ 'i-know-what-im-doing': boolean;
5
+ force?: boolean;
6
+ verbose?: boolean;
7
+ };
8
+ }
9
+ export declare function blueprintPromoteCore(options: BlueprintPromoteOptions): Promise<CoreResult>;
@@ -0,0 +1,50 @@
1
+ import { confirm } from '@inquirer/prompts';
2
+ import { patchConfigFile } from '../../actions/blueprints/config.js';
3
+ import { promoteStack } from '../../actions/blueprints/stacks.js';
4
+ import { niceId } from '../../utils/display/presenters.js';
5
+ export async function blueprintPromoteCore(options) {
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
+ let message = `"${deployedStack.name}" ${niceId(deployedStack.id)}`;
11
+ if (deployedStack.scopeType === 'organization') {
12
+ message = `Stack ${message} is already org-scoped. Promote again?`;
13
+ }
14
+ else {
15
+ message = `Are you sure you want to promote Stack ${message}?`;
16
+ }
17
+ if (!flags.force) {
18
+ const confirmed = await confirm({ message });
19
+ if (!confirmed) {
20
+ return { success: false, error: 'Promotion cancelled by user' };
21
+ }
22
+ }
23
+ try {
24
+ const { ok, error, stack } = await promoteStack({ stackId, auth, logger: log });
25
+ if (!ok) {
26
+ return { success: false, error: error || 'Failed to promote Stack' };
27
+ }
28
+ log(`Stack "${stack.name}" ${niceId(stack.id)} promoted successfully`);
29
+ const { blueprintFilePath } = blueprint.fileInfo;
30
+ try {
31
+ patchConfigFile(blueprintFilePath, {
32
+ stackId: stack.id,
33
+ organizationId: stack.scopeId,
34
+ projectId: null,
35
+ });
36
+ log('Blueprint configuration updated');
37
+ }
38
+ catch {
39
+ return {
40
+ success: false,
41
+ error: 'Stack promoted successfully but failed to update local Blueprint configuration. No config file found.',
42
+ };
43
+ }
44
+ return { success: true, data: { stack } };
45
+ }
46
+ catch (error) {
47
+ const errorMessage = error instanceof Error ? error.message : String(error);
48
+ return { success: false, error: errorMessage };
49
+ }
50
+ }
@@ -21,7 +21,7 @@ const generateFunctionBlueprintResourceTemplate = (fnName, eventNames) => {
21
21
  definer = `defineMediaLibraryAssetFunction({name: '${fnName}', event: {on: [${eventOns.join(', ')}], resource: {type: 'media-library', id: 'my-media-library-id'}}}), // ← add this line`;
22
22
  break;
23
23
  case 'scheduled':
24
- definer = `defineScheduleFunction({name: '${fnName}', event: {expression: '0 0 * * *'}}), // ← add this line`;
24
+ definer = `defineScheduledFunction({name: '${fnName}', event: {expression: '0 0 * * *'}}), // ← add this line`;
25
25
  break;
26
26
  }
27
27
  return `
@@ -1,11 +1,14 @@
1
1
  import { update } from '../../../actions/functions/env/update.js';
2
2
  import { findFunctionInStack } from '../../../utils/find-function.js';
3
+ import { resolveFunctionAuth } from '../../../utils/functions/resolve-function-auth.js';
3
4
  import { styleText } from '../../../utils/style-text.js';
4
5
  export async function functionEnvAddCore(options) {
5
6
  const { args, log } = options;
6
7
  const spinner = log.ora(`Updating "${args.key}" environment variable in "${args.name}"`).start();
7
- const { externalId } = findFunctionInStack(options.deployedStack, args.name);
8
- const result = await update(externalId, args.key, args.value, options.auth, options.log);
8
+ const resource = findFunctionInStack(options.deployedStack, args.name);
9
+ const { externalId } = resource;
10
+ const auth = resolveFunctionAuth(options.auth, options.deployedStack, resource);
11
+ const result = await update(externalId, args.key, args.value, auth, options.log);
9
12
  if (!result.ok) {
10
13
  spinner.fail(`${styleText('red', 'Failed')} to update ${args.key}`);
11
14
  return {
@@ -1,10 +1,13 @@
1
1
  import { list } from '../../../actions/functions/env/list.js';
2
2
  import { findFunctionInStack } from '../../../utils/find-function.js';
3
+ import { resolveFunctionAuth } from '../../../utils/functions/resolve-function-auth.js';
3
4
  export async function functionEnvListCore(options) {
4
5
  const { args, log } = options;
5
6
  const spinner = log.ora(`Listing environment variables for "${args.name}"`).start();
6
- const { externalId } = findFunctionInStack(options.deployedStack, args.name);
7
- const result = await list(externalId, options.auth, options.log);
7
+ const resource = findFunctionInStack(options.deployedStack, args.name);
8
+ const { externalId } = resource;
9
+ const auth = resolveFunctionAuth(options.auth, options.deployedStack, resource);
10
+ const result = await list(externalId, auth, options.log);
8
11
  if (!result.ok) {
9
12
  spinner.stop();
10
13
  return { success: false, error: result.error || 'Unknown error' };
@@ -1,11 +1,14 @@
1
1
  import { remove } from '../../../actions/functions/env/remove.js';
2
2
  import { findFunctionInStack } from '../../../utils/find-function.js';
3
+ import { resolveFunctionAuth } from '../../../utils/functions/resolve-function-auth.js';
3
4
  import { styleText } from '../../../utils/style-text.js';
4
5
  export async function functionEnvRemoveCore(options) {
5
6
  const { args, log } = options;
6
7
  const spinner = log.ora(`Removing "${args.key}" environment variable in "${args.name}"`).start();
7
- const { externalId } = findFunctionInStack(options.deployedStack, args.name);
8
- const result = await remove(externalId, args.key, options.auth, options.log);
8
+ const resource = findFunctionInStack(options.deployedStack, args.name);
9
+ const { externalId } = resource;
10
+ const auth = resolveFunctionAuth(options.auth, options.deployedStack, resource);
11
+ const result = await remove(externalId, args.key, auth, options.log);
9
12
  if (!result.ok) {
10
13
  spinner.fail(`${styleText('red', 'Failed')} to remove ${args.key}`);
11
14
  return { success: false, error: result.error || 'Unknown error' };
@@ -3,9 +3,10 @@ import { deleteLogs as deleteLogsAction, logs as getLogsAction, streamLogs as st
3
3
  import { formatTitle } from '../../utils/display/blueprints-formatting.js';
4
4
  import { niceId } from '../../utils/display/presenters.js';
5
5
  import { findFunctionInStack, getFunctionNames } from '../../utils/find-function.js';
6
+ import { resolveFunctionAuth } from '../../utils/functions/resolve-function-auth.js';
6
7
  import { styleText } from '../../utils/style-text.js';
7
8
  export async function functionLogsCore(options) {
8
- const { args, flags, log, error, auth, deployedStack, blueprint, helpText } = options;
9
+ const { args, flags, log, error, deployedStack, blueprint, helpText } = options;
9
10
  const { name } = args;
10
11
  const { delete: shouldDelete, watch: shouldWatch, force, limit, json, utc } = flags;
11
12
  if (!name) {
@@ -17,7 +18,9 @@ export async function functionLogsCore(options) {
17
18
  log(helpText);
18
19
  return { success: true };
19
20
  }
20
- const { externalId } = findFunctionInStack(deployedStack, name); // throws if not found
21
+ const resource = findFunctionInStack(deployedStack, name); // throws if not found
22
+ const { externalId } = resource;
23
+ const auth = resolveFunctionAuth(options.auth, deployedStack, resource);
21
24
  if (shouldDelete)
22
25
  return deleteLogs({ name, externalId, auth, force, log });
23
26
  if (shouldWatch)
@@ -94,7 +97,7 @@ async function getLogs({ name, externalId, auth, limit, json, utc, log, }) {
94
97
  }
95
98
  }
96
99
  else {
97
- log(JSON.stringify(filteredLogs, null, 2));
100
+ return { success: true, json: { logs: filteredLogs } };
98
101
  }
99
102
  return { success: true };
100
103
  }
@@ -33,12 +33,14 @@ export type CoreResult = {
33
33
  /** The error message, if the operation failed. */
34
34
  error: string;
35
35
  streaming?: never;
36
+ json?: never;
36
37
  } | {
37
38
  /** Everything went well. */
38
39
  success: true;
39
40
  /** The streaming function, if the operation is streaming. */
40
41
  streaming?: Promise<void>;
41
42
  error?: never;
43
+ json?: Record<string, unknown>;
42
44
  });
43
45
  type InitBlueprintConfigParams = CoreConfig & ({
44
46
  validateToken: true;
package/dist/index.d.ts CHANGED
@@ -8,6 +8,7 @@ export { default as BlueprintsInfoCommand } from './commands/blueprints/info.js'
8
8
  export { default as BlueprintsInitCommand } from './commands/blueprints/init.js';
9
9
  export { default as BlueprintsLogsCommand } from './commands/blueprints/logs.js';
10
10
  export { default as BlueprintsPlanCommand } from './commands/blueprints/plan.js';
11
+ export { default as BlueprintsPromoteCommand } from './commands/blueprints/promote.js';
11
12
  export { default as BlueprintsStacksCommand } from './commands/blueprints/stacks.js';
12
13
  export { default as FunctionsAddCommand } from './commands/functions/add.js';
13
14
  export { default as FunctionsDevCommand } from './commands/functions/dev.js';
package/dist/index.js CHANGED
@@ -9,6 +9,7 @@ export { default as BlueprintsInfoCommand } from './commands/blueprints/info.js'
9
9
  export { default as BlueprintsInitCommand } from './commands/blueprints/init.js';
10
10
  export { default as BlueprintsLogsCommand } from './commands/blueprints/logs.js';
11
11
  export { default as BlueprintsPlanCommand } from './commands/blueprints/plan.js';
12
+ export { default as BlueprintsPromoteCommand } from './commands/blueprints/promote.js';
12
13
  export { default as BlueprintsStacksCommand } from './commands/blueprints/stacks.js';
13
14
  // Functions command classes
14
15
  export { default as FunctionsAddCommand } from './commands/functions/add.js';
@@ -5839,11 +5839,11 @@ class SelectionRange {
5839
5839
  /**
5840
5840
  Extend this range to cover at least `from` to `to`.
5841
5841
  */
5842
- extend(from, to = from) {
5842
+ extend(from, to = from, assoc = 0) {
5843
5843
  if (from <= this.anchor && to >= this.anchor)
5844
- return EditorSelection.range(from, to);
5844
+ return EditorSelection.range(from, to, undefined, undefined, assoc);
5845
5845
  let head = Math.abs(from - this.anchor) > Math.abs(to - this.anchor) ? from : to;
5846
- return EditorSelection.range(this.anchor, head);
5846
+ return EditorSelection.range(this.anchor, head, undefined, undefined, assoc);
5847
5847
  }
5848
5848
  /**
5849
5849
  Compare this range to another range.
@@ -5990,11 +5990,13 @@ class EditorSelection {
5990
5990
  /**
5991
5991
  Create a selection range.
5992
5992
  */
5993
- static range(anchor, head, goalColumn, bidiLevel) {
5993
+ static range(anchor, head, goalColumn, bidiLevel, assoc) {
5994
5994
  let flags = ((goalColumn !== null && goalColumn !== void 0 ? goalColumn : 16777215 /* RangeFlag.NoGoalColumn */) << 6 /* RangeFlag.GoalColumnOffset */) |
5995
5995
  (bidiLevel == null ? 7 : Math.min(6, bidiLevel));
5996
+ if (!assoc && anchor != head)
5997
+ assoc = head < anchor ? 1 : -1;
5996
5998
  return head < anchor ? SelectionRange.create(head, anchor, 32 /* RangeFlag.Inverted */ | 16 /* RangeFlag.AssocAfter */ | flags)
5997
- : SelectionRange.create(anchor, head, (head > anchor ? 8 /* RangeFlag.AssocBefore */ : 0) | flags);
5999
+ : SelectionRange.create(anchor, head, (!assoc ? 0 : assoc < 0 ? 8 /* RangeFlag.AssocBefore */ : 16 /* RangeFlag.AssocAfter */) | flags);
5998
6000
  }
5999
6001
  /**
6000
6002
  @internal
@@ -0,0 +1,9 @@
1
+ import type { AuthParams, DeployedResource, Stack } from '../types.js';
2
+ /**
3
+ * Resolves the correct auth scope for a function API request.
4
+ *
5
+ * 1. explicit `project` or `organization` on the resource parameters (highest priority)
6
+ * 2. stack's `defaultProjectId` for project-scoped function types in org-scoped stacks
7
+ * 3. pass-through the stack's deployment scope (lowest priority)
8
+ */
9
+ export declare function resolveFunctionAuth(auth: AuthParams, deployedStack: Stack, resource: DeployedResource): AuthParams;
@@ -0,0 +1,36 @@
1
+ import { ORGANIZATION_SCOPED_FUNCTION_TYPES, PROJECT_SCOPED_FUNCTION_TYPES } from '../../constants.js';
2
+ /**
3
+ * Resolves the correct auth scope for a function API request.
4
+ *
5
+ * 1. explicit `project` or `organization` on the resource parameters (highest priority)
6
+ * 2. stack's `defaultProjectId` for project-scoped function types in org-scoped stacks
7
+ * 3. pass-through the stack's deployment scope (lowest priority)
8
+ */
9
+ export function resolveFunctionAuth(auth, deployedStack, resource) {
10
+ // 1. explicit resource-level scope takes highest priority
11
+ if (typeof resource.parameters.project === 'string') {
12
+ return { ...auth, scopeType: 'project', scopeId: resource.parameters.project };
13
+ }
14
+ if (typeof resource.parameters.organization === 'string') {
15
+ return { ...auth, scopeType: 'organization', scopeId: resource.parameters.organization };
16
+ }
17
+ // 2. project-scoped function types: resolve a project ID or fail
18
+ if (PROJECT_SCOPED_FUNCTION_TYPES.has(resource.type)) {
19
+ if (auth.scopeType === 'project')
20
+ return auth;
21
+ if (deployedStack.defaultProjectId) {
22
+ return { ...auth, scopeType: 'project', scopeId: deployedStack.defaultProjectId };
23
+ }
24
+ throw new Error(`Function "${resource.name}" (${resource.type}) requires project scope, but no project ID could be resolved. ` +
25
+ 'Set the `project` attribute on the function resource.');
26
+ }
27
+ // 3. Organization-scoped function types: must be in an org-scoped stack
28
+ if (ORGANIZATION_SCOPED_FUNCTION_TYPES.has(resource.type)) {
29
+ if (auth.scopeType === 'organization')
30
+ return auth;
31
+ throw new Error(`Function "${resource.name}" (${resource.type}) requires organization scope, ` +
32
+ 'but the current blueprint is project-scoped.');
33
+ }
34
+ // 4. Unknown type; pass it along (don't break on future types)
35
+ return auth;
36
+ }
@@ -121,6 +121,7 @@ interface StackBase {
121
121
  displayName: string;
122
122
  scopeType: ScopeType;
123
123
  scopeId: string;
124
+ defaultProjectId: string | null;
124
125
  recentOperation?: StackOperation;
125
126
  createdAt?: string;
126
127
  updatedAt?: string;
@@ -1031,6 +1031,97 @@
1031
1031
  "plan.js"
1032
1032
  ]
1033
1033
  },
1034
+ "blueprints:promote": {
1035
+ "aliases": [],
1036
+ "args": {},
1037
+ "description": "EXPERIMENTAL! Promotes a Stack, changing its scope from project to organization. This is a one-way trip.",
1038
+ "examples": [
1039
+ "<%= config.bin %> <%= command.id %>",
1040
+ "<%= config.bin %> <%= command.id %> --stack <name-or-id>"
1041
+ ],
1042
+ "flags": {
1043
+ "json": {
1044
+ "description": "Format output as json.",
1045
+ "hidden": true,
1046
+ "name": "json",
1047
+ "allowNo": false,
1048
+ "type": "boolean"
1049
+ },
1050
+ "path": {
1051
+ "aliases": [
1052
+ "blueprint-path"
1053
+ ],
1054
+ "char": "p",
1055
+ "description": "Path to a Blueprint file or directory containing one",
1056
+ "env": "SANITY_BLUEPRINT_PATH",
1057
+ "hidden": true,
1058
+ "name": "path",
1059
+ "hasDynamicHelp": false,
1060
+ "multiple": false,
1061
+ "type": "option"
1062
+ },
1063
+ "trace": {
1064
+ "description": "Trace output",
1065
+ "hidden": true,
1066
+ "name": "trace",
1067
+ "allowNo": false,
1068
+ "type": "boolean"
1069
+ },
1070
+ "validate-resources": {
1071
+ "description": "Validate resources",
1072
+ "hidden": true,
1073
+ "name": "validate-resources",
1074
+ "allowNo": true,
1075
+ "type": "boolean"
1076
+ },
1077
+ "verbose": {
1078
+ "description": "Verbose output",
1079
+ "hidden": true,
1080
+ "name": "verbose",
1081
+ "allowNo": false,
1082
+ "type": "boolean"
1083
+ },
1084
+ "stack": {
1085
+ "aliases": [
1086
+ "id"
1087
+ ],
1088
+ "description": "Stack name or ID to promote",
1089
+ "name": "stack",
1090
+ "hasDynamicHelp": false,
1091
+ "multiple": false,
1092
+ "type": "option"
1093
+ },
1094
+ "i-know-what-im-doing": {
1095
+ "description": "Must be set",
1096
+ "name": "i-know-what-im-doing",
1097
+ "allowNo": false,
1098
+ "type": "boolean"
1099
+ },
1100
+ "force": {
1101
+ "description": "Skip confirmation prompt",
1102
+ "name": "force",
1103
+ "allowNo": false,
1104
+ "type": "boolean"
1105
+ }
1106
+ },
1107
+ "hasDynamicHelp": false,
1108
+ "hidden": true,
1109
+ "hiddenAliases": [],
1110
+ "id": "blueprints:promote",
1111
+ "pluginAlias": "@sanity/runtime-cli",
1112
+ "pluginName": "@sanity/runtime-cli",
1113
+ "pluginType": "core",
1114
+ "strict": true,
1115
+ "summary": "Promote a Stack deployment to a broader scope",
1116
+ "enableJsonFlag": true,
1117
+ "isESM": true,
1118
+ "relativePath": [
1119
+ "dist",
1120
+ "commands",
1121
+ "blueprints",
1122
+ "promote.js"
1123
+ ]
1124
+ },
1034
1125
  "blueprints:stacks": {
1035
1126
  "aliases": [],
1036
1127
  "args": {},
@@ -2198,5 +2289,5 @@
2198
2289
  ]
2199
2290
  }
2200
2291
  },
2201
- "version": "14.5.1"
2292
+ "version": "14.6.0"
2202
2293
  }
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": "14.5.1",
4
+ "version": "14.6.0",
5
5
  "author": "Sanity Runtime Team",
6
6
  "type": "module",
7
7
  "license": "MIT",
@@ -100,38 +100,38 @@
100
100
  "dependencies": {
101
101
  "@architect/hydrate": "^5.0.2",
102
102
  "@architect/inventory": "^5.0.0",
103
- "@inquirer/prompts": "^8.2.1",
104
- "@oclif/core": "^4.8.0",
105
- "@oclif/plugin-help": "^6.2.37",
103
+ "@inquirer/prompts": "^8.3.2",
104
+ "@oclif/core": "^4.9.0",
105
+ "@oclif/plugin-help": "^6.2.38",
106
106
  "@sanity/blueprints": "^0.13.1",
107
107
  "@sanity/blueprints-parser": "^0.4.0",
108
- "@sanity/client": "^7.15.0",
108
+ "@sanity/client": "^7.17.0",
109
109
  "adm-zip": "^0.5.16",
110
110
  "array-treeify": "^0.1.5",
111
111
  "cardinal": "^2.1.1",
112
112
  "empathic": "^2.0.0",
113
113
  "eventsource": "^4.1.0",
114
- "groq-js": "^1.27.1",
114
+ "groq-js": "^1.29.0",
115
115
  "jiti": "^2.6.1",
116
116
  "mime-types": "^3.0.2",
117
117
  "ora": "^9.3.0",
118
- "tar-stream": "^3.1.7",
118
+ "tar-stream": "^3.1.8",
119
119
  "vite": "^7.3.1",
120
120
  "vite-tsconfig-paths": "^6.1.1",
121
121
  "ws": "^8.19.0",
122
122
  "xdg-basedir": "^5.1.0"
123
123
  },
124
124
  "devDependencies": {
125
- "@biomejs/biome": "2.4.3",
125
+ "@biomejs/biome": "2.4.8",
126
126
  "@codemirror/lang-json": "^6.0.2",
127
- "@codemirror/state": "^6.5.4",
127
+ "@codemirror/state": "^6.6.0",
128
128
  "@enhance/store": "^1.0.2",
129
129
  "@lezer/highlight": "^1.2.3",
130
130
  "@oclif/test": "^4.1.16",
131
131
  "@playwright/test": "^1.58.2",
132
132
  "@rollup/plugin-node-resolve": "^16.0.3",
133
- "@sanity/functions": "^1.2.0",
134
- "@types/adm-zip": "^0.5.7",
133
+ "@sanity/functions": "^1.2.1",
134
+ "@types/adm-zip": "^0.5.8",
135
135
  "@types/cardinal": "^2.1.1",
136
136
  "@types/mime-types": "^3.0.1",
137
137
  "@types/node": "20",
@@ -139,14 +139,14 @@
139
139
  "@types/ws": "^8.18.1",
140
140
  "codemirror": "^6.0.2",
141
141
  "mentoss": "^0.13.0",
142
- "oclif": "^4.22.79",
142
+ "oclif": "^4.22.92",
143
143
  "pretty-bytes": "^7.1.0",
144
144
  "pretty-ms": "^9.3.0",
145
- "rollup": "^4.57.1",
145
+ "rollup": "^4.59.0",
146
146
  "shx": "^0.4.0",
147
147
  "ts-node": "^10.9.2",
148
148
  "typescript": "^5.9.3",
149
- "vitest": "4.0.18"
149
+ "vitest": "4.1.0"
150
150
  },
151
151
  "oclif": {
152
152
  "bin": "sanity-run",