@reventlessdev/reventless-gwt 1.0.0-alpha.114 → 1.0.0-alpha.116
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/package.json +5 -12
- package/rescript.json +0 -1
- package/src/JestBind.res +21 -22
- package/src/JestBind.res.mjs +15 -11
- package/src/RunnerHook.res +38 -0
- package/src/RunnerHook.res.mjs +26 -0
- package/bin/reventless-dev.mjs +0 -21
- package/src/Bind.res +0 -29
- package/src/Bind.res.mjs +0 -22
- package/src/BuildClassifier.res +0 -147
- package/src/BuildClassifier.res.mjs +0 -136
- package/src/Cancellation.res +0 -37
- package/src/Cancellation.res.mjs +0 -46
- package/src/ChildProcess.res +0 -102
- package/src/ChildProcess.res.mjs +0 -107
- package/src/Cli.res +0 -905
- package/src/Cli.res.mjs +0 -952
- package/src/Collector.res +0 -212
- package/src/Collector.res.mjs +0 -218
- package/src/ComponentMeta.res +0 -77
- package/src/ComponentMeta.res.mjs +0 -91
- package/src/ComponentScan.res +0 -103
- package/src/ComponentScan.res.mjs +0 -105
- package/src/Discovery.res +0 -105
- package/src/Discovery.res.mjs +0 -99
- package/src/DomainDeadCode.res +0 -98
- package/src/DomainDeadCode.res.mjs +0 -72
- package/src/DomainGraph.res +0 -0
- package/src/DomainGraph.res.mjs +0 -0
- package/src/Filter.res +0 -45
- package/src/Filter.res.mjs +0 -47
- package/src/FormatterHuman.res +0 -109
- package/src/FormatterHuman.res.mjs +0 -115
- package/src/FormatterJson.res +0 -264
- package/src/FormatterJson.res.mjs +0 -322
- package/src/FormatterJunit.res +0 -93
- package/src/FormatterJunit.res.mjs +0 -90
- package/src/FormatterTap.res +0 -100
- package/src/FormatterTap.res.mjs +0 -131
- package/src/FormatterVsCode.res +0 -352
- package/src/FormatterVsCode.res.mjs +0 -546
- package/src/Loader.res +0 -24
- package/src/Loader.res.mjs +0 -23
- package/src/PackageScan.res +0 -80
- package/src/PackageScan.res.mjs +0 -81
- package/src/PlatformRunner.res +0 -191
- package/src/PlatformRunner.res.mjs +0 -178
- package/src/PlatformScan.res +0 -142
- package/src/PlatformScan.res.mjs +0 -132
- package/src/ProcessManager.res +0 -135
- package/src/ProcessManager.res.mjs +0 -119
- package/src/RunWorker.res +0 -24
- package/src/RunWorker.res.mjs +0 -16
- package/src/RunnerTypes.res +0 -68
- package/src/RunnerTypes.res.mjs +0 -60
- package/src/Watch.res +0 -108
- package/src/Watch.res.mjs +0 -85
- package/src/WatcherProbe.res +0 -56
- package/src/WatcherProbe.res.mjs +0 -59
- package/src/Worker.res +0 -24
- package/src/Worker.res.mjs +0 -9
- package/tests/BuildClassifierTest.res +0 -133
- package/tests/BuildClassifierTest.res.mjs +0 -176
- package/tests/CliRunEntryTest.res +0 -57
- package/tests/CliRunEntryTest.res.mjs +0 -65
- package/tests/CliWatchScopeTest.res +0 -62
- package/tests/CliWatchScopeTest.res.mjs +0 -95
- package/tests/ComponentMetaTest.res +0 -67
- package/tests/ComponentMetaTest.res.mjs +0 -71
- package/tests/DiscoveryTest.res +0 -42
- package/tests/DiscoveryTest.res.mjs +0 -43
- package/tests/DomainDeadCodeTest.res +0 -119
- package/tests/DomainDeadCodeTest.res.mjs +0 -158
- package/tests/DomainGraphTest.res +0 -448
- package/tests/DomainGraphTest.res.mjs +0 -505
- package/tests/FormatterGoldenTest.res +0 -125
- package/tests/FormatterGoldenTest.res.mjs +0 -162
- package/tests/FormatterVsCodeMessageTest.res +0 -41
- package/tests/FormatterVsCodeMessageTest.res.mjs +0 -42
- package/tests/PlatformRunnerTest.res +0 -66
- package/tests/PlatformRunnerTest.res.mjs +0 -85
- package/tests/PlatformScanTest.res +0 -61
- package/tests/PlatformScanTest.res.mjs +0 -56
- package/tests/RunnerUnitTest.res +0 -219
- package/tests/RunnerUnitTest.res.mjs +0 -244
- package/tests/WatchDebounceTest.res +0 -106
- package/tests/WatchDebounceTest.res.mjs +0 -128
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
-
|
|
3
|
-
import * as Nodefs from "node:fs";
|
|
4
|
-
import * as Nodepath from "node:path";
|
|
5
|
-
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
6
|
-
import * as Promises from "node:fs/promises";
|
|
7
|
-
import * as ComponentMeta$ReventlessGwt from "./ComponentMeta.res.mjs";
|
|
8
|
-
|
|
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
|
-
let gwtIgnoreFile = ".gwtignore";
|
|
22
|
-
|
|
23
|
-
function isPruned(entries) {
|
|
24
|
-
return entries.some(e => e.name === gwtIgnoreFile);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function isSrcResFile(name) {
|
|
28
|
-
return name.endsWith(".res");
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
async function walk(dir, acc) {
|
|
32
|
-
let entries;
|
|
33
|
-
try {
|
|
34
|
-
entries = await Promises.readdir(dir, {
|
|
35
|
-
withFileTypes: true
|
|
36
|
-
});
|
|
37
|
-
} catch (exn) {
|
|
38
|
-
entries = [];
|
|
39
|
-
}
|
|
40
|
-
if (isPruned(entries)) {
|
|
41
|
-
return acc;
|
|
42
|
-
}
|
|
43
|
-
let found = acc;
|
|
44
|
-
for (let i = 0, i_finish = entries.length; i < i_finish; ++i) {
|
|
45
|
-
let entry = entries[i];
|
|
46
|
-
if (!ignoreNames.includes(entry.name)) {
|
|
47
|
-
let full = Nodepath.join(dir, entry.name);
|
|
48
|
-
if (entry.isDirectory()) {
|
|
49
|
-
let nested = await walk(full, []);
|
|
50
|
-
found = found.concat(nested);
|
|
51
|
-
} else if (entry.isFile() && entry.name.endsWith(".res")) {
|
|
52
|
-
found = found.concat([full]);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
return found;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
async function scan(pkgDirs) {
|
|
60
|
-
let order = [];
|
|
61
|
-
let filesByKey = {};
|
|
62
|
-
for (let i = 0, i_finish = pkgDirs.length; i < i_finish; ++i) {
|
|
63
|
-
let dir = pkgDirs[i];
|
|
64
|
-
let srcDir = Nodepath.join(dir, "src");
|
|
65
|
-
if (Nodefs.existsSync(srcDir)) {
|
|
66
|
-
let files = await walk(srcDir, []);
|
|
67
|
-
files.forEach(file => {
|
|
68
|
-
let c = ComponentMeta$ReventlessGwt.componentOfSrcFile(file);
|
|
69
|
-
if (c === undefined) {
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
let key = dir + "::" + c.kind + "::" + c.name;
|
|
73
|
-
let existing = filesByKey[key];
|
|
74
|
-
if (existing !== undefined) {
|
|
75
|
-
filesByKey[key] = existing.concat([file]);
|
|
76
|
-
} else {
|
|
77
|
-
filesByKey[key] = [file];
|
|
78
|
-
order.push({
|
|
79
|
-
dir: dir,
|
|
80
|
-
kind: c.kind,
|
|
81
|
-
name: c.name,
|
|
82
|
-
key: key
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
return order.map(o => ({
|
|
89
|
-
dir: o.dir,
|
|
90
|
-
kind: o.kind,
|
|
91
|
-
name: o.name,
|
|
92
|
-
files: Stdlib_Option.getOr(filesByKey[o.key], [])
|
|
93
|
-
}));
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export {
|
|
97
|
-
ignoreNames,
|
|
98
|
-
shouldIgnore,
|
|
99
|
-
gwtIgnoreFile,
|
|
100
|
-
isPruned,
|
|
101
|
-
isSrcResFile,
|
|
102
|
-
walk,
|
|
103
|
-
scan,
|
|
104
|
-
}
|
|
105
|
-
/* node:fs Not a pure module */
|
package/src/Discovery.res
DELETED
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
// Walks a directory tree and returns absolute paths of compiled GWT test
|
|
2
|
-
// files (`*_GWT.res.mjs` plus `*GwtTest.res.mjs` — the two shapes used in
|
|
3
|
-
// the monorepo today). Respects a minimal ignore set (`node_modules`,
|
|
4
|
-
// `lib`, `.git`, `dist`, `.history`). Pruning `lib` covers compiled
|
|
5
|
-
// `lib/bs` / `lib/ocaml` outputs. Full `.gitignore` parsing is out of scope;
|
|
6
|
-
// the ignore set is enough to make a bare cwd scan (the auto-discovery
|
|
7
|
-
// default) cheap and correct across a multi-package workspace.
|
|
8
|
-
|
|
9
|
-
type dirent = {
|
|
10
|
-
name: string,
|
|
11
|
-
@as("isDirectory") _isDirectory: unit => bool,
|
|
12
|
-
@as("isFile") _isFile: unit => bool,
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
type readdirOpts = {withFileTypes: bool}
|
|
16
|
-
|
|
17
|
-
@module("node:fs/promises")
|
|
18
|
-
external _readdir: (string, readdirOpts) => promise<array<dirent>> = "readdir"
|
|
19
|
-
|
|
20
|
-
type stats = {
|
|
21
|
-
@as("isDirectory") _isDirectory: unit => bool,
|
|
22
|
-
@as("isFile") _isFile: unit => bool,
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
@module("node:fs/promises") external _stat: string => promise<stats> = "stat"
|
|
26
|
-
|
|
27
|
-
@module("node:path") external join: (string, string) => string = "join"
|
|
28
|
-
@module("node:path") external isAbsolute: string => bool = "isAbsolute"
|
|
29
|
-
@module("node:path") external resolve: string => string = "resolve"
|
|
30
|
-
|
|
31
|
-
let ignoreNames = ["node_modules", ".git", "dist", "lib", ".history"]
|
|
32
|
-
let shouldIgnore = (name: string) => Array.includes(ignoreNames, name)
|
|
33
|
-
|
|
34
|
-
// A directory carrying this sentinel file — and its whole subtree — is pruned
|
|
35
|
-
// from discovery. Unlike the `ignoreNames` dir-name list, this is filesystem
|
|
36
|
-
// state, so it excludes generated/vendored trees (e.g. codegen golden fixtures)
|
|
37
|
-
// even when compiled `*_GWT.res.mjs` are present on disk.
|
|
38
|
-
let gwtIgnoreFile = ".gwtignore"
|
|
39
|
-
let isPruned = (entries: array<dirent>) => entries->Array.some(e => e.name == gwtIgnoreFile)
|
|
40
|
-
|
|
41
|
-
let isGwtTestFile = (name: string) =>
|
|
42
|
-
String.endsWith(name, "_GWT.res.mjs") ||
|
|
43
|
-
String.endsWith(name, "GwtTest.res.mjs") ||
|
|
44
|
-
String.endsWith(name, "Gwt.res.mjs")
|
|
45
|
-
|
|
46
|
-
// Depth-first, pushing into a shared accumulator instead of rebuilding it with
|
|
47
|
-
// `Array.concat` per entry (which was O(n²) over a large tree). The traversal
|
|
48
|
-
// order is unchanged: files and nested subtree results still land in on-disk
|
|
49
|
-
// entry order.
|
|
50
|
-
let rec walk = async (dir: string, acc: array<string>): unit => {
|
|
51
|
-
let entries = try {
|
|
52
|
-
await _readdir(dir, {withFileTypes: true})
|
|
53
|
-
} catch {
|
|
54
|
-
| _ => []
|
|
55
|
-
}
|
|
56
|
-
if isPruned(entries) {
|
|
57
|
-
()
|
|
58
|
-
} else {
|
|
59
|
-
for i in 0 to entries->Array.length - 1 {
|
|
60
|
-
let entry = entries->Array.getUnsafe(i)
|
|
61
|
-
if !shouldIgnore(entry.name) {
|
|
62
|
-
let full = join(dir, entry.name)
|
|
63
|
-
if entry._isDirectory() {
|
|
64
|
-
await walk(full, acc)
|
|
65
|
-
} else if entry._isFile() && isGwtTestFile(entry.name) {
|
|
66
|
-
acc->Array.push(full)
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Returns absolute paths of `*GWT*.res.mjs` test files reachable from the
|
|
74
|
-
// supplied roots. A root may be a directory or a single file.
|
|
75
|
-
let discover = async (roots: array<string>): array<string> => {
|
|
76
|
-
let found = []
|
|
77
|
-
for i in 0 to roots->Array.length - 1 {
|
|
78
|
-
let root = roots->Array.getUnsafe(i)
|
|
79
|
-
let absolute = isAbsolute(root) ? root : resolve(root)
|
|
80
|
-
let isDir = try {
|
|
81
|
-
let s = await _stat(absolute)
|
|
82
|
-
s._isDirectory()
|
|
83
|
-
} catch {
|
|
84
|
-
| _ => false
|
|
85
|
-
}
|
|
86
|
-
if isDir {
|
|
87
|
-
await walk(absolute, found)
|
|
88
|
-
} else if isGwtTestFile(absolute) {
|
|
89
|
-
found->Array.push(absolute)
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
// Deduplicate (if roots overlap).
|
|
93
|
-
let seen = Dict.make()
|
|
94
|
-
let unique = []
|
|
95
|
-
found->Array.forEach(path =>
|
|
96
|
-
switch seen->Dict.get(path) {
|
|
97
|
-
| Some(_) => ()
|
|
98
|
-
| None => {
|
|
99
|
-
seen->Dict.set(path, true)
|
|
100
|
-
unique->Array.push(path)
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
)
|
|
104
|
-
unique
|
|
105
|
-
}
|
package/src/Discovery.res.mjs
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
-
|
|
3
|
-
import * as Nodepath from "node:path";
|
|
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
|
-
}
|
|
17
|
-
|
|
18
|
-
let gwtIgnoreFile = ".gwtignore";
|
|
19
|
-
|
|
20
|
-
function isPruned(entries) {
|
|
21
|
-
return entries.some(e => e.name === gwtIgnoreFile);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function isGwtTestFile(name) {
|
|
25
|
-
if (name.endsWith("_GWT.res.mjs") || name.endsWith("GwtTest.res.mjs")) {
|
|
26
|
-
return true;
|
|
27
|
-
} else {
|
|
28
|
-
return name.endsWith("Gwt.res.mjs");
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
async function walk(dir, acc) {
|
|
33
|
-
let entries;
|
|
34
|
-
try {
|
|
35
|
-
entries = await Promises.readdir(dir, {
|
|
36
|
-
withFileTypes: true
|
|
37
|
-
});
|
|
38
|
-
} catch (exn) {
|
|
39
|
-
entries = [];
|
|
40
|
-
}
|
|
41
|
-
if (isPruned(entries)) {
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
for (let i = 0, i_finish = entries.length; i < i_finish; ++i) {
|
|
45
|
-
let entry = entries[i];
|
|
46
|
-
if (!ignoreNames.includes(entry.name)) {
|
|
47
|
-
let full = Nodepath.join(dir, entry.name);
|
|
48
|
-
if (entry.isDirectory()) {
|
|
49
|
-
await walk(full, acc);
|
|
50
|
-
} else if (entry.isFile() && isGwtTestFile(entry.name)) {
|
|
51
|
-
acc.push(full);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
async function discover(roots) {
|
|
58
|
-
let found = [];
|
|
59
|
-
for (let i = 0, i_finish = roots.length; i < i_finish; ++i) {
|
|
60
|
-
let root = roots[i];
|
|
61
|
-
let absolute = Nodepath.isAbsolute(root) ? root : Nodepath.resolve(root);
|
|
62
|
-
let isDir;
|
|
63
|
-
try {
|
|
64
|
-
let s = await Promises.stat(absolute);
|
|
65
|
-
isDir = s.isDirectory();
|
|
66
|
-
} catch (exn) {
|
|
67
|
-
isDir = false;
|
|
68
|
-
}
|
|
69
|
-
if (isDir) {
|
|
70
|
-
await walk(absolute, found);
|
|
71
|
-
} else if (isGwtTestFile(absolute)) {
|
|
72
|
-
found.push(absolute);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
let seen = {};
|
|
76
|
-
let unique = [];
|
|
77
|
-
found.forEach(path => {
|
|
78
|
-
let match = seen[path];
|
|
79
|
-
if (match !== undefined) {
|
|
80
|
-
return;
|
|
81
|
-
} else {
|
|
82
|
-
seen[path] = true;
|
|
83
|
-
unique.push(path);
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
});
|
|
87
|
-
return unique;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export {
|
|
91
|
-
ignoreNames,
|
|
92
|
-
shouldIgnore,
|
|
93
|
-
gwtIgnoreFile,
|
|
94
|
-
isPruned,
|
|
95
|
-
isGwtTestFile,
|
|
96
|
-
walk,
|
|
97
|
-
discover,
|
|
98
|
-
}
|
|
99
|
-
/* node:path Not a pure module */
|
package/src/DomainDeadCode.res
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
// Domain-level dead-code analysis (Phase 5). Pure reflection over the loaded
|
|
2
|
-
// pluginStructures (LocalHost.loadGraph): finds produced event
|
|
3
|
-
// types that no component consumes — the event-sourcing counterpart to reactive
|
|
4
|
-
// dead code, the domain layer the language LSP cannot see.
|
|
5
|
-
//
|
|
6
|
-
// An event is reachable when ANY of these hold:
|
|
7
|
-
// 1. Some component lists it in `consumedEventTypes` — the DCB-style link used by
|
|
8
|
-
// state-view slices, automation slices, outbound translations, and aggregate /
|
|
9
|
-
// state-change-slice consistency reads. Scanned across ALL plugins, so a
|
|
10
|
-
// cross-plugin consumer clears it.
|
|
11
|
-
// 2. The producing write-side has a non-empty `linkedViews`. Classic aggregate →
|
|
12
|
-
// read-model projections express the link on the PRODUCER side (the read model's
|
|
13
|
-
// `consumedEventTypes` is empty; it projects the aggregate's whole stream). This
|
|
14
|
-
// is event-opaque, so a write-side with at least one linked view is treated as
|
|
15
|
-
// fully consumed — see the precision caveat below.
|
|
16
|
-
// A produced event matched by neither of these is reported as an orphan, anchored on
|
|
17
|
-
// the producing write-side declaration.
|
|
18
|
-
//
|
|
19
|
-
// Precision caveat: because classic read models carry no per-event consumption, rule
|
|
20
|
-
// (2) is conservative — a write-side with a linked view is never flagged, even if a
|
|
21
|
-
// specific event it produces is projected by nothing. This avoids false positives at
|
|
22
|
-
// the cost of missing per-event orphans inside an otherwise-projected aggregate. DCB
|
|
23
|
-
// consumers (rule 1) keep full per-event precision.
|
|
24
|
-
//
|
|
25
|
-
// Scope (v1): orphan produced events only. "Extension points with zero extensions"
|
|
26
|
-
// and "read models nothing resolves against" need data `pluginStructure` does not
|
|
27
|
-
// carry (the EP declaration list / the id-resolver table) — deferred.
|
|
28
|
-
|
|
29
|
-
// Finding kinds are the typed protocol vocabulary — constructing the variant here
|
|
30
|
-
// (not a string literal) means a typo or rename is a compile error, and the emitter
|
|
31
|
-
// forwards findings without conversion.
|
|
32
|
-
module P = ReventlessDomainProtocol.Protocol
|
|
33
|
-
|
|
34
|
-
type finding = {
|
|
35
|
-
kind: P.deadCodeKind,
|
|
36
|
-
pluginName: string, // plugin owning the producer
|
|
37
|
-
componentName: string, // the producing aggregate / state-change slice
|
|
38
|
-
detail: string, // the orphaned event type
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
let analyze = (
|
|
42
|
-
~structures: array<(string, Reventless.Plugin.pluginStructure)>,
|
|
43
|
-
): array<finding> => {
|
|
44
|
-
// Every event type consumed anywhere becomes a key in `consumed`.
|
|
45
|
-
let consumed = Dict.make()
|
|
46
|
-
let mark = (evt: string) => consumed->Dict.set(evt, true)
|
|
47
|
-
structures->Array.forEach(((_, s)) => {
|
|
48
|
-
s.readModels->Array.forEach((q: Reventless.Plugin.queryableDef) =>
|
|
49
|
-
q.consumedEventTypes->Array.forEach(mark)
|
|
50
|
-
)
|
|
51
|
-
s.stateViewSlices->Array.forEach((q: Reventless.Plugin.queryableDef) =>
|
|
52
|
-
q.consumedEventTypes->Array.forEach(mark)
|
|
53
|
-
)
|
|
54
|
-
s.aggregates->Array.forEach((w: Reventless.Plugin.writableDef) =>
|
|
55
|
-
w.consumedEventTypes->Array.forEach(mark)
|
|
56
|
-
)
|
|
57
|
-
s.stateChangeSlices->Array.forEach((w: Reventless.Plugin.writableDef) =>
|
|
58
|
-
w.consumedEventTypes->Array.forEach(mark)
|
|
59
|
-
)
|
|
60
|
-
s.automationSlices->Array.forEach((a: Reventless.Plugin.automationSliceDef) =>
|
|
61
|
-
a.consumedEventTypes->Array.forEach(mark)
|
|
62
|
-
)
|
|
63
|
-
s.outboundTranslationSlices->Array.forEach((o: Reventless.Plugin.outboundTranslationSliceDef) =>
|
|
64
|
-
o.consumedEventTypes->Array.forEach(mark)
|
|
65
|
-
)
|
|
66
|
-
s.extensions->Array.forEach((e: Reventless.Plugin.extensionDef) =>
|
|
67
|
-
e.eventTypes->Array.forEach(mark)
|
|
68
|
-
)
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
// Produced events with no consumer, reported per producing write-side. A write-side
|
|
72
|
-
// with a linked view is treated as fully consumed (rule 2 above).
|
|
73
|
-
let findings = []
|
|
74
|
-
let scanProducers = (pluginName, writables: array<Reventless.Plugin.writableDef>) =>
|
|
75
|
-
writables->Array.forEach(w =>
|
|
76
|
-
if w.linkedViews->Array.length > 0 {
|
|
77
|
-
()
|
|
78
|
-
} else {
|
|
79
|
-
w.producedEventTypes->Array.forEach(evt =>
|
|
80
|
-
switch consumed->Dict.get(evt) {
|
|
81
|
-
| Some(_) => ()
|
|
82
|
-
| None =>
|
|
83
|
-
findings->Array.push({
|
|
84
|
-
kind: OrphanEvent,
|
|
85
|
-
pluginName,
|
|
86
|
-
componentName: w.name,
|
|
87
|
-
detail: evt,
|
|
88
|
-
})
|
|
89
|
-
}
|
|
90
|
-
)
|
|
91
|
-
}
|
|
92
|
-
)
|
|
93
|
-
structures->Array.forEach(((pluginName, s)) => {
|
|
94
|
-
scanProducers(pluginName, s.aggregates)
|
|
95
|
-
scanProducers(pluginName, s.stateChangeSlices)
|
|
96
|
-
})
|
|
97
|
-
findings
|
|
98
|
-
}
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
function analyze(structures) {
|
|
5
|
-
let consumed = {};
|
|
6
|
-
let mark = evt => {
|
|
7
|
-
consumed[evt] = true;
|
|
8
|
-
};
|
|
9
|
-
structures.forEach(param => {
|
|
10
|
-
let s = param[1];
|
|
11
|
-
s.readModels.forEach(q => {
|
|
12
|
-
q.consumedEventTypes.forEach(mark);
|
|
13
|
-
});
|
|
14
|
-
s.stateViewSlices.forEach(q => {
|
|
15
|
-
q.consumedEventTypes.forEach(mark);
|
|
16
|
-
});
|
|
17
|
-
s.aggregates.forEach(w => {
|
|
18
|
-
w.consumedEventTypes.forEach(mark);
|
|
19
|
-
});
|
|
20
|
-
s.stateChangeSlices.forEach(w => {
|
|
21
|
-
w.consumedEventTypes.forEach(mark);
|
|
22
|
-
});
|
|
23
|
-
s.automationSlices.forEach(a => {
|
|
24
|
-
a.consumedEventTypes.forEach(mark);
|
|
25
|
-
});
|
|
26
|
-
s.outboundTranslationSlices.forEach(o => {
|
|
27
|
-
o.consumedEventTypes.forEach(mark);
|
|
28
|
-
});
|
|
29
|
-
s.extensions.forEach(e => {
|
|
30
|
-
e.eventTypes.forEach(mark);
|
|
31
|
-
});
|
|
32
|
-
});
|
|
33
|
-
let findings = [];
|
|
34
|
-
let scanProducers = (pluginName, writables) => {
|
|
35
|
-
writables.forEach(w => {
|
|
36
|
-
if (w.linkedViews.length !== 0) {
|
|
37
|
-
return;
|
|
38
|
-
} else {
|
|
39
|
-
w.producedEventTypes.forEach(evt => {
|
|
40
|
-
let match = consumed[evt];
|
|
41
|
-
if (match !== undefined) {
|
|
42
|
-
return;
|
|
43
|
-
} else {
|
|
44
|
-
findings.push({
|
|
45
|
-
kind: "OrphanEvent",
|
|
46
|
-
pluginName: pluginName,
|
|
47
|
-
componentName: w.name,
|
|
48
|
-
detail: evt
|
|
49
|
-
});
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
});
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
});
|
|
56
|
-
};
|
|
57
|
-
structures.forEach(param => {
|
|
58
|
-
let s = param[1];
|
|
59
|
-
let pluginName = param[0];
|
|
60
|
-
scanProducers(pluginName, s.aggregates);
|
|
61
|
-
scanProducers(pluginName, s.stateChangeSlices);
|
|
62
|
-
});
|
|
63
|
-
return findings;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
let P;
|
|
67
|
-
|
|
68
|
-
export {
|
|
69
|
-
P,
|
|
70
|
-
analyze,
|
|
71
|
-
}
|
|
72
|
-
/* No side effect */
|
package/src/DomainGraph.res
DELETED
|
Binary file
|
package/src/DomainGraph.res.mjs
DELETED
|
Binary file
|
package/src/Filter.res
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
// Source-level test filters: `only`, `skip`, `xtest`, `xdescribe`.
|
|
2
|
-
//
|
|
3
|
-
// Jest ships `describe.only` / `test.only` / `xtest` as properties/aliases
|
|
4
|
-
// on the globals; reventless-gwt exposes them as explicit helpers. When the
|
|
5
|
-
// CLI runner is active, they mutate `Collector` state; when Jest is driving,
|
|
6
|
-
// they forward to the Jest globals.
|
|
7
|
-
|
|
8
|
-
@val external jestXTest: (string, unit => unit) => unit = "xtest"
|
|
9
|
-
@val external jestXDescribe: (string, unit => unit) => unit = "xdescribe"
|
|
10
|
-
|
|
11
|
-
// Mark the next registered `test` / `testPromise` as skipped.
|
|
12
|
-
let skip = () => Collector.markSkipNext()
|
|
13
|
-
|
|
14
|
-
// Mark the next registered `test` / `testPromise` as the focus of the run.
|
|
15
|
-
// If at least one entry is flagged `Only`, only those entries execute.
|
|
16
|
-
let only = () => Collector.markOnlyNext()
|
|
17
|
-
|
|
18
|
-
// xtest: register a test but mark it as skipped.
|
|
19
|
-
let xtest = (~slice as _: option<string>=?, name: string, _body: unit => Outcome.outcome) =>
|
|
20
|
-
if Collector.isActive() {
|
|
21
|
-
Collector.markSkipNext()
|
|
22
|
-
Collector.push(name, () => Promise.resolve(Outcome.pass))
|
|
23
|
-
} else {
|
|
24
|
-
jestXTest(name, () => ())
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// xdescribe: register a describe block with every nested test pre-marked
|
|
28
|
-
// skipped.
|
|
29
|
-
let xdescribe = (label: string, body: unit => unit) =>
|
|
30
|
-
if Collector.isActive() {
|
|
31
|
-
Collector.skipDepth := Collector.skipDepth.contents + 1
|
|
32
|
-
// Decrement on both the normal and the throwing path (ReScript has no
|
|
33
|
-
// `finally`): a body that raises must not leak the incremented depth and
|
|
34
|
-
// skip every following sibling/file.
|
|
35
|
-
try {
|
|
36
|
-
Collector.pushDescribe(label, body)
|
|
37
|
-
Collector.skipDepth := Collector.skipDepth.contents - 1
|
|
38
|
-
} catch {
|
|
39
|
-
| e =>
|
|
40
|
-
Collector.skipDepth := Collector.skipDepth.contents - 1
|
|
41
|
-
throw(e)
|
|
42
|
-
}
|
|
43
|
-
} else {
|
|
44
|
-
jestXDescribe(label, body)
|
|
45
|
-
}
|
package/src/Filter.res.mjs
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
-
|
|
3
|
-
import * as Outcome$ReventlessGwt from "./Outcome.res.mjs";
|
|
4
|
-
import * as Collector$ReventlessGwt from "./Collector.res.mjs";
|
|
5
|
-
|
|
6
|
-
function skip() {
|
|
7
|
-
Collector$ReventlessGwt.markSkipNext();
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
function only() {
|
|
11
|
-
Collector$ReventlessGwt.markOnlyNext();
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function xtest(param, name, _body) {
|
|
15
|
-
if (Collector$ReventlessGwt.isActive()) {
|
|
16
|
-
Collector$ReventlessGwt.markSkipNext();
|
|
17
|
-
return Collector$ReventlessGwt.push(undefined, undefined, undefined, name, () => Promise.resolve(Outcome$ReventlessGwt.pass));
|
|
18
|
-
} else {
|
|
19
|
-
globalThis.xtest(name, () => {});
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function xdescribe(label, body) {
|
|
25
|
-
if (Collector$ReventlessGwt.isActive()) {
|
|
26
|
-
Collector$ReventlessGwt.skipDepth.contents = Collector$ReventlessGwt.skipDepth.contents + 1 | 0;
|
|
27
|
-
try {
|
|
28
|
-
Collector$ReventlessGwt.pushDescribe(label, body);
|
|
29
|
-
Collector$ReventlessGwt.skipDepth.contents = Collector$ReventlessGwt.skipDepth.contents - 1 | 0;
|
|
30
|
-
return;
|
|
31
|
-
} catch (e) {
|
|
32
|
-
Collector$ReventlessGwt.skipDepth.contents = Collector$ReventlessGwt.skipDepth.contents - 1 | 0;
|
|
33
|
-
throw e;
|
|
34
|
-
}
|
|
35
|
-
} else {
|
|
36
|
-
globalThis.xdescribe(label, body);
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export {
|
|
42
|
-
skip,
|
|
43
|
-
only,
|
|
44
|
-
xtest,
|
|
45
|
-
xdescribe,
|
|
46
|
-
}
|
|
47
|
-
/* No side effect */
|
package/src/FormatterHuman.res
DELETED
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
// Terminal-coloured human formatter. Shipped as the default output for
|
|
2
|
-
// `reventless-gwt run`. Uses picocolors for ANSI colours — it is a tiny
|
|
3
|
-
// zero-dependency helper that transparently disables colour when stdout
|
|
4
|
-
// is not a TTY.
|
|
5
|
-
|
|
6
|
-
type colour = string => string
|
|
7
|
-
type picocolors = {
|
|
8
|
-
red: colour,
|
|
9
|
-
green: colour,
|
|
10
|
-
yellow: colour,
|
|
11
|
-
cyan: colour,
|
|
12
|
-
gray: colour,
|
|
13
|
-
bold: colour,
|
|
14
|
-
dim: colour,
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
@module("picocolors") external pc: picocolors = "default"
|
|
18
|
-
|
|
19
|
-
@val external processStdout: {"write": string => unit} = "process.stdout"
|
|
20
|
-
@val external processCwd: unit => string = "process.cwd"
|
|
21
|
-
@module("node:path") external pathBasename: string => string = "basename"
|
|
22
|
-
@module("node:path") external pathRelative: (string, string) => string = "relative"
|
|
23
|
-
|
|
24
|
-
let write = (s: string) => processStdout["write"](s)
|
|
25
|
-
let writeLine = (s: string) => write(s ++ "\n")
|
|
26
|
-
|
|
27
|
-
let formatFilePath = (abs: string) => {
|
|
28
|
-
let base = pathBasename(abs)
|
|
29
|
-
let name = if String.endsWith(base, ".res.mjs") {
|
|
30
|
-
String.slice(base, ~start=0, ~end=String.length(base) - 8)
|
|
31
|
-
} else {
|
|
32
|
-
base
|
|
33
|
-
}
|
|
34
|
-
let rel = pathRelative(processCwd(), abs)
|
|
35
|
-
`${name} - ${rel}`
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
let formatLocation = (loc: option<Collector.location>) =>
|
|
39
|
-
switch loc {
|
|
40
|
-
| None => ""
|
|
41
|
-
| Some(l) => `${l.file}:${Int.toString(l.line)}`
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Frames one normalized field as a terminal line. Labels are padded so values
|
|
45
|
-
// align at column 12 (`key:`, `expected:`, `actual:`); `error:` is its own
|
|
46
|
-
// shorter form and the `Throw` stack is dumped raw beneath it.
|
|
47
|
-
let renderField = (f: MismatchRender.field) =>
|
|
48
|
-
switch f {
|
|
49
|
-
| Expected(v) => ` expected: ${v}`
|
|
50
|
-
| Actual(v) => ` actual: ${v}`
|
|
51
|
-
| Key(k) => ` key: "${k}"`
|
|
52
|
-
| ExpectedNoEvents => " expected no events"
|
|
53
|
-
| Error(e) => ` error: ${e}`
|
|
54
|
-
| Stack(s) => s
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
let renderMismatch = (m: Outcome.mismatch) =>
|
|
58
|
-
MismatchRender.normalize(m).fields->Array.map(renderField)->Array.join("\n")
|
|
59
|
-
|
|
60
|
-
let emitTest = (t: RunnerTypes.testResult) => {
|
|
61
|
-
let path = t.describePath->Array.join(" > ")
|
|
62
|
-
let full = path == "" ? t.name : `${path} > ${t.name}`
|
|
63
|
-
let loc = formatLocation(t.location)
|
|
64
|
-
let locStr = loc == "" ? "" : ` ${pc.dim(loc)}`
|
|
65
|
-
switch t.status {
|
|
66
|
-
| Pass => writeLine(` ${pc.green("✓")} ${full}${locStr}`)
|
|
67
|
-
| Skip =>
|
|
68
|
-
writeLine(
|
|
69
|
-
` ${pc.yellow("○")} ${full}${locStr} ${pc.dim(
|
|
70
|
-
t.skipReason->Option.getOr("skipped"),
|
|
71
|
-
)}`,
|
|
72
|
-
)
|
|
73
|
-
| Fail => {
|
|
74
|
-
writeLine(` ${pc.red("✗")} ${pc.bold(full)}${locStr}`)
|
|
75
|
-
switch t.mismatch {
|
|
76
|
-
| Some(m) => {
|
|
77
|
-
writeLine("")
|
|
78
|
-
writeLine(renderMismatch(m))
|
|
79
|
-
let hint = Hint.forMismatch(~slice=?t.slice, m)
|
|
80
|
-
writeLine("")
|
|
81
|
-
writeLine(` ${pc.cyan(Hint.format(hint))}`)
|
|
82
|
-
writeLine("")
|
|
83
|
-
}
|
|
84
|
-
| None => ()
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
let emitFile = (f: RunnerTypes.fileResult) => {
|
|
91
|
-
writeLine("")
|
|
92
|
-
writeLine(pc.bold(formatFilePath(f.path)))
|
|
93
|
-
f.tests->Array.forEach(emitTest)
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
let emitSummary = (s: RunnerTypes.summary) => {
|
|
97
|
-
writeLine("")
|
|
98
|
-
let line = `Tests: ${Int.toString(s.total)} ${pc.green(
|
|
99
|
-
Int.toString(s.passed) ++ " passed",
|
|
100
|
-
)} ${pc.red(Int.toString(s.failed) ++ " failed")} ${pc.yellow(
|
|
101
|
-
Int.toString(s.skipped) ++ " skipped",
|
|
102
|
-
)} (${Float.toString(s.durationMs)} ms)`
|
|
103
|
-
writeLine(line)
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
let emit = (r: RunnerTypes.runResult) => {
|
|
107
|
-
r.files->Array.forEach(emitFile)
|
|
108
|
-
emitSummary(r.summary)
|
|
109
|
-
}
|