anhedral 0.10.0 → 0.11.0

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 (120) hide show
  1. package/README.md +78 -39
  2. package/assets/{anhedral-cli-init-technical.svg → anhedral-stack-technical.svg} +6 -6
  3. package/assets/{anhedral-cli-init.svg → anhedral-stack.svg} +7 -7
  4. package/assets/security/audit-deps.mjs +6 -1
  5. package/dist/bin.js +271 -154
  6. package/dist/bounded-file.d.ts +1 -0
  7. package/dist/bounded-file.js +1 -0
  8. package/dist/capabilities.d.ts +4 -4
  9. package/dist/capabilities.js +1 -1
  10. package/dist/capability-contract.d.ts +47 -0
  11. package/dist/capability-contract.js +136 -0
  12. package/dist/cli-arguments.d.ts +16 -0
  13. package/dist/cli-arguments.js +115 -0
  14. package/dist/cli-output.d.ts +8 -0
  15. package/dist/cli-output.js +211 -0
  16. package/dist/cloudflare.d.ts +7 -2
  17. package/dist/cloudflare.js +166 -36
  18. package/dist/command-schema.d.ts +21 -0
  19. package/dist/command-schema.js +374 -0
  20. package/dist/delivery.d.ts +2 -2
  21. package/dist/delivery.js +21 -4
  22. package/dist/dependencies.d.ts +32 -0
  23. package/dist/dependencies.js +47 -1
  24. package/dist/index.d.ts +23 -1
  25. package/dist/index.js +15 -1
  26. package/dist/inspector-model.d.ts +115 -0
  27. package/dist/inspector-model.js +1 -0
  28. package/dist/lifecycle-model.d.ts +15 -0
  29. package/dist/lifecycle-model.js +20 -0
  30. package/dist/lifecycle.d.ts +12 -0
  31. package/dist/lifecycle.js +92 -0
  32. package/dist/mobile-delivery.d.ts +100 -0
  33. package/dist/mobile-delivery.js +319 -0
  34. package/dist/platforms/desktop.js +4 -5
  35. package/dist/platforms/extension.js +9 -73
  36. package/dist/platforms/mobile.js +14 -52
  37. package/dist/project-inspector.d.ts +4 -0
  38. package/dist/project-inspector.js +646 -0
  39. package/dist/project-state.d.ts +17 -0
  40. package/dist/project-state.js +239 -11
  41. package/dist/provider-auth.d.ts +41 -0
  42. package/dist/provider-auth.js +210 -0
  43. package/dist/providers/adapters.d.ts +17 -0
  44. package/dist/providers/adapters.js +269 -0
  45. package/dist/providers/api.d.ts +28 -0
  46. package/dist/providers/api.js +252 -0
  47. package/dist/providers/config.d.ts +15 -0
  48. package/dist/providers/config.js +184 -0
  49. package/dist/providers/definitions.d.ts +318 -0
  50. package/dist/providers/definitions.js +127 -0
  51. package/dist/providers/provision.d.ts +24 -0
  52. package/dist/providers/provision.js +76 -0
  53. package/dist/providers/receipts.d.ts +20 -0
  54. package/dist/providers/receipts.js +66 -0
  55. package/dist/providers/recovery-verification.d.ts +7 -0
  56. package/dist/providers/recovery-verification.js +112 -0
  57. package/dist/providers/recovery.d.ts +30 -0
  58. package/dist/providers/recovery.js +34 -0
  59. package/dist/providers/runtime.d.ts +10 -0
  60. package/dist/providers/runtime.js +231 -0
  61. package/dist/providers/schema.d.ts +28 -0
  62. package/dist/providers/schema.js +49 -0
  63. package/dist/providers/state.d.ts +56 -0
  64. package/dist/providers/state.js +329 -0
  65. package/dist/provision.d.ts +1 -1
  66. package/dist/provision.js +61 -3
  67. package/dist/readiness.js +11 -3
  68. package/dist/registry/add.d.ts +6 -0
  69. package/dist/registry/add.js +35 -4
  70. package/dist/registry/commands.d.ts +3 -2
  71. package/dist/registry/commands.js +53 -14
  72. package/dist/registry/options.js +3 -2
  73. package/dist/registry/preview.d.ts +11 -0
  74. package/dist/registry/preview.js +91 -0
  75. package/dist/registry/receipt.js +1 -0
  76. package/dist/registry/resolve.js +19 -13
  77. package/dist/registry/types.d.ts +1 -0
  78. package/dist/registry/usage.d.ts +10 -0
  79. package/dist/registry/usage.js +22 -37
  80. package/dist/render.d.ts +2 -0
  81. package/dist/render.js +10 -0
  82. package/dist/scaffold/api.js +20 -3
  83. package/dist/scaffold/auth.js +68 -28
  84. package/dist/scaffold/bootstrap.d.ts +2 -0
  85. package/dist/scaffold/bootstrap.js +9 -0
  86. package/dist/scaffold/database.js +12 -6
  87. package/dist/scaffold/documentation.js +11 -8
  88. package/dist/scaffold/environment.d.ts +2 -0
  89. package/dist/scaffold/environment.js +18 -1
  90. package/dist/scaffold/files.d.ts +1 -0
  91. package/dist/scaffold/files.js +15 -4
  92. package/dist/scaffold/services.js +37 -13
  93. package/dist/scaffold/stack-documentation.d.ts +3 -0
  94. package/dist/scaffold/stack-documentation.js +52 -0
  95. package/dist/scaffold/styling.d.ts +3 -0
  96. package/dist/scaffold/styling.js +84 -0
  97. package/dist/scaffold/web.js +31 -11
  98. package/dist/scaffold/workspace.js +49 -22
  99. package/dist/ship.d.ts +320 -0
  100. package/dist/ship.js +211 -0
  101. package/dist/stack-registry.d.ts +2 -2
  102. package/dist/stack-registry.js +44 -11
  103. package/dist/standard.d.ts +2 -1
  104. package/dist/standard.js +13 -12
  105. package/dist/vercel.d.ts +3 -3
  106. package/dist/vercel.js +27 -1
  107. package/dist/version.d.ts +1 -1
  108. package/dist/version.js +1 -1
  109. package/dist/vinext.js +9 -18
  110. package/dist/workspace-graph.js +3 -2
  111. package/docs/application-stack-standard.md +51 -80
  112. package/docs/cli-design.md +67 -0
  113. package/docs/delivery-workflow.md +29 -15
  114. package/docs/plugin-ui.md +61 -0
  115. package/docs/provider-configuration.md +117 -0
  116. package/package.json +16 -12
  117. package/dist/managed-hosting.d.ts +0 -49
  118. package/dist/managed-hosting.js +0 -373
  119. package/docs/managed-hosting-architecture.md +0 -87
  120. package/docs/sponsorships.md +0 -25
package/dist/bin.js CHANGED
@@ -1,47 +1,159 @@
1
1
  #!/usr/bin/env node
2
+ import { shipCloudflareApplication, shipSelectedVercelApplication, shipSelectedStack, } from './ship.js';
3
+ import { integrationStatus, recoverIntegrationResource } from './providers/recovery.js';
4
+ import { INTEGRATION_PROVIDERS } from './providers/config.js';
5
+ import { recoverDeliveryLock } from './project-state.js';
6
+ import { readLifecycleOperation } from './lifecycle.js';
7
+ import { buildMobileApplication } from './mobile-delivery.js';
8
+ import { authenticateProviders } from './provider-auth.js';
2
9
  import { runAdd, runRegistry } from './registry/commands.js';
3
- import { registerManagedApplication, stageManagedVersion, saveManagedVersion, deployManagedVersion, managedApplicationStatus, } from './managed-hosting.js';
4
10
  import { setupRepository } from './repository.js';
5
11
  import path from 'node:path';
6
12
  import { existsSync, readFileSync } from 'node:fs';
7
- import { registerVercelApplication, connectVercelApplication, planVercelDelivery, shipVercelApplication, vercelApplicationStatus, verifyVercelApplication, rollbackVercelApplication, adoptVercelApplication, recoverVercelApplication, writeVercelOverview, } from './vercel.js';
8
- import { registerApplication, connectApplication, planDelivery, shipApplication, applicationStatus, verifyApplication, rollbackApplication, recoverApplication, } from './delivery.js';
13
+ import { registerVercelApplication, connectVercelApplication, vercelApplicationStatus, verifyVercelApplication, rollbackVercelApplication, adoptVercelApplication, recoverVercelApplication, writeVercelOverview, } from './vercel.js';
14
+ import { registerApplication, connectApplication, applicationStatus, verifyApplication, rollbackApplication, recoverApplication, } from './delivery.js';
9
15
  import { planProvisioning, provisionApplication } from './provision.js';
10
16
  import { writeManagementOverview } from './management.js';
11
17
  import { inspectProviderReadiness } from './provider-readiness.js';
12
18
  import { inspectProject } from './readiness.js';
19
+ import { inspectApplication } from './project-inspector.js';
13
20
  import { argv } from 'node:process';
14
21
  import { STANDARD_USAGE, parseStandardOptions, scaffoldStandardProject } from './standard.js';
15
22
  import { GENERATOR_VERSION } from './version.js';
16
23
  import { PostCommitError } from './transaction.js';
17
- const [command, ...args] = argv.slice(2);
18
- const json = args.includes('--json');
24
+ import { CliArgumentError, globalArguments, inlineArguments, validateCommand, } from './cli-arguments.js';
25
+ import { printResult } from './cli-output.js';
26
+ import { commandHelp } from './registry/usage.js';
27
+ let [command, ...args] = argv.slice(2);
28
+ let json = argv.includes('--json');
29
+ let output = { json };
30
+ function argumentError(message) {
31
+ return new CliArgumentError(message, `Run anhedral ${command && commandHelp(command) ? command + ' ' : ''}--help.`);
32
+ }
19
33
  async function main() {
20
- if ([undefined, '--help', '-h'].includes(command)) {
21
- console.log(json ? JSON.stringify({ usage: STANDARD_USAGE }) : STANDARD_USAGE);
34
+ const global = globalArguments(argv.slice(2));
35
+ [command, ...args] = global.args;
36
+ json = global.json;
37
+ if (json)
38
+ args.push('--json');
39
+ args = inlineArguments(command, args);
40
+ output = { json, quiet: global.quiet };
41
+ if ([undefined, '--help', '-h', 'help'].includes(command) ||
42
+ args.includes('--help') ||
43
+ args.includes('-h')) {
44
+ const target = command === 'help'
45
+ ? args.filter((arg) => arg !== '--json')[0]
46
+ : ['--help', '-h'].includes(command)
47
+ ? undefined
48
+ : command;
49
+ const help = target ? commandHelp(target) : STANDARD_USAGE;
50
+ if (!help)
51
+ throw new CliArgumentError('Unknown help topic.', 'Run anhedral --help.');
52
+ console.log(json ? JSON.stringify({ usage: help }) : help);
22
53
  return;
23
54
  }
55
+ validateCommand(command, args);
24
56
  if (['--version', '-v'].includes(command)) {
25
57
  console.log(json ? JSON.stringify({ version: GENERATOR_VERSION }) : GENERATOR_VERSION);
26
58
  return;
27
59
  }
28
- if (command === 'add') {
29
- await runAdd(args);
60
+ if (global.cwd) {
61
+ try {
62
+ process.chdir(global.cwd);
63
+ }
64
+ catch {
65
+ throw new CliArgumentError('Working directory is unavailable.', 'Choose an existing directory with --cwd <directory>.');
66
+ }
67
+ }
68
+ if (global.quiet || json)
69
+ process.env.ANHEDRAL_QUIET = '1';
70
+ if (global.verbose && !json && !global.quiet)
71
+ process.env.ANHEDRAL_VERBOSE = '1';
72
+ if (command === 'inspect') {
73
+ const directories = args.filter((arg) => arg !== '--json');
74
+ if (directories.length > 1 || directories.some((arg) => arg.startsWith('-')))
75
+ throw argumentError('Specify at most one project directory.');
76
+ printResult(command, inspectApplication(path.resolve(directories[0] ?? process.cwd())), output);
30
77
  return;
31
78
  }
32
- if (command === 'registry') {
33
- await runRegistry(args, json);
79
+ if (command === 'integrations') {
80
+ const [action, ...input] = args.filter((arg) => arg !== '--json');
81
+ const directories = [];
82
+ const options = {};
83
+ for (let index = 0; index < input.length; index++) {
84
+ const flag = input[index];
85
+ if (['--provider', '--operation', '--resource'].includes(flag))
86
+ options[flag] = input[++index];
87
+ else if (flag.startsWith('-'))
88
+ throw argumentError('Unknown integration option.');
89
+ else
90
+ directories.push(flag);
91
+ }
92
+ if (directories.length > 1)
93
+ throw argumentError('Specify one application directory.');
94
+ const root = path.resolve(directories[0] ?? process.cwd());
95
+ if (action === 'status') {
96
+ if (Object.keys(options).length)
97
+ throw argumentError('Integration status accepts no recovery options.');
98
+ printResult(command, integrationStatus(root), output);
99
+ }
100
+ else if (action === 'recover') {
101
+ if (!INTEGRATION_PROVIDERS.includes(options['--provider']) ||
102
+ !options['--operation'] ||
103
+ !options['--resource'])
104
+ throw argumentError('Recovery requires --provider, --operation and --resource.');
105
+ printResult(command, await recoverIntegrationResource(root, {
106
+ provider: options['--provider'],
107
+ operation: options['--operation'],
108
+ resourceId: options['--resource'],
109
+ }), output);
110
+ }
111
+ else
112
+ throw argumentError('Use integrations status or integrations recover.');
34
113
  return;
35
114
  }
36
- if (command === 'cloud') {
37
- await runCloud(args);
115
+ if (command === 'auth') {
116
+ let provider;
117
+ const directories = [];
118
+ for (let index = 0; index < args.length; index++) {
119
+ const flag = args[index];
120
+ if (['--json', '--status', '--non-interactive'].includes(flag))
121
+ continue;
122
+ if (flag === '--provider') {
123
+ const value = args[++index];
124
+ if (provider || !value || value.startsWith('-'))
125
+ throw argumentError('Specify one auth provider.');
126
+ provider = value;
127
+ }
128
+ else if (flag.startsWith('-'))
129
+ throw argumentError('Unknown auth option.');
130
+ else
131
+ directories.push(flag);
132
+ }
133
+ if (directories.length > 1)
134
+ throw argumentError('Specify one application directory.');
135
+ const result = await authenticateProviders(path.resolve(directories[0] ?? process.cwd()), {
136
+ provider,
137
+ status: args.includes('--status'),
138
+ interactive: !json &&
139
+ !output.quiet &&
140
+ !args.includes('--non-interactive') &&
141
+ Boolean(process.stdin.isTTY),
142
+ });
143
+ printResult('auth', result, output);
144
+ if (!result.authenticated)
145
+ process.exitCode = 1;
146
+ return;
147
+ }
148
+ if (command === 'add') {
149
+ await runAdd(args, output);
150
+ return;
151
+ }
152
+ if (command === 'registry') {
153
+ await runRegistry(args, json, output);
38
154
  return;
39
155
  }
40
156
  if (command === 'repository') {
41
- if (args.includes('--help') || args.includes('-h')) {
42
- console.log(STANDARD_USAGE);
43
- return;
44
- }
45
157
  let owner;
46
158
  let name;
47
159
  let visibility = 'private';
@@ -58,36 +170,36 @@ async function main() {
58
170
  if (flag === '--owner' || flag === '--name' || flag === '--visibility') {
59
171
  const value = args[++index];
60
172
  if (!value || value.startsWith('-'))
61
- throw new Error('Missing repository option value.');
173
+ throw argumentError('Missing repository option value.');
62
174
  if (flag === '--owner') {
63
175
  if (owner)
64
- throw new Error('Specify one GitHub owner.');
176
+ throw argumentError('Specify one GitHub owner.');
65
177
  owner = value;
66
178
  }
67
179
  else if (flag === '--name') {
68
180
  if (name)
69
- throw new Error('Specify one repository name.');
181
+ throw argumentError('Specify one repository name.');
70
182
  name = value;
71
183
  }
72
184
  else {
73
185
  if (!['private', 'public'].includes(value))
74
- throw new Error('Visibility must be private or public.');
186
+ throw argumentError('Visibility must be private or public.');
75
187
  visibility = value;
76
188
  }
77
189
  continue;
78
190
  }
79
191
  if (flag.startsWith('-'))
80
- throw new Error('Unknown repository option.');
192
+ throw argumentError('Unknown repository option.');
81
193
  directories.push(flag);
82
194
  }
83
195
  if (directories.length > 1)
84
- throw new Error('Specify one application directory.');
85
- console.log(JSON.stringify(await setupRepository(path.resolve(directories[0] ?? process.cwd()), {
196
+ throw argumentError('Specify one application directory.');
197
+ printResult('repository', await setupRepository(path.resolve(directories[0] ?? process.cwd()), {
86
198
  owner,
87
199
  name,
88
200
  visibility,
89
201
  create,
90
- }), null, 2));
202
+ }), output);
91
203
  return;
92
204
  }
93
205
  if ([
@@ -110,86 +222,13 @@ async function main() {
110
222
  return;
111
223
  }
112
224
  if (command !== 'new' && command !== 'init') {
113
- throw new Error(`Unknown command: ${command}. Use new, init, add, registry, doctor, repository, cloud, register, connect, provision, ship, status, manage, verify, rollback, adopt, recover, --help, or --version.`);
114
- }
115
- if (args.some((arg) => ['--help', '-h'].includes(arg))) {
116
- console.log(json ? JSON.stringify({ usage: STANDARD_USAGE }) : STANDARD_USAGE);
117
- return;
225
+ throw argumentError(`Unknown command: ${command}. Use init, auth, ship, add, registry, inspect, doctor, repository, register, connect, provision, status, manage, verify, rollback, adopt, recover, new, --help, or --version.`);
118
226
  }
119
227
  const options = readOptions(command, args);
120
228
  if (options)
121
- await scaffoldStandardProject(options);
122
- }
123
- async function runCloud(args) {
124
- const [action, ...flags] = args;
125
- if (!action || flags.includes('--help') || action === '--help') {
126
- console.log('anhedral cloud register|stage|save|deploy|status [directory] [--artifact directory] [--version stage-id] [--audience private|public] [--json]');
127
- return;
128
- }
129
- let artifact;
130
- let version;
131
- let audience = 'private';
132
- const directories = [];
133
- for (let index = 0; index < flags.length; index++) {
134
- const flag = flags[index];
135
- if (flag === '--json')
136
- continue;
137
- if ((flag === '--artifact' && action === 'stage') ||
138
- (flag === '--version' && ['save', 'deploy'].includes(action)) ||
139
- (flag === '--audience' && action === 'register')) {
140
- const value = flags[++index];
141
- if (!value || value.startsWith('-'))
142
- throw new Error('Missing cloud option value.');
143
- if (flag === '--artifact') {
144
- if (artifact)
145
- throw new Error('Specify one artifact.');
146
- artifact = value;
147
- }
148
- else if (flag === '--version') {
149
- if (version)
150
- throw new Error('Specify one version.');
151
- version = value;
152
- }
153
- else {
154
- if (value !== 'private' && value !== 'public')
155
- throw new Error('Audience must be private or public.');
156
- audience = value;
157
- }
158
- continue;
159
- }
160
- if (flag.startsWith('-'))
161
- throw new Error('Unknown cloud option.');
162
- directories.push(flag);
163
- }
164
- if (directories.length > 1)
165
- throw new Error('Specify one application directory.');
166
- const root = path.resolve(directories[0] ?? process.cwd());
167
- let result;
168
- if (action === 'register')
169
- result = registerManagedApplication(root, audience);
170
- else if (action === 'stage') {
171
- if (!artifact)
172
- throw new Error('Stage requires --artifact <complete-build-directory>.');
173
- result = await stageManagedVersion(root, path.resolve(artifact));
174
- }
175
- else if (action === 'save' || action === 'deploy') {
176
- if (!version)
177
- throw new Error('Save/deploy requires --version <stage-id>.');
178
- result = await (action === 'save'
179
- ? saveManagedVersion(root, version)
180
- : deployManagedVersion(root, version));
181
- }
182
- else if (action === 'status')
183
- result = await managedApplicationStatus(root);
184
- else
185
- throw new Error('Unknown cloud action.');
186
- console.log(JSON.stringify(result, null, 2));
229
+ await scaffoldStandardProject(options, undefined, output);
187
230
  }
188
231
  async function runDelivery(command, args) {
189
- if (args.includes('--help') || args.includes('-h')) {
190
- console.log(STANDARD_USAGE);
191
- return;
192
- }
193
232
  const positional = [];
194
233
  let account;
195
234
  let audience;
@@ -200,27 +239,47 @@ async function runDelivery(command, args) {
200
239
  let use;
201
240
  let create = false;
202
241
  let git = false;
242
+ let platform;
243
+ let profile;
203
244
  for (let index = 0; index < args.length; index++) {
204
245
  const arg = args[index];
246
+ if (command === 'ship' && (arg === '--platform' || arg === '--profile')) {
247
+ const value = args[++index];
248
+ if (!value || value.startsWith('-'))
249
+ throw argumentError('Missing mobile build option.');
250
+ if (arg === '--platform') {
251
+ if (platform || !['ios', 'android'].includes(value))
252
+ throw argumentError('Select one mobile platform.');
253
+ platform = value;
254
+ }
255
+ else {
256
+ if (profile)
257
+ throw argumentError('Select one build profile.');
258
+ profile = value;
259
+ }
260
+ continue;
261
+ }
262
+ if (command === 'recover' && arg === '--lock')
263
+ continue;
205
264
  if (arg === '--provider') {
206
265
  const value = args[++index];
207
266
  if (!['vercel', 'cloudflare'].includes(value) || provider)
208
- throw new Error('Specify one provider: vercel or cloudflare.');
267
+ throw argumentError('Specify one provider: vercel or cloudflare.');
209
268
  provider = value;
210
269
  continue;
211
270
  }
212
271
  if (command === 'connect' && arg === '--scope') {
213
272
  if (scope)
214
- throw new Error('Specify one Vercel scope.');
273
+ throw argumentError('Specify one Vercel scope.');
215
274
  scope = args[++index];
216
275
  if (!scope || scope.startsWith('-'))
217
- throw new Error('Missing Vercel scope.');
276
+ throw argumentError('Missing Vercel scope.');
218
277
  continue;
219
278
  }
220
279
  if (command === 'connect' && arg === '--use') {
221
280
  const value = args[++index];
222
281
  if (!['personal', 'commercial'].includes(value) || use)
223
- throw new Error('Specify --use personal or commercial.');
282
+ throw argumentError('Specify --use personal or commercial.');
224
283
  use = value;
225
284
  continue;
226
285
  }
@@ -235,7 +294,7 @@ async function runDelivery(command, args) {
235
294
  if (command === 'rollback' && arg === '--release') {
236
295
  releaseId = args[++index];
237
296
  if (!releaseId || releaseId.startsWith('-'))
238
- throw new Error('Missing release ID.');
297
+ throw argumentError('Missing release ID.');
239
298
  continue;
240
299
  }
241
300
  if (command === 'rollback' && arg === '--data-compatible')
@@ -248,10 +307,10 @@ async function runDelivery(command, args) {
248
307
  continue;
249
308
  if (command === 'connect' && arg === '--account') {
250
309
  if (account)
251
- throw new Error('Specify one account.');
310
+ throw argumentError('Specify one account.');
252
311
  account = args[++index];
253
312
  if (!account || account.startsWith('-'))
254
- throw new Error('Missing account ID.');
313
+ throw argumentError('Missing account ID.');
255
314
  continue;
256
315
  }
257
316
  if (command === 'connect' &&
@@ -264,18 +323,40 @@ async function runDelivery(command, args) {
264
323
  if (command === 'connect' && arg === '--allow-email') {
265
324
  const email = args[++index];
266
325
  if (!email || email.startsWith('-'))
267
- throw new Error('Missing allowed email address.');
326
+ throw argumentError('Missing allowed email address.');
268
327
  emails.push(email);
269
328
  continue;
270
329
  }
271
330
  if (arg.startsWith('-'))
272
- throw new Error(`Unknown ${command} option: ${arg}`);
331
+ throw argumentError(`Unknown ${command} option: ${arg}`);
273
332
  positional.push(arg);
274
333
  }
275
334
  if (positional.length > 1)
276
- throw new Error('Specify at most one application directory.');
335
+ throw argumentError('Specify at most one application directory.');
277
336
  const root = path.resolve(positional[0] ?? process.cwd());
278
337
  let result;
338
+ if (command === 'recover' && args.includes('--lock')) {
339
+ if (provider || args.includes('--refresh'))
340
+ throw argumentError('Local lock recovery cannot be combined with provider recovery or --refresh.');
341
+ printResult(command, recoverDeliveryLock(root), output);
342
+ return;
343
+ }
344
+ if (platform || profile) {
345
+ if (!platform || !profile || provider)
346
+ throw argumentError('Mobile shipping requires --platform and --profile, without --provider.');
347
+ result = await buildMobileApplication(root, {
348
+ platform,
349
+ profile,
350
+ dryRun: args.includes('--dry-run'),
351
+ });
352
+ printResult(command, result, output);
353
+ if (result &&
354
+ typeof result === 'object' &&
355
+ 'state' in result &&
356
+ ['configuration-required', 'provider-configuration-required'].includes(String(result.state)))
357
+ process.exitCode = 1;
358
+ return;
359
+ }
279
360
  const web = provider === 'vercel' ||
280
361
  (provider !== 'cloudflare' &&
281
362
  (existsSync(path.join(root, 'anhedral.web.json')) ||
@@ -283,20 +364,28 @@ async function runDelivery(command, args) {
283
364
  existsSync(path.join(root, 'anhedral.setup.json')) &&
284
365
  JSON.parse(readFileSync(path.join(root, 'anhedral.setup.json'), 'utf8')).hosting ===
285
366
  'vercel')));
367
+ if (command === 'ship' && !provider) {
368
+ result = await shipSelectedStack(root, args.includes('--dry-run'));
369
+ printResult(command, result, output);
370
+ if (result &&
371
+ typeof result === 'object' &&
372
+ 'state' in result &&
373
+ ['configuration-required', 'provider-configuration-required', 'partial'].includes(String(result.state)))
374
+ process.exitCode = 1;
375
+ return;
376
+ }
286
377
  if (web) {
287
378
  if (account || emails.length)
288
- throw new Error('Cloudflare account/member flags require --provider cloudflare.');
379
+ throw argumentError('Cloudflare account/member flags require --provider cloudflare.');
289
380
  if (command === 'register')
290
381
  result = registerVercelApplication(root);
291
382
  else if (command === 'connect') {
292
383
  if (!audience || !use)
293
- throw new Error('Vercel connect requires --audience public|private and --use personal|commercial.');
384
+ throw argumentError('Vercel connect requires --audience public|private and --use personal|commercial.');
294
385
  result = await connectVercelApplication(root, { scope, audience, use, create, git });
295
386
  }
296
387
  else if (command === 'ship')
297
- result = args.includes('--dry-run')
298
- ? planVercelDelivery(root)
299
- : await shipVercelApplication(root);
388
+ result = await shipSelectedVercelApplication(root, args.includes('--dry-run'));
300
389
  else if (command === 'adopt')
301
390
  result = await adoptVercelApplication(root);
302
391
  else if (command === 'recover')
@@ -305,7 +394,7 @@ async function runDelivery(command, args) {
305
394
  result = await verifyVercelApplication(root);
306
395
  else if (command === 'rollback') {
307
396
  if (!releaseId)
308
- throw new Error('Rollback requires --release <recorded-id>.');
397
+ throw argumentError('Rollback requires --release <recorded-id>.');
309
398
  result = await rollbackVercelApplication(root, releaseId, args.includes('--data-compatible'));
310
399
  }
311
400
  else if (command === 'status')
@@ -315,12 +404,19 @@ async function runDelivery(command, args) {
315
404
  result = { file: writeVercelOverview(root, snapshot), evidence: snapshot.evidence };
316
405
  }
317
406
  else
318
- throw new Error('Vercel project creation is handled by connect --create; Cloudflare resource provisioning requires --provider cloudflare.');
319
- console.log(JSON.stringify(result, null, 2));
407
+ throw argumentError('Vercel project creation is handled by connect --create; Cloudflare resource provisioning requires --provider cloudflare.');
408
+ if (command === 'status')
409
+ result = { ...result, operation: readLifecycleOperation(root) };
410
+ printResult(command, result, output);
411
+ if (result &&
412
+ typeof result === 'object' &&
413
+ 'state' in result &&
414
+ result.state === 'provider-configuration-required')
415
+ process.exitCode = 1;
320
416
  return;
321
417
  }
322
418
  if (scope || use || create || git)
323
- throw new Error('Vercel connect options require a Vercel web manifest or --provider vercel.');
419
+ throw argumentError('Vercel connect options require a Vercel web manifest or --provider vercel.');
324
420
  if (command === 'register')
325
421
  result = registerApplication(root);
326
422
  else if (command === 'connect') {
@@ -329,16 +425,16 @@ async function runDelivery(command, args) {
329
425
  else if (command === 'provision')
330
426
  result = args.includes('--dry-run') ? planProvisioning(root) : await provisionApplication(root);
331
427
  else if (command === 'ship')
332
- result = args.includes('--dry-run') ? planDelivery(root) : await shipApplication(root);
428
+ result = await shipCloudflareApplication(root, args.includes('--dry-run'));
333
429
  else if (command === 'adopt')
334
- throw new Error('Git deployment adoption requires --provider vercel.');
430
+ throw argumentError('Git deployment adoption requires --provider vercel.');
335
431
  else if (command === 'recover')
336
432
  result = await recoverApplication(root);
337
433
  else if (command === 'verify')
338
434
  result = await verifyApplication(root);
339
435
  else if (command === 'rollback') {
340
436
  if (!releaseId)
341
- throw new Error('Rollback requires --release <recorded-id>.');
437
+ throw argumentError('Rollback requires --release <recorded-id>.');
342
438
  result = await rollbackApplication(root, releaseId, args.includes('--data-compatible'));
343
439
  }
344
440
  else if (command === 'manage') {
@@ -347,17 +443,24 @@ async function runDelivery(command, args) {
347
443
  }
348
444
  else
349
445
  result = await applicationStatus(root, args.includes('--refresh'));
350
- console.log(JSON.stringify(result, null, 2));
446
+ if (command === 'status')
447
+ result = { ...result, operation: readLifecycleOperation(root) };
448
+ printResult(command, result, output);
449
+ if (result &&
450
+ typeof result === 'object' &&
451
+ 'state' in result &&
452
+ ['configuration-required', 'provider-configuration-required'].includes(String(result.state)))
453
+ process.exitCode = 1;
351
454
  }
352
455
  async function runDoctor(args) {
353
456
  const positional = args.filter((arg) => arg !== '--json' && arg !== '--providers');
354
457
  if (positional.length > 1 || positional.some((arg) => arg.startsWith('-')))
355
- throw new Error('Usage: anhedral doctor [directory] [--providers] [--json]');
458
+ throw argumentError('Usage: anhedral doctor [directory] [--providers] [--json]');
356
459
  const root = path.resolve(positional[0] ?? process.cwd());
357
460
  const report = args.includes('--providers')
358
461
  ? await inspectProviderReadiness(root)
359
462
  : inspectProject(root);
360
- console.log(JSON.stringify(report, null, json ? 2 : undefined));
463
+ printResult('doctor', report, output);
361
464
  if (!report.localReady ||
362
465
  ('prerequisitesReady' in report &&
363
466
  (!report.prerequisitesReady || report.providerVerification !== 'verified-existing-context')))
@@ -369,7 +472,13 @@ function readOptions(command, args) {
369
472
  }
370
473
  catch (error) {
371
474
  const message = error instanceof Error ? error.message : String(error);
372
- console.error(json ? JSON.stringify({ error: message, code: 'INVALID_ARGUMENT' }) : `Error: ${message}`);
475
+ console.error(json
476
+ ? JSON.stringify({
477
+ error: message,
478
+ code: 'INVALID_ARGUMENT',
479
+ nextAction: `Run anhedral ${command} --help.`,
480
+ })
481
+ : `Error: ${message}\nNext: Run anhedral ${command} --help.`);
373
482
  process.exitCode = 1;
374
483
  return;
375
484
  }
@@ -379,33 +488,41 @@ try {
379
488
  }
380
489
  catch (error) {
381
490
  const message = error instanceof Error ? error.message : String(error);
382
- const code = error instanceof PostCommitError
383
- ? 'POST_COMMIT_FAILED'
384
- : [
385
- 'repository',
386
- 'register',
387
- 'connect',
388
- 'provision',
389
- 'ship',
390
- 'status',
391
- 'manage',
392
- 'verify',
393
- 'rollback',
394
- 'adopt',
395
- 'recover',
396
- ].includes(command ?? '')
397
- ? 'DELIVERY_FAILED'
398
- : command === 'cloud'
399
- ? 'HOSTING_FAILED'
400
- : command === 'doctor'
401
- ? 'INVALID_ARGUMENT'
402
- : command === 'new' || command === 'init'
403
- ? 'GENERATION_FAILED'
404
- : command === 'add'
405
- ? 'ADDITION_FAILED'
406
- : command === 'registry'
407
- ? 'REGISTRY_FAILED'
408
- : 'UNKNOWN_COMMAND';
409
- console.error(json ? JSON.stringify({ error: message, code }) : `Error: ${message}`);
491
+ const code = error instanceof CliArgumentError
492
+ ? error.code
493
+ : error instanceof PostCommitError
494
+ ? 'POST_COMMIT_FAILED'
495
+ : [
496
+ 'repository',
497
+ 'register',
498
+ 'connect',
499
+ 'provision',
500
+ 'ship',
501
+ 'status',
502
+ 'manage',
503
+ 'verify',
504
+ 'rollback',
505
+ 'adopt',
506
+ 'recover',
507
+ 'integrations',
508
+ ].includes(command ?? '')
509
+ ? 'DELIVERY_FAILED'
510
+ : command === 'auth'
511
+ ? 'AUTH_FAILED'
512
+ : command === 'doctor'
513
+ ? 'INVALID_ARGUMENT'
514
+ : command === 'new' || command === 'init'
515
+ ? 'GENERATION_FAILED'
516
+ : command === 'add'
517
+ ? 'ADDITION_FAILED'
518
+ : command === 'registry'
519
+ ? 'REGISTRY_FAILED'
520
+ : 'UNKNOWN_COMMAND';
521
+ const nextAction = error instanceof CliArgumentError
522
+ ? error.nextAction
523
+ : `Run anhedral ${command && commandHelp(command) ? command + ' ' : ''}--help.`;
524
+ console.error(json
525
+ ? JSON.stringify({ error: message, code, nextAction })
526
+ : `Error: ${message}\nNext: ${nextAction}`);
410
527
  process.exitCode = 1;
411
528
  }
@@ -1,2 +1,3 @@
1
+ export declare const MAX_PROJECT_JSON_BYTES: number;
1
2
  /** Reject devices, links and oversized or growing files before parsing local input. */
2
3
  export declare function readBoundedFile(filePath: string, maximumBytes: number): Buffer;
@@ -1,4 +1,5 @@
1
1
  import { closeSync, constants, fstatSync, lstatSync, openSync, readSync } from 'node:fs';
2
+ export const MAX_PROJECT_JSON_BYTES = 2 * 1024 * 1024;
2
3
  /** Reject devices, links and oversized or growing files before parsing local input. */
3
4
  export function readBoundedFile(filePath, maximumBytes) {
4
5
  const stat = lstatSync(filePath);