@reventlessdev/reventless-gwt 1.0.0-alpha.100
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 +916 -0
- package/LICENSE +202 -0
- package/bin/reventless-dev.mjs +21 -0
- package/package.json +54 -0
- package/rescript.json +36 -0
- package/src/.gwtignore +10 -0
- package/src/Automation_GWT.res +207 -0
- package/src/Automation_GWT.res.mjs +160 -0
- package/src/Behavior_GWT.res +569 -0
- package/src/Behavior_GWT.res.mjs +523 -0
- package/src/Bind.res +29 -0
- package/src/Bind.res.mjs +22 -0
- package/src/BuildClassifier.res +147 -0
- package/src/BuildClassifier.res.mjs +136 -0
- package/src/Cancellation.res +37 -0
- package/src/Cancellation.res.mjs +46 -0
- package/src/ChildProcess.res +102 -0
- package/src/ChildProcess.res.mjs +107 -0
- package/src/Cli.res +905 -0
- package/src/Cli.res.mjs +952 -0
- package/src/Collector.res +212 -0
- package/src/Collector.res.mjs +218 -0
- package/src/ComponentMeta.res +77 -0
- package/src/ComponentMeta.res.mjs +91 -0
- package/src/ComponentScan.res +103 -0
- package/src/ComponentScan.res.mjs +105 -0
- package/src/Delegate_GWT.res +303 -0
- package/src/Delegate_GWT.res.mjs +372 -0
- package/src/Diff.res +91 -0
- package/src/Diff.res.mjs +98 -0
- package/src/Discovery.res +105 -0
- package/src/Discovery.res.mjs +99 -0
- package/src/DomainDeadCode.res +98 -0
- package/src/DomainDeadCode.res.mjs +72 -0
- package/src/DomainGraph.res +0 -0
- package/src/DomainGraph.res.mjs +0 -0
- package/src/EventMapping_GWT.res +29 -0
- package/src/EventMapping_GWT.res.mjs +44 -0
- package/src/Filter.res +45 -0
- package/src/Filter.res.mjs +47 -0
- package/src/Flow_GWT.res +664 -0
- package/src/Flow_GWT.res.mjs +604 -0
- package/src/FormatterHuman.res +109 -0
- package/src/FormatterHuman.res.mjs +115 -0
- package/src/FormatterJson.res +264 -0
- package/src/FormatterJson.res.mjs +322 -0
- package/src/FormatterJunit.res +93 -0
- package/src/FormatterJunit.res.mjs +90 -0
- package/src/FormatterTap.res +100 -0
- package/src/FormatterTap.res.mjs +131 -0
- package/src/FormatterVsCode.res +352 -0
- package/src/FormatterVsCode.res.mjs +546 -0
- package/src/Hint.res +119 -0
- package/src/Hint.res.mjs +124 -0
- package/src/InboundTranslation_GWT.res +83 -0
- package/src/InboundTranslation_GWT.res.mjs +94 -0
- package/src/JestBind.res +70 -0
- package/src/JestBind.res.mjs +63 -0
- package/src/Loader.res +24 -0
- package/src/Loader.res.mjs +23 -0
- package/src/LocalHost.res +119 -0
- package/src/LocalHost.res.mjs +99 -0
- package/src/Mapping_GWT.res +474 -0
- package/src/Mapping_GWT.res.mjs +322 -0
- package/src/MismatchRender.res +72 -0
- package/src/MismatchRender.res.mjs +127 -0
- package/src/MultiSourceProjection_GWT.res +316 -0
- package/src/MultiSourceProjection_GWT.res.mjs +354 -0
- package/src/OutboundTranslation_GWT.res +238 -0
- package/src/OutboundTranslation_GWT.res.mjs +186 -0
- package/src/Outcome.res +97 -0
- package/src/Outcome.res.mjs +93 -0
- package/src/PackageScan.res +80 -0
- package/src/PackageScan.res.mjs +81 -0
- package/src/PlatformRunner.res +181 -0
- package/src/PlatformRunner.res.mjs +176 -0
- package/src/PlatformScan.res +142 -0
- package/src/PlatformScan.res.mjs +132 -0
- package/src/ProcessManager.res +135 -0
- package/src/ProcessManager.res.mjs +119 -0
- package/src/Projection_GWT.res +312 -0
- package/src/Projection_GWT.res.mjs +332 -0
- package/src/Query_GWT.res +379 -0
- package/src/Query_GWT.res.mjs +367 -0
- package/src/RenderRescript.res +125 -0
- package/src/RenderRescript.res.mjs +141 -0
- package/src/RunWorker.res +24 -0
- package/src/RunWorker.res.mjs +16 -0
- package/src/RunnerTypes.res +68 -0
- package/src/RunnerTypes.res.mjs +60 -0
- package/src/SideEffect_GWT.res +157 -0
- package/src/SideEffect_GWT.res.mjs +113 -0
- package/src/StateChange/ExternalAddCategorySlice.res +25 -0
- package/src/StateChange/ExternalAddCategorySlice.res.mjs +34 -0
- package/src/StateChange/ExternalAddCategorySlice_Behavior.res +19 -0
- package/src/StateChange/ExternalAddCategorySlice_Behavior.res.mjs +46 -0
- package/src/StateChange/WithFixtures.res +22 -0
- package/src/StateChange/WithFixtures.res.mjs +31 -0
- package/src/StateChange/WithFixtures_Behavior.res +17 -0
- package/src/StateChange/WithFixtures_Behavior.res.mjs +37 -0
- package/src/StateChange/WithManualOpen.res +23 -0
- package/src/StateChange/WithManualOpen.res.mjs +31 -0
- package/src/StateChange/WithManualOpen_Behavior.res +17 -0
- package/src/StateChange/WithManualOpen_Behavior.res.mjs +37 -0
- package/src/StubRuntime.res +54 -0
- package/src/StubRuntime.res.mjs +55 -0
- package/src/TestFixtures.res +16 -0
- package/src/TestFixtures.res.mjs +34 -0
- package/src/Watch.res +108 -0
- package/src/Watch.res.mjs +85 -0
- package/src/WatcherProbe.res +56 -0
- package/src/WatcherProbe.res.mjs +59 -0
- package/src/Worker.res +24 -0
- package/src/Worker.res.mjs +9 -0
- package/tests/AutomationGwtTest.res +71 -0
- package/tests/AutomationGwtTest.res.mjs +189 -0
- package/tests/BuildClassifierTest.res +133 -0
- package/tests/BuildClassifierTest.res.mjs +176 -0
- package/tests/CliRunEntryTest.res +57 -0
- package/tests/CliRunEntryTest.res.mjs +65 -0
- package/tests/CliWatchScopeTest.res +62 -0
- package/tests/CliWatchScopeTest.res.mjs +95 -0
- package/tests/ComponentMetaTest.res +67 -0
- package/tests/ComponentMetaTest.res.mjs +71 -0
- package/tests/DelegateGwtTest.res +487 -0
- package/tests/DelegateGwtTest.res.mjs +891 -0
- package/tests/DiscoveryTest.res +42 -0
- package/tests/DiscoveryTest.res.mjs +43 -0
- package/tests/DomainDeadCodeTest.res +119 -0
- package/tests/DomainDeadCodeTest.res.mjs +158 -0
- package/tests/DomainGraphTest.res +448 -0
- package/tests/DomainGraphTest.res.mjs +505 -0
- package/tests/FlowAggregateGwtTest.res +171 -0
- package/tests/FlowAggregateGwtTest.res.mjs +273 -0
- package/tests/FlowCrossPluginGwtTest.res +340 -0
- package/tests/FlowCrossPluginGwtTest.res.mjs +798 -0
- package/tests/FlowGwtTest.res +222 -0
- package/tests/FlowGwtTest.res.mjs +433 -0
- package/tests/FormatterGoldenTest.res +125 -0
- package/tests/FormatterGoldenTest.res.mjs +162 -0
- package/tests/FormatterVsCodeMessageTest.res +41 -0
- package/tests/FormatterVsCodeMessageTest.res.mjs +42 -0
- package/tests/InboundTranslationGwtTest.res +34 -0
- package/tests/InboundTranslationGwtTest.res.mjs +99 -0
- package/tests/LocalHostIntegration.res +70 -0
- package/tests/LocalHostIntegration.res.mjs +64 -0
- package/tests/LocalHostTest.res +71 -0
- package/tests/LocalHostTest.res.mjs +63 -0
- package/tests/MappingGwtTest.res +350 -0
- package/tests/MappingGwtTest.res.mjs +596 -0
- package/tests/OutboundTranslationGwtTest.res +59 -0
- package/tests/OutboundTranslationGwtTest.res.mjs +139 -0
- package/tests/PlatformRunnerTest.res +66 -0
- package/tests/PlatformRunnerTest.res.mjs +85 -0
- package/tests/PlatformScanTest.res +61 -0
- package/tests/PlatformScanTest.res.mjs +56 -0
- package/tests/QueryGwtTest.res +161 -0
- package/tests/QueryGwtTest.res.mjs +314 -0
- package/tests/QueryResolverGwtTest.res +71 -0
- package/tests/QueryResolverGwtTest.res.mjs +136 -0
- package/tests/RunnerUnitTest.res +219 -0
- package/tests/RunnerUnitTest.res.mjs +244 -0
- package/tests/SideEffect/FakeOrderNotification.res +23 -0
- package/tests/SideEffect/FakeOrderNotification.res.mjs +36 -0
- package/tests/SideEffect/FakeOrderNotificationGwtTest.res +35 -0
- package/tests/SideEffect/FakeOrderNotificationGwtTest.res.mjs +73 -0
- package/tests/SideEffect/MockEmail.res +18 -0
- package/tests/SideEffect/MockEmail.res.mjs +47 -0
- package/tests/StateChange/ExternalAddCategorySlice_GWT.res +27 -0
- package/tests/StateChange/ExternalAddCategorySlice_GWT.res.mjs +70 -0
- package/tests/StateChange/WithFixtures_Fixtures.res +9 -0
- package/tests/StateChange/WithFixtures_Fixtures.res.mjs +20 -0
- package/tests/StateChange/WithFixtures_GWT.res +23 -0
- package/tests/StateChange/WithFixtures_GWT.res.mjs +56 -0
- package/tests/StateChange/WithManualOpen_Fixtures.res +7 -0
- package/tests/StateChange/WithManualOpen_Fixtures.res.mjs +20 -0
- package/tests/StateChange/WithManualOpen_GWT.res +17 -0
- package/tests/StateChange/WithManualOpen_GWT.res.mjs +56 -0
- package/tests/StateChangeSliceGwtTest.res +160 -0
- package/tests/StateChangeSliceGwtTest.res.mjs +280 -0
- package/tests/StateViewSliceGwtTest.res +64 -0
- package/tests/StateViewSliceGwtTest.res.mjs +161 -0
- package/tests/WatchDebounceTest.res +106 -0
- package/tests/WatchDebounceTest.res.mjs +128 -0
|
@@ -0,0 +1,147 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
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 */
|
|
@@ -0,0 +1,37 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
let cancelled = {
|
|
5
|
+
contents: false
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
let handlers = {
|
|
9
|
+
contents: []
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
function isCancelled() {
|
|
13
|
+
return cancelled.contents;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function onCancel(handler) {
|
|
17
|
+
handlers.contents = handlers.contents.concat([handler]);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function install() {
|
|
21
|
+
let handler = () => {
|
|
22
|
+
if (cancelled.contents) {
|
|
23
|
+
process.exit(130);
|
|
24
|
+
} else {
|
|
25
|
+
cancelled.contents = true;
|
|
26
|
+
handlers.contents.forEach(h => {
|
|
27
|
+
try {
|
|
28
|
+
return h();
|
|
29
|
+
} catch (exn) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
process.on("SIGINT", handler);
|
|
36
|
+
process.on("SIGTERM", handler);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export {
|
|
40
|
+
cancelled,
|
|
41
|
+
handlers,
|
|
42
|
+
isCancelled,
|
|
43
|
+
onCancel,
|
|
44
|
+
install,
|
|
45
|
+
}
|
|
46
|
+
/* No side effect */
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// Minimal `node:child_process` binding for the watch-mode build manager. The
|
|
2
|
+
// only consumer is ProcessManager, which spawns one `pnpm run start`
|
|
3
|
+
// (`rescript build -w`) per package. Children are spawned **detached** so each
|
|
4
|
+
// gets its own process group; killing the negative pid then tears down the whole
|
|
5
|
+
// tree (pnpm + the rescript watcher it spawns) — no orphaned compilers.
|
|
6
|
+
|
|
7
|
+
type t
|
|
8
|
+
type stream
|
|
9
|
+
|
|
10
|
+
type spawnOptions = {
|
|
11
|
+
cwd: string,
|
|
12
|
+
detached: bool,
|
|
13
|
+
env: Dict.t<string>,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@module("node:child_process")
|
|
17
|
+
external _spawn: (string, array<string>, spawnOptions) => t = "spawn"
|
|
18
|
+
|
|
19
|
+
@val external _processEnv: Dict.t<string> = "process.env"
|
|
20
|
+
|
|
21
|
+
// Merge the current process env with optional overrides into a fresh dict — node's
|
|
22
|
+
// `spawn` replaces the whole env, so callers that pass `~env` still inherit PATH etc.
|
|
23
|
+
let mergeEnv = (overrides: option<Dict.t<string>>): Dict.t<string> => {
|
|
24
|
+
let m = Dict.make()
|
|
25
|
+
_processEnv->Dict.toArray->Array.forEach(((k, v)) => m->Dict.set(k, v))
|
|
26
|
+
switch overrides {
|
|
27
|
+
| Some(o) => o->Dict.toArray->Array.forEach(((k, v)) => m->Dict.set(k, v))
|
|
28
|
+
| None => ()
|
|
29
|
+
}
|
|
30
|
+
m
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@get external _pid: t => Nullable.t<int> = "pid"
|
|
34
|
+
@get external _stdout: t => stream = "stdout"
|
|
35
|
+
@get external _stderr: t => stream = "stderr"
|
|
36
|
+
@send external _streamSetEncoding: (stream, string) => unit = "setEncoding"
|
|
37
|
+
@send external _streamOn: (stream, string, string => unit) => unit = "on"
|
|
38
|
+
@send external _streamOnEnd: (stream, string, unit => unit) => unit = "on"
|
|
39
|
+
@send external _procOnClose: (t, string, Nullable.t<int> => unit) => unit = "on"
|
|
40
|
+
@send external _procOnError: (t, string, JsExn.t => unit) => unit = "on"
|
|
41
|
+
@send external _kill: (t, string) => bool = "kill"
|
|
42
|
+
@val external _processKill: (int, string) => unit = "process.kill"
|
|
43
|
+
|
|
44
|
+
let spawn = (cmd: string, args: array<string>, ~cwd: string, ~env: option<Dict.t<string>>=?): t =>
|
|
45
|
+
_spawn(cmd, args, {cwd, detached: true, env: mergeEnv(env)})
|
|
46
|
+
|
|
47
|
+
let pid = (proc: t): option<int> => proc->_pid->Nullable.toOption
|
|
48
|
+
|
|
49
|
+
// Subscribe to a stream line-by-line (stdout or stderr), buffering partial
|
|
50
|
+
// chunks. Used to classify ReScript compiler markers in Phase 5. On stream
|
|
51
|
+
// `end` the final unterminated line is flushed — crash/panic messages
|
|
52
|
+
// typically lack a trailing newline, so without this the last (often most
|
|
53
|
+
// diagnostic) line was silently dropped.
|
|
54
|
+
let onLines = (s: stream, cb: string => unit): unit => {
|
|
55
|
+
_streamSetEncoding(s, "utf8")
|
|
56
|
+
let buf = ref("")
|
|
57
|
+
_streamOn(s, "data", chunk => {
|
|
58
|
+
buf := buf.contents ++ chunk
|
|
59
|
+
let rec drain = () =>
|
|
60
|
+
switch String.indexOf(buf.contents, "\n") {
|
|
61
|
+
| -1 => ()
|
|
62
|
+
| idx => {
|
|
63
|
+
let line = String.slice(buf.contents, ~start=0, ~end=idx)
|
|
64
|
+
buf := String.slice(buf.contents, ~start=idx + 1, ~end=String.length(buf.contents))
|
|
65
|
+
cb(line)
|
|
66
|
+
drain()
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
drain()
|
|
70
|
+
})
|
|
71
|
+
_streamOnEnd(s, "end", () =>
|
|
72
|
+
if buf.contents != "" {
|
|
73
|
+
let last = buf.contents
|
|
74
|
+
buf := ""
|
|
75
|
+
cb(last)
|
|
76
|
+
}
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
let onStdoutLine = (proc: t, cb: string => unit): unit => onLines(proc->_stdout, cb)
|
|
81
|
+
let onStderrLine = (proc: t, cb: string => unit): unit => onLines(proc->_stderr, cb)
|
|
82
|
+
let onClose = (proc: t, cb: option<int> => unit): unit =>
|
|
83
|
+
_procOnClose(proc, "close", code => cb(code->Nullable.toOption))
|
|
84
|
+
|
|
85
|
+
// A spawn failure (ENOENT for a missing binary, EACCES, …) emits `"error"` on
|
|
86
|
+
// the child, not `"close"`. Without a listener Node re-throws it as an uncaught
|
|
87
|
+
// exception and crashes the whole CLI. Route the message to the caller instead.
|
|
88
|
+
let onError = (proc: t, cb: string => unit): unit =>
|
|
89
|
+
_procOnError(proc, "error", e => cb(e->JsExn.message->Option.getOr("spawn failed")))
|
|
90
|
+
|
|
91
|
+
// Kill the child's whole process group (negative pid) so the rescript watcher
|
|
92
|
+
// pnpm spawned dies too. Falls back to a direct kill if the pid is gone.
|
|
93
|
+
let killTree = (proc: t, signal: string): unit =>
|
|
94
|
+
switch proc->pid {
|
|
95
|
+
| Some(p) =>
|
|
96
|
+
try {
|
|
97
|
+
_processKill(-p, signal)
|
|
98
|
+
} catch {
|
|
99
|
+
| _ => ignore(proc->_kill(signal))
|
|
100
|
+
}
|
|
101
|
+
| None => ignore(proc->_kill(signal))
|
|
102
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Stdlib_JsExn from "@rescript/runtime/lib/es6/Stdlib_JsExn.js";
|
|
4
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
5
|
+
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
6
|
+
import * as Nodechild_process from "node:child_process";
|
|
7
|
+
|
|
8
|
+
function mergeEnv(overrides) {
|
|
9
|
+
let m = {};
|
|
10
|
+
Object.entries(process.env).forEach(param => {
|
|
11
|
+
m[param[0]] = param[1];
|
|
12
|
+
});
|
|
13
|
+
if (overrides !== undefined) {
|
|
14
|
+
Object.entries(overrides).forEach(param => {
|
|
15
|
+
m[param[0]] = param[1];
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
return m;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function spawn(cmd, args, cwd, env) {
|
|
22
|
+
return Nodechild_process.spawn(cmd, args, {
|
|
23
|
+
cwd: cwd,
|
|
24
|
+
detached: true,
|
|
25
|
+
env: mergeEnv(env)
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function pid(proc) {
|
|
30
|
+
return Primitive_option.fromNullable(proc.pid);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function onLines(s, cb) {
|
|
34
|
+
s.setEncoding("utf8");
|
|
35
|
+
let buf = {
|
|
36
|
+
contents: ""
|
|
37
|
+
};
|
|
38
|
+
s.on("data", chunk => {
|
|
39
|
+
buf.contents = buf.contents + chunk;
|
|
40
|
+
let drain = () => {
|
|
41
|
+
while (true) {
|
|
42
|
+
let idx = buf.contents.indexOf("\n");
|
|
43
|
+
if (idx === -1) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
let line = buf.contents.slice(0, idx);
|
|
47
|
+
buf.contents = buf.contents.slice(idx + 1 | 0, buf.contents.length);
|
|
48
|
+
cb(line);
|
|
49
|
+
continue;
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
drain();
|
|
53
|
+
});
|
|
54
|
+
s.on("end", () => {
|
|
55
|
+
if (buf.contents === "") {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
let last = buf.contents;
|
|
59
|
+
buf.contents = "";
|
|
60
|
+
cb(last);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function onStdoutLine(proc, cb) {
|
|
65
|
+
onLines(proc.stdout, cb);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function onStderrLine(proc, cb) {
|
|
69
|
+
onLines(proc.stderr, cb);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function onClose(proc, cb) {
|
|
73
|
+
proc.on("close", code => cb((code == null) ? undefined : Primitive_option.some(code)));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function onError(proc, cb) {
|
|
77
|
+
proc.on("error", e => cb(Stdlib_Option.getOr(Stdlib_JsExn.message(e), "spawn failed")));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function killTree(proc, signal) {
|
|
81
|
+
let p = Primitive_option.fromNullable(proc.pid);
|
|
82
|
+
if (p !== undefined) {
|
|
83
|
+
try {
|
|
84
|
+
process.kill(-p | 0, signal);
|
|
85
|
+
return;
|
|
86
|
+
} catch (exn) {
|
|
87
|
+
proc.kill(signal);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
} else {
|
|
91
|
+
proc.kill(signal);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export {
|
|
97
|
+
mergeEnv,
|
|
98
|
+
spawn,
|
|
99
|
+
pid,
|
|
100
|
+
onLines,
|
|
101
|
+
onStdoutLine,
|
|
102
|
+
onStderrLine,
|
|
103
|
+
onClose,
|
|
104
|
+
onError,
|
|
105
|
+
killTree,
|
|
106
|
+
}
|
|
107
|
+
/* Stdlib_JsExn Not a pure module */
|