@sanity/runtime-cli 15.2.1 → 16.0.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 (37) hide show
  1. package/README.md +20 -20
  2. package/dist/actions/blueprints/assets.d.ts +2 -2
  3. package/dist/actions/blueprints/assets.js +6 -7
  4. package/dist/actions/blueprints/config.d.ts +9 -1
  5. package/dist/actions/blueprints/config.js +11 -1
  6. package/dist/actions/blueprints/logs.js +2 -3
  7. package/dist/actions/blueprints/stacks.js +2 -3
  8. package/dist/baseCommands.d.ts +12 -2
  9. package/dist/baseCommands.js +15 -2
  10. package/dist/commands/blueprints/destroy.d.ts +1 -1
  11. package/dist/commands/blueprints/destroy.js +5 -3
  12. package/dist/commands/blueprints/plan.js +2 -0
  13. package/dist/commands/blueprints/stacks.d.ts +1 -1
  14. package/dist/commands/blueprints/stacks.js +4 -3
  15. package/dist/commands/functions/add.js +2 -0
  16. package/dist/config.d.ts +8 -0
  17. package/dist/config.js +12 -0
  18. package/dist/cores/blueprints/deploy.js +8 -3
  19. package/dist/cores/blueprints/destroy.d.ts +12 -2
  20. package/dist/cores/blueprints/destroy.js +13 -23
  21. package/dist/cores/blueprints/doctor.js +33 -13
  22. package/dist/cores/blueprints/plan.js +4 -0
  23. package/dist/cores/blueprints/stacks.d.ts +9 -4
  24. package/dist/cores/blueprints/stacks.js +2 -24
  25. package/dist/cores/functions/add.js +3 -1
  26. package/dist/cores/index.d.ts +1 -2
  27. package/dist/utils/functions/transpile/function.d.ts +6 -0
  28. package/dist/utils/functions/transpile/function.js +34 -7
  29. package/dist/utils/types.d.ts +4 -0
  30. package/dist/utils/warnings.d.ts +14 -0
  31. package/dist/utils/warnings.js +45 -0
  32. package/dist/utils/workspace-checks.d.ts +9 -0
  33. package/dist/utils/workspace-checks.js +47 -0
  34. package/oclif.manifest.json +3 -5
  35. package/package.json +3 -3
  36. package/dist/utils/blueprints/warnings.d.ts +0 -19
  37. package/dist/utils/blueprints/warnings.js +0 -36
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/15.2.1 linux-x64 node-v24.16.0
23
+ @sanity/runtime-cli/16.0.1 linux-x64 node-v24.16.0
24
24
  $ sanity-run --help [COMMAND]
25
25
  USAGE
26
26
  $ sanity-run COMMAND
@@ -103,7 +103,7 @@ EXAMPLES
103
103
  $ sanity-run blueprints add function --name my-function --fn-type document-create --fn-type document-update --lang js
104
104
  ```
105
105
 
106
- _See code: [src/commands/blueprints/add.ts](https://github.com/sanity-io/runtime-cli/blob/v15.2.1/src/commands/blueprints/add.ts)_
106
+ _See code: [src/commands/blueprints/add.ts](https://github.com/sanity-io/runtime-cli/blob/v16.0.1/src/commands/blueprints/add.ts)_
107
107
 
108
108
  ## `sanity-run blueprints config`
109
109
 
@@ -140,7 +140,7 @@ EXAMPLES
140
140
  $ sanity-run blueprints config --edit --project-id <projectId> --stack <name-or-id>
141
141
  ```
142
142
 
143
- _See code: [src/commands/blueprints/config.ts](https://github.com/sanity-io/runtime-cli/blob/v15.2.1/src/commands/blueprints/config.ts)_
143
+ _See code: [src/commands/blueprints/config.ts](https://github.com/sanity-io/runtime-cli/blob/v16.0.1/src/commands/blueprints/config.ts)_
144
144
 
145
145
  ## `sanity-run blueprints deploy`
146
146
 
@@ -194,7 +194,7 @@ EXAMPLES
194
194
  $ sanity-run blueprints deploy --new-stack-name <new-name>
195
195
  ```
196
196
 
197
- _See code: [src/commands/blueprints/deploy.ts](https://github.com/sanity-io/runtime-cli/blob/v15.2.1/src/commands/blueprints/deploy.ts)_
197
+ _See code: [src/commands/blueprints/deploy.ts](https://github.com/sanity-io/runtime-cli/blob/v16.0.1/src/commands/blueprints/deploy.ts)_
198
198
 
199
199
  ## `sanity-run blueprints destroy`
200
200
 
@@ -232,7 +232,7 @@ EXAMPLES
232
232
  $ sanity-run blueprints destroy --stack <name-or-id> --project-id <projectId> --force --no-wait
233
233
  ```
234
234
 
235
- _See code: [src/commands/blueprints/destroy.ts](https://github.com/sanity-io/runtime-cli/blob/v15.2.1/src/commands/blueprints/destroy.ts)_
235
+ _See code: [src/commands/blueprints/destroy.ts](https://github.com/sanity-io/runtime-cli/blob/v16.0.1/src/commands/blueprints/destroy.ts)_
236
236
 
237
237
  ## `sanity-run blueprints doctor`
238
238
 
@@ -263,7 +263,7 @@ EXAMPLES
263
263
  $ sanity-run blueprints doctor --fix
264
264
  ```
265
265
 
266
- _See code: [src/commands/blueprints/doctor.ts](https://github.com/sanity-io/runtime-cli/blob/v15.2.1/src/commands/blueprints/doctor.ts)_
266
+ _See code: [src/commands/blueprints/doctor.ts](https://github.com/sanity-io/runtime-cli/blob/v16.0.1/src/commands/blueprints/doctor.ts)_
267
267
 
268
268
  ## `sanity-run blueprints info`
269
269
 
@@ -300,7 +300,7 @@ EXAMPLES
300
300
  $ sanity-run blueprints info --organization-id <orgId> --stack <name-or-id>
301
301
  ```
302
302
 
303
- _See code: [src/commands/blueprints/info.ts](https://github.com/sanity-io/runtime-cli/blob/v15.2.1/src/commands/blueprints/info.ts)_
303
+ _See code: [src/commands/blueprints/info.ts](https://github.com/sanity-io/runtime-cli/blob/v16.0.1/src/commands/blueprints/info.ts)_
304
304
 
305
305
  ## `sanity-run blueprints init [DIR]`
306
306
 
@@ -359,7 +359,7 @@ EXAMPLES
359
359
  $ sanity-run blueprints init old-stack --type <json|js|ts> --project-id <projectId> --stack-id <existingStackId>
360
360
  ```
361
361
 
362
- _See code: [src/commands/blueprints/init.ts](https://github.com/sanity-io/runtime-cli/blob/v15.2.1/src/commands/blueprints/init.ts)_
362
+ _See code: [src/commands/blueprints/init.ts](https://github.com/sanity-io/runtime-cli/blob/v16.0.1/src/commands/blueprints/init.ts)_
363
363
 
364
364
  ## `sanity-run blueprints logs`
365
365
 
@@ -405,7 +405,7 @@ EXAMPLES
405
405
  $ sanity-run blueprints logs --before 2026-05-01T00:00:00Z
406
406
  ```
407
407
 
408
- _See code: [src/commands/blueprints/logs.ts](https://github.com/sanity-io/runtime-cli/blob/v15.2.1/src/commands/blueprints/logs.ts)_
408
+ _See code: [src/commands/blueprints/logs.ts](https://github.com/sanity-io/runtime-cli/blob/v16.0.1/src/commands/blueprints/logs.ts)_
409
409
 
410
410
  ## `sanity-run blueprints mint-deploy-token`
411
411
 
@@ -450,7 +450,7 @@ EXAMPLES
450
450
  $ sanity-run blueprints mint-deploy-token --organization-id <orgId>
451
451
  ```
452
452
 
453
- _See code: [src/commands/blueprints/mint-deploy-token.ts](https://github.com/sanity-io/runtime-cli/blob/v15.2.1/src/commands/blueprints/mint-deploy-token.ts)_
453
+ _See code: [src/commands/blueprints/mint-deploy-token.ts](https://github.com/sanity-io/runtime-cli/blob/v16.0.1/src/commands/blueprints/mint-deploy-token.ts)_
454
454
 
455
455
  ## `sanity-run blueprints plan`
456
456
 
@@ -483,7 +483,7 @@ EXAMPLES
483
483
  $ sanity-run blueprints plan --organization-id <orgId> --stack <name-or-id>
484
484
  ```
485
485
 
486
- _See code: [src/commands/blueprints/plan.ts](https://github.com/sanity-io/runtime-cli/blob/v15.2.1/src/commands/blueprints/plan.ts)_
486
+ _See code: [src/commands/blueprints/plan.ts](https://github.com/sanity-io/runtime-cli/blob/v16.0.1/src/commands/blueprints/plan.ts)_
487
487
 
488
488
  ## `sanity-run blueprints promote`
489
489
 
@@ -519,7 +519,7 @@ EXAMPLES
519
519
  $ sanity-run blueprints promote --new-stack-name <new-name>
520
520
  ```
521
521
 
522
- _See code: [src/commands/blueprints/promote.ts](https://github.com/sanity-io/runtime-cli/blob/v15.2.1/src/commands/blueprints/promote.ts)_
522
+ _See code: [src/commands/blueprints/promote.ts](https://github.com/sanity-io/runtime-cli/blob/v16.0.1/src/commands/blueprints/promote.ts)_
523
523
 
524
524
  ## `sanity-run blueprints stacks`
525
525
 
@@ -555,7 +555,7 @@ EXAMPLES
555
555
  $ sanity-run blueprints stacks --organization-id <organizationId> --include-projects
556
556
  ```
557
557
 
558
- _See code: [src/commands/blueprints/stacks.ts](https://github.com/sanity-io/runtime-cli/blob/v15.2.1/src/commands/blueprints/stacks.ts)_
558
+ _See code: [src/commands/blueprints/stacks.ts](https://github.com/sanity-io/runtime-cli/blob/v16.0.1/src/commands/blueprints/stacks.ts)_
559
559
 
560
560
  ## `sanity-run functions add`
561
561
 
@@ -607,7 +607,7 @@ EXAMPLES
607
607
  $ sanity-run functions add --name my-function --type document-create --type document-update --lang js
608
608
  ```
609
609
 
610
- _See code: [src/commands/functions/add.ts](https://github.com/sanity-io/runtime-cli/blob/v15.2.1/src/commands/functions/add.ts)_
610
+ _See code: [src/commands/functions/add.ts](https://github.com/sanity-io/runtime-cli/blob/v16.0.1/src/commands/functions/add.ts)_
611
611
 
612
612
  ## `sanity-run functions dev`
613
613
 
@@ -642,7 +642,7 @@ EXAMPLES
642
642
  $ sanity-run functions dev --timeout 60
643
643
  ```
644
644
 
645
- _See code: [src/commands/functions/dev.ts](https://github.com/sanity-io/runtime-cli/blob/v15.2.1/src/commands/functions/dev.ts)_
645
+ _See code: [src/commands/functions/dev.ts](https://github.com/sanity-io/runtime-cli/blob/v16.0.1/src/commands/functions/dev.ts)_
646
646
 
647
647
  ## `sanity-run functions env add NAME KEY VALUE`
648
648
 
@@ -672,7 +672,7 @@ EXAMPLES
672
672
  $ sanity-run functions env add MyFunction API_URL https://api.example.com/
673
673
  ```
674
674
 
675
- _See code: [src/commands/functions/env/add.ts](https://github.com/sanity-io/runtime-cli/blob/v15.2.1/src/commands/functions/env/add.ts)_
675
+ _See code: [src/commands/functions/env/add.ts](https://github.com/sanity-io/runtime-cli/blob/v16.0.1/src/commands/functions/env/add.ts)_
676
676
 
677
677
  ## `sanity-run functions env list NAME`
678
678
 
@@ -699,7 +699,7 @@ EXAMPLES
699
699
  $ sanity-run functions env list MyFunction
700
700
  ```
701
701
 
702
- _See code: [src/commands/functions/env/list.ts](https://github.com/sanity-io/runtime-cli/blob/v15.2.1/src/commands/functions/env/list.ts)_
702
+ _See code: [src/commands/functions/env/list.ts](https://github.com/sanity-io/runtime-cli/blob/v16.0.1/src/commands/functions/env/list.ts)_
703
703
 
704
704
  ## `sanity-run functions env remove NAME KEY`
705
705
 
@@ -728,7 +728,7 @@ EXAMPLES
728
728
  $ sanity-run functions env remove MyFunction API_URL
729
729
  ```
730
730
 
731
- _See code: [src/commands/functions/env/remove.ts](https://github.com/sanity-io/runtime-cli/blob/v15.2.1/src/commands/functions/env/remove.ts)_
731
+ _See code: [src/commands/functions/env/remove.ts](https://github.com/sanity-io/runtime-cli/blob/v16.0.1/src/commands/functions/env/remove.ts)_
732
732
 
733
733
  ## `sanity-run functions logs [NAME]`
734
734
 
@@ -768,7 +768,7 @@ EXAMPLES
768
768
  $ sanity-run functions logs <name> --delete
769
769
  ```
770
770
 
771
- _See code: [src/commands/functions/logs.ts](https://github.com/sanity-io/runtime-cli/blob/v15.2.1/src/commands/functions/logs.ts)_
771
+ _See code: [src/commands/functions/logs.ts](https://github.com/sanity-io/runtime-cli/blob/v16.0.1/src/commands/functions/logs.ts)_
772
772
 
773
773
  ## `sanity-run functions test [NAME]`
774
774
 
@@ -825,7 +825,7 @@ EXAMPLES
825
825
  $ sanity-run functions test <name> --event update --data-before '{ "title": "before" }' --data-after '{ "title": "after" }'
826
826
  ```
827
827
 
828
- _See code: [src/commands/functions/test.ts](https://github.com/sanity-io/runtime-cli/blob/v15.2.1/src/commands/functions/test.ts)_
828
+ _See code: [src/commands/functions/test.ts](https://github.com/sanity-io/runtime-cli/blob/v16.0.1/src/commands/functions/test.ts)_
829
829
 
830
830
  ## `sanity-run help [COMMAND]`
831
831
 
@@ -1,8 +1,8 @@
1
1
  import AdmZip from 'adm-zip';
2
2
  import type { Logger } from '../../utils/logger.js';
3
3
  import type { AssetResource, AuthParams, InstallerType } from '../../utils/types.js';
4
- export declare const ASSET_CHECK_URL: string;
5
- export declare const ASSET_STASH_URL: string;
4
+ export declare const assetCheckUrl: string;
5
+ export declare const assetStashUrl: string;
6
6
  export declare function stashAsset({ resource, auth, logger, installer, }: {
7
7
  resource: AssetResource;
8
8
  auth: AuthParams;
@@ -1,16 +1,15 @@
1
1
  import crypto from 'node:crypto';
2
2
  import fs from 'node:fs';
3
3
  import AdmZip from 'adm-zip';
4
- import config from '../../config.js';
4
+ import { blueprintsUrl } from '../../config.js';
5
5
  import { CONVERT_BYTES_TO_MB, MAX_ASSET_SIZE } from '../../constants.js';
6
6
  import { prepareAsset as prepareFunctionAsset } from '../../utils/functions/prepare-asset.js';
7
7
  import getHeaders from '../../utils/get-headers.js';
8
8
  import { createTracedFetch } from '../../utils/traced-fetch.js';
9
9
  import { isLocalFunctionResource, isStudioResource } from '../../utils/types.js';
10
- const { apiUrl } = config;
11
- const ASSETS_URL = `${apiUrl}vX/blueprints/assets`;
12
- export const ASSET_CHECK_URL = `${ASSETS_URL}/check`;
13
- export const ASSET_STASH_URL = `${ASSETS_URL}/stash`;
10
+ const assetsUrl = blueprintsUrl('assets');
11
+ export const assetCheckUrl = `${assetsUrl}/check`;
12
+ export const assetStashUrl = `${assetsUrl}/stash`;
14
13
  export async function stashAsset({ resource, auth, logger, installer, }) {
15
14
  try {
16
15
  let b64;
@@ -88,7 +87,7 @@ async function stashSingleAsset({ b64, hash, cleanup, resource, auth, logger, ex
88
87
  const fetchFn = createTracedFetch(logger);
89
88
  try {
90
89
  try {
91
- const checkResponse = await fetchFn(`${ASSET_CHECK_URL}/${hash}`, {
90
+ const checkResponse = await fetchFn(`${assetCheckUrl}/${hash}`, {
92
91
  method: 'GET',
93
92
  headers: getHeaders(auth),
94
93
  });
@@ -99,7 +98,7 @@ async function stashSingleAsset({ b64, hash, cleanup, resource, auth, logger, ex
99
98
  }
100
99
  catch { } // Continue to stash the Asset
101
100
  // Asset doesn't exist, so stash it
102
- const assetResponse = await fetchFn(ASSET_STASH_URL, {
101
+ const assetResponse = await fetchFn(assetStashUrl, {
103
102
  method: 'POST',
104
103
  headers: getHeaders(auth),
105
104
  body: JSON.stringify({
@@ -37,7 +37,7 @@ export interface ConfigPatch {
37
37
  /**
38
38
  * Update the config file with the given properties.
39
39
  * Config file must already exist.
40
- * Adds timestamp. No validation or transformation is performed.
40
+ * Adds timestamp. No ID combination validation.
41
41
  * @param blueprintFilePath - the path to the blueprint file
42
42
  * @param updateableProperties - the properties to update
43
43
  * @param updateableProperties.organizationId - the organization ID (null to remove)
@@ -45,6 +45,14 @@ export interface ConfigPatch {
45
45
  * @param updateableProperties.stackId - the stack ID (null to remove)
46
46
  */
47
47
  export declare function patchConfigFile(blueprintFilePath: string, updateableProperties: ConfigPatch): BlueprintsConfig;
48
+ /**
49
+ * Patch a known config using its `configPath`.
50
+ * Unlike {@link patchConfigFile}, this does not re-derive the config location.
51
+ * Adds timestamp. No ID combination validation.
52
+ * @param existingConfig - the located config (includes `configPath`)
53
+ * @param updateableProperties - the properties to update (null to remove)
54
+ */
55
+ export declare function patchLocatedConfig(existingConfig: LocatedBlueprintsConfig, updateableProperties: ConfigPatch): BlueprintsConfig;
48
56
  /**
49
57
  * Find and write an organizationId to the config file by getting it from the projectId
50
58
  * @throws {Error} if unable to fetch project
@@ -54,7 +54,7 @@ export function writeConfigFile(blueprintFilePath, options) {
54
54
  /**
55
55
  * Update the config file with the given properties.
56
56
  * Config file must already exist.
57
- * Adds timestamp. No validation or transformation is performed.
57
+ * Adds timestamp. No ID combination validation.
58
58
  * @param blueprintFilePath - the path to the blueprint file
59
59
  * @param updateableProperties - the properties to update
60
60
  * @param updateableProperties.organizationId - the organization ID (null to remove)
@@ -65,6 +65,16 @@ export function patchConfigFile(blueprintFilePath, updateableProperties) {
65
65
  const existingConfig = readConfigFile(blueprintFilePath);
66
66
  if (!existingConfig)
67
67
  throw new Error('No config file found');
68
+ return patchLocatedConfig(existingConfig, updateableProperties);
69
+ }
70
+ /**
71
+ * Patch a known config using its `configPath`.
72
+ * Unlike {@link patchConfigFile}, this does not re-derive the config location.
73
+ * Adds timestamp. No ID combination validation.
74
+ * @param existingConfig - the located config (includes `configPath`)
75
+ * @param updateableProperties - the properties to update (null to remove)
76
+ */
77
+ export function patchLocatedConfig(existingConfig, updateableProperties) {
68
78
  const { configPath, ...existingConfigProperties } = existingConfig;
69
79
  const merged = {
70
80
  blueprintConfigVersion: BLUEPRINT_CONFIG_VERSION, // patch doesn't overwrite versions
@@ -1,8 +1,7 @@
1
- import config from '../../config.js';
1
+ import { blueprintsUrl } from '../../config.js';
2
2
  import getHeaders from '../../utils/get-headers.js';
3
3
  import { createTracedFetch } from '../../utils/traced-fetch.js';
4
- const { apiUrl } = config;
5
- export const logsUrl = `${apiUrl}vX/blueprints/logs`;
4
+ export const logsUrl = blueprintsUrl('logs');
6
5
  export async function getLogs(params, auth, logger) {
7
6
  const fetchFn = createTracedFetch(logger);
8
7
  const url = new URL(logsUrl);
@@ -1,8 +1,7 @@
1
- import config from '../../config.js';
1
+ import { blueprintsUrl } from '../../config.js';
2
2
  import getHeaders from '../../utils/get-headers.js';
3
3
  import { createTracedFetch } from '../../utils/traced-fetch.js';
4
- const { apiUrl } = config;
5
- export const stacksUrl = `${apiUrl}vX/blueprints/stacks`;
4
+ export const stacksUrl = blueprintsUrl('stacks');
6
5
  export async function listStacks(auth, logger) {
7
6
  const fetchFn = createTracedFetch(logger);
8
7
  try {
@@ -1,9 +1,9 @@
1
1
  import type { Interfaces } from '@oclif/core';
2
2
  import { Command } from '@oclif/core';
3
3
  import { type ReadBlueprintResult } from './actions/blueprints/blueprint.js';
4
+ import { type LocatedBlueprintsConfig } from './actions/blueprints/config.js';
4
5
  import type { CoreResult } from './cores/index.js';
5
- import type { Warning } from './utils/blueprints/warnings.js';
6
- import type { AuthParams, ScopeType, Stack } from './utils/types.js';
6
+ import type { AuthParams, ScopeType, Stack, Warning } from './utils/types.js';
7
7
  export type Flags<T extends typeof Command> = Interfaces.InferredFlags<(typeof RuntimeCommand)['baseFlags'] & T['flags']>;
8
8
  export type Args<T extends typeof Command> = Interfaces.InferredArgs<T['args']>;
9
9
  export declare const pathFlagConfig: {
@@ -126,6 +126,16 @@ export declare abstract class ResolvedCommand<T extends typeof Command> extends
126
126
  protected scopeType: ScopeType;
127
127
  protected scopeId: string;
128
128
  protected stackId: string;
129
+ /**
130
+ * locally-resolved Stack ID (flags > env > module > config)
131
+ * populated regardless of `needs` and is never required
132
+ */
133
+ protected resolvedStackId?: string;
134
+ /**
135
+ * the located `.sanity/blueprint.config.json` (if any), populated regardless of
136
+ * `needs`. Lets commands read/update local config without requiring a manifest.
137
+ */
138
+ protected blueprintConfig?: LocatedBlueprintsConfig | null;
129
139
  protected auth: AuthParams;
130
140
  protected deployedStack: Stack;
131
141
  init(): Promise<void>;
@@ -1,7 +1,7 @@
1
1
  // * https://oclif.io/docs/base_class
2
2
  import { Command, CommandHelp, Flags as OclifFlags } from '@oclif/core';
3
3
  import { findBlueprintFile, loadBlueprintFile, parseBlueprintContent, } from './actions/blueprints/blueprint.js';
4
- import { backfillProjectBasedStackId, readConfigFile } from './actions/blueprints/config.js';
4
+ import { backfillProjectBasedStackId, readConfigFile, } from './actions/blueprints/config.js';
5
5
  import { resolveIds } from './actions/blueprints/resolve.js';
6
6
  import { getStack, resolveStackIdByNameOrId } from './actions/blueprints/stacks.js';
7
7
  import { presentBlueprintParserErrors } from './utils/display/errors.js';
@@ -146,7 +146,7 @@ export class RuntimeCommand extends Command {
146
146
  if (!warnings.length)
147
147
  return;
148
148
  this.log('');
149
- this.log(styleText(['bold', 'yellow'], 'Suggestions'));
149
+ this.log(styleText(['bold', 'yellow'], 'Notice'));
150
150
  this.log('');
151
151
  for (const entry of warnings) {
152
152
  entry.suggestions.forEach((sug) => {
@@ -206,6 +206,16 @@ export class ResolvedCommand extends RuntimeCommand {
206
206
  scopeType;
207
207
  scopeId;
208
208
  stackId;
209
+ /**
210
+ * locally-resolved Stack ID (flags > env > module > config)
211
+ * populated regardless of `needs` and is never required
212
+ */
213
+ resolvedStackId;
214
+ /**
215
+ * the located `.sanity/blueprint.config.json` (if any), populated regardless of
216
+ * `needs`. Lets commands read/update local config without requiring a manifest.
217
+ */
218
+ blueprintConfig;
209
219
  auth;
210
220
  deployedStack;
211
221
  async init() {
@@ -233,6 +243,7 @@ export class ResolvedCommand extends RuntimeCommand {
233
243
  // Anchor to --path flag or the blueprint file location if found, else cwd.
234
244
  const blueprintFileInfo = findBlueprintFile(this.flags.path);
235
245
  const blueprintConfig = readConfigFile(blueprintFileInfo?.blueprintFilePath);
246
+ this.blueprintConfig = blueprintConfig;
236
247
  // 3. Blueprint (if needed)
237
248
  let blueprintModule;
238
249
  if (needsBlueprint) {
@@ -292,6 +303,8 @@ export class ResolvedCommand extends RuntimeCommand {
292
303
  // assumption was wrong; leave stackId undefined
293
304
  }
294
305
  }
306
+ // Expose the resolved Stack ID regardless of `needs` (optional, for listings/highlighting)
307
+ this.resolvedStackId = resolved.stackId;
295
308
  // Backfill resolved IDs onto the blueprint result if it was loaded
296
309
  if (this.blueprint) {
297
310
  Object.assign(this.blueprint, {
@@ -1,6 +1,6 @@
1
1
  import { ResolvedCommand } from '../../baseCommands.js';
2
2
  export default class DestroyCommand extends ResolvedCommand<typeof DestroyCommand> {
3
- static needs: readonly ["token", "blueprint"];
3
+ static needs: readonly ["scope"];
4
4
  static summary: string;
5
5
  static description: string;
6
6
  static examples: string[];
@@ -3,7 +3,7 @@ import { organizationIdFlagConfig, projectIdFlagConfig, ResolvedCommand } from '
3
3
  import { blueprintDestroyCore } from '../../cores/blueprints/destroy.js';
4
4
  import { Logger } from '../../utils/logger.js';
5
5
  export default class DestroyCommand extends ResolvedCommand {
6
- static needs = ['token', 'blueprint'];
6
+ static needs = ['scope'];
7
7
  static summary = 'Destroy a remote Stack deployment and its resources';
8
8
  static description = `Permanently removes the remote Stack and all its provisioned resources. Your Blueprint manifest and resource files remain intact; "stackId" is unset in your local config.
9
9
 
@@ -44,8 +44,10 @@ Exit codes: 0 destroyed, 2 destruction failed, 75 destruction accepted but compl
44
44
  bin: this.config.bin,
45
45
  log: Logger(this.log.bind(this), this.flags),
46
46
  token: this.sanityToken,
47
- blueprint: this.blueprint,
48
- validateResources: this.flags['validate-resources'],
47
+ scopeType: this.scopeType,
48
+ scopeId: this.scopeId,
49
+ localStackId: this.resolvedStackId,
50
+ blueprintConfig: this.blueprintConfig,
49
51
  flags: this.flags,
50
52
  });
51
53
  if (!result.success)
@@ -28,6 +28,8 @@ Run 'blueprints plan' after making local changes to your Blueprint manifest to v
28
28
  });
29
29
  if (!result.success)
30
30
  this.coreError(result);
31
+ if (result.warnings)
32
+ this.renderWarnings(result?.warnings);
31
33
  return result.json;
32
34
  }
33
35
  }
@@ -1,6 +1,6 @@
1
1
  import { ResolvedCommand } from '../../baseCommands.js';
2
2
  export default class StacksCommand extends ResolvedCommand<typeof StacksCommand> {
3
- static needs: readonly ["token", "blueprint"];
3
+ static needs: readonly ["scope"];
4
4
  static summary: string;
5
5
  static description: string;
6
6
  static examples: string[];
@@ -3,7 +3,7 @@ import { organizationIdFlagConfig, projectIdFlagConfig, ResolvedCommand } from '
3
3
  import { blueprintStacksCore } from '../../cores/blueprints/stacks.js';
4
4
  import { Logger } from '../../utils/logger.js';
5
5
  export default class StacksCommand extends ResolvedCommand {
6
- static needs = ['token', 'blueprint'];
6
+ static needs = ['scope'];
7
7
  static summary = 'List remote Stack deployments for your project or organization';
8
8
  static description = `Shows all Stacks associated with a project or organization. By default, lists Stacks scoped to the local Blueprint.
9
9
 
@@ -32,8 +32,9 @@ Use --include-projects with --organization-id to also list Stacks from all proje
32
32
  bin: this.config.bin,
33
33
  log: Logger(this.log.bind(this), this.flags),
34
34
  token: this.sanityToken,
35
- blueprint: this.blueprint,
36
- validateResources: this.flags['validate-resources'],
35
+ scopeType: this.scopeType,
36
+ scopeId: this.scopeId,
37
+ localStackId: this.resolvedStackId,
37
38
  flags: this.flags,
38
39
  });
39
40
  if (!result.success)
@@ -71,5 +71,7 @@ After adding, use 'functions dev' to test locally, then 'blueprints deploy' to p
71
71
  });
72
72
  if (!result.success)
73
73
  this.coreError(result);
74
+ if (result.warnings)
75
+ this.renderWarnings(result?.warnings);
74
76
  }
75
77
  }
package/dist/config.d.ts CHANGED
@@ -1,9 +1,17 @@
1
1
  export declare const BLUEPRINT_CONFIG_VERSION = "v2025-05-08";
2
+ export declare const BLUEPRINTS_API_VERSION: string;
2
3
  export declare const BLUEPRINT_CONFIG_DIR = ".sanity";
3
4
  export declare const BLUEPRINT_CONFIG_FILE = "blueprint.config.json";
4
5
  export declare const RUNTIME_CLI_NAME = "@sanity/runtime-cli";
5
6
  export declare let RUNTIME_CLI_VERSION: string | undefined;
6
7
  export declare const RUNTIME_CLI_USER_AGENT: string;
8
+ /**
9
+ * Build a Blueprints API URL for the given path.
10
+ *
11
+ * Uses `BLUEPRINTS_API_VERSION` by default (itself overridable via the
12
+ * `SANITY_BLUEPRINTS_API_VERSION` env var); pass `version` to override per-endpoint.
13
+ */
14
+ export declare function blueprintsUrl(path: string, version?: string): string;
7
15
  declare const _default: {
8
16
  isTest: boolean;
9
17
  isLive: boolean;
package/dist/config.js CHANGED
@@ -4,6 +4,9 @@ import { fileURLToPath } from 'node:url';
4
4
  import * as pkg from 'empathic/package';
5
5
  import getToken from './utils/get-token.js';
6
6
  export const BLUEPRINT_CONFIG_VERSION = 'v2025-05-08';
7
+ // Default API version for Blueprints service calls.
8
+ // Overridable globally via env, or per-endpoint via the `version` arg to `blueprintsUrl`.
9
+ export const BLUEPRINTS_API_VERSION = env.SANITY_BLUEPRINTS_API_VERSION ?? 'v2026-06-15';
7
10
  export const BLUEPRINT_CONFIG_DIR = '.sanity';
8
11
  export const BLUEPRINT_CONFIG_FILE = 'blueprint.config.json';
9
12
  export const RUNTIME_CLI_NAME = '@sanity/runtime-cli';
@@ -35,6 +38,15 @@ const apiUrls = {
35
38
  const apiUrl = sanityEnv ? (apiUrls[sanityEnv] ?? sanityEnv) : apiUrls.default;
36
39
  // allow override of populus URL
37
40
  const populusApiUrl = env.POPULUS_API_URL ?? apiUrl;
41
+ /**
42
+ * Build a Blueprints API URL for the given path.
43
+ *
44
+ * Uses `BLUEPRINTS_API_VERSION` by default (itself overridable via the
45
+ * `SANITY_BLUEPRINTS_API_VERSION` env var); pass `version` to override per-endpoint.
46
+ */
47
+ export function blueprintsUrl(path, version = BLUEPRINTS_API_VERSION) {
48
+ return `${apiUrl}${version}/blueprints/${path}`;
49
+ }
38
50
  let _token;
39
51
  function loadToken() {
40
52
  let token = null;
@@ -1,3 +1,4 @@
1
+ import { dirname } from 'node:path';
1
2
  import { stashAsset } from '../../actions/blueprints/assets.js';
2
3
  import { getStack, updateStack } from '../../actions/blueprints/stacks.js';
3
4
  import { waitForOperation } from '../../actions/blueprints/wait-for-operation.js';
@@ -7,6 +8,7 @@ import { niceId } from '../../utils/display/presenters.js';
7
8
  import { CODE_OPERATION_UNCONFIRMED, EXIT_OPERATION_UNCONFIRMED } from '../../utils/exit-codes.js';
8
9
  import { styleText } from '../../utils/style-text.js';
9
10
  import { isAssetResource } from '../../utils/types.js';
11
+ import { getWarnings } from '../../utils/warnings.js';
10
12
  const DEFAULT_ASSET_TIMEOUT = 60;
11
13
  const assetTimeoutS = Number(process.env.SANITY_ASSET_TIMEOUT) || DEFAULT_ASSET_TIMEOUT;
12
14
  const assetTimeoutMs = assetTimeoutS * 1000;
@@ -17,6 +19,7 @@ export async function blueprintDeployCore(options) {
17
19
  const noWait = flags['no-wait'] || false;
18
20
  const stackName = flags['new-stack-name'] ?? deployedStack.name;
19
21
  const installer = flags['fn-installer'] || undefined;
22
+ const workspaceWarnings = getWarnings({ dir: dirname(blueprint.fileInfo.blueprintFilePath) });
20
23
  log(`Deploying "${stackName}" ${niceId(deployedStack.id)}...`);
21
24
  try {
22
25
  const { ok: checkOk, stack: currentStack } = await getStack({ stackId, auth, logger: log });
@@ -89,11 +92,12 @@ export async function blueprintDeployCore(options) {
89
92
  log(styleText(['bold', 'green'], 'Stack deployment started!'));
90
93
  log(`Use \`npx ${bin} blueprints info\` to check status`);
91
94
  const legacyWarning = await legacyPermissionsNoticePromise;
95
+ const warnings = [...(legacyWarning ? [legacyWarning] : []), ...workspaceWarnings];
92
96
  return {
93
97
  success: true,
94
98
  json: { stackId, resources },
95
99
  data: { resources },
96
- warnings: legacyWarning ? [legacyWarning] : undefined,
100
+ warnings: warnings.length ? warnings : undefined,
97
101
  };
98
102
  }
99
103
  log(styleText('dim', 'Stack deployment progress:'));
@@ -110,11 +114,12 @@ export async function blueprintDeployCore(options) {
110
114
  if (outcome.type === 'completed') {
111
115
  log(styleText(['bold', 'green'], 'Stack deployment completed!'));
112
116
  const legacyWarning = await legacyPermissionsNoticePromise;
117
+ const warnings = [...(legacyWarning ? [legacyWarning] : []), ...workspaceWarnings];
113
118
  return {
114
119
  success: true,
115
120
  json: { stackId, resources },
116
121
  data: { resources },
117
- warnings: legacyWarning ? [legacyWarning] : undefined,
122
+ warnings: warnings.length ? warnings : undefined,
118
123
  };
119
124
  }
120
125
  if (outcome.type === 'unconfirmed') {
@@ -212,7 +217,7 @@ async function checkLegacyDeployPermission({ auth, scopeType, scopeId, log, }) {
212
217
  if (result.hasPermission)
213
218
  return null;
214
219
  return {
215
- label: 'Legacy deploy permission',
220
+ code: 'Legacy deploy permission',
216
221
  suggestions: [
217
222
  `This deploy used a legacy permission. Starting ${BLUEPRINTS_PERMISSION_ENFORCEMENT_DATE}, deploys will require the "${requiredPermission}" permission.`,
218
223
  scopeType === 'project'
@@ -1,5 +1,15 @@
1
- import type { BlueprintConfig, CoreResult } from '../index.js';
2
- export interface BlueprintDestroyOptions extends BlueprintConfig {
1
+ import { type LocatedBlueprintsConfig } from '../../actions/blueprints/config.js';
2
+ import type { ScopeType } from '../../utils/types.js';
3
+ import type { CoreConfig, CoreResult } from '../index.js';
4
+ export interface BlueprintDestroyOptions extends CoreConfig {
5
+ token: string;
6
+ /** Scope to destroy within, resolved by the base command (flags > env > config). */
7
+ scopeType?: ScopeType;
8
+ scopeId?: string;
9
+ /** Locally-configured Stack ID, used when no --stack override is provided. */
10
+ localStackId?: string;
11
+ /** Located local config, used to unset stackId after a successful destroy. */
12
+ blueprintConfig?: LocatedBlueprintsConfig | null;
3
13
  flags: {
4
14
  force?: boolean;
5
15
  'project-id'?: string;
@@ -1,27 +1,18 @@
1
1
  import { setTimeout as sleep } from 'node:timers/promises';
2
2
  import { confirm } from '@inquirer/prompts';
3
- import { patchConfigFile } from '../../actions/blueprints/config.js';
3
+ import { patchLocatedConfig } from '../../actions/blueprints/config.js';
4
4
  import { destroyStack, getStack, resolveStackIdByNameOrId } from '../../actions/blueprints/stacks.js';
5
5
  import { waitForOperation } from '../../actions/blueprints/wait-for-operation.js';
6
6
  import { niceId } from '../../utils/display/presenters.js';
7
7
  import { CODE_OPERATION_UNCONFIRMED, EXIT_OPERATION_UNCONFIRMED } from '../../utils/exit-codes.js';
8
8
  import { styleText } from '../../utils/style-text.js';
9
9
  export async function blueprintDestroyCore(options) {
10
- const { bin = 'sanity', log, token, blueprint, flags } = options;
10
+ const { bin = 'sanity', log, token, scopeType, scopeId, localStackId, blueprintConfig, flags, } = options;
11
11
  const { force = false, 'project-id': flagProjectId, 'organization-id': flagOrganizationId, stack: flagStack, 'no-wait': noWait = false, verbose = false, } = flags;
12
- // 3-flag combo: destroy without needing a local blueprint config
12
+ // 3-flag combo: non-interactive destroy that does not need a local Blueprint nearby.
13
+ // Scope comes from the --project-id/--organization-id flags (resolved by the base command).
13
14
  if ((flagProjectId || flagOrganizationId) && flagStack && force) {
14
- let scopeType;
15
- let scopeId;
16
- if (flagOrganizationId) {
17
- scopeType = 'organization';
18
- scopeId = flagOrganizationId;
19
- }
20
- else if (flagProjectId) {
21
- scopeType = 'project';
22
- scopeId = flagProjectId;
23
- }
24
- else {
15
+ if (!scopeType || !scopeId) {
25
16
  return { success: false, error: 'Unable to determine scope for Blueprint Destroy' };
26
17
  }
27
18
  const auth = { token, scopeType, scopeId };
@@ -33,7 +24,7 @@ export async function blueprintDestroyCore(options) {
33
24
  });
34
25
  if (!ok)
35
26
  return { success: false, error: error || 'Failed to destroy Stack deployment' };
36
- clearLocalStackIdFromConfig(blueprint, stack.id, log);
27
+ clearLocalStackIdFromConfig(blueprintConfig, stack.id, log);
37
28
  if (noWait) {
38
29
  log(styleText(['bold', 'magenta'], 'Stack destruction started!'));
39
30
  return { success: true, json: { stackId: stack.id, stackName: stack.name } };
@@ -48,7 +39,6 @@ export async function blueprintDestroyCore(options) {
48
39
  verbose,
49
40
  });
50
41
  }
51
- const { scopeType, scopeId, stackId } = blueprint;
52
42
  if (!scopeType || !scopeId)
53
43
  return { success: false, error: 'Scope is required' };
54
44
  const auth = { token, scopeType, scopeId };
@@ -63,8 +53,8 @@ export async function blueprintDestroyCore(options) {
63
53
  return { success: false, error: overrideStack.error || 'Failed to get Stack' };
64
54
  stack = overrideStack.stack;
65
55
  }
66
- else if (stackId) {
67
- const blueprintStack = await getStack({ stackId, auth, logger: log });
56
+ else if (localStackId) {
57
+ const blueprintStack = await getStack({ stackId: localStackId, auth, logger: log });
68
58
  if (!blueprintStack.ok)
69
59
  return { success: false, error: blueprintStack.error || 'Failed to get Stack' };
70
60
  stack = blueprintStack.stack;
@@ -115,7 +105,7 @@ export async function blueprintDestroyCore(options) {
115
105
  return { success: false, error: error || 'Failed to destroy Stack deployment' };
116
106
  }
117
107
  destroySpinner.stop().clear();
118
- clearLocalStackIdFromConfig(blueprint, stack.id, log);
108
+ clearLocalStackIdFromConfig(blueprintConfig, stack.id, log);
119
109
  if (noWait) {
120
110
  log(styleText(['bold', 'magenta'], 'Stack destruction started!'));
121
111
  return { success: true, json: { stackId: stack.id, stackName: stack.name } };
@@ -135,13 +125,13 @@ export async function blueprintDestroyCore(options) {
135
125
  return { success: false, error: errorMessage };
136
126
  }
137
127
  }
138
- function clearLocalStackIdFromConfig(blueprint, destroyedStackId, log) {
139
- if (!blueprint.blueprintConfig)
128
+ function clearLocalStackIdFromConfig(blueprintConfig, destroyedStackId, log) {
129
+ if (!blueprintConfig)
140
130
  return;
141
- if (blueprint.blueprintConfig.stackId !== destroyedStackId)
131
+ if (blueprintConfig.stackId !== destroyedStackId)
142
132
  return;
143
133
  try {
144
- patchConfigFile(blueprint.fileInfo.blueprintFilePath, { stackId: null });
134
+ patchLocatedConfig(blueprintConfig, { stackId: null });
145
135
  log(styleText('dim', 'Removed stackId from .sanity/blueprint.config.json'));
146
136
  }
147
137
  catch {
@@ -8,12 +8,12 @@ import { readConfigFile } from '../../actions/blueprints/config.js';
8
8
  import { resolveIds } from '../../actions/blueprints/resolve.js';
9
9
  import { getStack } from '../../actions/blueprints/stacks.js';
10
10
  import config, { RUNTIME_CLI_VERSION } from '../../config.js';
11
- import { getWarnings } from '../../utils/blueprints/warnings.js';
12
11
  import { formatDateTime } from '../../utils/display/dates.js';
13
12
  import { advise, check, filePathRelativeToCwd, niceId, renderSection, severe, unsure, } from '../../utils/display/presenters.js';
14
13
  import { styleText } from '../../utils/style-text.js';
15
14
  import { createTracedFetch } from '../../utils/traced-fetch.js';
16
15
  import { validTokenOrErrorMessage } from '../../utils/validated-token.js';
16
+ import { getWarnings } from '../../utils/warnings.js';
17
17
  import { blueprintConfigCore } from './config.js';
18
18
  const diagLookup = {
19
19
  online: 'Host online',
@@ -23,7 +23,9 @@ const diagLookup = {
23
23
  userHasAccess: 'User has access',
24
24
  };
25
25
  const warnLookup = {
26
- projectLayout: 'Project layout',
26
+ studio: 'In studio',
27
+ floating: 'In own dir',
28
+ functions: 'In functions dir',
27
29
  };
28
30
  function sourceLabel(source) {
29
31
  switch (source) {
@@ -243,16 +245,32 @@ export async function blueprintDoctorCore(options) {
243
245
  for (const key in warnLookup) {
244
246
  warningDiagnostics[key] = { status: null };
245
247
  }
246
- // LAYOUT ISSUES; IE Functions in Studio
247
- let layoutWarnings = [];
248
+ // Workspace issues; IE Functions in Studio
249
+ let warnings = [];
248
250
  if (localBlueprint) {
249
251
  const blueprintDir = dirname(localBlueprint.fileInfo.blueprintFilePath || '.');
250
- layoutWarnings = getWarnings({ dir: blueprintDir });
251
- if (layoutWarnings.length) {
252
- warningDiagnostics.projectLayout = {
253
- status: true,
254
- detail: 'Blueprint config in the Studio dir',
255
- };
252
+ warnings = getWarnings({ dir: blueprintDir });
253
+ if (warnings.length) {
254
+ for (const warning of warnings) {
255
+ if (warning.code === 'studio') {
256
+ warningDiagnostics.studio = {
257
+ status: true,
258
+ detail: 'Blueprint config in the Studio dir',
259
+ };
260
+ }
261
+ if (warning.code === 'functions') {
262
+ warningDiagnostics.functions = {
263
+ status: true,
264
+ detail: 'Blueprints config in the Functions dir',
265
+ };
266
+ }
267
+ if (warning.code === 'floating') {
268
+ warningDiagnostics.floating = {
269
+ status: true,
270
+ detail: 'Blueprint not part of a project',
271
+ };
272
+ }
273
+ }
256
274
  }
257
275
  }
258
276
  // --- RENDER REPORT ---
@@ -292,12 +310,14 @@ export async function blueprintDoctorCore(options) {
292
310
  }
293
311
  }
294
312
  let hasWarnings = false;
313
+ // Because TS is TS - narrow string keys to WarningCode before indexing warnLookup
314
+ const isWarningCode = (code) => code in warnLookup;
295
315
  if (Object.values(warningDiagnostics).some((entry) => entry.status === true)) {
296
316
  hasWarnings = true;
297
317
  const maxLabel = Math.max(...Object.values(warnLookup).map((l) => l.length));
298
318
  log(styleText('bold', 'Warnings'));
299
319
  for (const [key, entry] of Object.entries(warningDiagnostics)) {
300
- if (entry.status) {
320
+ if (entry.status && isWarningCode(key)) {
301
321
  const label = warnLookup[key].padEnd(maxLabel);
302
322
  const detail = entry.detail ? ` ${styleText('dim', entry.detail)}` : '';
303
323
  log(` ${advise(label)}${detail}`);
@@ -320,7 +340,7 @@ export async function blueprintDoctorCore(options) {
320
340
  success: true,
321
341
  json: { diagnostics: flatDiagnostics },
322
342
  data: { diagnostics: flatDiagnostics },
323
- warnings: hasWarnings ? layoutWarnings : undefined,
343
+ warnings: hasWarnings ? warnings : undefined,
324
344
  };
325
345
  }
326
346
  if (fix) {
@@ -364,7 +384,7 @@ export async function blueprintDoctorCore(options) {
364
384
  flags: { edit: true, verbose: v },
365
385
  });
366
386
  if (fixResult.success) {
367
- return { ...fixResult, warnings: hasWarnings ? layoutWarnings : undefined };
387
+ return { ...fixResult, warnings: hasWarnings ? warnings : undefined };
368
388
  }
369
389
  return fixResult;
370
390
  }
@@ -1,7 +1,9 @@
1
+ import { dirname } from 'node:path';
1
2
  import { planStack, resolveStackIdByNameOrId } from '../../actions/blueprints/stacks.js';
2
3
  import { createHintCollector } from '../../utils/blueprints/hints.js';
3
4
  import { formatDeploymentPlan, formatResourceTree, hasActionableChanges, } from '../../utils/display/blueprints-formatting.js';
4
5
  import { styleText } from '../../utils/style-text.js';
6
+ import { getWarnings } from '../../utils/warnings.js';
5
7
  export async function blueprintPlanCore(options) {
6
8
  const { bin = 'sanity', log, blueprint, token, flags } = options;
7
9
  const { verbose: _verbose = false } = flags;
@@ -69,6 +71,7 @@ export async function blueprintPlanCore(options) {
69
71
  ],
70
72
  };
71
73
  }
74
+ const warnings = getWarnings({ dir: dirname(fileInfo.blueprintFilePath) });
72
75
  log('');
73
76
  log(formatDeploymentPlan(planResponse.deploymentPlan));
74
77
  if (hasActionableChanges(planResponse.deploymentPlan)) {
@@ -83,5 +86,6 @@ export async function blueprintPlanCore(options) {
83
86
  resources,
84
87
  plan: planResponse.deploymentPlan,
85
88
  },
89
+ warnings: warnings,
86
90
  };
87
91
  }
@@ -1,8 +1,13 @@
1
- import type { BlueprintConfig, CoreResult } from '../index.js';
2
- export interface BlueprintStacksOptions extends BlueprintConfig {
1
+ import type { ScopeType } from '../../utils/types.js';
2
+ import type { CoreConfig, CoreResult } from '../index.js';
3
+ export interface BlueprintStacksOptions extends CoreConfig {
4
+ token: string;
5
+ /** Scope to list Stacks for, resolved by the base command (flags > env > config). */
6
+ scopeType: ScopeType;
7
+ scopeId: string;
8
+ /** Locally-configured Stack ID, used only to highlight the current Stack in listings. */
9
+ localStackId?: string;
3
10
  flags: {
4
- 'project-id'?: string;
5
- 'organization-id'?: string;
6
11
  'include-projects'?: boolean;
7
12
  verbose?: boolean;
8
13
  };
@@ -6,30 +6,8 @@ import { styleText } from '../../utils/style-text.js';
6
6
  /** Max projects to query for stacks during --include-projects */
7
7
  const PROJECT_LIMIT = 50;
8
8
  export async function blueprintStacksCore(options) {
9
- const { log, token, blueprint, flags } = options;
10
- const { scopeType: blueprintScopeType, scopeId: blueprintScopeId, stackId: blueprintStackId, } = blueprint;
11
- const { 'project-id': flagProjectId, 'organization-id': flagOrganizationId, 'include-projects': includeProjects = false, verbose: _verbose = false, } = flags;
12
- if (flagOrganizationId && flagProjectId) {
13
- log.error('Cannot specify both --organization-id and --project-id');
14
- return { success: false, error: 'Cannot specify both --organization-id and --project-id' };
15
- }
16
- let scopeType = blueprintScopeType;
17
- let scopeId = blueprintScopeId;
18
- if (flagOrganizationId) {
19
- scopeType = 'organization';
20
- scopeId = flagOrganizationId;
21
- }
22
- if (flagProjectId) {
23
- scopeType = 'project';
24
- scopeId = flagProjectId;
25
- }
26
- if (!scopeType || !scopeId) {
27
- return {
28
- success: false,
29
- error: 'Unable to determine scope for Blueprint Stacks',
30
- suggestions: ['Run in a Blueprint directory or provide a project with --project-id.'],
31
- };
32
- }
9
+ const { log, token, scopeType, scopeId, localStackId: blueprintStackId, flags } = options;
10
+ const { 'include-projects': includeProjects = false } = flags;
33
11
  if (includeProjects && scopeType === 'organization') {
34
12
  return deepListStacks({ organizationId: scopeId, token, log, blueprintStackId });
35
13
  }
@@ -9,6 +9,7 @@ import { EVENT_DOCUMENT_CREATE, EVENT_DOCUMENT_DELETE, EVENT_DOCUMENT_UPDATE, EV
9
9
  import { check, indent, warn } from '../../utils/display/presenters.js';
10
10
  import { validateFunctionName } from '../../utils/functions/validate-name.js';
11
11
  import { styleText } from '../../utils/style-text.js';
12
+ import { getWarnings } from '../../utils/warnings.js';
12
13
  export const generateFunctionBlueprintResourceTemplate = (fnName, eventNames) => {
13
14
  const functionType = MAP_EVENT_TO_FUNCTION_TYPE[eventNames[0]];
14
15
  let definer = '';
@@ -40,6 +41,7 @@ export async function functionAddCore(options) {
40
41
  const root = cwd();
41
42
  const { log, blueprint, flags } = options;
42
43
  const { blueprintFilePath } = blueprint.fileInfo;
44
+ const warnings = getWarnings({ dir: dirname(blueprintFilePath) });
43
45
  const { example: flagExample, name: flagResourceName, type: flagType, javascript: flagJs, helpers: flagHelpers, install: flagI, installer: flagInstaller, } = flags;
44
46
  let { language: flagLang } = flags;
45
47
  flagLang = flagJs ? 'js' : flagLang;
@@ -208,7 +210,7 @@ export async function functionAddCore(options) {
208
210
  else {
209
211
  log(`Function "${styleText('bold', fnName)}" added to Blueprint file.`);
210
212
  }
211
- return { success: true };
213
+ return { success: true, warnings };
212
214
  }
213
215
  catch (err) {
214
216
  const errorMessage = err instanceof Error ? err.message : String(err);
@@ -1,7 +1,6 @@
1
1
  import { type ReadBlueprintResult } from '../actions/blueprints/blueprint.js';
2
- import type { Warning } from '../utils/blueprints/warnings.js';
3
2
  import type { Logger } from '../utils/logger.js';
4
- import type { AuthParams, Result, ScopeType, Stack } from '../utils/types.js';
3
+ import type { AuthParams, Result, ScopeType, Stack, Warning } from '../utils/types.js';
5
4
  export interface CoreConfig {
6
5
  /** The CLI binary name. */
7
6
  bin: string;
@@ -10,3 +10,9 @@ export declare function transpileFunction(resource: FunctionResource, { installe
10
10
  timings: Record<string, number>;
11
11
  bundled: boolean;
12
12
  }>;
13
+ /**
14
+ * Vite 8's `build()` wraps failures in a `BundleError` whose `message` contains
15
+ * a "Build failed with N errors" preamble and full stack traces. The individual
16
+ * errors on `.errors` carry the original, human-readable messages.
17
+ */
18
+ export declare function formatBuildError(err: unknown): string;
@@ -4,7 +4,6 @@ import { performance } from 'node:perf_hooks';
4
4
  import { cwd } from 'node:process';
5
5
  import * as find from 'empathic/find';
6
6
  import { build as viteBuild } from 'vite';
7
- import tsConfigPaths from 'vite-tsconfig-paths';
8
7
  import { detectNativeModulesPlugin } from '../detect-native-modules.js';
9
8
  import { findFunctionEntryPoint } from '../find-entry-point.js';
10
9
  import { cleanupSourceMaps } from './cleanup-source-maps.js';
@@ -51,7 +50,7 @@ export async function transpileFunction(resource, { installer } = {}) {
51
50
  minify: false,
52
51
  sourcemap: true,
53
52
  ssr: true,
54
- rollupOptions: {
53
+ rolldownOptions: {
55
54
  input: entry,
56
55
  output: {
57
56
  format: 'esm',
@@ -66,16 +65,18 @@ export async function transpileFunction(resource, { installer } = {}) {
66
65
  external: bundle ? [] : [/node_modules/],
67
66
  },
68
67
  },
68
+ resolve: {
69
+ // Replaces the vite-tsconfig-paths plugin; vite 8 resolves tsconfig
70
+ // `compilerOptions.paths` natively
71
+ tsconfigPaths: true,
72
+ },
69
73
  ssr: {
70
74
  noExternal: bundle ? true : [],
71
75
  resolve: {
72
76
  conditions: ['node'],
73
77
  },
74
78
  },
75
- plugins: [
76
- ...(bundle ? [detectNativeModulesPlugin(), pnpmResolvePlugin(cwd())] : []),
77
- tsConfigPaths(),
78
- ],
79
+ plugins: bundle ? [detectNativeModulesPlugin(), pnpmResolvePlugin(cwd())] : [],
79
80
  });
80
81
  timings['transpile:build'] = performance.now() - viteStart;
81
82
  const verifyStart = performance.now();
@@ -99,11 +100,37 @@ export async function transpileFunction(resource, { installer } = {}) {
99
100
  }
100
101
  catch (err) {
101
102
  await cleanupTmpDir();
102
- throw new Error(`Transpiling of function failed: ${err instanceof Error ? err.message : err}`, {
103
+ throw new Error(`Transpiling of function failed: ${formatBuildError(err)}`, {
103
104
  cause: err,
104
105
  });
105
106
  }
106
107
  }
108
+ /**
109
+ * Vite 8's `build()` wraps failures in a `BundleError` whose `message` contains
110
+ * a "Build failed with N errors" preamble and full stack traces. The individual
111
+ * errors on `.errors` carry the original, human-readable messages.
112
+ */
113
+ export function formatBuildError(err) {
114
+ if (!(err instanceof Error))
115
+ return String(err);
116
+ if ('errors' in err && Array.isArray(err.errors) && err.errors.length > 0) {
117
+ return err.errors.map(formatBuildErrorEntry).join('\n');
118
+ }
119
+ return err.message;
120
+ }
121
+ /**
122
+ * A `BundleError.errors` entry may be...
123
+ * a real `Error`,
124
+ * a plain object carrying a `message` (Rolldown/Oxc diagnostics),
125
+ * or unknown
126
+ * Prefer `.message` and fall back to `String()`
127
+ */
128
+ function formatBuildErrorEntry(e) {
129
+ if (e instanceof Error)
130
+ return e.message;
131
+ const message = e?.message;
132
+ return typeof message === 'string' ? message : String(e);
133
+ }
107
134
  async function writeTranspiledPackageJson(inputDir, outputFilePath) {
108
135
  const baseName = path.basename(outputFilePath);
109
136
  let original;
@@ -218,4 +218,8 @@ export type AssetPrepResult = {
218
218
  cleanup: () => Promise<void>;
219
219
  error: string;
220
220
  };
221
+ export type Warning = {
222
+ code: string;
223
+ suggestions: string[];
224
+ };
221
225
  export {};
@@ -0,0 +1,14 @@
1
+ import type { Warning } from './types.js';
2
+ export type WarningCode = 'studio' | 'floating' | 'functions';
3
+ export type WarningContext = {
4
+ dir: string;
5
+ };
6
+ export type WarningType = {
7
+ code: WarningCode;
8
+ suggestions: string[];
9
+ };
10
+ /**
11
+ * Generates warnings based upon the provided context by running through all rules and returning any matches
12
+ * @param ctx
13
+ */
14
+ export declare const getWarnings: (ctx: WarningContext) => Warning[];
@@ -0,0 +1,45 @@
1
+ import { checkWorkspaceStruct } from './workspace-checks.js';
2
+ const workspaceRuleWarnings = [
3
+ {
4
+ code: 'studio',
5
+ suggestions: [
6
+ 'Blueprint should not be co-located with a Sanity Studio. See docs for correct layout options:',
7
+ // @todo: this will need to updated once the layout is published.
8
+ 'https://www.sanity.io/docs/blueprints/blueprints-introduction#aaeea93bdd42',
9
+ ],
10
+ },
11
+ {
12
+ code: 'floating',
13
+ suggestions: [
14
+ 'Blueprint is in a separate directory from its stack. See docs for recommended layout options:',
15
+ // @todo: this will need to updated once the layout is published.
16
+ 'https://www.sanity.io/docs/blueprints/blueprints-introduction#aaeea93bdd42',
17
+ ],
18
+ },
19
+ {
20
+ code: 'functions',
21
+ suggestions: [
22
+ 'Blueprint is co-located with the functions directory. See docs for correct layout options:',
23
+ // @todo: this will need to updated once the layout is published.
24
+ 'https://www.sanity.io/docs/blueprints/blueprints-introduction#aaeea93bdd42',
25
+ ],
26
+ },
27
+ ];
28
+ const workspaceRulesWarnings = (ctx) => {
29
+ const code = checkWorkspaceStruct(ctx.dir);
30
+ const warnings = workspaceRuleWarnings.find((w) => w.code === code);
31
+ return warnings ? [warnings] : [];
32
+ };
33
+ /**
34
+ * Additive way of generating warnings based upon context
35
+ * At the moment, we have a single workspace warning check,
36
+ * more could be added via [workspaceRulesWarnings, otherWarningChecks, ...]
37
+ */
38
+ const rules = [workspaceRulesWarnings];
39
+ /**
40
+ * Generates warnings based upon the provided context by running through all rules and returning any matches
41
+ * @param ctx
42
+ */
43
+ export const getWarnings = (ctx) => {
44
+ return rules.flatMap((rule) => rule(ctx));
45
+ };
@@ -0,0 +1,9 @@
1
+ import type { WarningCode } from './warnings.js';
2
+ /**
3
+ * Shared utility called by functions or blueprints to deterministically show warnings to users if it's not ideal
4
+ * 1. Checks to see if the given filepath is in a studio specific workspace
5
+ * 2. Checks to see if the given filepath is under a functions directory (/functions/sanity.blueprint.ts)
6
+ * 3. Checks to see if the given filepath is in a self-contained directory (/blueprints/my-blueprint/sanity.blueprint.ts)
7
+ * @param filePath
8
+ */
9
+ export declare const checkWorkspaceStruct: (filePath: string) => WarningCode | undefined;
@@ -0,0 +1,47 @@
1
+ import { existsSync, readdirSync } from 'node:fs';
2
+ import { dirname, join, relative, resolve, sep } from 'node:path';
3
+ /**
4
+ * Shared utility called by functions or blueprints to deterministically show warnings to users if it's not ideal
5
+ * 1. Checks to see if the given filepath is in a studio specific workspace
6
+ * 2. Checks to see if the given filepath is under a functions directory (/functions/sanity.blueprint.ts)
7
+ * 3. Checks to see if the given filepath is in a self-contained directory (/blueprints/my-blueprint/sanity.blueprint.ts)
8
+ * @param filePath
9
+ */
10
+ export const checkWorkspaceStruct = (filePath) => {
11
+ const startDir = resolve(filePath);
12
+ let currentDir = startDir;
13
+ let packageRoot = null;
14
+ let underFunctionsDir = false;
15
+ let isInStudio = false;
16
+ while (true) {
17
+ if (existsSync(join(currentDir, 'package.json'))) {
18
+ packageRoot = currentDir;
19
+ break;
20
+ }
21
+ const parent = dirname(currentDir);
22
+ if (parent === currentDir)
23
+ break;
24
+ currentDir = parent;
25
+ }
26
+ try {
27
+ isInStudio = readdirSync(startDir, { withFileTypes: true }).some((entry) => entry.isFile() &&
28
+ (entry.name.startsWith('sanity.config') || entry.name.startsWith('sanity.cli')));
29
+ }
30
+ catch (_e) {
31
+ // readdirSync lookup failed for some reason
32
+ isInStudio = false;
33
+ }
34
+ if (packageRoot !== null) {
35
+ const rel = packageRoot ? relative(packageRoot, startDir) : '';
36
+ underFunctionsDir = rel.split(sep).includes('functions');
37
+ }
38
+ const isPackageRoot = packageRoot === startDir && !underFunctionsDir;
39
+ const isFloating = !isInStudio && !isPackageRoot && !underFunctionsDir;
40
+ return isInStudio
41
+ ? 'studio'
42
+ : underFunctionsDir
43
+ ? 'functions'
44
+ : isFloating
45
+ ? 'floating'
46
+ : undefined;
47
+ };
@@ -643,8 +643,7 @@
643
643
  "summary": "Destroy a remote Stack deployment and its resources",
644
644
  "enableJsonFlag": true,
645
645
  "needs": [
646
- "token",
647
- "blueprint"
646
+ "scope"
648
647
  ],
649
648
  "isESM": true,
650
649
  "relativePath": [
@@ -1648,8 +1647,7 @@
1648
1647
  "summary": "List remote Stack deployments for your project or organization",
1649
1648
  "enableJsonFlag": true,
1650
1649
  "needs": [
1651
- "token",
1652
- "blueprint"
1650
+ "scope"
1653
1651
  ],
1654
1652
  "isESM": true,
1655
1653
  "relativePath": [
@@ -2998,5 +2996,5 @@
2998
2996
  ]
2999
2997
  }
3000
2998
  },
3001
- "version": "15.2.1"
2999
+ "version": "16.0.1"
3002
3000
  }
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": "15.2.1",
4
+ "version": "16.0.1",
5
5
  "author": "Sanity Runtime Team",
6
6
  "type": "module",
7
7
  "license": "MIT",
@@ -50,6 +50,7 @@
50
50
  "test:depmgmt": "vitest run --config ./test-depmgmt/vitest.config.ts",
51
51
  "test:coverage": "vitest run --coverage --coverage.include \"runtime-cli/src/**/*\"",
52
52
  "test:scenarios": "vitest run test/scenarios",
53
+ "test:bp-version-smoke": "node ./test/blueprints-version-smoke.js",
53
54
  "test:unit": "vitest run --exclude \"test/scenarios/**/*\"",
54
55
  "test:vitest": "vitest run",
55
56
  "typecheck": "tsc --project tsconfig.typecheck.json",
@@ -75,8 +76,7 @@
75
76
  "mime-types": "^3.0.2",
76
77
  "ora": "^9.4.0",
77
78
  "tar-stream": "^3.2.0",
78
- "vite": "^7.3.3",
79
- "vite-tsconfig-paths": "^6.1.1",
79
+ "vite": "^8.0.16",
80
80
  "ws": "^8.20.0",
81
81
  "xdg-basedir": "^5.1.0"
82
82
  },
@@ -1,19 +0,0 @@
1
- export type Warning = {
2
- label: string;
3
- suggestions: string[];
4
- };
5
- export type WarningContext = {
6
- dir: string;
7
- };
8
- export type WarningEntryCheck = {
9
- label: string;
10
- suggestions: string[];
11
- check: (ctx: WarningContext) => boolean;
12
- };
13
- /**
14
- * Checks if the given directory contains files Sanity Studio specific files.
15
- * Directory is either the cwd where `init` is run or during the `doctor` command
16
- */
17
- export declare const isStudioDir: (filePath: string) => boolean;
18
- export declare const warningCheck: WarningEntryCheck[];
19
- export declare const getWarnings: (ctx: WarningContext) => Warning[];
@@ -1,36 +0,0 @@
1
- import { readdirSync } from 'node:fs';
2
- import { resolve } from 'node:path';
3
- /**
4
- * Checks if the given directory contains files Sanity Studio specific files.
5
- * Directory is either the cwd where `init` is run or during the `doctor` command
6
- */
7
- export const isStudioDir = (filePath) => {
8
- try {
9
- const dir = resolve(filePath);
10
- return readdirSync(dir, { withFileTypes: true }).some((entry) => {
11
- if (entry.isFile()) {
12
- return entry.name.startsWith('sanity.config') || entry.name.startsWith('sanity.cli');
13
- }
14
- return false;
15
- });
16
- }
17
- catch (_err) {
18
- return false;
19
- }
20
- };
21
- export const warningCheck = [
22
- {
23
- label: 'Project layout',
24
- suggestions: [
25
- 'A Blueprints file is co-located with a Sanity Studio. Please see the docs for better layout options',
26
- // @todo: this will need to updated once the layout is published.
27
- 'https://www.sanity.io/docs/blueprints/blueprints-introduction#aaeea93bdd42',
28
- ],
29
- check: ({ dir }) => isStudioDir(dir),
30
- },
31
- ];
32
- export const getWarnings = (ctx) => {
33
- return warningCheck
34
- .filter(({ check }) => check(ctx))
35
- .map(({ label, suggestions }) => ({ label, suggestions }));
36
- };