@goodbones/core 0.1.0-beta.1 → 0.1.0-beta.11
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/build/dts/core/coverage.d.ts +20 -0
- package/build/dts/core/coverage.d.ts.map +1 -1
- package/build/dts/core/graph.d.ts +2 -0
- package/build/dts/core/graph.d.ts.map +1 -1
- package/build/dts/core/imports.d.ts +3 -1
- package/build/dts/core/imports.d.ts.map +1 -1
- package/build/dts/core/slack.d.ts +22 -0
- package/build/dts/core/slack.d.ts.map +1 -0
- package/build/dts/core/structure.d.ts +1 -0
- package/build/dts/core/structure.d.ts.map +1 -1
- package/build/dts/domain/architecture-config.d.ts +19 -3
- package/build/dts/domain/architecture-config.d.ts.map +1 -1
- package/build/dts/domain/architecture-error.d.ts +8 -0
- package/build/dts/domain/architecture-error.d.ts.map +1 -1
- package/build/dts/domain/facts.d.ts +1 -0
- package/build/dts/domain/facts.d.ts.map +1 -1
- package/build/dts/domain/manifest-location.d.ts +9 -0
- package/build/dts/domain/manifest-location.d.ts.map +1 -0
- package/build/dts/domain/snapshot.d.ts +356 -0
- package/build/dts/domain/snapshot.d.ts.map +1 -0
- package/build/dts/domain/violation.d.ts +1 -1
- package/build/dts/domain/violation.d.ts.map +1 -1
- package/build/dts/index.d.ts +22 -10
- package/build/dts/index.d.ts.map +1 -1
- package/build/dts/infrastructure/file-system-fake.d.ts.map +1 -1
- package/build/dts/infrastructure/file-system-live.d.ts.map +1 -1
- package/build/dts/infrastructure/manifest-file.d.ts +11 -2
- package/build/dts/infrastructure/manifest-file.d.ts.map +1 -1
- package/build/dts/infrastructure/manifest-include.d.ts +16 -0
- package/build/dts/infrastructure/manifest-include.d.ts.map +1 -0
- package/build/dts/infrastructure/syntax-matcher-fake.d.ts +10 -0
- package/build/dts/infrastructure/syntax-matcher-fake.d.ts.map +1 -0
- package/build/dts/infrastructure/walk.d.ts +12 -1
- package/build/dts/infrastructure/walk.d.ts.map +1 -1
- package/build/dts/load/extension.d.ts +33 -0
- package/build/dts/load/extension.d.ts.map +1 -0
- package/build/dts/load/policy.d.ts +10 -0
- package/build/dts/load/policy.d.ts.map +1 -1
- package/build/dts/manifest/compile.d.ts +11 -1
- package/build/dts/manifest/compile.d.ts.map +1 -1
- package/build/dts/manifest/expand.d.ts +27 -0
- package/build/dts/manifest/expand.d.ts.map +1 -0
- package/build/dts/manifest/extension.d.ts +8 -0
- package/build/dts/manifest/extension.d.ts.map +1 -0
- package/build/dts/manifest/glob.d.ts +1 -0
- package/build/dts/manifest/glob.d.ts.map +1 -1
- package/build/dts/manifest/infer.d.ts +55 -0
- package/build/dts/manifest/infer.d.ts.map +1 -0
- package/build/dts/manifest/json-schema.d.ts +14 -0
- package/build/dts/manifest/json-schema.d.ts.map +1 -0
- package/build/dts/manifest/manifest.d.ts +26 -1
- package/build/dts/manifest/manifest.d.ts.map +1 -1
- package/build/dts/ports/file-system.d.ts +1 -0
- package/build/dts/ports/file-system.d.ts.map +1 -1
- package/build/dts/ports/language.d.ts +4 -0
- package/build/dts/ports/language.d.ts.map +1 -1
- package/build/dts/ports/syntax-matcher.d.ts +21 -0
- package/build/dts/ports/syntax-matcher.d.ts.map +1 -0
- package/build/dts/testing.d.ts +1 -0
- package/build/dts/testing.d.ts.map +1 -1
- package/build/esm/core/coverage.js +107 -32
- package/build/esm/core/coverage.js.map +1 -1
- package/build/esm/core/graph.js +45 -0
- package/build/esm/core/graph.js.map +1 -1
- package/build/esm/core/imports.js +14 -9
- package/build/esm/core/imports.js.map +1 -1
- package/build/esm/core/slack.js +76 -0
- package/build/esm/core/slack.js.map +1 -0
- package/build/esm/core/structure.js +6 -3
- package/build/esm/core/structure.js.map +1 -1
- package/build/esm/domain/architecture-config.js +28 -4
- package/build/esm/domain/architecture-config.js.map +1 -1
- package/build/esm/domain/architecture-error.js +27 -0
- package/build/esm/domain/architecture-error.js.map +1 -1
- package/build/esm/domain/manifest-location.js +21 -0
- package/build/esm/domain/manifest-location.js.map +1 -0
- package/build/esm/domain/snapshot.js +181 -0
- package/build/esm/domain/snapshot.js.map +1 -0
- package/build/esm/domain/violation.js.map +1 -1
- package/build/esm/index.js +28 -9
- package/build/esm/index.js.map +1 -1
- package/build/esm/infrastructure/file-system-fake.js +10 -0
- package/build/esm/infrastructure/file-system-fake.js.map +1 -1
- package/build/esm/infrastructure/file-system-live.js +9 -1
- package/build/esm/infrastructure/file-system-live.js.map +1 -1
- package/build/esm/infrastructure/manifest-file.js +158 -7
- package/build/esm/infrastructure/manifest-file.js.map +1 -1
- package/build/esm/infrastructure/manifest-include.js +187 -0
- package/build/esm/infrastructure/manifest-include.js.map +1 -0
- package/build/esm/infrastructure/syntax-matcher-fake.js +33 -0
- package/build/esm/infrastructure/syntax-matcher-fake.js.map +1 -0
- package/build/esm/infrastructure/walk.js +106 -4
- package/build/esm/infrastructure/walk.js.map +1 -1
- package/build/esm/load/extension.js +2 -0
- package/build/esm/load/extension.js.map +1 -0
- package/build/esm/load/policy.js +49 -2
- package/build/esm/load/policy.js.map +1 -1
- package/build/esm/manifest/compile.js +50 -27
- package/build/esm/manifest/compile.js.map +1 -1
- package/build/esm/manifest/expand.js +116 -0
- package/build/esm/manifest/expand.js.map +1 -0
- package/build/esm/manifest/extension.js +2 -0
- package/build/esm/manifest/extension.js.map +1 -0
- package/build/esm/manifest/glob.js +3 -0
- package/build/esm/manifest/glob.js.map +1 -1
- package/build/esm/manifest/infer.js +455 -0
- package/build/esm/manifest/infer.js.map +1 -0
- package/build/esm/manifest/json-schema.js +169 -0
- package/build/esm/manifest/json-schema.js.map +1 -0
- package/build/esm/manifest/manifest.js +127 -5
- package/build/esm/manifest/manifest.js.map +1 -1
- package/build/esm/ports/syntax-matcher.js +12 -0
- package/build/esm/ports/syntax-matcher.js.map +1 -0
- package/build/esm/testing.js +1 -0
- package/build/esm/testing.js.map +1 -1
- package/package.json +9 -3
- package/schema/architecture-node.schema.json +1518 -0
- package/schema/architecture.schema.json +4498 -0
- package/schema/conformance.schema.json +1019 -0
- package/src/core/coverage.ts +164 -34
- package/src/core/graph.ts +48 -0
- package/src/core/imports.ts +29 -13
- package/src/core/slack.ts +135 -0
- package/src/core/structure.ts +15 -8
- package/src/domain/architecture-config.ts +31 -4
- package/src/domain/architecture-error.ts +30 -0
- package/src/domain/facts.ts +9 -4
- package/src/domain/manifest-location.ts +41 -0
- package/src/domain/snapshot.ts +402 -0
- package/src/domain/violation.ts +4 -1
- package/src/index.ts +149 -7
- package/src/infrastructure/file-system-fake.ts +12 -0
- package/src/infrastructure/file-system-live.ts +8 -1
- package/src/infrastructure/manifest-file.ts +204 -8
- package/src/infrastructure/manifest-include.ts +318 -0
- package/src/infrastructure/syntax-matcher-fake.ts +51 -0
- package/src/infrastructure/walk.ts +127 -3
- package/src/load/extension.ts +66 -0
- package/src/load/policy.ts +77 -2
- package/src/manifest/compile.ts +81 -30
- package/src/manifest/expand.ts +183 -0
- package/src/manifest/extension.ts +41 -0
- package/src/manifest/glob.ts +5 -0
- package/src/manifest/infer.ts +643 -0
- package/src/manifest/json-schema.ts +210 -0
- package/src/manifest/manifest.ts +206 -10
- package/src/ports/file-system.ts +3 -0
- package/src/ports/language.ts +17 -0
- package/src/ports/syntax-matcher.ts +42 -0
- package/src/testing.ts +1 -0
package/src/index.ts
CHANGED
|
@@ -3,6 +3,15 @@
|
|
|
3
3
|
// manifest into a policy. A host composes these with a language pack and the
|
|
4
4
|
// live file system; a language pack implements the ports. Nothing here names a
|
|
5
5
|
// language. The fakes are under `@goodbones/core/testing`.
|
|
6
|
+
//
|
|
7
|
+
// A family the core does not own — `campaigns`, in @goodbones/campaigns —
|
|
8
|
+
// arrives as a `PolicyExtension` the host passes to `loadPolicy`: it claims
|
|
9
|
+
// its own manifest keys, decodes them with its own codec, and reads its state
|
|
10
|
+
// back off `LoadedPolicy.extensions`. What such a family needs from here is
|
|
11
|
+
// exported deliberately: the glob primitives its lowering must match
|
|
12
|
+
// character for character, the pattern vocabulary its detectors share with
|
|
13
|
+
// the other families, and `lowerManifest` itself, which an `endState` runs a
|
|
14
|
+
// sector-relative tree through.
|
|
6
15
|
export {
|
|
7
16
|
type Baseline,
|
|
8
17
|
type BaselineFilter,
|
|
@@ -21,6 +30,12 @@ export {
|
|
|
21
30
|
coverageOf,
|
|
22
31
|
coverageShortfalls,
|
|
23
32
|
fractionsOf,
|
|
33
|
+
type Reach,
|
|
34
|
+
reachOf,
|
|
35
|
+
type Residue,
|
|
36
|
+
residueOf,
|
|
37
|
+
type Vacancy,
|
|
38
|
+
vacancyOf,
|
|
24
39
|
} from "./core/coverage.js";
|
|
25
40
|
export {
|
|
26
41
|
type BindingEdge,
|
|
@@ -37,17 +52,20 @@ export {
|
|
|
37
52
|
export {
|
|
38
53
|
type CompiledGraph,
|
|
39
54
|
compileGraphRules,
|
|
55
|
+
cyclesIn,
|
|
40
56
|
EMPTY_GRAPH_RULES,
|
|
41
57
|
evaluateGraph,
|
|
42
58
|
type Graph,
|
|
43
59
|
graphRulesFailingTheirProbe,
|
|
44
60
|
hasGraphRules,
|
|
61
|
+
heightOf,
|
|
45
62
|
} from "./core/graph.js";
|
|
46
63
|
export {
|
|
47
64
|
type CompiledImportRule,
|
|
48
65
|
compileImportRule,
|
|
49
66
|
compileImportRules,
|
|
50
67
|
evaluateImportEdge,
|
|
68
|
+
evaluateResolvedEdge,
|
|
51
69
|
evaluateSelectedEdge,
|
|
52
70
|
type ImportEdge,
|
|
53
71
|
probeTargetOf,
|
|
@@ -62,12 +80,30 @@ export {
|
|
|
62
80
|
memberRulesFailingTheirProbe,
|
|
63
81
|
memberRulesSelecting,
|
|
64
82
|
} from "./core/members.js";
|
|
83
|
+
export {
|
|
84
|
+
compilePatterns,
|
|
85
|
+
firstFromMatch,
|
|
86
|
+
matchesAny,
|
|
87
|
+
type Selectable,
|
|
88
|
+
sourcesOf,
|
|
89
|
+
targetAllowed,
|
|
90
|
+
type Targeted,
|
|
91
|
+
validateTargetPatterns,
|
|
92
|
+
} from "./core/patterns.js";
|
|
93
|
+
export {
|
|
94
|
+
type Concentration,
|
|
95
|
+
type ObservedEdge,
|
|
96
|
+
type Slack,
|
|
97
|
+
slackOf,
|
|
98
|
+
type SlackReport,
|
|
99
|
+
} from "./core/slack.js";
|
|
65
100
|
export {
|
|
66
101
|
type CompiledStructure,
|
|
67
102
|
compileStructure,
|
|
68
103
|
EMPTY_STRUCTURE,
|
|
69
104
|
evaluateStructure,
|
|
70
105
|
requiredSiblingsOf,
|
|
106
|
+
siblingsOf,
|
|
71
107
|
structureRulesFailingTheirProbe,
|
|
72
108
|
} from "./core/structure.js";
|
|
73
109
|
export {
|
|
@@ -78,8 +114,9 @@ export {
|
|
|
78
114
|
surfaceRulesSelecting,
|
|
79
115
|
} from "./core/surface.js";
|
|
80
116
|
export {
|
|
81
|
-
type
|
|
82
|
-
|
|
117
|
+
type Allowance,
|
|
118
|
+
BindingKind,
|
|
119
|
+
DeclarationKind,
|
|
83
120
|
type ExportFix,
|
|
84
121
|
type ExportRule,
|
|
85
122
|
type GraphConfig,
|
|
@@ -87,10 +124,11 @@ export {
|
|
|
87
124
|
type GraphOrphanRule,
|
|
88
125
|
type GraphReachRule,
|
|
89
126
|
type ImportProbe,
|
|
90
|
-
|
|
127
|
+
ImportProbeTarget,
|
|
91
128
|
type ImportRule,
|
|
92
129
|
type MemberRule,
|
|
93
|
-
|
|
130
|
+
MemberSubject,
|
|
131
|
+
PatternList,
|
|
94
132
|
type ResolveConfig,
|
|
95
133
|
type ResolveScope,
|
|
96
134
|
type StructureConfig,
|
|
@@ -100,6 +138,7 @@ export {
|
|
|
100
138
|
ConfigInvalid,
|
|
101
139
|
ImportUnresolved,
|
|
102
140
|
PatternInvalid,
|
|
141
|
+
ReportUnavailable,
|
|
103
142
|
ScopeInvalid,
|
|
104
143
|
} from "./domain/architecture-error.js";
|
|
105
144
|
export {
|
|
@@ -108,6 +147,30 @@ export {
|
|
|
108
147
|
type MemberSite,
|
|
109
148
|
type SourceFacts,
|
|
110
149
|
} from "./domain/facts.js";
|
|
150
|
+
export {
|
|
151
|
+
type ManifestLocator,
|
|
152
|
+
type ManifestPath,
|
|
153
|
+
type ManifestPosition,
|
|
154
|
+
renderManifestPath,
|
|
155
|
+
} from "./domain/manifest-location.js";
|
|
156
|
+
export {
|
|
157
|
+
CONFORMANCE_MEASURES,
|
|
158
|
+
type ConformanceMeasure,
|
|
159
|
+
decodeSnapshot,
|
|
160
|
+
type Snapshot,
|
|
161
|
+
SNAPSHOT_SCHEMA_ID,
|
|
162
|
+
SNAPSHOT_VERSION,
|
|
163
|
+
type SnapshotCampaign,
|
|
164
|
+
type SnapshotConcentration,
|
|
165
|
+
snapshotJsonSchema,
|
|
166
|
+
type SnapshotObjective,
|
|
167
|
+
type SnapshotPhase,
|
|
168
|
+
Snapshot as SnapshotSchema,
|
|
169
|
+
type SnapshotSector,
|
|
170
|
+
type SnapshotSlack,
|
|
171
|
+
type SnapshotVacancy,
|
|
172
|
+
type SnapshotViolation,
|
|
173
|
+
} from "./domain/snapshot.js";
|
|
111
174
|
export {
|
|
112
175
|
fingerprintOf,
|
|
113
176
|
formatMessage,
|
|
@@ -115,16 +178,89 @@ export {
|
|
|
115
178
|
type ViolationKind,
|
|
116
179
|
} from "./domain/violation.js";
|
|
117
180
|
export { makeFileSystemLive } from "./infrastructure/file-system-live.js";
|
|
118
|
-
export {
|
|
119
|
-
|
|
181
|
+
export {
|
|
182
|
+
findManifestFile,
|
|
183
|
+
formatManifestYaml,
|
|
184
|
+
MANIFEST_FILENAMES,
|
|
185
|
+
type ManifestFile,
|
|
186
|
+
readManifestFile,
|
|
187
|
+
} from "./infrastructure/manifest-file.js";
|
|
188
|
+
export {
|
|
189
|
+
expandIncludes,
|
|
190
|
+
type IncludedManifest,
|
|
191
|
+
type IncludeReader,
|
|
192
|
+
type SourceDocument,
|
|
193
|
+
} from "./infrastructure/manifest-include.js";
|
|
194
|
+
export {
|
|
195
|
+
listPackageRoots,
|
|
196
|
+
listSourceFiles,
|
|
197
|
+
listWorkspaceProjects,
|
|
198
|
+
type PackagedLanguage,
|
|
199
|
+
type PackageRoot,
|
|
200
|
+
type WalkedLanguage,
|
|
201
|
+
type WorkspaceProject,
|
|
202
|
+
} from "./infrastructure/walk.js";
|
|
203
|
+
export {
|
|
204
|
+
type ExtensionContext,
|
|
205
|
+
type ExtensionRoute,
|
|
206
|
+
type LoadedExtension,
|
|
207
|
+
type PolicyExtension,
|
|
208
|
+
} from "./load/extension.js";
|
|
120
209
|
export { type LoadedPolicy, loadPolicy, type LoadPolicyInput } from "./load/policy.js";
|
|
121
|
-
export {
|
|
210
|
+
export {
|
|
211
|
+
CONVENTIONS,
|
|
212
|
+
expandAliases,
|
|
213
|
+
type LoweredRules,
|
|
214
|
+
lowerManifest,
|
|
215
|
+
type LowerOptions,
|
|
216
|
+
type ProbeLanguage,
|
|
217
|
+
} from "./manifest/compile.js";
|
|
218
|
+
export {
|
|
219
|
+
type ExpandedManifest,
|
|
220
|
+
type ExpandIssue,
|
|
221
|
+
expandManifest,
|
|
222
|
+
type Origin,
|
|
223
|
+
originOf,
|
|
224
|
+
type Substitution,
|
|
225
|
+
} from "./manifest/expand.js";
|
|
226
|
+
export { type ManifestExtension } from "./manifest/extension.js";
|
|
227
|
+
export {
|
|
228
|
+
anchored,
|
|
229
|
+
type CaptureIndex,
|
|
230
|
+
type GlobCompilation,
|
|
231
|
+
globToRegExp,
|
|
232
|
+
globToRegexSource,
|
|
233
|
+
prefixed,
|
|
234
|
+
} from "./manifest/glob.js";
|
|
235
|
+
export {
|
|
236
|
+
type Candidate,
|
|
237
|
+
candidatesOf,
|
|
238
|
+
type Generalization,
|
|
239
|
+
type InferInput,
|
|
240
|
+
inferManifest,
|
|
241
|
+
type InferOptions,
|
|
242
|
+
type InferPackage,
|
|
243
|
+
type Inferred,
|
|
244
|
+
type InferredTarget,
|
|
245
|
+
singularOf,
|
|
246
|
+
} from "./manifest/infer.js";
|
|
247
|
+
export {
|
|
248
|
+
MANIFEST_NODE_SCHEMA_ID,
|
|
249
|
+
MANIFEST_SCHEMA_ID,
|
|
250
|
+
manifestJsonSchema,
|
|
251
|
+
type ManifestJsonSchemaOptions,
|
|
252
|
+
manifestNodeJsonSchema,
|
|
253
|
+
} from "./manifest/json-schema.js";
|
|
122
254
|
export {
|
|
123
255
|
type DecodedManifest,
|
|
124
256
|
decodeManifest,
|
|
257
|
+
type DecodeManifestOptions,
|
|
258
|
+
decodeManifestTree,
|
|
259
|
+
globsOf,
|
|
125
260
|
type Manifest,
|
|
126
261
|
type ManifestNode,
|
|
127
262
|
Manifest as ManifestSchema,
|
|
263
|
+
SurfaceConvention,
|
|
128
264
|
} from "./manifest/manifest.js";
|
|
129
265
|
export { type FactExtractor } from "./ports/fact-extractor.js";
|
|
130
266
|
export { type FileSystem } from "./ports/file-system.js";
|
|
@@ -134,3 +270,9 @@ export {
|
|
|
134
270
|
type ModuleResolver,
|
|
135
271
|
type ResolvedTarget,
|
|
136
272
|
} from "./ports/module-resolver.js";
|
|
273
|
+
export {
|
|
274
|
+
type Position,
|
|
275
|
+
type SyntaxMatch,
|
|
276
|
+
type SyntaxMatcher,
|
|
277
|
+
type SyntaxTree,
|
|
278
|
+
} from "./ports/syntax-matcher.js";
|
|
@@ -10,5 +10,17 @@ export const makeFileSystemFake = (
|
|
|
10
10
|
return {
|
|
11
11
|
exists: (repoRelativePath) => paths.has(repoRelativePath),
|
|
12
12
|
readText: (repoRelativePath) => contents[repoRelativePath] ?? null,
|
|
13
|
+
list: (repoRelativeDir) => {
|
|
14
|
+
const prefix = repoRelativeDir === "" ? "" : `${repoRelativeDir}/`;
|
|
15
|
+
return [
|
|
16
|
+
...new Set(
|
|
17
|
+
[...paths]
|
|
18
|
+
.filter((one) => one.startsWith(prefix))
|
|
19
|
+
.map((one) => one.slice(prefix.length).split("/")[0] ?? ""),
|
|
20
|
+
),
|
|
21
|
+
]
|
|
22
|
+
.filter((one) => one !== "")
|
|
23
|
+
.sort();
|
|
24
|
+
},
|
|
13
25
|
};
|
|
14
26
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
1
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
|
|
4
4
|
import type { FileSystem } from "../ports/file-system.js";
|
|
@@ -23,5 +23,12 @@ export const makeFileSystemLive = (repoRoot: string): FileSystem => {
|
|
|
23
23
|
return null;
|
|
24
24
|
}
|
|
25
25
|
},
|
|
26
|
+
list: (repoRelativeDir) => {
|
|
27
|
+
try {
|
|
28
|
+
return readdirSync(path.resolve(repoRoot, repoRelativeDir)).sort();
|
|
29
|
+
} catch {
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
},
|
|
26
33
|
};
|
|
27
34
|
};
|
|
@@ -1,17 +1,213 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
1
3
|
import { pathToFileURL } from "node:url";
|
|
2
4
|
|
|
5
|
+
import {
|
|
6
|
+
type Document,
|
|
7
|
+
isAlias,
|
|
8
|
+
isMap,
|
|
9
|
+
isScalar,
|
|
10
|
+
isSeq,
|
|
11
|
+
LineCounter,
|
|
12
|
+
type Node,
|
|
13
|
+
type Pair,
|
|
14
|
+
parseDocument,
|
|
15
|
+
stringify,
|
|
16
|
+
} from "yaml";
|
|
17
|
+
|
|
3
18
|
import { ConfigInvalid } from "../domain/architecture-error.js";
|
|
19
|
+
import type {
|
|
20
|
+
ManifestLocator,
|
|
21
|
+
ManifestPath,
|
|
22
|
+
ManifestPosition,
|
|
23
|
+
} from "../domain/manifest-location.js";
|
|
24
|
+
import { expandIncludes, type SourceDocument } from "./manifest-include.js";
|
|
25
|
+
|
|
26
|
+
// The manifest, as the files on disk state it, before any decoding. A data
|
|
27
|
+
// file — YAML, or JSON, which YAML 1.2 contains — is what any host in any
|
|
28
|
+
// language can read, and is the form the docs are written in. A JavaScript
|
|
29
|
+
// module is the escape hatch a Node host alone honours: for a manifest
|
|
30
|
+
// generated from other data, and for what the ecosystem expects of a config
|
|
31
|
+
// file. Either may `include` further data files, so a monorepo's policy can
|
|
32
|
+
// live beside the packages it governs. Whichever it is, what comes out is one
|
|
33
|
+
// `unknown` value for the decoder, and, from the data formats, a way to turn
|
|
34
|
+
// a path in it back into a file and a line.
|
|
35
|
+
|
|
36
|
+
// In discovery order. A repository with none of these is told all four; one
|
|
37
|
+
// with more than one is refused, so nobody edits the wrong file for a week.
|
|
38
|
+
export const MANIFEST_FILENAMES = [
|
|
39
|
+
"architecture.yaml",
|
|
40
|
+
"architecture.yml",
|
|
41
|
+
"architecture.json",
|
|
42
|
+
"architecture.config.mjs",
|
|
43
|
+
] as const;
|
|
4
44
|
|
|
5
|
-
export
|
|
45
|
+
export type ManifestFile = {
|
|
46
|
+
readonly configPath: string;
|
|
47
|
+
readonly manifest: unknown;
|
|
48
|
+
// Absent for a JavaScript module that includes nothing, which has no
|
|
49
|
+
// positions to give.
|
|
50
|
+
readonly locate: ManifestLocator | undefined;
|
|
51
|
+
// Every file the manifest was read from, the root first: what a host that
|
|
52
|
+
// caches or watches the policy has to look at.
|
|
53
|
+
readonly files: ReadonlyArray<string>;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const findManifestFile = (repoRoot: string): string => {
|
|
57
|
+
const present = MANIFEST_FILENAMES.filter((name) => existsSync(path.resolve(repoRoot, name)));
|
|
58
|
+
const [found] = present;
|
|
59
|
+
if (found === undefined) {
|
|
60
|
+
throw new ConfigInvalid({
|
|
61
|
+
configPath: repoRoot,
|
|
62
|
+
detail:
|
|
63
|
+
`no architecture manifest found. Looked for ${MANIFEST_FILENAMES.join(", ")} ` +
|
|
64
|
+
`in this directory; \`architecture init\` writes a starter architecture.yaml.`,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
if (present.length > 1) {
|
|
68
|
+
throw new ConfigInvalid({
|
|
69
|
+
configPath: repoRoot,
|
|
70
|
+
detail:
|
|
71
|
+
`more than one architecture manifest is present (${present.join(", ")}), and only one ` +
|
|
72
|
+
`can be the policy. Delete the others, or name one with ARCHITECTURE_CONFIG.`,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
return path.resolve(repoRoot, found);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const extensionOf = (configPath: string): string => path.extname(configPath).toLowerCase();
|
|
79
|
+
|
|
80
|
+
const isDataManifest = (configPath: string): boolean =>
|
|
81
|
+
[".yaml", ".yml", ".json"].includes(extensionOf(configPath));
|
|
82
|
+
|
|
83
|
+
const isModuleManifest = (configPath: string): boolean =>
|
|
84
|
+
[".mjs", ".js", ".cjs"].includes(extensionOf(configPath));
|
|
85
|
+
|
|
86
|
+
// Whichever form the root takes, an `include` in it names a data file, read
|
|
87
|
+
// by the same parser; the locator that comes back answers across every file.
|
|
88
|
+
export const readManifestFile = async (configPath: string): Promise<ManifestFile> => {
|
|
89
|
+
const root = isDataManifest(configPath)
|
|
90
|
+
? parseDataFile(configPath)
|
|
91
|
+
: isModuleManifest(configPath)
|
|
92
|
+
? await readModule(configPath)
|
|
93
|
+
: undefined;
|
|
94
|
+
if (root === undefined) {
|
|
95
|
+
throw new ConfigInvalid({
|
|
96
|
+
configPath,
|
|
97
|
+
detail:
|
|
98
|
+
"a manifest is a .yaml, .yml or .json file, or a .mjs/.js module — " +
|
|
99
|
+
`not ${JSON.stringify(path.basename(configPath))}.`,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
const assembled = expandIncludes(configPath, root, { exists: existsSync, read: parseDataFile });
|
|
103
|
+
return {
|
|
104
|
+
configPath,
|
|
105
|
+
manifest: assembled.value,
|
|
106
|
+
locate: assembled.locate,
|
|
107
|
+
files: assembled.files,
|
|
108
|
+
};
|
|
109
|
+
};
|
|
6
110
|
|
|
7
|
-
|
|
8
|
-
// is a JavaScript module and its default export; a manifest in another format
|
|
9
|
-
// changes this function and nothing behind it.
|
|
10
|
-
export const readManifestFile = async (configPath: string): Promise<unknown> => {
|
|
111
|
+
const readModule = async (configPath: string): Promise<SourceDocument> => {
|
|
11
112
|
const module: unknown = await import(pathToFileURL(configPath).href).catch((cause: unknown) => {
|
|
12
113
|
throw new ConfigInvalid({ configPath, detail: String(cause) });
|
|
13
114
|
});
|
|
14
|
-
|
|
15
|
-
? module.default
|
|
16
|
-
|
|
115
|
+
const value =
|
|
116
|
+
typeof module === "object" && module !== null && "default" in module ? module.default : module;
|
|
117
|
+
return { value, locate: undefined };
|
|
17
118
|
};
|
|
119
|
+
|
|
120
|
+
// YAML 1.2 core schema, which is what a reader without a YAML background
|
|
121
|
+
// expects: `on` and `no` are strings, not booleans. Merge keys (`<<`) resolve,
|
|
122
|
+
// because the parser handles them before the manifest sees the document, and
|
|
123
|
+
// duplicate keys are refused rather than last-one-wins. A tag the parser does
|
|
124
|
+
// not know is refused too — a manifest is data, and a `!!js/function` in it
|
|
125
|
+
// would be a manifest only one runtime could read.
|
|
126
|
+
const parseDataFile = (configPath: string): SourceDocument => {
|
|
127
|
+
let text: string;
|
|
128
|
+
try {
|
|
129
|
+
text = readFileSync(configPath, "utf8");
|
|
130
|
+
} catch (cause) {
|
|
131
|
+
throw new ConfigInvalid({ configPath, detail: String(cause) });
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const lines = new LineCounter();
|
|
135
|
+
const document = parseDocument(text, {
|
|
136
|
+
merge: true,
|
|
137
|
+
uniqueKeys: true,
|
|
138
|
+
customTags: [],
|
|
139
|
+
lineCounter: lines,
|
|
140
|
+
// A JSON file is read by the same parser; strict JSON is valid YAML.
|
|
141
|
+
schema: "core",
|
|
142
|
+
});
|
|
143
|
+
const problems = [...document.errors, ...document.warnings];
|
|
144
|
+
if (problems.length > 0) {
|
|
145
|
+
const file = path.basename(configPath);
|
|
146
|
+
throw new ConfigInvalid({
|
|
147
|
+
configPath,
|
|
148
|
+
detail:
|
|
149
|
+
"the manifest does not parse:\n" +
|
|
150
|
+
problems
|
|
151
|
+
.map((problem) => {
|
|
152
|
+
const [position] = problem.linePos ?? [];
|
|
153
|
+
const at =
|
|
154
|
+
position === undefined
|
|
155
|
+
? ""
|
|
156
|
+
: `${file}:${String(position.line)}:${String(position.col)} `;
|
|
157
|
+
return ` ${at}${problem.code}: ${problem.message.split("\n")[0] ?? problem.message}`;
|
|
158
|
+
})
|
|
159
|
+
.join("\n"),
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
value: document.toJS({ mapAsMap: false }) as unknown,
|
|
165
|
+
locate: makeLocator(document, lines),
|
|
166
|
+
};
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const keyMatches = (pair: Pair, segment: PropertyKey): boolean =>
|
|
170
|
+
isScalar(pair.key) && String(pair.key.value) === String(segment);
|
|
171
|
+
|
|
172
|
+
// Walks the document's own tree by the decoder's path and answers with the
|
|
173
|
+
// position of the deepest thing that exists along it. A map key is reported
|
|
174
|
+
// at the key, where the reader's eye lands; a sequence item at the item. A
|
|
175
|
+
// key merged in through `<<` lives in the fragment it came from, which the
|
|
176
|
+
// walk cannot see — the reader then gets the position of the map instead.
|
|
177
|
+
const makeLocator = (document: Document, lines: LineCounter): ManifestLocator => {
|
|
178
|
+
const positionOf = (node: Node | null | undefined): ManifestPosition | null => {
|
|
179
|
+
const offset = node?.range?.[0];
|
|
180
|
+
if (offset === undefined) return null;
|
|
181
|
+
const { col, line } = lines.linePos(offset);
|
|
182
|
+
return { line, column: col };
|
|
183
|
+
};
|
|
184
|
+
const resolved = (node: unknown): unknown => (isAlias(node) ? node.resolve(document) : node);
|
|
185
|
+
|
|
186
|
+
return (manifestPath: ManifestPath) => {
|
|
187
|
+
let current: unknown = resolved(document.contents);
|
|
188
|
+
let position = positionOf(document.contents);
|
|
189
|
+
for (const segment of manifestPath) {
|
|
190
|
+
if (isMap(current)) {
|
|
191
|
+
const pair = current.items.find((one) => keyMatches(one, segment));
|
|
192
|
+
if (pair === undefined) break;
|
|
193
|
+
position = positionOf(isScalar(pair.key) ? pair.key : null) ?? position;
|
|
194
|
+
current = resolved(pair.value);
|
|
195
|
+
} else if (isSeq(current) && typeof segment === "number") {
|
|
196
|
+
const item = current.items[segment];
|
|
197
|
+
if (item === undefined) break;
|
|
198
|
+
position = positionOf(item as Node) ?? position;
|
|
199
|
+
current = resolved(item);
|
|
200
|
+
} else {
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return position;
|
|
205
|
+
};
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
// The value as a YAML document, for `architecture migrate` and anything else
|
|
209
|
+
// that writes a manifest rather than reads one. Strings are quoted whenever
|
|
210
|
+
// the core schema would read them as something else, and a multi-line one —
|
|
211
|
+
// a probe source — becomes a block scalar.
|
|
212
|
+
export const formatManifestYaml = (manifest: unknown): string =>
|
|
213
|
+
stringify(manifest, { lineWidth: 100, blockQuote: "literal", schema: "core" });
|