@sanity/runtime-cli 16.0.3 → 17.1.0-next.g3cb2d2e

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 (41) hide show
  1. package/README.md +27 -22
  2. package/dist/actions/functions/test.js +1 -1
  3. package/dist/baseCommands.js +13 -2
  4. package/dist/commands/blueprints/init.js +5 -2
  5. package/dist/config.d.ts +1 -0
  6. package/dist/config.js +7 -1
  7. package/dist/constants.d.ts +1 -0
  8. package/dist/constants.js +1 -0
  9. package/dist/cores/blueprints/init.js +16 -2
  10. package/dist/cores/functions/test.js +44 -0
  11. package/dist/server/app.js +31 -3
  12. package/dist/server/handlers/invoke.d.ts +2 -2
  13. package/dist/server/handlers/invoke.js +7 -4
  14. package/dist/server/static/components/console-panel.js +59 -8
  15. package/dist/server/static/components/rule-panel.js +11 -8
  16. package/dist/server/static/components/shared-styles.js +24 -21
  17. package/dist/server/static/index.html +1 -1
  18. package/dist/server/static/vendor/design-tokens/color.css +48 -48
  19. package/dist/server/static/vendor/design-tokens/index.css +0 -1
  20. package/dist/server/static/vendor/vendor.bundle.js +3807 -3259
  21. package/dist/server/static/web-socket.js +41 -0
  22. package/dist/utils/functions/child-process-wrapper.js +62 -7
  23. package/dist/utils/functions/{invoke-local.d.ts → invoke/local.d.ts} +1 -1
  24. package/dist/utils/functions/invoke/local.js +306 -0
  25. package/dist/utils/functions/invoke/nested.d.ts +25 -0
  26. package/dist/utils/functions/invoke/nested.js +41 -0
  27. package/dist/utils/functions/invoke/prepare.d.ts +20 -0
  28. package/dist/utils/functions/invoke/prepare.js +90 -0
  29. package/dist/utils/interactive.d.ts +1 -0
  30. package/dist/utils/interactive.js +4 -0
  31. package/dist/utils/isTrueish.d.ts +6 -0
  32. package/dist/utils/isTrueish.js +9 -0
  33. package/dist/utils/logger.d.ts +1 -0
  34. package/dist/utils/logger.js +1 -0
  35. package/dist/utils/style-text.js +4 -5
  36. package/dist/utils/traced-fetch.js +2 -0
  37. package/dist/utils/types.d.ts +62 -4
  38. package/oclif.manifest.json +4 -3
  39. package/package.json +18 -14
  40. package/dist/server/static/hot-reload.js +0 -16
  41. package/dist/utils/functions/invoke-local.js +0 -185
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/16.0.3 linux-x64 node-v24.16.0
23
+ @sanity/runtime-cli/17.1.0-next.g3cb2d2e linux-x64 node-v24.18.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/v16.0.3/src/commands/blueprints/add.ts)_
106
+ _See code: [src/commands/blueprints/add.ts](https://github.com/sanity-io/runtime-cli/blob/v17.1.0-next.g3cb2d2e/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/v16.0.3/src/commands/blueprints/config.ts)_
143
+ _See code: [src/commands/blueprints/config.ts](https://github.com/sanity-io/runtime-cli/blob/v17.1.0-next.g3cb2d2e/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/v16.0.3/src/commands/blueprints/deploy.ts)_
197
+ _See code: [src/commands/blueprints/deploy.ts](https://github.com/sanity-io/runtime-cli/blob/v17.1.0-next.g3cb2d2e/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/v16.0.3/src/commands/blueprints/destroy.ts)_
235
+ _See code: [src/commands/blueprints/destroy.ts](https://github.com/sanity-io/runtime-cli/blob/v17.1.0-next.g3cb2d2e/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/v16.0.3/src/commands/blueprints/doctor.ts)_
266
+ _See code: [src/commands/blueprints/doctor.ts](https://github.com/sanity-io/runtime-cli/blob/v17.1.0-next.g3cb2d2e/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/v16.0.3/src/commands/blueprints/info.ts)_
303
+ _See code: [src/commands/blueprints/info.ts](https://github.com/sanity-io/runtime-cli/blob/v17.1.0-next.g3cb2d2e/src/commands/blueprints/info.ts)_
304
304
 
305
305
  ## `sanity-run blueprints init [DIR]`
306
306
 
@@ -312,7 +312,7 @@ USAGE
312
312
  --stack-id <value> | --stack-name <value>] [--project-id <value> | --organization-id <value>]
313
313
 
314
314
  ARGUMENTS
315
- [DIR] Directory to create the local Blueprint in
315
+ [DIR] Directory to create the local Blueprint in (defaults to the current directory)
316
316
 
317
317
  FLAGS
318
318
  --blueprint-type=<option> Blueprint manifest type to use for the local Blueprint
@@ -339,9 +339,14 @@ DESCRIPTION
339
339
 
340
340
  After initialization, use 'blueprints plan' to preview changes, then 'blueprints deploy' to apply them.
341
341
 
342
+ Running without a directory prompts to confirm the current directory. Run 'blueprints init .' to initialize in the
343
+ current directory without a prompt.
344
+
342
345
  EXAMPLES
343
346
  $ sanity-run blueprints init
344
347
 
348
+ $ sanity-run blueprints init .
349
+
345
350
  $ sanity-run blueprints init [directory]
346
351
 
347
352
  $ sanity-run blueprints init --blueprint-type <json|js|ts>
@@ -359,7 +364,7 @@ EXAMPLES
359
364
  $ sanity-run blueprints init old-stack --type <json|js|ts> --project-id <projectId> --stack-id <existingStackId>
360
365
  ```
361
366
 
362
- _See code: [src/commands/blueprints/init.ts](https://github.com/sanity-io/runtime-cli/blob/v16.0.3/src/commands/blueprints/init.ts)_
367
+ _See code: [src/commands/blueprints/init.ts](https://github.com/sanity-io/runtime-cli/blob/v17.1.0-next.g3cb2d2e/src/commands/blueprints/init.ts)_
363
368
 
364
369
  ## `sanity-run blueprints logs`
365
370
 
@@ -405,7 +410,7 @@ EXAMPLES
405
410
  $ sanity-run blueprints logs --before 2026-05-01T00:00:00Z
406
411
  ```
407
412
 
408
- _See code: [src/commands/blueprints/logs.ts](https://github.com/sanity-io/runtime-cli/blob/v16.0.3/src/commands/blueprints/logs.ts)_
413
+ _See code: [src/commands/blueprints/logs.ts](https://github.com/sanity-io/runtime-cli/blob/v17.1.0-next.g3cb2d2e/src/commands/blueprints/logs.ts)_
409
414
 
410
415
  ## `sanity-run blueprints mint-deploy-token`
411
416
 
@@ -450,7 +455,7 @@ EXAMPLES
450
455
  $ sanity-run blueprints mint-deploy-token --organization-id <orgId>
451
456
  ```
452
457
 
453
- _See code: [src/commands/blueprints/mint-deploy-token.ts](https://github.com/sanity-io/runtime-cli/blob/v16.0.3/src/commands/blueprints/mint-deploy-token.ts)_
458
+ _See code: [src/commands/blueprints/mint-deploy-token.ts](https://github.com/sanity-io/runtime-cli/blob/v17.1.0-next.g3cb2d2e/src/commands/blueprints/mint-deploy-token.ts)_
454
459
 
455
460
  ## `sanity-run blueprints plan`
456
461
 
@@ -483,7 +488,7 @@ EXAMPLES
483
488
  $ sanity-run blueprints plan --organization-id <orgId> --stack <name-or-id>
484
489
  ```
485
490
 
486
- _See code: [src/commands/blueprints/plan.ts](https://github.com/sanity-io/runtime-cli/blob/v16.0.3/src/commands/blueprints/plan.ts)_
491
+ _See code: [src/commands/blueprints/plan.ts](https://github.com/sanity-io/runtime-cli/blob/v17.1.0-next.g3cb2d2e/src/commands/blueprints/plan.ts)_
487
492
 
488
493
  ## `sanity-run blueprints promote`
489
494
 
@@ -519,7 +524,7 @@ EXAMPLES
519
524
  $ sanity-run blueprints promote --new-stack-name <new-name>
520
525
  ```
521
526
 
522
- _See code: [src/commands/blueprints/promote.ts](https://github.com/sanity-io/runtime-cli/blob/v16.0.3/src/commands/blueprints/promote.ts)_
527
+ _See code: [src/commands/blueprints/promote.ts](https://github.com/sanity-io/runtime-cli/blob/v17.1.0-next.g3cb2d2e/src/commands/blueprints/promote.ts)_
523
528
 
524
529
  ## `sanity-run blueprints stacks`
525
530
 
@@ -555,7 +560,7 @@ EXAMPLES
555
560
  $ sanity-run blueprints stacks --organization-id <organizationId> --include-projects
556
561
  ```
557
562
 
558
- _See code: [src/commands/blueprints/stacks.ts](https://github.com/sanity-io/runtime-cli/blob/v16.0.3/src/commands/blueprints/stacks.ts)_
563
+ _See code: [src/commands/blueprints/stacks.ts](https://github.com/sanity-io/runtime-cli/blob/v17.1.0-next.g3cb2d2e/src/commands/blueprints/stacks.ts)_
559
564
 
560
565
  ## `sanity-run functions add`
561
566
 
@@ -607,7 +612,7 @@ EXAMPLES
607
612
  $ sanity-run functions add --name my-function --type document-create --type document-update --lang js
608
613
  ```
609
614
 
610
- _See code: [src/commands/functions/add.ts](https://github.com/sanity-io/runtime-cli/blob/v16.0.3/src/commands/functions/add.ts)_
615
+ _See code: [src/commands/functions/add.ts](https://github.com/sanity-io/runtime-cli/blob/v17.1.0-next.g3cb2d2e/src/commands/functions/add.ts)_
611
616
 
612
617
  ## `sanity-run functions dev`
613
618
 
@@ -642,7 +647,7 @@ EXAMPLES
642
647
  $ sanity-run functions dev --timeout 60
643
648
  ```
644
649
 
645
- _See code: [src/commands/functions/dev.ts](https://github.com/sanity-io/runtime-cli/blob/v16.0.3/src/commands/functions/dev.ts)_
650
+ _See code: [src/commands/functions/dev.ts](https://github.com/sanity-io/runtime-cli/blob/v17.1.0-next.g3cb2d2e/src/commands/functions/dev.ts)_
646
651
 
647
652
  ## `sanity-run functions env add NAME KEY VALUE`
648
653
 
@@ -672,7 +677,7 @@ EXAMPLES
672
677
  $ sanity-run functions env add MyFunction API_URL https://api.example.com/
673
678
  ```
674
679
 
675
- _See code: [src/commands/functions/env/add.ts](https://github.com/sanity-io/runtime-cli/blob/v16.0.3/src/commands/functions/env/add.ts)_
680
+ _See code: [src/commands/functions/env/add.ts](https://github.com/sanity-io/runtime-cli/blob/v17.1.0-next.g3cb2d2e/src/commands/functions/env/add.ts)_
676
681
 
677
682
  ## `sanity-run functions env list NAME`
678
683
 
@@ -699,7 +704,7 @@ EXAMPLES
699
704
  $ sanity-run functions env list MyFunction
700
705
  ```
701
706
 
702
- _See code: [src/commands/functions/env/list.ts](https://github.com/sanity-io/runtime-cli/blob/v16.0.3/src/commands/functions/env/list.ts)_
707
+ _See code: [src/commands/functions/env/list.ts](https://github.com/sanity-io/runtime-cli/blob/v17.1.0-next.g3cb2d2e/src/commands/functions/env/list.ts)_
703
708
 
704
709
  ## `sanity-run functions env remove NAME KEY`
705
710
 
@@ -728,7 +733,7 @@ EXAMPLES
728
733
  $ sanity-run functions env remove MyFunction API_URL
729
734
  ```
730
735
 
731
- _See code: [src/commands/functions/env/remove.ts](https://github.com/sanity-io/runtime-cli/blob/v16.0.3/src/commands/functions/env/remove.ts)_
736
+ _See code: [src/commands/functions/env/remove.ts](https://github.com/sanity-io/runtime-cli/blob/v17.1.0-next.g3cb2d2e/src/commands/functions/env/remove.ts)_
732
737
 
733
738
  ## `sanity-run functions logs [NAME]`
734
739
 
@@ -768,7 +773,7 @@ EXAMPLES
768
773
  $ sanity-run functions logs <name> --delete
769
774
  ```
770
775
 
771
- _See code: [src/commands/functions/logs.ts](https://github.com/sanity-io/runtime-cli/blob/v16.0.3/src/commands/functions/logs.ts)_
776
+ _See code: [src/commands/functions/logs.ts](https://github.com/sanity-io/runtime-cli/blob/v17.1.0-next.g3cb2d2e/src/commands/functions/logs.ts)_
772
777
 
773
778
  ## `sanity-run functions test [NAME]`
774
779
 
@@ -825,7 +830,7 @@ EXAMPLES
825
830
  $ sanity-run functions test <name> --event update --data-before '{ "title": "before" }' --data-after '{ "title": "after" }'
826
831
  ```
827
832
 
828
- _See code: [src/commands/functions/test.ts](https://github.com/sanity-io/runtime-cli/blob/v16.0.3/src/commands/functions/test.ts)_
833
+ _See code: [src/commands/functions/test.ts](https://github.com/sanity-io/runtime-cli/blob/v17.1.0-next.g3cb2d2e/src/commands/functions/test.ts)_
829
834
 
830
835
  ## `sanity-run help [COMMAND]`
831
836
 
@@ -845,5 +850,5 @@ DESCRIPTION
845
850
  Display help for sanity-run.
846
851
  ```
847
852
 
848
- _See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/6.2.46/src/commands/help.ts)_
853
+ _See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/6.2.53/src/commands/help.ts)_
849
854
  <!-- commandsstop -->
@@ -1,4 +1,4 @@
1
- import invoke from '../../utils/functions/invoke-local.js';
1
+ import invoke from '../../utils/functions/invoke/local.js';
2
2
  export async function testAction(resource, payload, context, options) {
3
3
  try {
4
4
  const { json, logs, error } = await invoke(resource, payload, context, options);
@@ -4,6 +4,8 @@ import { findBlueprintFile, loadBlueprintFile, parseBlueprintContent, } from './
4
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
+ import config from './config.js';
8
+ import { CI_SETUP_DOCS_URL } from './constants.js';
7
9
  import { presentBlueprintParserErrors } from './utils/display/errors.js';
8
10
  import { Logger } from './utils/logger.js';
9
11
  import { styleText } from './utils/style-text.js';
@@ -319,10 +321,15 @@ export class ResolvedCommand extends RuntimeCommand {
319
321
  // 5. Scope
320
322
  if (needsScope) {
321
323
  if (!resolved.scopeType || !resolved.scopeId) {
322
- this.error('Missing scope: provide --project-id or --organization-id, or run `blueprints init` to create a Blueprint config (.sanity/blueprint.config.json).', {
324
+ // In CI environments, since .sanity/blueprint.config.json is not guaranteed to exist, we can provide a more specific hint
325
+ const errorMessage = config.isCi
326
+ ? 'set SANITY_PROJECT_ID or SANITY_ORGANIZATION_ID.'
327
+ : 'provide --project-id or --organization-id, or run `blueprints init` to create a Blueprint config (.sanity/blueprint.config.json).';
328
+ this.error(`Missing scope: ${errorMessage}`, {
323
329
  suggestions: [
324
330
  `Run \`${this.config.bin} blueprints doctor\` to check your configuration.`,
325
331
  ],
332
+ ref: CI_SETUP_DOCS_URL,
326
333
  });
327
334
  }
328
335
  this.scopeType = resolved.scopeType;
@@ -341,10 +348,14 @@ export class ResolvedCommand extends RuntimeCommand {
341
348
  stackId = await resolveStackIdByNameOrId(stackOverride, auth, log);
342
349
  }
343
350
  if (!stackId) {
344
- this.error(`Missing Stack: provide --stack, or set a Stack in your Blueprint config (\`${this.config.bin} blueprints config --edit\`).`, {
351
+ const errorMessage = config.isCi
352
+ ? "couldn't determine which Stack to target. Set SANITY_BLUEPRINT_STACK_ID."
353
+ : `provide --stack, or set a Stack in your Blueprint config (\`${this.config.bin} blueprints config --edit\`).`;
354
+ this.error(`Missing Stack: ${errorMessage}`, {
345
355
  suggestions: [
346
356
  `Run \`${this.config.bin} blueprints doctor\` to check your configuration.`,
347
357
  ],
358
+ ref: CI_SETUP_DOCS_URL,
348
359
  });
349
360
  }
350
361
  this.stackId = stackId;
@@ -11,9 +11,12 @@ export default class InitCommand extends RuntimeCommand {
11
11
  This is typically the first command you run in a new project. It creates a local Blueprint manifest file (sanity.blueprint.ts, .js, or .json) and provisions a new remote Stack.
12
12
  Additionally, a Blueprint configuration file is created in .sanity/ containing the scope and Stack IDs. This is .gitignored by default.
13
13
 
14
- After initialization, use 'blueprints plan' to preview changes, then 'blueprints deploy' to apply them.`;
14
+ After initialization, use 'blueprints plan' to preview changes, then 'blueprints deploy' to apply them.
15
+
16
+ Running without a directory prompts to confirm the current directory. Run 'blueprints init .' to initialize in the current directory without a prompt.`;
15
17
  static examples = [
16
18
  '<%= config.bin %> <%= command.id %>',
19
+ '<%= config.bin %> <%= command.id %> .',
17
20
  '<%= config.bin %> <%= command.id %> [directory]',
18
21
  '<%= config.bin %> <%= command.id %> --blueprint-type <json|js|ts>',
19
22
  '<%= config.bin %> <%= command.id %> --organization-id <organizationId>',
@@ -25,7 +28,7 @@ After initialization, use 'blueprints plan' to preview changes, then 'blueprints
25
28
  ];
26
29
  static args = {
27
30
  dir: Args.string({
28
- description: 'Directory to create the local Blueprint in',
31
+ description: 'Directory to create the local Blueprint in (defaults to the current directory)',
29
32
  }),
30
33
  };
31
34
  static flags = {
package/dist/config.d.ts CHANGED
@@ -17,6 +17,7 @@ declare const _default: {
17
17
  isLive: boolean;
18
18
  apiUrl: string;
19
19
  populusApiUrl: string;
20
+ isCi: boolean;
20
21
  token: string | null;
21
22
  };
22
23
  export default _default;
package/dist/config.js CHANGED
@@ -3,6 +3,7 @@ import { env } from 'node:process';
3
3
  import { fileURLToPath } from 'node:url';
4
4
  import * as pkg from 'empathic/package';
5
5
  import getToken from './utils/get-token.js';
6
+ import isTrueish from './utils/isTrueish.js';
6
7
  export const BLUEPRINT_CONFIG_VERSION = 'v2025-05-08';
7
8
  // Default API version for Blueprints service calls.
8
9
  // Overridable globally via env, or per-endpoint via the `version` arg to `blueprintsUrl`.
@@ -29,7 +30,11 @@ const isTest = nodeEnv === 'test';
29
30
  const sanityEnv = env.SANITY_INTERNAL_ENV?.toLowerCase() ?? 'production';
30
31
  const sanityProd = sanityEnv === 'production';
31
32
  const isLive = sanityProd || sanityEnv === 'staging';
32
- const isPublishing = env?.PUBLISHING?.toLowerCase() === 'true';
33
+ const isPublishing = isTrueish(env?.PUBLISHING);
34
+ // https://github.com/sanity-io/cli/blob/adf90ea6691ee6c36849e426de9729533706f47f/packages/%40sanity/cli-core/src/util/isCi.ts
35
+ const isCi =
36
+ // Travis CI, CircleCI, Gitlab CI, Appveyor, CodeShip, Travis CI, Jenkins, TeamCity
37
+ isTrueish(env?.CI) || isTrueish(env?.CONTINUOUS_INTEGRATION) || isTrueish(env?.BUILD_NUMBER);
33
38
  const apiUrls = {
34
39
  production: 'https://api.sanity.io/',
35
40
  staging: 'https://api.sanity.work/',
@@ -74,6 +79,7 @@ export default {
74
79
  isLive,
75
80
  apiUrl,
76
81
  populusApiUrl,
82
+ isCi,
77
83
  get token() {
78
84
  return _token === undefined ? loadToken() : _token;
79
85
  },
@@ -43,3 +43,4 @@ export declare const LABEL_MEDIA_LIBRARY_ASSET_UPDATE = "Media Library Asset Upd
43
43
  export declare const LABEL_MEDIA_LIBRARY_ASSET_DELETE = "Media Library Asset Delete";
44
44
  export declare const LABEL_SCHEDULED = "Scheduled";
45
45
  export declare const LABEL_SYNC_TAG_INVALIDATE = "Sync Tag Invalidate";
46
+ export declare const CI_SETUP_DOCS_URL = "https://www.sanity.io/docs/blueprints/deploy-blueprints-from-ci";
package/dist/constants.js CHANGED
@@ -48,3 +48,4 @@ export const LABEL_MEDIA_LIBRARY_ASSET_UPDATE = 'Media Library Asset Update';
48
48
  export const LABEL_MEDIA_LIBRARY_ASSET_DELETE = 'Media Library Asset Delete';
49
49
  export const LABEL_SCHEDULED = 'Scheduled';
50
50
  export const LABEL_SYNC_TAG_INVALIDATE = 'Sync Tag Invalidate';
51
+ export const CI_SETUP_DOCS_URL = 'https://www.sanity.io/docs/blueprints/deploy-blueprints-from-ci';
@@ -1,5 +1,6 @@
1
1
  import { existsSync, readFileSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
+ import { cwd } from 'node:process';
3
4
  import { confirm } from '@inquirer/prompts';
4
5
  import { findBlueprintFile, readLocalBlueprint, writeBlueprintToDisk, } from '../../actions/blueprints/blueprint.js';
5
6
  import { writeConfigFile } from '../../actions/blueprints/config.js';
@@ -11,6 +12,7 @@ import { getProject } from '../../actions/sanity/projects.js';
11
12
  import { BLUEPRINT_CONFIG_DIR, BLUEPRINT_CONFIG_FILE } from '../../config.js';
12
13
  import { check, filePathRelativeToCwd, labeledId, warn } from '../../utils/display/presenters.js';
13
14
  import { promptForBlueprintType, promptForOrganization, promptForProjectInOrg, promptForStack, runScopeAndStackWizard, } from '../../utils/display/prompt.js';
15
+ import { isInteractive } from '../../utils/interactive.js';
14
16
  import { styleText } from '../../utils/style-text.js';
15
17
  import { blueprintConfigCore } from './config.js';
16
18
  /** Derive scope type and ID from mutually exclusive flag values. */
@@ -25,8 +27,10 @@ export async function blueprintInitCore(options) {
25
27
  const { bin = 'sanity', log, token, knownProjectId, args, flags, validateResources } = options;
26
28
  const { dir: flagDir, example: flagExample, 'blueprint-type': flagBlueprintType, 'project-id': flagProjectId, 'organization-id': flagOrganizationId, 'stack-id': flagStackId, 'stack-name': flagStackName, verbose = false, } = flags;
27
29
  const { dir: argDir } = args;
28
- const userProvidedDirName = argDir || flagDir;
29
- const blueprintDir = userProvidedDirName || '.';
30
+ const explicitDir = argDir || flagDir;
31
+ const isHere = !explicitDir || explicitDir === '.' || explicitDir === './';
32
+ const blueprintDir = isHere ? '.' : explicitDir;
33
+ const userProvidedDirName = isHere ? undefined : explicitDir;
30
34
  const validationError = validateFlags({
31
35
  stackId: flagStackId,
32
36
  stackName: flagStackName,
@@ -65,6 +69,16 @@ export async function blueprintInitCore(options) {
65
69
  });
66
70
  }
67
71
  // no blueprint on disk → create new blueprint files from scratch
72
+ if (!explicitDir && isInteractive) {
73
+ const proceed = await confirm({
74
+ message: `Initialize a Blueprint here in ${cwd()}?`,
75
+ default: true,
76
+ });
77
+ if (!proceed) {
78
+ log(`Run \`${bin} blueprints init <directory>\` to initialize in a different location.`);
79
+ return { success: false, error: 'Initialization cancelled.' };
80
+ }
81
+ }
68
82
  return handleFreshInitialization({
69
83
  blueprintDir,
70
84
  userProvidedDirName,
@@ -8,6 +8,7 @@ import { resolveDeployedResources } from '../../utils/blueprints/resolve-deploye
8
8
  import buildPayload from '../../utils/functions/build-payload.js';
9
9
  import { fetchAsset, fetchDocument } from '../../utils/functions/fetch-document.js';
10
10
  import { findFunctionInResources, getFunctionNames } from '../../utils/functions/find.js';
11
+ import { runFunction } from '../../utils/functions/invoke/nested.js';
11
12
  import { parseJsonObject } from '../../utils/parse-json-object.js';
12
13
  import { isEventType, } from '../../utils/types.js';
13
14
  export async function functionTestCore(options) {
@@ -146,12 +147,54 @@ export async function functionTestCore(options) {
146
147
  : {
147
148
  event: 'scheduled',
148
149
  };
150
+ // Records each nested context.invoke() so they can be surfaced in order after the spinner resolves.
151
+ const nestedRuns = [];
152
+ let nestedOrder = 0;
153
+ /**
154
+ * Re-entrant callback that resolves a nested context.invoke(name, payload):
155
+ * it finds the named function in the same blueprint, reuses the already
156
+ * resolved resources, and runs it via the shared invoke-chain helper (which
157
+ * applies the depth/cycle guard and extends the call stack). Passing itself
158
+ * as onNestedInvoke keeps it recursable. Every run is recorded so it can be
159
+ * surfaced after the spinner resolves, including failed ones — a downstream
160
+ * error is fire-and-forget (it doesn't reject the caller) but is still shown.
161
+ */
162
+ const runNested = async (nestedName, nestedPayload, ctx, depth, callStack) => {
163
+ const order = ++nestedOrder;
164
+ const nestedResource = findFunctionInResources(blueprint.resources, nestedName);
165
+ const result = await runFunction(nestedResource, { payload: { data: nestedPayload }, event: 'nested' }, { ...ctx, resources: resolvedResources }, { depth, callStack, onNestedInvoke: runNested });
166
+ nestedRuns.push({ order, name: nestedName, logs: result.logs, error: result.error });
167
+ return result;
168
+ };
169
+ const logNestedRuns = () => {
170
+ for (const run of [...nestedRuns].sort((a, b) => a.order - b.order)) {
171
+ log(`↳ BEGIN: invoke('${run.name}')`);
172
+ // Show any logs received
173
+ if (run.logs?.trim()) {
174
+ log(run.logs.trim());
175
+ }
176
+ // Before any error occurs
177
+ if (run.error) {
178
+ log(run.error.stack || run.error.message || run.error.name);
179
+ }
180
+ log(`↳ END: invoke('${run.name}')`);
181
+ }
182
+ };
149
183
  const spinner = log.ora('Executing function...').start();
150
184
  const { json: response, logs, error, } = await testAction(resource, invokeOptions, contextOptions, {
151
185
  timeout: timeout ? timeout : resource.timeout,
186
+ onNestedInvoke: runNested,
187
+ depth: 0,
188
+ callStack: [fnName],
152
189
  });
153
190
  if (error) {
154
191
  spinner.fail('Function execution failed.');
192
+ // Surface the function's own logs even on failure
193
+ if (logs?.trim()) {
194
+ log('Logs:');
195
+ log(logs);
196
+ }
197
+ logNestedRuns();
155
198
  const errorMessage = error.stack || error.message || error.name;
156
199
  const isTimeout = error.message?.includes('Timeout');
157
200
  return {
@@ -166,6 +209,7 @@ export async function functionTestCore(options) {
166
209
  spinner.succeed('Function execution succeeded.');
167
210
  log('Logs:');
168
211
  log(logs || '');
212
+ logNestedRuns();
169
213
  if (response) {
170
214
  log('Response:');
171
215
  log(JSON.stringify(response, null, 2));
@@ -1,7 +1,7 @@
1
1
  import { existsSync, readFileSync, watchFile } from 'node:fs';
2
2
  import * as http from 'node:http';
3
3
  import { default as mime } from 'mime-types';
4
- import { WebSocketServer } from 'ws';
4
+ import { WebSocket, WebSocketServer } from 'ws';
5
5
  import { findBlueprintFile, loadAndParseBlueprint } from '../actions/blueprints/blueprint.js';
6
6
  import { readConfigFile } from '../actions/blueprints/config.js';
7
7
  import { resolveIds } from '../actions/blueprints/resolve.js';
@@ -10,6 +10,35 @@ import { isRecord } from '../utils/is-record.js';
10
10
  import { isEventType, isGroqContextOptions, } from '../utils/types.js';
11
11
  import { handleInvokeRequest } from './handlers/invoke.js';
12
12
  const app = (host, port, validateResources, executionOptions) => {
13
+ const wss = new WebSocketServer({ port: 8974 });
14
+ // Send a JSON envelope to every connected UI client.
15
+ const broadcast = (envelope) => {
16
+ const message = JSON.stringify(envelope);
17
+ for (const client of wss.clients) {
18
+ if (client.readyState === WebSocket.OPEN)
19
+ client.send(message);
20
+ }
21
+ };
22
+ let nestedOrder = 0;
23
+ /**
24
+ * Re-entrant callback that resolves a nested `context.invoke(name, payload)` by
25
+ * re-entering the invoke pipeline (which spawns its own child) and streams the
26
+ * nested run to the UI. Passing `nestedInvoke` to itself keeps it recursable.
27
+ */
28
+ const nestedInvoke = async (name, payload, ctx, depth, callStack) => {
29
+ const order = ++nestedOrder;
30
+ const result = await handleInvokeRequest(name, { data: payload }, { event: 'nested' }, ctx, validateResources, executionOptions, depth, callStack, nestedInvoke);
31
+ broadcast({
32
+ type: 'nested-invoke',
33
+ name,
34
+ order,
35
+ logs: result.logs,
36
+ json: result.json,
37
+ error: result.error,
38
+ timings: result.timings,
39
+ });
40
+ return result;
41
+ };
13
42
  const requestListener = async (req, res) => {
14
43
  res.setHeader('Content-Type', 'application/json');
15
44
  switch (true) {
@@ -50,7 +79,7 @@ const app = (host, port, validateResources, executionOptions) => {
50
79
  delete context.clientOptions.token;
51
80
  }
52
81
  }
53
- const result = await handleInvokeRequest(functionName, event, metadata, context, validateResources, executionOptions);
82
+ const result = await handleInvokeRequest(functionName, event, metadata, context, validateResources, executionOptions, 0, [], nestedInvoke);
54
83
  // Add Server-Timing header
55
84
  const timingHeaders = [];
56
85
  for (const [key, value] of Object.entries(result.timings)) {
@@ -235,7 +264,6 @@ const app = (host, port, validateResources, executionOptions) => {
235
264
  };
236
265
  const server = http.createServer(requestListener);
237
266
  server.listen(port, host, () => { });
238
- const wss = new WebSocketServer({ port: 8974 });
239
267
  wss.on('connection', function connection(ws) {
240
268
  ws.on('error', console.error);
241
269
  const fileInfo = findBlueprintFile();
@@ -1,4 +1,4 @@
1
- import type { InvocationResponse, InvokeContextOptions, InvokeExecutionOptions, InvokePayloadMetadata } from '../../utils/types.js';
2
- export declare function handleInvokeRequest(functionName: string, event: Record<string, unknown>, metadata: InvokePayloadMetadata, context: InvokeContextOptions, validateResources: boolean, executionOptions?: Partial<InvokeExecutionOptions>): Promise<InvocationResponse & {
1
+ import type { InvocationResponse, InvokeContextOptions, InvokeExecutionOptions, InvokePayloadMetadata, NestedInvoke } from '../../utils/types.js';
2
+ export declare function handleInvokeRequest(functionName: string, event: Record<string, unknown>, metadata: InvokePayloadMetadata, context: InvokeContextOptions, validateResources: boolean, executionOptions?: Partial<InvokeExecutionOptions>, depth?: number, callStack?: string[], onNestedInvoke?: NestedInvoke): Promise<InvocationResponse & {
3
3
  timings: Record<string, number>;
4
4
  }>;
@@ -2,10 +2,10 @@ import { readLocalBlueprint } from '../../actions/blueprints/blueprint.js';
2
2
  import config from '../../config.js';
3
3
  import { resolveDeployedResources } from '../../utils/blueprints/resolve-deployed-resources.js';
4
4
  import { findFunctionInResources } from '../../utils/functions/find.js';
5
- import invoke from '../../utils/functions/invoke-local.js';
5
+ import { runFunction } from '../../utils/functions/invoke/nested.js';
6
6
  import { Logger } from '../../utils/logger.js';
7
7
  const silentLogger = Logger(() => { });
8
- export async function handleInvokeRequest(functionName, event, metadata, context, validateResources, executionOptions) {
8
+ export async function handleInvokeRequest(functionName, event, metadata, context, validateResources, executionOptions, depth = 0, callStack = [], onNestedInvoke) {
9
9
  const start = performance.now();
10
10
  const { resources, stackId, scopeType, scopeId } = await readLocalBlueprint(silentLogger, {
11
11
  resources: validateResources,
@@ -22,9 +22,12 @@ export async function handleInvokeRequest(functionName, event, metadata, context
22
22
  payload: event,
23
23
  ...metadata,
24
24
  };
25
- const response = await invoke(resource, payload, { ...context, resources: resolvedResources }, {
25
+ const response = await runFunction(resource, payload, { ...context, resources: resolvedResources }, {
26
26
  forceColor: executionOptions?.forceColor ?? false,
27
- timeout: executionOptions?.timeout ?? resource.timeout,
27
+ timeout: executionOptions?.timeout,
28
+ depth,
29
+ callStack,
30
+ onNestedInvoke,
28
31
  });
29
32
  const timings = { ...response.timings, 'blueprint:read': readBlueprintTime };
30
33
  return { ...response, timings };