@amodalai/amodal 0.3.27 → 0.3.29

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.
package/src/e2e.test.ts DELETED
@@ -1,493 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright 2025 Amodal Labs, Inc.
4
- * SPDX-License-Identifier: MIT
5
- */
6
-
7
- /**
8
- * End-to-end tests for the `amodal` CLI commands.
9
- *
10
- * Strategy: register each command's CommandModule in yargs, replace
11
- * the handler with a capture function, parse real argv arrays, and
12
- * verify the parsed args. This validates the full yargs pipeline:
13
- * builder options -> positionals -> defaults -> type coercion.
14
- */
15
-
16
- import {describe, it, expect} from 'vitest';
17
- import yargs from 'yargs/yargs';
18
- import type {ArgumentsCamelCase, CommandModule} from 'yargs';
19
-
20
- import {connectCommand} from './commands/connect.js';
21
- import {installPkgCommand} from './commands/install-pkg.js';
22
- import {uninstallCommand} from './commands/uninstall.js';
23
- import {loginCommand} from './commands/login.js';
24
- import {linkCommand} from './commands/link.js';
25
- import {secretsCommand} from './commands/secrets.js';
26
- import {validateCommand} from './commands/validate.js';
27
- import {syncCommand} from './commands/sync.js';
28
- import {initCommand} from './commands/init.js';
29
- import {devCommand} from './commands/dev.js';
30
- import {inspectCommand} from './commands/inspect.js';
31
- import {deployCommand} from './commands/deploy.js';
32
- import {buildCommand} from './commands/build.js';
33
- import {dockerCommand} from './commands/docker.js';
34
- import {rollbackCommand} from './commands/rollback.js';
35
- import {deploymentsCommand} from './commands/deployments.js';
36
- import {promoteCommand} from './commands/promote.js';
37
- import {serveCommand} from './commands/serve.js';
38
- import {statusCommand} from './commands/status.js';
39
- import {auditCommand} from './commands/audit.js';
40
- import {evalCommand} from './commands/eval.js';
41
- import {experimentCommand} from './commands/experiment.js';
42
- import {testQueryCommand} from './commands/test-query.js';
43
- import {amodalCommands} from './commands/index.js';
44
-
45
- /**
46
- * Parse argv through yargs with a command module, capturing the parsed args
47
- * instead of executing the real handler.
48
- */
49
- async function parseArgs(cmd: CommandModule, argv: string[]): Promise<ArgumentsCamelCase> {
50
- let captured: ArgumentsCamelCase | undefined;
51
-
52
- // Replace handler with a capture function
53
- const testCmd: CommandModule = {
54
- ...cmd,
55
- handler: (args: ArgumentsCamelCase) => {
56
- captured = args;
57
- },
58
- };
59
-
60
- const parser = yargs(argv)
61
- .command(testCmd)
62
- .fail((msg, err) => {
63
- if (err) throw err;
64
- throw new Error(msg);
65
- })
66
- .exitProcess(false)
67
- .strict(false);
68
-
69
- await parser.parse();
70
-
71
- if (!captured) {
72
- throw new Error(`Handler not invoked for argv: ${argv.join(' ')}`);
73
- }
74
- return captured;
75
- }
76
-
77
- describe('amodal CLI e2e', () => {
78
- // --- Registration ---
79
-
80
- describe('command registration', () => {
81
- it('exports all 30 commands', () => {
82
- expect(amodalCommands).toHaveLength(30);
83
- });
84
-
85
- it('all commands have valid structure', () => {
86
- for (const cmd of amodalCommands) {
87
- expect(cmd.command).toBeDefined();
88
- expect(typeof cmd.handler).toBe('function');
89
- }
90
- });
91
- });
92
-
93
- // --- connect ---
94
-
95
- describe('connect', () => {
96
- it('parses name positional', async () => {
97
- const args = await parseArgs(connectCommand, ['connect', 'stripe']);
98
- expect(args['name']).toBe('stripe');
99
- expect(args['force']).toBe(false);
100
- });
101
-
102
- it('parses --force flag', async () => {
103
- const args = await parseArgs(connectCommand, ['connect', 'stripe', '--force']);
104
- expect(args['name']).toBe('stripe');
105
- expect(args['force']).toBe(true);
106
- });
107
- });
108
-
109
- // --- install ---
110
-
111
- describe('install', () => {
112
- it('bare install has empty packages array', async () => {
113
- const args = await parseArgs(installPkgCommand, ['install']);
114
- expect(args['packages']).toEqual([]);
115
- });
116
-
117
- it('variadic positional captures package names', async () => {
118
- const args = await parseArgs(installPkgCommand, ['install', 'alert-enrichment', 'soc-agent']);
119
- expect(args['packages']).toEqual(['alert-enrichment', 'soc-agent']);
120
- });
121
- });
122
-
123
- // --- uninstall ---
124
-
125
- describe('uninstall', () => {
126
- it('parses name positional', async () => {
127
- const args = await parseArgs(uninstallCommand, ['uninstall', 'connection-stripe']);
128
- expect(args['name']).toBe('connection-stripe');
129
- });
130
- });
131
-
132
- // --- login ---
133
-
134
- describe('login', () => {
135
- it('defaults', async () => {
136
- const args = await parseArgs(loginCommand, ['login']);
137
- expect(args['platformUrl']).toBeUndefined();
138
- expect(args['adminUrl']).toBeUndefined();
139
- });
140
-
141
- it('parses --platform-url', async () => {
142
- const args = await parseArgs(loginCommand, ['login', '--platform-url', 'https://custom.dev']);
143
- expect(args['platformUrl']).toBe('https://custom.dev');
144
- });
145
-
146
- it('parses --admin-url', async () => {
147
- const args = await parseArgs(loginCommand, ['login', '--admin-url', 'https://app.custom.dev']);
148
- expect(args['adminUrl']).toBe('https://app.custom.dev');
149
- });
150
- });
151
-
152
- // --- link ---
153
-
154
- describe('link', () => {
155
- it('defaults', async () => {
156
- const args = await parseArgs(linkCommand, ['link']);
157
- expect(args['yes']).toBeUndefined();
158
- expect(args['orgId']).toBeUndefined();
159
- expect(args['appId']).toBeUndefined();
160
- });
161
-
162
- it('parses --yes', async () => {
163
- const args = await parseArgs(linkCommand, ['link', '--yes']);
164
- expect(args['yes']).toBe(true);
165
- });
166
-
167
- it('parses --org-id and --app-id', async () => {
168
- const args = await parseArgs(linkCommand, ['link', '--org-id', 'org-123', '--app-id', 'app-456']);
169
- expect(args['orgId']).toBe('org-123');
170
- expect(args['appId']).toBe('app-456');
171
- });
172
- });
173
-
174
- // --- secrets ---
175
-
176
- describe('secrets', () => {
177
- it('parses set with key and value', async () => {
178
- const args = await parseArgs(secretsCommand, ['secrets', 'set', 'API_KEY', 'secret123']);
179
- expect(args['subcommand']).toBe('set');
180
- expect(args['key']).toBe('API_KEY');
181
- expect(args['value']).toBe('secret123');
182
- });
183
-
184
- it('parses list', async () => {
185
- const args = await parseArgs(secretsCommand, ['secrets', 'list']);
186
- expect(args['subcommand']).toBe('list');
187
- expect(args['json']).toBe(false);
188
- });
189
-
190
- it('parses list --json', async () => {
191
- const args = await parseArgs(secretsCommand, ['secrets', 'list', '--json']);
192
- expect(args['subcommand']).toBe('list');
193
- expect(args['json']).toBe(true);
194
- });
195
-
196
- it('parses delete with key', async () => {
197
- const args = await parseArgs(secretsCommand, ['secrets', 'delete', 'OLD_KEY']);
198
- expect(args['subcommand']).toBe('delete');
199
- expect(args['key']).toBe('OLD_KEY');
200
- });
201
- });
202
-
203
- // --- validate ---
204
-
205
- describe('validate', () => {
206
- it('defaults to packages=false', async () => {
207
- const args = await parseArgs(validateCommand, ['validate']);
208
- expect(args['packages']).toBe(false);
209
- });
210
-
211
- it('parses --packages', async () => {
212
- const args = await parseArgs(validateCommand, ['validate', '--packages']);
213
- expect(args['packages']).toBe(true);
214
- });
215
- });
216
-
217
- // --- sync ---
218
-
219
- describe('sync', () => {
220
- it('defaults', async () => {
221
- const args = await parseArgs(syncCommand, ['sync']);
222
- expect(args['check']).toBe(false);
223
- expect(args['connection']).toBeUndefined();
224
- });
225
-
226
- it('parses --check and --connection', async () => {
227
- const args = await parseArgs(syncCommand, ['sync', '--check', '--connection', 'stripe']);
228
- expect(args['check']).toBe(true);
229
- expect(args['connection']).toBe('stripe');
230
- });
231
- });
232
-
233
- // --- init ---
234
-
235
- describe('init', () => {
236
- it('defaults', async () => {
237
- const args = await parseArgs(initCommand, ['init']);
238
- expect(args['name']).toBeUndefined();
239
- });
240
-
241
- it('parses --name', async () => {
242
- const args = await parseArgs(initCommand, ['init', '--name', 'my-project']);
243
- expect(args['name']).toBe('my-project');
244
- });
245
-
246
- it('parses --provider', async () => {
247
- const args = await parseArgs(initCommand, ['init', '--provider', 'anthropic']);
248
- expect(args['provider']).toBe('anthropic');
249
- });
250
- });
251
-
252
- // --- dev ---
253
-
254
- describe('dev', () => {
255
- it('defaults', async () => {
256
- const args = await parseArgs(devCommand, ['dev']);
257
- expect(args['port']).toBeUndefined();
258
- expect(args['host']).toBeUndefined();
259
- });
260
-
261
- it('parses --port and --host', async () => {
262
- const args = await parseArgs(devCommand, ['dev', '--port', '4000', '--host', '0.0.0.0']);
263
- expect(args['port']).toBe(4000);
264
- expect(args['host']).toBe('0.0.0.0');
265
- });
266
- });
267
-
268
- // --- inspect ---
269
-
270
- describe('inspect', () => {
271
- it('all flags default to false', async () => {
272
- const args = await parseArgs(inspectCommand, ['inspect']);
273
- expect(args['context']).toBe(false);
274
- expect(args['explore']).toBe(false);
275
- expect(args['tools']).toBe(false);
276
- expect(args['connections']).toBe(false);
277
- expect(args['resolved']).toBe(false);
278
- expect(args['scope']).toBeUndefined();
279
- });
280
-
281
- it('parses section flags and --scope', async () => {
282
- const args = await parseArgs(inspectCommand, ['inspect', '--context', '--resolved', '--scope', 'connections/stripe']);
283
- expect(args['context']).toBe(true);
284
- expect(args['resolved']).toBe(true);
285
- expect(args['scope']).toBe('connections/stripe');
286
- });
287
- });
288
-
289
- // --- deploy ---
290
-
291
- describe('deploy', () => {
292
- it('parses --message flag', async () => {
293
- const args = await parseArgs(deployCommand, ['deploy', '--message', 'v1.0 release']);
294
- expect(args['message']).toBe('v1.0 release');
295
- });
296
-
297
- it('parses -m alias', async () => {
298
- const args = await parseArgs(deployCommand, ['deploy', '-m', 'hotfix']);
299
- expect(args['message']).toBe('hotfix');
300
- });
301
-
302
- it('parses --env flag', async () => {
303
- const args = await parseArgs(deployCommand, ['deploy', '--env', 'staging']);
304
- expect(args['env']).toBe('staging');
305
- });
306
-
307
- it('parses --dry-run flag', async () => {
308
- const args = await parseArgs(deployCommand, ['deploy', '--dry-run']);
309
- expect(args['dryRun']).toBe(true);
310
- });
311
-
312
- it('defaults dry-run to false', async () => {
313
- const args = await parseArgs(deployCommand, ['deploy']);
314
- expect(args['dryRun']).toBe(false);
315
- });
316
- });
317
-
318
- // --- build ---
319
-
320
- describe('build', () => {
321
- it('parses --output flag', async () => {
322
- const args = await parseArgs(buildCommand, ['build', '--output', '/tmp/out.json']);
323
- expect(args['output']).toBe('/tmp/out.json');
324
- });
325
-
326
- it('parses -o alias', async () => {
327
- const args = await parseArgs(buildCommand, ['build', '-o', '/tmp/out.json']);
328
- expect(args['output']).toBe('/tmp/out.json');
329
- });
330
- });
331
-
332
- // --- docker ---
333
-
334
- describe('docker', () => {
335
- it('parses subcommand', async () => {
336
- const args = await parseArgs(dockerCommand, ['docker', 'check']);
337
- expect(args['subcommand']).toBe('check');
338
- });
339
-
340
- it('parses --tag with build', async () => {
341
- const args = await parseArgs(dockerCommand, ['docker', 'build', '--tag', 'v1.2.3']);
342
- expect(args['subcommand']).toBe('build');
343
- expect(args['tag']).toBe('v1.2.3');
344
- });
345
-
346
- it('accepts all valid subcommands', async () => {
347
- for (const sub of ['init', 'check', 'build']) {
348
- const args = await parseArgs(dockerCommand, ['docker', sub]);
349
- expect(args['subcommand']).toBe(sub);
350
- }
351
- });
352
- });
353
-
354
- // --- rollback ---
355
-
356
- describe('rollback', () => {
357
- it('parses deploy-id positional', async () => {
358
- const args = await parseArgs(rollbackCommand, ['rollback', 'deploy-abc1234']);
359
- expect(args['deployId']).toBe('deploy-abc1234');
360
- });
361
-
362
- it('parses --env flag', async () => {
363
- const args = await parseArgs(rollbackCommand, ['rollback', '--env', 'staging']);
364
- expect(args['env']).toBe('staging');
365
- });
366
- });
367
-
368
- // --- deployments ---
369
-
370
- describe('deployments', () => {
371
- it('parses --limit flag', async () => {
372
- const args = await parseArgs(deploymentsCommand, ['deployments', '--limit', '5']);
373
- expect(args['limit']).toBe(5);
374
- });
375
-
376
- it('parses --json flag', async () => {
377
- const args = await parseArgs(deploymentsCommand, ['deployments', '--json']);
378
- expect(args['json']).toBe(true);
379
- });
380
-
381
- it('defaults limit to 10', async () => {
382
- const args = await parseArgs(deploymentsCommand, ['deployments']);
383
- expect(args['limit']).toBe(10);
384
- });
385
- });
386
-
387
- // --- promote ---
388
-
389
- describe('promote', () => {
390
- it('parses from-env positional', async () => {
391
- const args = await parseArgs(promoteCommand, ['promote', 'staging']);
392
- expect(args['fromEnv']).toBe('staging');
393
- });
394
-
395
- it('parses --to flag', async () => {
396
- const args = await parseArgs(promoteCommand, ['promote', 'staging', '--to', 'production']);
397
- expect(args['to']).toBe('production');
398
- });
399
- });
400
-
401
- // --- serve ---
402
-
403
- describe('serve', () => {
404
- it('parses --config flag', async () => {
405
- const args = await parseArgs(serveCommand, ['serve', '--config', '/tmp/snapshot.json']);
406
- expect(args['config']).toBe('/tmp/snapshot.json');
407
- });
408
-
409
- it('parses --platform flag', async () => {
410
- const args = await parseArgs(serveCommand, ['serve', '--platform']);
411
- expect(args['platform']).toBe(true);
412
- });
413
- });
414
-
415
- // --- status ---
416
-
417
- describe('status', () => {
418
- it('parses --env flag', async () => {
419
- const args = await parseArgs(statusCommand, ['status', '--env', 'staging']);
420
- expect(args['env']).toBe('staging');
421
- });
422
-
423
- it('parses --json flag', async () => {
424
- const args = await parseArgs(statusCommand, ['status', '--json']);
425
- expect(args['json']).toBe(true);
426
- });
427
- });
428
-
429
- // --- audit ---
430
-
431
- describe('audit', () => {
432
- it('parses session-id positional', async () => {
433
- const args = await parseArgs(auditCommand, ['audit', 'sess-abc123']);
434
- expect(args['sessionId']).toBe('sess-abc123');
435
- });
436
-
437
- it('parses --format', async () => {
438
- const args = await parseArgs(auditCommand, ['audit', 'sess-abc123', '--format', 'json']);
439
- expect(args['sessionId']).toBe('sess-abc123');
440
- expect(args['format']).toBe('json');
441
- });
442
- });
443
-
444
- // --- eval ---
445
-
446
- describe('eval', () => {
447
- it('defaults', async () => {
448
- const args = await parseArgs(evalCommand, ['eval']);
449
- expect(args['diff']).toBe(false);
450
- expect(args['ci']).toBe(false);
451
- });
452
-
453
- it('parses --filter --diff --ci --port', async () => {
454
- const args = await parseArgs(evalCommand, ['eval', '--filter', 'stale', '--diff', '--ci', '--port', '5000']);
455
- expect(args['filter']).toBe('stale');
456
- expect(args['diff']).toBe(true);
457
- expect(args['ci']).toBe(true);
458
- expect(args['port']).toBe(5000);
459
- });
460
- });
461
-
462
- // --- experiment ---
463
-
464
- describe('experiment', () => {
465
- it('parses action positional', async () => {
466
- const args = await parseArgs(experimentCommand, ['experiment', 'list']);
467
- expect(args['action']).toBe('list');
468
- });
469
-
470
- it('parses --name and --id', async () => {
471
- const args = await parseArgs(experimentCommand, ['experiment', 'watch', '--name', 'test-exp', '--id', 'exp-123']);
472
- expect(args['action']).toBe('watch');
473
- expect(args['name']).toBe('test-exp');
474
- expect(args['id']).toBe('exp-123');
475
- });
476
- });
477
-
478
- // --- test-query ---
479
-
480
- describe('test-query', () => {
481
- it('parses message positional', async () => {
482
- const args = await parseArgs(testQueryCommand, ['test-query', 'What is the status?']);
483
- expect(args['message']).toBe('What is the status?');
484
- });
485
-
486
- it('parses --app-id and --port', async () => {
487
- const args = await parseArgs(testQueryCommand, ['test-query', 'hello', '--app-id', 't-123', '--port', '3001']);
488
- expect(args['message']).toBe('hello');
489
- expect(args['appId']).toBe('t-123');
490
- expect(args['port']).toBe(3001);
491
- });
492
- });
493
- });