@sanity/runtime-cli 13.4.1 → 14.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 (40) hide show
  1. package/README.md +43 -35
  2. package/dist/actions/blueprints/config.d.ts +3 -7
  3. package/dist/actions/blueprints/config.js +1 -1
  4. package/dist/actions/blueprints/logs-streaming.d.ts +1 -0
  5. package/dist/actions/blueprints/logs-streaming.js +1 -0
  6. package/dist/actions/blueprints/stacks.d.ts +1 -0
  7. package/dist/actions/blueprints/stacks.js +11 -0
  8. package/dist/baseCommands.d.ts +2 -0
  9. package/dist/baseCommands.js +6 -1
  10. package/dist/commands/blueprints/config.d.ts +1 -1
  11. package/dist/commands/blueprints/config.js +5 -5
  12. package/dist/commands/blueprints/deploy.d.ts +1 -0
  13. package/dist/commands/blueprints/deploy.js +5 -2
  14. package/dist/commands/blueprints/destroy.d.ts +1 -1
  15. package/dist/commands/blueprints/destroy.js +6 -6
  16. package/dist/commands/blueprints/doctor.js +5 -1
  17. package/dist/commands/blueprints/info.d.ts +1 -1
  18. package/dist/commands/blueprints/info.js +4 -4
  19. package/dist/commands/blueprints/init.js +1 -1
  20. package/dist/commands/blueprints/logs.d.ts +1 -0
  21. package/dist/commands/blueprints/logs.js +2 -1
  22. package/dist/commands/blueprints/plan.d.ts +3 -0
  23. package/dist/commands/blueprints/plan.js +4 -1
  24. package/dist/commands/functions/logs.d.ts +1 -0
  25. package/dist/commands/functions/logs.js +2 -1
  26. package/dist/cores/blueprints/config.d.ts +1 -1
  27. package/dist/cores/blueprints/config.js +27 -6
  28. package/dist/cores/blueprints/deploy.js +36 -3
  29. package/dist/cores/blueprints/destroy.d.ts +1 -1
  30. package/dist/cores/blueprints/destroy.js +30 -15
  31. package/dist/cores/blueprints/doctor.js +184 -90
  32. package/dist/cores/blueprints/info.d.ts +1 -3
  33. package/dist/cores/blueprints/info.js +5 -20
  34. package/dist/cores/blueprints/init.js +1 -1
  35. package/dist/cores/blueprints/plan.d.ts +1 -0
  36. package/dist/cores/blueprints/plan.js +20 -15
  37. package/dist/cores/index.d.ts +1 -0
  38. package/dist/cores/index.js +12 -7
  39. package/oclif.manifest.json +81 -24
  40. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- import { LocalBlueprintCommand } from '../../baseCommands.js';
1
+ import { LocalBlueprintCommand, stackFlag, unhide } from '../../baseCommands.js';
2
2
  import { blueprintPlanCore } from '../../cores/blueprints/plan.js';
3
3
  import { Logger } from '../../utils/logger.js';
4
4
  export default class PlanCommand extends LocalBlueprintCommand {
@@ -7,6 +7,9 @@ export default class PlanCommand extends LocalBlueprintCommand {
7
7
 
8
8
  Run 'blueprints plan' after making local changes to your Blueprint manifest to verify the expected diff. When ready, run 'blueprints deploy' to apply changes.`;
9
9
  static examples = ['<%= config.bin %> <%= command.id %>'];
10
+ static flags = {
11
+ stack: unhide(stackFlag),
12
+ };
10
13
  async run() {
11
14
  const { success, error } = await blueprintPlanCore({
12
15
  bin: this.config.bin,
@@ -7,6 +7,7 @@ export default class LogsCommand extends DeployedStackCommand<typeof LogsCommand
7
7
  };
8
8
  static examples: string[];
9
9
  static flags: {
10
+ stack: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
11
  limit: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
11
12
  json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
12
13
  utc: import("@oclif/core/interfaces").BooleanFlag<boolean>;
@@ -1,5 +1,5 @@
1
1
  import { Args, Flags } from '@oclif/core';
2
- import { DeployedStackCommand } from '../../baseCommands.js';
2
+ import { DeployedStackCommand, stackFlag, unhide } from '../../baseCommands.js';
3
3
  import { functionLogsCore } from '../../cores/functions/logs.js';
4
4
  import { Logger } from '../../utils/logger.js';
5
5
  export default class LogsCommand extends DeployedStackCommand {
@@ -17,6 +17,7 @@ Use --watch (-w) to stream logs in real-time. Use --delete to clear all logs for
17
17
  '<%= config.bin %> <%= command.id %> <name> --delete',
18
18
  ];
19
19
  static flags = {
20
+ stack: unhide(stackFlag),
20
21
  limit: Flags.integer({
21
22
  char: 'l',
22
23
  description: 'Total number of log entries to retrieve',
@@ -4,7 +4,7 @@ export interface BlueprintConfigOptions extends BlueprintConfig {
4
4
  edit?: boolean;
5
5
  'project-id'?: string;
6
6
  'organization-id'?: string;
7
- 'stack-id'?: string;
7
+ stack?: string;
8
8
  verbose?: boolean;
9
9
  };
10
10
  }
@@ -1,11 +1,12 @@
1
1
  import { patchConfigFile, writeConfigFile, } from '../../actions/blueprints/config.js';
2
+ import { resolveStackIdByNameOrId } from '../../actions/blueprints/stacks.js';
2
3
  import { filePathRelativeToCwd, labeledId, warn } from '../../utils/display/presenters.js';
3
4
  import { promptForProject, promptForStack } from '../../utils/display/prompt.js';
4
5
  import { styleText } from '../../utils/style-text.js';
5
6
  export async function blueprintConfigCore(options) {
6
7
  const { bin = 'sanity', blueprint, log, token, flags } = options;
7
- const { edit: editConfig = false, 'project-id': flagProjectId, 'organization-id': flagOrganizationId, 'stack-id': flagStackId, verbose: _v = false, } = flags;
8
- const providedConfigFlag = [flagProjectId, flagStackId, flagOrganizationId].some(Boolean);
8
+ const { edit: editConfig = false, 'project-id': flagProjectId, 'organization-id': flagOrganizationId, stack: flagStack, verbose: _v = false, } = flags;
9
+ const providedConfigFlag = [flagProjectId, flagStack, flagOrganizationId].some(Boolean);
9
10
  const { stackId: configStackId, scopeType: configScopeType, scopeId: configScopeId, blueprintConfig, fileInfo, } = blueprint;
10
11
  const blueprintFilePath = fileInfo.blueprintFilePath;
11
12
  if (!configStackId && !configScopeType && !configScopeId) {
@@ -34,8 +35,16 @@ export async function blueprintConfigCore(options) {
34
35
  const configUpdate = {};
35
36
  if (flagProjectId)
36
37
  configUpdate.projectId = flagProjectId;
37
- if (flagStackId)
38
- configUpdate.stackId = flagStackId;
38
+ if (flagStack) {
39
+ const scopeType = flagProjectId ? 'project' : configScopeType;
40
+ const scopeId = flagProjectId || configScopeId;
41
+ if (scopeType && scopeId) {
42
+ configUpdate.stackId = await resolveStackIdByNameOrId(flagStack, { token, scopeType, scopeId }, log);
43
+ }
44
+ else {
45
+ configUpdate.stackId = flagStack;
46
+ }
47
+ }
39
48
  if (flagOrganizationId)
40
49
  configUpdate.organizationId = flagOrganizationId;
41
50
  try {
@@ -44,7 +53,16 @@ export async function blueprintConfigCore(options) {
44
53
  return { success: true };
45
54
  }
46
55
  catch {
47
- return { success: false, error: 'Unable to update configuration.' };
56
+ // fallback to writeConfigFile if patchConfigFile fails
57
+ // creates a new config file with the given properties
58
+ try {
59
+ const newConfig = writeConfigFile(blueprintFilePath, configUpdate);
60
+ printConfig({ configLabel: 'Updated', log, config: newConfig });
61
+ return { success: true };
62
+ }
63
+ catch {
64
+ return { success: false, error: 'Unable to update configuration.' };
65
+ }
48
66
  }
49
67
  }
50
68
  // prompt for values interactively
@@ -60,7 +78,10 @@ export async function blueprintConfigCore(options) {
60
78
  }
61
79
  if (!updatedProjectId)
62
80
  return { success: false, error: 'Project ID is required.' };
63
- let updatedStackId = flagStackId;
81
+ let updatedStackId;
82
+ if (flagStack) {
83
+ updatedStackId = await resolveStackIdByNameOrId(flagStack, { token, scopeType: 'project', scopeId: updatedProjectId }, log);
84
+ }
64
85
  if (!updatedStackId) {
65
86
  const pickedStack = await promptForStack({ projectId: updatedProjectId, token, logger: log });
66
87
  updatedStackId = pickedStack.stackId;
@@ -1,10 +1,14 @@
1
- import { setTimeout } from 'node:timers/promises';
1
+ import { setTimeout as sleep } from 'node:timers/promises';
2
2
  import { stashAsset } from '../../actions/blueprints/assets.js';
3
3
  import { setupLogStreaming } from '../../actions/blueprints/logs-streaming.js';
4
4
  import { getStack, updateStack } from '../../actions/blueprints/stacks.js';
5
5
  import { niceId } from '../../utils/display/presenters.js';
6
6
  import { styleText } from '../../utils/style-text.js';
7
7
  import { isLocalFunctionCollection, isLocalFunctionResource } from '../../utils/types.js';
8
+ const DEFAULT_ASSET_TIMEOUT = 60;
9
+ const assetTimeoutS = Number(process.env.SANITY_ASSET_TIMEOUT) || DEFAULT_ASSET_TIMEOUT;
10
+ const assetTimeoutMs = assetTimeoutS * 1000;
11
+ const warnTimeoutMs = assetTimeoutMs / 2;
8
12
  export async function blueprintDeployCore(options) {
9
13
  const { bin = 'sanity', log, auth, stackId, scopeType, scopeId, deployedStack, blueprint, flags, } = options;
10
14
  const { verbose } = flags;
@@ -33,7 +37,26 @@ export async function blueprintDeployCore(options) {
33
37
  log('Processing function assets...');
34
38
  for (const resource of allFunctionResources) {
35
39
  const fnSpinner = log.ora({ text: `Processing ${resource.name}...`, prefixText: ' ' }).start();
36
- const result = await stashAsset({ resource, auth, logger: log });
40
+ const warnTimer = setTimeout(() => {
41
+ fnSpinner.text = `Still processing ${resource.name}, this can take a moment...`;
42
+ }, warnTimeoutMs);
43
+ let result;
44
+ try {
45
+ result = await Promise.race([
46
+ stashAsset({ resource, auth, logger: log }),
47
+ sleep(assetTimeoutMs).then(() => {
48
+ throw new Error(`Processing ${resource.name} timed out after ${assetTimeoutS}s`);
49
+ }),
50
+ ]);
51
+ }
52
+ catch (err) {
53
+ const msg = err instanceof Error ? err.message : String(err);
54
+ fnSpinner.fail(msg);
55
+ return { success: false, error: msg };
56
+ }
57
+ finally {
58
+ clearTimeout(warnTimer);
59
+ }
37
60
  if (result.success && result.assetId) {
38
61
  resource.src = result.assetId;
39
62
  if (isLocalFunctionCollection(resource)) {
@@ -102,12 +125,17 @@ export async function blueprintDeployCore(options) {
102
125
  log(styleText('dim', 'Stack deployment progress:'));
103
126
  let logStreamCleanup = null;
104
127
  try {
128
+ let lastLogAt = Date.now();
129
+ let idleMessageShown = false;
105
130
  logStreamCleanup = await setupLogStreaming({
106
131
  stackId: stack.id,
107
132
  after: isoNow,
108
133
  auth,
109
134
  log,
110
135
  verbose,
136
+ onActivity: () => {
137
+ lastLogAt = Date.now();
138
+ },
111
139
  });
112
140
  while (true) {
113
141
  const { ok, stack: currentStack } = await getStack({ stackId: stack.id, auth, logger: log });
@@ -133,7 +161,12 @@ export async function blueprintDeployCore(options) {
133
161
  log(styleText(['bold', 'green'], 'Stack deployment completed!'));
134
162
  return { success: true, data: { resources } };
135
163
  }
136
- await setTimeout(1500);
164
+ if (!idleMessageShown && Date.now() - lastLogAt > 60_000) {
165
+ log(`No new activity for 60 seconds. The deployment is still running on Sanity servers.`);
166
+ log(`You can safely exit and check status later with \`${bin} blueprints info\`.`);
167
+ idleMessageShown = true;
168
+ }
169
+ await sleep(1500);
137
170
  }
138
171
  }
139
172
  catch (error) {
@@ -4,7 +4,7 @@ export interface BlueprintDestroyOptions extends BlueprintConfig {
4
4
  force?: boolean;
5
5
  'project-id'?: string;
6
6
  'organization-id'?: string;
7
- 'stack-id'?: string;
7
+ stack?: string;
8
8
  'no-wait'?: boolean;
9
9
  verbose?: boolean;
10
10
  };
@@ -1,14 +1,14 @@
1
- import { setTimeout } from 'node:timers/promises';
1
+ import { setTimeout as sleep } from 'node:timers/promises';
2
2
  import { confirm } from '@inquirer/prompts';
3
3
  import { setupLogStreaming } from '../../actions/blueprints/logs-streaming.js';
4
- import { destroyStack, getStack } from '../../actions/blueprints/stacks.js';
4
+ import { destroyStack, getStack, resolveStackIdByNameOrId } from '../../actions/blueprints/stacks.js';
5
5
  import { niceId } from '../../utils/display/presenters.js';
6
6
  import { styleText } from '../../utils/style-text.js';
7
7
  export async function blueprintDestroyCore(options) {
8
- const { log, token, blueprint, flags } = options;
9
- const { force = false, 'project-id': flagProjectId, 'organization-id': flagOrganizationId, 'stack-id': flagStackId, 'no-wait': noWait = false, verbose: _verbose = false, } = flags;
8
+ const { bin = 'sanity', log, token, blueprint, flags } = options;
9
+ const { force = false, 'project-id': flagProjectId, 'organization-id': flagOrganizationId, stack: flagStack, 'no-wait': noWait = false, verbose: _verbose = false, } = flags;
10
10
  // 3-flag combo: just destroy it
11
- if ((flagProjectId || flagOrganizationId) && flagStackId && force) {
11
+ if ((flagProjectId || flagOrganizationId) && flagStack && force) {
12
12
  let scopeType;
13
13
  let scopeId;
14
14
  if (flagOrganizationId) {
@@ -22,9 +22,11 @@ export async function blueprintDestroyCore(options) {
22
22
  else {
23
23
  return { success: false, error: 'Unable to determine scope for Blueprint Destroy' };
24
24
  }
25
+ const auth = { token, scopeType, scopeId };
26
+ const resolvedId = await resolveStackIdByNameOrId(flagStack, auth, log);
25
27
  const { ok, error, stack } = await destroyStack({
26
- stackId: flagStackId,
27
- auth: { token, scopeType, scopeId },
28
+ stackId: resolvedId,
29
+ auth,
28
30
  logger: log,
29
31
  });
30
32
  if (!ok)
@@ -36,13 +38,16 @@ export async function blueprintDestroyCore(options) {
36
38
  if (!scopeType || !scopeId)
37
39
  return { success: false, error: 'Scope is required' };
38
40
  const auth = { token, scopeType, scopeId };
41
+ const resolvedStackId = flagStack
42
+ ? await resolveStackIdByNameOrId(flagStack, auth, log)
43
+ : undefined;
39
44
  let stack;
40
45
  try {
41
- if (flagStackId) {
42
- const flagStack = await getStack({ stackId: flagStackId, auth, logger: log });
43
- if (!flagStack.ok)
44
- return { success: false, error: flagStack.error || 'Failed to get Stack' };
45
- stack = flagStack.stack;
46
+ if (resolvedStackId) {
47
+ const overrideStack = await getStack({ stackId: resolvedStackId, auth, logger: log });
48
+ if (!overrideStack.ok)
49
+ return { success: false, error: overrideStack.error || 'Failed to get Stack' };
50
+ stack = overrideStack.stack;
46
51
  }
47
52
  else if (stackId) {
48
53
  const blueprintStack = await getStack({ stackId, auth, logger: log });
@@ -70,10 +75,10 @@ export async function blueprintDestroyCore(options) {
70
75
  let i = 5;
71
76
  while (i >= 0) {
72
77
  destroySpinner.text = `Destroying Stack deployment in ${styleText('bold', (i--).toString())} seconds...`;
73
- await setTimeout(1000);
78
+ await sleep(1000);
74
79
  }
75
80
  destroySpinner.text = 'Destroying Stack deployment 💥';
76
- await setTimeout(500);
81
+ await sleep(500);
77
82
  }
78
83
  else {
79
84
  destroySpinner.start();
@@ -92,11 +97,16 @@ export async function blueprintDestroyCore(options) {
92
97
  log(styleText('dim', 'Stack destruction progress:'));
93
98
  let logStreamCleanup = null;
94
99
  try {
100
+ let lastLogAt = Date.now();
101
+ let idleMessageShown = false;
95
102
  logStreamCleanup = await setupLogStreaming({
96
103
  stackId: stack.id,
97
104
  after: isoNow,
98
105
  auth,
99
106
  log,
107
+ onActivity: () => {
108
+ lastLogAt = Date.now();
109
+ },
100
110
  });
101
111
  while (true) {
102
112
  const { ok, stack: currentStack } = await getStack({ stackId: stack.id, auth, logger: log });
@@ -114,7 +124,12 @@ export async function blueprintDestroyCore(options) {
114
124
  logStreamCleanup();
115
125
  return { success: false, error: 'Stack destruction failed' };
116
126
  }
117
- await setTimeout(1500);
127
+ if (!idleMessageShown && Date.now() - lastLogAt > 60_000) {
128
+ log(`No new activity for 60 seconds. The destruction is still running on Sanity servers.`);
129
+ log(`You can safely exit and check status later with \`${bin} blueprints info\`.`);
130
+ idleMessageShown = true;
131
+ }
132
+ await sleep(1500);
118
133
  }
119
134
  }
120
135
  catch (error) {