@sanity/runtime-cli 14.11.0 → 14.12.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 (63) hide show
  1. package/README.md +45 -43
  2. package/dist/actions/blueprints/blueprint.d.ts +49 -16
  3. package/dist/actions/blueprints/blueprint.js +97 -139
  4. package/dist/actions/blueprints/resolve.d.ts +51 -0
  5. package/dist/actions/blueprints/resolve.js +52 -0
  6. package/dist/actions/blueprints/resources.js +35 -12
  7. package/dist/actions/functions/dev.d.ts +1 -2
  8. package/dist/actions/functions/dev.js +2 -2
  9. package/dist/baseCommands.d.ts +47 -30
  10. package/dist/baseCommands.js +187 -72
  11. package/dist/commands/blueprints/add.d.ts +3 -2
  12. package/dist/commands/blueprints/add.js +3 -2
  13. package/dist/commands/blueprints/config.d.ts +3 -2
  14. package/dist/commands/blueprints/config.js +3 -2
  15. package/dist/commands/blueprints/deploy.d.ts +3 -2
  16. package/dist/commands/blueprints/deploy.js +4 -3
  17. package/dist/commands/blueprints/destroy.d.ts +3 -2
  18. package/dist/commands/blueprints/destroy.js +3 -2
  19. package/dist/commands/blueprints/doctor.d.ts +0 -1
  20. package/dist/commands/blueprints/doctor.js +2 -3
  21. package/dist/commands/blueprints/info.d.ts +4 -2
  22. package/dist/commands/blueprints/info.js +6 -3
  23. package/dist/commands/blueprints/init.d.ts +0 -1
  24. package/dist/commands/blueprints/init.js +1 -2
  25. package/dist/commands/blueprints/logs.d.ts +3 -2
  26. package/dist/commands/blueprints/logs.js +4 -3
  27. package/dist/commands/blueprints/plan.d.ts +3 -2
  28. package/dist/commands/blueprints/plan.js +5 -3
  29. package/dist/commands/blueprints/promote.d.ts +3 -2
  30. package/dist/commands/blueprints/promote.js +3 -2
  31. package/dist/commands/blueprints/stacks.d.ts +3 -2
  32. package/dist/commands/blueprints/stacks.js +3 -2
  33. package/dist/commands/functions/add.d.ts +3 -2
  34. package/dist/commands/functions/add.js +4 -3
  35. package/dist/commands/functions/build.d.ts +3 -2
  36. package/dist/commands/functions/build.js +3 -2
  37. package/dist/commands/functions/dev.d.ts +3 -2
  38. package/dist/commands/functions/dev.js +3 -2
  39. package/dist/commands/functions/env/add.d.ts +3 -2
  40. package/dist/commands/functions/env/add.js +3 -2
  41. package/dist/commands/functions/env/list.d.ts +3 -2
  42. package/dist/commands/functions/env/list.js +3 -2
  43. package/dist/commands/functions/env/remove.d.ts +3 -2
  44. package/dist/commands/functions/env/remove.js +3 -2
  45. package/dist/commands/functions/logs.d.ts +3 -2
  46. package/dist/commands/functions/logs.js +4 -3
  47. package/dist/commands/functions/test.d.ts +3 -2
  48. package/dist/commands/functions/test.js +3 -2
  49. package/dist/constants.d.ts +15 -2
  50. package/dist/constants.js +14 -10
  51. package/dist/cores/blueprints/config.js +9 -4
  52. package/dist/cores/blueprints/destroy.js +78 -56
  53. package/dist/cores/blueprints/doctor.js +19 -5
  54. package/dist/cores/blueprints/init.js +2 -2
  55. package/dist/cores/functions/add.js +11 -7
  56. package/dist/cores/functions/dev.js +1 -1
  57. package/dist/server/app.d.ts +1 -2
  58. package/dist/server/app.js +16 -8
  59. package/dist/server/handlers/invoke.d.ts +1 -2
  60. package/dist/server/handlers/invoke.js +4 -4
  61. package/dist/server/static/components/rule-panel.js +8 -10
  62. package/oclif.manifest.json +503 -73
  63. package/package.json +2 -2
@@ -1,5 +1,6 @@
1
- import { LocalBlueprintCommand } from '../../baseCommands.js';
2
- export default class AddCommand extends LocalBlueprintCommand<typeof AddCommand> {
1
+ import { ResolvedCommand } from '../../baseCommands.js';
2
+ export default class AddCommand extends ResolvedCommand<typeof AddCommand> {
3
+ static needs: readonly ["blueprint"];
3
4
  static summary: string;
4
5
  static description: string;
5
6
  static examples: string[];
@@ -1,14 +1,15 @@
1
1
  import { Flags } from '@oclif/core';
2
- import { LocalBlueprintCommand } from '../../baseCommands.js';
2
+ import { ResolvedCommand } from '../../baseCommands.js';
3
3
  import { FUNCTION_TYPES } from '../../constants.js';
4
4
  import { functionAddCore } from '../../cores/functions/index.js';
5
5
  import { Logger } from '../../utils/logger.js';
6
6
  import { INSTALLER_OPTIONS } from '../../utils/types.js';
7
- export default class AddCommand extends LocalBlueprintCommand {
7
+ export default class AddCommand extends ResolvedCommand {
8
+ static needs = ['blueprint'];
8
9
  static summary = 'Add a Function to your Blueprint';
9
10
  static description = `Scaffolds a new Function in the functions/ folder and templates a resource for your Blueprint manifest.
10
11
 
11
- Functions are serverless handlers triggered by document events (create, update, delete, publish) or media library events.
12
+ Functions are serverless handlers triggered by document, live content or media-library events (create, update, delete, publish).
12
13
 
13
14
  After adding, use 'functions dev' to test locally, then 'blueprints deploy' to publish.`;
14
15
  static examples = [
@@ -1,5 +1,6 @@
1
- import { LocalBlueprintCommand } from '../../baseCommands.js';
2
- export default class BuildCommand extends LocalBlueprintCommand<typeof BuildCommand> {
1
+ import { ResolvedCommand } from '../../baseCommands.js';
2
+ export default class BuildCommand extends ResolvedCommand<typeof BuildCommand> {
3
+ static needs: readonly ["blueprint"];
3
4
  static summary: string;
4
5
  static hidden: boolean;
5
6
  static args: {
@@ -1,9 +1,10 @@
1
1
  import { Args, Flags } from '@oclif/core';
2
- import { LocalBlueprintCommand } from '../../baseCommands.js';
2
+ import { ResolvedCommand } from '../../baseCommands.js';
3
3
  import { functionBuildCore } from '../../cores/functions/build.js';
4
4
  import { Logger } from '../../utils/logger.js';
5
5
  import { INSTALLER_OPTIONS } from '../../utils/types.js';
6
- export default class BuildCommand extends LocalBlueprintCommand {
6
+ export default class BuildCommand extends ResolvedCommand {
7
+ static needs = ['blueprint'];
7
8
  static summary = 'Build Sanity Function(s) to zip archives';
8
9
  static hidden = true;
9
10
  static args = {
@@ -1,5 +1,6 @@
1
- import { LocalBlueprintCommand } from '../../baseCommands.js';
2
- export default class DevCommand extends LocalBlueprintCommand<typeof DevCommand> {
1
+ import { ResolvedCommand } from '../../baseCommands.js';
2
+ export default class DevCommand extends ResolvedCommand<typeof DevCommand> {
3
+ static needs: readonly ["blueprint"];
3
4
  static summary: string;
4
5
  static description: string;
5
6
  static examples: string[];
@@ -1,8 +1,9 @@
1
1
  import { Flags } from '@oclif/core';
2
- import { LocalBlueprintCommand } from '../../baseCommands.js';
2
+ import { ResolvedCommand } from '../../baseCommands.js';
3
3
  import { functionDevCore } from '../../cores/functions/dev.js';
4
4
  import { Logger } from '../../utils/logger.js';
5
- export default class DevCommand extends LocalBlueprintCommand {
5
+ export default class DevCommand extends ResolvedCommand {
6
+ static needs = ['blueprint'];
6
7
  static summary = 'Start the Sanity Function emulator';
7
8
  static description = `Runs a local, web-based development server to test your functions before deploying.
8
9
 
@@ -1,5 +1,6 @@
1
- import { DeployedStackCommand } from '../../../baseCommands.js';
2
- export default class EnvAddCommand extends DeployedStackCommand<typeof EnvAddCommand> {
1
+ import { ResolvedCommand } from '../../../baseCommands.js';
2
+ export default class EnvAddCommand extends ResolvedCommand<typeof EnvAddCommand> {
3
+ static needs: readonly ["deployedStack", "blueprint"];
3
4
  static summary: string;
4
5
  static description: string;
5
6
  static args: {
@@ -1,8 +1,9 @@
1
1
  import { Args } from '@oclif/core';
2
- import { DeployedStackCommand } from '../../../baseCommands.js';
2
+ import { ResolvedCommand } from '../../../baseCommands.js';
3
3
  import { functionEnvAddCore } from '../../../cores/functions/env/add.js';
4
4
  import { Logger } from '../../../utils/logger.js';
5
- export default class EnvAddCommand extends DeployedStackCommand {
5
+ export default class EnvAddCommand extends ResolvedCommand {
6
+ static needs = ['deployedStack', 'blueprint'];
6
7
  static summary = 'Add or set an environment variable for a deployed function';
7
8
  static description = `Sets an environment variable in a deployed Sanity Function. If the variable already exists, its value is updated.
8
9
 
@@ -1,5 +1,6 @@
1
- import { DeployedStackCommand } from '../../../baseCommands.js';
2
- export default class EnvListCommand extends DeployedStackCommand<typeof EnvListCommand> {
1
+ import { ResolvedCommand } from '../../../baseCommands.js';
2
+ export default class EnvListCommand extends ResolvedCommand<typeof EnvListCommand> {
3
+ static needs: readonly ["deployedStack", "blueprint"];
3
4
  static summary: string;
4
5
  static description: string;
5
6
  static args: {
@@ -1,8 +1,9 @@
1
1
  import { Args } from '@oclif/core';
2
- import { DeployedStackCommand } from '../../../baseCommands.js';
2
+ import { ResolvedCommand } from '../../../baseCommands.js';
3
3
  import { functionEnvListCore } from '../../../cores/functions/env/list.js';
4
4
  import { Logger } from '../../../utils/logger.js';
5
- export default class EnvListCommand extends DeployedStackCommand {
5
+ export default class EnvListCommand extends ResolvedCommand {
6
+ static needs = ['deployedStack', 'blueprint'];
6
7
  static summary = 'List environment variables for a deployed function';
7
8
  static description = `Displays all environment variables (keys only) configured in a deployed Sanity Function.
8
9
 
@@ -1,5 +1,6 @@
1
- import { DeployedStackCommand } from '../../../baseCommands.js';
2
- export default class EnvRemoveCommand extends DeployedStackCommand<typeof EnvRemoveCommand> {
1
+ import { ResolvedCommand } from '../../../baseCommands.js';
2
+ export default class EnvRemoveCommand extends ResolvedCommand<typeof EnvRemoveCommand> {
3
+ static needs: readonly ["deployedStack", "blueprint"];
3
4
  static summary: string;
4
5
  static description: string;
5
6
  static args: {
@@ -1,8 +1,9 @@
1
1
  import { Args } from '@oclif/core';
2
- import { DeployedStackCommand } from '../../../baseCommands.js';
2
+ import { ResolvedCommand } from '../../../baseCommands.js';
3
3
  import { functionEnvRemoveCore } from '../../../cores/functions/env/remove.js';
4
4
  import { Logger } from '../../../utils/logger.js';
5
- export default class EnvRemoveCommand extends DeployedStackCommand {
5
+ export default class EnvRemoveCommand extends ResolvedCommand {
6
+ static needs = ['deployedStack', 'blueprint'];
6
7
  static summary = 'Remove an environment variable from a deployed function';
7
8
  static description = `Deletes an environment variable from a deployed Sanity Function. The change takes effect on the next function invocation.
8
9
 
@@ -1,5 +1,6 @@
1
- import { DeployedStackCommand } from '../../baseCommands.js';
2
- export default class LogsCommand extends DeployedStackCommand<typeof LogsCommand> {
1
+ import { ResolvedCommand } from '../../baseCommands.js';
2
+ export default class LogsCommand extends ResolvedCommand<typeof LogsCommand> {
3
+ static needs: readonly ["deployedStack", "blueprint"];
3
4
  static summary: string;
4
5
  static description: string;
5
6
  static args: {
@@ -1,8 +1,9 @@
1
1
  import { Args, Flags } from '@oclif/core';
2
- import { DeployedStackCommand, stackFlag, unhide } from '../../baseCommands.js';
2
+ import { ResolvedCommand, stackFlagConfig } from '../../baseCommands.js';
3
3
  import { functionLogsCore } from '../../cores/functions/logs.js';
4
4
  import { Logger } from '../../utils/logger.js';
5
- export default class LogsCommand extends DeployedStackCommand {
5
+ export default class LogsCommand extends ResolvedCommand {
6
+ static needs = ['deployedStack', 'blueprint'];
6
7
  static summary = 'Retrieve or delete logs for a Sanity Function';
7
8
  static description = `Fetches execution logs from a deployed function, useful for debugging production issues or monitoring activity.
8
9
 
@@ -17,7 +18,7 @@ Use --watch (-w) to stream logs in real-time. Use --delete to clear all logs for
17
18
  '<%= config.bin %> <%= command.id %> <name> --delete',
18
19
  ];
19
20
  static flags = {
20
- stack: unhide(stackFlag),
21
+ stack: Flags.string({ ...stackFlagConfig }),
21
22
  limit: Flags.integer({
22
23
  char: 'l',
23
24
  description: 'Total number of log entries to retrieve',
@@ -1,5 +1,6 @@
1
- import { LocalBlueprintCommand } from '../../baseCommands.js';
2
- export default class TestCommand extends LocalBlueprintCommand<typeof TestCommand> {
1
+ import { ResolvedCommand } from '../../baseCommands.js';
2
+ export default class TestCommand extends ResolvedCommand<typeof TestCommand> {
3
+ static needs: readonly ["blueprint"];
3
4
  static summary: string;
4
5
  static description: string;
5
6
  static args: {
@@ -1,8 +1,9 @@
1
1
  import { Args, Flags } from '@oclif/core';
2
- import { LocalBlueprintCommand, organizationIdFlagConfig, projectIdFlagConfig, } from '../../baseCommands.js';
2
+ import { organizationIdFlagConfig, projectIdFlagConfig, ResolvedCommand } from '../../baseCommands.js';
3
3
  import { functionTestCore } from '../../cores/functions/test.js';
4
4
  import { Logger } from '../../utils/logger.js';
5
- export default class TestCommand extends LocalBlueprintCommand {
5
+ export default class TestCommand extends ResolvedCommand {
6
+ static needs = ['blueprint'];
6
7
  static summary = 'Invoke a local Sanity Function';
7
8
  static description = `Executes a function locally with the provided payload, simulating how it would run when deployed. Use this to test your function logic before deploying.
8
9
 
@@ -17,8 +17,20 @@ export declare const EVENT_MEDIA_LIBRARY_ASSET_CREATE = "media-library-asset-cre
17
17
  export declare const EVENT_MEDIA_LIBRARY_ASSET_UPDATE = "media-library-asset-update";
18
18
  export declare const EVENT_MEDIA_LIBRARY_ASSET_DELETE = "media-library-asset-delete";
19
19
  export declare const EVENT_SCHEDULED = "scheduled-function";
20
- export declare const FUNCTION_TYPES: readonly ["document-publish", "document-create", "document-update", "document-delete", "media-library-asset-create", "media-library-asset-update", "media-library-asset-delete", "scheduled-function"];
21
- export type ARRAY_OF_FUNCTION_TYPES = (typeof FUNCTION_TYPES)[number][];
20
+ export declare const EVENT_SYNC_TAG_INVALIDATE = "sync-tag-invalidate";
21
+ export declare const MAP_EVENT_TO_FUNCTION_TYPE: {
22
+ readonly "document-publish": "sanity.function.document";
23
+ readonly "document-create": "sanity.function.document";
24
+ readonly "document-delete": "sanity.function.document";
25
+ readonly "document-update": "sanity.function.document";
26
+ readonly "media-library-asset-create": "sanity.function.media-library.asset";
27
+ readonly "media-library-asset-delete": "sanity.function.media-library.asset";
28
+ readonly "media-library-asset-update": "sanity.function.media-library.asset";
29
+ readonly "scheduled-function": "sanity.function.cron";
30
+ readonly "sync-tag-invalidate": "sanity.function.sync-tag-invalidate";
31
+ };
32
+ export declare const FUNCTION_TYPES: (keyof typeof MAP_EVENT_TO_FUNCTION_TYPE)[];
33
+ export type ARRAY_OF_FUNCTION_TYPES = typeof FUNCTION_TYPES;
22
34
  export declare const PROJECT_SCOPED_FUNCTION_TYPES: ReadonlySet<string>;
23
35
  export declare const ORGANIZATION_SCOPED_FUNCTION_TYPES: ReadonlySet<string>;
24
36
  export declare const MAX_ASSET_SIZE = 209715200;
@@ -30,3 +42,4 @@ export declare const LABEL_MEDIA_LIBRARY_ASSET_CREATE = "Media Library Asset Cre
30
42
  export declare const LABEL_MEDIA_LIBRARY_ASSET_UPDATE = "Media Library Asset Update";
31
43
  export declare const LABEL_MEDIA_LIBRARY_ASSET_DELETE = "Media Library Asset Delete";
32
44
  export declare const LABEL_SCHEDULED = "Scheduled";
45
+ export declare const LABEL_SYNC_TAG_INVALIDATE = "Sync Tag Invalidate";
package/dist/constants.js CHANGED
@@ -17,16 +17,19 @@ export const EVENT_MEDIA_LIBRARY_ASSET_CREATE = 'media-library-asset-create';
17
17
  export const EVENT_MEDIA_LIBRARY_ASSET_UPDATE = 'media-library-asset-update';
18
18
  export const EVENT_MEDIA_LIBRARY_ASSET_DELETE = 'media-library-asset-delete';
19
19
  export const EVENT_SCHEDULED = 'scheduled-function';
20
- export const FUNCTION_TYPES = [
21
- EVENT_DOCUMENT_PUBLISH,
22
- EVENT_DOCUMENT_CREATE,
23
- EVENT_DOCUMENT_UPDATE,
24
- EVENT_DOCUMENT_DELETE,
25
- EVENT_MEDIA_LIBRARY_ASSET_CREATE,
26
- EVENT_MEDIA_LIBRARY_ASSET_UPDATE,
27
- EVENT_MEDIA_LIBRARY_ASSET_DELETE,
28
- EVENT_SCHEDULED,
29
- ];
20
+ export const EVENT_SYNC_TAG_INVALIDATE = 'sync-tag-invalidate';
21
+ export const MAP_EVENT_TO_FUNCTION_TYPE = {
22
+ [EVENT_DOCUMENT_PUBLISH]: SANITY_FUNCTION_DOCUMENT,
23
+ [EVENT_DOCUMENT_CREATE]: SANITY_FUNCTION_DOCUMENT,
24
+ [EVENT_DOCUMENT_DELETE]: SANITY_FUNCTION_DOCUMENT,
25
+ [EVENT_DOCUMENT_UPDATE]: SANITY_FUNCTION_DOCUMENT,
26
+ [EVENT_MEDIA_LIBRARY_ASSET_CREATE]: SANITY_FUNCTION_MEDIA_LIBRARY_ASSET,
27
+ [EVENT_MEDIA_LIBRARY_ASSET_DELETE]: SANITY_FUNCTION_MEDIA_LIBRARY_ASSET,
28
+ [EVENT_MEDIA_LIBRARY_ASSET_UPDATE]: SANITY_FUNCTION_MEDIA_LIBRARY_ASSET,
29
+ [EVENT_SCHEDULED]: SANITY_FUNCTION_SCHEDULED,
30
+ [EVENT_SYNC_TAG_INVALIDATE]: SANITY_FUNCTION_SYNC_TAG_INVALIDATE,
31
+ };
32
+ export const FUNCTION_TYPES = Object.keys(MAP_EVENT_TO_FUNCTION_TYPE);
30
33
  export const PROJECT_SCOPED_FUNCTION_TYPES = new Set([
31
34
  SANITY_FUNCTION_DOCUMENT,
32
35
  SANITY_FUNCTION_MEDIA_LIBRARY_ASSET,
@@ -44,3 +47,4 @@ export const LABEL_MEDIA_LIBRARY_ASSET_CREATE = 'Media Library Asset Create';
44
47
  export const LABEL_MEDIA_LIBRARY_ASSET_UPDATE = 'Media Library Asset Update';
45
48
  export const LABEL_MEDIA_LIBRARY_ASSET_DELETE = 'Media Library Asset Delete';
46
49
  export const LABEL_SCHEDULED = 'Scheduled';
50
+ export const LABEL_SYNC_TAG_INVALIDATE = 'Sync Tag Invalidate';
@@ -9,10 +9,15 @@ export async function blueprintConfigCore(options) {
9
9
  const providedConfigFlag = [flagProjectId, flagStack, flagOrganizationId].some(Boolean);
10
10
  const { stackId: configStackId, scopeType: configScopeType, scopeId: configScopeId, blueprintConfig, fileInfo, } = blueprint;
11
11
  const blueprintFilePath = fileInfo.blueprintFilePath;
12
+ const hasConfigFile = !!blueprintConfig;
12
13
  if (!configStackId && !configScopeType && !configScopeId) {
13
- log(warn('Incomplete configuration.'));
14
+ if (hasConfigFile) {
15
+ log(warn('Incomplete configuration.'));
16
+ }
17
+ else {
18
+ log('No configuration file found.');
19
+ }
14
20
  if (!editConfig) {
15
- // blueprint.json exists but no config JSON
16
21
  log(`Run \`npx ${bin} blueprints doctor\` for diagnostics.`);
17
22
  return { success: true }; // not necessarily fatal
18
23
  }
@@ -61,7 +66,7 @@ export async function blueprintConfigCore(options) {
61
66
  // creates a new config file with the given properties
62
67
  try {
63
68
  const newConfig = writeConfigFile(blueprintFilePath, configUpdate);
64
- printConfig({ configLabel: 'Updated', log, config: newConfig });
69
+ printConfig({ configLabel: hasConfigFile ? 'Updated' : 'New', log, config: newConfig });
65
70
  return { success: true, json: { config: newConfig } };
66
71
  }
67
72
  catch {
@@ -98,7 +103,7 @@ export async function blueprintConfigCore(options) {
98
103
  projectId: updatedProjectId,
99
104
  stackId: updatedStackId,
100
105
  });
101
- printConfig({ configLabel: 'Updated', log, config: newConfig });
106
+ printConfig({ configLabel: hasConfigFile ? 'Updated' : 'New', log, config: newConfig });
102
107
  return { success: true, json: { config: newConfig } };
103
108
  }
104
109
  catch {
@@ -8,7 +8,7 @@ import { styleText } from '../../utils/style-text.js';
8
8
  export async function blueprintDestroyCore(options) {
9
9
  const { bin = 'sanity', log, token, blueprint, flags } = options;
10
10
  const { force = false, 'project-id': flagProjectId, 'organization-id': flagOrganizationId, stack: flagStack, 'no-wait': noWait = false, verbose: _verbose = false, } = flags;
11
- // 3-flag combo: just destroy it
11
+ // 3-flag combo: destroy without needing a local blueprint config
12
12
  if ((flagProjectId || flagOrganizationId) && flagStack && force) {
13
13
  let scopeType;
14
14
  let scopeId;
@@ -25,6 +25,7 @@ export async function blueprintDestroyCore(options) {
25
25
  }
26
26
  const auth = { token, scopeType, scopeId };
27
27
  const resolvedId = await resolveStackIdByNameOrId(flagStack, auth, log);
28
+ const isoNow = new Date().toISOString();
28
29
  const { ok, error, stack } = await destroyStack({
29
30
  stackId: resolvedId,
30
31
  auth,
@@ -32,8 +33,18 @@ export async function blueprintDestroyCore(options) {
32
33
  });
33
34
  if (!ok)
34
35
  return { success: false, error: error || 'Failed to destroy Stack deployment' };
35
- log(`Stack deployment "${stack.name}" ${niceId(stack.id)} destroyed`);
36
- return { success: true, json: { stackId: stack.id, stackName: stack.name } };
36
+ if (noWait) {
37
+ log(styleText(['bold', 'magenta'], 'Stack destruction started!'));
38
+ return { success: true, json: { stackId: stack.id, stackName: stack.name } };
39
+ }
40
+ return waitForDestruction({
41
+ stackId: stack.id,
42
+ stackName: stack.name,
43
+ auth,
44
+ log,
45
+ bin,
46
+ since: isoNow,
47
+ });
37
48
  }
38
49
  const { scopeType, scopeId, stackId } = blueprint;
39
50
  if (!scopeType || !scopeId)
@@ -103,62 +114,73 @@ export async function blueprintDestroyCore(options) {
103
114
  log(styleText(['bold', 'magenta'], 'Stack destruction started!'));
104
115
  return { success: true, json: { stackId: stack.id, stackName: stack.name } };
105
116
  }
106
- log(styleText('dim', 'Stack destruction progress:'));
107
- const logHints = createLogHintCollector(bin);
108
- let logStreamCleanup = null;
109
- try {
110
- let lastLogAt = Date.now();
111
- let idleMessageShown = false;
112
- logStreamCleanup = await setupLogStreaming({
113
- stackId: stack.id,
114
- after: isoNow,
115
- auth,
116
- log,
117
- onActivity: () => {
118
- lastLogAt = Date.now();
119
- },
120
- onLogEntry: (logEntry) => logHints.inspect(logEntry),
121
- });
122
- while (true) {
123
- const { ok, stack: currentStack } = await getStack({ stackId: stack.id, auth, logger: log });
124
- const operation = currentStack?.recentOperation;
125
- if (!ok || !operation || operation?.status === 'COMPLETED') {
126
- // Operation is also marked destroyed when stack is deleted;
127
- // it's possible that the operation is "gone" or available and "COMPLETED"
128
- if (logStreamCleanup)
129
- logStreamCleanup();
130
- log(styleText(['bold', 'magenta'], 'Stack destruction completed!'));
131
- return { success: true, json: { stackId: stack.id, stackName: stack.name } };
132
- }
133
- if (operation.status === 'FAILED') {
134
- if (logStreamCleanup)
135
- logStreamCleanup();
136
- return {
137
- success: false,
138
- error: 'Stack destruction failed',
139
- suggestions: [
140
- ...logHints.getSuggestions(),
141
- `Run \`npx ${bin} blueprints logs\` to review destruction logs.`,
142
- `Run \`npx ${bin} blueprints info\` to view current Stack status.`,
143
- ],
144
- };
145
- }
146
- if (!idleMessageShown && Date.now() - lastLogAt > 60_000) {
147
- log(`No new activity for 60 seconds. The destruction is still running on Sanity servers.`);
148
- log(`You can safely exit and check status later with \`npx ${bin} blueprints info\`.`);
149
- idleMessageShown = true;
150
- }
151
- await sleep(1500);
152
- }
153
- }
154
- catch (error) {
155
- if (logStreamCleanup)
156
- logStreamCleanup();
157
- throw error;
158
- }
117
+ return waitForDestruction({
118
+ stackId: stack.id,
119
+ stackName: stack.name,
120
+ auth,
121
+ log,
122
+ bin,
123
+ since: isoNow,
124
+ });
159
125
  }
160
126
  catch (error) {
161
127
  const errorMessage = error instanceof Error ? error.message : String(error);
162
128
  return { success: false, error: errorMessage };
163
129
  }
164
130
  }
131
+ async function waitForDestruction(options) {
132
+ const { stackId, stackName, auth, log, bin, since } = options;
133
+ log(styleText('dim', 'Stack destruction progress:'));
134
+ const logHints = createLogHintCollector(bin);
135
+ let logStreamCleanup = null;
136
+ try {
137
+ let lastLogAt = Date.now();
138
+ let idleMessageShown = false;
139
+ logStreamCleanup = await setupLogStreaming({
140
+ stackId,
141
+ after: since,
142
+ auth,
143
+ log,
144
+ onActivity: () => {
145
+ lastLogAt = Date.now();
146
+ },
147
+ onLogEntry: (logEntry) => logHints.inspect(logEntry),
148
+ });
149
+ while (true) {
150
+ const { ok, stack: currentStack } = await getStack({ stackId, auth, logger: log });
151
+ const operation = currentStack?.recentOperation;
152
+ if (!ok || !operation || operation?.status === 'COMPLETED') {
153
+ // Operation is also marked destroyed when stack is deleted;
154
+ // it's possible that the operation is "gone" or available and "COMPLETED"
155
+ if (logStreamCleanup)
156
+ logStreamCleanup();
157
+ log(styleText(['bold', 'magenta'], 'Stack destruction completed!'));
158
+ return { success: true, json: { stackId, stackName } };
159
+ }
160
+ if (operation.status === 'FAILED') {
161
+ if (logStreamCleanup)
162
+ logStreamCleanup();
163
+ return {
164
+ success: false,
165
+ error: 'Stack destruction failed',
166
+ suggestions: [
167
+ ...logHints.getSuggestions(),
168
+ `Run \`npx ${bin} blueprints logs\` to review destruction logs.`,
169
+ `Run \`npx ${bin} blueprints info\` to view current Stack status.`,
170
+ ],
171
+ };
172
+ }
173
+ if (!idleMessageShown && Date.now() - lastLogAt > 60_000) {
174
+ log(`No new activity for 60 seconds. The destruction is still running on Sanity servers.`);
175
+ log(`You can safely exit and check status later with \`npx ${bin} blueprints info\`.`);
176
+ idleMessageShown = true;
177
+ }
178
+ await sleep(1500);
179
+ }
180
+ }
181
+ catch (error) {
182
+ if (logStreamCleanup)
183
+ logStreamCleanup();
184
+ throw error;
185
+ }
186
+ }
@@ -2,7 +2,9 @@ import { readFileSync } from 'node:fs';
2
2
  import { arch, cwd, env, version as nodeVersion, platform } from 'node:process';
3
3
  import * as resolve from 'empathic/resolve';
4
4
  import ora from 'ora';
5
- import { readLocalBlueprint, } from '../../actions/blueprints/blueprint.js';
5
+ import { loadAndParseBlueprint, } from '../../actions/blueprints/blueprint.js';
6
+ import { readConfigFile } from '../../actions/blueprints/config.js';
7
+ import { resolveIds } from '../../actions/blueprints/resolve.js';
6
8
  import { getStack } from '../../actions/blueprints/stacks.js';
7
9
  import config, { RUNTIME_CLI_VERSION } from '../../config.js';
8
10
  import { check, filePathRelativeToCwd, niceId, renderSection, severe, unsure, } from '../../utils/display/presenters.js';
@@ -76,15 +78,27 @@ export async function blueprintDoctorCore(options) {
76
78
  // --- BLUEPRINT ---
77
79
  let localBlueprint;
78
80
  try {
79
- localBlueprint = await readLocalBlueprint(log, { resources: options.validateResources || false }, path);
80
- envRows.push(['Blueprint', filePathRelativeToCwd(localBlueprint.fileInfo.blueprintFilePath)]);
81
- if (localBlueprint.errors.length === 0) {
81
+ const blueprint = await loadAndParseBlueprint(path, {
82
+ validateResources: options.validateResources || false,
83
+ });
84
+ const blueprintConfig = readConfigFile(blueprint.fileInfo.blueprintFilePath);
85
+ const resolved = resolveIds({
86
+ module: blueprint.module,
87
+ config: blueprintConfig,
88
+ });
89
+ localBlueprint = {
90
+ ...blueprint,
91
+ blueprintConfig,
92
+ ...resolved,
93
+ };
94
+ envRows.push(['Blueprint', filePathRelativeToCwd(blueprint.fileInfo.blueprintFilePath)]);
95
+ if (blueprint.errors.length === 0) {
82
96
  diagnostics.blueprintValid = { status: true };
83
97
  }
84
98
  else {
85
99
  diagnostics.blueprintValid = {
86
100
  status: false,
87
- detail: `${localBlueprint.errors.length} error(s)`,
101
+ detail: `${blueprint.errors.length} error(s)`,
88
102
  };
89
103
  }
90
104
  }
@@ -17,7 +17,7 @@ const SCOPE_PROJECT = 'project';
17
17
  const SCOPE_ORGANIZATION = 'organization';
18
18
  export async function blueprintInitCore(options) {
19
19
  const { bin = 'sanity', log, token, knownProjectId, args, flags, validateResources } = options;
20
- const { dir: flagDir, example: flagExample, 'blueprint-type': flagBlueprintType, 'project-id': flagProjectId, 'organization-id': flagOrganizationId, 'stack-id': flagStackId, 'stack-name': flagStackName, verbose: v = false, } = flags;
20
+ const { dir: flagDir, example: flagExample, 'blueprint-type': flagBlueprintType, 'project-id': flagProjectId, 'organization-id': flagOrganizationId, 'stack-id': flagStackId, 'stack-name': flagStackName, verbose = false, } = flags;
21
21
  const { dir: argDir } = args;
22
22
  const userProvidedDirName = argDir || flagDir;
23
23
  const blueprintDir = userProvidedDirName || '.';
@@ -44,7 +44,7 @@ export async function blueprintInitCore(options) {
44
44
  flagOrganizationId,
45
45
  flagStackId,
46
46
  flagStackName,
47
- verbose: v,
47
+ verbose,
48
48
  });
49
49
  }
50
50
  // --example flag → scaffold from an example template
@@ -5,26 +5,29 @@ import { checkbox, confirm, input, select } from '@inquirer/prompts';
5
5
  import { highlight } from 'cardinal';
6
6
  import { createFunctionResource } from '../../actions/blueprints/resources.js';
7
7
  import { verifyExampleExists, writeExample } from '../../actions/sanity/examples.js';
8
- import { EVENT_DOCUMENT_CREATE, EVENT_DOCUMENT_DELETE, EVENT_DOCUMENT_UPDATE, EVENT_MEDIA_LIBRARY_ASSET_CREATE, EVENT_MEDIA_LIBRARY_ASSET_DELETE, EVENT_MEDIA_LIBRARY_ASSET_UPDATE,
8
+ import { EVENT_DOCUMENT_CREATE, EVENT_DOCUMENT_DELETE, EVENT_DOCUMENT_UPDATE, EVENT_MEDIA_LIBRARY_ASSET_CREATE, EVENT_MEDIA_LIBRARY_ASSET_DELETE, EVENT_MEDIA_LIBRARY_ASSET_UPDATE, EVENT_SYNC_TAG_INVALIDATE,
9
9
  // EVENT_SCHEDULED,
10
- FUNCTION_TYPES, LABEL_DOCUMENT_CREATE, LABEL_DOCUMENT_DELETE, LABEL_DOCUMENT_UPDATE, LABEL_MEDIA_LIBRARY_ASSET_CREATE, LABEL_MEDIA_LIBRARY_ASSET_DELETE, LABEL_MEDIA_LIBRARY_ASSET_UPDATE, LABEL_SCHEDULED, SANITY_FUNCTION_MEDIA_LIBRARY_ASSET, SANITY_FUNCTION_SCHEDULED, } from '../../constants.js';
10
+ FUNCTION_TYPES, LABEL_DOCUMENT_CREATE, LABEL_DOCUMENT_DELETE, LABEL_DOCUMENT_UPDATE, LABEL_MEDIA_LIBRARY_ASSET_CREATE, LABEL_MEDIA_LIBRARY_ASSET_DELETE, LABEL_MEDIA_LIBRARY_ASSET_UPDATE, LABEL_SCHEDULED, LABEL_SYNC_TAG_INVALIDATE, MAP_EVENT_TO_FUNCTION_TYPE, SANITY_FUNCTION_DOCUMENT, SANITY_FUNCTION_MEDIA_LIBRARY_ASSET, SANITY_FUNCTION_SCHEDULED, SANITY_FUNCTION_SYNC_TAG_INVALIDATE, } from '../../constants.js';
11
11
  import { check, indent, warn } from '../../utils/display/presenters.js';
12
12
  import { styleText } from '../../utils/style-text.js';
13
13
  import { validateFunctionName } from '../../utils/validate/resource.js';
14
14
  export const generateFunctionBlueprintResourceTemplate = (fnName, eventNames) => {
15
- const functionType = eventNames[0].substring(0, eventNames[0].lastIndexOf('-'));
15
+ const functionType = MAP_EVENT_TO_FUNCTION_TYPE[eventNames[0]];
16
16
  let definer = '';
17
17
  const eventOns = eventNames.map((e) => `'${e.substring(e.lastIndexOf('-') + 1)}'`);
18
18
  switch (functionType) {
19
- case 'document':
19
+ case SANITY_FUNCTION_DOCUMENT:
20
20
  definer = `defineDocumentFunction({name: '${fnName}', event: {on: [${eventOns.join(', ')}]}}), // ← add this line`;
21
21
  break;
22
- case 'media-library-asset':
22
+ case SANITY_FUNCTION_MEDIA_LIBRARY_ASSET:
23
23
  definer = `defineMediaLibraryAssetFunction({name: '${fnName}', event: {on: [${eventOns.join(', ')}], resource: {type: 'media-library', id: 'my-media-library-id'}}}), // ← add this line`;
24
24
  break;
25
- case 'scheduled':
25
+ case SANITY_FUNCTION_SCHEDULED:
26
26
  definer = `defineScheduledFunction({name: '${fnName}', event: {expression: '0 0 * * *'}}), // ← add this line`;
27
27
  break;
28
+ case SANITY_FUNCTION_SYNC_TAG_INVALIDATE:
29
+ definer = `defineSyncTagInvalidateFunction({name: '${fnName}'}), // ← add this line`;
30
+ break;
28
31
  }
29
32
  return `
30
33
  export default defineBlueprint({
@@ -154,7 +157,7 @@ export async function functionAddCore(options) {
154
157
  }
155
158
  const eventSources = new Set(fnTypes.map((t) => t.substring(0, t.lastIndexOf('-'))));
156
159
  if (eventSources.size > 1) {
157
- throw new Error('Invalid function type. Cannot mix document-*, media-library-asset-*, and/or scheduled-* types.');
160
+ throw new Error('Invalid function type. Cannot mix document-*, media-library-asset-*, sync-tag-* and/or scheduled-* types.');
158
161
  }
159
162
  let addHelpers;
160
163
  let installCommand;
@@ -238,6 +241,7 @@ async function promptForFunctionType() {
238
241
  { name: LABEL_MEDIA_LIBRARY_ASSET_CREATE, value: EVENT_MEDIA_LIBRARY_ASSET_CREATE },
239
242
  { name: LABEL_MEDIA_LIBRARY_ASSET_UPDATE, value: EVENT_MEDIA_LIBRARY_ASSET_UPDATE },
240
243
  { name: LABEL_MEDIA_LIBRARY_ASSET_DELETE, value: EVENT_MEDIA_LIBRARY_ASSET_DELETE },
244
+ { name: LABEL_SYNC_TAG_INVALIDATE, value: EVENT_SYNC_TAG_INVALIDATE },
241
245
  // {name: LABEL_SCHEDULED, value: EVENT_SCHEDULED},
242
246
  ],
243
247
  validate(choices) {
@@ -7,7 +7,7 @@ export async function functionDevCore(options) {
7
7
  ? { timeout }
8
8
  : undefined;
9
9
  try {
10
- await dev(host, Number(port), log, options.validateResources || false, executionOptions);
10
+ await dev(host, Number(port), options.validateResources || false, executionOptions);
11
11
  log(`Server is running on http://${host}:${port}\n`);
12
12
  return {
13
13
  success: true,
@@ -1,6 +1,5 @@
1
- import type { Logger } from '../utils/logger.js';
2
1
  import { type InvokeExecutionOptions } from '../utils/types.js';
3
- declare const app: (host: string, port: number, logger: Logger, validateResources: boolean, executionOptions?: Partial<InvokeExecutionOptions>) => void;
2
+ declare const app: (host: string, port: number, validateResources: boolean, executionOptions?: Partial<InvokeExecutionOptions>) => void;
4
3
  declare function parseDocumentUrl(url: string): {
5
4
  projectId: string;
6
5
  dataset: string;