@reventlessdev/reventless-gwt 1.0.0-alpha.75 → 1.0.0-alpha.77

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 (70) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/package.json +8 -8
  3. package/src/Behavior_GWT.res +125 -159
  4. package/src/Behavior_GWT.res.mjs +156 -59
  5. package/src/Bind.res.mjs +2 -2
  6. package/src/BuildClassifier.res +15 -5
  7. package/src/BuildClassifier.res.mjs +3 -3
  8. package/src/Cancellation.res +6 -0
  9. package/src/Cancellation.res.mjs +3 -2
  10. package/src/ChildProcess.res +19 -1
  11. package/src/ChildProcess.res.mjs +16 -1
  12. package/src/Cli.res +260 -26
  13. package/src/Cli.res.mjs +227 -29
  14. package/src/Collector.res +13 -2
  15. package/src/Collector.res.mjs +8 -5
  16. package/src/ComponentMeta.res +16 -43
  17. package/src/ComponentMeta.res.mjs +12 -41
  18. package/src/Discovery.res +19 -19
  19. package/src/Discovery.res.mjs +5 -9
  20. package/src/DomainGraph.res +0 -0
  21. package/src/DomainGraph.res.mjs +0 -0
  22. package/src/Filter.res +11 -2
  23. package/src/Filter.res.mjs +10 -3
  24. package/src/FormatterHuman.res +14 -43
  25. package/src/FormatterHuman.res.mjs +21 -28
  26. package/src/FormatterJson.res +12 -5
  27. package/src/FormatterJson.res.mjs +8 -6
  28. package/src/FormatterTap.res +13 -52
  29. package/src/FormatterTap.res.mjs +25 -43
  30. package/src/FormatterVsCode.res +4 -19
  31. package/src/FormatterVsCode.res.mjs +4 -23
  32. package/src/JestBind.res +1 -1
  33. package/src/JestBind.res.mjs +2 -2
  34. package/src/LocalHost.res +11 -27
  35. package/src/LocalHost.res.mjs +6 -34
  36. package/src/MismatchRender.res +72 -0
  37. package/src/MismatchRender.res.mjs +127 -0
  38. package/src/Outcome.res +0 -64
  39. package/src/Outcome.res.mjs +0 -47
  40. package/src/PlatformRunner.res +27 -6
  41. package/src/PlatformRunner.res.mjs +27 -6
  42. package/src/PlatformScan.res +14 -10
  43. package/src/PlatformScan.res.mjs +14 -13
  44. package/src/ProcessManager.res +51 -7
  45. package/src/ProcessManager.res.mjs +43 -4
  46. package/src/RunWorker.res +24 -0
  47. package/src/RunWorker.res.mjs +16 -0
  48. package/src/Watch.res +46 -30
  49. package/src/Watch.res.mjs +34 -32
  50. package/src/WatcherProbe.res +11 -3
  51. package/src/WatcherProbe.res.mjs +15 -2
  52. package/src/Worker.res +24 -0
  53. package/src/Worker.res.mjs +9 -0
  54. package/tests/BuildClassifierTest.res +39 -0
  55. package/tests/BuildClassifierTest.res.mjs +44 -0
  56. package/tests/CliRunEntryTest.res +57 -0
  57. package/tests/CliRunEntryTest.res.mjs +65 -0
  58. package/tests/CliWatchScopeTest.res +62 -0
  59. package/tests/CliWatchScopeTest.res.mjs +95 -0
  60. package/tests/ComponentMetaTest.res +15 -0
  61. package/tests/ComponentMetaTest.res.mjs +14 -0
  62. package/tests/DomainGraphTest.res +2 -2
  63. package/tests/FlowAggregateGwtTest.res +2 -0
  64. package/tests/FlowAggregateGwtTest.res.mjs +4 -0
  65. package/tests/FormatterGoldenTest.res +125 -0
  66. package/tests/FormatterGoldenTest.res.mjs +162 -0
  67. package/tests/MappingGwtTest.res +2 -0
  68. package/tests/MappingGwtTest.res.mjs +4 -0
  69. package/tests/WatchDebounceTest.res +35 -7
  70. package/tests/WatchDebounceTest.res.mjs +37 -6
package/src/Cli.res.mjs CHANGED
@@ -1,11 +1,17 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
+ import * as Nodepath from "node:path";
4
+ import * as Nodecrypto from "node:crypto";
3
5
  import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
4
6
  import * as Stdlib_JsExn from "@rescript/runtime/lib/es6/Stdlib_JsExn.js";
5
7
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
8
+ import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
9
+ import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
6
10
  import * as Watch$ReventlessGwt from "./Watch.res.mjs";
11
+ import * as Nodeworker_threads from "node:worker_threads";
7
12
  import * as Loader$ReventlessGwt from "./Loader.res.mjs";
8
13
  import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
14
+ import * as Worker$ReventlessGwt from "./Worker.res.mjs";
9
15
  import * as Outcome$ReventlessGwt from "./Outcome.res.mjs";
10
16
  import * as Collector$ReventlessGwt from "./Collector.res.mjs";
11
17
  import * as Discovery$ReventlessGwt from "./Discovery.res.mjs";
@@ -30,6 +36,14 @@ import * as Platform_ComponentDefinitionsApi$ReventlessCore from "@reventlessdev
30
36
 
31
37
  let toolVersion = "0.1.0";
32
38
 
39
+ function sha256(s) {
40
+ return Nodecrypto.createHash("sha256").update(s).digest("hex");
41
+ }
42
+
43
+ let lastDomainHash = {
44
+ contents: undefined
45
+ };
46
+
33
47
  let dateNowIso = (() => new Date().toISOString());
34
48
 
35
49
  function parseFormat(s) {
@@ -291,7 +305,8 @@ Exit code is 1 if any test failed, 0 otherwise.
291
305
  backend: backend,
292
306
  uiPorts: uiPorts,
293
307
  listPlatforms: listPlatforms,
294
- toolVersion: toolVersion
308
+ toolVersion: toolVersion,
309
+ paths: undefined
295
310
  }
296
311
  };
297
312
  }
@@ -317,6 +332,29 @@ let exnMessage = (function(e) {
317
332
  return "unknown error"
318
333
  });
319
334
 
335
+ function raceWithTimeout(body, ms) {
336
+ let handleRef = {
337
+ contents: undefined
338
+ };
339
+ let timeout = new Promise((resolve, _reject) => {
340
+ handleRef.contents = Primitive_option.some(setTimeout(() => resolve(Outcome$ReventlessGwt.fail({
341
+ TAG: "Throw",
342
+ error: `timed out after ` + ms.toString() + `ms`,
343
+ stack: ""
344
+ })), ms));
345
+ });
346
+ return Promise.race([
347
+ body(),
348
+ timeout
349
+ ]).then(o => {
350
+ let h = handleRef.contents;
351
+ if (h !== undefined) {
352
+ clearTimeout(Primitive_option.valFromOption(h));
353
+ }
354
+ return Promise.resolve(o);
355
+ });
356
+ }
357
+
320
358
  async function runEntry(entry) {
321
359
  let start = performance.now();
322
360
  let status = "Pass";
@@ -334,7 +372,8 @@ async function runEntry(entry) {
334
372
  }
335
373
  if (exit === 1) {
336
374
  try {
337
- let outcome = await entry.body();
375
+ let deadline = Stdlib_Option.getOr(entry.timeout, 5000);
376
+ let outcome = await raceWithTimeout(entry.body, deadline);
338
377
  if (outcome.TAG === "Ok") {
339
378
  status = "Pass";
340
379
  } else {
@@ -375,7 +414,7 @@ async function loadAndCollect(path) {
375
414
  } catch (raw_exn) {
376
415
  let exn = Primitive_exceptions.internalToException(raw_exn);
377
416
  let msg = Stdlib_Option.getOr(Stdlib_Option.flatMap(Stdlib_JsExn.fromException(exn), Stdlib_JsExn.message), "import failed");
378
- Collector$ReventlessGwt.push(undefined, undefined, `<file-load-error>`, () => Promise.resolve(Outcome$ReventlessGwt.fail({
417
+ Collector$ReventlessGwt.push(undefined, undefined, undefined, `<file-load-error>`, () => Promise.resolve(Outcome$ReventlessGwt.fail({
379
418
  TAG: "Throw",
380
419
  error: "Failed to load " + path + ": " + msg,
381
420
  stack: ""
@@ -469,7 +508,7 @@ function emitResult(opts, r) {
469
508
  if (opts.stream) {
470
509
  return FormatterJson$ReventlessGwt.streamRunEnd(r);
471
510
  } else {
472
- return FormatterJson$ReventlessGwt.emit(r, opts.toolVersion);
511
+ return FormatterJson$ReventlessGwt.emit(r, opts.toolVersion, opts.schemaVersion);
473
512
  }
474
513
  case "Tap" :
475
514
  return FormatterTap$ReventlessGwt.emit(r);
@@ -480,10 +519,48 @@ function emitResult(opts, r) {
480
519
  }
481
520
  }
482
521
 
522
+ function mergeScope(a, b) {
523
+ if (typeof a !== "object") {
524
+ return "RunAll";
525
+ }
526
+ if (typeof b !== "object") {
527
+ return "RunAll";
528
+ }
529
+ let seen = {};
530
+ let union = [];
531
+ a._0.concat(b._0).forEach(d => {
532
+ let match = seen[d];
533
+ if (match !== undefined) {
534
+ return;
535
+ } else {
536
+ seen[d] = true;
537
+ union.push(d);
538
+ return;
539
+ }
540
+ });
541
+ return {
542
+ TAG: "RunPackages",
543
+ _0: union
544
+ };
545
+ }
546
+
547
+ function pathUnderDir(path, dir) {
548
+ return path.startsWith(dir.endsWith("/") ? dir : dir + "/");
549
+ }
550
+
551
+ function scopeSubset(scope, allPaths) {
552
+ if (typeof scope !== "object") {
553
+ return allPaths;
554
+ }
555
+ let dirs = scope._0;
556
+ return allPaths.filter(p => dirs.some(d => pathUnderDir(p, d)));
557
+ }
558
+
483
559
  async function runOnce(opts) {
484
- let paths = await Discovery$ReventlessGwt.discover(opts.roots);
560
+ let p = opts.paths;
561
+ let paths = p !== undefined ? p : await Discovery$ReventlessGwt.discover(opts.roots);
485
562
  if (opts.format === "Json" && opts.stream) {
486
- FormatterJson$ReventlessGwt.streamRunStart(opts.toolVersion, dateNowIso());
563
+ FormatterJson$ReventlessGwt.streamRunStart(opts.toolVersion, dateNowIso(), opts.schemaVersion);
487
564
  }
488
565
  let match = opts.format;
489
566
  let match$1 = opts.stream;
@@ -530,9 +607,16 @@ async function emitDomainAnalysis(pkgs) {
530
607
  }
531
608
  try {
532
609
  let loaded = await LocalHost$ReventlessGwt.loadGraph(platformModulePath, plugins);
533
- FormatterVsCode$ReventlessGwt.deadCode(DomainDeadCode$ReventlessGwt.analyze(loaded.structures));
534
- FormatterVsCode$ReventlessGwt.graph(DomainGraph$ReventlessGwt.build(loaded.structures));
535
- return FormatterVsCode$ReventlessGwt.definitions(loaded.structures.map(param => Platform_ComponentDefinitionsApi$ReventlessCore.encodePluginStructureEntry(param[0], param[1])));
610
+ let definitions = loaded.structures.map(param => Platform_ComponentDefinitionsApi$ReventlessCore.encodePluginStructureEntry(param[0], param[1]));
611
+ let fingerprint = sha256(JSON.stringify(definitions));
612
+ if (Primitive_object.notequal(lastDomainHash.contents, fingerprint)) {
613
+ lastDomainHash.contents = fingerprint;
614
+ FormatterVsCode$ReventlessGwt.deadCode(DomainDeadCode$ReventlessGwt.analyze(loaded.structures));
615
+ FormatterVsCode$ReventlessGwt.graph(DomainGraph$ReventlessGwt.build(loaded.structures));
616
+ return FormatterVsCode$ReventlessGwt.definitions(definitions);
617
+ } else {
618
+ return;
619
+ }
536
620
  } catch (exn) {
537
621
  return;
538
622
  }
@@ -593,55 +677,160 @@ function startBuildWatchers(paths) {
593
677
 
594
678
  async function runWatch(opts) {
595
679
  let roots = opts.roots;
680
+ let discoveredPaths = {
681
+ contents: []
682
+ };
596
683
  if (opts.format === "VsCode") {
597
684
  let paths = await Discovery$ReventlessGwt.discover(roots);
685
+ discoveredPaths.contents = paths;
598
686
  await emitDiscovery(paths);
599
687
  startBuildWatchers(paths);
600
688
  }
689
+ let currentWorker = {
690
+ contents: undefined
691
+ };
692
+ Cancellation$ReventlessGwt.onCancel(() => {
693
+ let w = currentWorker.contents;
694
+ if (w !== undefined) {
695
+ Primitive_option.valFromOption(w).terminate();
696
+ return;
697
+ }
698
+ });
699
+ let runInWorker = paths => new Promise((resolve, _reject) => {
700
+ let w = new Nodeworker_threads.Worker(Worker$ReventlessGwt.runWorkerUrl, {
701
+ workerData: {
702
+ subcommand: opts.subcommand,
703
+ format: opts.format,
704
+ stream: opts.stream,
705
+ watch: opts.watch,
706
+ filters: opts.filters,
707
+ schemaVersion: opts.schemaVersion,
708
+ roots: opts.roots,
709
+ backend: opts.backend,
710
+ uiPorts: opts.uiPorts,
711
+ listPlatforms: opts.listPlatforms,
712
+ toolVersion: opts.toolVersion,
713
+ paths: paths
714
+ }
715
+ });
716
+ currentWorker.contents = Primitive_option.some(w);
717
+ let settled = {
718
+ contents: false
719
+ };
720
+ let settle = () => {
721
+ if (!settled.contents) {
722
+ settled.contents = true;
723
+ currentWorker.contents = undefined;
724
+ return resolve();
725
+ }
726
+ };
727
+ w.on("exit", _code => settle());
728
+ w.on("error", e => {
729
+ console.error("gwt run worker error:", e);
730
+ settle();
731
+ });
732
+ });
733
+ let pathsForScope = scope => {
734
+ if (opts.format === "VsCode") {
735
+ return scopeSubset(scope, discoveredPaths.contents);
736
+ }
737
+ };
601
738
  let runInProgress = {
602
739
  contents: false
603
740
  };
604
- let rerunPending = {
605
- contents: false
741
+ let pendingScope = {
742
+ contents: undefined
606
743
  };
607
- let run = async () => {
744
+ let requestRun = async scope => {
608
745
  if (runInProgress.contents) {
609
- rerunPending.contents = true;
746
+ let s = pendingScope.contents;
747
+ pendingScope.contents = s !== undefined ? mergeScope(s, scope) : scope;
610
748
  return;
611
- } else {
612
- runInProgress.contents = true;
613
- await runOnce(opts);
614
- runInProgress.contents = false;
615
- if (rerunPending.contents) {
616
- rerunPending.contents = false;
617
- await runOnce(opts);
618
- return;
749
+ }
750
+ runInProgress.contents = true;
751
+ await runInWorker(pathsForScope(scope));
752
+ let drain = async () => {
753
+ let next = pendingScope.contents;
754
+ if (next !== undefined) {
755
+ pendingScope.contents = undefined;
756
+ await runInWorker(pathsForScope(next));
757
+ return await drain();
619
758
  } else {
759
+ runInProgress.contents = false;
620
760
  return;
621
761
  }
762
+ };
763
+ return await drain();
764
+ };
765
+ await requestRun("RunAll");
766
+ let rediscoverAndRun = async () => {
767
+ if (opts.format === "VsCode") {
768
+ discoveredPaths.contents = await Discovery$ReventlessGwt.discover(roots);
769
+ }
770
+ await requestRun("RunAll");
771
+ };
772
+ let rebuilding = {};
773
+ let refreshDiscovery = async () => {
774
+ let paths = await Discovery$ReventlessGwt.discover(roots);
775
+ discoveredPaths.contents = paths;
776
+ await emitDiscovery(paths);
777
+ startBuildWatchers(paths);
778
+ await requestRun("RunAll");
779
+ };
780
+ let affectedScope = path => {
781
+ if (opts.format !== "VsCode") {
782
+ return "RunAll";
783
+ }
784
+ let abs = Nodepath.resolve(path);
785
+ let pkg = PackageScan$ReventlessGwt.findOwning(Nodepath.dirname(abs));
786
+ if (pkg !== undefined && discoveredPaths.contents.some(p => pathUnderDir(p, pkg.dir))) {
787
+ return {
788
+ TAG: "RunPackages",
789
+ _0: [pkg.dir]
790
+ };
791
+ } else {
792
+ return "RunAll";
622
793
  }
623
794
  };
624
- await run();
625
795
  Watch$ReventlessGwt.start(roots, (event, path) => {
626
796
  if (opts.format === "VsCode" && Watch$ReventlessGwt.isStructuralSource(event, path)) {
627
797
  let pkg = PackageScan$ReventlessGwt.findOwning(path);
628
798
  if (pkg !== undefined) {
799
+ if (Stdlib_Option.getOr(rebuilding[pkg.dir], false)) {
800
+ return;
801
+ }
802
+ rebuilding[pkg.dir] = true;
629
803
  let t0 = Date.now();
630
804
  FormatterVsCode$ReventlessGwt.buildStart(pkg.dir);
805
+ let sawError = {
806
+ contents: false
807
+ };
631
808
  let feed = BuildClassifier$ReventlessGwt.make(undefined, {
632
809
  onStart: () => {},
633
810
  onOk: _ms => {},
634
- onFail: msg => FormatterVsCode$ReventlessGwt.buildFail(pkg.dir, msg)
811
+ onFail: msg => {
812
+ sawError.contents = true;
813
+ FormatterVsCode$ReventlessGwt.buildFail(pkg.dir, msg);
814
+ }
635
815
  });
636
- return ProcessManager$ReventlessGwt.cleanRebuild(pkg, (_dir, line) => feed(line), () => {
637
- FormatterVsCode$ReventlessGwt.buildOk(pkg.dir, Date.now() - t0);
638
- run();
816
+ return ProcessManager$ReventlessGwt.cleanRebuild(pkg, (_dir, line) => feed(line), ok => {
817
+ rebuilding[pkg.dir] = false;
818
+ if (ok) {
819
+ FormatterVsCode$ReventlessGwt.buildOk(pkg.dir, Date.now() - t0);
820
+ } else if (!sawError.contents) {
821
+ FormatterVsCode$ReventlessGwt.buildFail(pkg.dir, "clean rebuild failed");
822
+ }
823
+ rediscoverAndRun();
639
824
  });
640
825
  }
641
- run();
826
+ requestRun("RunAll");
642
827
  return;
643
828
  } else {
644
- run();
829
+ if (opts.format === "VsCode" && event === "Add" && path.endsWith(".res")) {
830
+ refreshDiscovery();
831
+ } else {
832
+ requestRun(affectedScope(path));
833
+ }
645
834
  return;
646
835
  }
647
836
  });
@@ -728,8 +917,12 @@ async function main() {
728
917
  }
729
918
  }
730
919
 
920
+ let defaultTimeoutMs = 5000;
921
+
731
922
  export {
732
923
  toolVersion,
924
+ sha256,
925
+ lastDomainHash,
733
926
  dateNowIso,
734
927
  parseFormat,
735
928
  defaultRoots,
@@ -737,10 +930,15 @@ export {
737
930
  parseArgv,
738
931
  passesFilter,
739
932
  exnMessage,
933
+ defaultTimeoutMs,
934
+ raceWithTimeout,
740
935
  runEntry,
741
936
  loadAndCollect,
742
937
  runFiles,
743
938
  emitResult,
939
+ mergeScope,
940
+ pathUnderDir,
941
+ scopeSubset,
744
942
  runOnce,
745
943
  emitDomainAnalysis,
746
944
  emitDiscovery,
@@ -751,4 +949,4 @@ export {
751
949
  listPlatforms,
752
950
  main,
753
951
  }
754
- /* Stdlib_JsExn Not a pure module */
952
+ /* node:path Not a pure module */
package/src/Collector.res CHANGED
@@ -25,6 +25,10 @@ type entry = {
25
25
  body: unit => promise<Outcome.outcome>,
26
26
  status: status,
27
27
  location: option<location>,
28
+ // Per-test deadline in ms (from `~timeout` on `testPromise`). `None` = use the
29
+ // runner default. The CLI runner races the body against this; the Jest arm
30
+ // passes it to Jest's own timeout instead.
31
+ timeout: option<int>,
28
32
  }
29
33
 
30
34
  let active = ref(false)
@@ -43,6 +47,10 @@ let activate = () => {
43
47
  hasOnly := false
44
48
  skipNext := false
45
49
  onlyNext := false
50
+ // Reset the skip nesting depth too: a throwing `xdescribe` body in a prior
51
+ // file could otherwise leave it > 0, silently skipping every test in every
52
+ // subsequently loaded file.
53
+ skipDepth := 0
46
54
  currentFile := None
47
55
  }
48
56
 
@@ -73,6 +81,7 @@ let buildId = (describePath, name) => {
73
81
  let push = (
74
82
  ~slice=?,
75
83
  ~location=?,
84
+ ~timeout=?,
76
85
  name: string,
77
86
  body: unit => promise<Outcome.outcome>,
78
87
  ) => {
@@ -97,8 +106,9 @@ let push = (
97
106
  body,
98
107
  status,
99
108
  location,
109
+ timeout,
100
110
  }
101
- entries := Array.concat(entries.contents, [entry])
111
+ entries.contents->Array.push(entry)
102
112
  }
103
113
 
104
114
  // A `todo` placeholder: a spec-less slice still ships a discoverable, compiling
@@ -115,8 +125,9 @@ let pushTodo = (name: string) => {
115
125
  body: () => Promise.resolve(Outcome.pass),
116
126
  status: Skipped,
117
127
  location: None,
128
+ timeout: None,
118
129
  }
119
- entries := Array.concat(entries.contents, [entry])
130
+ entries.contents->Array.push(entry)
120
131
  }
121
132
 
122
133
  let drain = () => {
@@ -42,6 +42,7 @@ function activate() {
42
42
  hasOnly.contents = false;
43
43
  skipNext.contents = false;
44
44
  onlyNext.contents = false;
45
+ skipDepth.contents = 0;
45
46
  currentFile.contents = undefined;
46
47
  }
47
48
 
@@ -86,7 +87,7 @@ function buildId(describePath, name) {
86
87
  }
87
88
  }
88
89
 
89
- function push(slice, location, name, body) {
90
+ function push(slice, location, timeout, name, body) {
90
91
  let status = skipDepth.contents > 0 ? "Skipped" : (
91
92
  skipNext.contents ? (skipNext.contents = false, "Skipped") : (
92
93
  onlyNext.contents ? (onlyNext.contents = false, "Only") : "Runnable"
@@ -101,9 +102,10 @@ function push(slice, location, name, body) {
101
102
  slice: slice,
102
103
  body: body,
103
104
  status: status,
104
- location: location
105
+ location: location,
106
+ timeout: timeout
105
107
  };
106
- entries.contents = entries.contents.concat([entry]);
108
+ entries.contents.push(entry);
107
109
  }
108
110
 
109
111
  function pushTodo(name) {
@@ -117,9 +119,10 @@ function pushTodo(name) {
117
119
  slice: undefined,
118
120
  body: entry_body,
119
121
  status: "Skipped",
120
- location: undefined
122
+ location: undefined,
123
+ timeout: undefined
121
124
  };
122
- entries.contents = entries.contents.concat([entry]);
125
+ entries.contents.push(entry);
123
126
  }
124
127
 
125
128
  function drain() {
@@ -5,42 +5,16 @@
5
5
 
6
6
  type component = {kind: string, name: string}
7
7
 
8
- // Folder names that denote a component kind. A file counts as a component when
9
- // its immediate parent folder is one of these.
10
- let kindFolders = [
11
- "Aggregate",
12
- "StateChangeSlice",
13
- "StateViewSlice",
14
- "StateViewSliceStream",
15
- "ReadModel",
16
- "ReadModelStream",
17
- "AutomationSlice",
18
- "InboundTranslationSlice",
19
- "OutboundTranslationSlice",
20
- "Extension",
21
- "ExtensionPoint",
22
- "Task",
23
- ]
24
-
25
- let isKindFolder = (segment: string) => kindFolders->Array.includes(segment)
26
-
27
- // Body-file suffixes stripped to recover the spec stem (longest first so
28
- // `_ExtensionPointMapping` is removed before `_ExtensionPoint`). Spec and body
29
- // files in one folder collapse to the same component name.
30
- let bodySuffixes = [
31
- "_ExtensionPointMapping",
32
- "_ExtensionPoint",
33
- "_Extension",
34
- "_Projections",
35
- "_Projection",
36
- "_Mappings",
37
- "_Behavior",
38
- "_Automation",
39
- "_Translation",
40
- ]
8
+ // The folder→kind vocabulary and body-file suffixes are the single source in
9
+ // `Reventless.ComponentKind` (shared with the plugin generator). Deriving from it
10
+ // means the gwt discovery recognises exactly the folder spellings the generator
11
+ // classifies — including the plural / short forms this file used to miss.
12
+ module Kind = Reventless.ComponentKind
41
13
 
14
+ // Strip a body-file suffix (longest first) to recover the spec stem, so spec and
15
+ // body files in one folder collapse to the same component name.
42
16
  let stripBody = (stem: string) =>
43
- switch bodySuffixes->Array.find(suf => String.endsWith(stem, suf)) {
17
+ switch Kind.bodySuffixes->Array.find(suf => String.endsWith(stem, suf)) {
44
18
  | Some(suf) => String.slice(stem, ~start=0, ~end=String.length(stem) - String.length(suf))
45
19
  | None => stem
46
20
  }
@@ -87,18 +61,17 @@ let stem = (filename: string) =>
87
61
  }
88
62
 
89
63
  // {kind, name} for a discovered GWT test file, or None if it isn't inside a
90
- // recognised kind folder.
64
+ // recognised kind folder. `kind` is the canonical folder name, so a plural
65
+ // folder (`Aggregates/`) reports the same kind as its singular form.
91
66
  let componentOfTestFile = (path: string): option<component> =>
92
- switch parentFolder(path) {
93
- | Some(folder) if isKindFolder(folder) =>
94
- Some({kind: folder, name: path->basename->stem->stripGwt->stripBody})
95
- | _ => None
67
+ switch parentFolder(path)->Option.flatMap(Kind.folderToKind) {
68
+ | Some(kind) => Some({kind: Kind.folderName(kind), name: path->basename->stem->stripGwt->stripBody})
69
+ | None => None
96
70
  }
97
71
 
98
72
  // {kind, name} for a src/ component file (spec or body), or None.
99
73
  let componentOfSrcFile = (path: string): option<component> =>
100
- switch parentFolder(path) {
101
- | Some(folder) if isKindFolder(folder) =>
102
- Some({kind: folder, name: path->basename->stem->stripBody})
103
- | _ => None
74
+ switch parentFolder(path)->Option.flatMap(Kind.folderToKind) {
75
+ | Some(kind) => Some({kind: Kind.folderName(kind), name: path->basename->stem->stripBody})
76
+ | None => None
104
77
  }
@@ -1,40 +1,11 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
3
  import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
4
-
5
- let kindFolders = [
6
- "Aggregate",
7
- "StateChangeSlice",
8
- "StateViewSlice",
9
- "StateViewSliceStream",
10
- "ReadModel",
11
- "ReadModelStream",
12
- "AutomationSlice",
13
- "InboundTranslationSlice",
14
- "OutboundTranslationSlice",
15
- "Extension",
16
- "ExtensionPoint",
17
- "Task"
18
- ];
19
-
20
- function isKindFolder(segment) {
21
- return kindFolders.includes(segment);
22
- }
23
-
24
- let bodySuffixes = [
25
- "_ExtensionPointMapping",
26
- "_ExtensionPoint",
27
- "_Extension",
28
- "_Projections",
29
- "_Projection",
30
- "_Mappings",
31
- "_Behavior",
32
- "_Automation",
33
- "_Translation"
34
- ];
4
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
5
+ import * as ComponentKind$Reventless from "@reventlessdev/reventless-spec/src/components/ComponentKind.res.mjs";
35
6
 
36
7
  function stripBody(stem) {
37
- let suf = bodySuffixes.find(suf => stem.endsWith(suf));
8
+ let suf = ComponentKind$Reventless.bodySuffixes.find(suf => stem.endsWith(suf));
38
9
  if (suf !== undefined) {
39
10
  return stem.slice(0, stem.length - suf.length | 0);
40
11
  } else {
@@ -86,29 +57,29 @@ function stem(filename) {
86
57
  }
87
58
 
88
59
  function componentOfTestFile(path) {
89
- let folder = parentFolder(path);
90
- if (folder !== undefined && kindFolders.includes(folder)) {
60
+ let kind = Stdlib_Option.flatMap(parentFolder(path), ComponentKind$Reventless.folderToKind);
61
+ if (kind !== undefined) {
91
62
  return {
92
- kind: folder,
63
+ kind: ComponentKind$Reventless.folderName(kind),
93
64
  name: stripBody(stripGwt(stem(basename(path))))
94
65
  };
95
66
  }
96
67
  }
97
68
 
98
69
  function componentOfSrcFile(path) {
99
- let folder = parentFolder(path);
100
- if (folder !== undefined && kindFolders.includes(folder)) {
70
+ let kind = Stdlib_Option.flatMap(parentFolder(path), ComponentKind$Reventless.folderToKind);
71
+ if (kind !== undefined) {
101
72
  return {
102
- kind: folder,
73
+ kind: ComponentKind$Reventless.folderName(kind),
103
74
  name: stripBody(stem(basename(path)))
104
75
  };
105
76
  }
106
77
  }
107
78
 
79
+ let Kind;
80
+
108
81
  export {
109
- kindFolders,
110
- isKindFolder,
111
- bodySuffixes,
82
+ Kind,
112
83
  stripBody,
113
84
  stripGwt,
114
85
  basename,