tuffgal 0.1.0-alpha.0 → 0.1.0-alpha.2
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/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +146 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/init.d.ts +17 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/{src/commands/init.ts → dist/commands/init.js} +44 -61
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/supervise.d.ts +40 -0
- package/dist/commands/supervise.d.ts.map +1 -0
- package/dist/commands/supervise.js +205 -0
- package/dist/commands/supervise.js.map +1 -0
- package/dist/config.d.ts +156 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +77 -0
- package/dist/config.js.map +1 -0
- package/dist/coverage/flows.d.ts +19 -0
- package/dist/coverage/flows.d.ts.map +1 -0
- package/dist/coverage/flows.js +77 -0
- package/dist/coverage/flows.js.map +1 -0
- package/dist/coverage/screens.d.ts +15 -0
- package/dist/coverage/screens.d.ts.map +1 -0
- package/dist/coverage/screens.js +43 -0
- package/dist/coverage/screens.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/reporter/template.d.ts +10 -0
- package/dist/reporter/template.d.ts.map +1 -0
- package/dist/reporter/template.js +274 -0
- package/dist/reporter/template.js.map +1 -0
- package/dist/reporter/writeReport.d.ts +8 -0
- package/dist/reporter/writeReport.d.ts.map +1 -0
- package/dist/reporter/writeReport.js +22 -0
- package/dist/reporter/writeReport.js.map +1 -0
- package/dist/runner/approve.d.ts +15 -0
- package/dist/runner/approve.d.ts.map +1 -0
- package/dist/runner/approve.js +41 -0
- package/dist/runner/approve.js.map +1 -0
- package/dist/runner/bridges/database.d.ts +15 -0
- package/dist/runner/bridges/database.d.ts.map +1 -0
- package/dist/runner/bridges/database.js +27 -0
- package/dist/runner/bridges/database.js.map +1 -0
- package/dist/runner/bridges/devServers.d.ts +16 -0
- package/dist/runner/bridges/devServers.d.ts.map +1 -0
- package/dist/runner/bridges/devServers.js +141 -0
- package/dist/runner/bridges/devServers.js.map +1 -0
- package/dist/runner/coverage.d.ts +26 -0
- package/dist/runner/coverage.d.ts.map +1 -0
- package/dist/runner/coverage.js +68 -0
- package/dist/runner/coverage.js.map +1 -0
- package/dist/runner/interpolate.d.ts +13 -0
- package/dist/runner/interpolate.d.ts.map +1 -0
- package/dist/runner/interpolate.js +28 -0
- package/dist/runner/interpolate.js.map +1 -0
- package/dist/runner/resolveLocator.d.ts +22 -0
- package/dist/runner/resolveLocator.d.ts.map +1 -0
- package/dist/runner/resolveLocator.js +41 -0
- package/dist/runner/resolveLocator.js.map +1 -0
- package/dist/runner/run.d.ts +18 -0
- package/dist/runner/run.d.ts.map +1 -0
- package/dist/runner/run.js +122 -0
- package/dist/runner/run.js.map +1 -0
- package/dist/runner/runAction.d.ts +25 -0
- package/dist/runner/runAction.d.ts.map +1 -0
- package/dist/runner/runAction.js +289 -0
- package/dist/runner/runAction.js.map +1 -0
- package/dist/runner/runStory.d.ts +25 -0
- package/dist/runner/runStory.d.ts.map +1 -0
- package/dist/runner/runStory.js +149 -0
- package/dist/runner/runStory.js.map +1 -0
- package/dist/runner/scheduler.d.ts +31 -0
- package/dist/runner/scheduler.d.ts.map +1 -0
- package/dist/runner/scheduler.js +165 -0
- package/dist/runner/scheduler.js.map +1 -0
- package/dist/runner/steps/click.d.ts +4 -0
- package/dist/runner/steps/click.d.ts.map +1 -0
- package/dist/runner/steps/click.js +11 -0
- package/dist/runner/steps/click.js.map +1 -0
- package/dist/runner/steps/input.d.ts +4 -0
- package/dist/runner/steps/input.d.ts.map +1 -0
- package/dist/runner/steps/input.js +11 -0
- package/dist/runner/steps/input.js.map +1 -0
- package/dist/runner/steps/intercept.d.ts +13 -0
- package/dist/runner/steps/intercept.d.ts.map +1 -0
- package/dist/runner/steps/intercept.js +22 -0
- package/dist/runner/steps/intercept.js.map +1 -0
- package/dist/runner/steps/navigate.d.ts +4 -0
- package/dist/runner/steps/navigate.d.ts.map +1 -0
- package/dist/runner/steps/navigate.js +8 -0
- package/dist/runner/steps/navigate.js.map +1 -0
- package/dist/runner/steps/read.d.ts +9 -0
- package/dist/runner/steps/read.d.ts.map +1 -0
- package/dist/runner/steps/read.js +19 -0
- package/dist/runner/steps/read.js.map +1 -0
- package/dist/runner/steps/scroll.d.ts +3 -0
- package/dist/runner/steps/scroll.d.ts.map +1 -0
- package/dist/runner/steps/scroll.js +6 -0
- package/dist/runner/steps/scroll.js.map +1 -0
- package/{src/runner/steps/type.ts → dist/runner/steps/type.d.ts} +2 -4
- package/dist/runner/steps/type.d.ts.map +1 -0
- package/dist/runner/steps/type.js +10 -0
- package/dist/runner/steps/type.js.map +1 -0
- package/dist/runner/steps/wait.d.ts +3 -0
- package/dist/runner/steps/wait.d.ts.map +1 -0
- package/dist/runner/steps/wait.js +4 -0
- package/dist/runner/steps/wait.js.map +1 -0
- package/dist/runner/steps/waitFor.d.ts +4 -0
- package/dist/runner/steps/waitFor.d.ts.map +1 -0
- package/dist/runner/steps/waitFor.js +11 -0
- package/dist/runner/steps/waitFor.js.map +1 -0
- package/dist/schema/action.d.ts +526 -0
- package/dist/schema/action.d.ts.map +1 -0
- package/dist/schema/action.js +168 -0
- package/dist/schema/action.js.map +1 -0
- package/dist/schema/load.d.ts +20 -0
- package/dist/schema/load.d.ts.map +1 -0
- package/dist/schema/load.js +77 -0
- package/dist/schema/load.js.map +1 -0
- package/dist/schema/result.d.ts +79 -0
- package/dist/schema/result.d.ts.map +1 -0
- package/dist/schema/result.js +2 -0
- package/dist/schema/result.js.map +1 -0
- package/dist/schema/story.d.ts +39 -0
- package/dist/schema/story.d.ts.map +1 -0
- package/dist/schema/story.js +52 -0
- package/dist/schema/story.js.map +1 -0
- package/dist/screenshots/baselineStore.d.ts +33 -0
- package/dist/screenshots/baselineStore.d.ts.map +1 -0
- package/dist/screenshots/baselineStore.js +65 -0
- package/dist/screenshots/baselineStore.js.map +1 -0
- package/{src/screenshots/capture.ts → dist/screenshots/capture.d.ts} +2 -12
- package/dist/screenshots/capture.d.ts.map +1 -0
- package/dist/screenshots/capture.js +15 -0
- package/dist/screenshots/capture.js.map +1 -0
- package/dist/screenshots/diff.d.ts +41 -0
- package/dist/screenshots/diff.d.ts.map +1 -0
- package/dist/screenshots/diff.js +52 -0
- package/dist/screenshots/diff.js.map +1 -0
- package/package.json +13 -11
- package/bin/tuffgal.mjs +0 -2
- package/src/.gitkeep +0 -0
- package/src/cli.ts +0 -158
- package/src/commands/supervise.ts +0 -267
- package/src/config.ts +0 -222
- package/src/coverage/flows.ts +0 -90
- package/src/coverage/screens.ts +0 -52
- package/src/index.ts +0 -28
- package/src/reporter/template.ts +0 -355
- package/src/reporter/writeReport.ts +0 -37
- package/src/runner/approve.ts +0 -65
- package/src/runner/bridges/database.ts +0 -34
- package/src/runner/bridges/devServers.ts +0 -174
- package/src/runner/coverage.ts +0 -76
- package/src/runner/interpolate.ts +0 -36
- package/src/runner/resolveLocator.ts +0 -47
- package/src/runner/run.ts +0 -177
- package/src/runner/runAction.ts +0 -422
- package/src/runner/runStory.ts +0 -195
- package/src/runner/scheduler.ts +0 -223
- package/src/runner/steps/click.ts +0 -16
- package/src/runner/steps/input.ts +0 -17
- package/src/runner/steps/intercept.ts +0 -28
- package/src/runner/steps/navigate.ts +0 -14
- package/src/runner/steps/read.ts +0 -20
- package/src/runner/steps/scroll.ts +0 -12
- package/src/runner/steps/wait.ts +0 -5
- package/src/runner/steps/waitFor.ts +0 -16
- package/src/schema/action.ts +0 -176
- package/src/schema/load.ts +0 -94
- package/src/schema/result.ts +0 -83
- package/src/schema/story.ts +0 -58
- package/src/screenshots/baselineStore.ts +0 -114
- package/src/screenshots/diff.ts +0 -101
- /package/{src → dist}/reporter/assets/report.css +0 -0
- /package/{src → dist}/reporter/assets/report.js +0 -0
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { init } from "./commands/init.js";
|
|
3
|
+
import { supervise } from "./commands/supervise.js";
|
|
4
|
+
import { loadConfig } from "./config.js";
|
|
5
|
+
import { approveAll } from "./runner/approve.js";
|
|
6
|
+
import { runAll } from "./runner/run.js";
|
|
7
|
+
function parseArguments(argv) {
|
|
8
|
+
const [command, ...rest] = argv;
|
|
9
|
+
const parsed = {
|
|
10
|
+
command: command === 'run' ||
|
|
11
|
+
command === 'approve' ||
|
|
12
|
+
command === 'init' ||
|
|
13
|
+
command === 'supervise' ||
|
|
14
|
+
command === 'help'
|
|
15
|
+
? command
|
|
16
|
+
: 'help',
|
|
17
|
+
headed: false,
|
|
18
|
+
manageServers: false,
|
|
19
|
+
coverage: false,
|
|
20
|
+
};
|
|
21
|
+
for (let index = 0; index < rest.length; index += 1) {
|
|
22
|
+
const arg = rest[index];
|
|
23
|
+
if (arg === undefined)
|
|
24
|
+
continue;
|
|
25
|
+
if (arg === '--headed') {
|
|
26
|
+
parsed.headed = true;
|
|
27
|
+
}
|
|
28
|
+
else if (arg === '--story') {
|
|
29
|
+
parsed.storyFilter = rest[index + 1];
|
|
30
|
+
index += 1;
|
|
31
|
+
}
|
|
32
|
+
else if (arg.startsWith('--story=')) {
|
|
33
|
+
parsed.storyFilter = arg.slice('--story='.length);
|
|
34
|
+
}
|
|
35
|
+
else if (arg === '--workers') {
|
|
36
|
+
parsed.workers = Number(rest[index + 1]);
|
|
37
|
+
index += 1;
|
|
38
|
+
}
|
|
39
|
+
else if (arg.startsWith('--workers=')) {
|
|
40
|
+
parsed.workers = Number(arg.slice('--workers='.length));
|
|
41
|
+
}
|
|
42
|
+
else if (arg === '--manage-servers') {
|
|
43
|
+
parsed.manageServers = true;
|
|
44
|
+
}
|
|
45
|
+
else if (arg === '--coverage') {
|
|
46
|
+
parsed.coverage = true;
|
|
47
|
+
}
|
|
48
|
+
else if (arg === '--healthcheck-interval') {
|
|
49
|
+
parsed.healthcheckIntervalMs = Number(rest[index + 1]);
|
|
50
|
+
index += 1;
|
|
51
|
+
}
|
|
52
|
+
else if (arg.startsWith('--healthcheck-interval=')) {
|
|
53
|
+
parsed.healthcheckIntervalMs = Number(arg.slice('--healthcheck-interval='.length));
|
|
54
|
+
}
|
|
55
|
+
else if (arg === '--idle-limit') {
|
|
56
|
+
parsed.idleLimitMs = Number(rest[index + 1]);
|
|
57
|
+
index += 1;
|
|
58
|
+
}
|
|
59
|
+
else if (arg.startsWith('--idle-limit=')) {
|
|
60
|
+
parsed.idleLimitMs = Number(arg.slice('--idle-limit='.length));
|
|
61
|
+
}
|
|
62
|
+
else if (arg === '--max-runtime') {
|
|
63
|
+
parsed.maxRuntimeMs = Number(rest[index + 1]);
|
|
64
|
+
index += 1;
|
|
65
|
+
}
|
|
66
|
+
else if (arg.startsWith('--max-runtime=')) {
|
|
67
|
+
parsed.maxRuntimeMs = Number(arg.slice('--max-runtime='.length));
|
|
68
|
+
}
|
|
69
|
+
else if (arg === '--max-respawns') {
|
|
70
|
+
parsed.maxRespawns = Number(rest[index + 1]);
|
|
71
|
+
index += 1;
|
|
72
|
+
}
|
|
73
|
+
else if (arg.startsWith('--max-respawns=')) {
|
|
74
|
+
parsed.maxRespawns = Number(arg.slice('--max-respawns='.length));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return parsed;
|
|
78
|
+
}
|
|
79
|
+
function printHelp() {
|
|
80
|
+
process.stdout.write([
|
|
81
|
+
'Usage: tuffgal <command> [options]',
|
|
82
|
+
'',
|
|
83
|
+
'Commands:',
|
|
84
|
+
' run Run every story under the configured stories directory.',
|
|
85
|
+
' approve Promote every "changed" actual to its baseline.',
|
|
86
|
+
' init Scaffold a tuffgal.config.ts in the current directory.',
|
|
87
|
+
' supervise Long-running wrapper around devServers.command with',
|
|
88
|
+
' healthcheck restart, idle auto-term, and wall-clock cap.',
|
|
89
|
+
' help Show this message.',
|
|
90
|
+
'',
|
|
91
|
+
'Options:',
|
|
92
|
+
' --story <name> Filter to a single story (filename or story text).',
|
|
93
|
+
' --headed Show the browser while running.',
|
|
94
|
+
' --workers N Override the worker pool size (default min(cpus/2, 4)).',
|
|
95
|
+
' --manage-servers Spawn devServers.command, wait, run, then kill it.',
|
|
96
|
+
' --coverage Capture V8 JS + CSS coverage and emit a monocart report.',
|
|
97
|
+
'',
|
|
98
|
+
'Supervise options:',
|
|
99
|
+
' --healthcheck-interval N Probe interval in ms (default 30_000).',
|
|
100
|
+
' --idle-limit N Ms with no `tuffgal run` heartbeat before exit (default 600_000).',
|
|
101
|
+
' --max-runtime N Wall-clock cap in ms (default 3_600_000).',
|
|
102
|
+
' --max-respawns N Respawn budget after unhealthy/exit (default 3).',
|
|
103
|
+
].join('\n') + '\n');
|
|
104
|
+
}
|
|
105
|
+
async function main() {
|
|
106
|
+
const args = parseArguments(process.argv.slice(2));
|
|
107
|
+
if (args.command === 'help') {
|
|
108
|
+
printHelp();
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (args.command === 'init') {
|
|
112
|
+
await init({ cwd: process.cwd() });
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const config = await loadConfig(process.cwd());
|
|
116
|
+
if (args.command === 'run') {
|
|
117
|
+
const result = await runAll(config, {
|
|
118
|
+
storyFilter: args.storyFilter,
|
|
119
|
+
headed: args.headed,
|
|
120
|
+
workers: args.workers,
|
|
121
|
+
manageServers: args.manageServers,
|
|
122
|
+
coverage: args.coverage,
|
|
123
|
+
});
|
|
124
|
+
process.stdout.write(`\nTotals: ${result.totals.passed} pass · ${result.totals.changed} changed · ${result.totals.failed} failed\n`);
|
|
125
|
+
process.exit(result.totals.failed > 0 ? 1 : 0);
|
|
126
|
+
}
|
|
127
|
+
if (args.command === 'approve') {
|
|
128
|
+
const summary = await approveAll(config, {
|
|
129
|
+
storyFilter: args.storyFilter,
|
|
130
|
+
});
|
|
131
|
+
process.stdout.write(`\nApproved ${summary.approved} baselines; skipped ${summary.skipped} actions.\n`);
|
|
132
|
+
}
|
|
133
|
+
if (args.command === 'supervise') {
|
|
134
|
+
await supervise(config, {
|
|
135
|
+
healthcheckIntervalMs: args.healthcheckIntervalMs,
|
|
136
|
+
idleLimitMs: args.idleLimitMs,
|
|
137
|
+
maxRuntimeMs: args.maxRuntimeMs,
|
|
138
|
+
maxRespawns: args.maxRespawns,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
main().catch((error) => {
|
|
143
|
+
process.stderr.write(`tuffgal error: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
144
|
+
process.exit(1);
|
|
145
|
+
});
|
|
146
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface InitOptions {
|
|
2
|
+
cwd: string;
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* Scaffolds a starter Tuffgal layout in the given directory:
|
|
6
|
+
* - tuffgal.config.ts (commented template)
|
|
7
|
+
* - tuffgal/actions/.gitkeep
|
|
8
|
+
* - tuffgal/stories/.gitkeep
|
|
9
|
+
* - tuffgal/baselines/.gitkeep
|
|
10
|
+
* - tuffgal/.gitignore (ignores .auth/ and report/)
|
|
11
|
+
*
|
|
12
|
+
* Refuses to overwrite an existing tuffgal.config.{ts,js}. Existing
|
|
13
|
+
* scaffolded directories are left alone — a second \`init\` is safe to run
|
|
14
|
+
* after manually deleting just the config.
|
|
15
|
+
*/
|
|
16
|
+
export declare function init(options: InitOptions): Promise<void>;
|
|
17
|
+
//# sourceMappingURL=init.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAuEA,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CA2C9D"}
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { mkdir, writeFile, access } from 'node:fs/promises';
|
|
2
2
|
import { join, relative } from 'node:path';
|
|
3
|
-
|
|
4
3
|
const CONFIG_FILES = ['tuffgal.config.ts', 'tuffgal.config.js'];
|
|
5
|
-
|
|
6
4
|
const CONFIG_TEMPLATE = `import { defineConfig } from 'tuffgal';
|
|
7
5
|
|
|
8
6
|
/**
|
|
@@ -57,22 +55,15 @@ export default defineConfig({
|
|
|
57
55
|
// },
|
|
58
56
|
});
|
|
59
57
|
`;
|
|
60
|
-
|
|
61
58
|
const GITKEEP_SUBDIRECTORIES = [
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
59
|
+
'tuffgal/actions',
|
|
60
|
+
'tuffgal/stories',
|
|
61
|
+
'tuffgal/baselines',
|
|
65
62
|
];
|
|
66
|
-
|
|
67
63
|
const REPORT_GITIGNORE = `# Generated by Tuffgal — do not commit.
|
|
68
64
|
.auth/
|
|
69
65
|
report/
|
|
70
66
|
`;
|
|
71
|
-
|
|
72
|
-
export interface InitOptions {
|
|
73
|
-
cwd: string;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
67
|
/**
|
|
77
68
|
* Scaffolds a starter Tuffgal layout in the given directory:
|
|
78
69
|
* - tuffgal.config.ts (commented template)
|
|
@@ -85,56 +76,48 @@ export interface InitOptions {
|
|
|
85
76
|
* scaffolded directories are left alone — a second \`init\` is safe to run
|
|
86
77
|
* after manually deleting just the config.
|
|
87
78
|
*/
|
|
88
|
-
export async function init(options
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
`Delete it manually if you want to start over.`,
|
|
96
|
-
);
|
|
79
|
+
export async function init(options) {
|
|
80
|
+
const { cwd } = options;
|
|
81
|
+
for (const candidate of CONFIG_FILES) {
|
|
82
|
+
if (await pathExists(join(cwd, candidate))) {
|
|
83
|
+
throw new Error(`${candidate} already exists at ${cwd}. Refusing to overwrite. ` +
|
|
84
|
+
`Delete it manually if you want to start over.`);
|
|
85
|
+
}
|
|
97
86
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
await writeFile(gitkeep, '', 'utf8');
|
|
110
|
-
process.stdout.write(`Wrote ${relative(cwd, gitkeep)}\n`);
|
|
87
|
+
const configPath = join(cwd, 'tuffgal.config.ts');
|
|
88
|
+
await writeFile(configPath, CONFIG_TEMPLATE, 'utf8');
|
|
89
|
+
process.stdout.write(`Wrote ${relative(cwd, configPath) || 'tuffgal.config.ts'}\n`);
|
|
90
|
+
for (const subdirectory of GITKEEP_SUBDIRECTORIES) {
|
|
91
|
+
const absolute = join(cwd, subdirectory);
|
|
92
|
+
await mkdir(absolute, { recursive: true });
|
|
93
|
+
const gitkeep = join(absolute, '.gitkeep');
|
|
94
|
+
if (!(await pathExists(gitkeep))) {
|
|
95
|
+
await writeFile(gitkeep, '', 'utf8');
|
|
96
|
+
process.stdout.write(`Wrote ${relative(cwd, gitkeep)}\n`);
|
|
97
|
+
}
|
|
111
98
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
process.stdout.write(
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
' 3. Write your first story under tuffgal/stories/.',
|
|
127
|
-
' 4. Run `npx tuffgal run` to capture baselines.',
|
|
128
|
-
'',
|
|
129
|
-
].join('\n'),
|
|
130
|
-
);
|
|
99
|
+
const gitignorePath = join(cwd, 'tuffgal', '.gitignore');
|
|
100
|
+
if (!(await pathExists(gitignorePath))) {
|
|
101
|
+
await writeFile(gitignorePath, REPORT_GITIGNORE, 'utf8');
|
|
102
|
+
process.stdout.write(`Wrote ${relative(cwd, gitignorePath)}\n`);
|
|
103
|
+
}
|
|
104
|
+
process.stdout.write([
|
|
105
|
+
'',
|
|
106
|
+
'Tuffgal scaffold ready. Next steps:',
|
|
107
|
+
' 1. Edit tuffgal.config.ts to point at your dev server.',
|
|
108
|
+
' 2. Write your first action under tuffgal/actions/.',
|
|
109
|
+
' 3. Write your first story under tuffgal/stories/.',
|
|
110
|
+
' 4. Run `npx tuffgal run` to capture baselines.',
|
|
111
|
+
'',
|
|
112
|
+
].join('\n'));
|
|
131
113
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
114
|
+
async function pathExists(absolute) {
|
|
115
|
+
try {
|
|
116
|
+
await access(absolute);
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
140
122
|
}
|
|
123
|
+
//# sourceMappingURL=init.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAE3C,MAAM,YAAY,GAAG,CAAC,mBAAmB,EAAE,mBAAmB,CAAC,CAAC;AAEhE,MAAM,eAAe,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqDvB,CAAC;AAEF,MAAM,sBAAsB,GAAG;IAC7B,iBAAiB;IACjB,iBAAiB;IACjB,mBAAmB;CACpB,CAAC;AAEF,MAAM,gBAAgB,GAAG;;;CAGxB,CAAC;AAMF;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,OAAoB;IAC7C,MAAM,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC;IAExB,KAAK,MAAM,SAAS,IAAI,YAAY,EAAE,CAAC;QACrC,IAAI,MAAM,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC,EAAE,CAAC;YAC3C,MAAM,IAAI,KAAK,CACb,GAAG,SAAS,sBAAsB,GAAG,2BAA2B;gBAC9D,+CAA+C,CAClD,CAAC;QACJ,CAAC;IACH,CAAC;IAED,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,mBAAmB,CAAC,CAAC;IAClD,MAAM,SAAS,CAAC,UAAU,EAAE,eAAe,EAAE,MAAM,CAAC,CAAC;IACrD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,QAAQ,CAAC,GAAG,EAAE,UAAU,CAAC,IAAI,mBAAmB,IAAI,CAAC,CAAC;IAEpF,KAAK,MAAM,YAAY,IAAI,sBAAsB,EAAE,CAAC;QAClD,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;QACzC,MAAM,KAAK,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3C,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;QAC3C,IAAI,CAAC,CAAC,MAAM,UAAU,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC;YACjC,MAAM,SAAS,CAAC,OAAO,EAAE,EAAE,EAAE,MAAM,CAAC,CAAC;YACrC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;QAC5D,CAAC;IACH,CAAC;IAED,MAAM,aAAa,GAAG,IAAI,CAAC,GAAG,EAAE,SAAS,EAAE,YAAY,CAAC,CAAC;IACzD,IAAI,CAAC,CAAC,MAAM,UAAU,CAAC,aAAa,CAAC,CAAC,EAAE,CAAC;QACvC,MAAM,SAAS,CAAC,aAAa,EAAE,gBAAgB,EAAE,MAAM,CAAC,CAAC;QACzD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,QAAQ,CAAC,GAAG,EAAE,aAAa,CAAC,IAAI,CAAC,CAAC;IAClE,CAAC;IAED,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB;QACE,EAAE;QACF,qCAAqC;QACrC,0DAA0D;QAC1D,sDAAsD;QACtD,qDAAqD;QACrD,kDAAkD;QAClD,EAAE;KACH,CAAC,IAAI,CAAC,IAAI,CAAC,CACb,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,UAAU,CAAC,QAAgB;IACxC,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,QAAQ,CAAC,CAAC;QACvB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { ResolvedConfig } from '../config.ts';
|
|
2
|
+
export interface SuperviseOptions {
|
|
3
|
+
/** Milliseconds between port + heartbeat probes. Default 30_000. */
|
|
4
|
+
healthcheckIntervalMs?: number;
|
|
5
|
+
/**
|
|
6
|
+
* Window with no `tuffgal run` heartbeat before the supervisor self-
|
|
7
|
+
* terminates. Default 10 minutes. The heartbeat file lives at
|
|
8
|
+
* `config.paths.report/.heartbeat` and is touched by every `tuffgal
|
|
9
|
+
* run` invocation.
|
|
10
|
+
*/
|
|
11
|
+
idleLimitMs?: number;
|
|
12
|
+
/** Wall-clock cap, end-to-end. Default 60 minutes. */
|
|
13
|
+
maxRuntimeMs?: number;
|
|
14
|
+
/**
|
|
15
|
+
* How many times the supervisor may respawn `devServers.command` after
|
|
16
|
+
* an unhealthy probe or unexpected exit before giving up. Default 3.
|
|
17
|
+
*/
|
|
18
|
+
maxRespawns?: number;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Long-running supervisor around `config.devServers.command`. Solves
|
|
22
|
+
* three problems observed during heavy harness iteration:
|
|
23
|
+
*
|
|
24
|
+
* 1. Hot-reload rot — after many file-watch restarts the framework
|
|
25
|
+
* drifts into a broken state. Supervisor probes every declared
|
|
26
|
+
* healthcheck URL and restarts the whole tree on failure.
|
|
27
|
+
* 2. Forgotten dev servers — supervisor self-terminates after a
|
|
28
|
+
* wall-clock cap and after an idle window with no heartbeat from
|
|
29
|
+
* `tuffgal run`.
|
|
30
|
+
* 3. Manual signal management — SIGINT/SIGTERM teardown kills the
|
|
31
|
+
* whole detached process group, not just the shell wrapper.
|
|
32
|
+
*
|
|
33
|
+
* Each invocation of `tuffgal run` writes
|
|
34
|
+
* `config.paths.report/.heartbeat` (ISO timestamp). Supervisor reads
|
|
35
|
+
* its mtime every healthcheck. If `Date.now() - mtime > idleLimitMs`,
|
|
36
|
+
* supervisor shuts down. Missing heartbeat file is treated as "no runs
|
|
37
|
+
* yet", not stale, so a fresh shell does not self-terminate immediately.
|
|
38
|
+
*/
|
|
39
|
+
export declare function supervise(config: ResolvedConfig, options?: SuperviseOptions): Promise<void>;
|
|
40
|
+
//# sourceMappingURL=supervise.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"supervise.d.ts","sourceRoot":"","sources":["../../src/commands/supervise.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAmB,MAAM,cAAc,CAAC;AAWpE,MAAM,WAAW,gBAAgB;IAC/B,oEAAoE;IACpE,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sDAAsD;IACtD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,SAAS,CAC7B,MAAM,EAAE,cAAc,EACtB,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAuGf"}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { createWriteStream, mkdirSync, statSync } from 'node:fs';
|
|
3
|
+
import { createConnection } from 'node:net';
|
|
4
|
+
import { join, resolve } from 'node:path';
|
|
5
|
+
const HEARTBEAT_FILE = '.heartbeat';
|
|
6
|
+
const SIGTERM_GRACE_MS = 5_000;
|
|
7
|
+
const HEALTHCHECK_PROBE_TIMEOUT_MS = 2_000;
|
|
8
|
+
const DEFAULT_HEALTHCHECK_INTERVAL_MS = 30_000;
|
|
9
|
+
const DEFAULT_IDLE_LIMIT_MS = 10 * 60_000;
|
|
10
|
+
const DEFAULT_MAX_RUNTIME_MS = 60 * 60_000;
|
|
11
|
+
const DEFAULT_MAX_RESPAWNS = 3;
|
|
12
|
+
/**
|
|
13
|
+
* Long-running supervisor around `config.devServers.command`. Solves
|
|
14
|
+
* three problems observed during heavy harness iteration:
|
|
15
|
+
*
|
|
16
|
+
* 1. Hot-reload rot — after many file-watch restarts the framework
|
|
17
|
+
* drifts into a broken state. Supervisor probes every declared
|
|
18
|
+
* healthcheck URL and restarts the whole tree on failure.
|
|
19
|
+
* 2. Forgotten dev servers — supervisor self-terminates after a
|
|
20
|
+
* wall-clock cap and after an idle window with no heartbeat from
|
|
21
|
+
* `tuffgal run`.
|
|
22
|
+
* 3. Manual signal management — SIGINT/SIGTERM teardown kills the
|
|
23
|
+
* whole detached process group, not just the shell wrapper.
|
|
24
|
+
*
|
|
25
|
+
* Each invocation of `tuffgal run` writes
|
|
26
|
+
* `config.paths.report/.heartbeat` (ISO timestamp). Supervisor reads
|
|
27
|
+
* its mtime every healthcheck. If `Date.now() - mtime > idleLimitMs`,
|
|
28
|
+
* supervisor shuts down. Missing heartbeat file is treated as "no runs
|
|
29
|
+
* yet", not stale, so a fresh shell does not self-terminate immediately.
|
|
30
|
+
*/
|
|
31
|
+
export async function supervise(config, options = {}) {
|
|
32
|
+
if (!config.devServers) {
|
|
33
|
+
throw new Error('tuffgal supervise requires a `devServers` block in tuffgal.config.ts. ' +
|
|
34
|
+
'Declare `devServers: { command, healthCheck: [...] }` and try again.');
|
|
35
|
+
}
|
|
36
|
+
const healthcheckIntervalMs = options.healthcheckIntervalMs ??
|
|
37
|
+
numberFromEnv('TUFFGAL_HEALTHCHECK_INTERVAL_MS', DEFAULT_HEALTHCHECK_INTERVAL_MS);
|
|
38
|
+
const idleLimitMs = options.idleLimitMs ??
|
|
39
|
+
numberFromEnv('TUFFGAL_IDLE_LIMIT_MS', DEFAULT_IDLE_LIMIT_MS);
|
|
40
|
+
const maxRuntimeMs = options.maxRuntimeMs ??
|
|
41
|
+
numberFromEnv('TUFFGAL_MAX_RUNTIME_MS', DEFAULT_MAX_RUNTIME_MS);
|
|
42
|
+
const maxRespawns = options.maxRespawns ??
|
|
43
|
+
numberFromEnv('TUFFGAL_MAX_RESPAWNS', DEFAULT_MAX_RESPAWNS);
|
|
44
|
+
mkdirSync(config.paths.report, { recursive: true });
|
|
45
|
+
const logPath = join(config.paths.report, 'dev-servers.log');
|
|
46
|
+
const heartbeatPath = join(config.paths.report, HEARTBEAT_FILE);
|
|
47
|
+
process.stdout.write([
|
|
48
|
+
'Starting tuffgal supervisor.',
|
|
49
|
+
` Command: ${config.devServers.command}`,
|
|
50
|
+
` Healthcheck: ${config.devServers.healthCheck.map((entry) => entry.url).join(', ')}`,
|
|
51
|
+
` Log: ${logPath}`,
|
|
52
|
+
` Heartbeat: ${heartbeatPath}`,
|
|
53
|
+
` Interval: ${healthcheckIntervalMs}ms`,
|
|
54
|
+
` Idle limit: ${idleLimitMs}ms`,
|
|
55
|
+
` Max runtime: ${maxRuntimeMs}ms`,
|
|
56
|
+
` Max respawns: ${maxRespawns}`,
|
|
57
|
+
'',
|
|
58
|
+
].join('\n'));
|
|
59
|
+
const startedAt = Date.now();
|
|
60
|
+
let respawns = 0;
|
|
61
|
+
let child = spawnDevServers(config.devServers, config.rootDir, logPath);
|
|
62
|
+
let stopped = false;
|
|
63
|
+
const teardown = async (reason) => {
|
|
64
|
+
if (stopped)
|
|
65
|
+
return;
|
|
66
|
+
stopped = true;
|
|
67
|
+
process.stdout.write(`Supervisor stopping: ${reason}\n`);
|
|
68
|
+
await stopChild(child, config.devServers);
|
|
69
|
+
process.exit(0);
|
|
70
|
+
};
|
|
71
|
+
process.on('SIGINT', () => void teardown('SIGINT received'));
|
|
72
|
+
process.on('SIGTERM', () => void teardown('SIGTERM received'));
|
|
73
|
+
while (!stopped) {
|
|
74
|
+
await sleep(healthcheckIntervalMs);
|
|
75
|
+
if (stopped)
|
|
76
|
+
break;
|
|
77
|
+
if (Date.now() - startedAt > maxRuntimeMs) {
|
|
78
|
+
await teardown('wall-clock cap reached');
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (heartbeatIsStale(heartbeatPath, idleLimitMs)) {
|
|
82
|
+
await teardown(`no tuffgal run activity in ${idleLimitMs}ms`);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (child.exitCode !== null) {
|
|
86
|
+
respawns += 1;
|
|
87
|
+
if (respawns > maxRespawns) {
|
|
88
|
+
await teardown(`dev servers exited and respawn budget exhausted (${maxRespawns})`);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
process.stdout.write(`Dev servers exited; respawning (${respawns}/${maxRespawns}).\n`);
|
|
92
|
+
child = spawnDevServers(config.devServers, config.rootDir, logPath);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
const healthy = await probeAllHealthchecks(config.devServers);
|
|
96
|
+
if (!healthy) {
|
|
97
|
+
respawns += 1;
|
|
98
|
+
if (respawns > maxRespawns) {
|
|
99
|
+
await teardown(`unhealthy ports and respawn budget exhausted (${maxRespawns})`);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
process.stdout.write(`Healthcheck failed; killing + respawning (${respawns}/${maxRespawns}).\n`);
|
|
103
|
+
await stopChild(child, config.devServers);
|
|
104
|
+
child = spawnDevServers(config.devServers, config.rootDir, logPath);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function spawnDevServers(devServers, rootDir, logPath) {
|
|
109
|
+
const stream = createWriteStream(logPath, { flags: 'a' });
|
|
110
|
+
stream.write(`\n--- dev servers spawned @ ${new Date().toISOString()} ---\n`);
|
|
111
|
+
const cwd = devServers.cwd ? resolve(rootDir, devServers.cwd) : rootDir;
|
|
112
|
+
const child = spawn('sh', ['-c', devServers.command], {
|
|
113
|
+
cwd,
|
|
114
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
115
|
+
detached: true,
|
|
116
|
+
});
|
|
117
|
+
if (child.stdout)
|
|
118
|
+
child.stdout.pipe(stream);
|
|
119
|
+
if (child.stderr)
|
|
120
|
+
child.stderr.pipe(stream);
|
|
121
|
+
return child;
|
|
122
|
+
}
|
|
123
|
+
async function probeAllHealthchecks(devServers) {
|
|
124
|
+
const results = await Promise.all(devServers.healthCheck.map((entry) => probeUrl(entry.url)));
|
|
125
|
+
return results.every((result) => result);
|
|
126
|
+
}
|
|
127
|
+
function probeUrl(url) {
|
|
128
|
+
const parsed = new URL(url);
|
|
129
|
+
const port = parsed.port
|
|
130
|
+
? Number(parsed.port)
|
|
131
|
+
: parsed.protocol === 'https:'
|
|
132
|
+
? 443
|
|
133
|
+
: 80;
|
|
134
|
+
const host = parsed.hostname;
|
|
135
|
+
return new Promise((resolveProbe) => {
|
|
136
|
+
const socket = createConnection({ port, host });
|
|
137
|
+
const cleanup = (result) => {
|
|
138
|
+
socket.removeAllListeners();
|
|
139
|
+
socket.destroy();
|
|
140
|
+
resolveProbe(result);
|
|
141
|
+
};
|
|
142
|
+
socket.once('connect', () => cleanup(true));
|
|
143
|
+
socket.once('error', () => cleanup(false));
|
|
144
|
+
socket.once('timeout', () => cleanup(false));
|
|
145
|
+
socket.setTimeout(HEALTHCHECK_PROBE_TIMEOUT_MS);
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
function heartbeatIsStale(heartbeatPath, idleLimitMs) {
|
|
149
|
+
try {
|
|
150
|
+
const stat = statSync(heartbeatPath);
|
|
151
|
+
return Date.now() - stat.mtimeMs > idleLimitMs;
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
// No heartbeat yet — count grace period from supervisor start so a
|
|
155
|
+
// fresh shell does not immediately kill itself.
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
async function stopChild(child, devServers) {
|
|
160
|
+
if (!child.pid || child.exitCode !== null)
|
|
161
|
+
return;
|
|
162
|
+
const pid = child.pid;
|
|
163
|
+
const signal = devServers?.shutdownSignal ?? 'SIGTERM';
|
|
164
|
+
const graceMs = devServers?.shutdownGraceMs ?? SIGTERM_GRACE_MS;
|
|
165
|
+
await new Promise((resolveOuter) => {
|
|
166
|
+
let done = false;
|
|
167
|
+
const finish = () => {
|
|
168
|
+
if (done)
|
|
169
|
+
return;
|
|
170
|
+
done = true;
|
|
171
|
+
resolveOuter();
|
|
172
|
+
};
|
|
173
|
+
child.once('exit', finish);
|
|
174
|
+
try {
|
|
175
|
+
process.kill(-pid, signal);
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
finish();
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
setTimeout(() => {
|
|
182
|
+
if (child.exitCode === null) {
|
|
183
|
+
try {
|
|
184
|
+
process.kill(-pid, 'SIGKILL');
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
// Already gone.
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}, graceMs);
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
function numberFromEnv(name, fallback) {
|
|
194
|
+
const raw = process.env[name];
|
|
195
|
+
if (!raw)
|
|
196
|
+
return fallback;
|
|
197
|
+
const parsed = Number(raw);
|
|
198
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
199
|
+
}
|
|
200
|
+
function sleep(ms) {
|
|
201
|
+
return new Promise((resolveSleep) => {
|
|
202
|
+
setTimeout(resolveSleep, ms);
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
//# sourceMappingURL=supervise.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"supervise.js","sourceRoot":"","sources":["../../src/commands/supervise.ts"],"names":[],"mappings":"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"}
|