@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.
Files changed (184) hide show
  1. package/CHANGELOG.md +916 -0
  2. package/LICENSE +202 -0
  3. package/bin/reventless-dev.mjs +21 -0
  4. package/package.json +54 -0
  5. package/rescript.json +36 -0
  6. package/src/.gwtignore +10 -0
  7. package/src/Automation_GWT.res +207 -0
  8. package/src/Automation_GWT.res.mjs +160 -0
  9. package/src/Behavior_GWT.res +569 -0
  10. package/src/Behavior_GWT.res.mjs +523 -0
  11. package/src/Bind.res +29 -0
  12. package/src/Bind.res.mjs +22 -0
  13. package/src/BuildClassifier.res +147 -0
  14. package/src/BuildClassifier.res.mjs +136 -0
  15. package/src/Cancellation.res +37 -0
  16. package/src/Cancellation.res.mjs +46 -0
  17. package/src/ChildProcess.res +102 -0
  18. package/src/ChildProcess.res.mjs +107 -0
  19. package/src/Cli.res +905 -0
  20. package/src/Cli.res.mjs +952 -0
  21. package/src/Collector.res +212 -0
  22. package/src/Collector.res.mjs +218 -0
  23. package/src/ComponentMeta.res +77 -0
  24. package/src/ComponentMeta.res.mjs +91 -0
  25. package/src/ComponentScan.res +103 -0
  26. package/src/ComponentScan.res.mjs +105 -0
  27. package/src/Delegate_GWT.res +303 -0
  28. package/src/Delegate_GWT.res.mjs +372 -0
  29. package/src/Diff.res +91 -0
  30. package/src/Diff.res.mjs +98 -0
  31. package/src/Discovery.res +105 -0
  32. package/src/Discovery.res.mjs +99 -0
  33. package/src/DomainDeadCode.res +98 -0
  34. package/src/DomainDeadCode.res.mjs +72 -0
  35. package/src/DomainGraph.res +0 -0
  36. package/src/DomainGraph.res.mjs +0 -0
  37. package/src/EventMapping_GWT.res +29 -0
  38. package/src/EventMapping_GWT.res.mjs +44 -0
  39. package/src/Filter.res +45 -0
  40. package/src/Filter.res.mjs +47 -0
  41. package/src/Flow_GWT.res +664 -0
  42. package/src/Flow_GWT.res.mjs +604 -0
  43. package/src/FormatterHuman.res +109 -0
  44. package/src/FormatterHuman.res.mjs +115 -0
  45. package/src/FormatterJson.res +264 -0
  46. package/src/FormatterJson.res.mjs +322 -0
  47. package/src/FormatterJunit.res +93 -0
  48. package/src/FormatterJunit.res.mjs +90 -0
  49. package/src/FormatterTap.res +100 -0
  50. package/src/FormatterTap.res.mjs +131 -0
  51. package/src/FormatterVsCode.res +352 -0
  52. package/src/FormatterVsCode.res.mjs +546 -0
  53. package/src/Hint.res +119 -0
  54. package/src/Hint.res.mjs +124 -0
  55. package/src/InboundTranslation_GWT.res +83 -0
  56. package/src/InboundTranslation_GWT.res.mjs +94 -0
  57. package/src/JestBind.res +70 -0
  58. package/src/JestBind.res.mjs +63 -0
  59. package/src/Loader.res +24 -0
  60. package/src/Loader.res.mjs +23 -0
  61. package/src/LocalHost.res +119 -0
  62. package/src/LocalHost.res.mjs +99 -0
  63. package/src/Mapping_GWT.res +474 -0
  64. package/src/Mapping_GWT.res.mjs +322 -0
  65. package/src/MismatchRender.res +72 -0
  66. package/src/MismatchRender.res.mjs +127 -0
  67. package/src/MultiSourceProjection_GWT.res +316 -0
  68. package/src/MultiSourceProjection_GWT.res.mjs +354 -0
  69. package/src/OutboundTranslation_GWT.res +238 -0
  70. package/src/OutboundTranslation_GWT.res.mjs +186 -0
  71. package/src/Outcome.res +97 -0
  72. package/src/Outcome.res.mjs +93 -0
  73. package/src/PackageScan.res +80 -0
  74. package/src/PackageScan.res.mjs +81 -0
  75. package/src/PlatformRunner.res +181 -0
  76. package/src/PlatformRunner.res.mjs +176 -0
  77. package/src/PlatformScan.res +142 -0
  78. package/src/PlatformScan.res.mjs +132 -0
  79. package/src/ProcessManager.res +135 -0
  80. package/src/ProcessManager.res.mjs +119 -0
  81. package/src/Projection_GWT.res +312 -0
  82. package/src/Projection_GWT.res.mjs +332 -0
  83. package/src/Query_GWT.res +379 -0
  84. package/src/Query_GWT.res.mjs +367 -0
  85. package/src/RenderRescript.res +125 -0
  86. package/src/RenderRescript.res.mjs +141 -0
  87. package/src/RunWorker.res +24 -0
  88. package/src/RunWorker.res.mjs +16 -0
  89. package/src/RunnerTypes.res +68 -0
  90. package/src/RunnerTypes.res.mjs +60 -0
  91. package/src/SideEffect_GWT.res +157 -0
  92. package/src/SideEffect_GWT.res.mjs +113 -0
  93. package/src/StateChange/ExternalAddCategorySlice.res +25 -0
  94. package/src/StateChange/ExternalAddCategorySlice.res.mjs +34 -0
  95. package/src/StateChange/ExternalAddCategorySlice_Behavior.res +19 -0
  96. package/src/StateChange/ExternalAddCategorySlice_Behavior.res.mjs +46 -0
  97. package/src/StateChange/WithFixtures.res +22 -0
  98. package/src/StateChange/WithFixtures.res.mjs +31 -0
  99. package/src/StateChange/WithFixtures_Behavior.res +17 -0
  100. package/src/StateChange/WithFixtures_Behavior.res.mjs +37 -0
  101. package/src/StateChange/WithManualOpen.res +23 -0
  102. package/src/StateChange/WithManualOpen.res.mjs +31 -0
  103. package/src/StateChange/WithManualOpen_Behavior.res +17 -0
  104. package/src/StateChange/WithManualOpen_Behavior.res.mjs +37 -0
  105. package/src/StubRuntime.res +54 -0
  106. package/src/StubRuntime.res.mjs +55 -0
  107. package/src/TestFixtures.res +16 -0
  108. package/src/TestFixtures.res.mjs +34 -0
  109. package/src/Watch.res +108 -0
  110. package/src/Watch.res.mjs +85 -0
  111. package/src/WatcherProbe.res +56 -0
  112. package/src/WatcherProbe.res.mjs +59 -0
  113. package/src/Worker.res +24 -0
  114. package/src/Worker.res.mjs +9 -0
  115. package/tests/AutomationGwtTest.res +71 -0
  116. package/tests/AutomationGwtTest.res.mjs +189 -0
  117. package/tests/BuildClassifierTest.res +133 -0
  118. package/tests/BuildClassifierTest.res.mjs +176 -0
  119. package/tests/CliRunEntryTest.res +57 -0
  120. package/tests/CliRunEntryTest.res.mjs +65 -0
  121. package/tests/CliWatchScopeTest.res +62 -0
  122. package/tests/CliWatchScopeTest.res.mjs +95 -0
  123. package/tests/ComponentMetaTest.res +67 -0
  124. package/tests/ComponentMetaTest.res.mjs +71 -0
  125. package/tests/DelegateGwtTest.res +487 -0
  126. package/tests/DelegateGwtTest.res.mjs +891 -0
  127. package/tests/DiscoveryTest.res +42 -0
  128. package/tests/DiscoveryTest.res.mjs +43 -0
  129. package/tests/DomainDeadCodeTest.res +119 -0
  130. package/tests/DomainDeadCodeTest.res.mjs +158 -0
  131. package/tests/DomainGraphTest.res +448 -0
  132. package/tests/DomainGraphTest.res.mjs +505 -0
  133. package/tests/FlowAggregateGwtTest.res +171 -0
  134. package/tests/FlowAggregateGwtTest.res.mjs +273 -0
  135. package/tests/FlowCrossPluginGwtTest.res +340 -0
  136. package/tests/FlowCrossPluginGwtTest.res.mjs +798 -0
  137. package/tests/FlowGwtTest.res +222 -0
  138. package/tests/FlowGwtTest.res.mjs +433 -0
  139. package/tests/FormatterGoldenTest.res +125 -0
  140. package/tests/FormatterGoldenTest.res.mjs +162 -0
  141. package/tests/FormatterVsCodeMessageTest.res +41 -0
  142. package/tests/FormatterVsCodeMessageTest.res.mjs +42 -0
  143. package/tests/InboundTranslationGwtTest.res +34 -0
  144. package/tests/InboundTranslationGwtTest.res.mjs +99 -0
  145. package/tests/LocalHostIntegration.res +70 -0
  146. package/tests/LocalHostIntegration.res.mjs +64 -0
  147. package/tests/LocalHostTest.res +71 -0
  148. package/tests/LocalHostTest.res.mjs +63 -0
  149. package/tests/MappingGwtTest.res +350 -0
  150. package/tests/MappingGwtTest.res.mjs +596 -0
  151. package/tests/OutboundTranslationGwtTest.res +59 -0
  152. package/tests/OutboundTranslationGwtTest.res.mjs +139 -0
  153. package/tests/PlatformRunnerTest.res +66 -0
  154. package/tests/PlatformRunnerTest.res.mjs +85 -0
  155. package/tests/PlatformScanTest.res +61 -0
  156. package/tests/PlatformScanTest.res.mjs +56 -0
  157. package/tests/QueryGwtTest.res +161 -0
  158. package/tests/QueryGwtTest.res.mjs +314 -0
  159. package/tests/QueryResolverGwtTest.res +71 -0
  160. package/tests/QueryResolverGwtTest.res.mjs +136 -0
  161. package/tests/RunnerUnitTest.res +219 -0
  162. package/tests/RunnerUnitTest.res.mjs +244 -0
  163. package/tests/SideEffect/FakeOrderNotification.res +23 -0
  164. package/tests/SideEffect/FakeOrderNotification.res.mjs +36 -0
  165. package/tests/SideEffect/FakeOrderNotificationGwtTest.res +35 -0
  166. package/tests/SideEffect/FakeOrderNotificationGwtTest.res.mjs +73 -0
  167. package/tests/SideEffect/MockEmail.res +18 -0
  168. package/tests/SideEffect/MockEmail.res.mjs +47 -0
  169. package/tests/StateChange/ExternalAddCategorySlice_GWT.res +27 -0
  170. package/tests/StateChange/ExternalAddCategorySlice_GWT.res.mjs +70 -0
  171. package/tests/StateChange/WithFixtures_Fixtures.res +9 -0
  172. package/tests/StateChange/WithFixtures_Fixtures.res.mjs +20 -0
  173. package/tests/StateChange/WithFixtures_GWT.res +23 -0
  174. package/tests/StateChange/WithFixtures_GWT.res.mjs +56 -0
  175. package/tests/StateChange/WithManualOpen_Fixtures.res +7 -0
  176. package/tests/StateChange/WithManualOpen_Fixtures.res.mjs +20 -0
  177. package/tests/StateChange/WithManualOpen_GWT.res +17 -0
  178. package/tests/StateChange/WithManualOpen_GWT.res.mjs +56 -0
  179. package/tests/StateChangeSliceGwtTest.res +160 -0
  180. package/tests/StateChangeSliceGwtTest.res.mjs +280 -0
  181. package/tests/StateViewSliceGwtTest.res +64 -0
  182. package/tests/StateViewSliceGwtTest.res.mjs +161 -0
  183. package/tests/WatchDebounceTest.res +106 -0
  184. 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 */