@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.
- package/CHANGELOG.md +19 -0
- package/package.json +5 -12
- package/rescript.json +0 -1
- package/src/JestBind.res +21 -22
- package/src/JestBind.res.mjs +15 -11
- package/src/RunnerHook.res +38 -0
- package/src/RunnerHook.res.mjs +26 -0
- package/bin/reventless-dev.mjs +0 -21
- package/src/Bind.res +0 -29
- package/src/Bind.res.mjs +0 -22
- package/src/BuildClassifier.res +0 -147
- package/src/BuildClassifier.res.mjs +0 -136
- package/src/Cancellation.res +0 -37
- package/src/Cancellation.res.mjs +0 -46
- package/src/ChildProcess.res +0 -102
- package/src/ChildProcess.res.mjs +0 -107
- package/src/Cli.res +0 -905
- package/src/Cli.res.mjs +0 -952
- package/src/Collector.res +0 -212
- package/src/Collector.res.mjs +0 -218
- package/src/ComponentMeta.res +0 -77
- package/src/ComponentMeta.res.mjs +0 -91
- package/src/ComponentScan.res +0 -103
- package/src/ComponentScan.res.mjs +0 -105
- package/src/Discovery.res +0 -105
- package/src/Discovery.res.mjs +0 -99
- package/src/DomainDeadCode.res +0 -98
- package/src/DomainDeadCode.res.mjs +0 -72
- package/src/DomainGraph.res +0 -0
- package/src/DomainGraph.res.mjs +0 -0
- package/src/Filter.res +0 -45
- package/src/Filter.res.mjs +0 -47
- package/src/FormatterHuman.res +0 -109
- package/src/FormatterHuman.res.mjs +0 -115
- package/src/FormatterJson.res +0 -264
- package/src/FormatterJson.res.mjs +0 -322
- package/src/FormatterJunit.res +0 -93
- package/src/FormatterJunit.res.mjs +0 -90
- package/src/FormatterTap.res +0 -100
- package/src/FormatterTap.res.mjs +0 -131
- package/src/FormatterVsCode.res +0 -352
- package/src/FormatterVsCode.res.mjs +0 -546
- package/src/Loader.res +0 -24
- package/src/Loader.res.mjs +0 -23
- package/src/PackageScan.res +0 -80
- package/src/PackageScan.res.mjs +0 -81
- package/src/PlatformRunner.res +0 -191
- package/src/PlatformRunner.res.mjs +0 -178
- package/src/PlatformScan.res +0 -142
- package/src/PlatformScan.res.mjs +0 -132
- package/src/ProcessManager.res +0 -135
- package/src/ProcessManager.res.mjs +0 -119
- package/src/RunWorker.res +0 -24
- package/src/RunWorker.res.mjs +0 -16
- package/src/RunnerTypes.res +0 -68
- package/src/RunnerTypes.res.mjs +0 -60
- package/src/Watch.res +0 -108
- package/src/Watch.res.mjs +0 -85
- package/src/WatcherProbe.res +0 -56
- package/src/WatcherProbe.res.mjs +0 -59
- package/src/Worker.res +0 -24
- package/src/Worker.res.mjs +0 -9
- package/tests/BuildClassifierTest.res +0 -133
- package/tests/BuildClassifierTest.res.mjs +0 -176
- package/tests/CliRunEntryTest.res +0 -57
- package/tests/CliRunEntryTest.res.mjs +0 -65
- package/tests/CliWatchScopeTest.res +0 -62
- package/tests/CliWatchScopeTest.res.mjs +0 -95
- package/tests/ComponentMetaTest.res +0 -67
- package/tests/ComponentMetaTest.res.mjs +0 -71
- package/tests/DiscoveryTest.res +0 -42
- package/tests/DiscoveryTest.res.mjs +0 -43
- package/tests/DomainDeadCodeTest.res +0 -119
- package/tests/DomainDeadCodeTest.res.mjs +0 -158
- package/tests/DomainGraphTest.res +0 -448
- package/tests/DomainGraphTest.res.mjs +0 -505
- package/tests/FormatterGoldenTest.res +0 -125
- package/tests/FormatterGoldenTest.res.mjs +0 -162
- package/tests/FormatterVsCodeMessageTest.res +0 -41
- package/tests/FormatterVsCodeMessageTest.res.mjs +0 -42
- package/tests/PlatformRunnerTest.res +0 -66
- package/tests/PlatformRunnerTest.res.mjs +0 -85
- package/tests/PlatformScanTest.res +0 -61
- package/tests/PlatformScanTest.res.mjs +0 -56
- package/tests/RunnerUnitTest.res +0 -219
- package/tests/RunnerUnitTest.res.mjs +0 -244
- package/tests/WatchDebounceTest.res +0 -106
- 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.
|
|
4
|
-
"description": "Given-When-Then DSLs and
|
|
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-
|
|
27
|
-
"@reventlessdev/reventless-
|
|
28
|
-
"@reventlessdev/reventless-
|
|
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
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
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
|
38
|
+
// specification. Registers a non-running, non-failing entry under both drivers.
|
|
40
39
|
let todo = (label: string) =>
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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)
|
package/src/JestBind.res.mjs
CHANGED
|
@@ -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
|
|
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
|
-
|
|
19
|
-
|
|
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
|
-
|
|
28
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
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 =
|
|
53
|
-
|
|
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 */
|
package/bin/reventless-dev.mjs
DELETED
|
@@ -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 */
|
package/src/BuildClassifier.res
DELETED
|
@@ -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 */
|
package/src/Cancellation.res
DELETED
|
@@ -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
|
-
}
|