@reventlessdev/reventless-gwt 1.0.0-alpha.114 → 1.0.0-alpha.116

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 (88) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/package.json +5 -12
  3. package/rescript.json +0 -1
  4. package/src/JestBind.res +21 -22
  5. package/src/JestBind.res.mjs +15 -11
  6. package/src/RunnerHook.res +38 -0
  7. package/src/RunnerHook.res.mjs +26 -0
  8. package/bin/reventless-dev.mjs +0 -21
  9. package/src/Bind.res +0 -29
  10. package/src/Bind.res.mjs +0 -22
  11. package/src/BuildClassifier.res +0 -147
  12. package/src/BuildClassifier.res.mjs +0 -136
  13. package/src/Cancellation.res +0 -37
  14. package/src/Cancellation.res.mjs +0 -46
  15. package/src/ChildProcess.res +0 -102
  16. package/src/ChildProcess.res.mjs +0 -107
  17. package/src/Cli.res +0 -905
  18. package/src/Cli.res.mjs +0 -952
  19. package/src/Collector.res +0 -212
  20. package/src/Collector.res.mjs +0 -218
  21. package/src/ComponentMeta.res +0 -77
  22. package/src/ComponentMeta.res.mjs +0 -91
  23. package/src/ComponentScan.res +0 -103
  24. package/src/ComponentScan.res.mjs +0 -105
  25. package/src/Discovery.res +0 -105
  26. package/src/Discovery.res.mjs +0 -99
  27. package/src/DomainDeadCode.res +0 -98
  28. package/src/DomainDeadCode.res.mjs +0 -72
  29. package/src/DomainGraph.res +0 -0
  30. package/src/DomainGraph.res.mjs +0 -0
  31. package/src/Filter.res +0 -45
  32. package/src/Filter.res.mjs +0 -47
  33. package/src/FormatterHuman.res +0 -109
  34. package/src/FormatterHuman.res.mjs +0 -115
  35. package/src/FormatterJson.res +0 -264
  36. package/src/FormatterJson.res.mjs +0 -322
  37. package/src/FormatterJunit.res +0 -93
  38. package/src/FormatterJunit.res.mjs +0 -90
  39. package/src/FormatterTap.res +0 -100
  40. package/src/FormatterTap.res.mjs +0 -131
  41. package/src/FormatterVsCode.res +0 -352
  42. package/src/FormatterVsCode.res.mjs +0 -546
  43. package/src/Loader.res +0 -24
  44. package/src/Loader.res.mjs +0 -23
  45. package/src/PackageScan.res +0 -80
  46. package/src/PackageScan.res.mjs +0 -81
  47. package/src/PlatformRunner.res +0 -191
  48. package/src/PlatformRunner.res.mjs +0 -178
  49. package/src/PlatformScan.res +0 -142
  50. package/src/PlatformScan.res.mjs +0 -132
  51. package/src/ProcessManager.res +0 -135
  52. package/src/ProcessManager.res.mjs +0 -119
  53. package/src/RunWorker.res +0 -24
  54. package/src/RunWorker.res.mjs +0 -16
  55. package/src/RunnerTypes.res +0 -68
  56. package/src/RunnerTypes.res.mjs +0 -60
  57. package/src/Watch.res +0 -108
  58. package/src/Watch.res.mjs +0 -85
  59. package/src/WatcherProbe.res +0 -56
  60. package/src/WatcherProbe.res.mjs +0 -59
  61. package/src/Worker.res +0 -24
  62. package/src/Worker.res.mjs +0 -9
  63. package/tests/BuildClassifierTest.res +0 -133
  64. package/tests/BuildClassifierTest.res.mjs +0 -176
  65. package/tests/CliRunEntryTest.res +0 -57
  66. package/tests/CliRunEntryTest.res.mjs +0 -65
  67. package/tests/CliWatchScopeTest.res +0 -62
  68. package/tests/CliWatchScopeTest.res.mjs +0 -95
  69. package/tests/ComponentMetaTest.res +0 -67
  70. package/tests/ComponentMetaTest.res.mjs +0 -71
  71. package/tests/DiscoveryTest.res +0 -42
  72. package/tests/DiscoveryTest.res.mjs +0 -43
  73. package/tests/DomainDeadCodeTest.res +0 -119
  74. package/tests/DomainDeadCodeTest.res.mjs +0 -158
  75. package/tests/DomainGraphTest.res +0 -448
  76. package/tests/DomainGraphTest.res.mjs +0 -505
  77. package/tests/FormatterGoldenTest.res +0 -125
  78. package/tests/FormatterGoldenTest.res.mjs +0 -162
  79. package/tests/FormatterVsCodeMessageTest.res +0 -41
  80. package/tests/FormatterVsCodeMessageTest.res.mjs +0 -42
  81. package/tests/PlatformRunnerTest.res +0 -66
  82. package/tests/PlatformRunnerTest.res.mjs +0 -85
  83. package/tests/PlatformScanTest.res +0 -61
  84. package/tests/PlatformScanTest.res.mjs +0 -56
  85. package/tests/RunnerUnitTest.res +0 -219
  86. package/tests/RunnerUnitTest.res.mjs +0 -244
  87. package/tests/WatchDebounceTest.res +0 -106
  88. package/tests/WatchDebounceTest.res.mjs +0 -128
package/CHANGELOG.md CHANGED
@@ -3,6 +3,25 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # 1.0.0-alpha.116 (2026-07-16)
7
+
8
+ * refactor(gwt)!: focus the package on the GWT test harness library ([925e944](https://github.com/ReventlessDev/reventless-core/commit/925e944e996613f4fb894564696ea8e1ff6b7489))
9
+
10
+ ### BREAKING CHANGES
11
+
12
+ * the reventless-dev / reventless-gwt bin entries and the
13
+ runner/formatter modules are no longer part of this package.
14
+
15
+
16
+
17
+ # 1.0.0-alpha.115 (2026-07-15)
18
+
19
+ **Note:** Version bump only for package @reventlessdev/reventless-gwt
20
+
21
+
22
+
23
+
24
+
6
25
  # 1.0.0-alpha.114 (2026-07-14)
7
26
 
8
27
  ### Bug Fixes
package/package.json CHANGED
@@ -1,12 +1,8 @@
1
1
  {
2
2
  "name": "@reventlessdev/reventless-gwt",
3
- "version": "1.0.0-alpha.114",
4
- "description": "Given-When-Then DSLs and CLI runner for Reventless slice testing",
3
+ "version": "1.0.0-alpha.116",
4
+ "description": "Given-When-Then DSLs and test harness for Reventless slice testing",
5
5
  "license": "Apache-2.0",
6
- "bin": {
7
- "reventless-dev": "./bin/reventless-dev.mjs",
8
- "reventless-gwt": "./bin/reventless-dev.mjs"
9
- },
10
6
  "jest": {
11
7
  "testMatch": [
12
8
  "<rootDir>/tests/**/*Test.res.mjs"
@@ -17,15 +13,12 @@
17
13
  ]
18
14
  },
19
15
  "dependencies": {
20
- "chokidar": "^3.6.0",
21
- "picocolors": "^1.1.1",
22
16
  "sury": "11.0.0-alpha.4",
23
17
  "@reventlessdev/rescript-effect": "0.1.0-alpha.27",
24
- "@reventlessdev/reventless-core": "3.0.0-alpha.168",
25
18
  "@reventlessdev/rescript-jest": "1.0.0-alpha.7",
26
- "@reventlessdev/reventless-domain-protocol": "1.0.0-alpha.20",
27
- "@reventlessdev/reventless-infra": "3.0.0-alpha.99",
28
- "@reventlessdev/reventless-spec": "3.0.0-alpha.76"
19
+ "@reventlessdev/reventless-core": "3.0.0-alpha.169",
20
+ "@reventlessdev/reventless-spec": "3.0.0-alpha.77",
21
+ "@reventlessdev/reventless-infra": "3.0.0-alpha.100"
29
22
  },
30
23
  "devDependencies": {
31
24
  "rescript": "^12.3.0",
package/rescript.json CHANGED
@@ -23,7 +23,6 @@
23
23
  }
24
24
  ],
25
25
  "dependencies": [
26
- "@reventlessdev/reventless-domain-protocol",
27
26
  "sury",
28
27
  "@reventlessdev/rescript-jest",
29
28
  "@reventlessdev/rescript-effect",
package/src/JestBind.res CHANGED
@@ -2,10 +2,10 @@
2
2
  //
3
3
  // GWT DSLs emit `Outcome.outcome` from every `then*` combinator. Test files
4
4
  // registered via `JestBind.test` / `JestBind.testPromise` have their body's
5
- // outcome translated into a Jest pass/fail, unless the standalone CLI runner
6
- // has activated its in-process `Collector` — in which case the call pushes
7
- // straight into the collector and skips Jest. This lets the same test file
8
- // run under either `pnpm jest` or `reventless-gwt run` without modification.
5
+ // outcome translated into a Jest pass/fail, unless an external runner has
6
+ // registered an in-process sink (`RunnerHook`) — in which case the call is
7
+ // routed to that sink and skips Jest. This lets the same test file run under
8
+ // either plain `jest` or an external GWT runner without modification.
9
9
  //
10
10
  // `~slice` is the slice/component name — each DSL threads `Spec.name` through
11
11
  // so failure hints read `Look at AddCategory.decide` rather than the generic
@@ -29,27 +29,25 @@ let assertOutcome = (~slice=?, outcome: Outcome.outcome): unit =>
29
29
  }
30
30
 
31
31
  let describe = (label: string, body: unit => unit) =>
32
- if Collector.isActive() {
33
- Collector.pushDescribe(label, body)
34
- } else {
35
- JestGlobals.describe(label, body)
32
+ switch RunnerHook.get() {
33
+ | Some(sink) => sink.describe(label, body)
34
+ | None => JestGlobals.describe(label, body)
36
35
  }
37
36
 
38
37
  // Pending-spec placeholder emitted by the codegen for slices with no upstream
39
- // specification. Registers a non-running, non-failing entry under both runners.
38
+ // specification. Registers a non-running, non-failing entry under both drivers.
40
39
  let todo = (label: string) =>
41
- if Collector.isActive() {
42
- Collector.pushTodo(label)
43
- } else {
44
- JestGlobals.todo(label)
40
+ switch RunnerHook.get() {
41
+ | Some(sink) => sink.todo(label)
42
+ | None => JestGlobals.todo(label)
45
43
  }
46
44
 
47
45
  let test = (~slice=?, name: string, body: unit => Outcome.outcome) =>
48
- if Collector.isActive() {
49
- let location = Collector.captureLocation(1)
50
- Collector.push(~slice?, ~location?, name, () => Promise.resolve(body()))
51
- } else {
52
- JestGlobals.testSync(name, () => assertOutcome(~slice?, body()))
46
+ switch RunnerHook.get() {
47
+ | Some(sink) =>
48
+ let location = sink.captureLocation(1)
49
+ sink.test(~slice?, ~location?, name, () => Promise.resolve(body()))
50
+ | None => JestGlobals.testSync(name, () => assertOutcome(~slice?, body()))
53
51
  }
54
52
 
55
53
  let testPromise = (
@@ -58,10 +56,11 @@ let testPromise = (
58
56
  ~timeout: option<int>=?,
59
57
  body: unit => promise<Outcome.outcome>,
60
58
  ) =>
61
- if Collector.isActive() {
62
- let location = Collector.captureLocation(1)
63
- Collector.push(~slice?, ~location?, ~timeout?, name, body)
64
- } else {
59
+ switch RunnerHook.get() {
60
+ | Some(sink) =>
61
+ let location = sink.captureLocation(1)
62
+ sink.test(~slice?, ~location?, ~timeout?, name, body)
63
+ | None =>
65
64
  switch timeout {
66
65
  | Some(t) =>
67
66
  JestGlobals.testWithTimeout(name, async () => assertOutcome(~slice?, await body()), t)
@@ -3,7 +3,7 @@
3
3
  import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
4
4
  import * as Hint$ReventlessGwt from "./Hint.res.mjs";
5
5
  import * as Outcome$ReventlessGwt from "./Outcome.res.mjs";
6
- import * as Collector$ReventlessGwt from "./Collector.res.mjs";
6
+ import * as RunnerHook$ReventlessGwt from "./RunnerHook.res.mjs";
7
7
 
8
8
  function assertOutcome(slice, outcome) {
9
9
  if (outcome.TAG === "Ok") {
@@ -15,8 +15,9 @@ function assertOutcome(slice, outcome) {
15
15
  }
16
16
 
17
17
  function describe(label, body) {
18
- if (Collector$ReventlessGwt.isActive()) {
19
- return Collector$ReventlessGwt.pushDescribe(label, body);
18
+ let sink = RunnerHook$ReventlessGwt.get();
19
+ if (sink !== undefined) {
20
+ return sink.describe(label, body);
20
21
  } else {
21
22
  globalThis.describe(label, body);
22
23
  return;
@@ -24,8 +25,9 @@ function describe(label, body) {
24
25
  }
25
26
 
26
27
  function todo(label) {
27
- if (Collector$ReventlessGwt.isActive()) {
28
- return Collector$ReventlessGwt.pushTodo(label);
28
+ let sink = RunnerHook$ReventlessGwt.get();
29
+ if (sink !== undefined) {
30
+ return sink.todo(label);
29
31
  } else {
30
32
  globalThis.test.todo(label);
31
33
  return;
@@ -33,15 +35,17 @@ function todo(label) {
33
35
  }
34
36
 
35
37
  function test(slice, name, body) {
36
- if (Collector$ReventlessGwt.isActive()) {
37
- let location = Collector$ReventlessGwt.captureLocation(1);
38
- return Collector$ReventlessGwt.push(slice, location, undefined, name, () => Promise.resolve(body()));
38
+ let sink = RunnerHook$ReventlessGwt.get();
39
+ if (sink !== undefined) {
40
+ let location = sink.captureLocation(1);
41
+ return sink.test(slice, location, undefined, name, () => Promise.resolve(body()));
39
42
  }
40
43
  globalThis.test(name, () => assertOutcome(slice, body()));
41
44
  }
42
45
 
43
46
  function testPromise(slice, name, timeout, body) {
44
- if (!Collector$ReventlessGwt.isActive()) {
47
+ let sink = RunnerHook$ReventlessGwt.get();
48
+ if (sink === undefined) {
45
49
  if (timeout !== undefined) {
46
50
  globalThis.test(name, async () => assertOutcome(slice, await body()), timeout);
47
51
  } else {
@@ -49,8 +53,8 @@ function testPromise(slice, name, timeout, body) {
49
53
  }
50
54
  return;
51
55
  }
52
- let location = Collector$ReventlessGwt.captureLocation(1);
53
- Collector$ReventlessGwt.push(slice, location, timeout, name, body);
56
+ let location = sink.captureLocation(1);
57
+ sink.test(slice, location, timeout, name, body);
54
58
  }
55
59
 
56
60
  export {
@@ -0,0 +1,38 @@
1
+ // Registration seam that lets an external runner intercept GWT test
2
+ // registration in-process instead of forwarding to Jest.
3
+ //
4
+ // By default no runner is registered, so `JestBind` forwards every
5
+ // `describe` / `test` / `testPromise` to Jest's globals — the workflow the
6
+ // example apps use (`npx jest`), unchanged. An external runner may register an
7
+ // in-process sink at startup; `JestBind` then routes each registration to the
8
+ // sink instead, so the same `*_GWT.res` file runs unmodified under either
9
+ // driver.
10
+
11
+ type location = {
12
+ file: string,
13
+ line: int,
14
+ column: int,
15
+ }
16
+
17
+ type sink = {
18
+ describe: (string, unit => unit) => unit,
19
+ todo: string => unit,
20
+ // Capture the caller's source location by walking the current stack, skipping
21
+ // `n` internal frames. Invoked directly from `JestBind` so the resolved frame
22
+ // is the user's test file (the skip count is tuned for that call site).
23
+ captureLocation: int => option<location>,
24
+ test: (
25
+ ~slice: string=?,
26
+ ~location: location=?,
27
+ ~timeout: int=?,
28
+ string,
29
+ unit => promise<Outcome.outcome>,
30
+ ) => unit,
31
+ }
32
+
33
+ // The currently-registered sink, if any. `None` → plain Jest behaviour.
34
+ let current: ref<option<sink>> = ref(None)
35
+
36
+ let register = (s: sink) => current := Some(s)
37
+ let reset = () => current := None
38
+ let get = () => current.contents
@@ -0,0 +1,26 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+
4
+ let current = {
5
+ contents: undefined
6
+ };
7
+
8
+ function register(s) {
9
+ current.contents = s;
10
+ }
11
+
12
+ function reset() {
13
+ current.contents = undefined;
14
+ }
15
+
16
+ function get() {
17
+ return current.contents;
18
+ }
19
+
20
+ export {
21
+ current,
22
+ register,
23
+ reset,
24
+ get,
25
+ }
26
+ /* No side effect */
@@ -1,21 +0,0 @@
1
- #!/usr/bin/env node
2
- // Thin launcher for the reventless-dev CLI (formerly reventless-gwt). The heavy lifting lives in
3
- // Cli.res — this file only resolves the compiled module and exits with the
4
- // code Cli.main returns.
5
- //
6
- // `LOG_LEVEL=silent` is the default so framework Info/Debug logs don't
7
- // interleave with NDJSON / TAP / JUnit streams. Set `LOG_LEVEL=info` (or
8
- // debug/warn/error) explicitly to re-enable. We use a dynamic `import()` so
9
- // the env is applied BEFORE any ReScript module runs its top-level
10
- // `Logger.fromEnv()` call.
11
-
12
- if (!process.env.LOG_LEVEL) process.env.LOG_LEVEL = "silent";
13
-
14
- const { main } = await import("../src/Cli.res.mjs");
15
-
16
- main()
17
- .then((code) => process.exit(typeof code === "number" ? code : 0))
18
- .catch((err) => {
19
- console.error(err && err.stack ? err.stack : err);
20
- process.exit(1);
21
- });
package/src/Bind.res DELETED
@@ -1,29 +0,0 @@
1
- // CLI-mode binding for test files.
2
- //
3
- // Mirrors the shape of `JestBind` (`describe`, `test`, `testPromise`) but
4
- // pushes directly into `Collector` instead of forwarding to Jest globals.
5
- // Test files can either:
6
- //
7
- // 1. Rely on the DSL default (every `*_GWT.res` module uses `JestBind`
8
- // internally, which routes to `Collector` when the CLI has activated it),
9
- // or
10
- // 2. Explicitly `open ReventlessGwt.Bind` to get these functions bound at
11
- // compile time — useful for ad-hoc harnesses or tests that want to
12
- // bypass the JestBind fallback entirely.
13
- //
14
- // Either way, the runtime contract is identical: each call pushes one entry
15
- // into `Collector.entries`. The CLI drains the list after dynamic-import
16
- // resolves.
17
-
18
- let describe = (label: string, body: unit => unit) =>
19
- Collector.pushDescribe(label, body)
20
-
21
- let test = (~slice=?, name: string, body: unit => Outcome.outcome) => {
22
- let location = Collector.captureLocation(1)
23
- Collector.push(~slice?, ~location?, name, () => Promise.resolve(body()))
24
- }
25
-
26
- let testPromise = (~slice=?, name: string, body: unit => promise<Outcome.outcome>) => {
27
- let location = Collector.captureLocation(1)
28
- Collector.push(~slice?, ~location?, name, body)
29
- }
package/src/Bind.res.mjs DELETED
@@ -1,22 +0,0 @@
1
- // Generated by ReScript, PLEASE EDIT WITH CARE
2
-
3
- import * as Collector$ReventlessGwt from "./Collector.res.mjs";
4
-
5
- let describe = Collector$ReventlessGwt.pushDescribe;
6
-
7
- function test(slice, name, body) {
8
- let location = Collector$ReventlessGwt.captureLocation(1);
9
- Collector$ReventlessGwt.push(slice, location, undefined, name, () => Promise.resolve(body()));
10
- }
11
-
12
- function testPromise(slice, name, body) {
13
- let location = Collector$ReventlessGwt.captureLocation(1);
14
- Collector$ReventlessGwt.push(slice, location, undefined, name, body);
15
- }
16
-
17
- export {
18
- describe,
19
- test,
20
- testPromise,
21
- }
22
- /* No side effect */
@@ -1,147 +0,0 @@
1
- // Turns a ReScript v12 `rescript build -w` stdout line-stream into build status
2
- // callbacks. One classifier per managed package.
3
- //
4
- // The watch output cycle is:
5
- // Parsed N source files ← a compile pass begins
6
- // Compiled N modules
7
- // Finished incremental compilation ← success terminator
8
- // On a type/syntax error there is NO terminator — rescript prints a
9
- // "We've found a bug for you!" block and waits. So success is detected on the
10
- // "Finished … compilation" line; failure is detected by an error marker and
11
- // flushed after the output settles (debounced), since there is no end line.
12
- //
13
- // Backstop: a build that prints "Parsed …" but then emits neither a terminator
14
- // nor a recognised error marker — a crashed/hung watcher, or an error in a form
15
- // the markers miss — would otherwise pin the consumer (the VS Code status bar)
16
- // at "building" forever. A generous wall-clock watchdog fails such a build so
17
- // the UI recovers.
18
-
19
- @val external now: unit => float = "performance.now"
20
- @val external setTimeout: (unit => unit, int) => float = "setTimeout"
21
- @val external clearTimeout: float => unit = "clearTimeout"
22
-
23
- let stripAnsi: string => string = %raw(`s => s.replace(/\x1b\[[0-9;]*m/g, "")`)
24
-
25
- type callbacks = {
26
- onStart: unit => unit,
27
- onOk: float => unit,
28
- onFail: string => unit,
29
- }
30
-
31
- let isStartLine = (l: string) =>
32
- String.startsWith(l, "Parsed ") && String.endsWith(l, "source files")
33
-
34
- let isFinishLine = (l: string) =>
35
- String.includes(l, "Finished") && String.includes(l, "compilation")
36
-
37
- let isErrorMarker = (l: string) =>
38
- String.includes(l, "We've found a bug for you!") ||
39
- String.includes(l, "Syntax error") ||
40
- (String.startsWith(l, "Found ") && String.includes(l, "error"))
41
-
42
- // Returns a `feed` function: hand it each raw stdout line. `watchdogMs` bounds
43
- // how long a started-but-unterminated compile may stay "building" before it is
44
- // reported failed (see the backstop note above).
45
- let make = (~watchdogMs: int=120_000, cb: callbacks): (string => unit) => {
46
- let compiling = ref(false)
47
- let hasError = ref(false)
48
- let startTime = ref(0.0)
49
- let buffer = ref([])
50
- let timer: ref<option<float>> = ref(None)
51
- let watchdog: ref<option<float>> = ref(None)
52
-
53
- let clearTimer = () =>
54
- switch timer.contents {
55
- | Some(t) => {
56
- clearTimeout(t)
57
- timer := None
58
- }
59
- | None => ()
60
- }
61
-
62
- let clearWatchdog = () =>
63
- switch watchdog.contents {
64
- | Some(t) => {
65
- clearTimeout(t)
66
- watchdog := None
67
- }
68
- | None => ()
69
- }
70
-
71
- let reset = () => {
72
- compiling := false
73
- hasError := false
74
- buffer := []
75
- }
76
-
77
- // Cap the failure message so a pathological build can't emit an unbounded
78
- // line. 20 was too tight — a multi-error build has several "We've found a bug"
79
- // blocks and the tail (later errors) was silently dropped; keep enough to
80
- // carry a handful of blocks.
81
- let maxMessageLines = 200
82
-
83
- // After output settles, a build that saw an error (and thus no "Finished"
84
- // terminator) is reported failed, capturing a concise message. The condition
85
- // is `hasError` alone — not `compiling && hasError` — so an error emitted
86
- // *before* the first "Parsed …" line (a crashed toolchain, a pnpm/resolution
87
- // failure) still settles into an `onFail` instead of leaving the consumer
88
- // pinned at its previous state forever.
89
- let settle = () =>
90
- if hasError.contents {
91
- let msg = buffer.contents->Array.slice(~start=0, ~end=maxMessageLines)->Array.join("\n")
92
- clearWatchdog()
93
- cb.onFail(msg)
94
- reset()
95
- }
96
-
97
- let arm = () => {
98
- clearTimer()
99
- timer := Some(setTimeout(settle, 400))
100
- }
101
-
102
- // Last-resort timer: fail a build still compiling after `watchdogMs` with no
103
- // terminator and no recognised error, so the consumer never spins forever.
104
- let armWatchdog = () => {
105
- clearWatchdog()
106
- watchdog :=
107
- Some(
108
- setTimeout(() =>
109
- if compiling.contents {
110
- clearTimer()
111
- cb.onFail("build watchdog: no completion within " ++ Int.toString(watchdogMs / 1000) ++ "s")
112
- reset()
113
- }
114
- , watchdogMs),
115
- )
116
- }
117
-
118
- line => {
119
- let l = stripAnsi(line)->String.trim
120
- if isStartLine(l) {
121
- if !compiling.contents {
122
- cb.onStart()
123
- }
124
- compiling := true
125
- hasError := false
126
- buffer := []
127
- startTime := now()
128
- arm()
129
- armWatchdog()
130
- } else if isFinishLine(l) {
131
- clearTimer()
132
- clearWatchdog()
133
- if compiling.contents {
134
- cb.onOk(now() -. startTime.contents)
135
- reset()
136
- }
137
- } else {
138
- if String.length(l) > 0 {
139
- buffer.contents->Array.push(l)
140
- }
141
- if isErrorMarker(l) {
142
- hasError := true
143
- }
144
- arm()
145
- }
146
- }
147
- }
@@ -1,136 +0,0 @@
1
- // Generated by ReScript, PLEASE EDIT WITH CARE
2
-
3
-
4
- let stripAnsi = (s => s.replace(/\x1b\[[0-9;]*m/g, ""));
5
-
6
- function isStartLine(l) {
7
- if (l.startsWith("Parsed ")) {
8
- return l.endsWith("source files");
9
- } else {
10
- return false;
11
- }
12
- }
13
-
14
- function isFinishLine(l) {
15
- if (l.includes("Finished")) {
16
- return l.includes("compilation");
17
- } else {
18
- return false;
19
- }
20
- }
21
-
22
- function isErrorMarker(l) {
23
- if (l.includes("We've found a bug for you!") || l.includes("Syntax error")) {
24
- return true;
25
- } else if (l.startsWith("Found ")) {
26
- return l.includes("error");
27
- } else {
28
- return false;
29
- }
30
- }
31
-
32
- function make(watchdogMsOpt, cb) {
33
- let watchdogMs = watchdogMsOpt !== undefined ? watchdogMsOpt : 120000;
34
- let compiling = {
35
- contents: false
36
- };
37
- let hasError = {
38
- contents: false
39
- };
40
- let startTime = {
41
- contents: 0.0
42
- };
43
- let buffer = {
44
- contents: []
45
- };
46
- let timer = {
47
- contents: undefined
48
- };
49
- let watchdog = {
50
- contents: undefined
51
- };
52
- let clearTimer = () => {
53
- let t = timer.contents;
54
- if (t !== undefined) {
55
- clearTimeout(t);
56
- timer.contents = undefined;
57
- return;
58
- }
59
- };
60
- let clearWatchdog = () => {
61
- let t = watchdog.contents;
62
- if (t !== undefined) {
63
- clearTimeout(t);
64
- watchdog.contents = undefined;
65
- return;
66
- }
67
- };
68
- let reset = () => {
69
- compiling.contents = false;
70
- hasError.contents = false;
71
- buffer.contents = [];
72
- };
73
- let settle = () => {
74
- if (!hasError.contents) {
75
- return;
76
- }
77
- let msg = buffer.contents.slice(0, 200).join("\n");
78
- clearWatchdog();
79
- cb.onFail(msg);
80
- reset();
81
- };
82
- let arm = () => {
83
- clearTimer();
84
- timer.contents = setTimeout(settle, 400);
85
- };
86
- let armWatchdog = () => {
87
- clearWatchdog();
88
- watchdog.contents = setTimeout(() => {
89
- if (compiling.contents) {
90
- clearTimer();
91
- cb.onFail("build watchdog: no completion within " + (watchdogMs / 1000 | 0).toString() + "s");
92
- return reset();
93
- }
94
- }, watchdogMs);
95
- };
96
- return line => {
97
- let l = stripAnsi(line).trim();
98
- if (isStartLine(l)) {
99
- if (!compiling.contents) {
100
- cb.onStart();
101
- }
102
- compiling.contents = true;
103
- hasError.contents = false;
104
- buffer.contents = [];
105
- startTime.contents = performance.now();
106
- arm();
107
- return armWatchdog();
108
- } else if (isFinishLine(l)) {
109
- clearTimer();
110
- clearWatchdog();
111
- if (compiling.contents) {
112
- cb.onOk(performance.now() - startTime.contents);
113
- return reset();
114
- } else {
115
- return;
116
- }
117
- } else {
118
- if (l.length > 0) {
119
- buffer.contents.push(l);
120
- }
121
- if (isErrorMarker(l)) {
122
- hasError.contents = true;
123
- }
124
- return arm();
125
- }
126
- };
127
- }
128
-
129
- export {
130
- stripAnsi,
131
- isStartLine,
132
- isFinishLine,
133
- isErrorMarker,
134
- make,
135
- }
136
- /* No side effect */
@@ -1,37 +0,0 @@
1
- // SIGINT / SIGTERM trap. Sets a module-level flag that the main loop
2
- // polls between tests. The VS Code and JSON streaming formatters flush a
3
- // `runEnd` event with the skipped remainder before the process exits.
4
-
5
- type process
6
- @val external process: process = "process"
7
- @send external onSignal: (process, string, unit => unit) => unit = "on"
8
- @send external exit: (process, int) => unit = "exit"
9
-
10
- let cancelled = ref(false)
11
- let handlers: ref<array<unit => unit>> = ref([])
12
-
13
- let isCancelled = () => cancelled.contents
14
-
15
- let onCancel = (handler: unit => unit) =>
16
- handlers := Array.concat(handlers.contents, [handler])
17
-
18
- let install = () => {
19
- let handler = () => {
20
- if !cancelled.contents {
21
- cancelled := true
22
- handlers.contents->Array.forEach(h =>
23
- try h() catch {
24
- | _ => ()
25
- }
26
- )
27
- } else {
28
- // Second signal: the first requested a graceful drain, but a hung await
29
- // can keep the process alive indefinitely. Honour the operator's repeat
30
- // SIGINT/SIGTERM as a hard exit (130 = terminated by SIGINT) so only the
31
- // one signal — not SIGKILL — is needed to break out.
32
- process->exit(130)
33
- }
34
- }
35
- process->onSignal("SIGINT", handler)
36
- process->onSignal("SIGTERM", handler)
37
- }