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.
Files changed (118) hide show
  1. package/README.md +34 -6
  2. package/dist/cli-allocate/cli.d.ts +2 -0
  3. package/dist/cli-allocate/cli.d.ts.map +1 -0
  4. package/dist/cli-allocate/cli.js +57 -0
  5. package/dist/cli-allocate/cli.js.map +1 -0
  6. package/dist/cli-allocate/readHistory.d.ts +29 -0
  7. package/dist/cli-allocate/readHistory.d.ts.map +1 -0
  8. package/dist/cli-allocate/readHistory.js +136 -0
  9. package/dist/cli-allocate/readHistory.js.map +1 -0
  10. package/dist/cli-allocate/readParts.d.ts +3 -0
  11. package/dist/cli-allocate/readParts.d.ts.map +1 -0
  12. package/dist/cli-allocate/readParts.js +28 -0
  13. package/dist/cli-allocate/readParts.js.map +1 -0
  14. package/dist/cli-allocate/readTree.d.ts +16 -0
  15. package/dist/cli-allocate/readTree.d.ts.map +1 -0
  16. package/dist/cli-allocate/readTree.js +106 -0
  17. package/dist/cli-allocate/readTree.js.map +1 -0
  18. package/dist/cli-allocate/refStore.d.ts +3 -0
  19. package/dist/cli-allocate/refStore.d.ts.map +1 -0
  20. package/dist/cli-allocate/refStore.js +47 -0
  21. package/dist/cli-allocate/refStore.js.map +1 -0
  22. package/dist/cli-allocate/request.d.ts +11 -0
  23. package/dist/cli-allocate/request.d.ts.map +1 -0
  24. package/dist/cli-allocate/request.js +29 -0
  25. package/dist/cli-allocate/request.js.map +1 -0
  26. package/dist/cli-allocate/stderr.d.ts +14 -0
  27. package/dist/cli-allocate/stderr.d.ts.map +1 -0
  28. package/dist/cli-allocate/stderr.js +33 -0
  29. package/dist/cli-allocate/stderr.js.map +1 -0
  30. package/dist/cli-allocate/unreadable.d.ts +24 -0
  31. package/dist/cli-allocate/unreadable.d.ts.map +1 -0
  32. package/dist/cli-allocate/unreadable.js +43 -0
  33. package/dist/cli-allocate/unreadable.js.map +1 -0
  34. package/dist/cli-args.d.ts +73 -18
  35. package/dist/cli-args.d.ts.map +1 -1
  36. package/dist/cli-args.js +244 -22
  37. package/dist/cli-args.js.map +1 -1
  38. package/dist/cli-balance/cli.d.ts +31 -13
  39. package/dist/cli-balance/cli.d.ts.map +1 -1
  40. package/dist/cli-balance/cli.js +206 -251
  41. package/dist/cli-balance/cli.js.map +1 -1
  42. package/dist/cli-balance/emit/format.d.ts +18 -1
  43. package/dist/cli-balance/emit/format.d.ts.map +1 -1
  44. package/dist/cli-balance/emit/format.js +27 -2
  45. package/dist/cli-balance/emit/format.js.map +1 -1
  46. package/dist/cli-balance/emit/writer.d.ts +11 -25
  47. package/dist/cli-balance/emit/writer.d.ts.map +1 -1
  48. package/dist/cli-balance/emit/writer.js +58 -33
  49. package/dist/cli-balance/emit/writer.js.map +1 -1
  50. package/dist/cli-balance/exitStatus.d.ts +3 -0
  51. package/dist/cli-balance/exitStatus.d.ts.map +1 -0
  52. package/dist/cli-balance/exitStatus.js +12 -0
  53. package/dist/cli-balance/exitStatus.js.map +1 -0
  54. package/dist/cli-balance/reRender.d.ts +22 -0
  55. package/dist/cli-balance/reRender.d.ts.map +1 -0
  56. package/dist/cli-balance/reRender.js +36 -0
  57. package/dist/cli-balance/reRender.js.map +1 -0
  58. package/dist/cli-balance/unreadableInputs.d.ts +35 -0
  59. package/dist/cli-balance/unreadableInputs.d.ts.map +1 -0
  60. package/dist/cli-balance/unreadableInputs.js +101 -0
  61. package/dist/cli-balance/unreadableInputs.js.map +1 -0
  62. package/dist/cli-registry.d.ts +28 -0
  63. package/dist/cli-registry.d.ts.map +1 -1
  64. package/dist/cli-registry.js +40 -35
  65. package/dist/cli-registry.js.map +1 -1
  66. package/dist/cli.d.ts +4 -5
  67. package/dist/cli.d.ts.map +1 -1
  68. package/dist/cli.js +38 -22
  69. package/dist/cli.js.map +1 -1
  70. package/dist/host.d.ts +33 -3
  71. package/dist/host.d.ts.map +1 -1
  72. package/dist/host.js +153 -17
  73. package/dist/host.js.map +1 -1
  74. package/dist/ingest/gherkinValidation.d.ts +3 -7
  75. package/dist/ingest/gherkinValidation.d.ts.map +1 -1
  76. package/dist/ingest/gherkinValidation.js +3 -7
  77. package/dist/ingest/gherkinValidation.js.map +1 -1
  78. package/dist/ingest/ingestQualityChecks.d.ts +25 -54
  79. package/dist/ingest/ingestQualityChecks.d.ts.map +1 -1
  80. package/dist/ingest/ingestQualityChecks.js +112 -145
  81. package/dist/ingest/ingestQualityChecks.js.map +1 -1
  82. package/dist/ingest/ingestScenarios.d.ts +11 -17
  83. package/dist/ingest/ingestScenarios.d.ts.map +1 -1
  84. package/dist/ingest/ingestScenarios.js +52 -62
  85. package/dist/ingest/ingestScenarios.js.map +1 -1
  86. package/dist/ingest/inputShapes.d.ts +47 -0
  87. package/dist/ingest/inputShapes.d.ts.map +1 -0
  88. package/dist/ingest/inputShapes.js +143 -0
  89. package/dist/ingest/inputShapes.js.map +1 -0
  90. package/dist/outputLocation.d.ts +48 -0
  91. package/dist/outputLocation.d.ts.map +1 -0
  92. package/dist/outputLocation.js +140 -0
  93. package/dist/outputLocation.js.map +1 -0
  94. package/dist/run-management/keptRun.d.ts +29 -25
  95. package/dist/run-management/keptRun.d.ts.map +1 -1
  96. package/dist/run-management/keptRun.js +25 -21
  97. package/dist/run-management/keptRun.js.map +1 -1
  98. package/dist/storedRun.d.ts +27 -0
  99. package/dist/storedRun.d.ts.map +1 -0
  100. package/dist/storedRun.js +49 -0
  101. package/dist/storedRun.js.map +1 -0
  102. package/package.json +2 -2
  103. package/dist/corpus/cli.d.ts +0 -34
  104. package/dist/corpus/cli.d.ts.map +0 -1
  105. package/dist/corpus/cli.js +0 -128
  106. package/dist/corpus/cli.js.map +0 -1
  107. package/dist/mutation-ratchet/index.d.ts +0 -51
  108. package/dist/mutation-ratchet/index.d.ts.map +0 -1
  109. package/dist/mutation-ratchet/index.js +0 -51
  110. package/dist/mutation-ratchet/index.js.map +0 -1
  111. package/dist/mutation-ratchet/record.d.ts +0 -178
  112. package/dist/mutation-ratchet/record.d.ts.map +0 -1
  113. package/dist/mutation-ratchet/record.js +0 -314
  114. package/dist/mutation-ratchet/record.js.map +0 -1
  115. package/dist/mutation-ratchet/report.d.ts +0 -109
  116. package/dist/mutation-ratchet/report.d.ts.map +0 -1
  117. package/dist/mutation-ratchet/report.js +0 -156
  118. 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 parses each file's feature code + scenario codes into DiscoveredFeature
4
- * records (split out of the old spec/featureCodes.ts). The pure per-source
5
- * parsers (extractFeatureCode / parseScenarioCodes) live here with their sole consumer;
6
- * the `scnId → feature-code` grammar helper is engine-side (packages/core/src/lib/
7
- * featureCode.ts); the duplicate-code lint is engine-side too since 3F-3313, as @SCN-STG-010 in
8
- * parse/corpusFaults.ts, and no longer a private check under tests/checks/.
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
- for (let i = 0; i < lines.length; i++) {
25
- const line = lines[i].trim();
26
- if (line === "" || line.startsWith("#"))
27
- continue;
28
- if (line.startsWith("Feature:")) {
29
- for (let j = i - 1; j >= 0; j--) {
30
- const prev = lines[j].trim();
31
- if (prev === "")
32
- continue;
33
- if (!prev.startsWith("@"))
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
- * Parse every @SCN-<FFF>-<NNN> scenario code in a feature file's source, in
53
- * document order, duplicates preserved. Only genuine TAG LINES are scanned (a
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
- export function parseScenarioCodes(source) {
58
- const codes = [];
59
- const codePattern = /^SCN-[A-Z0-9]+-\d+$/;
60
- for (const rawLine of source.split(/\r?\n/)) {
61
- const line = rawLine.trim();
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
- const tokens = line.split(/\s+/);
65
- if (!tokens.every((t) => t.startsWith("@")))
66
- continue;
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 codes;
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` (@SCN-CLI-017,
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); `tags` keeps refusing, unedited, because
92
- * a `MissingFeatureCorpusError` is still an `Error` and still lands in its generic catch. Returning
93
- * `[]` here instead would make that decision for both, and measured it turns two named refusals
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 (@SCN-CLI-002). Supplied-but-absent is the operator mistyping; absent-by-default is
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 (@SCN-GPG-001, 3F-2817).
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 (@SCN-CLI-016). Refusing at the first
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: readFileSync(path, "utf8") };
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":"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"}
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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"}