@argo-video/cli 0.1.0 → 0.1.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/LICENSE +21 -0
- package/README.md +2 -2
- package/dist/asset-server.d.ts +7 -0
- package/dist/asset-server.d.ts.map +1 -0
- package/dist/asset-server.js +66 -0
- package/dist/asset-server.js.map +1 -0
- package/dist/captions.d.ts +17 -0
- package/dist/captions.d.ts.map +1 -0
- package/dist/captions.js +23 -0
- package/dist/captions.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +87 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +44 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +74 -0
- package/dist/config.js.map +1 -0
- package/dist/export.d.ts +18 -0
- package/dist/export.d.ts.map +1 -0
- package/dist/export.js +64 -0
- package/dist/export.js.map +1 -0
- package/dist/fixtures.d.ts +13 -0
- package/dist/fixtures.d.ts.map +1 -0
- package/dist/fixtures.js +36 -0
- package/dist/fixtures.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/init.d.ts +2 -0
- package/dist/init.d.ts.map +1 -0
- package/{src/init.ts → dist/init.js} +39 -54
- package/dist/init.js.map +1 -0
- package/dist/narration.d.ts +9 -0
- package/dist/narration.d.ts.map +1 -0
- package/dist/narration.js +27 -0
- package/dist/narration.js.map +1 -0
- package/dist/overlays/index.d.ts +8 -0
- package/dist/overlays/index.d.ts.map +1 -0
- package/dist/overlays/index.js +34 -0
- package/dist/overlays/index.js.map +1 -0
- package/dist/overlays/manifest.d.ts +5 -0
- package/dist/overlays/manifest.d.ts.map +1 -0
- package/dist/overlays/manifest.js +52 -0
- package/dist/overlays/manifest.js.map +1 -0
- package/dist/overlays/motion.d.ts +4 -0
- package/dist/overlays/motion.d.ts.map +1 -0
- package/dist/overlays/motion.js +25 -0
- package/dist/overlays/motion.js.map +1 -0
- package/dist/overlays/templates.d.ts +7 -0
- package/dist/overlays/templates.d.ts.map +1 -0
- package/dist/overlays/templates.js +98 -0
- package/dist/overlays/templates.js.map +1 -0
- package/dist/overlays/types.d.ts +42 -0
- package/dist/overlays/types.d.ts.map +1 -0
- package/dist/overlays/types.js +25 -0
- package/dist/overlays/types.js.map +1 -0
- package/dist/overlays/zones.d.ts +15 -0
- package/dist/overlays/zones.d.ts.map +1 -0
- package/dist/overlays/zones.js +69 -0
- package/dist/overlays/zones.js.map +1 -0
- package/dist/pipeline.d.ts +3 -0
- package/dist/pipeline.d.ts.map +1 -0
- package/dist/pipeline.js +93 -0
- package/dist/pipeline.js.map +1 -0
- package/dist/record.d.ts +14 -0
- package/dist/record.d.ts.map +1 -0
- package/dist/record.js +100 -0
- package/dist/record.js.map +1 -0
- package/dist/tts/align.d.ts +17 -0
- package/dist/tts/align.d.ts.map +1 -0
- package/dist/tts/align.js +40 -0
- package/dist/tts/align.js.map +1 -0
- package/dist/tts/cache.d.ts +31 -0
- package/dist/tts/cache.d.ts.map +1 -0
- package/dist/tts/cache.js +51 -0
- package/dist/tts/cache.js.map +1 -0
- package/dist/tts/engine.d.ts +41 -0
- package/dist/tts/engine.d.ts.map +1 -0
- package/dist/tts/engine.js +108 -0
- package/dist/tts/engine.js.map +1 -0
- package/dist/tts/generate.d.ts +20 -0
- package/dist/tts/generate.d.ts.map +1 -0
- package/dist/tts/generate.js +58 -0
- package/dist/tts/generate.js.map +1 -0
- package/dist/tts/kokoro.d.ts +13 -0
- package/dist/tts/kokoro.d.ts.map +1 -0
- package/dist/tts/kokoro.js +46 -0
- package/dist/tts/kokoro.js.map +1 -0
- package/package.json +13 -1
- package/.claude/settings.local.json +0 -34
- package/DESIGN.md +0 -261
- package/docs/enhancement-proposal.md +0 -262
- package/docs/superpowers/plans/2026-03-12-argo.md +0 -208
- package/docs/superpowers/plans/2026-03-12-editorial-overlay-system.md +0 -1560
- package/docs/superpowers/plans/2026-03-13-npm-rename-skill-showcase.md +0 -499
- package/docs/superpowers/specs/2026-03-13-npm-rename-skill-showcase-design.md +0 -109
- package/skills/argo-demo-creator.md +0 -355
- package/src/asset-server.ts +0 -81
- package/src/captions.ts +0 -36
- package/src/cli.ts +0 -97
- package/src/config.ts +0 -125
- package/src/export.ts +0 -93
- package/src/fixtures.ts +0 -50
- package/src/index.ts +0 -41
- package/src/narration.ts +0 -31
- package/src/overlays/index.ts +0 -54
- package/src/overlays/manifest.ts +0 -68
- package/src/overlays/motion.ts +0 -27
- package/src/overlays/templates.ts +0 -121
- package/src/overlays/types.ts +0 -73
- package/src/overlays/zones.ts +0 -82
- package/src/pipeline.ts +0 -120
- package/src/record.ts +0 -123
- package/src/tts/align.ts +0 -75
- package/src/tts/cache.ts +0 -65
- package/src/tts/engine.ts +0 -147
- package/src/tts/generate.ts +0 -83
- package/src/tts/kokoro.ts +0 -51
- package/tests/asset-server.test.ts +0 -67
- package/tests/captions.test.ts +0 -76
- package/tests/cli.test.ts +0 -131
- package/tests/config.test.ts +0 -150
- package/tests/e2e/fake-server.ts +0 -45
- package/tests/e2e/record.e2e.test.ts +0 -131
- package/tests/export.test.ts +0 -155
- package/tests/fixtures.test.ts +0 -74
- package/tests/init.test.ts +0 -77
- package/tests/narration.test.ts +0 -120
- package/tests/overlays/index.test.ts +0 -73
- package/tests/overlays/manifest.test.ts +0 -120
- package/tests/overlays/motion.test.ts +0 -34
- package/tests/overlays/templates.test.ts +0 -69
- package/tests/overlays/types.test.ts +0 -36
- package/tests/overlays/zones.test.ts +0 -49
- package/tests/pipeline.test.ts +0 -177
- package/tests/record.test.ts +0 -87
- package/tests/tts/align.test.ts +0 -118
- package/tests/tts/cache.test.ts +0 -110
- package/tests/tts/engine.test.ts +0 -204
- package/tests/tts/generate.test.ts +0 -177
- package/tests/tts/kokoro.test.ts +0 -25
- package/tsconfig.json +0 -19
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, afterEach } from 'vitest';
|
|
2
|
-
import { startAssetServer } from '../src/asset-server.js';
|
|
3
|
-
import { mkdtempSync, writeFileSync, mkdirSync, rmSync } from 'node:fs';
|
|
4
|
-
import { join } from 'node:path';
|
|
5
|
-
import { tmpdir } from 'node:os';
|
|
6
|
-
|
|
7
|
-
describe('startAssetServer', () => {
|
|
8
|
-
let tmpDir: string;
|
|
9
|
-
let close: (() => Promise<void>) | undefined;
|
|
10
|
-
|
|
11
|
-
function setup() {
|
|
12
|
-
tmpDir = mkdtempSync(join(tmpdir(), 'argo-asset-'));
|
|
13
|
-
mkdirSync(join(tmpDir, 'assets'), { recursive: true });
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
afterEach(async () => {
|
|
17
|
-
if (close) await close();
|
|
18
|
-
if (tmpDir) rmSync(tmpDir, { recursive: true, force: true });
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it('serves files from the asset directory', async () => {
|
|
22
|
-
setup();
|
|
23
|
-
writeFileSync(join(tmpDir, 'assets', 'test.txt'), 'hello');
|
|
24
|
-
const server = await startAssetServer(join(tmpDir, 'assets'));
|
|
25
|
-
close = server.close;
|
|
26
|
-
|
|
27
|
-
const res = await fetch(`${server.url}/test.txt`);
|
|
28
|
-
expect(res.status).toBe(200);
|
|
29
|
-
expect(await res.text()).toBe('hello');
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it('returns 404 for missing files', async () => {
|
|
33
|
-
setup();
|
|
34
|
-
const server = await startAssetServer(join(tmpDir, 'assets'));
|
|
35
|
-
close = server.close;
|
|
36
|
-
|
|
37
|
-
const res = await fetch(`${server.url}/nope.png`);
|
|
38
|
-
expect(res.status).toBe(404);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it('prevents path traversal', async () => {
|
|
42
|
-
setup();
|
|
43
|
-
writeFileSync(join(tmpDir, 'secret.txt'), 'private');
|
|
44
|
-
const server = await startAssetServer(join(tmpDir, 'assets'));
|
|
45
|
-
close = server.close;
|
|
46
|
-
|
|
47
|
-
const res = await fetch(`${server.url}/../secret.txt`);
|
|
48
|
-
expect(res.status).toBe(403);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it('assigns a random available port', async () => {
|
|
52
|
-
setup();
|
|
53
|
-
const server = await startAssetServer(join(tmpDir, 'assets'));
|
|
54
|
-
close = server.close;
|
|
55
|
-
expect(server.port).toBeGreaterThan(0);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it('serves PNG with correct content type', async () => {
|
|
59
|
-
setup();
|
|
60
|
-
writeFileSync(join(tmpDir, 'assets', 'img.png'), Buffer.from([0x89, 0x50]));
|
|
61
|
-
const server = await startAssetServer(join(tmpDir, 'assets'));
|
|
62
|
-
close = server.close;
|
|
63
|
-
|
|
64
|
-
const res = await fetch(`${server.url}/img.png`);
|
|
65
|
-
expect(res.headers.get('content-type')).toBe('image/png');
|
|
66
|
-
});
|
|
67
|
-
});
|
package/tests/captions.test.ts
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
-
import { showCaption, hideCaption, withCaption } from '../src/captions.js';
|
|
3
|
-
|
|
4
|
-
function createMockPage() {
|
|
5
|
-
const calls: string[] = [];
|
|
6
|
-
const page = {
|
|
7
|
-
evaluate: vi.fn(async () => {
|
|
8
|
-
calls.push('evaluate');
|
|
9
|
-
}),
|
|
10
|
-
waitForTimeout: vi.fn(async () => {
|
|
11
|
-
calls.push('waitForTimeout');
|
|
12
|
-
}),
|
|
13
|
-
};
|
|
14
|
-
return { page: page as any, calls };
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
// ---------- showCaption ----------
|
|
18
|
-
describe('showCaption', () => {
|
|
19
|
-
it('calls evaluate to inject, waits for duration, then calls evaluate to remove', async () => {
|
|
20
|
-
const { page, calls } = createMockPage();
|
|
21
|
-
await showCaption(page, 'intro', 'Hello world', 3000);
|
|
22
|
-
|
|
23
|
-
expect(page.evaluate).toHaveBeenCalledTimes(2);
|
|
24
|
-
expect(page.waitForTimeout).toHaveBeenCalledWith(3000);
|
|
25
|
-
expect(calls).toEqual(['evaluate', 'waitForTimeout', 'evaluate']);
|
|
26
|
-
});
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
// ---------- hideCaption ----------
|
|
30
|
-
describe('hideCaption', () => {
|
|
31
|
-
it('calls evaluate once to remove the overlay', async () => {
|
|
32
|
-
const { page } = createMockPage();
|
|
33
|
-
await hideCaption(page);
|
|
34
|
-
|
|
35
|
-
expect(page.evaluate).toHaveBeenCalledTimes(1);
|
|
36
|
-
});
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
// ---------- withCaption ----------
|
|
40
|
-
describe('withCaption', () => {
|
|
41
|
-
it('shows caption, runs action, then hides caption in order', async () => {
|
|
42
|
-
const order: string[] = [];
|
|
43
|
-
const page = {
|
|
44
|
-
evaluate: vi.fn(async () => {
|
|
45
|
-
order.push('evaluate');
|
|
46
|
-
}),
|
|
47
|
-
waitForTimeout: vi.fn(async () => {
|
|
48
|
-
order.push('waitForTimeout');
|
|
49
|
-
}),
|
|
50
|
-
} as any;
|
|
51
|
-
|
|
52
|
-
const action = async () => {
|
|
53
|
-
order.push('action');
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
await withCaption(page, 'demo', 'Doing stuff', action);
|
|
57
|
-
|
|
58
|
-
// inject, action, remove
|
|
59
|
-
expect(order).toEqual(['evaluate', 'action', 'evaluate']);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it('hides caption even if action throws', async () => {
|
|
63
|
-
const { page } = createMockPage();
|
|
64
|
-
|
|
65
|
-
const failingAction = async () => {
|
|
66
|
-
throw new Error('boom');
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
await expect(
|
|
70
|
-
withCaption(page, 'demo', 'Failing', failingAction),
|
|
71
|
-
).rejects.toThrow('boom');
|
|
72
|
-
|
|
73
|
-
// Should still have called evaluate twice: inject + remove
|
|
74
|
-
expect(page.evaluate).toHaveBeenCalledTimes(2);
|
|
75
|
-
});
|
|
76
|
-
});
|
package/tests/cli.test.ts
DELETED
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
|
|
3
|
-
vi.mock('../src/config.js', () => ({
|
|
4
|
-
loadConfig: vi.fn(),
|
|
5
|
-
}));
|
|
6
|
-
|
|
7
|
-
vi.mock('../src/record.js', () => ({
|
|
8
|
-
record: vi.fn(),
|
|
9
|
-
}));
|
|
10
|
-
|
|
11
|
-
vi.mock('../src/tts/generate.js', () => ({
|
|
12
|
-
generateClips: vi.fn(),
|
|
13
|
-
}));
|
|
14
|
-
|
|
15
|
-
vi.mock('../src/export.js', () => ({
|
|
16
|
-
exportVideo: vi.fn(),
|
|
17
|
-
}));
|
|
18
|
-
|
|
19
|
-
vi.mock('../src/pipeline.js', () => ({
|
|
20
|
-
runPipeline: vi.fn(),
|
|
21
|
-
}));
|
|
22
|
-
|
|
23
|
-
vi.mock('../src/init.js', () => ({
|
|
24
|
-
init: vi.fn(),
|
|
25
|
-
}));
|
|
26
|
-
|
|
27
|
-
import { loadConfig } from '../src/config.js';
|
|
28
|
-
import { record } from '../src/record.js';
|
|
29
|
-
import { generateClips } from '../src/tts/generate.js';
|
|
30
|
-
import { exportVideo } from '../src/export.js';
|
|
31
|
-
import { runPipeline } from '../src/pipeline.js';
|
|
32
|
-
import { init } from '../src/init.js';
|
|
33
|
-
import { createProgram } from '../src/cli.js';
|
|
34
|
-
|
|
35
|
-
const mockedLoadConfig = vi.mocked(loadConfig);
|
|
36
|
-
const mockedRecord = vi.mocked(record);
|
|
37
|
-
const mockedGenerateClips = vi.mocked(generateClips);
|
|
38
|
-
const mockedExportVideo = vi.mocked(exportVideo);
|
|
39
|
-
const mockedRunPipeline = vi.mocked(runPipeline);
|
|
40
|
-
const mockedInit = vi.mocked(init);
|
|
41
|
-
|
|
42
|
-
const defaultConfig = {
|
|
43
|
-
baseURL: 'http://localhost:3000',
|
|
44
|
-
demosDir: 'demos',
|
|
45
|
-
outputDir: 'videos',
|
|
46
|
-
tts: { defaultVoice: 'af_heart', defaultSpeed: 1.0 },
|
|
47
|
-
video: { width: 1920, height: 1080, fps: 30 },
|
|
48
|
-
export: { preset: 'slow', crf: 16 },
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
beforeEach(() => {
|
|
52
|
-
vi.resetAllMocks();
|
|
53
|
-
mockedLoadConfig.mockResolvedValue(defaultConfig as any);
|
|
54
|
-
mockedRecord.mockResolvedValue({ videoPath: '', timingPath: '' });
|
|
55
|
-
mockedGenerateClips.mockResolvedValue([]);
|
|
56
|
-
mockedExportVideo.mockResolvedValue('videos/onboarding.mp4');
|
|
57
|
-
mockedRunPipeline.mockResolvedValue('videos/onboarding.mp4');
|
|
58
|
-
mockedInit.mockResolvedValue(undefined as any);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
function run(...args: string[]) {
|
|
62
|
-
const program = createProgram();
|
|
63
|
-
program.exitOverride();
|
|
64
|
-
return program.parseAsync(['node', 'argo', ...args]);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
describe('CLI', () => {
|
|
68
|
-
describe('argo record <demo>', () => {
|
|
69
|
-
it('calls loadConfig and record with demo name', async () => {
|
|
70
|
-
await run('record', 'onboarding');
|
|
71
|
-
|
|
72
|
-
expect(mockedLoadConfig).toHaveBeenCalledWith(process.cwd(), undefined);
|
|
73
|
-
expect(mockedRecord).toHaveBeenCalledWith('onboarding', expect.objectContaining({
|
|
74
|
-
demosDir: 'demos',
|
|
75
|
-
}));
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it('passes --config to loadConfig', async () => {
|
|
79
|
-
await run('--config', 'custom.ts', 'record', 'onboarding');
|
|
80
|
-
|
|
81
|
-
expect(mockedLoadConfig).toHaveBeenCalledWith(process.cwd(), 'custom.ts');
|
|
82
|
-
});
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
describe('argo tts generate <manifest>', () => {
|
|
86
|
-
it('calls loadConfig and generateClips', async () => {
|
|
87
|
-
await run('tts', 'generate', 'manifest.json');
|
|
88
|
-
|
|
89
|
-
expect(mockedLoadConfig).toHaveBeenCalledWith(process.cwd(), undefined);
|
|
90
|
-
expect(mockedGenerateClips).toHaveBeenCalledWith(
|
|
91
|
-
expect.objectContaining({ manifestPath: 'manifest.json' }),
|
|
92
|
-
);
|
|
93
|
-
});
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
describe('argo export <demo>', () => {
|
|
97
|
-
it('calls loadConfig and exportVideo', async () => {
|
|
98
|
-
await run('export', 'onboarding');
|
|
99
|
-
|
|
100
|
-
expect(mockedLoadConfig).toHaveBeenCalledWith(process.cwd(), undefined);
|
|
101
|
-
expect(mockedExportVideo).toHaveBeenCalledWith(
|
|
102
|
-
expect.objectContaining({ demoName: 'onboarding' }),
|
|
103
|
-
);
|
|
104
|
-
});
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
describe('argo pipeline <demo>', () => {
|
|
108
|
-
it('calls loadConfig and runPipeline', async () => {
|
|
109
|
-
await run('pipeline', 'onboarding');
|
|
110
|
-
|
|
111
|
-
expect(mockedLoadConfig).toHaveBeenCalledWith(process.cwd(), undefined);
|
|
112
|
-
expect(mockedRunPipeline).toHaveBeenCalledWith('onboarding', defaultConfig);
|
|
113
|
-
});
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
describe('argo init', () => {
|
|
117
|
-
it('calls init()', async () => {
|
|
118
|
-
await run('init');
|
|
119
|
-
|
|
120
|
-
expect(mockedInit).toHaveBeenCalled();
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
describe('global --config option', () => {
|
|
125
|
-
it('passes config path to loadConfig for export command', async () => {
|
|
126
|
-
await run('-c', 'my-config.ts', 'export', 'onboarding');
|
|
127
|
-
|
|
128
|
-
expect(mockedLoadConfig).toHaveBeenCalledWith(process.cwd(), 'my-config.ts');
|
|
129
|
-
});
|
|
130
|
-
});
|
|
131
|
-
});
|
package/tests/config.test.ts
DELETED
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
-
import { mkdtemp, rm, writeFile, mkdir } from 'node:fs/promises';
|
|
4
|
-
import { tmpdir } from 'node:os';
|
|
5
|
-
import {
|
|
6
|
-
defineConfig,
|
|
7
|
-
loadConfig,
|
|
8
|
-
demosProject,
|
|
9
|
-
type ArgoConfig,
|
|
10
|
-
type TTSEngine,
|
|
11
|
-
} from '../src/config.js';
|
|
12
|
-
|
|
13
|
-
const DEFAULTS: ArgoConfig = {
|
|
14
|
-
demosDir: 'demos',
|
|
15
|
-
outputDir: 'videos',
|
|
16
|
-
tts: { defaultVoice: 'af_heart', defaultSpeed: 1.0 },
|
|
17
|
-
video: { width: 1920, height: 1080, fps: 30 },
|
|
18
|
-
export: { preset: 'slow', crf: 16 },
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
// ---------- defineConfig ----------
|
|
22
|
-
describe('defineConfig', () => {
|
|
23
|
-
it('returns defaults when given an empty object', () => {
|
|
24
|
-
const config = defineConfig({});
|
|
25
|
-
expect(config).toEqual(DEFAULTS);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it('merges top-level overrides while preserving unset defaults', () => {
|
|
29
|
-
const config = defineConfig({ baseURL: 'http://localhost:3000', demosDir: 'my-demos' });
|
|
30
|
-
expect(config.baseURL).toBe('http://localhost:3000');
|
|
31
|
-
expect(config.demosDir).toBe('my-demos');
|
|
32
|
-
expect(config.outputDir).toBe('videos');
|
|
33
|
-
expect(config.tts).toEqual(DEFAULTS.tts);
|
|
34
|
-
expect(config.video).toEqual(DEFAULTS.video);
|
|
35
|
-
expect(config.export).toEqual(DEFAULTS.export);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it('deep-merges nested tts config', () => {
|
|
39
|
-
const config = defineConfig({ tts: { defaultSpeed: 1.5 } });
|
|
40
|
-
expect(config.tts.defaultVoice).toBe('af_heart');
|
|
41
|
-
expect(config.tts.defaultSpeed).toBe(1.5);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it('deep-merges nested video config', () => {
|
|
45
|
-
const config = defineConfig({ video: { width: 1920, height: 1080 } });
|
|
46
|
-
expect(config.video.width).toBe(1920);
|
|
47
|
-
expect(config.video.height).toBe(1080);
|
|
48
|
-
expect(config.video.fps).toBe(30);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it('deep-merges nested export config', () => {
|
|
52
|
-
const config = defineConfig({ export: { crf: 23 } });
|
|
53
|
-
expect(config.export.preset).toBe('slow');
|
|
54
|
-
expect(config.export.crf).toBe(23);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it('preserves a custom TTS engine', () => {
|
|
58
|
-
const engine: TTSEngine = {
|
|
59
|
-
generate: async (_text, _options) => Buffer.from('audio'),
|
|
60
|
-
};
|
|
61
|
-
const config = defineConfig({ tts: { engine } });
|
|
62
|
-
expect(config.tts.engine).toBe(engine);
|
|
63
|
-
expect(config.tts.defaultVoice).toBe('af_heart');
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
// ---------- demosProject ----------
|
|
68
|
-
describe('demosProject', () => {
|
|
69
|
-
it('returns the correct Playwright project shape', () => {
|
|
70
|
-
const project = demosProject({ baseURL: 'http://localhost:4000' });
|
|
71
|
-
expect(project).toEqual({
|
|
72
|
-
name: 'demos',
|
|
73
|
-
testDir: 'demos',
|
|
74
|
-
testMatch: '**/*.demo.ts',
|
|
75
|
-
use: {
|
|
76
|
-
baseURL: 'http://localhost:4000',
|
|
77
|
-
video: 'on',
|
|
78
|
-
},
|
|
79
|
-
});
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it('uses a custom demosDir as testDir', () => {
|
|
83
|
-
const project = demosProject({ baseURL: 'http://localhost:4000', demosDir: 'my-demos' });
|
|
84
|
-
expect(project.testDir).toBe('my-demos');
|
|
85
|
-
});
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
// ---------- loadConfig ----------
|
|
89
|
-
describe('loadConfig', () => {
|
|
90
|
-
let tmpDir: string;
|
|
91
|
-
|
|
92
|
-
beforeEach(async () => {
|
|
93
|
-
tmpDir = await mkdtemp(join(tmpdir(), 'argo-config-test-'));
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
afterEach(async () => {
|
|
97
|
-
await rm(tmpDir, { recursive: true, force: true });
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
it('returns defaults when no config file exists', async () => {
|
|
101
|
-
const config = await loadConfig(tmpDir);
|
|
102
|
-
expect(config).toEqual(DEFAULTS);
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
it('loads argo.config.js and merges with defaults', async () => {
|
|
106
|
-
await writeFile(
|
|
107
|
-
join(tmpDir, 'argo.config.js'),
|
|
108
|
-
`export default { demosDir: 'custom-demos', tts: { defaultSpeed: 2.0 } };`,
|
|
109
|
-
);
|
|
110
|
-
const config = await loadConfig(tmpDir);
|
|
111
|
-
expect(config.demosDir).toBe('custom-demos');
|
|
112
|
-
expect(config.tts.defaultSpeed).toBe(2.0);
|
|
113
|
-
expect(config.tts.defaultVoice).toBe('af_heart');
|
|
114
|
-
expect(config.outputDir).toBe('videos');
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
it('loads argo.config.mjs', async () => {
|
|
118
|
-
await writeFile(
|
|
119
|
-
join(tmpDir, 'argo.config.mjs'),
|
|
120
|
-
`export default { outputDir: 'out' };`,
|
|
121
|
-
);
|
|
122
|
-
const config = await loadConfig(tmpDir);
|
|
123
|
-
expect(config.outputDir).toBe('out');
|
|
124
|
-
expect(config.demosDir).toBe('demos');
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
it('loads from an explicit path', async () => {
|
|
128
|
-
const customDir = join(tmpDir, 'nested');
|
|
129
|
-
await mkdir(customDir, { recursive: true });
|
|
130
|
-
const customPath = join(customDir, 'my-config.mjs');
|
|
131
|
-
await writeFile(customPath, `export default { baseURL: 'http://example.com' };`);
|
|
132
|
-
|
|
133
|
-
const config = await loadConfig(tmpDir, customPath);
|
|
134
|
-
expect(config.baseURL).toBe('http://example.com');
|
|
135
|
-
expect(config.demosDir).toBe('demos');
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
it('finds argo.config.ts first in search order', async () => {
|
|
139
|
-
await writeFile(
|
|
140
|
-
join(tmpDir, 'argo.config.ts'),
|
|
141
|
-
`export default { demosDir: 'from-ts' };`,
|
|
142
|
-
);
|
|
143
|
-
await writeFile(
|
|
144
|
-
join(tmpDir, 'argo.config.js'),
|
|
145
|
-
`export default { demosDir: 'from-js' };`,
|
|
146
|
-
);
|
|
147
|
-
const config = await loadConfig(tmpDir);
|
|
148
|
-
expect(config.demosDir).toBe('from-ts');
|
|
149
|
-
});
|
|
150
|
-
});
|
package/tests/e2e/fake-server.ts
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import http from 'node:http';
|
|
2
|
-
import type { AddressInfo } from 'node:net';
|
|
3
|
-
|
|
4
|
-
const HTML = `<!DOCTYPE html>
|
|
5
|
-
<html>
|
|
6
|
-
<head><title>Argo E2E Test</title></head>
|
|
7
|
-
<body>
|
|
8
|
-
<h1>Welcome to Argo Demo</h1>
|
|
9
|
-
<p>This is a fake app for E2E testing.</p>
|
|
10
|
-
<button id="action" onclick="document.getElementById('result').textContent='Done!'">
|
|
11
|
-
Get Started
|
|
12
|
-
</button>
|
|
13
|
-
<div id="result"></div>
|
|
14
|
-
</body>
|
|
15
|
-
</html>`;
|
|
16
|
-
|
|
17
|
-
export interface FakeServer {
|
|
18
|
-
url: string;
|
|
19
|
-
port: number;
|
|
20
|
-
close: () => Promise<void>;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function startFakeServer(): Promise<FakeServer> {
|
|
24
|
-
return new Promise((resolve, reject) => {
|
|
25
|
-
const server = http.createServer((_req, res) => {
|
|
26
|
-
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
27
|
-
res.end(HTML);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
const onError = (error: Error) => {
|
|
31
|
-
reject(error);
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
server.once('error', onError);
|
|
35
|
-
server.listen(0, '127.0.0.1', () => {
|
|
36
|
-
server.off('error', onError);
|
|
37
|
-
const { port } = server.address() as AddressInfo;
|
|
38
|
-
resolve({
|
|
39
|
-
url: `http://127.0.0.1:${port}`,
|
|
40
|
-
port,
|
|
41
|
-
close: () => new Promise((res, rej) => server.close((error) => (error ? rej(error) : res()))),
|
|
42
|
-
});
|
|
43
|
-
});
|
|
44
|
-
});
|
|
45
|
-
}
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeAll, afterAll, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import { mkdtemp, rm, writeFile, mkdir, readdir, stat } from 'node:fs/promises';
|
|
3
|
-
import { join, resolve } from 'node:path';
|
|
4
|
-
import { tmpdir } from 'node:os';
|
|
5
|
-
import { execFile } from 'node:child_process';
|
|
6
|
-
import { startFakeServer, type FakeServer } from './fake-server.js';
|
|
7
|
-
|
|
8
|
-
const PROJECT_ROOT = resolve(import.meta.dirname, '../..');
|
|
9
|
-
|
|
10
|
-
async function canBindLocalhost(): Promise<boolean> {
|
|
11
|
-
try {
|
|
12
|
-
const probeServer = await startFakeServer();
|
|
13
|
-
await probeServer.close();
|
|
14
|
-
return true;
|
|
15
|
-
} catch (error) {
|
|
16
|
-
const err = error as NodeJS.ErrnoException;
|
|
17
|
-
if (err.code === 'EPERM' || err.code === 'EACCES') {
|
|
18
|
-
return false;
|
|
19
|
-
}
|
|
20
|
-
throw error;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const describeE2E = (await canBindLocalhost()) ? describe : describe.skip;
|
|
25
|
-
|
|
26
|
-
describeE2E('E2E: argo record', () => {
|
|
27
|
-
let server: FakeServer;
|
|
28
|
-
let workDir: string;
|
|
29
|
-
|
|
30
|
-
beforeAll(async () => {
|
|
31
|
-
server = await startFakeServer();
|
|
32
|
-
}, 30_000);
|
|
33
|
-
|
|
34
|
-
afterAll(async () => {
|
|
35
|
-
await server.close();
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
beforeEach(async () => {
|
|
39
|
-
workDir = await mkdtemp(join(tmpdir(), 'argo-e2e-'));
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
afterEach(async () => {
|
|
43
|
-
await rm(workDir, { recursive: true, force: true });
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
function writePlaywrightConfig(baseURL: string): Promise<void> {
|
|
47
|
-
// testDir uses absolute path so Playwright can find it from any cwd
|
|
48
|
-
const demosDir = join(workDir, 'demos');
|
|
49
|
-
const config = `
|
|
50
|
-
import { defineConfig } from '@playwright/test';
|
|
51
|
-
|
|
52
|
-
export default defineConfig({
|
|
53
|
-
projects: [
|
|
54
|
-
{
|
|
55
|
-
name: 'demos',
|
|
56
|
-
testDir: '${demosDir}',
|
|
57
|
-
testMatch: '**/*.demo.ts',
|
|
58
|
-
use: {
|
|
59
|
-
baseURL: '${baseURL}',
|
|
60
|
-
video: 'on',
|
|
61
|
-
},
|
|
62
|
-
},
|
|
63
|
-
],
|
|
64
|
-
outputDir: '${join(workDir, 'test-results')}',
|
|
65
|
-
});
|
|
66
|
-
`;
|
|
67
|
-
return writeFile(join(workDir, 'playwright.config.ts'), config);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function writeDemoFile(): Promise<void> {
|
|
71
|
-
const demo = `
|
|
72
|
-
import { test, expect } from '@playwright/test';
|
|
73
|
-
|
|
74
|
-
test('example', async ({ page }) => {
|
|
75
|
-
await page.goto('/');
|
|
76
|
-
await expect(page.locator('h1')).toHaveText('Welcome to Argo Demo');
|
|
77
|
-
await page.click('#action');
|
|
78
|
-
await expect(page.locator('#result')).toHaveText('Done!');
|
|
79
|
-
});
|
|
80
|
-
`;
|
|
81
|
-
return writeFile(join(workDir, 'demos', 'example.demo.ts'), demo);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function runPlaywright(): Promise<{ stdout: string; stderr: string }> {
|
|
85
|
-
return new Promise((resolve, reject) => {
|
|
86
|
-
execFile(
|
|
87
|
-
'npx',
|
|
88
|
-
[
|
|
89
|
-
'playwright', 'test',
|
|
90
|
-
'--config', join(workDir, 'playwright.config.ts'),
|
|
91
|
-
'--grep', 'example',
|
|
92
|
-
'--project', 'demos',
|
|
93
|
-
],
|
|
94
|
-
{
|
|
95
|
-
cwd: PROJECT_ROOT,
|
|
96
|
-
env: {
|
|
97
|
-
...process.env,
|
|
98
|
-
BASE_URL: server.url,
|
|
99
|
-
NODE_PATH: join(PROJECT_ROOT, 'node_modules'),
|
|
100
|
-
},
|
|
101
|
-
timeout: 30_000,
|
|
102
|
-
},
|
|
103
|
-
(error, stdout, stderr) => {
|
|
104
|
-
if (error) {
|
|
105
|
-
reject(new Error(`Playwright failed:\n${stdout}\n${stderr}`));
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
resolve({ stdout, stderr });
|
|
109
|
-
},
|
|
110
|
-
);
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
it('records a demo against the fake server', async () => {
|
|
115
|
-
await mkdir(join(workDir, 'demos'), { recursive: true });
|
|
116
|
-
await writePlaywrightConfig(server.url);
|
|
117
|
-
await writeDemoFile();
|
|
118
|
-
|
|
119
|
-
await runPlaywright();
|
|
120
|
-
|
|
121
|
-
// Playwright with video: 'on' creates test-results/<test-name>/video.webm
|
|
122
|
-
const testResults = join(workDir, 'test-results');
|
|
123
|
-
const resultsStat = await stat(testResults);
|
|
124
|
-
expect(resultsStat.isDirectory()).toBe(true);
|
|
125
|
-
|
|
126
|
-
// Find the video file somewhere in test-results
|
|
127
|
-
const entries = await readdir(testResults, { recursive: true });
|
|
128
|
-
const videoFile = entries.find((e) => e.endsWith('.webm'));
|
|
129
|
-
expect(videoFile).toBeDefined();
|
|
130
|
-
}, 60_000);
|
|
131
|
-
});
|