@reventlessdev/reventless-gwt 1.0.0-alpha.113 → 1.0.0-alpha.115
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 +18 -0
- package/package.json +5 -5
- package/src/Cli.res +4 -20
- package/src/Cli.res.mjs +10 -18
- package/src/Collector.res +15 -5
- package/src/Collector.res.mjs +10 -0
- package/src/ComponentScan.res +1 -3
- package/src/ComponentScan.res.mjs +2 -15
- package/src/Discovery.res +2 -5
- package/src/Discovery.res.mjs +2 -15
- package/src/ExnMessage.res +14 -0
- package/src/ExnMessage.res.mjs +17 -0
- package/src/ExnReflect.mjs +24 -0
- package/src/FormatterVsCode.res +1 -1
- package/src/JestBind.res +21 -22
- package/src/JestBind.res.mjs +15 -11
- package/src/MultiSourceProjection_GWT.res.mjs +3 -3
- package/src/PlatformRunner.res +15 -5
- package/src/PlatformRunner.res.mjs +3 -1
- package/src/PlatformScan.res +1 -2
- package/src/PlatformScan.res.mjs +2 -10
- package/src/Projection_GWT.res.mjs +3 -2
- package/src/RunnerHook.res +38 -0
- package/src/RunnerHook.res.mjs +26 -0
- package/src/ScanIgnore.res +11 -0
- package/src/ScanIgnore.res.mjs +23 -0
- package/src/Watch.res +2 -8
- package/src/Watch.res.mjs +2 -7
- package/tests/ExnMessageTest.res +42 -0
- package/tests/ExnMessageTest.res.mjs +64 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,24 @@
|
|
|
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.115 (2026-07-15)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @reventlessdev/reventless-gwt
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# 1.0.0-alpha.114 (2026-07-14)
|
|
15
|
+
|
|
16
|
+
### Bug Fixes
|
|
17
|
+
|
|
18
|
+
* show debug logs on the local platform (both launch paths) ([81837d7](https://github.com/ReventlessDev/reventless-core/commit/81837d7e02abe1065a722b43b195adef2c836794))
|
|
19
|
+
### Features
|
|
20
|
+
|
|
21
|
+
* **core:** thread ~comp through Projection.handleAction for attributed action logging ([fa32d93](https://github.com/ReventlessDev/reventless-core/commit/fa32d93dedabea1b1f655f01a4ea769a5908b143))
|
|
22
|
+
|
|
23
|
+
|
|
6
24
|
# 1.0.0-alpha.113 (2026-07-14)
|
|
7
25
|
|
|
8
26
|
**Note:** Version bump only for package @reventlessdev/reventless-gwt
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reventlessdev/reventless-gwt",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.115",
|
|
4
4
|
"description": "Given-When-Then DSLs and CLI runner for Reventless slice testing",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"bin": {
|
|
@@ -21,11 +21,11 @@
|
|
|
21
21
|
"picocolors": "^1.1.1",
|
|
22
22
|
"sury": "11.0.0-alpha.4",
|
|
23
23
|
"@reventlessdev/rescript-jest": "1.0.0-alpha.7",
|
|
24
|
-
"@reventlessdev/reventless-core": "3.0.0-alpha.167",
|
|
25
|
-
"@reventlessdev/reventless-domain-protocol": "1.0.0-alpha.20",
|
|
26
24
|
"@reventlessdev/rescript-effect": "0.1.0-alpha.27",
|
|
27
|
-
"@reventlessdev/reventless-
|
|
28
|
-
"@reventlessdev/reventless-
|
|
25
|
+
"@reventlessdev/reventless-core": "3.0.0-alpha.169",
|
|
26
|
+
"@reventlessdev/reventless-infra": "3.0.0-alpha.100",
|
|
27
|
+
"@reventlessdev/reventless-domain-protocol": "1.0.0-alpha.20",
|
|
28
|
+
"@reventlessdev/reventless-spec": "3.0.0-alpha.77"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"rescript": "^12.3.0",
|
package/src/Cli.res
CHANGED
|
@@ -60,7 +60,7 @@ let sha256 = (s: string): string => createHash("sha256")->hashUpdate(s)->hashDig
|
|
|
60
60
|
// re-emit forces the client to re-render the domain view for nothing.
|
|
61
61
|
let lastDomainHash: ref<option<string>> = ref(None)
|
|
62
62
|
|
|
63
|
-
let dateNowIso
|
|
63
|
+
let dateNowIso = (): string => Date.make()->Date.toISOString
|
|
64
64
|
|
|
65
65
|
let parseFormat = (s: string) =>
|
|
66
66
|
switch s {
|
|
@@ -229,23 +229,6 @@ let passesFilter = (id: string, filters: array<string>) =>
|
|
|
229
229
|
filters->Array.length == 0 ||
|
|
230
230
|
filters->Array.some(f => id->String.includes(f))
|
|
231
231
|
|
|
232
|
-
// Extract a human-readable message from any thrown value. JS Errors carry a
|
|
233
|
-
// `.message`; ReScript exceptions are tagged objects (`RE_EXN_ID` + an optional
|
|
234
|
-
// string payload), e.g. `failwith("…")` raises `Failure("…")`. Without this the
|
|
235
|
-
// catch block below collapsed every ReScript exception to "unknown error",
|
|
236
|
-
// hiding messages like a slice's `failwith("not implemented: …")`.
|
|
237
|
-
let exnMessage: exn => string = %raw(`function(e) {
|
|
238
|
-
if (e == null) return "unknown error"
|
|
239
|
-
if (typeof e === "string") return e
|
|
240
|
-
if (typeof e.message === "string" && e.message.length) return e.message
|
|
241
|
-
if (typeof e.RE_EXN_ID === "string") {
|
|
242
|
-
var id = e.RE_EXN_ID
|
|
243
|
-
var tag = id.lastIndexOf(".") >= 0 ? id.slice(id.lastIndexOf(".") + 1) : id
|
|
244
|
-
return (typeof e._1 === "string" && e._1.length) ? e._1 : tag
|
|
245
|
-
}
|
|
246
|
-
return "unknown error"
|
|
247
|
-
}`)
|
|
248
|
-
|
|
249
232
|
// Default per-test deadline (ms) when a test body doesn't set `~timeout`.
|
|
250
233
|
// Mirrors Jest's default so behaviour is consistent across both runners.
|
|
251
234
|
let defaultTimeoutMs = 5000
|
|
@@ -304,8 +287,8 @@ let runEntry = async (entry: Collector.entry): RunnerTypes.testResult => {
|
|
|
304
287
|
} catch {
|
|
305
288
|
| exn => {
|
|
306
289
|
status := Fail
|
|
307
|
-
let err =
|
|
308
|
-
let stack
|
|
290
|
+
let err = ExnMessage.extract(exn)
|
|
291
|
+
let stack = ExnMessage.stack(exn)
|
|
309
292
|
mismatch := Some(Outcome.Throw({error: err, stack: stack}))
|
|
310
293
|
}
|
|
311
294
|
}
|
|
@@ -326,6 +309,7 @@ let runEntry = async (entry: Collector.entry): RunnerTypes.testResult => {
|
|
|
326
309
|
|
|
327
310
|
let loadAndCollect = async (path: string): array<Collector.entry> => {
|
|
328
311
|
Collector.activate()
|
|
312
|
+
RunnerHook.register(Collector.asSink)
|
|
329
313
|
Collector.setCurrentFile(path)
|
|
330
314
|
try {
|
|
331
315
|
await Loader.loadFile(path)
|
package/src/Cli.res.mjs
CHANGED
|
@@ -16,6 +16,8 @@ import * as Outcome$ReventlessGwt from "./Outcome.res.mjs";
|
|
|
16
16
|
import * as Collector$ReventlessGwt from "./Collector.res.mjs";
|
|
17
17
|
import * as Discovery$ReventlessGwt from "./Discovery.res.mjs";
|
|
18
18
|
import * as LocalHost$ReventlessGwt from "./LocalHost.res.mjs";
|
|
19
|
+
import * as ExnMessage$ReventlessGwt from "./ExnMessage.res.mjs";
|
|
20
|
+
import * as RunnerHook$ReventlessGwt from "./RunnerHook.res.mjs";
|
|
19
21
|
import * as DomainGraph$ReventlessGwt from "./DomainGraph.res.mjs";
|
|
20
22
|
import * as PackageScan$ReventlessGwt from "./PackageScan.res.mjs";
|
|
21
23
|
import * as RunnerTypes$ReventlessGwt from "./RunnerTypes.res.mjs";
|
|
@@ -44,7 +46,9 @@ let lastDomainHash = {
|
|
|
44
46
|
contents: undefined
|
|
45
47
|
};
|
|
46
48
|
|
|
47
|
-
|
|
49
|
+
function dateNowIso() {
|
|
50
|
+
return new Date().toISOString();
|
|
51
|
+
}
|
|
48
52
|
|
|
49
53
|
function parseFormat(s) {
|
|
50
54
|
switch (s) {
|
|
@@ -320,18 +324,6 @@ function passesFilter(id, filters) {
|
|
|
320
324
|
}
|
|
321
325
|
}
|
|
322
326
|
|
|
323
|
-
let exnMessage = (function(e) {
|
|
324
|
-
if (e == null) return "unknown error"
|
|
325
|
-
if (typeof e === "string") return e
|
|
326
|
-
if (typeof e.message === "string" && e.message.length) return e.message
|
|
327
|
-
if (typeof e.RE_EXN_ID === "string") {
|
|
328
|
-
var id = e.RE_EXN_ID
|
|
329
|
-
var tag = id.lastIndexOf(".") >= 0 ? id.slice(id.lastIndexOf(".") + 1) : id
|
|
330
|
-
return (typeof e._1 === "string" && e._1.length) ? e._1 : tag
|
|
331
|
-
}
|
|
332
|
-
return "unknown error"
|
|
333
|
-
});
|
|
334
|
-
|
|
335
327
|
function raceWithTimeout(body, ms) {
|
|
336
328
|
let handleRef = {
|
|
337
329
|
contents: undefined
|
|
@@ -383,8 +375,8 @@ async function runEntry(entry) {
|
|
|
383
375
|
} catch (raw_exn) {
|
|
384
376
|
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
385
377
|
status = "Fail";
|
|
386
|
-
let err =
|
|
387
|
-
let stack =
|
|
378
|
+
let err = ExnMessage$ReventlessGwt.extract(exn);
|
|
379
|
+
let stack = ExnMessage$ReventlessGwt.stack(exn);
|
|
388
380
|
mismatch = {
|
|
389
381
|
TAG: "Throw",
|
|
390
382
|
error: err,
|
|
@@ -408,6 +400,7 @@ async function runEntry(entry) {
|
|
|
408
400
|
|
|
409
401
|
async function loadAndCollect(path) {
|
|
410
402
|
Collector$ReventlessGwt.activate();
|
|
403
|
+
RunnerHook$ReventlessGwt.register(Collector$ReventlessGwt.asSink);
|
|
411
404
|
Collector$ReventlessGwt.setCurrentFile(path);
|
|
412
405
|
try {
|
|
413
406
|
await Loader$ReventlessGwt.loadFile(path);
|
|
@@ -426,7 +419,7 @@ async function loadAndCollect(path) {
|
|
|
426
419
|
}
|
|
427
420
|
|
|
428
421
|
async function runFiles(opts, paths, onFileFinished, onTestStart, onTestFinished) {
|
|
429
|
-
let startedAt =
|
|
422
|
+
let startedAt = new Date().toISOString();
|
|
430
423
|
let startTime = performance.now();
|
|
431
424
|
let files = [];
|
|
432
425
|
for (let i = 0, i_finish = paths.length; i < i_finish; ++i) {
|
|
@@ -560,7 +553,7 @@ async function runOnce(opts) {
|
|
|
560
553
|
let p = opts.paths;
|
|
561
554
|
let paths = p !== undefined ? p : await Discovery$ReventlessGwt.discover(opts.roots);
|
|
562
555
|
if (opts.format === "Json" && opts.stream) {
|
|
563
|
-
FormatterJson$ReventlessGwt.streamRunStart(opts.toolVersion,
|
|
556
|
+
FormatterJson$ReventlessGwt.streamRunStart(opts.toolVersion, new Date().toISOString(), opts.schemaVersion);
|
|
564
557
|
}
|
|
565
558
|
let match = opts.format;
|
|
566
559
|
let match$1 = opts.stream;
|
|
@@ -929,7 +922,6 @@ export {
|
|
|
929
922
|
help,
|
|
930
923
|
parseArgv,
|
|
931
924
|
passesFilter,
|
|
932
|
-
exnMessage,
|
|
933
925
|
defaultTimeoutMs,
|
|
934
926
|
raceWithTimeout,
|
|
935
927
|
runEntry,
|
package/src/Collector.res
CHANGED
|
@@ -11,11 +11,9 @@
|
|
|
11
11
|
|
|
12
12
|
type status = Skipped | Runnable | Only
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
column: int,
|
|
18
|
-
}
|
|
14
|
+
// Source location shape is shared with `RunnerHook` so this collector can be
|
|
15
|
+
// exposed as a `RunnerHook.sink` without a conversion layer.
|
|
16
|
+
type location = RunnerHook.location
|
|
19
17
|
|
|
20
18
|
type entry = {
|
|
21
19
|
id: string,
|
|
@@ -210,3 +208,15 @@ let captureLocation = (skip: int): option<location> => {
|
|
|
210
208
|
find(skip + 1)
|
|
211
209
|
}
|
|
212
210
|
}
|
|
211
|
+
|
|
212
|
+
// Expose this collector as a `RunnerHook.sink` so a driver can route `JestBind`
|
|
213
|
+
// registrations here. `captureLocation` is bound directly (no wrapper closure)
|
|
214
|
+
// so the stack depth `JestBind` skips when capturing the caller's location is
|
|
215
|
+
// unchanged from calling `captureLocation` inline.
|
|
216
|
+
let asSink: RunnerHook.sink = {
|
|
217
|
+
describe: pushDescribe,
|
|
218
|
+
todo: pushTodo,
|
|
219
|
+
captureLocation,
|
|
220
|
+
test: (~slice=?, ~location=?, ~timeout=?, name, body) =>
|
|
221
|
+
push(~slice?, ~location?, ~timeout?, name, body),
|
|
222
|
+
}
|
package/src/Collector.res.mjs
CHANGED
|
@@ -191,6 +191,15 @@ function captureLocation(skip) {
|
|
|
191
191
|
};
|
|
192
192
|
}
|
|
193
193
|
|
|
194
|
+
let asSink_test = push;
|
|
195
|
+
|
|
196
|
+
let asSink = {
|
|
197
|
+
describe: pushDescribe,
|
|
198
|
+
todo: pushTodo,
|
|
199
|
+
captureLocation: captureLocation,
|
|
200
|
+
test: asSink_test
|
|
201
|
+
};
|
|
202
|
+
|
|
194
203
|
export {
|
|
195
204
|
active,
|
|
196
205
|
describeStack,
|
|
@@ -214,5 +223,6 @@ export {
|
|
|
214
223
|
drain,
|
|
215
224
|
parseFrame,
|
|
216
225
|
captureLocation,
|
|
226
|
+
asSink,
|
|
217
227
|
}
|
|
218
228
|
/* No side effect */
|
package/src/ComponentScan.res
CHANGED
|
@@ -26,8 +26,6 @@ external _readdir: (string, readdirOpts) => promise<array<dirent>> = "readdir"
|
|
|
26
26
|
@module("node:fs") external _existsSync: string => bool = "existsSync"
|
|
27
27
|
@module("node:path") external join: (string, string) => string = "join"
|
|
28
28
|
|
|
29
|
-
let ignoreNames = ["node_modules", ".git", "dist", "lib", ".history"]
|
|
30
|
-
let shouldIgnore = (name: string) => Array.includes(ignoreNames, name)
|
|
31
29
|
|
|
32
30
|
// A directory carrying this sentinel file — and its whole subtree — is pruned
|
|
33
31
|
// from the component scan, matching `Discovery`'s `.gwtignore` convention so
|
|
@@ -51,7 +49,7 @@ let rec walk = async (dir: string, acc: array<string>): array<string> => {
|
|
|
51
49
|
let found = ref(acc)
|
|
52
50
|
for i in 0 to entries->Array.length - 1 {
|
|
53
51
|
let entry = entries->Array.getUnsafe(i)
|
|
54
|
-
if !shouldIgnore(entry.name) {
|
|
52
|
+
if !ScanIgnore.shouldIgnore(entry.name) {
|
|
55
53
|
let full = join(dir, entry.name)
|
|
56
54
|
if entry._isDirectory() {
|
|
57
55
|
let nested = await walk(full, [])
|
|
@@ -4,20 +4,9 @@ import * as Nodefs from "node:fs";
|
|
|
4
4
|
import * as Nodepath from "node:path";
|
|
5
5
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
6
6
|
import * as Promises from "node:fs/promises";
|
|
7
|
+
import * as ScanIgnore$ReventlessGwt from "./ScanIgnore.res.mjs";
|
|
7
8
|
import * as ComponentMeta$ReventlessGwt from "./ComponentMeta.res.mjs";
|
|
8
9
|
|
|
9
|
-
let ignoreNames = [
|
|
10
|
-
"node_modules",
|
|
11
|
-
".git",
|
|
12
|
-
"dist",
|
|
13
|
-
"lib",
|
|
14
|
-
".history"
|
|
15
|
-
];
|
|
16
|
-
|
|
17
|
-
function shouldIgnore(name) {
|
|
18
|
-
return ignoreNames.includes(name);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
10
|
let gwtIgnoreFile = ".gwtignore";
|
|
22
11
|
|
|
23
12
|
function isPruned(entries) {
|
|
@@ -43,7 +32,7 @@ async function walk(dir, acc) {
|
|
|
43
32
|
let found = acc;
|
|
44
33
|
for (let i = 0, i_finish = entries.length; i < i_finish; ++i) {
|
|
45
34
|
let entry = entries[i];
|
|
46
|
-
if (!
|
|
35
|
+
if (!ScanIgnore$ReventlessGwt.shouldIgnore(entry.name)) {
|
|
47
36
|
let full = Nodepath.join(dir, entry.name);
|
|
48
37
|
if (entry.isDirectory()) {
|
|
49
38
|
let nested = await walk(full, []);
|
|
@@ -94,8 +83,6 @@ async function scan(pkgDirs) {
|
|
|
94
83
|
}
|
|
95
84
|
|
|
96
85
|
export {
|
|
97
|
-
ignoreNames,
|
|
98
|
-
shouldIgnore,
|
|
99
86
|
gwtIgnoreFile,
|
|
100
87
|
isPruned,
|
|
101
88
|
isSrcResFile,
|
package/src/Discovery.res
CHANGED
|
@@ -28,11 +28,8 @@ type stats = {
|
|
|
28
28
|
@module("node:path") external isAbsolute: string => bool = "isAbsolute"
|
|
29
29
|
@module("node:path") external resolve: string => string = "resolve"
|
|
30
30
|
|
|
31
|
-
let ignoreNames = ["node_modules", ".git", "dist", "lib", ".history"]
|
|
32
|
-
let shouldIgnore = (name: string) => Array.includes(ignoreNames, name)
|
|
33
|
-
|
|
34
31
|
// A directory carrying this sentinel file — and its whole subtree — is pruned
|
|
35
|
-
// from discovery. Unlike the `
|
|
32
|
+
// from discovery. Unlike the `ScanIgnore.names` dir-name list, this is filesystem
|
|
36
33
|
// state, so it excludes generated/vendored trees (e.g. codegen golden fixtures)
|
|
37
34
|
// even when compiled `*_GWT.res.mjs` are present on disk.
|
|
38
35
|
let gwtIgnoreFile = ".gwtignore"
|
|
@@ -58,7 +55,7 @@ let rec walk = async (dir: string, acc: array<string>): unit => {
|
|
|
58
55
|
} else {
|
|
59
56
|
for i in 0 to entries->Array.length - 1 {
|
|
60
57
|
let entry = entries->Array.getUnsafe(i)
|
|
61
|
-
if !shouldIgnore(entry.name) {
|
|
58
|
+
if !ScanIgnore.shouldIgnore(entry.name) {
|
|
62
59
|
let full = join(dir, entry.name)
|
|
63
60
|
if entry._isDirectory() {
|
|
64
61
|
await walk(full, acc)
|
package/src/Discovery.res.mjs
CHANGED
|
@@ -2,18 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import * as Nodepath from "node:path";
|
|
4
4
|
import * as Promises from "node:fs/promises";
|
|
5
|
-
|
|
6
|
-
let ignoreNames = [
|
|
7
|
-
"node_modules",
|
|
8
|
-
".git",
|
|
9
|
-
"dist",
|
|
10
|
-
"lib",
|
|
11
|
-
".history"
|
|
12
|
-
];
|
|
13
|
-
|
|
14
|
-
function shouldIgnore(name) {
|
|
15
|
-
return ignoreNames.includes(name);
|
|
16
|
-
}
|
|
5
|
+
import * as ScanIgnore$ReventlessGwt from "./ScanIgnore.res.mjs";
|
|
17
6
|
|
|
18
7
|
let gwtIgnoreFile = ".gwtignore";
|
|
19
8
|
|
|
@@ -43,7 +32,7 @@ async function walk(dir, acc) {
|
|
|
43
32
|
}
|
|
44
33
|
for (let i = 0, i_finish = entries.length; i < i_finish; ++i) {
|
|
45
34
|
let entry = entries[i];
|
|
46
|
-
if (!
|
|
35
|
+
if (!ScanIgnore$ReventlessGwt.shouldIgnore(entry.name)) {
|
|
47
36
|
let full = Nodepath.join(dir, entry.name);
|
|
48
37
|
if (entry.isDirectory()) {
|
|
49
38
|
await walk(full, acc);
|
|
@@ -88,8 +77,6 @@ async function discover(roots) {
|
|
|
88
77
|
}
|
|
89
78
|
|
|
90
79
|
export {
|
|
91
|
-
ignoreNames,
|
|
92
|
-
shouldIgnore,
|
|
93
80
|
gwtIgnoreFile,
|
|
94
81
|
isPruned,
|
|
95
82
|
isGwtTestFile,
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Typed bindings for the exception reflection in the companion `ExnReflect.mjs`.
|
|
2
|
+
// Inspecting an arbitrary thrown value's shape (`.message`, `RE_EXN_ID`/`_1`,
|
|
3
|
+
// `.stack`, a bare string, `throw null`) is untyped work ReScript can't
|
|
4
|
+
// pattern-match, so it lives whole in that JS module — no `%raw`, no `Obj.magic`
|
|
5
|
+
// here. Used by the runner's catch block to surface a test body's real error
|
|
6
|
+
// (e.g. a slice's `failwith("not implemented: …")`) instead of "unknown error".
|
|
7
|
+
|
|
8
|
+
// A human-readable message from any thrown value.
|
|
9
|
+
@module("./ExnReflect.mjs")
|
|
10
|
+
external extract: exn => string = "extractMessage"
|
|
11
|
+
|
|
12
|
+
// The `.stack` of a thrown JS `Error`, or "" when absent.
|
|
13
|
+
@module("./ExnReflect.mjs")
|
|
14
|
+
external stack: exn => string = "stackOf"
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as ExnReflectMjs from "./ExnReflect.mjs";
|
|
4
|
+
|
|
5
|
+
function extract(prim) {
|
|
6
|
+
return ExnReflectMjs.extractMessage(prim);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function stack(prim) {
|
|
10
|
+
return ExnReflectMjs.stackOf(prim);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export {
|
|
14
|
+
extract,
|
|
15
|
+
stack,
|
|
16
|
+
}
|
|
17
|
+
/* ./ExnReflect.mjs Not a pure module */
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Untyped reflection over an arbitrary thrown value — the shapes ReScript can't
|
|
2
|
+
// pattern-match. Kept whole in JS so the ReScript side (ExnMessage.res) is a
|
|
3
|
+
// clean typed binding with no `%raw` or `Obj.magic`.
|
|
4
|
+
|
|
5
|
+
// A human-readable message: a `throw null`, a bare thrown string, a JS `Error`
|
|
6
|
+
// (`.message`), or a ReScript exception's constructor payload (`RE_EXN_ID` +
|
|
7
|
+
// `_1`, e.g. `failwith("…")` raises `Failure`).
|
|
8
|
+
export function extractMessage(e) {
|
|
9
|
+
if (e == null) return "unknown error";
|
|
10
|
+
if (typeof e === "string") return e;
|
|
11
|
+
if (typeof e.message === "string" && e.message.length) return e.message;
|
|
12
|
+
if (typeof e.RE_EXN_ID === "string") {
|
|
13
|
+
const id = e.RE_EXN_ID;
|
|
14
|
+
const dot = id.lastIndexOf(".");
|
|
15
|
+
const tag = dot >= 0 ? id.slice(dot + 1) : id;
|
|
16
|
+
return typeof e._1 === "string" && e._1.length ? e._1 : tag;
|
|
17
|
+
}
|
|
18
|
+
return "unknown error";
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// The `.stack` of a thrown JS `Error`, or "" for anything without one.
|
|
22
|
+
export function stackOf(e) {
|
|
23
|
+
return (e && e.stack) || "";
|
|
24
|
+
}
|
package/src/FormatterVsCode.res
CHANGED
|
@@ -100,7 +100,7 @@ let locateInSource = (mjsPath: string, label: string): option<Collector.location
|
|
|
100
100
|
if col < 0 {
|
|
101
101
|
find(i + 1)
|
|
102
102
|
} else {
|
|
103
|
-
Some({
|
|
103
|
+
Some({RunnerHook.file: resPath, line: i + 1, column: col})
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
106
|
find(0)
|
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 {
|
|
@@ -45,9 +45,9 @@ function Make(Projection) {
|
|
|
45
45
|
let deleteSubState = (store, id, subId, getSubId) => {
|
|
46
46
|
store[id] = Stdlib_Option.getOr(Stdlib_Option.map(store[id], states => states.filter(state => Primitive_object.notequal(getSubId(state), subId))), []);
|
|
47
47
|
};
|
|
48
|
+
let handleActions = (actions, operations) => Projection$ReventlessCore.handleActions(undefined, actions, operations, Projection.subIdConfig);
|
|
48
49
|
let update = async (store, events$p) => {
|
|
49
|
-
|
|
50
|
-
await Projection$ReventlessCore.handleActions(actions, {
|
|
50
|
+
await handleActions(events$p.map(event$p => Projection.project(event$p)), {
|
|
51
51
|
load: extra => Promise.resolve({
|
|
52
52
|
TAG: "Ok",
|
|
53
53
|
_0: states(store, extra)
|
|
@@ -163,7 +163,7 @@ function Make(Projection) {
|
|
|
163
163
|
_0: undefined
|
|
164
164
|
});
|
|
165
165
|
}
|
|
166
|
-
}
|
|
166
|
+
});
|
|
167
167
|
if (Projection.subIdConfig !== undefined) {
|
|
168
168
|
return Stdlib_Dict.mapValues(store, states => states.toSorted((state1, state2) => Primitive_string.compare(getSubId(state1), getSubId(state2))));
|
|
169
169
|
} else {
|
package/src/PlatformRunner.res
CHANGED
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
|
|
12
12
|
let tapSentinel = "@@RVLESS_EVT@@ "
|
|
13
13
|
|
|
14
|
+
@val external _envLogLevel: option<string> = "process.env.LOG_LEVEL"
|
|
15
|
+
|
|
14
16
|
type lineClass =
|
|
15
17
|
| Domain(JSON.t) // a tap line; the parsed `{event:"domainEvent",…}` payload
|
|
16
18
|
| Ready // the Domain GraphQL server's "listening on …" line
|
|
@@ -110,6 +112,18 @@ let run = async (
|
|
|
110
112
|
let pPort = ports->Array.getUnsafe(1)
|
|
111
113
|
let dMcp = ports->Array.getUnsafe(2)
|
|
112
114
|
let pMcp = ports->Array.getUnsafe(3)
|
|
115
|
+
// The gwt bin defaults LOG_LEVEL=silent so its OWN stdout stays pure NDJSON;
|
|
116
|
+
// the platform child is a separate process whose stdout we parse, so it needs
|
|
117
|
+
// real logs — the "listening on" line drives readiness and the rest becomes
|
|
118
|
+
// platformLog. Default it to the local platform's own verbosity (debug — same
|
|
119
|
+
// as `pnpm run serve`, which relies on reventless-local's Debug default), so
|
|
120
|
+
// projection/action debug lines surface in the runner log. An explicit
|
|
121
|
+
// LOG_LEVEL the developer set (anything but the bin's silent default) is
|
|
122
|
+
// honored, so `LOG_LEVEL=info` still quietens it.
|
|
123
|
+
let childLogLevel = switch _envLogLevel {
|
|
124
|
+
| Some(l) if l != "silent" && l != "" => l
|
|
125
|
+
| _ => "debug"
|
|
126
|
+
}
|
|
113
127
|
let env = Dict.fromArray([
|
|
114
128
|
("REVENTLESS_EVENT_TAP", "ndjson"),
|
|
115
129
|
("REVENTLESS_LOCAL_BACKEND", backend),
|
|
@@ -118,11 +132,7 @@ let run = async (
|
|
|
118
132
|
("REVENTLESS_DOMAIN_MCP_PORT", Int.toString(dMcp)),
|
|
119
133
|
("REVENTLESS_PLATFORM_MCP_PORT", Int.toString(pMcp)),
|
|
120
134
|
("NODE_OPTIONS", "--disable-warning=ExperimentalWarning"),
|
|
121
|
-
|
|
122
|
-
// the platform child is a separate process whose stdout we parse, so it needs
|
|
123
|
-
// real logs — the "listening on" line drives readiness and the rest becomes
|
|
124
|
-
// platformLog. Override the inherited silent.
|
|
125
|
-
("LOG_LEVEL", "info"),
|
|
135
|
+
("LOG_LEVEL", childLogLevel),
|
|
126
136
|
// The child's stdout is a pipe (we line-parse it), so the framework's sink
|
|
127
137
|
// detection would auto-pick JSON (non-TTY). But every consumer of `onLog`
|
|
128
138
|
// renders ANSI — the CLI forwards to the developer's terminal, the extension
|
|
@@ -80,6 +80,8 @@ async function run(roots, backend, fixedPortsOpt, callbacks) {
|
|
|
80
80
|
let pPort = ports[1];
|
|
81
81
|
let dMcp = ports[2];
|
|
82
82
|
let pMcp = ports[3];
|
|
83
|
+
let l = process.env.LOG_LEVEL;
|
|
84
|
+
let childLogLevel = l !== undefined && l !== "silent" && l !== "" ? l : "debug";
|
|
83
85
|
let env = Object.fromEntries([
|
|
84
86
|
[
|
|
85
87
|
"REVENTLESS_EVENT_TAP",
|
|
@@ -111,7 +113,7 @@ async function run(roots, backend, fixedPortsOpt, callbacks) {
|
|
|
111
113
|
],
|
|
112
114
|
[
|
|
113
115
|
"LOG_LEVEL",
|
|
114
|
-
|
|
116
|
+
childLogLevel
|
|
115
117
|
],
|
|
116
118
|
[
|
|
117
119
|
"REVENTLESS_LOG_FORMAT",
|
package/src/PlatformScan.res
CHANGED
|
@@ -31,7 +31,6 @@ external _readdir: (string, readdirOpts) => promise<array<dirent>> = "readdir"
|
|
|
31
31
|
@module("node:path") external join: (string, string) => string = "join"
|
|
32
32
|
|
|
33
33
|
let localPlatformDep = "@reventlessdev/reventless-local"
|
|
34
|
-
let ignoreNames = ["node_modules", ".git", "dist", "lib", ".history"]
|
|
35
34
|
|
|
36
35
|
// Pure predicate: given a package.json's text and whether src/Main.res.mjs exists,
|
|
37
36
|
// decide whether this is a launchable platform package and surface its name +
|
|
@@ -116,7 +115,7 @@ let rec walk = async (dir: string, acc: array<platformPkg>): array<platformPkg>
|
|
|
116
115
|
}
|
|
117
116
|
for i in 0 to entries->Array.length - 1 {
|
|
118
117
|
let entry = entries->Array.getUnsafe(i)
|
|
119
|
-
if entry._isDirectory() && !
|
|
118
|
+
if entry._isDirectory() && !ScanIgnore.shouldIgnore(entry.name) {
|
|
120
119
|
let _ = await walk(join(dir, entry.name), acc)
|
|
121
120
|
}
|
|
122
121
|
}
|
package/src/PlatformScan.res.mjs
CHANGED
|
@@ -5,17 +5,10 @@ import * as Nodepath from "node:path";
|
|
|
5
5
|
import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
|
|
6
6
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
7
7
|
import * as Promises from "node:fs/promises";
|
|
8
|
+
import * as ScanIgnore$ReventlessGwt from "./ScanIgnore.res.mjs";
|
|
8
9
|
|
|
9
10
|
let localPlatformDep = "@reventlessdev/reventless-local";
|
|
10
11
|
|
|
11
|
-
let ignoreNames = [
|
|
12
|
-
"node_modules",
|
|
13
|
-
".git",
|
|
14
|
-
"dist",
|
|
15
|
-
"lib",
|
|
16
|
-
".history"
|
|
17
|
-
];
|
|
18
|
-
|
|
19
12
|
function matchPlatform(pkgJsonText, mainExists) {
|
|
20
13
|
if (!mainExists) {
|
|
21
14
|
return;
|
|
@@ -94,7 +87,7 @@ async function walk(dir, acc) {
|
|
|
94
87
|
}
|
|
95
88
|
for (let i = 0, i_finish = entries.length; i < i_finish; ++i) {
|
|
96
89
|
let entry = entries[i];
|
|
97
|
-
if (entry.isDirectory() && !
|
|
90
|
+
if (entry.isDirectory() && !ScanIgnore$ReventlessGwt.shouldIgnore(entry.name)) {
|
|
98
91
|
await walk(Nodepath.join(dir, entry.name), acc);
|
|
99
92
|
}
|
|
100
93
|
}
|
|
@@ -123,7 +116,6 @@ async function scan(roots) {
|
|
|
123
116
|
|
|
124
117
|
export {
|
|
125
118
|
localPlatformDep,
|
|
126
|
-
ignoreNames,
|
|
127
119
|
matchPlatform,
|
|
128
120
|
inspectDir,
|
|
129
121
|
walk,
|
|
@@ -59,9 +59,10 @@ function Make(Spec) {
|
|
|
59
59
|
let deleteSubState = (store, id, subId, getSubId) => {
|
|
60
60
|
store[id] = Stdlib_Option.getOr(Stdlib_Option.map(store[id], states => states.filter(state => Primitive_object.notequal(getSubId(state), subId))), []);
|
|
61
61
|
};
|
|
62
|
+
let runActions = (actions, operations) => Projection$ReventlessCore.handleActions(undefined, actions, operations, Spec.subIdConfig);
|
|
62
63
|
let update = async (store, events) => {
|
|
63
64
|
let actions = events.map(ev => Projection.project(ev)).flat();
|
|
64
|
-
await
|
|
65
|
+
await runActions(actions, {
|
|
65
66
|
load: extra => Promise.resolve({
|
|
66
67
|
TAG: "Ok",
|
|
67
68
|
_0: states(store, extra)
|
|
@@ -177,7 +178,7 @@ function Make(Spec) {
|
|
|
177
178
|
_0: undefined
|
|
178
179
|
});
|
|
179
180
|
}
|
|
180
|
-
}
|
|
181
|
+
});
|
|
181
182
|
if (Spec.subIdConfig !== undefined) {
|
|
182
183
|
return Stdlib_Dict.mapValues(store, states => states.toSorted((s1, s2) => Primitive_string.compare(getSubId(s1), getSubId(s2))));
|
|
183
184
|
} else {
|
|
@@ -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 */
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Single source for the directory names pruned from every monorepo scan — test
|
|
2
|
+
// discovery, component scan, platform scan — and the equivalent chokidar globs
|
|
3
|
+
// for the watch. Previously duplicated verbatim across the three walkers and,
|
|
4
|
+
// divergently, as inline globs in `Watch`; that drift once let `dist/` writes
|
|
5
|
+
// drive a re-run loop.
|
|
6
|
+
let names = ["node_modules", ".git", "dist", "lib", ".history"]
|
|
7
|
+
|
|
8
|
+
let shouldIgnore = (name: string): bool => names->Array.includes(name)
|
|
9
|
+
|
|
10
|
+
// chokidar-style globs derived from the same list (the watch's ignore set).
|
|
11
|
+
let globs = names->Array.map(n => "**/" ++ n ++ "/**")
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
let names = [
|
|
5
|
+
"node_modules",
|
|
6
|
+
".git",
|
|
7
|
+
"dist",
|
|
8
|
+
"lib",
|
|
9
|
+
".history"
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
function shouldIgnore(name) {
|
|
13
|
+
return names.includes(name);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let globs = names.map(n => "**/" + n + "/**");
|
|
17
|
+
|
|
18
|
+
export {
|
|
19
|
+
names,
|
|
20
|
+
shouldIgnore,
|
|
21
|
+
globs,
|
|
22
|
+
}
|
|
23
|
+
/* globs Not a pure module */
|
package/src/Watch.res
CHANGED
|
@@ -88,16 +88,10 @@ let start = (roots: array<string>, onChange: (event, string) => unit): watcher =
|
|
|
88
88
|
roots,
|
|
89
89
|
{
|
|
90
90
|
ignoreInitial: true,
|
|
91
|
-
//
|
|
91
|
+
// Shared with the scanners via `ScanIgnore` — writes under `dist/` or
|
|
92
92
|
// `.history/` are build/editor output, not source, and a `dist/` write
|
|
93
93
|
// could otherwise drive a re-run loop.
|
|
94
|
-
ignored:
|
|
95
|
-
"**/node_modules/**",
|
|
96
|
-
"**/lib/**",
|
|
97
|
-
"**/.git/**",
|
|
98
|
-
"**/dist/**",
|
|
99
|
-
"**/.history/**",
|
|
100
|
-
],
|
|
94
|
+
ignored: ScanIgnore.globs,
|
|
101
95
|
},
|
|
102
96
|
)
|
|
103
97
|
let debounced = debounce(120, onChange)
|
package/src/Watch.res.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
3
|
import * as Chokidar from "chokidar";
|
|
4
|
+
import * as ScanIgnore$ReventlessGwt from "./ScanIgnore.res.mjs";
|
|
4
5
|
|
|
5
6
|
function isStructuralSource(event, path) {
|
|
6
7
|
if (event === "Unlink") {
|
|
@@ -65,13 +66,7 @@ function debounce(wait, fn) {
|
|
|
65
66
|
function start(roots, onChange) {
|
|
66
67
|
let w = Chokidar.watch(roots, {
|
|
67
68
|
ignoreInitial: true,
|
|
68
|
-
ignored:
|
|
69
|
-
"**/node_modules/**",
|
|
70
|
-
"**/lib/**",
|
|
71
|
-
"**/.git/**",
|
|
72
|
-
"**/dist/**",
|
|
73
|
-
"**/.history/**"
|
|
74
|
-
]
|
|
69
|
+
ignored: ScanIgnore$ReventlessGwt.globs
|
|
75
70
|
});
|
|
76
71
|
let debounced = debounce(120, onChange);
|
|
77
72
|
return w.on("add", p => debounced("Add", p)).on("change", p => debounced("Change", p)).on("unlink", p => debounced("Unlink", p));
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Pins `ExnMessage.extract` across the value shapes it inspects: ReScript
|
|
2
|
+
// exceptions (matched or reflected via `RE_EXN_ID`/`_1`), thrown JS `Error`s,
|
|
3
|
+
// bare thrown strings, and non-message values. The reflective cases are the
|
|
4
|
+
// reason the extractor exists, so they are asserted explicitly.
|
|
5
|
+
|
|
6
|
+
open JestGlobals
|
|
7
|
+
|
|
8
|
+
exception Boom(string)
|
|
9
|
+
exception NoPayload
|
|
10
|
+
|
|
11
|
+
// Fixtures the type system can't produce as a well-typed `exn`.
|
|
12
|
+
let jsError: exn = %raw(`new Error("kaboom")`)
|
|
13
|
+
let rawString: exn = %raw(`"just a string"`)
|
|
14
|
+
let nullish: exn = %raw(`null`)
|
|
15
|
+
let bareObject: exn = %raw(`({ some: "thing" })`)
|
|
16
|
+
|
|
17
|
+
describe("ExnMessage.extract", () => {
|
|
18
|
+
testSync("Failure carries its message (the failwith path)", () =>
|
|
19
|
+
expect(ExnMessage.extract(Failure("not implemented: X")))->toEqual("not implemented: X")
|
|
20
|
+
)
|
|
21
|
+
testSync("empty Failure falls back to the constructor tag", () =>
|
|
22
|
+
expect(ExnMessage.extract(Failure("")))->toEqual("Failure")
|
|
23
|
+
)
|
|
24
|
+
testSync("a custom exception with a string payload yields the payload", () =>
|
|
25
|
+
expect(ExnMessage.extract(Boom("custom boom")))->toEqual("custom boom")
|
|
26
|
+
)
|
|
27
|
+
testSync("a payload-less custom exception yields its constructor tag", () =>
|
|
28
|
+
expect(ExnMessage.extract(NoPayload))->toEqual("NoPayload")
|
|
29
|
+
)
|
|
30
|
+
testSync("a thrown JS Error yields its .message", () =>
|
|
31
|
+
expect(ExnMessage.extract(jsError))->toEqual("kaboom")
|
|
32
|
+
)
|
|
33
|
+
testSync("a bare thrown string is returned as-is", () =>
|
|
34
|
+
expect(ExnMessage.extract(rawString))->toEqual("just a string")
|
|
35
|
+
)
|
|
36
|
+
testSync("a value with no message/RE_EXN_ID falls back", () =>
|
|
37
|
+
expect(ExnMessage.extract(bareObject))->toEqual("unknown error")
|
|
38
|
+
)
|
|
39
|
+
testSync("null-ish falls back", () =>
|
|
40
|
+
expect(ExnMessage.extract(nullish))->toEqual("unknown error")
|
|
41
|
+
)
|
|
42
|
+
})
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
4
|
+
import * as ExnMessage$ReventlessGwt from "../src/ExnMessage.res.mjs";
|
|
5
|
+
|
|
6
|
+
let Boom = /* @__PURE__ */Primitive_exceptions.create("ExnMessageTest-ReventlessGwt.Boom");
|
|
7
|
+
|
|
8
|
+
let NoPayload = /* @__PURE__ */Primitive_exceptions.create("ExnMessageTest-ReventlessGwt.NoPayload");
|
|
9
|
+
|
|
10
|
+
let jsError = (new Error("kaboom"));
|
|
11
|
+
|
|
12
|
+
let rawString = "just a string";
|
|
13
|
+
|
|
14
|
+
let nullish = null;
|
|
15
|
+
|
|
16
|
+
let bareObject = ({ some: "thing" });
|
|
17
|
+
|
|
18
|
+
globalThis.describe("ExnMessage.extract", () => {
|
|
19
|
+
globalThis.test("Failure carries its message (the failwith path)", () => {
|
|
20
|
+
globalThis.expect(ExnMessage$ReventlessGwt.extract({
|
|
21
|
+
RE_EXN_ID: "Failure",
|
|
22
|
+
_1: "not implemented: X"
|
|
23
|
+
})).toEqual("not implemented: X");
|
|
24
|
+
});
|
|
25
|
+
globalThis.test("empty Failure falls back to the constructor tag", () => {
|
|
26
|
+
globalThis.expect(ExnMessage$ReventlessGwt.extract({
|
|
27
|
+
RE_EXN_ID: "Failure",
|
|
28
|
+
_1: ""
|
|
29
|
+
})).toEqual("Failure");
|
|
30
|
+
});
|
|
31
|
+
globalThis.test("a custom exception with a string payload yields the payload", () => {
|
|
32
|
+
globalThis.expect(ExnMessage$ReventlessGwt.extract({
|
|
33
|
+
RE_EXN_ID: Boom,
|
|
34
|
+
_1: "custom boom"
|
|
35
|
+
})).toEqual("custom boom");
|
|
36
|
+
});
|
|
37
|
+
globalThis.test("a payload-less custom exception yields its constructor tag", () => {
|
|
38
|
+
globalThis.expect(ExnMessage$ReventlessGwt.extract({
|
|
39
|
+
RE_EXN_ID: NoPayload
|
|
40
|
+
})).toEqual("NoPayload");
|
|
41
|
+
});
|
|
42
|
+
globalThis.test("a thrown JS Error yields its .message", () => {
|
|
43
|
+
globalThis.expect(ExnMessage$ReventlessGwt.extract(jsError)).toEqual("kaboom");
|
|
44
|
+
});
|
|
45
|
+
globalThis.test("a bare thrown string is returned as-is", () => {
|
|
46
|
+
globalThis.expect(ExnMessage$ReventlessGwt.extract(rawString)).toEqual("just a string");
|
|
47
|
+
});
|
|
48
|
+
globalThis.test("a value with no message/RE_EXN_ID falls back", () => {
|
|
49
|
+
globalThis.expect(ExnMessage$ReventlessGwt.extract(bareObject)).toEqual("unknown error");
|
|
50
|
+
});
|
|
51
|
+
globalThis.test("null-ish falls back", () => {
|
|
52
|
+
globalThis.expect(ExnMessage$ReventlessGwt.extract(nullish)).toEqual("unknown error");
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
export {
|
|
57
|
+
Boom,
|
|
58
|
+
NoPayload,
|
|
59
|
+
jsError,
|
|
60
|
+
rawString,
|
|
61
|
+
nullish,
|
|
62
|
+
bareObject,
|
|
63
|
+
}
|
|
64
|
+
/* jsError Not a pure module */
|