@automaze/proof 0.20260311.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.
- package/LICENSE +202 -0
- package/README.md +221 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +934 -0
- package/dist/cli.test.d.ts +1 -0
- package/dist/cli.test.js +200 -0
- package/dist/detect.d.ts +2 -0
- package/dist/detect.js +31 -0
- package/dist/detect.test.d.ts +1 -0
- package/dist/detect.test.js +54 -0
- package/dist/duration.d.ts +1 -0
- package/dist/duration.js +23 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +843 -0
- package/dist/index.test.d.ts +1 -0
- package/dist/index.test.js +261 -0
- package/dist/modes/terminal.d.ts +5 -0
- package/dist/modes/terminal.js +287 -0
- package/dist/modes/visual.d.ts +10 -0
- package/dist/modes/visual.js +165 -0
- package/dist/report.d.ts +2 -0
- package/dist/report.js +196 -0
- package/dist/types.d.ts +54 -0
- package/dist/types.js +1 -0
- package/package.json +39 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/cli.test.js
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach, afterEach } from "bun:test";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { mkdtemp, rm, readFile } from "fs/promises";
|
|
4
|
+
import { tmpdir } from "os";
|
|
5
|
+
import { existsSync } from "fs";
|
|
6
|
+
const CLI_PATH = join(import.meta.dir, "cli.ts");
|
|
7
|
+
const TEST_APP_DIR = join(import.meta.dir, "../test-app");
|
|
8
|
+
async function runCli(args, stdin) {
|
|
9
|
+
const proc = Bun.spawn(["bun", "run", CLI_PATH, ...args], {
|
|
10
|
+
stdin: stdin ? new Blob([stdin]) : undefined,
|
|
11
|
+
stdout: "pipe",
|
|
12
|
+
stderr: "pipe",
|
|
13
|
+
env: { ...process.env, FORCE_COLOR: "0" },
|
|
14
|
+
});
|
|
15
|
+
const [stdout, stderr] = await Promise.all([
|
|
16
|
+
new Response(proc.stdout).text(),
|
|
17
|
+
new Response(proc.stderr).text(),
|
|
18
|
+
]);
|
|
19
|
+
const exitCode = await proc.exited;
|
|
20
|
+
return { stdout: stdout.trim(), stderr: stderr.trim(), exitCode };
|
|
21
|
+
}
|
|
22
|
+
describe("CLI", () => {
|
|
23
|
+
let tempDir;
|
|
24
|
+
beforeEach(async () => {
|
|
25
|
+
tempDir = await mkdtemp(join(tmpdir(), "proof-cli-test-"));
|
|
26
|
+
});
|
|
27
|
+
afterEach(async () => {
|
|
28
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
29
|
+
});
|
|
30
|
+
describe("help and usage", () => {
|
|
31
|
+
test("prints usage with --help", async () => {
|
|
32
|
+
const { stdout, exitCode } = await runCli(["--help"]);
|
|
33
|
+
expect(exitCode).toBe(0);
|
|
34
|
+
expect(stdout).toContain("@automaze/proof");
|
|
35
|
+
expect(stdout).toContain("proof capture");
|
|
36
|
+
expect(stdout).toContain("--app");
|
|
37
|
+
});
|
|
38
|
+
test("prints usage with no args", async () => {
|
|
39
|
+
const { stdout, exitCode } = await runCli([]);
|
|
40
|
+
expect(exitCode).toBe(0);
|
|
41
|
+
expect(stdout).toContain("@automaze/proof");
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
describe("arg validation", () => {
|
|
45
|
+
test("errors when --app is missing", async () => {
|
|
46
|
+
const { stderr, exitCode } = await runCli(["capture", "--command", "echo hi"]);
|
|
47
|
+
expect(exitCode).toBe(1);
|
|
48
|
+
expect(stderr).toContain("--app is required");
|
|
49
|
+
});
|
|
50
|
+
test("errors when capture has no --command or --test-file", async () => {
|
|
51
|
+
const { stderr, exitCode } = await runCli(["capture", "--app", "test"]);
|
|
52
|
+
expect(exitCode).toBe(1);
|
|
53
|
+
expect(stderr).toContain("--command or --test-file is required");
|
|
54
|
+
});
|
|
55
|
+
test("errors on unknown action", async () => {
|
|
56
|
+
const { stderr, exitCode } = await runCli(["bogus"]);
|
|
57
|
+
expect(exitCode).toBe(1);
|
|
58
|
+
expect(stderr).toContain("Unknown action");
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
describe("capture (arg mode)", () => {
|
|
62
|
+
test("captures terminal output and returns JSON", async () => {
|
|
63
|
+
const { stdout, exitCode } = await runCli([
|
|
64
|
+
"capture",
|
|
65
|
+
"--app", "cli-test",
|
|
66
|
+
"--dir", tempDir,
|
|
67
|
+
"--run", "r1",
|
|
68
|
+
"--command", "echo hello",
|
|
69
|
+
"--mode", "terminal",
|
|
70
|
+
"--label", "echo-test",
|
|
71
|
+
]);
|
|
72
|
+
expect(exitCode).toBe(0);
|
|
73
|
+
const result = JSON.parse(stdout);
|
|
74
|
+
expect(result.action).toBe("capture");
|
|
75
|
+
expect(result.appName).toBe("cli-test");
|
|
76
|
+
expect(result.recordings).toHaveLength(1);
|
|
77
|
+
expect(result.recordings[0].mode).toBe("terminal");
|
|
78
|
+
expect(result.recordings[0].label).toBe("echo-test");
|
|
79
|
+
expect(result.recordings[0].duration).toBeGreaterThanOrEqual(0);
|
|
80
|
+
expect(existsSync(result.recordings[0].path)).toBe(true);
|
|
81
|
+
});
|
|
82
|
+
test("passes --description through to manifest", async () => {
|
|
83
|
+
const { stdout, exitCode } = await runCli([
|
|
84
|
+
"capture",
|
|
85
|
+
"--app", "cli-test",
|
|
86
|
+
"--dir", tempDir,
|
|
87
|
+
"--run", "r1",
|
|
88
|
+
"--command", "echo hi",
|
|
89
|
+
"--mode", "terminal",
|
|
90
|
+
"--description", "A test capture",
|
|
91
|
+
]);
|
|
92
|
+
expect(exitCode).toBe(0);
|
|
93
|
+
const result = JSON.parse(stdout);
|
|
94
|
+
const manifestPath = join(tempDir, "cli-test", "*", "r1", "proof.json").replace("*", new Date().toISOString().slice(0, 10).replace(/-/g, ""));
|
|
95
|
+
const manifest = JSON.parse(await readFile(manifestPath, "utf-8"));
|
|
96
|
+
expect(manifest.entries[0].description).toBe("A test capture");
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
describe("JSON stdin mode", () => {
|
|
100
|
+
test("captures single entry via JSON", async () => {
|
|
101
|
+
const input = JSON.stringify({
|
|
102
|
+
action: "capture",
|
|
103
|
+
appName: "json-test",
|
|
104
|
+
proofDir: tempDir,
|
|
105
|
+
run: "j1",
|
|
106
|
+
command: "echo json-mode",
|
|
107
|
+
mode: "terminal",
|
|
108
|
+
label: "json-cap",
|
|
109
|
+
});
|
|
110
|
+
const { stdout, exitCode } = await runCli(["--json"], input);
|
|
111
|
+
expect(exitCode).toBe(0);
|
|
112
|
+
const result = JSON.parse(stdout);
|
|
113
|
+
expect(result.action).toBe("capture");
|
|
114
|
+
expect(result.recordings).toHaveLength(1);
|
|
115
|
+
expect(result.recordings[0].mode).toBe("terminal");
|
|
116
|
+
expect(result.recordings[0].label).toBe("json-cap");
|
|
117
|
+
});
|
|
118
|
+
test("captures multiple entries via JSON captures array", async () => {
|
|
119
|
+
const input = JSON.stringify({
|
|
120
|
+
action: "capture",
|
|
121
|
+
appName: "multi-test",
|
|
122
|
+
proofDir: tempDir,
|
|
123
|
+
run: "m1",
|
|
124
|
+
captures: [
|
|
125
|
+
{ command: "echo one", mode: "terminal", label: "first" },
|
|
126
|
+
{ command: "echo two", mode: "terminal", label: "second" },
|
|
127
|
+
],
|
|
128
|
+
});
|
|
129
|
+
const { stdout, exitCode } = await runCli(["--json"], input);
|
|
130
|
+
expect(exitCode).toBe(0);
|
|
131
|
+
const result = JSON.parse(stdout);
|
|
132
|
+
expect(result.recordings).toHaveLength(2);
|
|
133
|
+
expect(result.recordings[0].label).toBe("first");
|
|
134
|
+
expect(result.recordings[1].label).toBe("second");
|
|
135
|
+
});
|
|
136
|
+
test("generates report via JSON", async () => {
|
|
137
|
+
// First capture something
|
|
138
|
+
const captureInput = JSON.stringify({
|
|
139
|
+
action: "capture",
|
|
140
|
+
appName: "report-test",
|
|
141
|
+
proofDir: tempDir,
|
|
142
|
+
run: "rpt",
|
|
143
|
+
command: "echo for-report",
|
|
144
|
+
mode: "terminal",
|
|
145
|
+
});
|
|
146
|
+
await runCli(["--json"], captureInput);
|
|
147
|
+
// Then generate report
|
|
148
|
+
const reportInput = JSON.stringify({
|
|
149
|
+
action: "report",
|
|
150
|
+
appName: "report-test",
|
|
151
|
+
proofDir: tempDir,
|
|
152
|
+
run: "rpt",
|
|
153
|
+
});
|
|
154
|
+
const { stdout, exitCode } = await runCli(["--json"], reportInput);
|
|
155
|
+
expect(exitCode).toBe(0);
|
|
156
|
+
const result = JSON.parse(stdout);
|
|
157
|
+
expect(result.action).toBe("report");
|
|
158
|
+
expect(result.path).toContain("report.md");
|
|
159
|
+
expect(existsSync(result.path)).toBe(true);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
describe("report (arg mode)", () => {
|
|
163
|
+
test("generates report after capture", async () => {
|
|
164
|
+
// Capture first
|
|
165
|
+
await runCli([
|
|
166
|
+
"capture",
|
|
167
|
+
"--app", "rpt-test",
|
|
168
|
+
"--dir", tempDir,
|
|
169
|
+
"--run", "r1",
|
|
170
|
+
"--command", "echo hi",
|
|
171
|
+
"--mode", "terminal",
|
|
172
|
+
]);
|
|
173
|
+
// Generate report
|
|
174
|
+
const { stdout, exitCode } = await runCli([
|
|
175
|
+
"report",
|
|
176
|
+
"--app", "rpt-test",
|
|
177
|
+
"--dir", tempDir,
|
|
178
|
+
"--run", "r1",
|
|
179
|
+
]);
|
|
180
|
+
expect(exitCode).toBe(0);
|
|
181
|
+
const result = JSON.parse(stdout);
|
|
182
|
+
expect(result.action).toBe("report");
|
|
183
|
+
expect(result.path).toContain("report.md");
|
|
184
|
+
expect(existsSync(result.path)).toBe(true);
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
describe("error handling", () => {
|
|
188
|
+
test("outputs JSON error on failure", async () => {
|
|
189
|
+
const input = JSON.stringify({
|
|
190
|
+
action: "report",
|
|
191
|
+
appName: "nonexistent",
|
|
192
|
+
proofDir: tempDir,
|
|
193
|
+
run: "nope",
|
|
194
|
+
});
|
|
195
|
+
const { stderr, exitCode } = await runCli(["--json"], input);
|
|
196
|
+
expect(exitCode).toBe(1);
|
|
197
|
+
expect(JSON.parse(stderr).error).toContain("No proof.json found");
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
});
|
package/dist/detect.d.ts
ADDED
package/dist/detect.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { existsSync } from "fs";
|
|
2
|
+
import { readFile } from "fs/promises";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
const BROWSER_CONFIG_PATTERNS = [
|
|
5
|
+
"playwright.config.ts",
|
|
6
|
+
"playwright.config.js",
|
|
7
|
+
"playwright.config.mjs",
|
|
8
|
+
];
|
|
9
|
+
export async function detectMode(projectDir) {
|
|
10
|
+
for (const config of BROWSER_CONFIG_PATTERNS) {
|
|
11
|
+
if (existsSync(join(projectDir, config))) {
|
|
12
|
+
return "browser";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
const pkgPath = join(projectDir, "package.json");
|
|
16
|
+
if (existsSync(pkgPath)) {
|
|
17
|
+
try {
|
|
18
|
+
const pkg = JSON.parse(await readFile(pkgPath, "utf-8"));
|
|
19
|
+
const allDeps = {
|
|
20
|
+
...pkg.dependencies,
|
|
21
|
+
...pkg.devDependencies,
|
|
22
|
+
...pkg.peerDependencies,
|
|
23
|
+
};
|
|
24
|
+
if (allDeps["@playwright/test"] || allDeps["playwright"]) {
|
|
25
|
+
return "browser";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch { }
|
|
29
|
+
}
|
|
30
|
+
return "terminal";
|
|
31
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import { detectMode } from "./detect";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { mkdtemp, writeFile, rm } from "fs/promises";
|
|
5
|
+
import { tmpdir } from "os";
|
|
6
|
+
describe("detectMode", () => {
|
|
7
|
+
let tempDir;
|
|
8
|
+
async function makeTempDir() {
|
|
9
|
+
tempDir = await mkdtemp(join(tmpdir(), "proof-test-"));
|
|
10
|
+
return tempDir;
|
|
11
|
+
}
|
|
12
|
+
async function cleanup() {
|
|
13
|
+
if (tempDir)
|
|
14
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
15
|
+
}
|
|
16
|
+
test("returns 'browser' when playwright.config.ts exists", async () => {
|
|
17
|
+
const dir = await makeTempDir();
|
|
18
|
+
await writeFile(join(dir, "playwright.config.ts"), "export default {};");
|
|
19
|
+
expect(await detectMode(dir)).toBe("browser");
|
|
20
|
+
await cleanup();
|
|
21
|
+
});
|
|
22
|
+
test("returns 'browser' when playwright.config.js exists", async () => {
|
|
23
|
+
const dir = await makeTempDir();
|
|
24
|
+
await writeFile(join(dir, "playwright.config.js"), "module.exports = {};");
|
|
25
|
+
expect(await detectMode(dir)).toBe("browser");
|
|
26
|
+
await cleanup();
|
|
27
|
+
});
|
|
28
|
+
test("returns 'browser' when @playwright/test is in devDependencies", async () => {
|
|
29
|
+
const dir = await makeTempDir();
|
|
30
|
+
const pkg = { devDependencies: { "@playwright/test": "^1.50.0" } };
|
|
31
|
+
await writeFile(join(dir, "package.json"), JSON.stringify(pkg));
|
|
32
|
+
expect(await detectMode(dir)).toBe("browser");
|
|
33
|
+
await cleanup();
|
|
34
|
+
});
|
|
35
|
+
test("returns 'browser' when playwright is in dependencies", async () => {
|
|
36
|
+
const dir = await makeTempDir();
|
|
37
|
+
const pkg = { dependencies: { playwright: "^1.50.0" } };
|
|
38
|
+
await writeFile(join(dir, "package.json"), JSON.stringify(pkg));
|
|
39
|
+
expect(await detectMode(dir)).toBe("browser");
|
|
40
|
+
await cleanup();
|
|
41
|
+
});
|
|
42
|
+
test("returns 'terminal' when no playwright signals found", async () => {
|
|
43
|
+
const dir = await makeTempDir();
|
|
44
|
+
expect(await detectMode(dir)).toBe("terminal");
|
|
45
|
+
await cleanup();
|
|
46
|
+
});
|
|
47
|
+
test("returns 'terminal' when package.json has no playwright deps", async () => {
|
|
48
|
+
const dir = await makeTempDir();
|
|
49
|
+
const pkg = { dependencies: { express: "^4.0.0" } };
|
|
50
|
+
await writeFile(join(dir, "package.json"), JSON.stringify(pkg));
|
|
51
|
+
expect(await detectMode(dir)).toBe("terminal");
|
|
52
|
+
await cleanup();
|
|
53
|
+
});
|
|
54
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function getVideoDuration(filePath: string): Promise<number>;
|
package/dist/duration.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { spawn } from "child_process";
|
|
2
|
+
export async function getVideoDuration(filePath) {
|
|
3
|
+
try {
|
|
4
|
+
const output = await new Promise((resolve, reject) => {
|
|
5
|
+
const proc = spawn("ffprobe", [
|
|
6
|
+
"-v", "quiet",
|
|
7
|
+
"-print_format", "json",
|
|
8
|
+
"-show_format",
|
|
9
|
+
filePath,
|
|
10
|
+
], { stdio: ["ignore", "pipe", "pipe"] });
|
|
11
|
+
let stdout = "";
|
|
12
|
+
proc.stdout.on("data", (data) => { stdout += data.toString(); });
|
|
13
|
+
proc.on("close", (code) => code === 0 ? resolve(stdout) : reject(new Error("ffprobe failed")));
|
|
14
|
+
proc.on("error", reject);
|
|
15
|
+
});
|
|
16
|
+
const parsed = JSON.parse(output);
|
|
17
|
+
const seconds = parseFloat(parsed.format?.duration ?? "0");
|
|
18
|
+
return Math.round(seconds * 1000);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return 0;
|
|
22
|
+
}
|
|
23
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ProofConfig, Recording, CaptureOptions, RecordingMode, ProofManifest, ProofEntry, ReportFormat, ReportOptions } from "./types";
|
|
2
|
+
export type { ProofConfig, Recording, CaptureOptions, RecordingMode, ProofManifest, ProofEntry, ReportFormat, ReportOptions, };
|
|
3
|
+
export { getCursorHighlightScript } from "./modes/visual";
|
|
4
|
+
export declare class Proof {
|
|
5
|
+
private config;
|
|
6
|
+
private runDir;
|
|
7
|
+
private runName;
|
|
8
|
+
private initTime;
|
|
9
|
+
constructor(config: ProofConfig);
|
|
10
|
+
private formatDate;
|
|
11
|
+
private formatTime;
|
|
12
|
+
private formatTimestamp;
|
|
13
|
+
private ensureRunDir;
|
|
14
|
+
private resolveMode;
|
|
15
|
+
private appendToManifest;
|
|
16
|
+
capture(options: CaptureOptions): Promise<Recording>;
|
|
17
|
+
report(options?: ReportOptions): Promise<string | string[]>;
|
|
18
|
+
}
|