@goodbones/core 0.1.0-beta.10 → 0.1.0-beta.12

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 (119) hide show
  1. package/build/dts/core/structure.d.ts.map +1 -1
  2. package/build/dts/domain/architecture-config.d.ts +3 -310
  3. package/build/dts/domain/architecture-config.d.ts.map +1 -1
  4. package/build/dts/domain/snapshot.d.ts +150 -18
  5. package/build/dts/domain/snapshot.d.ts.map +1 -1
  6. package/build/dts/index.d.ts +11 -14
  7. package/build/dts/index.d.ts.map +1 -1
  8. package/build/dts/infrastructure/file-system-fake.d.ts.map +1 -1
  9. package/build/dts/infrastructure/file-system-live.d.ts.map +1 -1
  10. package/build/dts/infrastructure/walk.d.ts +6 -1
  11. package/build/dts/infrastructure/walk.d.ts.map +1 -1
  12. package/build/dts/load/extension.d.ts +33 -0
  13. package/build/dts/load/extension.d.ts.map +1 -0
  14. package/build/dts/load/policy.d.ts +4 -10
  15. package/build/dts/load/policy.d.ts.map +1 -1
  16. package/build/dts/manifest/compile.d.ts +7 -2
  17. package/build/dts/manifest/compile.d.ts.map +1 -1
  18. package/build/dts/manifest/extension.d.ts +8 -0
  19. package/build/dts/manifest/extension.d.ts.map +1 -0
  20. package/build/dts/manifest/glob.d.ts +1 -0
  21. package/build/dts/manifest/glob.d.ts.map +1 -1
  22. package/build/dts/manifest/json-schema.d.ts +6 -2
  23. package/build/dts/manifest/json-schema.d.ts.map +1 -1
  24. package/build/dts/manifest/manifest.d.ts +7 -362
  25. package/build/dts/manifest/manifest.d.ts.map +1 -1
  26. package/build/dts/ports/file-system.d.ts +1 -0
  27. package/build/dts/ports/file-system.d.ts.map +1 -1
  28. package/build/dts/testing.d.ts +0 -1
  29. package/build/dts/testing.d.ts.map +1 -1
  30. package/build/esm/core/structure.js +1 -1
  31. package/build/esm/core/structure.js.map +1 -1
  32. package/build/esm/domain/architecture-config.js +3 -136
  33. package/build/esm/domain/architecture-config.js.map +1 -1
  34. package/build/esm/domain/snapshot.js +55 -15
  35. package/build/esm/domain/snapshot.js.map +1 -1
  36. package/build/esm/index.js +16 -10
  37. package/build/esm/index.js.map +1 -1
  38. package/build/esm/infrastructure/file-system-fake.js +10 -0
  39. package/build/esm/infrastructure/file-system-fake.js.map +1 -1
  40. package/build/esm/infrastructure/file-system-live.js +9 -1
  41. package/build/esm/infrastructure/file-system-live.js.map +1 -1
  42. package/build/esm/infrastructure/walk.js +50 -4
  43. package/build/esm/infrastructure/walk.js.map +1 -1
  44. package/build/esm/load/extension.js +2 -0
  45. package/build/esm/load/extension.js.map +1 -0
  46. package/build/esm/load/policy.js +43 -116
  47. package/build/esm/load/policy.js.map +1 -1
  48. package/build/esm/manifest/compile.js +3 -182
  49. package/build/esm/manifest/compile.js.map +1 -1
  50. package/build/esm/manifest/extension.js +2 -0
  51. package/build/esm/manifest/extension.js.map +1 -0
  52. package/build/esm/manifest/glob.js +3 -0
  53. package/build/esm/manifest/glob.js.map +1 -1
  54. package/build/esm/manifest/json-schema.js +45 -11
  55. package/build/esm/manifest/json-schema.js.map +1 -1
  56. package/build/esm/manifest/manifest.js +56 -172
  57. package/build/esm/manifest/manifest.js.map +1 -1
  58. package/build/esm/testing.js +0 -1
  59. package/build/esm/testing.js.map +1 -1
  60. package/package.json +1 -1
  61. package/schema/architecture-node.schema.json +0 -887
  62. package/schema/architecture.schema.json +3264 -1591
  63. package/schema/conformance.schema.json +291 -47
  64. package/src/core/structure.ts +5 -3
  65. package/src/domain/architecture-config.ts +3 -180
  66. package/src/domain/snapshot.ts +125 -25
  67. package/src/index.ts +51 -78
  68. package/src/infrastructure/file-system-fake.ts +12 -0
  69. package/src/infrastructure/file-system-live.ts +8 -1
  70. package/src/infrastructure/walk.ts +58 -3
  71. package/src/load/extension.ts +66 -0
  72. package/src/load/policy.ts +60 -175
  73. package/src/manifest/compile.ts +2 -213
  74. package/src/manifest/extension.ts +41 -0
  75. package/src/manifest/glob.ts +5 -0
  76. package/src/manifest/json-schema.ts +53 -11
  77. package/src/manifest/manifest.ts +82 -214
  78. package/src/ports/file-system.ts +3 -0
  79. package/src/testing.ts +0 -1
  80. package/build/dts/core/campaigns.d.ts +0 -141
  81. package/build/dts/core/campaigns.d.ts.map +0 -1
  82. package/build/dts/core/ledger.d.ts +0 -61
  83. package/build/dts/core/ledger.d.ts.map +0 -1
  84. package/build/dts/domain/report.d.ts +0 -16
  85. package/build/dts/domain/report.d.ts.map +0 -1
  86. package/build/dts/infrastructure/campaign-functions.d.ts +0 -7
  87. package/build/dts/infrastructure/campaign-functions.d.ts.map +0 -1
  88. package/build/dts/infrastructure/report-source-fake.d.ts +0 -4
  89. package/build/dts/infrastructure/report-source-fake.d.ts.map +0 -1
  90. package/build/dts/infrastructure/report-source-live.d.ts +0 -3
  91. package/build/dts/infrastructure/report-source-live.d.ts.map +0 -1
  92. package/build/dts/ports/campaign-predicate.d.ts +0 -24
  93. package/build/dts/ports/campaign-predicate.d.ts.map +0 -1
  94. package/build/dts/ports/report-source.d.ts +0 -14
  95. package/build/dts/ports/report-source.d.ts.map +0 -1
  96. package/build/esm/core/campaigns.js +0 -778
  97. package/build/esm/core/campaigns.js.map +0 -1
  98. package/build/esm/core/ledger.js +0 -171
  99. package/build/esm/core/ledger.js.map +0 -1
  100. package/build/esm/domain/report.js +0 -168
  101. package/build/esm/domain/report.js.map +0 -1
  102. package/build/esm/infrastructure/campaign-functions.js +0 -67
  103. package/build/esm/infrastructure/campaign-functions.js.map +0 -1
  104. package/build/esm/infrastructure/report-source-fake.js +0 -6
  105. package/build/esm/infrastructure/report-source-fake.js.map +0 -1
  106. package/build/esm/infrastructure/report-source-live.js +0 -165
  107. package/build/esm/infrastructure/report-source-live.js.map +0 -1
  108. package/build/esm/ports/campaign-predicate.js +0 -2
  109. package/build/esm/ports/campaign-predicate.js.map +0 -1
  110. package/build/esm/ports/report-source.js +0 -7
  111. package/build/esm/ports/report-source.js.map +0 -1
  112. package/src/core/campaigns.ts +0 -1056
  113. package/src/core/ledger.ts +0 -242
  114. package/src/domain/report.ts +0 -203
  115. package/src/infrastructure/campaign-functions.ts +0 -98
  116. package/src/infrastructure/report-source-fake.ts +0 -10
  117. package/src/infrastructure/report-source-live.ts +0 -192
  118. package/src/ports/campaign-predicate.ts +0 -30
  119. package/src/ports/report-source.ts +0 -35
@@ -1,192 +0,0 @@
1
- import { spawn, spawnSync } from "node:child_process";
2
- import { readFileSync } from "node:fs";
3
- import { availableParallelism } from "node:os";
4
- import * as path from "node:path";
5
-
6
- import { ReportUnavailable } from "../domain/architecture-error.js";
7
- import { type Diagnostic, indexByFile, parseReport, uniqueDiagnostics } from "../domain/report.js";
8
- import { type ReportSource, reportSourcesOf, type ReportSpec } from "../ports/report-source.js";
9
-
10
- // The report source on this host: runs a `report` term's commands from the
11
- // repository root, or reads the files it names, and parses the output once
12
- // — a report is about the whole repository, a campaign asks per file, and
13
- // the plugin asks for one file at a time across a whole lint run. Cached
14
- // per spec for the life of the process: the CLI is one process per `check`,
15
- // and oxlint's language server is one process per editor session, which
16
- // sees the report as of when it loaded the plugin. A report the build
17
- // writes to a file is the predictable form for the editor, and for CI.
18
- //
19
- // A term may name several commands — a tool that checks one project at a
20
- // time, run once per project — and their outputs are one report: parsed
21
- // each, joined, and a diagnostic two of them print kept once. `read` runs
22
- // them concurrently, as many at a time as the machine has cores, which is
23
- // why both hosts call it before any file asks; `diagnosticsOf` with no
24
- // answer kept runs them one after another, synchronously, and keeps that.
25
- //
26
- // A non-zero exit is not a failure — `tsc` exits 2 when there are errors,
27
- // which is the case the term exists for. A command that cannot be spawned,
28
- // or a file that is not there, is — and that failure is cached as the report
29
- // would have been, so the second file to ask gets the same answer without a
30
- // second spawn. The command is forked from the asking process; under the
31
- // oxlint plugin that is the linter, which Linux refuses to fork once its AST
32
- // buffers have merged into one mapping larger than RAM and swap — so the
33
- // plugin asks at load, and retrying per file would only repeat the refusal.
34
-
35
- const MAX_BUFFER = 256 * 1024 * 1024;
36
-
37
- const fileTextOf = (repoRoot: string, file: string): string => {
38
- const at = path.resolve(repoRoot, file);
39
- try {
40
- return readFileSync(at, "utf8");
41
- } catch (cause) {
42
- throw new ReportUnavailable({ kind: "file", source: file, detail: String(cause) });
43
- }
44
- };
45
-
46
- const commandTextSync = (repoRoot: string, command: string): string => {
47
- const run = spawnSync(command, {
48
- cwd: repoRoot,
49
- shell: true,
50
- encoding: "utf8",
51
- maxBuffer: MAX_BUFFER,
52
- stdio: ["ignore", "pipe", "pipe"],
53
- });
54
- if (run.error !== undefined) {
55
- throw new ReportUnavailable({ kind: "command", source: command, detail: run.error.message });
56
- }
57
- return run.stdout;
58
- };
59
-
60
- const commandText = (repoRoot: string, command: string): Promise<string> =>
61
- new Promise((resolve, reject) => {
62
- const child = spawn(command, {
63
- cwd: repoRoot,
64
- shell: true,
65
- stdio: ["ignore", "pipe", "pipe"],
66
- });
67
- const chunks: Array<Buffer> = [];
68
- let size = 0;
69
- child.stdout.on("data", (chunk: Buffer) => {
70
- size += chunk.length;
71
- if (size > MAX_BUFFER) {
72
- child.kill();
73
- reject(
74
- new ReportUnavailable({
75
- kind: "command",
76
- source: command,
77
- detail: `stdout exceeded ${String(MAX_BUFFER)} bytes`,
78
- }),
79
- );
80
- return;
81
- }
82
- chunks.push(chunk);
83
- });
84
- child.stderr.resume();
85
- child.on("error", (cause) => {
86
- reject(new ReportUnavailable({ kind: "command", source: command, detail: cause.message }));
87
- });
88
- child.on("close", () => {
89
- resolve(Buffer.concat(chunks).toString("utf8"));
90
- });
91
- });
92
-
93
- // `work` over `items`, at most `lanes` at a time, answers in order.
94
- const inLanes = async <A, B>(
95
- items: ReadonlyArray<A>,
96
- lanes: number,
97
- work: (item: A) => Promise<B>,
98
- ): Promise<ReadonlyArray<B>> => {
99
- const answers = new Array<B>(items.length);
100
- let next = 0;
101
- const lane = async (): Promise<void> => {
102
- while (next < items.length) {
103
- const index = next;
104
- next += 1;
105
- answers[index] = await work(items[index] as A);
106
- }
107
- };
108
- await Promise.all(Array.from({ length: Math.min(lanes, items.length) }, lane));
109
- return answers;
110
- };
111
-
112
- // One command named as a string and the same one as a list of one are the
113
- // same report.
114
- const keyOf = (spec: ReportSpec): string =>
115
- JSON.stringify([
116
- spec.file === undefined ? "command" : "file",
117
- reportSourcesOf(spec),
118
- spec.format,
119
- spec.pattern ?? null,
120
- ]);
121
-
122
- type Indexed = ReadonlyMap<string, ReadonlyArray<Diagnostic>>;
123
-
124
- export const makeReportSourceLive = (repoRoot: string): ReportSource => {
125
- const cache = new Map<string, Indexed | ReportUnavailable>();
126
- const reading = new Map<string, Promise<void>>();
127
-
128
- // The outputs, in the order named, as one report.
129
- const indexOf = (spec: ReportSpec, texts: ReadonlyArray<string>): Indexed =>
130
- indexByFile(
131
- uniqueDiagnostics(
132
- texts.flatMap((text) =>
133
- parseReport(spec.format, text, { repoRoot, pattern: spec.pattern }),
134
- ),
135
- ),
136
- );
137
-
138
- const answerSync = (spec: ReportSpec): Indexed | ReportUnavailable => {
139
- try {
140
- const texts = reportSourcesOf(spec).map((source) =>
141
- spec.file === undefined ? commandTextSync(repoRoot, source) : fileTextOf(repoRoot, source),
142
- );
143
- return indexOf(spec, texts);
144
- } catch (cause) {
145
- if (cause instanceof ReportUnavailable) return cause;
146
- throw cause;
147
- }
148
- };
149
-
150
- const answer = async (spec: ReportSpec): Promise<Indexed | ReportUnavailable> => {
151
- try {
152
- const sources = reportSourcesOf(spec);
153
- const texts =
154
- spec.file === undefined
155
- ? await inLanes(sources, availableParallelism(), (command) =>
156
- commandText(repoRoot, command),
157
- )
158
- : sources.map((file) => fileTextOf(repoRoot, file));
159
- return indexOf(spec, texts);
160
- } catch (cause) {
161
- if (cause instanceof ReportUnavailable) return cause;
162
- throw cause;
163
- }
164
- };
165
-
166
- const read = (spec: ReportSpec): Promise<void> => {
167
- const key = keyOf(spec);
168
- if (cache.has(key)) return Promise.resolve();
169
- const pending = reading.get(key);
170
- if (pending !== undefined) return pending;
171
- const started = answer(spec).then((found) => {
172
- cache.set(key, found);
173
- reading.delete(key);
174
- });
175
- reading.set(key, started);
176
- return started;
177
- };
178
-
179
- return {
180
- read,
181
- diagnosticsOf: (spec, file) => {
182
- const key = keyOf(spec);
183
- let found = cache.get(key);
184
- if (found === undefined) {
185
- found = answerSync(spec);
186
- cache.set(key, found);
187
- }
188
- if (found instanceof ReportUnavailable) throw found;
189
- return found.get(file) ?? [];
190
- },
191
- };
192
- };
@@ -1,30 +0,0 @@
1
- import type { SourceFacts } from "../domain/facts.js";
2
- import type { SyntaxTree } from "./syntax-matcher.js";
3
-
4
- // The floor of a campaign's detector: a predicate function the repository
5
- // writes, named from the manifest as `module#export` and handed to the
6
- // policy by the host. It is given what the evaluator has about one file and
7
- // answers either a verdict about the file or the subjects it found in it —
8
- // a declaration name, or any key a `match` campaign should ledger — each
9
- // optionally with where it sits. Exported as a public type so a referenced
10
- // module typechecks on its own.
11
-
12
- export type Range = {
13
- // Zero-based.
14
- readonly start: { readonly line: number; readonly column: number };
15
- readonly end: { readonly line: number; readonly column: number };
16
- };
17
-
18
- export type CampaignPredicateInput = {
19
- readonly file: string;
20
- readonly text: string;
21
- readonly facts: SourceFacts;
22
- // The file parsed by the scope's syntax matcher, or `null` without one.
23
- readonly syntax: SyntaxTree | null;
24
- };
25
-
26
- export type CampaignSubject = { readonly subject: string; readonly range?: Range };
27
-
28
- export type CampaignPredicate = (
29
- input: CampaignPredicateInput,
30
- ) => boolean | ReadonlyArray<CampaignSubject>;
@@ -1,35 +0,0 @@
1
- import type { Diagnostic, ReportFormat } from "../domain/report.js";
2
-
3
- // What a `report` term names: a program to run from the repository root, or
4
- // a file some earlier step wrote — or several of either, read as one report
5
- // — and the format the output is read in.
6
- export type ReportSpec = {
7
- readonly command?: string | ReadonlyArray<string> | undefined;
8
- readonly file?: string | ReadonlyArray<string> | undefined;
9
- readonly format: ReportFormat;
10
- // `regex` only.
11
- readonly pattern?: string | undefined;
12
- };
13
-
14
- // The commands or files a spec names, as a list.
15
- export const reportSourcesOf = (spec: ReportSpec): ReadonlyArray<string> => {
16
- const named = spec.command ?? spec.file ?? [];
17
- return typeof named === "string" ? [named] : named;
18
- };
19
-
20
- // Answers a `report` term for one file. The live source runs the commands
21
- // once per process (a report is about the whole repository, and a campaign
22
- // asks per file) and reads a file once; a fake answers from a table. The
23
- // core never spawns anything.
24
- //
25
- // `read` is the host's chance to ask ahead: it reads the report — several
26
- // commands at once — and keeps the answer, or the failure, for
27
- // `diagnosticsOf` to hand back. Both hosts call it before any file is
28
- // judged; a source that does not offer it is asked synchronously, one
29
- // command after another, the first time a file asks.
30
- export type ReportSource = {
31
- readonly diagnosticsOf: (spec: ReportSpec, file: string) => ReadonlyArray<Diagnostic>;
32
- readonly read?: ((spec: ReportSpec) => Promise<void>) | undefined;
33
- };
34
-
35
- export const NO_REPORTS: ReportSource = { diagnosticsOf: () => [] };