@reventlessdev/reventless-spec 3.0.0-alpha.130 → 3.0.0-alpha.132
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 +17 -0
- package/package.json +3 -2
- package/run-check-lifecycle.mjs +2 -0
- package/schema/platform-api.graphql +1 -0
- package/src/components/ComponentKind.res +14 -8
- package/src/components/ComponentKind.res.mjs +8 -6
- package/src/lifecycle/CheckLifecycleModel.res +1353 -0
- package/src/lifecycle/CheckLifecycleModel.res.mjs +1134 -0
- package/src/lifecycle/loadPluginStructure.mjs +66 -0
- package/src/semantic/CalendarDate.res +58 -0
- package/src/semantic/CalendarDate.res.mjs +37 -0
- package/src/semantic/DateRange.res +10 -10
- package/src/semantic/DateRange.res.mjs +12 -14
- package/src/semantic/DateTime.res +92 -0
- package/src/semantic/DateTime.res.mjs +65 -0
- package/src/semantic/RowImage.res +122 -0
- package/src/semantic/RowImage.res.mjs +101 -0
- package/src/semantic/Semantic.res +8 -0
- package/src/semantic/Semantic.res.mjs +2 -0
- package/src/types/Lifecycle.res +106 -0
- package/src/types/Lifecycle.res.mjs +115 -0
- package/src/types/DateTime.res +0 -29
- package/src/types/DateTime.res.mjs +0 -16
|
@@ -0,0 +1,1134 @@
|
|
|
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_Dict from "@rescript/runtime/lib/es6/Stdlib_Dict.js";
|
|
6
|
+
import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
|
|
7
|
+
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
8
|
+
import * as Stdlib_JsExn from "@rescript/runtime/lib/es6/Stdlib_JsExn.js";
|
|
9
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
10
|
+
import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
|
|
11
|
+
import * as Primitive_string from "@rescript/runtime/lib/es6/Primitive_string.js";
|
|
12
|
+
import * as Nodechild_process from "node:child_process";
|
|
13
|
+
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
14
|
+
import * as ComponentKind$Reventless from "../components/ComponentKind.res.mjs";
|
|
15
|
+
import * as LoadPluginStructureMjs from "./loadPluginStructure.mjs";
|
|
16
|
+
|
|
17
|
+
process.env["REVENTLESS_DECLARED_TRANSITIONS_ONLY"] = "1";
|
|
18
|
+
|
|
19
|
+
let repoRoot = process.cwd();
|
|
20
|
+
|
|
21
|
+
let examplesDir = Nodepath.join(repoRoot, "examples");
|
|
22
|
+
|
|
23
|
+
let update = process.argv.includes("--update");
|
|
24
|
+
|
|
25
|
+
function flagValues(flag) {
|
|
26
|
+
let argv = process.argv;
|
|
27
|
+
let out = [];
|
|
28
|
+
for (let i = 0, i_finish = argv.length; i < i_finish; ++i) {
|
|
29
|
+
if (Primitive_object.equal(argv[i], flag)) {
|
|
30
|
+
let value = argv[i + 1 | 0];
|
|
31
|
+
if (value !== undefined && !value.startsWith("--")) {
|
|
32
|
+
out.push(value);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return out;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let reuseSidecars = process.argv.includes("--reuse-sidecars");
|
|
40
|
+
|
|
41
|
+
let json = process.argv.includes("--json");
|
|
42
|
+
|
|
43
|
+
let noRow = "(none)";
|
|
44
|
+
|
|
45
|
+
function asObj(j) {
|
|
46
|
+
return Stdlib_JSON.Decode.object(j);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function getStr(d, k) {
|
|
50
|
+
return Stdlib_Option.flatMap(d[k], Stdlib_JSON.Decode.string);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function getArr(d, k) {
|
|
54
|
+
return Stdlib_Option.getOr(Stdlib_Option.flatMap(d[k], Stdlib_JSON.Decode.array), []);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function getObjs(d, k) {
|
|
58
|
+
return Stdlib_Array.filterMap(getArr(d, k), asObj);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function getStrs(d, k) {
|
|
62
|
+
return Stdlib_Array.filterMap(getArr(d, k), Stdlib_JSON.Decode.string);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function getStrsOpt(d, k) {
|
|
66
|
+
let v = d[k];
|
|
67
|
+
if (v !== undefined) {
|
|
68
|
+
return Stdlib_Option.map(Stdlib_JSON.Decode.array(v), a => Stdlib_Array.filterMap(a, Stdlib_JSON.Decode.string));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function last(xs) {
|
|
73
|
+
return xs[xs.length - 1 | 0];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function lastSegment(name) {
|
|
77
|
+
let parts = name.split(".");
|
|
78
|
+
return Stdlib_Option.getOr(last(parts), name);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function sortedUnique(xs) {
|
|
82
|
+
let out = [];
|
|
83
|
+
xs.forEach(x => {
|
|
84
|
+
if (!out.includes(x)) {
|
|
85
|
+
out.push(x);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
return out.toSorted(Primitive_string.compare);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function valuesOf(step) {
|
|
93
|
+
return Stdlib_Array.filterMap(getArr(step, "values"), v => {
|
|
94
|
+
let match = Stdlib_JSON.Decode.array(v);
|
|
95
|
+
if (match === undefined) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (match.length !== 2) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
let name = match[0];
|
|
102
|
+
let value = match[1];
|
|
103
|
+
return Stdlib_Option.map(Stdlib_JSON.Decode.string(name), n => [
|
|
104
|
+
n,
|
|
105
|
+
value
|
|
106
|
+
]);
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function elementsOf(steps) {
|
|
111
|
+
return Stdlib_Array.filterMap(steps, s => Stdlib_Option.map(getStr(s, "element"), name => ({
|
|
112
|
+
name: lastSegment(name),
|
|
113
|
+
values: valuesOf(s)
|
|
114
|
+
})));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function kindOf(steps) {
|
|
118
|
+
return Stdlib_Option.getOr(Stdlib_Option.flatMap(steps[0], s => getStr(s, "kind")), "");
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function scenarioOf(j) {
|
|
122
|
+
return Stdlib_Option.map(Stdlib_JSON.Decode.object(j), d => {
|
|
123
|
+
let given = getObjs(d, "given");
|
|
124
|
+
let when_ = getObjs(d, "when");
|
|
125
|
+
let then_ = getObjs(d, "then");
|
|
126
|
+
return {
|
|
127
|
+
title: Stdlib_Option.getOr(getStr(d, "title"), ""),
|
|
128
|
+
given: elementsOf(given),
|
|
129
|
+
whenKind: kindOf(when_),
|
|
130
|
+
whenElements: elementsOf(when_),
|
|
131
|
+
thenKind: kindOf(then_),
|
|
132
|
+
thenElements: elementsOf(then_),
|
|
133
|
+
thenValues: Stdlib_Option.getOr(Stdlib_Option.map(then_[0], valuesOf), [])
|
|
134
|
+
};
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function readCorpus(path) {
|
|
139
|
+
let d;
|
|
140
|
+
try {
|
|
141
|
+
d = Stdlib_JSON.Decode.object(JSON.parse(Nodefs.readFileSync(path, "utf8")));
|
|
142
|
+
} catch (exn) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (d === undefined) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
let stem = Stdlib_Option.getOr(getStr(d, "stem"), "");
|
|
149
|
+
return {
|
|
150
|
+
component: stem.replace("_GWT", ""),
|
|
151
|
+
path: path,
|
|
152
|
+
scenarios: Stdlib_Array.filterMap(getArr(d, "scenarios"), scenarioOf)
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function filesUnder(dir, suffix) {
|
|
157
|
+
let entries;
|
|
158
|
+
try {
|
|
159
|
+
entries = Nodefs.readdirSync(dir, {
|
|
160
|
+
withFileTypes: true
|
|
161
|
+
});
|
|
162
|
+
} catch (exn) {
|
|
163
|
+
return [];
|
|
164
|
+
}
|
|
165
|
+
return entries.flatMap(entry => {
|
|
166
|
+
let name = entry.name;
|
|
167
|
+
let full = Nodepath.join(dir, name);
|
|
168
|
+
if (entry.isDirectory()) {
|
|
169
|
+
if (name === "node_modules" || name === "lib" || name.startsWith(".")) {
|
|
170
|
+
return [];
|
|
171
|
+
} else {
|
|
172
|
+
return filesUnder(full, suffix);
|
|
173
|
+
}
|
|
174
|
+
} else if (name.endsWith(suffix)) {
|
|
175
|
+
return [full];
|
|
176
|
+
} else {
|
|
177
|
+
return [];
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function gwtSources(pluginDirs) {
|
|
183
|
+
return pluginDirs.flatMap(dir => filesUnder(Nodepath.join(dir, "tests"), "_GWT.res"));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function sidecarOf(gwt) {
|
|
187
|
+
return gwt.replace("_GWT.res", "_GWT.gwt.json");
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function hasCorpus(pluginDirs) {
|
|
191
|
+
return gwtSources(pluginDirs).some(f => Nodefs.existsSync(f.replace("_GWT.res", "_GWT.gwt.json")));
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function checkSidecars(pluginDirs) {
|
|
195
|
+
if (hasCorpus(pluginDirs)) {
|
|
196
|
+
return {
|
|
197
|
+
TAG: "Ok",
|
|
198
|
+
_0: undefined
|
|
199
|
+
};
|
|
200
|
+
} else {
|
|
201
|
+
return {
|
|
202
|
+
TAG: "Error",
|
|
203
|
+
_0: "--reuse-sidecars was passed, but no scenario sidecar exists. Build with REVENTLESS_EMIT_SIDECAR=1 first, or drop the flag."
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function emitSidecars(pluginDirs) {
|
|
209
|
+
let now = Date.now() / 1000.0;
|
|
210
|
+
gwtSources(pluginDirs).forEach(f => {
|
|
211
|
+
Nodefs.utimesSync(f, now, now);
|
|
212
|
+
});
|
|
213
|
+
let env = Object.fromEntries(Object.entries(process.env).concat([[
|
|
214
|
+
"REVENTLESS_EMIT_SIDECAR",
|
|
215
|
+
"1"
|
|
216
|
+
]]));
|
|
217
|
+
try {
|
|
218
|
+
Nodechild_process.execFileSync("pnpm", [
|
|
219
|
+
"run",
|
|
220
|
+
"build"
|
|
221
|
+
], {
|
|
222
|
+
cwd: repoRoot,
|
|
223
|
+
encoding: "utf8",
|
|
224
|
+
env: env,
|
|
225
|
+
maxBuffer: 268435456
|
|
226
|
+
});
|
|
227
|
+
return {
|
|
228
|
+
TAG: "Ok",
|
|
229
|
+
_0: undefined
|
|
230
|
+
};
|
|
231
|
+
} catch (raw_exn) {
|
|
232
|
+
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
233
|
+
return {
|
|
234
|
+
TAG: "Error",
|
|
235
|
+
_0: Stdlib_Option.getOr(Stdlib_Option.flatMap(Stdlib_JsExn.fromException(exn), Stdlib_JsExn.message), "the build that emits the scenario sidecars failed")
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function levelOf(d) {
|
|
241
|
+
let match = d["level"];
|
|
242
|
+
if (match === undefined) {
|
|
243
|
+
return "";
|
|
244
|
+
}
|
|
245
|
+
if (match === null || Array.isArray(match)) {
|
|
246
|
+
return "";
|
|
247
|
+
}
|
|
248
|
+
switch (typeof match) {
|
|
249
|
+
case "string" :
|
|
250
|
+
return match;
|
|
251
|
+
case "object" :
|
|
252
|
+
return Stdlib_Option.getOr(getStr(match, "TAG"), "");
|
|
253
|
+
default:
|
|
254
|
+
return "";
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function declaredCommandOf(d) {
|
|
259
|
+
return Stdlib_Option.map(getStr(d, "name"), command => ({
|
|
260
|
+
command: command,
|
|
261
|
+
level: levelOf(d),
|
|
262
|
+
aggregateIdField: getStr(d, "aggregateIdField"),
|
|
263
|
+
allowedStates: getStrsOpt(d, "allowedStates"),
|
|
264
|
+
targetState: getStr(d, "targetState"),
|
|
265
|
+
allowedStatesSource: getStr(d, "allowedStatesSource")
|
|
266
|
+
}));
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function declaredOf(structure) {
|
|
270
|
+
return Stdlib_Option.map(Stdlib_JSON.Decode.object(structure), s => {
|
|
271
|
+
let writablesFrom = key => Stdlib_Array.filterMap(getObjs(s, key), w => Stdlib_Option.map(getStr(w, "name"), name => ({
|
|
272
|
+
name: name,
|
|
273
|
+
linkedViews: getStrs(w, "linkedViews"),
|
|
274
|
+
commands: Stdlib_Array.filterMap(getObjs(w, "commands"), declaredCommandOf)
|
|
275
|
+
})));
|
|
276
|
+
let viewsFrom = key => Stdlib_Array.filterMap(getObjs(s, key), v => Stdlib_Option.map(getStr(v, "name"), name => ({
|
|
277
|
+
name: name,
|
|
278
|
+
lifecycleField: getStr(v, "lifecycleField")
|
|
279
|
+
})));
|
|
280
|
+
return {
|
|
281
|
+
writables: writablesFrom("aggregates").concat(writablesFrom("stateChangeSlices")),
|
|
282
|
+
views: viewsFrom("readModels").concat(viewsFrom("stateViewSlices"))
|
|
283
|
+
};
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function loadPluginStructure(prim) {
|
|
288
|
+
return LoadPluginStructureMjs.loadPluginStructure(prim);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
async function readDeclared(pluginDir) {
|
|
292
|
+
let raw = await LoadPluginStructureMjs.loadPluginStructure(pluginDir);
|
|
293
|
+
let d = Stdlib_JSON.Decode.object(raw);
|
|
294
|
+
if (d === undefined) {
|
|
295
|
+
return {
|
|
296
|
+
TAG: "Error",
|
|
297
|
+
_0: "the structure loader returned something unreadable"
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
let match = d["ok"];
|
|
301
|
+
if (match === true) {
|
|
302
|
+
let declared = Stdlib_Option.flatMap(d["structure"], declaredOf);
|
|
303
|
+
if (declared !== undefined) {
|
|
304
|
+
return {
|
|
305
|
+
TAG: "Ok",
|
|
306
|
+
_0: declared
|
|
307
|
+
};
|
|
308
|
+
} else {
|
|
309
|
+
return {
|
|
310
|
+
TAG: "Error",
|
|
311
|
+
_0: "the plugin structure could not be read"
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return {
|
|
316
|
+
TAG: "Error",
|
|
317
|
+
_0: Stdlib_Option.getOr(getStr(d, "error"), "the plugin structure could not be loaded")
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function lifecycleValue(values, field) {
|
|
322
|
+
return Stdlib_Option.flatMap(Stdlib_Option.flatMap(values.find(param => param[0] === field), param => Stdlib_JSON.Decode.object(param[1])), v => {
|
|
323
|
+
let match = getStr(v, "kind");
|
|
324
|
+
if (match === undefined) {
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
switch (match) {
|
|
328
|
+
case "enum" :
|
|
329
|
+
case "string" :
|
|
330
|
+
return Stdlib_Option.map(getStr(v, "value"), lastSegment);
|
|
331
|
+
default:
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function lifecycleMapFor(scenarios, field, ambiguities, view) {
|
|
338
|
+
let map = {};
|
|
339
|
+
let fold = events => Stdlib_Array.reduce(events, noRow, (current, event) => {
|
|
340
|
+
let value = map[event.name];
|
|
341
|
+
if (value !== undefined) {
|
|
342
|
+
return value;
|
|
343
|
+
} else {
|
|
344
|
+
return current;
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
let changed = true;
|
|
348
|
+
let rounds = 0;
|
|
349
|
+
while (changed && rounds < 8) {
|
|
350
|
+
changed = false;
|
|
351
|
+
rounds = rounds + 1 | 0;
|
|
352
|
+
let before = Object.keys(map).length;
|
|
353
|
+
scenarios.forEach(s => {
|
|
354
|
+
if (s.whenKind !== "event" || s.thenKind !== "state") {
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
let match = last(s.whenElements);
|
|
358
|
+
let match$1 = lifecycleValue(s.thenValues, field);
|
|
359
|
+
if (match !== undefined && match$1 !== undefined && (s.given.length === 0 || fold(s.given) !== match$1)) {
|
|
360
|
+
let event = match.name;
|
|
361
|
+
let title = s.title;
|
|
362
|
+
let existing = map[event];
|
|
363
|
+
if (existing !== undefined) {
|
|
364
|
+
if (existing !== match$1) {
|
|
365
|
+
ambiguities.push([
|
|
366
|
+
view,
|
|
367
|
+
view + `: ` + event + ` is projected as both "` + existing + `" and "` + match$1 + `" ` + (`(seen in "` + title + `") — the harvest keeps "` + existing + `"`)
|
|
368
|
+
]);
|
|
369
|
+
return;
|
|
370
|
+
} else {
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
} else {
|
|
374
|
+
map[event] = match$1;
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
if (Object.keys(map).length !== before) {
|
|
380
|
+
changed = true;
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
return map;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function outcomeOf(s) {
|
|
387
|
+
let match = s.thenKind;
|
|
388
|
+
switch (match) {
|
|
389
|
+
case "error" :
|
|
390
|
+
return "Refused";
|
|
391
|
+
case "event" :
|
|
392
|
+
return "Emitted";
|
|
393
|
+
default:
|
|
394
|
+
return "NoChange";
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function valueOf(values, field) {
|
|
399
|
+
return Stdlib_Option.map(values.find(param => param[0] === field), param => param[1]);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function sameRow(event, idField, idValue) {
|
|
403
|
+
if (idField === undefined) {
|
|
404
|
+
return true;
|
|
405
|
+
}
|
|
406
|
+
if (idValue === undefined) {
|
|
407
|
+
return true;
|
|
408
|
+
}
|
|
409
|
+
let actual = valueOf(event.values, idField);
|
|
410
|
+
if (actual !== undefined) {
|
|
411
|
+
return Primitive_object.equal(actual, idValue);
|
|
412
|
+
} else {
|
|
413
|
+
return true;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function observe(scenarios, map, idFieldFor) {
|
|
418
|
+
let fold = events => Stdlib_Array.reduce(events, noRow, (current, event) => {
|
|
419
|
+
let value = map[event.name];
|
|
420
|
+
if (value !== undefined) {
|
|
421
|
+
return value;
|
|
422
|
+
} else {
|
|
423
|
+
return current;
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
return Stdlib_Array.filterMap(scenarios, s => {
|
|
427
|
+
let command = s.whenElements[0];
|
|
428
|
+
if (command === undefined) {
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
if (s.whenKind !== "command") {
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
let idField = idFieldFor(command.name);
|
|
435
|
+
let idValue = Stdlib_Option.flatMap(idField, field => valueOf(command.values, field));
|
|
436
|
+
let history = s.given.filter(e => sameRow(e, idField, idValue));
|
|
437
|
+
let from = fold(history);
|
|
438
|
+
let outcome = outcomeOf(s);
|
|
439
|
+
return {
|
|
440
|
+
title: s.title,
|
|
441
|
+
command: command.name,
|
|
442
|
+
from: from,
|
|
443
|
+
outcome: outcome,
|
|
444
|
+
to: outcome === "Emitted" ? fold(history.concat(s.thenElements)) : from
|
|
445
|
+
};
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function deriveCommands(component, observations, labelled) {
|
|
450
|
+
let names = sortedUnique(observations.map(o => o.command));
|
|
451
|
+
return names.map(command => {
|
|
452
|
+
let mine = observations.filter(o => o.command === command);
|
|
453
|
+
let effective = mine.filter(o => o.outcome === "Emitted");
|
|
454
|
+
let match = effective.length;
|
|
455
|
+
return {
|
|
456
|
+
component: component,
|
|
457
|
+
command: command,
|
|
458
|
+
allowedStates: sortedUnique(Stdlib_Array.filterMap(effective, o => {
|
|
459
|
+
if (o.from === noRow) {
|
|
460
|
+
return;
|
|
461
|
+
} else {
|
|
462
|
+
return o.from;
|
|
463
|
+
}
|
|
464
|
+
})),
|
|
465
|
+
inertStates: sortedUnique(Stdlib_Array.filterMap(mine, o => {
|
|
466
|
+
if (o.outcome === "Emitted" || o.from === noRow) {
|
|
467
|
+
return;
|
|
468
|
+
} else {
|
|
469
|
+
return o.from;
|
|
470
|
+
}
|
|
471
|
+
})),
|
|
472
|
+
targets: sortedUnique(Stdlib_Array.filterMap(effective, o => {
|
|
473
|
+
if (o.to === o.from || o.to === noRow) {
|
|
474
|
+
return;
|
|
475
|
+
} else {
|
|
476
|
+
return o.to;
|
|
477
|
+
}
|
|
478
|
+
})),
|
|
479
|
+
level: labelled && match !== 0 ? (
|
|
480
|
+
effective.every(o => o.from === noRow) ? "Collection" : "Instance"
|
|
481
|
+
) : "",
|
|
482
|
+
scenarios: mine.length
|
|
483
|
+
};
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function allUnverified(cmd, add, why) {
|
|
488
|
+
let states = cmd.allowedStates;
|
|
489
|
+
if (states !== undefined && states.length !== 0) {
|
|
490
|
+
add("unverified", states, `the switch names ` + states.join(", ") + `, and ` + why);
|
|
491
|
+
}
|
|
492
|
+
let target = cmd.targetState;
|
|
493
|
+
if (target !== undefined) {
|
|
494
|
+
return add("unverified", [target], `the switch targets "` + target + `", and ` + why);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function compare(plugin, writable, derived, findings) {
|
|
499
|
+
let where = plugin + `/` + writable.name + `.` + derived.command;
|
|
500
|
+
let add = (severity, states, message) => {
|
|
501
|
+
findings.push({
|
|
502
|
+
severity: severity,
|
|
503
|
+
plugin: plugin,
|
|
504
|
+
component: writable.name,
|
|
505
|
+
command: derived.command,
|
|
506
|
+
states: states,
|
|
507
|
+
message: where + `: ` + message
|
|
508
|
+
});
|
|
509
|
+
};
|
|
510
|
+
let declared = writable.commands.find(c => c.command === derived.command);
|
|
511
|
+
if (declared === undefined) {
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
let states = declared.allowedStates;
|
|
515
|
+
if (states !== undefined) {
|
|
516
|
+
states.forEach(state => {
|
|
517
|
+
if (derived.allowedStates.includes(state)) {
|
|
518
|
+
return;
|
|
519
|
+
} else if (derived.inertStates.includes(state)) {
|
|
520
|
+
return add("contradicted", [state], `the switch names "` + state + `", and a scenario from "` + state + `" shows it refused or producing nothing`);
|
|
521
|
+
} else {
|
|
522
|
+
return add("unverified", [state], `the switch names "` + state + `", and no scenario starts there`);
|
|
523
|
+
}
|
|
524
|
+
});
|
|
525
|
+
derived.allowedStates.forEach(state => {
|
|
526
|
+
if (!states.includes(state)) {
|
|
527
|
+
return add("contradicted", [state], `a scenario shows it taking effect from "` + state + `", which its declared ` + (`from-set (` + states.join(", ") + `) excludes`));
|
|
528
|
+
}
|
|
529
|
+
});
|
|
530
|
+
if (states.length !== 0 && derived.allowedStates.length === 0) {
|
|
531
|
+
add("unverified", states, `the switch declares ` + states.length.toString() + ` state(s) and no scenario shows the command taking effect anywhere`);
|
|
532
|
+
}
|
|
533
|
+
} else if (Primitive_object.equal(declared.allowedStatesSource, "unrestricted")) {
|
|
534
|
+
derived.inertStates.forEach(state => add("contradicted", [state], `the switch declares it legal in every state, and a scenario from "` + state + `" shows it refused or producing nothing`));
|
|
535
|
+
} else if (derived.allowedStates.length !== 0) {
|
|
536
|
+
add("undeclared", derived.allowedStates, `scenarios show it taking effect from ` + derived.allowedStates.join(", ") + `, and it declares no edge`);
|
|
537
|
+
}
|
|
538
|
+
let match = declared.targetState;
|
|
539
|
+
let match$1 = derived.targets;
|
|
540
|
+
if (match !== undefined) {
|
|
541
|
+
if (match$1.length !== 0) {
|
|
542
|
+
if (!match$1.includes(match)) {
|
|
543
|
+
add("contradicted", [match], `the switch targets "` + match + `", and scenarios land in ` + match$1.join(", "));
|
|
544
|
+
}
|
|
545
|
+
match$1.forEach(state => {
|
|
546
|
+
if (state !== match) {
|
|
547
|
+
return add("contradicted", [
|
|
548
|
+
match,
|
|
549
|
+
state
|
|
550
|
+
], `the switch targets "` + match + `", and a scenario lands in "` + state + `" — the published targetState carries one state, so this edge cannot be expressed`);
|
|
551
|
+
}
|
|
552
|
+
});
|
|
553
|
+
} else {
|
|
554
|
+
add("unverified", [match], `the switch targets "` + match + `", and no scenario shows an edge`);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
if (derived.level !== "" && declared.level !== "" && derived.level !== declared.level) {
|
|
558
|
+
return add("level", [], `scenarios make it ` + derived.level + `-level; the published metadata says ` + declared.level);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
function pluginDirsIn(exampleDir) {
|
|
563
|
+
let entries;
|
|
564
|
+
try {
|
|
565
|
+
entries = Nodefs.readdirSync(exampleDir, {
|
|
566
|
+
withFileTypes: true
|
|
567
|
+
});
|
|
568
|
+
} catch (exn) {
|
|
569
|
+
return [];
|
|
570
|
+
}
|
|
571
|
+
return entries.filter(e => e.isDirectory()).map(e => Nodepath.join(exampleDir, e.name)).filter(dir => {
|
|
572
|
+
if (Nodefs.existsSync(Nodepath.join(dir, "src", "Plugin.res"))) {
|
|
573
|
+
return Nodefs.existsSync(Nodepath.join(dir, "tests"));
|
|
574
|
+
} else {
|
|
575
|
+
return false;
|
|
576
|
+
}
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
if (process.argv.includes("--root") && flagValues("--root").length === 0) {
|
|
581
|
+
console.error("--root needs a directory after it");
|
|
582
|
+
process.exit(1);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
let given = flagValues("--root");
|
|
586
|
+
|
|
587
|
+
let roots;
|
|
588
|
+
|
|
589
|
+
if (given.length !== 0) {
|
|
590
|
+
roots = given.map(given => {
|
|
591
|
+
let dir = Nodepath.resolve(given);
|
|
592
|
+
return {
|
|
593
|
+
label: Nodepath.basename(dir),
|
|
594
|
+
dir: dir
|
|
595
|
+
};
|
|
596
|
+
});
|
|
597
|
+
} else {
|
|
598
|
+
let exit = 0;
|
|
599
|
+
let entries;
|
|
600
|
+
try {
|
|
601
|
+
entries = Nodefs.readdirSync(examplesDir, {
|
|
602
|
+
withFileTypes: true
|
|
603
|
+
});
|
|
604
|
+
exit = 1;
|
|
605
|
+
} catch (exn) {
|
|
606
|
+
roots = [];
|
|
607
|
+
}
|
|
608
|
+
if (exit === 1) {
|
|
609
|
+
roots = entries.filter(e => e.isDirectory()).map(e => e.name).toSorted(Primitive_string.compare).map(name => ({
|
|
610
|
+
label: name,
|
|
611
|
+
dir: Nodepath.join(examplesDir, name)
|
|
612
|
+
}));
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
function kindOfPath(path) {
|
|
617
|
+
return Stdlib_Array.reduce(path.split("/"), undefined, (found, segment) => {
|
|
618
|
+
let kind = ComponentKind$Reventless.folderToKind(segment);
|
|
619
|
+
if (kind !== undefined) {
|
|
620
|
+
return kind;
|
|
621
|
+
} else {
|
|
622
|
+
return found;
|
|
623
|
+
}
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
function isViewPath(path) {
|
|
628
|
+
let match = kindOfPath(path);
|
|
629
|
+
if (match === undefined) {
|
|
630
|
+
return false;
|
|
631
|
+
}
|
|
632
|
+
switch (match) {
|
|
633
|
+
case "StateViewSlice" :
|
|
634
|
+
case "StateViewSliceStream" :
|
|
635
|
+
case "ReadModel" :
|
|
636
|
+
case "ReadModelStream" :
|
|
637
|
+
return true;
|
|
638
|
+
default:
|
|
639
|
+
return false;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
function isWritablePath(path) {
|
|
644
|
+
let match = kindOfPath(path);
|
|
645
|
+
if (match === undefined) {
|
|
646
|
+
return false;
|
|
647
|
+
}
|
|
648
|
+
switch (match) {
|
|
649
|
+
case "StateChangeSlice" :
|
|
650
|
+
case "Aggregate" :
|
|
651
|
+
return true;
|
|
652
|
+
default:
|
|
653
|
+
return false;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
async function runPlugin(plugin, pluginDir, findings, opaque) {
|
|
658
|
+
let msg = await readDeclared(pluginDir);
|
|
659
|
+
if (msg.TAG !== "Ok") {
|
|
660
|
+
return {
|
|
661
|
+
TAG: "Error",
|
|
662
|
+
_0: msg._0
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
let declared = msg._0;
|
|
666
|
+
let corpora = Stdlib_Array.filterMap(filesUnder(Nodepath.join(pluginDir, "tests"), ".gwt.json"), readCorpus);
|
|
667
|
+
corpora.forEach(c => {
|
|
668
|
+
let unreadable = c.scenarios.filter(s => s.whenElements.length === 0);
|
|
669
|
+
if (unreadable.length !== 0) {
|
|
670
|
+
opaque.push({
|
|
671
|
+
plugin: plugin,
|
|
672
|
+
component: c.component,
|
|
673
|
+
path: c.path,
|
|
674
|
+
scenarios: c.scenarios.length,
|
|
675
|
+
unreadable: unreadable.length
|
|
676
|
+
});
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
});
|
|
680
|
+
let mapsByView = {};
|
|
681
|
+
let ambiguities = [];
|
|
682
|
+
corpora.forEach(c => {
|
|
683
|
+
if (!isViewPath(c.path)) {
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
let match = declared.views.find(v => v.name === c.component);
|
|
687
|
+
if (match === undefined) {
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
let field = match.lifecycleField;
|
|
691
|
+
if (field !== undefined) {
|
|
692
|
+
mapsByView[c.component] = lifecycleMapFor(c.scenarios, field, ambiguities, c.component);
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
});
|
|
696
|
+
ambiguities.forEach(param => {
|
|
697
|
+
findings.push({
|
|
698
|
+
severity: "ambiguous",
|
|
699
|
+
plugin: plugin,
|
|
700
|
+
component: param[0],
|
|
701
|
+
command: "",
|
|
702
|
+
states: [],
|
|
703
|
+
message: param[1]
|
|
704
|
+
});
|
|
705
|
+
});
|
|
706
|
+
let derived = [];
|
|
707
|
+
corpora.forEach(c => {
|
|
708
|
+
if (!isWritablePath(c.path)) {
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
let writable = declared.writables.find(w => w.name === c.component);
|
|
712
|
+
if (writable === undefined) {
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
let map = {};
|
|
716
|
+
writable.linkedViews.forEach(view => {
|
|
717
|
+
let m = mapsByView[view];
|
|
718
|
+
if (m !== undefined) {
|
|
719
|
+
return Stdlib_Dict.forEachWithKey(m, (value, event) => {
|
|
720
|
+
map[event] = value;
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
});
|
|
724
|
+
let labelled = Object.keys(map).length !== 0;
|
|
725
|
+
let idFieldFor = command => Stdlib_Option.flatMap(writable.commands.find(c => c.command === command), c => c.aggregateIdField);
|
|
726
|
+
let observations = observe(c.scenarios, map, idFieldFor);
|
|
727
|
+
let commands = deriveCommands(c.component, observations, labelled);
|
|
728
|
+
commands.forEach(d => {
|
|
729
|
+
if (labelled) {
|
|
730
|
+
compare(plugin, writable, d, findings);
|
|
731
|
+
}
|
|
732
|
+
derived.push(d);
|
|
733
|
+
});
|
|
734
|
+
let why = labelled ? "no scenario exercises the command" : writable.linkedViews.join(", ") + ` declares no lifecycle field, so its scenarios cannot be labelled`;
|
|
735
|
+
writable.commands.forEach(cmd => {
|
|
736
|
+
if (labelled && commands.some(d => d.command === cmd.command)) {
|
|
737
|
+
return;
|
|
738
|
+
} else {
|
|
739
|
+
return allUnverified(cmd, (severity, states, message) => {
|
|
740
|
+
findings.push({
|
|
741
|
+
severity: severity,
|
|
742
|
+
plugin: plugin,
|
|
743
|
+
component: writable.name,
|
|
744
|
+
command: cmd.command,
|
|
745
|
+
states: states,
|
|
746
|
+
message: plugin + `/` + writable.name + `.` + cmd.command + `: ` + message
|
|
747
|
+
});
|
|
748
|
+
}, why);
|
|
749
|
+
}
|
|
750
|
+
});
|
|
751
|
+
});
|
|
752
|
+
return {
|
|
753
|
+
TAG: "Ok",
|
|
754
|
+
_0: derived
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
function byComponentThenCommand(derived) {
|
|
759
|
+
return derived.toSorted((a, b) => {
|
|
760
|
+
let c = Primitive_string.compare(a.component, b.component);
|
|
761
|
+
if (c !== 0) {
|
|
762
|
+
return c;
|
|
763
|
+
} else {
|
|
764
|
+
return Primitive_string.compare(a.command, b.command);
|
|
765
|
+
}
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
function goldenJson(derived) {
|
|
770
|
+
let entries = byComponentThenCommand(derived).map(d => Object.fromEntries([
|
|
771
|
+
[
|
|
772
|
+
"component",
|
|
773
|
+
d.component
|
|
774
|
+
],
|
|
775
|
+
[
|
|
776
|
+
"command",
|
|
777
|
+
d.command
|
|
778
|
+
],
|
|
779
|
+
[
|
|
780
|
+
"level",
|
|
781
|
+
d.level
|
|
782
|
+
],
|
|
783
|
+
[
|
|
784
|
+
"allowedStates",
|
|
785
|
+
d.allowedStates.map(prim => prim)
|
|
786
|
+
],
|
|
787
|
+
[
|
|
788
|
+
"targets",
|
|
789
|
+
d.targets.map(prim => prim)
|
|
790
|
+
],
|
|
791
|
+
[
|
|
792
|
+
"scenarios",
|
|
793
|
+
d.scenarios
|
|
794
|
+
]
|
|
795
|
+
]));
|
|
796
|
+
return JSON.stringify(entries, undefined, 2) + "\n";
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
function goldenPath(root) {
|
|
800
|
+
return Nodepath.join(root.dir, "schema", "lifecycle-model.json");
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
function reportJson(findings, opaque, derived, failures) {
|
|
804
|
+
let findingJson = f => Object.fromEntries([
|
|
805
|
+
[
|
|
806
|
+
"verdict",
|
|
807
|
+
f.severity
|
|
808
|
+
],
|
|
809
|
+
[
|
|
810
|
+
"plugin",
|
|
811
|
+
f.plugin
|
|
812
|
+
],
|
|
813
|
+
[
|
|
814
|
+
"component",
|
|
815
|
+
f.component
|
|
816
|
+
],
|
|
817
|
+
[
|
|
818
|
+
"command",
|
|
819
|
+
f.command
|
|
820
|
+
],
|
|
821
|
+
[
|
|
822
|
+
"states",
|
|
823
|
+
f.states.map(prim => prim)
|
|
824
|
+
],
|
|
825
|
+
[
|
|
826
|
+
"message",
|
|
827
|
+
f.message
|
|
828
|
+
]
|
|
829
|
+
]);
|
|
830
|
+
let commandJson = param => {
|
|
831
|
+
let d = param[1];
|
|
832
|
+
return Object.fromEntries([
|
|
833
|
+
[
|
|
834
|
+
"plugin",
|
|
835
|
+
param[0]
|
|
836
|
+
],
|
|
837
|
+
[
|
|
838
|
+
"component",
|
|
839
|
+
d.component
|
|
840
|
+
],
|
|
841
|
+
[
|
|
842
|
+
"command",
|
|
843
|
+
d.command
|
|
844
|
+
],
|
|
845
|
+
[
|
|
846
|
+
"level",
|
|
847
|
+
d.level
|
|
848
|
+
],
|
|
849
|
+
[
|
|
850
|
+
"allowedStates",
|
|
851
|
+
d.allowedStates.map(prim => prim)
|
|
852
|
+
],
|
|
853
|
+
[
|
|
854
|
+
"inertStates",
|
|
855
|
+
d.inertStates.map(prim => prim)
|
|
856
|
+
],
|
|
857
|
+
[
|
|
858
|
+
"targets",
|
|
859
|
+
d.targets.map(prim => prim)
|
|
860
|
+
],
|
|
861
|
+
[
|
|
862
|
+
"scenarios",
|
|
863
|
+
d.scenarios
|
|
864
|
+
]
|
|
865
|
+
]);
|
|
866
|
+
};
|
|
867
|
+
let opaqueJson = o => Object.fromEntries([
|
|
868
|
+
[
|
|
869
|
+
"plugin",
|
|
870
|
+
o.plugin
|
|
871
|
+
],
|
|
872
|
+
[
|
|
873
|
+
"component",
|
|
874
|
+
o.component
|
|
875
|
+
],
|
|
876
|
+
[
|
|
877
|
+
"path",
|
|
878
|
+
o.path
|
|
879
|
+
],
|
|
880
|
+
[
|
|
881
|
+
"scenarios",
|
|
882
|
+
o.scenarios
|
|
883
|
+
],
|
|
884
|
+
[
|
|
885
|
+
"unreadable",
|
|
886
|
+
o.unreadable
|
|
887
|
+
]
|
|
888
|
+
]);
|
|
889
|
+
return JSON.stringify(Object.fromEntries([
|
|
890
|
+
[
|
|
891
|
+
"version",
|
|
892
|
+
1
|
|
893
|
+
],
|
|
894
|
+
[
|
|
895
|
+
"findings",
|
|
896
|
+
findings.map(findingJson)
|
|
897
|
+
],
|
|
898
|
+
[
|
|
899
|
+
"commands",
|
|
900
|
+
derived.map(commandJson)
|
|
901
|
+
],
|
|
902
|
+
[
|
|
903
|
+
"opaque",
|
|
904
|
+
opaque.map(opaqueJson)
|
|
905
|
+
],
|
|
906
|
+
[
|
|
907
|
+
"unreadable",
|
|
908
|
+
failures.map(prim => prim)
|
|
909
|
+
]
|
|
910
|
+
]), undefined, 2) + "\n";
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
function modelSource(plugin, derived) {
|
|
914
|
+
let saysSomething = d => {
|
|
915
|
+
if (d.level !== "" || d.allowedStates.length !== 0) {
|
|
916
|
+
return true;
|
|
917
|
+
} else {
|
|
918
|
+
return d.targets.length !== 0;
|
|
919
|
+
}
|
|
920
|
+
};
|
|
921
|
+
let quoted = xs => "[" + xs.map(s => `"` + s + `"`).join(", ") + "]";
|
|
922
|
+
let entries = byComponentThenCommand(derived.filter(saysSomething)).map(d => {
|
|
923
|
+
let match = d.level;
|
|
924
|
+
let level;
|
|
925
|
+
switch (match) {
|
|
926
|
+
case "Collection" :
|
|
927
|
+
case "Instance" :
|
|
928
|
+
level = `level: Reventless.Plugin.` + d.level + `, `;
|
|
929
|
+
break;
|
|
930
|
+
default:
|
|
931
|
+
level = "";
|
|
932
|
+
}
|
|
933
|
+
return ` {component: "` + d.component + `", command: "` + d.command + `", ` + level + (`allowedStates: ` + quoted(d.allowedStates) + `, targets: ` + quoted(d.targets) + `},`);
|
|
934
|
+
});
|
|
935
|
+
return [
|
|
936
|
+
[
|
|
937
|
+
`// AUTO-GENERATED — do not edit. Run \`pnpm run check:lifecycle:update\` to update.`,
|
|
938
|
+
`//`,
|
|
939
|
+
`// What ` + plugin + `'s own given/when/then scenarios say about each command: the`,
|
|
940
|
+
`// states one shows it taking effect from, the states those land in, and whether`,
|
|
941
|
+
`// it brings a row into existence. \`Plugin_Structure\` prefers this to the`,
|
|
942
|
+
`// \`@transition\` annotation where it says anything, and falls back to the`,
|
|
943
|
+
`// annotation where it is silent.`,
|
|
944
|
+
``,
|
|
945
|
+
`let model: array<Reventless.Plugin.derivedEdge> = [`
|
|
946
|
+
],
|
|
947
|
+
entries,
|
|
948
|
+
[
|
|
949
|
+
"]",
|
|
950
|
+
""
|
|
951
|
+
]
|
|
952
|
+
].flat().join("\n");
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
function modelPath(pluginDir) {
|
|
956
|
+
return Nodepath.join(pluginDir, "src", "LifecycleModel.res");
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
function writeOrCompare(path, actual, label, drifted) {
|
|
960
|
+
let existed = Nodefs.existsSync(path);
|
|
961
|
+
if (update || !existed) {
|
|
962
|
+
Nodefs.writeFileSync(path, actual, "utf8");
|
|
963
|
+
console.log((
|
|
964
|
+
existed ? "updated" : "wrote"
|
|
965
|
+
) + ` ` + label);
|
|
966
|
+
return;
|
|
967
|
+
} else if (Nodefs.readFileSync(path, "utf8") !== actual) {
|
|
968
|
+
drifted.push(label);
|
|
969
|
+
console.error(`\ndrift in ` + label);
|
|
970
|
+
return;
|
|
971
|
+
} else {
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
async function main() {
|
|
977
|
+
let findings = [];
|
|
978
|
+
let opaque = [];
|
|
979
|
+
let failures = [];
|
|
980
|
+
let drifted = [];
|
|
981
|
+
let allDerived = [];
|
|
982
|
+
let allPluginDirs = roots.flatMap(root => pluginDirsIn(root.dir));
|
|
983
|
+
if (allPluginDirs.length === 0) {
|
|
984
|
+
console.error(`no plugins found under ` + roots.map(r => r.dir).join(", ") + ` — a plugin is a directory with both src/Plugin.res and tests/`);
|
|
985
|
+
process.exit(1);
|
|
986
|
+
}
|
|
987
|
+
let msg = reuseSidecars ? checkSidecars(allPluginDirs) : emitSidecars(allPluginDirs);
|
|
988
|
+
if (msg.TAG !== "Ok") {
|
|
989
|
+
console.error(msg._0);
|
|
990
|
+
process.exit(1);
|
|
991
|
+
}
|
|
992
|
+
if (!hasCorpus(allPluginDirs)) {
|
|
993
|
+
console.error(`no scenario sidecar exists under ` + roots.map(r => r.dir).join(", ") + ` after the build. Build that tree with REVENTLESS_EMIT_SIDECAR=1 and pass --reuse-sidecars.`);
|
|
994
|
+
process.exit(1);
|
|
995
|
+
}
|
|
996
|
+
for (let i = 0, i_finish = roots.length; i < i_finish; ++i) {
|
|
997
|
+
let root = roots[i];
|
|
998
|
+
if (root !== undefined) {
|
|
999
|
+
let example = root.label;
|
|
1000
|
+
let exampleDir = root.dir;
|
|
1001
|
+
let derived = [];
|
|
1002
|
+
let dirs = pluginDirsIn(exampleDir);
|
|
1003
|
+
for (let j = 0, j_finish = dirs.length; j < j_finish; ++j) {
|
|
1004
|
+
let pluginDir = dirs[j];
|
|
1005
|
+
if (pluginDir !== undefined) {
|
|
1006
|
+
let plugin = Nodepath.basename(pluginDir);
|
|
1007
|
+
let qualified = example + `/` + plugin;
|
|
1008
|
+
let commands = await runPlugin(qualified, pluginDir, findings, opaque);
|
|
1009
|
+
if (commands.TAG === "Ok") {
|
|
1010
|
+
let commands$1 = commands._0;
|
|
1011
|
+
commands$1.forEach(c => {
|
|
1012
|
+
derived.push(c);
|
|
1013
|
+
allDerived.push([
|
|
1014
|
+
qualified,
|
|
1015
|
+
c
|
|
1016
|
+
]);
|
|
1017
|
+
});
|
|
1018
|
+
if (!json) {
|
|
1019
|
+
writeOrCompare(modelPath(pluginDir), modelSource(plugin, commands$1), example + `/` + plugin + `/src/LifecycleModel.res`, drifted);
|
|
1020
|
+
}
|
|
1021
|
+
} else {
|
|
1022
|
+
failures.push(example + `/` + plugin + `: ` + commands._0);
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
if (dirs.length !== 0 && !json) {
|
|
1027
|
+
let dir = Nodepath.join(exampleDir, "schema");
|
|
1028
|
+
if (!Nodefs.existsSync(dir)) {
|
|
1029
|
+
Nodefs.mkdirSync(dir, {
|
|
1030
|
+
recursive: true
|
|
1031
|
+
});
|
|
1032
|
+
}
|
|
1033
|
+
writeOrCompare(goldenPath(root), goldenJson(derived), example + `/schema/lifecycle-model.json`, drifted);
|
|
1034
|
+
console.log(`ok ` + example + ` — ` + derived.length.toString() + ` commands derived from scenarios`);
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
let of_ = severity => findings.filter(f => f.severity === severity);
|
|
1039
|
+
let contradicted = of_("contradicted");
|
|
1040
|
+
if (json) {
|
|
1041
|
+
console.log(reportJson(findings, opaque, allDerived, failures));
|
|
1042
|
+
} else {
|
|
1043
|
+
[
|
|
1044
|
+
"contradicted",
|
|
1045
|
+
"unverified",
|
|
1046
|
+
"undeclared",
|
|
1047
|
+
"level",
|
|
1048
|
+
"ambiguous"
|
|
1049
|
+
].forEach(severity => {
|
|
1050
|
+
let group = of_(severity);
|
|
1051
|
+
if (group.length !== 0) {
|
|
1052
|
+
console.log(`\n` + severity + ` (` + group.length.toString() + `)`);
|
|
1053
|
+
group.forEach(f => {
|
|
1054
|
+
console.log(` ` + f.message);
|
|
1055
|
+
});
|
|
1056
|
+
return;
|
|
1057
|
+
}
|
|
1058
|
+
});
|
|
1059
|
+
}
|
|
1060
|
+
if (failures.length !== 0 && !json) {
|
|
1061
|
+
console.error(`\ncould not read:`);
|
|
1062
|
+
failures.forEach(f => {
|
|
1063
|
+
console.error(` ` + f);
|
|
1064
|
+
});
|
|
1065
|
+
}
|
|
1066
|
+
if (drifted.length !== 0) {
|
|
1067
|
+
console.error(`\n` + drifted.length.toString() + ` lifecycle artifact(s) changed. If the change is intended, re-run with --update and commit them alongside the change that moved them.`);
|
|
1068
|
+
}
|
|
1069
|
+
if (contradicted.length !== 0 || drifted.length !== 0 || failures.length !== 0) {
|
|
1070
|
+
process.exit(1);
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
main();
|
|
1076
|
+
|
|
1077
|
+
export {
|
|
1078
|
+
repoRoot,
|
|
1079
|
+
examplesDir,
|
|
1080
|
+
update,
|
|
1081
|
+
flagValues,
|
|
1082
|
+
reuseSidecars,
|
|
1083
|
+
json,
|
|
1084
|
+
noRow,
|
|
1085
|
+
asObj,
|
|
1086
|
+
getStr,
|
|
1087
|
+
getArr,
|
|
1088
|
+
getObjs,
|
|
1089
|
+
getStrs,
|
|
1090
|
+
getStrsOpt,
|
|
1091
|
+
last,
|
|
1092
|
+
lastSegment,
|
|
1093
|
+
sortedUnique,
|
|
1094
|
+
valuesOf,
|
|
1095
|
+
elementsOf,
|
|
1096
|
+
kindOf,
|
|
1097
|
+
scenarioOf,
|
|
1098
|
+
readCorpus,
|
|
1099
|
+
filesUnder,
|
|
1100
|
+
gwtSources,
|
|
1101
|
+
sidecarOf,
|
|
1102
|
+
hasCorpus,
|
|
1103
|
+
checkSidecars,
|
|
1104
|
+
emitSidecars,
|
|
1105
|
+
levelOf,
|
|
1106
|
+
declaredCommandOf,
|
|
1107
|
+
declaredOf,
|
|
1108
|
+
loadPluginStructure,
|
|
1109
|
+
readDeclared,
|
|
1110
|
+
lifecycleValue,
|
|
1111
|
+
lifecycleMapFor,
|
|
1112
|
+
outcomeOf,
|
|
1113
|
+
valueOf,
|
|
1114
|
+
sameRow,
|
|
1115
|
+
observe,
|
|
1116
|
+
deriveCommands,
|
|
1117
|
+
allUnverified,
|
|
1118
|
+
compare,
|
|
1119
|
+
pluginDirsIn,
|
|
1120
|
+
roots,
|
|
1121
|
+
kindOfPath,
|
|
1122
|
+
isViewPath,
|
|
1123
|
+
isWritablePath,
|
|
1124
|
+
runPlugin,
|
|
1125
|
+
byComponentThenCommand,
|
|
1126
|
+
goldenJson,
|
|
1127
|
+
goldenPath,
|
|
1128
|
+
reportJson,
|
|
1129
|
+
modelSource,
|
|
1130
|
+
modelPath,
|
|
1131
|
+
writeOrCompare,
|
|
1132
|
+
main,
|
|
1133
|
+
}
|
|
1134
|
+
/* Not a pure module */
|