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.
Files changed (177) hide show
  1. package/dist/cli.d.ts +3 -0
  2. package/dist/cli.d.ts.map +1 -0
  3. package/dist/cli.js +146 -0
  4. package/dist/cli.js.map +1 -0
  5. package/dist/commands/init.d.ts +17 -0
  6. package/dist/commands/init.d.ts.map +1 -0
  7. package/{src/commands/init.ts → dist/commands/init.js} +44 -61
  8. package/dist/commands/init.js.map +1 -0
  9. package/dist/commands/supervise.d.ts +40 -0
  10. package/dist/commands/supervise.d.ts.map +1 -0
  11. package/dist/commands/supervise.js +205 -0
  12. package/dist/commands/supervise.js.map +1 -0
  13. package/dist/config.d.ts +156 -0
  14. package/dist/config.d.ts.map +1 -0
  15. package/dist/config.js +77 -0
  16. package/dist/config.js.map +1 -0
  17. package/dist/coverage/flows.d.ts +19 -0
  18. package/dist/coverage/flows.d.ts.map +1 -0
  19. package/dist/coverage/flows.js +77 -0
  20. package/dist/coverage/flows.js.map +1 -0
  21. package/dist/coverage/screens.d.ts +15 -0
  22. package/dist/coverage/screens.d.ts.map +1 -0
  23. package/dist/coverage/screens.js +43 -0
  24. package/dist/coverage/screens.js.map +1 -0
  25. package/dist/index.d.ts +9 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +8 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/reporter/template.d.ts +10 -0
  30. package/dist/reporter/template.d.ts.map +1 -0
  31. package/dist/reporter/template.js +274 -0
  32. package/dist/reporter/template.js.map +1 -0
  33. package/dist/reporter/writeReport.d.ts +8 -0
  34. package/dist/reporter/writeReport.d.ts.map +1 -0
  35. package/dist/reporter/writeReport.js +22 -0
  36. package/dist/reporter/writeReport.js.map +1 -0
  37. package/dist/runner/approve.d.ts +15 -0
  38. package/dist/runner/approve.d.ts.map +1 -0
  39. package/dist/runner/approve.js +41 -0
  40. package/dist/runner/approve.js.map +1 -0
  41. package/dist/runner/bridges/database.d.ts +15 -0
  42. package/dist/runner/bridges/database.d.ts.map +1 -0
  43. package/dist/runner/bridges/database.js +27 -0
  44. package/dist/runner/bridges/database.js.map +1 -0
  45. package/dist/runner/bridges/devServers.d.ts +16 -0
  46. package/dist/runner/bridges/devServers.d.ts.map +1 -0
  47. package/dist/runner/bridges/devServers.js +141 -0
  48. package/dist/runner/bridges/devServers.js.map +1 -0
  49. package/dist/runner/coverage.d.ts +26 -0
  50. package/dist/runner/coverage.d.ts.map +1 -0
  51. package/dist/runner/coverage.js +68 -0
  52. package/dist/runner/coverage.js.map +1 -0
  53. package/dist/runner/interpolate.d.ts +13 -0
  54. package/dist/runner/interpolate.d.ts.map +1 -0
  55. package/dist/runner/interpolate.js +28 -0
  56. package/dist/runner/interpolate.js.map +1 -0
  57. package/dist/runner/resolveLocator.d.ts +22 -0
  58. package/dist/runner/resolveLocator.d.ts.map +1 -0
  59. package/dist/runner/resolveLocator.js +41 -0
  60. package/dist/runner/resolveLocator.js.map +1 -0
  61. package/dist/runner/run.d.ts +18 -0
  62. package/dist/runner/run.d.ts.map +1 -0
  63. package/dist/runner/run.js +122 -0
  64. package/dist/runner/run.js.map +1 -0
  65. package/dist/runner/runAction.d.ts +25 -0
  66. package/dist/runner/runAction.d.ts.map +1 -0
  67. package/dist/runner/runAction.js +289 -0
  68. package/dist/runner/runAction.js.map +1 -0
  69. package/dist/runner/runStory.d.ts +25 -0
  70. package/dist/runner/runStory.d.ts.map +1 -0
  71. package/dist/runner/runStory.js +149 -0
  72. package/dist/runner/runStory.js.map +1 -0
  73. package/dist/runner/scheduler.d.ts +31 -0
  74. package/dist/runner/scheduler.d.ts.map +1 -0
  75. package/dist/runner/scheduler.js +165 -0
  76. package/dist/runner/scheduler.js.map +1 -0
  77. package/dist/runner/steps/click.d.ts +4 -0
  78. package/dist/runner/steps/click.d.ts.map +1 -0
  79. package/dist/runner/steps/click.js +11 -0
  80. package/dist/runner/steps/click.js.map +1 -0
  81. package/dist/runner/steps/input.d.ts +4 -0
  82. package/dist/runner/steps/input.d.ts.map +1 -0
  83. package/dist/runner/steps/input.js +11 -0
  84. package/dist/runner/steps/input.js.map +1 -0
  85. package/dist/runner/steps/intercept.d.ts +13 -0
  86. package/dist/runner/steps/intercept.d.ts.map +1 -0
  87. package/dist/runner/steps/intercept.js +22 -0
  88. package/dist/runner/steps/intercept.js.map +1 -0
  89. package/dist/runner/steps/navigate.d.ts +4 -0
  90. package/dist/runner/steps/navigate.d.ts.map +1 -0
  91. package/dist/runner/steps/navigate.js +8 -0
  92. package/dist/runner/steps/navigate.js.map +1 -0
  93. package/dist/runner/steps/read.d.ts +9 -0
  94. package/dist/runner/steps/read.d.ts.map +1 -0
  95. package/dist/runner/steps/read.js +19 -0
  96. package/dist/runner/steps/read.js.map +1 -0
  97. package/dist/runner/steps/scroll.d.ts +3 -0
  98. package/dist/runner/steps/scroll.d.ts.map +1 -0
  99. package/dist/runner/steps/scroll.js +6 -0
  100. package/dist/runner/steps/scroll.js.map +1 -0
  101. package/{src/runner/steps/type.ts → dist/runner/steps/type.d.ts} +2 -4
  102. package/dist/runner/steps/type.d.ts.map +1 -0
  103. package/dist/runner/steps/type.js +10 -0
  104. package/dist/runner/steps/type.js.map +1 -0
  105. package/dist/runner/steps/wait.d.ts +3 -0
  106. package/dist/runner/steps/wait.d.ts.map +1 -0
  107. package/dist/runner/steps/wait.js +4 -0
  108. package/dist/runner/steps/wait.js.map +1 -0
  109. package/dist/runner/steps/waitFor.d.ts +4 -0
  110. package/dist/runner/steps/waitFor.d.ts.map +1 -0
  111. package/dist/runner/steps/waitFor.js +11 -0
  112. package/dist/runner/steps/waitFor.js.map +1 -0
  113. package/dist/schema/action.d.ts +526 -0
  114. package/dist/schema/action.d.ts.map +1 -0
  115. package/dist/schema/action.js +168 -0
  116. package/dist/schema/action.js.map +1 -0
  117. package/dist/schema/load.d.ts +20 -0
  118. package/dist/schema/load.d.ts.map +1 -0
  119. package/dist/schema/load.js +77 -0
  120. package/dist/schema/load.js.map +1 -0
  121. package/dist/schema/result.d.ts +79 -0
  122. package/dist/schema/result.d.ts.map +1 -0
  123. package/dist/schema/result.js +2 -0
  124. package/dist/schema/result.js.map +1 -0
  125. package/dist/schema/story.d.ts +39 -0
  126. package/dist/schema/story.d.ts.map +1 -0
  127. package/dist/schema/story.js +52 -0
  128. package/dist/schema/story.js.map +1 -0
  129. package/dist/screenshots/baselineStore.d.ts +33 -0
  130. package/dist/screenshots/baselineStore.d.ts.map +1 -0
  131. package/dist/screenshots/baselineStore.js +65 -0
  132. package/dist/screenshots/baselineStore.js.map +1 -0
  133. package/{src/screenshots/capture.ts → dist/screenshots/capture.d.ts} +2 -12
  134. package/dist/screenshots/capture.d.ts.map +1 -0
  135. package/dist/screenshots/capture.js +15 -0
  136. package/dist/screenshots/capture.js.map +1 -0
  137. package/dist/screenshots/diff.d.ts +41 -0
  138. package/dist/screenshots/diff.d.ts.map +1 -0
  139. package/dist/screenshots/diff.js +52 -0
  140. package/dist/screenshots/diff.js.map +1 -0
  141. package/package.json +13 -11
  142. package/bin/tuffgal.mjs +0 -2
  143. package/src/.gitkeep +0 -0
  144. package/src/cli.ts +0 -158
  145. package/src/commands/supervise.ts +0 -267
  146. package/src/config.ts +0 -222
  147. package/src/coverage/flows.ts +0 -90
  148. package/src/coverage/screens.ts +0 -52
  149. package/src/index.ts +0 -28
  150. package/src/reporter/template.ts +0 -355
  151. package/src/reporter/writeReport.ts +0 -37
  152. package/src/runner/approve.ts +0 -65
  153. package/src/runner/bridges/database.ts +0 -34
  154. package/src/runner/bridges/devServers.ts +0 -174
  155. package/src/runner/coverage.ts +0 -76
  156. package/src/runner/interpolate.ts +0 -36
  157. package/src/runner/resolveLocator.ts +0 -47
  158. package/src/runner/run.ts +0 -177
  159. package/src/runner/runAction.ts +0 -422
  160. package/src/runner/runStory.ts +0 -195
  161. package/src/runner/scheduler.ts +0 -223
  162. package/src/runner/steps/click.ts +0 -16
  163. package/src/runner/steps/input.ts +0 -17
  164. package/src/runner/steps/intercept.ts +0 -28
  165. package/src/runner/steps/navigate.ts +0 -14
  166. package/src/runner/steps/read.ts +0 -20
  167. package/src/runner/steps/scroll.ts +0 -12
  168. package/src/runner/steps/wait.ts +0 -5
  169. package/src/runner/steps/waitFor.ts +0 -16
  170. package/src/schema/action.ts +0 -176
  171. package/src/schema/load.ts +0 -94
  172. package/src/schema/result.ts +0 -83
  173. package/src/schema/story.ts +0 -58
  174. package/src/screenshots/baselineStore.ts +0 -114
  175. package/src/screenshots/diff.ts +0 -101
  176. /package/{src → dist}/reporter/assets/report.css +0 -0
  177. /package/{src → dist}/reporter/assets/report.js +0 -0
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -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
@@ -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
- 'tuffgal/actions',
63
- 'tuffgal/stories',
64
- 'tuffgal/baselines',
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: InitOptions): Promise<void> {
89
- const { cwd } = options;
90
-
91
- for (const candidate of CONFIG_FILES) {
92
- if (await pathExists(join(cwd, candidate))) {
93
- throw new Error(
94
- `${candidate} already exists at ${cwd}. Refusing to overwrite. ` +
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
- const configPath = join(cwd, 'tuffgal.config.ts');
101
- await writeFile(configPath, CONFIG_TEMPLATE, 'utf8');
102
- process.stdout.write(`Wrote ${relative(cwd, configPath) || 'tuffgal.config.ts'}\n`);
103
-
104
- for (const subdirectory of GITKEEP_SUBDIRECTORIES) {
105
- const absolute = join(cwd, subdirectory);
106
- await mkdir(absolute, { recursive: true });
107
- const gitkeep = join(absolute, '.gitkeep');
108
- if (!(await pathExists(gitkeep))) {
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
- const gitignorePath = join(cwd, 'tuffgal', '.gitignore');
115
- if (!(await pathExists(gitignorePath))) {
116
- await writeFile(gitignorePath, REPORT_GITIGNORE, 'utf8');
117
- process.stdout.write(`Wrote ${relative(cwd, gitignorePath)}\n`);
118
- }
119
-
120
- process.stdout.write(
121
- [
122
- '',
123
- 'Tuffgal scaffold ready. Next steps:',
124
- ' 1. Edit tuffgal.config.ts to point at your dev server.',
125
- ' 2. Write your first action under tuffgal/actions/.',
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
- async function pathExists(absolute: string): Promise<boolean> {
134
- try {
135
- await access(absolute);
136
- return true;
137
- } catch {
138
- return false;
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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"}