spec-controller 0.1.0-alpha.4 → 0.1.0-alpha.41
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/README.md +34 -6
- package/dist/cli-allocate/cli.d.ts +2 -0
- package/dist/cli-allocate/cli.d.ts.map +1 -0
- package/dist/cli-allocate/cli.js +57 -0
- package/dist/cli-allocate/cli.js.map +1 -0
- package/dist/cli-allocate/readHistory.d.ts +29 -0
- package/dist/cli-allocate/readHistory.d.ts.map +1 -0
- package/dist/cli-allocate/readHistory.js +136 -0
- package/dist/cli-allocate/readHistory.js.map +1 -0
- package/dist/cli-allocate/readParts.d.ts +3 -0
- package/dist/cli-allocate/readParts.d.ts.map +1 -0
- package/dist/cli-allocate/readParts.js +28 -0
- package/dist/cli-allocate/readParts.js.map +1 -0
- package/dist/cli-allocate/readTree.d.ts +16 -0
- package/dist/cli-allocate/readTree.d.ts.map +1 -0
- package/dist/cli-allocate/readTree.js +106 -0
- package/dist/cli-allocate/readTree.js.map +1 -0
- package/dist/cli-allocate/refStore.d.ts +3 -0
- package/dist/cli-allocate/refStore.d.ts.map +1 -0
- package/dist/cli-allocate/refStore.js +47 -0
- package/dist/cli-allocate/refStore.js.map +1 -0
- package/dist/cli-allocate/request.d.ts +11 -0
- package/dist/cli-allocate/request.d.ts.map +1 -0
- package/dist/cli-allocate/request.js +29 -0
- package/dist/cli-allocate/request.js.map +1 -0
- package/dist/cli-allocate/stderr.d.ts +14 -0
- package/dist/cli-allocate/stderr.d.ts.map +1 -0
- package/dist/cli-allocate/stderr.js +33 -0
- package/dist/cli-allocate/stderr.js.map +1 -0
- package/dist/cli-allocate/unreadable.d.ts +24 -0
- package/dist/cli-allocate/unreadable.d.ts.map +1 -0
- package/dist/cli-allocate/unreadable.js +43 -0
- package/dist/cli-allocate/unreadable.js.map +1 -0
- package/dist/cli-args.d.ts +73 -18
- package/dist/cli-args.d.ts.map +1 -1
- package/dist/cli-args.js +244 -22
- package/dist/cli-args.js.map +1 -1
- package/dist/cli-balance/cli.d.ts +31 -13
- package/dist/cli-balance/cli.d.ts.map +1 -1
- package/dist/cli-balance/cli.js +206 -251
- package/dist/cli-balance/cli.js.map +1 -1
- package/dist/cli-balance/emit/format.d.ts +18 -1
- package/dist/cli-balance/emit/format.d.ts.map +1 -1
- package/dist/cli-balance/emit/format.js +27 -2
- package/dist/cli-balance/emit/format.js.map +1 -1
- package/dist/cli-balance/emit/writer.d.ts +11 -25
- package/dist/cli-balance/emit/writer.d.ts.map +1 -1
- package/dist/cli-balance/emit/writer.js +58 -33
- package/dist/cli-balance/emit/writer.js.map +1 -1
- package/dist/cli-balance/exitStatus.d.ts +3 -0
- package/dist/cli-balance/exitStatus.d.ts.map +1 -0
- package/dist/cli-balance/exitStatus.js +12 -0
- package/dist/cli-balance/exitStatus.js.map +1 -0
- package/dist/cli-balance/reRender.d.ts +22 -0
- package/dist/cli-balance/reRender.d.ts.map +1 -0
- package/dist/cli-balance/reRender.js +36 -0
- package/dist/cli-balance/reRender.js.map +1 -0
- package/dist/cli-balance/unreadableInputs.d.ts +35 -0
- package/dist/cli-balance/unreadableInputs.d.ts.map +1 -0
- package/dist/cli-balance/unreadableInputs.js +101 -0
- package/dist/cli-balance/unreadableInputs.js.map +1 -0
- package/dist/cli-registry.d.ts +28 -0
- package/dist/cli-registry.d.ts.map +1 -1
- package/dist/cli-registry.js +40 -35
- package/dist/cli-registry.js.map +1 -1
- package/dist/cli.d.ts +4 -5
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +38 -22
- package/dist/cli.js.map +1 -1
- package/dist/host.d.ts +33 -3
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +153 -17
- package/dist/host.js.map +1 -1
- package/dist/ingest/gherkinValidation.d.ts +3 -7
- package/dist/ingest/gherkinValidation.d.ts.map +1 -1
- package/dist/ingest/gherkinValidation.js +3 -7
- package/dist/ingest/gherkinValidation.js.map +1 -1
- package/dist/ingest/ingestQualityChecks.d.ts +25 -54
- package/dist/ingest/ingestQualityChecks.d.ts.map +1 -1
- package/dist/ingest/ingestQualityChecks.js +112 -145
- package/dist/ingest/ingestQualityChecks.js.map +1 -1
- package/dist/ingest/ingestScenarios.d.ts +11 -17
- package/dist/ingest/ingestScenarios.d.ts.map +1 -1
- package/dist/ingest/ingestScenarios.js +52 -62
- package/dist/ingest/ingestScenarios.js.map +1 -1
- package/dist/ingest/inputShapes.d.ts +47 -0
- package/dist/ingest/inputShapes.d.ts.map +1 -0
- package/dist/ingest/inputShapes.js +143 -0
- package/dist/ingest/inputShapes.js.map +1 -0
- package/dist/outputLocation.d.ts +48 -0
- package/dist/outputLocation.d.ts.map +1 -0
- package/dist/outputLocation.js +140 -0
- package/dist/outputLocation.js.map +1 -0
- package/dist/run-management/keptRun.d.ts +29 -25
- package/dist/run-management/keptRun.d.ts.map +1 -1
- package/dist/run-management/keptRun.js +25 -21
- package/dist/run-management/keptRun.js.map +1 -1
- package/dist/storedRun.d.ts +27 -0
- package/dist/storedRun.d.ts.map +1 -0
- package/dist/storedRun.js +49 -0
- package/dist/storedRun.js.map +1 -0
- package/package.json +2 -2
- package/dist/corpus/cli.d.ts +0 -34
- package/dist/corpus/cli.d.ts.map +0 -1
- package/dist/corpus/cli.js +0 -128
- package/dist/corpus/cli.js.map +0 -1
- package/dist/mutation-ratchet/index.d.ts +0 -51
- package/dist/mutation-ratchet/index.d.ts.map +0 -1
- package/dist/mutation-ratchet/index.js +0 -51
- package/dist/mutation-ratchet/index.js.map +0 -1
- package/dist/mutation-ratchet/record.d.ts +0 -178
- package/dist/mutation-ratchet/record.d.ts.map +0 -1
- package/dist/mutation-ratchet/record.js +0 -314
- package/dist/mutation-ratchet/record.js.map +0 -1
- package/dist/mutation-ratchet/report.d.ts +0 -109
- package/dist/mutation-ratchet/report.d.ts.map +0 -1
- package/dist/mutation-ratchet/report.js +0 -156
- package/dist/mutation-ratchet/report.js.map +0 -1
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Ingest the target's feature files — the IMPURE reader that walks features/ on disk
|
|
3
|
-
* and
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* the
|
|
7
|
-
*
|
|
8
|
-
*
|
|
3
|
+
* and reads each file's header code into DiscoveredFeature records (split out of the old
|
|
4
|
+
* spec/featureCodes.ts). The pure header parser (extractFeatureCode) lives here with its sole
|
|
5
|
+
* consumer; the `scnId → feature-code` grammar helper is engine-side (packages/core/src/lib/
|
|
6
|
+
* featureCode.ts); the duplicate-code lint is engine-side too, in
|
|
7
|
+
* parse/scenarioCodeErrors.ts, and no longer a private check under tests/checks/.
|
|
8
|
+
*
|
|
9
|
+
* NO PER-FILE LIST OF SCENARIO CODES. One used to ride here, read by a copy of the
|
|
10
|
+
* @SCN grammar of its own, and nothing but its own test read it; a scenario's code is read by the
|
|
11
|
+
* engine's scenario walk, which is the reading the ledger's accounts are minted from.
|
|
9
12
|
*
|
|
10
13
|
* The foundation, folded into the @LDG runtime spine.
|
|
11
14
|
*/
|
|
@@ -21,56 +24,32 @@ const CODE_PATTERN = /^[A-Z0-9]{2,5}$/;
|
|
|
21
24
|
*/
|
|
22
25
|
export function extractFeatureCode(source) {
|
|
23
26
|
const lines = source.split(/\r?\n/);
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
break;
|
|
35
|
-
const tags = prev.split(/\s+/).filter((t) => t.startsWith("@"));
|
|
36
|
-
for (const tag of tags) {
|
|
37
|
-
const bare = tag.slice(1);
|
|
38
|
-
if (bare.startsWith("SCN-"))
|
|
39
|
-
continue;
|
|
40
|
-
if (["unit", "integration", "e2e", "performance", "smoke"].includes(bare))
|
|
41
|
-
continue;
|
|
42
|
-
if (CODE_PATTERN.test(bare))
|
|
43
|
-
return bare;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
return null;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
return null;
|
|
27
|
+
const featureLine = lines.findIndex((line) => line.trim().startsWith("Feature:"));
|
|
28
|
+
if (featureLine < 0)
|
|
29
|
+
return null;
|
|
30
|
+
return headerTagsAbove(lines, featureLine).find(isHeaderCode) ?? null;
|
|
31
|
+
}
|
|
32
|
+
/** The tags a tier or routing concern owns, which are never a file's header code. */
|
|
33
|
+
const NON_CODE_TAGS = ["unit", "integration", "e2e", "performance", "smoke"];
|
|
34
|
+
/** Is this bare tag the file's header code — not an @SCN id, not a tier tag, and code-shaped? */
|
|
35
|
+
function isHeaderCode(bare) {
|
|
36
|
+
return !bare.startsWith("SCN-") && !NON_CODE_TAGS.includes(bare) && CODE_PATTERN.test(bare);
|
|
50
37
|
}
|
|
51
38
|
/**
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
* line whose first non-blank character is `@` and whose every whitespace-
|
|
55
|
-
* separated token is a tag) — prose that merely mentions a code is skipped.
|
|
39
|
+
* The bare tags on the tag lines directly above the `Feature:` line, nearest line first and each
|
|
40
|
+
* line left to right. Blank lines are passed over; the first line that is not a tag line ends it.
|
|
56
41
|
*/
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
if (!line.startsWith("@"))
|
|
42
|
+
function headerTagsAbove(lines, featureLine) {
|
|
43
|
+
const tags = [];
|
|
44
|
+
for (let j = featureLine - 1; j >= 0; j--) {
|
|
45
|
+
const prev = lines[j].trim();
|
|
46
|
+
if (prev === "")
|
|
63
47
|
continue;
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
for (const token of tokens) {
|
|
68
|
-
const bare = token.slice(1);
|
|
69
|
-
if (codePattern.test(bare))
|
|
70
|
-
codes.push(bare);
|
|
71
|
-
}
|
|
48
|
+
if (!prev.startsWith("@"))
|
|
49
|
+
break;
|
|
50
|
+
tags.push(...prev.split(/\s+/).filter((t) => t.startsWith("@")).map((t) => t.slice(1)));
|
|
72
51
|
}
|
|
73
|
-
return
|
|
52
|
+
return tags;
|
|
74
53
|
}
|
|
75
54
|
/**
|
|
76
55
|
* Locate the default features/ directory — `./features` under the current
|
|
@@ -83,21 +62,19 @@ function featuresDir() {
|
|
|
83
62
|
return join(process.cwd(), "features");
|
|
84
63
|
}
|
|
85
64
|
/**
|
|
86
|
-
* List the corpus directory, REFUSING an absent one by name rather than by `ENOENT
|
|
87
|
-
* 3F-3345).
|
|
65
|
+
* List the corpus directory, REFUSING an absent one by name rather than by `ENOENT`.
|
|
88
66
|
*
|
|
89
67
|
* Absence stays a REFUSAL AT THE READER — what changed is that it is typed, so a caller can decide.
|
|
90
68
|
* `balance` opts in to reporting on a target that has no corpus (most targets have no `features/`
|
|
91
|
-
* at all, and `new-client` is a supported adoption state)
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
* into `tags: OK …` at exit 0.
|
|
69
|
+
* at all, and `new-client` is a supported adoption state) by catching `MissingFeatureCorpusError` by
|
|
70
|
+
* name. Returning `[]` here instead would make that decision for every caller, and one that
|
|
71
|
+
* certifies a corpus would certify an absent one as empty.
|
|
95
72
|
*
|
|
96
73
|
* ONLY `ENOENT`. A `features` path that exists but is a FILE, or one the process may not read, is
|
|
97
74
|
* a different fault and is not ours to reinterpret — it rethrows.
|
|
98
75
|
*
|
|
99
76
|
* A SUPPLIED `--features` never reaches here absent: `checkInputsExist` refuses it at the CLI edge
|
|
100
|
-
* with exit 2
|
|
77
|
+
* with exit 2. Supplied-but-absent is the operator mistyping; absent-by-default is
|
|
101
78
|
* the target's honest shape.
|
|
102
79
|
*/
|
|
103
80
|
function corpusFileNames(dir) {
|
|
@@ -110,9 +87,23 @@ function corpusFileNames(dir) {
|
|
|
110
87
|
throw err;
|
|
111
88
|
}
|
|
112
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* A feature file's text. A read the operating system refuses keeps its error, and gains the file's
|
|
92
|
+
* path when it has none: reading a directory fails with no path at all.
|
|
93
|
+
*/
|
|
94
|
+
function readFeatureSource(path) {
|
|
95
|
+
try {
|
|
96
|
+
return readFileSync(path, "utf8");
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
const errno = err;
|
|
100
|
+
errno.path ??= path;
|
|
101
|
+
throw err;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
113
104
|
/**
|
|
114
105
|
* Discover every feature file on disk and its header code — REFUSING a corpus the Gherkin
|
|
115
|
-
* parser rejects
|
|
106
|
+
* parser rejects.
|
|
116
107
|
*
|
|
117
108
|
* The refusal lives HERE, in the one reader every corpus consumer already goes through, so a
|
|
118
109
|
* consumer INHERITS it rather than restating it. It is not a second parser: it is the existing
|
|
@@ -120,7 +111,7 @@ function corpusFileNames(dir) {
|
|
|
120
111
|
* balance CLI's edge. One answer to "what is a feature file".
|
|
121
112
|
*
|
|
122
113
|
* WHY THE WHOLE CORPUS, NOT THE FIRST BAD FILE. `featureCorpusParseErrors` reports every
|
|
123
|
-
* unreadable file and the balance CLI prints them all
|
|
114
|
+
* unreadable file and the balance CLI prints them all. Refusing at the first
|
|
124
115
|
* failure would silently narrow that message, so every file is parsed and the refusal is raised
|
|
125
116
|
* once, carrying the lot in discovery order.
|
|
126
117
|
*
|
|
@@ -133,7 +124,7 @@ export function discoverFeatures(dir = featuresDir()) {
|
|
|
133
124
|
.sort()
|
|
134
125
|
.map((name) => {
|
|
135
126
|
const path = join(dir, name);
|
|
136
|
-
return { name, path, source:
|
|
127
|
+
return { name, path, source: readFeatureSource(path) };
|
|
137
128
|
});
|
|
138
129
|
const parseErrors = files.flatMap(({ path, source }) => parseErrorsIn(path, source));
|
|
139
130
|
if (parseErrors.length > 0)
|
|
@@ -142,7 +133,6 @@ export function discoverFeatures(dir = featuresDir()) {
|
|
|
142
133
|
slug: name.replace(/\.feature$/, ""),
|
|
143
134
|
path,
|
|
144
135
|
featureCode: extractFeatureCode(source),
|
|
145
|
-
scenarioCodes: parseScenarioCodes(source),
|
|
146
136
|
}));
|
|
147
137
|
}
|
|
148
138
|
//# sourceMappingURL=ingestScenarios.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ingestScenarios.js","sourceRoot":"","sources":["../../src/ingest/ingestScenarios.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"ingestScenarios.js","sourceRoot":"","sources":["../../src/ingest/ingestScenarios.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shape each JSON input `balance` reads must have before its reader is handed it: the fields
|
|
3
|
+
* that reader goes on to read, and nothing more. A report is its producer's document, so a field
|
|
4
|
+
* the reader never touches is never checked, and a well-formed empty report is still a report.
|
|
5
|
+
*
|
|
6
|
+
* A document that is not this shape is an input the tool cannot read. Read anyway, it either
|
|
7
|
+
* throws part-way or is read as nothing and reaches a verdict about evidence it discarded, and
|
|
8
|
+
* both of those end on the out-of-balance code.
|
|
9
|
+
*/
|
|
10
|
+
/** A leaf kind, a list of one shape, a record of named fields, or a map whose entries share one. */
|
|
11
|
+
export type Shape = "string" | "number" | "boolean" | "anything" | {
|
|
12
|
+
readonly list: Shape;
|
|
13
|
+
} | {
|
|
14
|
+
readonly fields: Readonly<Record<string, Shape>>;
|
|
15
|
+
readonly required?: readonly string[];
|
|
16
|
+
} | {
|
|
17
|
+
readonly entries: Shape;
|
|
18
|
+
readonly keys?: RegExp;
|
|
19
|
+
};
|
|
20
|
+
/** What the Vitest JSON reporter writes, as far as the Vitest reader reads it. */
|
|
21
|
+
export declare const VITEST_REPORT: Shape;
|
|
22
|
+
/** What the Cucumber JSON formatter writes, as far as the Cucumber reader reads it. */
|
|
23
|
+
export declare const CUCUMBER_REPORT: Shape;
|
|
24
|
+
/**
|
|
25
|
+
* A CI run's step record, as far as the step-record reader reads it: an object of steps keyed by
|
|
26
|
+
* step id, of which it credits only those keyed by a scenario code, reading each one's `outcome`.
|
|
27
|
+
* The key grammar is the reader's own, so a step it never credits is never checked.
|
|
28
|
+
*/
|
|
29
|
+
export declare const CI_STEP_RECORD: Shape;
|
|
30
|
+
/**
|
|
31
|
+
* A package.json, as far as the run reads it outside the `specController` block: an object whose
|
|
32
|
+
* `scripts` are a map of commands. The `specController` block is left to its own resolvers, which
|
|
33
|
+
* read a value they cannot use as absent rather than refuse the run over it.
|
|
34
|
+
*/
|
|
35
|
+
export declare const PACKAGE_MANIFEST: Shape;
|
|
36
|
+
/**
|
|
37
|
+
* A reconciliation this tool saved, at its top: the sections every one carries, and the optional
|
|
38
|
+
* ones in their kind. Below the top the renderer is the reader, and a document it cannot render
|
|
39
|
+
* is refused where it renders.
|
|
40
|
+
*/
|
|
41
|
+
export declare const SAVED_RECONCILIATION: Shape;
|
|
42
|
+
/**
|
|
43
|
+
* The first place `value` is not `shape`, said as the path to it and what was found there, or null
|
|
44
|
+
* when the whole value is that shape. `where` is the path so far; the top level has none.
|
|
45
|
+
*/
|
|
46
|
+
export declare function shapeFaultIn(value: unknown, shape: Shape, where?: string): string | null;
|
|
47
|
+
//# sourceMappingURL=inputShapes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inputShapes.d.ts","sourceRoot":"","sources":["../../src/ingest/inputShapes.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,oGAAoG;AACpG,MAAM,MAAM,KAAK,GACb,QAAQ,GACR,QAAQ,GACR,SAAS,GACT,UAAU,GACV;IAAE,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAA;CAAE,GACxB;IAAE,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC;IAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,GAC3F;IAAE,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAKxD,kFAAkF;AAClF,eAAO,MAAM,aAAa,EAAE,KAc3B,CAAC;AAEF,uFAAuF;AACvF,eAAO,MAAM,eAAe,EAAE,KAe7B,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,cAAc,EAAE,KAG5B,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,EAAE,KAAsD,CAAC;AAEtF;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,EAAE,KAWlC,CAAC;AAkBF;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,SAAK,GAAG,MAAM,GAAG,IAAI,CASpF"}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shape each JSON input `balance` reads must have before its reader is handed it: the fields
|
|
3
|
+
* that reader goes on to read, and nothing more. A report is its producer's document, so a field
|
|
4
|
+
* the reader never touches is never checked, and a well-formed empty report is still a report.
|
|
5
|
+
*
|
|
6
|
+
* A document that is not this shape is an input the tool cannot read. Read anyway, it either
|
|
7
|
+
* throws part-way or is read as nothing and reaches a verdict about evidence it discarded, and
|
|
8
|
+
* both of those end on the out-of-balance code.
|
|
9
|
+
*/
|
|
10
|
+
/** What the Vitest JSON reporter writes, as far as the Vitest reader reads it. */
|
|
11
|
+
export const VITEST_REPORT = {
|
|
12
|
+
fields: {
|
|
13
|
+
testResults: {
|
|
14
|
+
list: {
|
|
15
|
+
fields: {
|
|
16
|
+
name: "string",
|
|
17
|
+
status: "string",
|
|
18
|
+
message: "string",
|
|
19
|
+
assertionResults: { list: { fields: { fullName: "string", title: "string", status: "string" } } },
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
required: ["testResults"],
|
|
25
|
+
};
|
|
26
|
+
/** What the Cucumber JSON formatter writes, as far as the Cucumber reader reads it. */
|
|
27
|
+
export const CUCUMBER_REPORT = {
|
|
28
|
+
list: {
|
|
29
|
+
fields: {
|
|
30
|
+
uri: "string",
|
|
31
|
+
name: "string",
|
|
32
|
+
elements: {
|
|
33
|
+
list: {
|
|
34
|
+
fields: {
|
|
35
|
+
tags: { list: { fields: { name: "string" } } },
|
|
36
|
+
steps: { list: { fields: { keyword: "string", name: "string", result: { fields: { status: "string" } } } } },
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* A CI run's step record, as far as the step-record reader reads it: an object of steps keyed by
|
|
45
|
+
* step id, of which it credits only those keyed by a scenario code, reading each one's `outcome`.
|
|
46
|
+
* The key grammar is the reader's own, so a step it never credits is never checked.
|
|
47
|
+
*/
|
|
48
|
+
export const CI_STEP_RECORD = {
|
|
49
|
+
entries: { fields: { outcome: "string" } },
|
|
50
|
+
keys: /^SCN-[A-Z0-9]{2,5}-\d+$/,
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* A package.json, as far as the run reads it outside the `specController` block: an object whose
|
|
54
|
+
* `scripts` are a map of commands. The `specController` block is left to its own resolvers, which
|
|
55
|
+
* read a value they cannot use as absent rather than refuse the run over it.
|
|
56
|
+
*/
|
|
57
|
+
export const PACKAGE_MANIFEST = { fields: { scripts: { entries: "string" } } };
|
|
58
|
+
/**
|
|
59
|
+
* A reconciliation this tool saved, at its top: the sections every one carries, and the optional
|
|
60
|
+
* ones in their kind. Below the top the renderer is the reader, and a document it cannot render
|
|
61
|
+
* is refused where it renders.
|
|
62
|
+
*/
|
|
63
|
+
export const SAVED_RECONCILIATION = {
|
|
64
|
+
fields: {
|
|
65
|
+
runProvenance: { fields: {} },
|
|
66
|
+
summary: { fields: {} },
|
|
67
|
+
ledger: { fields: { scenarioReconciliations: { list: "anything" } }, required: ["scenarioReconciliations"] },
|
|
68
|
+
scenarioCodeErrors: { list: "anything" },
|
|
69
|
+
scenarioCodePopulation: { fields: {} },
|
|
70
|
+
evidenceObligations: { fields: {} },
|
|
71
|
+
metadata: { fields: {} },
|
|
72
|
+
},
|
|
73
|
+
required: ["runProvenance", "summary", "ledger"],
|
|
74
|
+
};
|
|
75
|
+
function kindOf(value) {
|
|
76
|
+
if (value === null)
|
|
77
|
+
return "null";
|
|
78
|
+
if (Array.isArray(value))
|
|
79
|
+
return "a list";
|
|
80
|
+
if (typeof value === "object")
|
|
81
|
+
return "an object";
|
|
82
|
+
return `a ${typeof value}`;
|
|
83
|
+
}
|
|
84
|
+
function expectedKindOf(shape) {
|
|
85
|
+
if (typeof shape === "string")
|
|
86
|
+
return `a ${shape}`;
|
|
87
|
+
return "list" in shape ? "a list" : "an object";
|
|
88
|
+
}
|
|
89
|
+
function pathOf(where, key) {
|
|
90
|
+
return where === "" ? key : `${where}.${key}`;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* The first place `value` is not `shape`, said as the path to it and what was found there, or null
|
|
94
|
+
* when the whole value is that shape. `where` is the path so far; the top level has none.
|
|
95
|
+
*/
|
|
96
|
+
export function shapeFaultIn(value, shape, where = "") {
|
|
97
|
+
if (shape === "anything")
|
|
98
|
+
return null;
|
|
99
|
+
const found = kindOf(value);
|
|
100
|
+
const expected = expectedKindOf(shape);
|
|
101
|
+
if (found !== expected)
|
|
102
|
+
return `${where === "" ? "the top level" : where} is ${found}, not ${expected}`;
|
|
103
|
+
if (typeof shape === "string")
|
|
104
|
+
return null;
|
|
105
|
+
if ("list" in shape)
|
|
106
|
+
return listFault(value, shape.list, where);
|
|
107
|
+
if ("entries" in shape)
|
|
108
|
+
return entriesFault(value, shape, where);
|
|
109
|
+
return fieldsFault(value, shape, where);
|
|
110
|
+
}
|
|
111
|
+
function listFault(items, item, where) {
|
|
112
|
+
for (const [index, value] of items.entries()) {
|
|
113
|
+
const fault = shapeFaultIn(value, item, `${where}[${index}]`);
|
|
114
|
+
if (fault !== null)
|
|
115
|
+
return fault;
|
|
116
|
+
}
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
function fieldsFault(record, shape, where) {
|
|
120
|
+
for (const [key, field] of Object.entries(shape.fields)) {
|
|
121
|
+
const path = pathOf(where, key);
|
|
122
|
+
if (record[key] === undefined) {
|
|
123
|
+
if (shape.required?.includes(key) === true)
|
|
124
|
+
return `${path} is missing`;
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
const fault = shapeFaultIn(record[key], field, path);
|
|
128
|
+
if (fault !== null)
|
|
129
|
+
return fault;
|
|
130
|
+
}
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
function entriesFault(record, shape, where) {
|
|
134
|
+
for (const [key, value] of Object.entries(record)) {
|
|
135
|
+
if (shape.keys !== undefined && !shape.keys.test(key))
|
|
136
|
+
continue;
|
|
137
|
+
const fault = shapeFaultIn(value, shape.entries, pathOf(where, key));
|
|
138
|
+
if (fault !== null)
|
|
139
|
+
return fault;
|
|
140
|
+
}
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=inputShapes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inputShapes.js","sourceRoot":"","sources":["../../src/ingest/inputShapes.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export declare function unwritableRefusal(subject: string, path: string, err: unknown): string;
|
|
2
|
+
/**
|
|
3
|
+
* The catch sits on the one write it guards rather than on the process, so a refusal can only ever
|
|
4
|
+
* name a location this call was asked to write, and a crash anywhere else is not dressed up as one.
|
|
5
|
+
*/
|
|
6
|
+
export declare function writeOrRefuse(subject: string, path: string, write: () => void): void;
|
|
7
|
+
/** Refuses the run on the usage code with one line, for a refusal decided before anything is written. */
|
|
8
|
+
export declare function refuse(message: string): never;
|
|
9
|
+
/** The operating system's identity for a regular file; anything else has none. */
|
|
10
|
+
export interface RegularFileIdentity {
|
|
11
|
+
readonly dev: bigint;
|
|
12
|
+
readonly ino: bigint;
|
|
13
|
+
}
|
|
14
|
+
/** What opening a destination found: the identity of the regular file it is, and the file it had to create, if any. */
|
|
15
|
+
export interface ProbedDestination {
|
|
16
|
+
readonly identity: RegularFileIdentity | undefined;
|
|
17
|
+
readonly created: string | undefined;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Throws the operating system's own error when `path` cannot be written as a file, and otherwise
|
|
21
|
+
* answers which regular file it is. An existing file is opened write-only without truncation (not
|
|
22
|
+
* "r+", which would also demand read permission). An absent one is created and left in place for the
|
|
23
|
+
* caller to remove once every destination has been identified: two names for one absent file share
|
|
24
|
+
* an identity only once one of them exists. So a missing, read-only or non-directory parent answers
|
|
25
|
+
* with its real reason instead of one this module would have to invent.
|
|
26
|
+
*
|
|
27
|
+
* The creation is not exclusive, because the write-only open has already answered that nothing exists
|
|
28
|
+
* at the end of the path, and an exclusive create would refuse a link whose target does not exist yet
|
|
29
|
+
* although the write would follow it. What the caller removes is the real file created, never the
|
|
30
|
+
* link that led to it.
|
|
31
|
+
*/
|
|
32
|
+
export declare function probeDestination(path: string): ProbedDestination;
|
|
33
|
+
/**
|
|
34
|
+
* The first of `others` that is the same regular file as `file`, or undefined when none is. Every
|
|
35
|
+
* file created to find out is removed again. A path that cannot be opened shares nothing here: the
|
|
36
|
+
* write that would use it refuses it on its own terms.
|
|
37
|
+
*/
|
|
38
|
+
export declare function firstSharingFile(file: string, others: readonly string[]): string | undefined;
|
|
39
|
+
/** The identity of the regular file standard output is, when it is one. */
|
|
40
|
+
export declare function stdoutIdentity(): RegularFileIdentity | undefined;
|
|
41
|
+
/**
|
|
42
|
+
* Whether `path` names standard output: spelled as file descriptor 1, or reaching such a spelling
|
|
43
|
+
* through links, followed one at a time as `realpath` would. Asking what the path spells rather than
|
|
44
|
+
* which file it opens is deliberate: when standard output and standard error share one terminal or
|
|
45
|
+
* one pipe, /dev/stderr opens the very same file, and it does not name standard output.
|
|
46
|
+
*/
|
|
47
|
+
export declare function namesStdout(path: string): boolean;
|
|
48
|
+
//# sourceMappingURL=outputLocation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outputLocation.d.ts","sourceRoot":"","sources":["../src/outputLocation.ts"],"names":[],"mappings":"AAKA,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,GAAG,MAAM,CAGrF;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,IAAI,GAAG,IAAI,CAOpF;AAED,yGAAyG;AACzG,wBAAgB,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,KAAK,CAG7C;AAED,kFAAkF;AAClF,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED,uHAAuH;AACvH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,QAAQ,EAAE,mBAAmB,GAAG,SAAS,CAAC;IACnD,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;CACtC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB,CAQhE;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,CAS5F;AAgBD,2EAA2E;AAC3E,wBAAgB,cAAc,IAAI,mBAAmB,GAAG,SAAS,CAMhE;AAoBD;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAOjD"}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { closeSync, constants, fstatSync, lstatSync, openSync, readlinkSync, realpathSync, unlinkSync } from "node:fs";
|
|
2
|
+
import { dirname, resolve, sep } from "node:path";
|
|
3
|
+
const USAGE_EXIT_CODE = 2;
|
|
4
|
+
export function unwritableRefusal(subject, path, err) {
|
|
5
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
6
|
+
return `${subject}: ${path} (${reason})`;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The catch sits on the one write it guards rather than on the process, so a refusal can only ever
|
|
10
|
+
* name a location this call was asked to write, and a crash anywhere else is not dressed up as one.
|
|
11
|
+
*/
|
|
12
|
+
export function writeOrRefuse(subject, path, write) {
|
|
13
|
+
try {
|
|
14
|
+
write();
|
|
15
|
+
}
|
|
16
|
+
catch (err) {
|
|
17
|
+
process.stderr.write(unwritableRefusal(subject, path, err) + "\n");
|
|
18
|
+
process.exit(USAGE_EXIT_CODE);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/** Refuses the run on the usage code with one line, for a refusal decided before anything is written. */
|
|
22
|
+
export function refuse(message) {
|
|
23
|
+
process.stderr.write(message + "\n");
|
|
24
|
+
return process.exit(USAGE_EXIT_CODE);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Throws the operating system's own error when `path` cannot be written as a file, and otherwise
|
|
28
|
+
* answers which regular file it is. An existing file is opened write-only without truncation (not
|
|
29
|
+
* "r+", which would also demand read permission). An absent one is created and left in place for the
|
|
30
|
+
* caller to remove once every destination has been identified: two names for one absent file share
|
|
31
|
+
* an identity only once one of them exists. So a missing, read-only or non-directory parent answers
|
|
32
|
+
* with its real reason instead of one this module would have to invent.
|
|
33
|
+
*
|
|
34
|
+
* The creation is not exclusive, because the write-only open has already answered that nothing exists
|
|
35
|
+
* at the end of the path, and an exclusive create would refuse a link whose target does not exist yet
|
|
36
|
+
* although the write would follow it. What the caller removes is the real file created, never the
|
|
37
|
+
* link that led to it.
|
|
38
|
+
*/
|
|
39
|
+
export function probeDestination(path) {
|
|
40
|
+
try {
|
|
41
|
+
return { identity: identityOf(openSync(path, constants.O_WRONLY)), created: undefined };
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
if (err.code !== "ENOENT")
|
|
45
|
+
throw err;
|
|
46
|
+
}
|
|
47
|
+
const identity = identityOf(openSync(path, constants.O_WRONLY | constants.O_CREAT));
|
|
48
|
+
return { identity, created: realpathSync(path) };
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The first of `others` that is the same regular file as `file`, or undefined when none is. Every
|
|
52
|
+
* file created to find out is removed again. A path that cannot be opened shares nothing here: the
|
|
53
|
+
* write that would use it refuses it on its own terms.
|
|
54
|
+
*/
|
|
55
|
+
export function firstSharingFile(file, others) {
|
|
56
|
+
const created = [];
|
|
57
|
+
try {
|
|
58
|
+
const identity = identityOrNone(file, created);
|
|
59
|
+
if (identity === undefined)
|
|
60
|
+
return undefined;
|
|
61
|
+
return others.find((other) => sameIdentity(identityOrNone(other, created), identity));
|
|
62
|
+
}
|
|
63
|
+
finally {
|
|
64
|
+
for (const path of created)
|
|
65
|
+
unlinkSync(path);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function identityOrNone(path, created) {
|
|
69
|
+
try {
|
|
70
|
+
const probed = probeDestination(path);
|
|
71
|
+
if (probed.created !== undefined)
|
|
72
|
+
created.push(probed.created);
|
|
73
|
+
return probed.identity;
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function sameIdentity(a, b) {
|
|
80
|
+
return a !== undefined && a.dev === b.dev && a.ino === b.ino;
|
|
81
|
+
}
|
|
82
|
+
/** The identity of the regular file standard output is, when it is one. */
|
|
83
|
+
export function stdoutIdentity() {
|
|
84
|
+
try {
|
|
85
|
+
return regularIdentity(fstatSync(1, { bigint: true }));
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function identityOf(fd) {
|
|
92
|
+
try {
|
|
93
|
+
return regularIdentity(fstatSync(fd, { bigint: true }));
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
closeSync(fd);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function regularIdentity(stats) {
|
|
100
|
+
return stats.isFile() ? { dev: stats.dev, ino: stats.ino } : undefined;
|
|
101
|
+
}
|
|
102
|
+
/** The spellings of file descriptor 1 itself, on macOS and on Linux. */
|
|
103
|
+
const STDOUT_SPELLINGS = new Set(["/dev/stdout", "/dev/fd/1", "/proc/self/fd/1", "/proc/thread-self/fd/1", `/proc/${process.pid}/fd/1`]);
|
|
104
|
+
/** A bound on the links followed, as the kernel sets one, so a loop of links answers rather than spins. */
|
|
105
|
+
const MAX_LINKS_FOLLOWED = 40;
|
|
106
|
+
/**
|
|
107
|
+
* Whether `path` names standard output: spelled as file descriptor 1, or reaching such a spelling
|
|
108
|
+
* through links, followed one at a time as `realpath` would. Asking what the path spells rather than
|
|
109
|
+
* which file it opens is deliberate: when standard output and standard error share one terminal or
|
|
110
|
+
* one pipe, /dev/stderr opens the very same file, and it does not name standard output.
|
|
111
|
+
*/
|
|
112
|
+
export function namesStdout(path) {
|
|
113
|
+
let current = resolve(path);
|
|
114
|
+
for (let followed = 0; current !== undefined && followed <= MAX_LINKS_FOLLOWED; followed++) {
|
|
115
|
+
if (STDOUT_SPELLINGS.has(current))
|
|
116
|
+
return true;
|
|
117
|
+
current = withFirstLinkFollowed(current);
|
|
118
|
+
}
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
/** `path` with its first linked component replaced by the link's target, or undefined when none is a link. */
|
|
122
|
+
function withFirstLinkFollowed(path) {
|
|
123
|
+
const parts = path.split(sep).filter((part) => part !== "");
|
|
124
|
+
for (let length = 1; length <= parts.length; length++) {
|
|
125
|
+
const prefix = sep + parts.slice(0, length).join(sep);
|
|
126
|
+
const target = linkTarget(prefix);
|
|
127
|
+
if (target !== undefined)
|
|
128
|
+
return resolve(dirname(prefix), target, ...parts.slice(length));
|
|
129
|
+
}
|
|
130
|
+
return undefined;
|
|
131
|
+
}
|
|
132
|
+
function linkTarget(path) {
|
|
133
|
+
try {
|
|
134
|
+
return lstatSync(path).isSymbolicLink() ? readlinkSync(path) : undefined;
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=outputLocation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outputLocation.js","sourceRoot":"","sources":["../src/outputLocation.ts"],"names":[],"mappings":"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"}
|