@sanity/runtime-cli 17.9.0-blueprinit.g14aaebd → 17.10.0-next.g5e0bba6

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 (73) hide show
  1. package/README.md +19 -19
  2. package/dist/actions/blueprints/blueprint.js +1 -1
  3. package/dist/actions/blueprints/pre-deploy.js +7 -6
  4. package/dist/actions/blueprints/wait-for-operation.js +2 -1
  5. package/dist/cores/blueprints/config.js +18 -8
  6. package/dist/cores/blueprints/deploy.js +9 -8
  7. package/dist/cores/blueprints/destroy.js +7 -6
  8. package/dist/cores/blueprints/doctor.js +15 -2
  9. package/dist/cores/blueprints/init.d.ts +2 -0
  10. package/dist/cores/blueprints/init.js +56 -20
  11. package/dist/cores/blueprints/plan.js +11 -6
  12. package/dist/cores/blueprints/promote.js +3 -2
  13. package/dist/cores/functions/add.js +11 -7
  14. package/dist/cores/functions/logs.js +50 -6
  15. package/dist/cores/index.d.ts +5 -0
  16. package/dist/utils/blueprints/hints.js +8 -3
  17. package/dist/utils/display/prompt.d.ts +15 -2
  18. package/dist/utils/display/prompt.js +70 -8
  19. package/dist/utils/functions/child-runtime/durable.js +19 -2
  20. package/dist/utils/package-manager.d.ts +20 -0
  21. package/dist/utils/package-manager.js +63 -0
  22. package/dist/utils/types.d.ts +10 -1
  23. package/oclif.manifest.json +1 -152
  24. package/package.json +2 -2
  25. package/dist/actions/blueprinit/manifest.d.ts +0 -46
  26. package/dist/actions/blueprinit/manifest.js +0 -121
  27. package/dist/actions/blueprinit/quote.d.ts +0 -6
  28. package/dist/actions/blueprinit/quote.js +0 -8
  29. package/dist/actions/blueprinit/studio.d.ts +0 -6
  30. package/dist/actions/blueprinit/studio.js +0 -43
  31. package/dist/actions/blueprinit/templates.d.ts +0 -73
  32. package/dist/actions/blueprinit/templates.js +0 -138
  33. package/dist/actions/blueprinit/versions.d.ts +0 -8
  34. package/dist/actions/blueprinit/versions.js +0 -11
  35. package/dist/actions/blueprinit/workspace.d.ts +0 -27
  36. package/dist/actions/blueprinit/workspace.js +0 -145
  37. package/dist/actions/sanity/create-project.d.ts +0 -19
  38. package/dist/actions/sanity/create-project.js +0 -32
  39. package/dist/actions/sanity/datasets.d.ts +0 -15
  40. package/dist/actions/sanity/datasets.js +0 -26
  41. package/dist/commands/init.d.ts +0 -21
  42. package/dist/commands/init.js +0 -82
  43. package/dist/cores/blueprinit/adopt.d.ts +0 -6
  44. package/dist/cores/blueprinit/adopt.js +0 -137
  45. package/dist/cores/blueprinit/columns.d.ts +0 -26
  46. package/dist/cores/blueprinit/columns.js +0 -27
  47. package/dist/cores/blueprinit/finish.d.ts +0 -15
  48. package/dist/cores/blueprinit/finish.js +0 -72
  49. package/dist/cores/blueprinit/fresh.d.ts +0 -3
  50. package/dist/cores/blueprinit/fresh.js +0 -219
  51. package/dist/cores/blueprinit/naming.d.ts +0 -27
  52. package/dist/cores/blueprinit/naming.js +0 -43
  53. package/dist/cores/blueprinit/preview.d.ts +0 -34
  54. package/dist/cores/blueprinit/preview.js +0 -110
  55. package/dist/cores/blueprinit/project-choice.d.ts +0 -28
  56. package/dist/cores/blueprinit/project-choice.js +0 -82
  57. package/dist/cores/blueprinit/prompts.d.ts +0 -13
  58. package/dist/cores/blueprinit/prompts.js +0 -42
  59. package/dist/cores/blueprinit/results.d.ts +0 -8
  60. package/dist/cores/blueprinit/results.js +0 -4
  61. package/dist/cores/blueprinit/selection.d.ts +0 -15
  62. package/dist/cores/blueprinit/selection.js +0 -90
  63. package/dist/cores/blueprinit/stack-name.d.ts +0 -39
  64. package/dist/cores/blueprinit/stack-name.js +0 -56
  65. package/dist/cores/blueprinit/target.d.ts +0 -33
  66. package/dist/cores/blueprinit/target.js +0 -80
  67. package/dist/cores/blueprinit/types.d.ts +0 -20
  68. package/dist/cores/blueprinit/types.js +0 -1
  69. package/dist/cores/blueprinit.d.ts +0 -7
  70. package/dist/cores/blueprinit.js +0 -64
  71. package/dist/utils/display/intro.d.ts +0 -6
  72. package/dist/utils/display/intro.js +0 -63
  73. package/skills/init.md +0 -199
@@ -3,6 +3,7 @@ import { planStack, resolveStackIdByNameOrId } from '../../actions/blueprints/st
3
3
  import { extractDurableConfig } from '../../utils/blueprints/extract-durable-config.js';
4
4
  import { createHintCollector } from '../../utils/blueprints/hints.js';
5
5
  import { formatDeploymentPlan, formatResourceTree, hasActionableChanges, } from '../../utils/display/blueprints-formatting.js';
6
+ import { runCommand } from '../../utils/package-manager.js';
6
7
  import { styleText } from '../../utils/style-text.js';
7
8
  import { getWarnings } from '../../utils/warnings.js';
8
9
  export async function blueprintPlanCore(options) {
@@ -18,12 +19,14 @@ export async function blueprintPlanCore(options) {
18
19
  ? {
19
20
  success: false,
20
21
  error: 'Missing authentication token',
21
- suggestions: [`Run \`npx ${bin} login\` to authenticate.`],
22
+ suggestions: [`Run \`${runCommand(bin)} login\` to authenticate.`],
22
23
  }
23
24
  : {
24
25
  success: false,
25
26
  error: 'Missing Blueprint configuration',
26
- suggestions: [`Run \`npx ${bin} blueprints doctor --fix\` to fix your configuration.`],
27
+ suggestions: [
28
+ `Run \`${runCommand(bin)} blueprints doctor --fix\` to fix your configuration.`,
29
+ ],
27
30
  };
28
31
  }
29
32
  const stackId = flags.stack
@@ -34,7 +37,9 @@ export async function blueprintPlanCore(options) {
34
37
  return {
35
38
  success: false,
36
39
  error: 'Missing Stack deployment configuration',
37
- suggestions: [`Run \`npx ${bin} blueprints doctor --fix\` to fix your configuration.`],
40
+ suggestions: [
41
+ `Run \`${runCommand(bin)} blueprints doctor --fix\` to fix your configuration.`,
42
+ ],
38
43
  };
39
44
  }
40
45
  const auth = { token, scopeType, scopeId };
@@ -69,7 +74,7 @@ export async function blueprintPlanCore(options) {
69
74
  error: 'Deployment plan has problems',
70
75
  suggestions: [
71
76
  ...hints.getSuggestions(),
72
- `Fix the issues above, then run \`npx ${bin} blueprints deploy\`.`,
77
+ `Fix the issues above, then run \`${runCommand(bin)} blueprints plan\` again.`,
73
78
  ],
74
79
  };
75
80
  }
@@ -77,10 +82,10 @@ export async function blueprintPlanCore(options) {
77
82
  log('');
78
83
  log(formatDeploymentPlan(planResponse.deploymentPlan));
79
84
  if (hasActionableChanges(planResponse.deploymentPlan)) {
80
- log(`\n Run "${styleText(['bold', 'magenta'], `npx ${bin} blueprints deploy`)}" to apply these Stack changes`);
85
+ log(`\n Run "${styleText(['bold', 'magenta'], `${runCommand(bin)} blueprints deploy`)}" to apply these Stack changes`);
81
86
  }
82
87
  else {
83
- log(`\n ${styleText('dim', `No significant changes to deploy. Run \`npx ${bin} blueprints deploy\` to apply.`)}`);
88
+ log(`\n ${styleText('dim', `No significant changes to deploy. Run \`${runCommand(bin)} blueprints deploy\` to apply.`)}`);
84
89
  }
85
90
  return {
86
91
  success: true,
@@ -2,6 +2,7 @@ import { confirm } from '@inquirer/prompts';
2
2
  import { patchConfigFile } from '../../actions/blueprints/config.js';
3
3
  import { promoteStack } from '../../actions/blueprints/stacks.js';
4
4
  import { niceId } from '../../utils/display/presenters.js';
5
+ import { runCommand } from '../../utils/package-manager.js';
5
6
  export async function blueprintPromoteCore(options) {
6
7
  const { bin = 'sanity', log, stackId, auth, flags, deployedStack, blueprint } = options;
7
8
  const newStackName = flags['new-stack-name'];
@@ -47,7 +48,7 @@ export async function blueprintPromoteCore(options) {
47
48
  if (error && /access|permission|forbidden/i.test(error)) {
48
49
  suggestions.push('Verify you have organization admin access.');
49
50
  }
50
- suggestions.push(`Run \`npx ${bin} blueprints info\` to check the current Stack scope.`);
51
+ suggestions.push(`Run \`${runCommand(bin)} blueprints info\` to check the current Stack scope.`);
51
52
  return { success: false, error: error || 'Failed to promote Stack', suggestions };
52
53
  }
53
54
  spinner.stop().clear();
@@ -66,7 +67,7 @@ export async function blueprintPromoteCore(options) {
66
67
  success: false,
67
68
  error: 'Stack promoted, but could not update Blueprint config (.sanity/blueprint.config.json).',
68
69
  suggestions: [
69
- `Run \`npx ${bin} blueprints config --edit\` to update your Blueprint config manually.`,
70
+ `Run \`${runCommand(bin)} blueprints config --edit\` to update your Blueprint config manually.`,
70
71
  ],
71
72
  };
72
73
  }
@@ -8,7 +8,9 @@ import { verifyExampleExists, writeExample } from '../../actions/sanity/examples
8
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_PUBSUB, EVENT_SCHEDULED, EVENT_SYNC_TAG_INVALIDATE, 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_PUBSUB, LABEL_SCHEDULED, LABEL_SYNC_TAG_INVALIDATE, MAP_EVENT_TO_FUNCTION_TYPE, SANITY_FUNCTION_DOCUMENT, SANITY_FUNCTION_MEDIA_LIBRARY_ASSET, SANITY_FUNCTION_PUBSUB, SANITY_FUNCTION_SCHEDULED, SANITY_FUNCTION_SYNC_TAG_INVALIDATE, } from '../../constants.js';
9
9
  import { check, indent, warn } from '../../utils/display/presenters.js';
10
10
  import { validateFunctionName } from '../../utils/functions/validate-name.js';
11
+ import { detectPackageManager } from '../../utils/package-manager.js';
11
12
  import { styleText } from '../../utils/style-text.js';
13
+ import { INSTALLER_OPTIONS } from '../../utils/types.js';
12
14
  import { getWarnings } from '../../utils/warnings.js';
13
15
  export const generateFunctionBlueprintResourceTemplate = (fnName, eventNames) => {
14
16
  const functionType = MAP_EVENT_TO_FUNCTION_TYPE[eventNames[0]];
@@ -44,7 +46,8 @@ export async function functionAddCore(options) {
44
46
  const root = cwd();
45
47
  const { log, blueprint, flags } = options;
46
48
  const { blueprintFilePath } = blueprint.fileInfo;
47
- const warnings = getWarnings({ dir: dirname(blueprintFilePath) });
49
+ const blueprintFileDir = dirname(blueprintFilePath);
50
+ const warnings = getWarnings({ dir: blueprintFileDir });
48
51
  const { example: flagExample, name: flagResourceName, type: flagType, javascript: flagJs, helpers: flagHelpers, install: flagI, installer: flagInstaller, } = flags;
49
52
  let { language: flagLang } = flags;
50
53
  flagLang = flagJs ? 'js' : flagLang;
@@ -183,7 +186,7 @@ export async function functionAddCore(options) {
183
186
  }
184
187
  else {
185
188
  addHelpers = flagHelpers || (await promptForAddHelpers());
186
- installCommand = addHelpers ? await promptForInstallCommand() : null;
189
+ installCommand = addHelpers ? await promptForInstallCommand(blueprintFileDir) : null;
187
190
  }
188
191
  if (installCommand)
189
192
  log(`${styleText('magenta', 'Installing')} with ${installCommand}...`);
@@ -286,16 +289,17 @@ async function promptForAddHelpers() {
286
289
  });
287
290
  return addHelpers;
288
291
  }
289
- async function promptForInstallCommand() {
292
+ async function promptForInstallCommand(dir) {
293
+ // hydrate only supports these three; a detected bun falls back to npm
294
+ const detected = detectPackageManager(dir);
295
+ const preferred = INSTALLER_OPTIONS.find((option) => option === detected) ?? 'npm';
290
296
  const command = await select({
291
297
  message: 'How to install the @sanity/functions helpers:',
292
298
  choices: [
293
- { name: 'npm', value: 'npm' },
294
- { name: 'pnpm', value: 'pnpm' },
295
- { name: 'yarn', value: 'yarn' },
299
+ ...INSTALLER_OPTIONS.map((option) => ({ name: option, value: option })),
296
300
  { name: 'Skip install', value: null },
297
301
  ],
298
- default: 'npm',
302
+ default: preferred,
299
303
  });
300
304
  return command;
301
305
  }
@@ -58,8 +58,7 @@ async function streamLogs({ name, externalId, auth, log, }) {
58
58
  alreadyOpened = true;
59
59
  };
60
60
  const renderLog = (logEntry) => {
61
- const { time, level, message } = logEntry;
62
- log(formatLog(time, level, message, true));
61
+ log(formatLog(logEntry, true));
63
62
  };
64
63
  streamLogsAction(externalId, auth, renderLog, onOpen, (error) => log.error(`${styleText('red', 'Error:')} ${error}`), log);
65
64
  // Return a special key for streaming mode
@@ -92,27 +91,72 @@ async function getLogs({ name, externalId, auth, limit, json, utc, log, }) {
92
91
  const logLength = filteredLogs.length;
93
92
  log(`Found ${styleText('bold', logLength.toString())} log ${logLength === 1 ? 'entry' : 'entries'} for function ${styleText('yellow', name)}`);
94
93
  log('\n');
95
- for (const { time, level, message } of filteredLogs) {
96
- log(formatLog(time, level, message, utc));
94
+ for (const entry of filteredLogs) {
95
+ log(formatLog(entry, utc));
97
96
  }
98
97
  }
99
98
  else {
100
- return { success: true, json: { logs: filteredLogs } };
99
+ const cleaned = filteredLogs.map(({ time, level, message, operationName, attempt, requestId, executionArn, }) => {
100
+ const isDurable = executionArn !== undefined;
101
+ const isStep = operationName !== undefined || attempt !== undefined;
102
+ const durableContext = isDurable ? (isStep ? 'step' : 'handler') : undefined;
103
+ return {
104
+ time,
105
+ level,
106
+ message,
107
+ requestId,
108
+ ...(durableContext ? { durableContext } : {}),
109
+ ...(operationName !== undefined ? { step: operationName } : {}),
110
+ ...(attempt !== undefined ? { attempt } : {}),
111
+ };
112
+ });
113
+ return { success: true, json: { logs: cleaned } };
101
114
  }
102
115
  return { success: true };
103
116
  }
104
- function formatLog(time, level, message, utc) {
117
+ /**
118
+ * Formats a log entry for display in the console.
119
+ * @param entry
120
+ * @param utc
121
+ */
122
+ function formatLog(entry, utc) {
123
+ const { time, level, message, operationName, attempt, executionArn } = entry;
105
124
  const date = new Date(time);
106
125
  const [dateString, timeString] = utc
107
126
  ? date.toISOString().slice(0, 19).split('T')
108
127
  : [date.toLocaleDateString(), date.toLocaleTimeString()];
128
+ const durableAnnotation = durableLogAnnotation({ operationName, attempt, executionArn });
109
129
  return [
110
130
  styleText('bold', dateString),
111
131
  styleText(['bold', 'blue'], timeString),
112
132
  logLevel(level.toUpperCase()),
133
+ ...(durableAnnotation ? [durableAnnotation] : []),
113
134
  message,
114
135
  ].join(' ');
115
136
  }
137
+ /**
138
+ * Log annotation for durable functions.
139
+ * @param operationName
140
+ * @param attempt
141
+ * @param executionArn
142
+ */
143
+ function durableLogAnnotation({ operationName, attempt, executionArn, }) {
144
+ /**
145
+ * executionArn is included on durable function logs, but not on regular function logs.
146
+ * If executionArn is defined, we can assume the log is from a durable function.
147
+ * If executionArn is defined and operationName and attempt are undefined, the logger is in the handler context
148
+ */
149
+ const annotationParts = [
150
+ executionArn !== undefined && operationName === undefined && attempt === undefined
151
+ ? 'handler'
152
+ : undefined,
153
+ ...(operationName ? [operationName] : []),
154
+ attempt !== undefined ? `attempt:${attempt}` : undefined,
155
+ ].filter((part) => part !== undefined);
156
+ return annotationParts.length > 0
157
+ ? styleText('grey', `[${annotationParts.join('|')}]`)
158
+ : undefined;
159
+ }
116
160
  function logLevel(level) {
117
161
  switch (level) {
118
162
  case 'ERROR':
@@ -39,6 +39,11 @@ export type CoreResult = {
39
39
  ref?: string;
40
40
  /** A machine-readable error code. */
41
41
  code?: string;
42
+ /**
43
+ * The user cancelled a prompt. A calling core reads it to keep its own message.
44
+ * Not printed: `code` would surface as `Code:` in the command output.
45
+ */
46
+ cancelled?: true;
42
47
  /** Overrides the process exit code (defaults to oclif's 2). */
43
48
  exitCode?: number;
44
49
  streaming?: never;
@@ -1,3 +1,5 @@
1
+ import { runCommand } from '../package-manager.js';
2
+ const promoteToOrganization = (bin) => `Run \`${runCommand(bin)} blueprints promote\` to move from project to organization scope.`;
1
3
  /**
2
4
  * Patterns matched against ERROR/FATAL log entries during streaming operations
3
5
  * like deploy and destroy. Each entry is a [regex, suggestionFactory] tuple.
@@ -13,9 +15,12 @@ export const HINT_PATTERNS = [
13
15
  [
14
16
  // Function requires organization scope
15
17
  /must be attached to an organization scoped stack/,
16
- (_match, bin) => [
17
- `Run \`npx ${bin} blueprints promote\` to move from project to organization scope.`,
18
- ],
18
+ (_match, bin) => [promoteToOrganization(bin)],
19
+ ],
20
+ [
21
+ // Resource needs an organization the project-scoped Stack cannot supply
22
+ /[Cc]annot resolve organization scope/,
23
+ (_match, bin) => [promoteToOrganization(bin)],
19
24
  ],
20
25
  ];
21
26
  /**
@@ -3,6 +3,16 @@ import type { Logger } from '../logger.js';
3
3
  import type { ScopeType } from '../types.js';
4
4
  export declare const BACK: unique symbol;
5
5
  export type Back = typeof BACK;
6
+ /** Thrown when the user picks "Cancel" in a prompt. Cores translate it to a clean result. */
7
+ export declare class CancelledError extends Error {
8
+ constructor(message?: string);
9
+ }
10
+ /**
11
+ * Ask where a new Blueprint should go.
12
+ * Returns the chosen directory, or null to use the current one.
13
+ * @throws CancelledError when the user cancels.
14
+ */
15
+ export declare function promptForInitLocation(currentDir: string): Promise<string | null>;
6
16
  export declare function promptForBlueprintType(): Promise<string>;
7
17
  /**
8
18
  * Fetches organizations and prompts the user to pick one (if multiple).
@@ -48,12 +58,14 @@ export declare function promptForProjectInOrg({ projects, knownProjectId, logger
48
58
  * Prompt the user for a Stack after scope has been determined.
49
59
  * Can create a new Stack or select an existing one.
50
60
  */
51
- export declare function promptForStack({ scopeType, scopeId, token, logger, allowBack, }: {
61
+ export declare function promptForStack({ scopeType, scopeId, token, logger, allowBack, suggestedName, }: {
52
62
  scopeType: ScopeType;
53
63
  scopeId: string;
54
64
  token: string;
55
65
  logger: Logger;
56
66
  allowBack?: boolean;
67
+ /** Prefilled name for a new Stack, usually the project directory name. */
68
+ suggestedName?: string;
57
69
  }): Promise<{
58
70
  stackId: string;
59
71
  name: string;
@@ -62,11 +74,12 @@ export declare function promptForStack({ scopeType, scopeId, token, logger, allo
62
74
  * Full wizard: Organization -> Scope -> Project (if project-scoped) -> Stack.
63
75
  * Supports back-navigation between steps.
64
76
  */
65
- export declare function runScopeAndStackWizard({ token, knownOrganizationId, knownProjectId, logger, }: {
77
+ export declare function runScopeAndStackWizard({ token, knownOrganizationId, knownProjectId, logger, suggestedStackName, }: {
66
78
  token: string;
67
79
  knownOrganizationId?: string;
68
80
  knownProjectId?: string;
69
81
  logger: Logger;
82
+ suggestedStackName?: string;
70
83
  }): Promise<{
71
84
  scopeType: ScopeType;
72
85
  scopeId: string;
@@ -5,6 +5,13 @@ import { styleText } from '../style-text.js';
5
5
  import { niceId } from './presenters.js';
6
6
  // --- Wizard infrastructure ---
7
7
  export const BACK = Symbol('back');
8
+ /** Thrown when the user picks "Cancel" in a prompt. Cores translate it to a clean result. */
9
+ export class CancelledError extends Error {
10
+ constructor(message = 'Cancelled by user.') {
11
+ super(message);
12
+ this.name = 'CancelledError';
13
+ }
14
+ }
8
15
  /**
9
16
  * Runs a sequence of prompt steps with back-navigation support.
10
17
  * Each step can return BACK to go to the previous non-skipped step.
@@ -57,13 +64,53 @@ function withBackChoice(choices, allowBack) {
57
64
  ];
58
65
  }
59
66
  // --- Standalone prompts ---
67
+ /**
68
+ * Ask where a new Blueprint should go.
69
+ * Returns the chosen directory, or null to use the current one.
70
+ * @throws CancelledError when the user cancels.
71
+ */
72
+ export async function promptForInitLocation(currentDir) {
73
+ const HERE = 'here';
74
+ const ELSEWHERE = 'elsewhere';
75
+ const CANCEL = 'cancel';
76
+ const choice = await select({
77
+ message: 'Where should the Blueprint go?',
78
+ choices: [
79
+ { name: 'Here', value: HERE, description: currentDir },
80
+ { name: 'In a new folder', value: ELSEWHERE, description: 'Name a folder to create' },
81
+ { name: 'Cancel', value: CANCEL, description: 'Stop here and change nothing' },
82
+ ],
83
+ default: HERE,
84
+ });
85
+ if (choice === CANCEL)
86
+ throw new CancelledError();
87
+ if (choice === HERE)
88
+ return null;
89
+ const folder = await input({
90
+ message: 'Folder name:',
91
+ validate: (val) => val.trim().length > 0 || 'A folder name is required',
92
+ });
93
+ return folder.trim();
94
+ }
60
95
  export async function promptForBlueprintType() {
61
96
  return await select({
62
97
  message: 'Choose a Blueprint file type:',
63
98
  choices: [
64
- { name: 'TypeScript', value: 'ts' },
65
- { name: 'JavaScript', value: 'js' },
66
- { name: 'JSON', value: 'json' },
99
+ {
100
+ name: 'TypeScript',
101
+ value: 'ts',
102
+ description: 'Editor completion and type checks. Adds @sanity/blueprints.',
103
+ },
104
+ {
105
+ name: 'JavaScript',
106
+ value: 'js',
107
+ description: 'The same define* helpers, without type checks. Adds @sanity/blueprints.',
108
+ },
109
+ {
110
+ name: 'JSON',
111
+ value: 'json',
112
+ description: 'No dependency and no install step. Write each resource by hand.',
113
+ },
67
114
  ],
68
115
  default: 'ts',
69
116
  });
@@ -203,7 +250,7 @@ export async function promptForProjectInOrg({ projects, knownProjectId, logger,
203
250
  * Prompt the user for a Stack after scope has been determined.
204
251
  * Can create a new Stack or select an existing one.
205
252
  */
206
- export async function promptForStack({ scopeType, scopeId, token, logger, allowBack = false, }) {
253
+ export async function promptForStack({ scopeType, scopeId, token, logger, allowBack = false, suggestedName, }) {
207
254
  if (!scopeId) {
208
255
  throw new Error(`Cannot list Stacks: no ${scopeType} ID provided. ` +
209
256
  'Provide --project-id or --organization-id, or select a scope in the wizard.');
@@ -247,16 +294,30 @@ export async function promptForStack({ scopeType, scopeId, token, logger, allowB
247
294
  pickedStackId = answer;
248
295
  }
249
296
  else if (allowBack) {
297
+ const CANCEL = 'cancel';
250
298
  const proceed = await select({
251
- message: 'No existing Stacks. Create a new one?',
252
- choices: withBackChoice([{ name: 'Yes, create a new Stack', value: 'yes' }], allowBack),
299
+ message: 'This scope has no Stacks yet:',
300
+ choices: withBackChoice([
301
+ {
302
+ name: 'Create the first Stack',
303
+ value: NEW_STACK_ID,
304
+ description: 'A Blueprint needs a Stack before it can deploy',
305
+ },
306
+ { name: 'Cancel', value: CANCEL, description: 'Stop here and change nothing' },
307
+ ], allowBack),
253
308
  });
254
309
  if (proceed === BACK)
255
310
  return BACK;
311
+ if (proceed === CANCEL)
312
+ throw new CancelledError();
256
313
  }
257
314
  if (pickedStackId === NEW_STACK_ID) {
315
+ // only suggest a free name; the API rejects a duplicate within a scope
316
+ const isTaken = stacks.some((s) => s.name === suggestedName);
317
+ logger(styleText('dim', 'The name is a label. Sanity assigns the Stack ID.'));
258
318
  const stackName = await input({
259
- message: 'Enter a name for your new Stack:',
319
+ message: 'Name your new Stack:',
320
+ default: isTaken ? undefined : suggestedName,
260
321
  validate: (val) => val.length > 0 || 'Stack name is required',
261
322
  });
262
323
  const stack = await createEmptyStack({
@@ -277,7 +338,7 @@ export async function promptForStack({ scopeType, scopeId, token, logger, allowB
277
338
  * Full wizard: Organization -> Scope -> Project (if project-scoped) -> Stack.
278
339
  * Supports back-navigation between steps.
279
340
  */
280
- export async function runScopeAndStackWizard({ token, knownOrganizationId, knownProjectId, logger, }) {
341
+ export async function runScopeAndStackWizard({ token, knownOrganizationId, knownProjectId, logger, suggestedStackName, }) {
281
342
  const steps = [
282
343
  {
283
344
  key: 'organization',
@@ -336,6 +397,7 @@ export async function runScopeAndStackWizard({ token, knownOrganizationId, known
336
397
  token,
337
398
  logger,
338
399
  allowBack: true,
400
+ suggestedName: suggestedStackName,
339
401
  });
340
402
  },
341
403
  },
@@ -84,12 +84,29 @@ export async function runDurable(handler, {context, event}, io) {
84
84
  }
85
85
 
86
86
  const step = {
87
- run: ({name, handler: fn}) => track(name || 'step-run', 'run', () => fn({logger, attempt: 1})),
87
+ run: ({name, handler: fn}) => track(name || '', 'run', () => fn({logger, attempt: 1})),
88
88
  wait: ({name, duration}) =>
89
- track(name, 'wait', async () => {
89
+ track(name || '', 'wait', async () => {
90
90
  await io.wait(durationToMs(duration))
91
91
  return undefined
92
92
  }),
93
+ waitForCondition: ({name, initial, poller, next}) =>
94
+ track(name || '', 'wait-for-condition', async () => {
95
+ let state = initial
96
+ let attempt = 1
97
+ let polling = true
98
+ while (polling) {
99
+ state = await poller(state, {logger, attempt})
100
+ const decision = next(state, {attempt})
101
+ if (decision.shouldRetry) {
102
+ await io.wait(durationToMs(decision.delay))
103
+ attempt += 1
104
+ } else {
105
+ polling = false
106
+ }
107
+ }
108
+ return state
109
+ }),
93
110
  }
94
111
 
95
112
  return handler({context, event, step, logger})
@@ -0,0 +1,20 @@
1
+ /** Package managers this CLI can name in the commands it prints. */
2
+ export type PackageManager = 'npm' | 'pnpm' | 'yarn' | 'bun';
3
+ /**
4
+ * Detect the package manager for a project.
5
+ *
6
+ * `npm_config_user_agent` is first then lockfile detection.
7
+ */
8
+ export declare function detectPackageManager(dir?: string): PackageManager | undefined;
9
+ /**
10
+ * The command that runs this CLI, matched to the detected package manager.
11
+ * Falls back to `npx`
12
+ *
13
+ * Yarn is weird: detected from a lockfile alone also falls bac
14
+ * lockfile name does not differentiate Yarn 1 vs Yarn 2+
15
+ */
16
+ export declare function runCommand(bin: string, dir?: string): string;
17
+ /** The command that installs dependencies, matched to the detected package manager. */
18
+ export declare function installCommand(dir?: string): string;
19
+ /** Clear the lockfile lookup cache. Tests use this after they move fixtures. */
20
+ export declare function resetPackageManagerCache(): void;
@@ -0,0 +1,63 @@
1
+ import { basename } from 'node:path';
2
+ import { getBinCommand, getRunningPackageManager } from '@sanity/cli-core/package-manager';
3
+ import * as find from 'empathic/find';
4
+ /**
5
+ * Lockfile names in priority order
6
+ */
7
+ const LOCKFILES = {
8
+ 'pnpm-lock.yaml': 'pnpm',
9
+ 'yarn.lock': 'yarn',
10
+ 'bun.lock': 'bun',
11
+ 'bun.lockb': 'bun',
12
+ 'package-lock.json': 'npm',
13
+ };
14
+ const LOCKFILE_NAMES = Object.keys(LOCKFILES);
15
+ /** Minimal user agents, so the lockfile path reuses the same command mapping. */
16
+ const SYNTHETIC_USER_AGENTS = {
17
+ npm: 'npm/0',
18
+ pnpm: 'pnpm/0',
19
+ yarn: 'yarn/0',
20
+ bun: 'bun/0',
21
+ };
22
+ const lockfileCache = new Map();
23
+ /** Find the package manager a lockfile at or above `dir` implies. */
24
+ function detectFromLockfile(dir) {
25
+ if (lockfileCache.has(dir))
26
+ return lockfileCache.get(dir);
27
+ const found = find.any(LOCKFILE_NAMES, { cwd: dir });
28
+ const manager = found ? LOCKFILES[basename(found)] : undefined;
29
+ lockfileCache.set(dir, manager);
30
+ return manager;
31
+ }
32
+ /**
33
+ * Detect the package manager for a project.
34
+ *
35
+ * `npm_config_user_agent` is first then lockfile detection.
36
+ */
37
+ export function detectPackageManager(dir = process.cwd()) {
38
+ return getRunningPackageManager() ?? detectFromLockfile(dir);
39
+ }
40
+ /**
41
+ * The command that runs this CLI, matched to the detected package manager.
42
+ * Falls back to `npx`
43
+ *
44
+ * Yarn is weird: detected from a lockfile alone also falls bac
45
+ * lockfile name does not differentiate Yarn 1 vs Yarn 2+
46
+ */
47
+ export function runCommand(bin, dir) {
48
+ const running = getRunningPackageManager();
49
+ if (running)
50
+ return getBinCommand({ bin });
51
+ const fromLockfile = detectFromLockfile(dir ?? process.cwd());
52
+ if (!fromLockfile || fromLockfile === 'yarn')
53
+ return `npx ${bin}`;
54
+ return getBinCommand({ bin, userAgent: SYNTHETIC_USER_AGENTS[fromLockfile] });
55
+ }
56
+ /** The command that installs dependencies, matched to the detected package manager. */
57
+ export function installCommand(dir) {
58
+ return `${detectPackageManager(dir) ?? 'npm'} install`;
59
+ }
60
+ /** Clear the lockfile lookup cache. Tests use this after they move fixtures. */
61
+ export function resetPackageManagerCache() {
62
+ lockfileCache.clear();
63
+ }
@@ -262,8 +262,17 @@ export interface FunctionLog {
262
262
  requestId: string;
263
263
  level: string;
264
264
  message: string;
265
+ operationName?: string;
266
+ attempt?: number;
267
+ operationId?: string;
268
+ executionArn?: string;
265
269
  }
266
270
  /** @internal */
271
+ export type FunctionLogJson = Omit<FunctionLog, 'executionArn' | 'operationId' | 'operationName'> & {
272
+ durableContext?: 'handler' | 'step';
273
+ step?: string;
274
+ };
275
+ /** @internal */
267
276
  export declare enum BlueprintParserErrorType {
268
277
  InvalidProperty = "invalid_property",
269
278
  InvalidVersion = "invalid_version",
@@ -304,7 +313,7 @@ export type Warning = {
304
313
  suggestions: string[];
305
314
  };
306
315
  /** @internal */
307
- export type DurableOperationSteps = 'run' | 'wait';
316
+ export type DurableOperationSteps = 'run' | 'wait' | 'waitForCondition';
308
317
  /** @internal */
309
318
  export type DurableStepPhase = 'start' | 'complete' | 'error';
310
319
  /**