@amodalai/amodal 0.2.10 → 0.3.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amodalai/amodal",
3
- "version": "0.2.10",
3
+ "version": "0.3.1",
4
4
  "description": "Amodal CLI",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -30,10 +30,11 @@
30
30
  "semver": "^7.6.0",
31
31
  "yargs": "^17.7.2",
32
32
  "zod": "^4.3.6",
33
- "@amodalai/types": "0.2.10",
34
- "@amodalai/core": "0.2.10",
35
- "@amodalai/runtime": "0.2.10",
36
- "@amodalai/runtime-app": "0.2.10"
33
+ "@amodalai/types": "0.3.1",
34
+ "@amodalai/core": "0.3.1",
35
+ "@amodalai/db": "0.3.1",
36
+ "@amodalai/runtime": "0.3.1",
37
+ "@amodalai/runtime-app": "0.3.1"
37
38
  },
38
39
  "devDependencies": {
39
40
  "@types/node": "^20.11.24",
@@ -1,63 +1,126 @@
1
1
  /**
2
2
  * @license
3
- * Copyright 2025 Amodal Labs, Inc.
3
+ * Copyright 2026 Amodal Labs, Inc.
4
4
  * SPDX-License-Identifier: MIT
5
5
  */
6
6
 
7
- import {describe, it, expect, vi} from 'vitest';
8
-
9
- vi.mock('../shared/repo-discovery.js', () => ({
10
- findRepoRoot: vi.fn(() => '/test/repo'),
11
- }));
7
+ import {describe, it, expect, beforeEach, afterEach, vi} from 'vitest';
8
+ import {mkdtempSync, writeFileSync, rmSync} from 'node:fs';
9
+ import path from 'node:path';
10
+ import os from 'node:os';
12
11
 
12
+ // Mock runtime dependencies that we don't need for the DATABASE_URL check
13
13
  vi.mock('@amodalai/runtime', () => ({
14
14
  createLocalServer: vi.fn().mockResolvedValue({
15
15
  app: {},
16
16
  start: vi.fn().mockResolvedValue({}),
17
17
  stop: vi.fn().mockResolvedValue(undefined),
18
18
  }),
19
+ initLogLevel: vi.fn(),
20
+ interceptConsole: vi.fn(),
21
+ log: {
22
+ info: vi.fn(),
23
+ warn: vi.fn(),
24
+ error: vi.fn(),
25
+ debug: vi.fn(),
26
+ },
27
+ }));
28
+
29
+ vi.mock('@amodalai/db', () => ({
30
+ getDb: vi.fn(),
31
+ ensureSchema: vi.fn().mockResolvedValue(undefined),
32
+ closeDb: vi.fn().mockResolvedValue(undefined),
33
+ }));
34
+
35
+ vi.mock('@amodalai/core', () => ({
36
+ resolveAdminAgent: vi.fn().mockResolvedValue(null),
37
+ }));
38
+
39
+ vi.mock('../shared/connection-preflight.js', () => ({
40
+ runConnectionPreflight: vi.fn().mockResolvedValue({results: [], hasFailures: false}),
41
+ printPreflightTable: vi.fn(),
19
42
  }));
20
43
 
44
+ /**
45
+ * Smoke test: `amodal dev` without DATABASE_URL should print instructions
46
+ * and exit with code 1.
47
+ */
21
48
  describe('dev command', () => {
22
- it('should import without error', async () => {
23
- const mod = await import('./dev.js');
24
- expect(mod.runDev).toBeDefined();
25
- expect(typeof mod.runDev).toBe('function');
49
+ let tmpDir: string;
50
+ let fakeHome: string;
51
+ let origHome: string;
52
+ let origDatabaseUrl: string | undefined;
53
+
54
+ beforeEach(() => {
55
+ tmpDir = mkdtempSync(path.join(os.tmpdir(), 'dev-test-'));
56
+ fakeHome = mkdtempSync(path.join(os.tmpdir(), 'dev-test-home-'));
57
+
58
+ // Create a minimal amodal.json so findRepoRoot succeeds
59
+ writeFileSync(path.join(tmpDir, 'amodal.json'), JSON.stringify({name: 'test-agent'}));
60
+
61
+ origHome = process.env['HOME'] ?? '';
62
+ origDatabaseUrl = process.env['DATABASE_URL'];
63
+ delete process.env['DATABASE_URL'];
64
+ process.env['HOME'] = fakeHome;
26
65
  });
27
66
 
28
- it('should find repo root', async () => {
29
- const {findRepoRoot} = await import('../shared/repo-discovery.js');
30
- const root = findRepoRoot('/some/path');
31
- expect(root).toBe('/test/repo');
67
+ afterEach(() => {
68
+ process.env['HOME'] = origHome;
69
+ if (origDatabaseUrl !== undefined) {
70
+ process.env['DATABASE_URL'] = origDatabaseUrl;
71
+ } else {
72
+ delete process.env['DATABASE_URL'];
73
+ }
74
+ rmSync(tmpDir, {recursive: true, force: true});
75
+ rmSync(fakeHome, {recursive: true, force: true});
76
+ vi.restoreAllMocks();
32
77
  });
33
78
 
34
- it('should create repo server with correct config', async () => {
35
- const {createLocalServer} = await import('@amodalai/runtime');
36
- const mockExit = vi.spyOn(process, 'exit').mockImplementation(() => {
37
- throw new Error('exit');
79
+ it('exits with code 1 and prints instructions when DATABASE_URL is missing', async () => {
80
+ // Capture stderr output
81
+ const stderrChunks: string[] = [];
82
+ vi.spyOn(process.stderr, 'write').mockImplementation((chunk: string | Uint8Array) => {
83
+ stderrChunks.push(String(chunk));
84
+ return true;
38
85
  });
39
86
 
40
- // runDev calls process.exit on success (after server starts), so we catch that
87
+ // Mock process.exit to throw so we can catch it
88
+ vi.spyOn(process, 'exit').mockImplementation((code?: number | string | null | undefined) => {
89
+ throw new ExitError(typeof code === 'number' ? code : 1);
90
+ });
91
+
92
+ const {runDev} = await import('./dev.js');
93
+
41
94
  try {
42
- const {runDev} = await import('./dev.js');
43
- // Don't actually run it — just verify the module loads
44
- expect(runDev).toBeDefined();
45
- } catch {
46
- // Expected
95
+ await runDev({cwd: tmpDir});
96
+ expect.unreachable('runDev should have called process.exit');
97
+ } catch (err: unknown) {
98
+ if (err instanceof ExitError) {
99
+ expect(err.code).toBe(1);
100
+ } else {
101
+ throw err;
102
+ }
47
103
  }
48
104
 
49
- expect(createLocalServer).toBeDefined();
50
- mockExit.mockRestore();
105
+ const output = stderrChunks.join('');
106
+ expect(output).toContain('DATABASE_URL is required');
107
+ expect(output).toContain('docker run');
108
+ expect(output).toContain('~/.amodal/.env');
51
109
  });
52
110
 
53
- it('should use default port 3847', async () => {
54
- const mod = await import('./dev.js');
55
- // Just verify the export exists — actual server test is integration
56
- expect(mod.runDev).toBeDefined();
57
- });
58
-
59
- it('should accept custom port', async () => {
111
+ it('should import without error', async () => {
60
112
  const mod = await import('./dev.js');
61
113
  expect(mod.runDev).toBeDefined();
114
+ expect(typeof mod.runDev).toBe('function');
62
115
  });
63
116
  });
117
+
118
+ /** Sentinel error thrown by our process.exit mock. */
119
+ class ExitError extends Error {
120
+ readonly code: number;
121
+ constructor(code: number) {
122
+ super(`process.exit(${String(code)})`);
123
+ this.name = 'ExitError';
124
+ this.code = code;
125
+ }
126
+ }