@goodbones/core 0.1.0-beta.3 → 0.1.0-beta.4

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.
@@ -0,0 +1,455 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Paths
3
+ const dirnameOf = (file) => {
4
+ const at = file.lastIndexOf("/");
5
+ return at === -1 ? "" : file.slice(0, at);
6
+ };
7
+ const basenameOf = (file) => file.slice(file.lastIndexOf("/") + 1);
8
+ // `folder` is `p` or an ancestor of it. The empty folder is every path's.
9
+ const isUnder = (p, folder) => folder === "" || p === folder || p.startsWith(`${folder}/`);
10
+ const depthBelow = (p, folder) => p === folder ? 0 : (folder === "" ? p : p.slice(folder.length + 1)).split("/").length;
11
+ const sorted = (items) => [...items].sort();
12
+ const memberOf = (p, generalize) => {
13
+ for (const group of generalize) {
14
+ for (const member of group.members) {
15
+ const folder = group.parent === "" ? member : `${group.parent}/${member}`;
16
+ if (isUnder(p, folder))
17
+ return { group, member };
18
+ }
19
+ }
20
+ return null;
21
+ };
22
+ const capturedPathOf = (group) => group.parent === "" ? `{${group.capture}}` : `${group.parent}/{${group.capture}}`;
23
+ const rewrite = (p, generalize) => {
24
+ const membership = memberOf(p, generalize);
25
+ if (membership === null)
26
+ return p;
27
+ const { group, member } = membership;
28
+ const folder = group.parent === "" ? member : `${group.parent}/${member}`;
29
+ const captured = capturedPathOf(group);
30
+ return p === folder ? captured : `${captured}${p.slice(folder.length)}`;
31
+ };
32
+ const folderAt = (root, p) => {
33
+ if (p === root.path)
34
+ return root;
35
+ const rest = root.path === "" ? p : p.slice(root.path.length + 1);
36
+ let at = root;
37
+ for (const segment of rest.split("/")) {
38
+ const existing = at.children.get(segment);
39
+ if (existing !== undefined) {
40
+ at = existing;
41
+ continue;
42
+ }
43
+ const made = {
44
+ path: at.path === "" ? segment : `${at.path}/${segment}`,
45
+ name: segment,
46
+ files: [],
47
+ children: new Map(),
48
+ };
49
+ at.children.set(segment, made);
50
+ at = made;
51
+ }
52
+ return at;
53
+ };
54
+ const trieOf = (input, generalize) => {
55
+ const roots = input.roots.map((root) => ({
56
+ path: root,
57
+ name: basenameOf(root),
58
+ files: [],
59
+ children: new Map(),
60
+ }));
61
+ for (const file of input.files) {
62
+ const folder = rewrite(dirnameOf(file), generalize);
63
+ const root = roots.find((one) => isUnder(folder, one.path));
64
+ if (root === undefined)
65
+ continue;
66
+ folderAt(root, folder).files.push(file);
67
+ }
68
+ return roots;
69
+ };
70
+ const basesOf = (input, generalize) => {
71
+ const all = new Set(input.roots);
72
+ for (const one of input.packages) {
73
+ all.add(rewrite(one.root, generalize));
74
+ if (one.source !== null)
75
+ all.add(rewrite(one.source, generalize));
76
+ }
77
+ return { all: sorted(all) };
78
+ };
79
+ const baseOf = (folder, bases) => {
80
+ let best = null;
81
+ for (const base of bases.all) {
82
+ if (!isUnder(folder, base))
83
+ continue;
84
+ if (best === null || base.length > best.length)
85
+ best = base;
86
+ }
87
+ return best ?? "";
88
+ };
89
+ // A folder is a node when it is within `depth` of the base it counts from, or
90
+ // when it lies on the way to one — the folders between a walk root and a
91
+ // package are structure, and a node each whatever the depth.
92
+ const isNode = (folder, input, bases) => input.roots.includes(folder.path) ||
93
+ bases.all.some((base) => isUnder(base, folder.path)) ||
94
+ depthBelow(folder.path, baseOf(folder.path, bases)) <= input.depth;
95
+ const nodeOf = (folder, input, bases) => {
96
+ const node = {
97
+ path: folder.path,
98
+ key: `${folder.name}/`,
99
+ folder,
100
+ files: [...folder.files],
101
+ children: new Map(),
102
+ stray: false,
103
+ allow: new Set(),
104
+ external: new Set(),
105
+ edges: 0,
106
+ };
107
+ const absorb = (from) => {
108
+ for (const child of from.children.values()) {
109
+ if (isNode(child, input, bases)) {
110
+ node.children.set(child.name, nodeOf(child, input, bases));
111
+ }
112
+ else {
113
+ node.stray = true;
114
+ for (const file of child.files)
115
+ node.files.push(file);
116
+ absorb(child);
117
+ }
118
+ }
119
+ };
120
+ absorb(folder);
121
+ return node;
122
+ };
123
+ // The deepest node whose folder holds `p` (a rewritten path), or null when
124
+ // no walk root does.
125
+ const governorOf = (p, roots) => {
126
+ const root = roots.find((one) => isUnder(p, one.path));
127
+ if (root === undefined)
128
+ return null;
129
+ const descend = (at) => {
130
+ for (const child of at.children.values())
131
+ if (isUnder(p, child.path))
132
+ return descend(child);
133
+ return at;
134
+ };
135
+ return descend(root);
136
+ };
137
+ const eachNode = (roots, visit) => {
138
+ const walk = (node) => {
139
+ visit(node);
140
+ for (const child of node.children.values())
141
+ walk(child);
142
+ };
143
+ for (const root of roots)
144
+ walk(root);
145
+ };
146
+ // ---------------------------------------------------------------------------
147
+ // Allow sets.
148
+ // What a local target is written as: the node that governs it, as a glob. A
149
+ // file directly in a node that has children of its own is `node/*` — the
150
+ // layer's own files, such as a barrel, and not the tiers beneath — and
151
+ // anything else is `node/**`. A capture in that path survives only when the
152
+ // importer is in the same member; from anywhere else it is `*`.
153
+ const entryFor = (importer, target, roots, generalize) => {
154
+ const rewritten = rewrite(target, generalize);
155
+ const node = governorOf(rewritten, roots);
156
+ if (node === null)
157
+ return target;
158
+ const from = memberOf(importer, generalize);
159
+ const to = memberOf(target, generalize);
160
+ const sameGroup = from !== null &&
161
+ to !== null &&
162
+ from.group.parent === to.group.parent &&
163
+ from.group.capture === to.group.capture;
164
+ if (to !== null && sameGroup && to.member !== from.member) {
165
+ const reach = to.group.crossReach;
166
+ const folder = to.group.parent === "" ? to.member : `${to.group.parent}/${to.member}`;
167
+ if (reach !== null && target === `${folder}/${reach}`) {
168
+ return `${to.group.parent === "" ? "" : `${to.group.parent}/`}*/${reach}`;
169
+ }
170
+ }
171
+ const direct = dirnameOf(rewritten) === node.path && node.children.size > 0;
172
+ const glob = `${node.path}/${direct ? "*" : "**"}`;
173
+ if (to === null)
174
+ return glob;
175
+ const sameMember = sameGroup && from.member === to.member;
176
+ return sameMember ? glob : glob.replace(`{${to.group.capture}}`, "*");
177
+ };
178
+ const fillAllowSets = (roots, input, generalize) => {
179
+ eachNode(roots, (node) => {
180
+ for (const file of node.files) {
181
+ for (const target of input.targetsOf(file)) {
182
+ node.edges += 1;
183
+ switch (target.kind) {
184
+ case "local":
185
+ node.allow.add(entryFor(file, target.path, roots, generalize));
186
+ break;
187
+ case "builtin":
188
+ node.allow.add(target.path);
189
+ break;
190
+ case "external":
191
+ node.external.add(target.package);
192
+ break;
193
+ }
194
+ }
195
+ }
196
+ });
197
+ };
198
+ // ---------------------------------------------------------------------------
199
+ // Candidates: siblings that share a shape.
200
+ // `auth.module.ts` and `user.module.ts` are one kind of file; `index.ts` is
201
+ // its own. The first segment is the name, the rest is what it is.
202
+ const stereotypeOf = (file) => {
203
+ const parts = basenameOf(file).split(".");
204
+ return parts.length >= 3 ? `*.${parts.slice(1).join(".")}` : basenameOf(file);
205
+ };
206
+ const intersect = (a, b) => sorted([...a].filter((one) => b.has(one)));
207
+ const alike = (a, b) => {
208
+ if (intersect(a.folders, b.folders).length >= 2)
209
+ return true;
210
+ const union = new Set([...a.stereotypes, ...b.stereotypes]);
211
+ return union.size > 0 && intersect(a.stereotypes, b.stereotypes).length / union.size >= 0.5;
212
+ };
213
+ const shapeOf = (folder) => ({
214
+ folders: new Set(folder.children.keys()),
215
+ stereotypes: new Set(folder.files.map(stereotypeOf)),
216
+ });
217
+ // `modules` → `module`; `entities` → `entity`; `packages` → `package`. What a
218
+ // human would name the capture; anything unpronounceable is `name`.
219
+ export const singularOf = (plural) => {
220
+ const word = plural.replace(/[^A-Za-z0-9]/g, "");
221
+ const singular = /ies$/.test(word)
222
+ ? word.replace(/ies$/, "y")
223
+ : /(ses|xes|zes|shes|ches)$/.test(word)
224
+ ? word.slice(0, -2)
225
+ : /[^s]s$/.test(word)
226
+ ? word.slice(0, -1)
227
+ : word;
228
+ return singular === "" || /^[0-9]/.test(singular) ? "name" : singular;
229
+ };
230
+ const groupsOf = (members) => {
231
+ const parent = new Map(members.map(([name]) => [name, name]));
232
+ const find = (name) => {
233
+ let at = name;
234
+ while ((parent.get(at) ?? at) !== at)
235
+ at = parent.get(at) ?? at;
236
+ return at;
237
+ };
238
+ for (const [i, [a, shapeA]] of members.entries()) {
239
+ for (const [b, shapeB] of members.slice(i + 1)) {
240
+ if (alike(shapeA, shapeB))
241
+ parent.set(find(a), find(b));
242
+ }
243
+ }
244
+ const groups = new Map();
245
+ for (const [name] of members) {
246
+ const key = find(name);
247
+ groups.set(key, [...(groups.get(key) ?? []), name]);
248
+ }
249
+ return [...groups.values()]
250
+ .filter((group) => group.length >= 2)
251
+ .map((group) => sorted(group))
252
+ .sort((a, b) => (a[0] ?? "").localeCompare(b[0] ?? ""));
253
+ };
254
+ // The sibling groups under every node of the tree as it stands with the
255
+ // generalizations already accepted — never inside one, since a generalized
256
+ // node's children are already a merge.
257
+ export const candidatesOf = (input, generalize = []) => {
258
+ const trie = trieOf(input, generalize);
259
+ const bases = basesOf(input, generalize);
260
+ const roots = trie.map((folder) => nodeOf(folder, input, bases));
261
+ const candidates = [];
262
+ eachNode(roots, (node) => {
263
+ if (node.path.includes("{") || node.children.size < 2)
264
+ return;
265
+ const members = sorted(node.children.keys()).flatMap((name) => {
266
+ const folder = node.folder.children.get(name);
267
+ return folder === undefined ? [] : [[name, shapeOf(folder)]];
268
+ });
269
+ const shapes = new Map(members);
270
+ const taken = new Set();
271
+ for (const group of groupsOf(members)) {
272
+ const shapesOfGroup = group.flatMap((name) => {
273
+ const shape = shapes.get(name);
274
+ return shape === undefined ? [] : [shape];
275
+ });
276
+ const sharedOf = (pick) => {
277
+ const [head, ...rest] = shapesOfGroup;
278
+ if (head === undefined)
279
+ return [];
280
+ return sorted(rest.reduce((acc, shape) => new Set(intersect(acc, pick(shape))), pick(head)));
281
+ };
282
+ const sharedFolders = sharedOf((shape) => shape.folders);
283
+ const sharedStereotypes = sharedOf((shape) => shape.stereotypes);
284
+ let capture = singularOf(node.folder.name);
285
+ for (let n = 2; taken.has(capture); n += 1)
286
+ capture = `${singularOf(node.folder.name)}${String(n)}`;
287
+ taken.add(capture);
288
+ // Edges between members, and whether they all land on one place.
289
+ const memberFolder = (name) => node.path === "" ? name : `${node.path}/${name}`;
290
+ const isIn = (p) => group.find((name) => isUnder(p, memberFolder(name))) ?? null;
291
+ let crossEdges = 0;
292
+ const landings = new Set();
293
+ for (const file of input.files) {
294
+ const from = isIn(file);
295
+ if (from === null)
296
+ continue;
297
+ for (const target of input.targetsOf(file)) {
298
+ if (target.kind !== "local")
299
+ continue;
300
+ const to = isIn(target.path);
301
+ if (to === null || to === from)
302
+ continue;
303
+ crossEdges += 1;
304
+ landings.add(target.path.slice(memberFolder(to).length + 1));
305
+ }
306
+ }
307
+ const [landing] = sorted(landings);
308
+ candidates.push({
309
+ parent: node.path,
310
+ members: group,
311
+ sharedFolders,
312
+ sharedStereotypes,
313
+ capture,
314
+ crossEdges,
315
+ crossReach: landings.size === 1 && landing !== undefined ? landing : null,
316
+ });
317
+ }
318
+ });
319
+ return candidates.sort((a, b) => {
320
+ const byParent = a.parent.localeCompare(b.parent);
321
+ return byParent !== 0 ? byParent : (a.members[0] ?? "").localeCompare(b.members[0] ?? "");
322
+ });
323
+ };
324
+ // ---------------------------------------------------------------------------
325
+ // Collapse: children that all reach the same things are one node.
326
+ const normalized = (node, under) => sorted(new Set([...node.allow].map((entry) => (isUnder(entry, under) ? `${under}/**` : entry))));
327
+ const sameList = (a, b) => a.length === b.length && a.every((one, index) => one === b[index]);
328
+ const collapse = (node) => {
329
+ for (const child of node.children.values())
330
+ collapse(child);
331
+ if (node.children.size === 0)
332
+ return;
333
+ const children = [...node.children.values()];
334
+ if (children.some((child) => child.children.size > 0))
335
+ return;
336
+ const [first] = children;
337
+ if (first === undefined)
338
+ return;
339
+ const shared = normalized(first, node.path);
340
+ const externals = sorted(first.external);
341
+ const agree = children.every((child) => sameList(normalized(child, node.path), shared) && sameList(sorted(child.external), externals));
342
+ if (!agree)
343
+ return;
344
+ const own = normalized(node, node.path);
345
+ if (!own.every((entry) => shared.includes(entry)))
346
+ return;
347
+ if (!sorted(node.external).every((entry) => externals.includes(entry)))
348
+ return;
349
+ node.allow = new Set(shared);
350
+ node.external = new Set(externals);
351
+ for (const child of children) {
352
+ for (const file of child.files)
353
+ node.files.push(file);
354
+ node.edges += child.edges;
355
+ }
356
+ node.children = new Map();
357
+ node.stray = true;
358
+ };
359
+ // ---------------------------------------------------------------------------
360
+ // Emitting.
361
+ const shortenWith = (aliases) => {
362
+ const entries = Object.entries(aliases).sort(([, a], [, b]) => b.length - a.length);
363
+ return (p) => {
364
+ for (const [alias, target] of entries) {
365
+ if (p === target)
366
+ return alias;
367
+ if (p.startsWith(`${target}/`))
368
+ return `${alias}${p.slice(target.length)}`;
369
+ }
370
+ return p;
371
+ };
372
+ };
373
+ const CATCH_ALL = "**/";
374
+ export const inferManifest = (input, options) => {
375
+ const { generalize } = options;
376
+ const trie = trieOf(input, generalize);
377
+ const bases = basesOf(input, generalize);
378
+ const roots = trie.map((folder) => nodeOf(folder, input, bases));
379
+ fillAllowSets(roots, input, generalize);
380
+ if (options.collapse)
381
+ for (const root of roots)
382
+ collapse(root);
383
+ const shorten = shortenWith(options.aliases ?? {});
384
+ let unrestricted = 0;
385
+ let nodes = 0;
386
+ let files = 0;
387
+ const describe = (node) => {
388
+ const membership = generalize.find((group) => capturedPathOf(group) === node.path);
389
+ const across = membership === undefined
390
+ ? ""
391
+ : ` across ${String(membership.members.length)} ${basenameOf(membership.parent)} (${membership.members.join(", ")})`;
392
+ const count = (n, noun) => `${String(n)} ${noun}${n === 1 ? "" : "s"}`;
393
+ return node.files.length === 0
394
+ ? `As built on ${options.date}: no files of its own.`
395
+ : `As built on ${options.date}${across}: ${count(node.files.length, "file")}, ${count(node.edges, "edge")}.`;
396
+ };
397
+ const emit = (node) => {
398
+ nodes += 1;
399
+ files += node.files.length;
400
+ const children = {};
401
+ for (const name of sorted(node.children.keys())) {
402
+ const child = node.children.get(name);
403
+ if (child !== undefined)
404
+ children[child.key] = emit(child);
405
+ }
406
+ if (node.stray)
407
+ children[CATCH_ALL] = { layout: "open", children: {} };
408
+ const imports = node.files.length === 0
409
+ ? {}
410
+ : {
411
+ imports: {
412
+ message: `This import is not on the allowlist infer wrote for ${node.path}/ on ${options.date}. Add it by name if it belongs here.`,
413
+ unrestricted: true,
414
+ ...(node.allow.size > 0 ? { allow: sorted([...node.allow].map(shorten)) } : {}),
415
+ ...(node.external.size > 0 ? { external: sorted(node.external) } : {}),
416
+ },
417
+ };
418
+ if (node.files.length > 0)
419
+ unrestricted += 1;
420
+ return {
421
+ message: describe(node),
422
+ layout: "open",
423
+ ...imports,
424
+ children,
425
+ };
426
+ };
427
+ const tree = {};
428
+ for (const root of roots)
429
+ tree[`${shorten(root.path)}/`] = emit(root);
430
+ const within = input.roots.map((root) => shorten(`${root}/**`));
431
+ const manifest = {
432
+ resolve: options.resolve,
433
+ baseline: options.baseline,
434
+ ...(options.aliases === undefined || Object.keys(options.aliases).length === 0
435
+ ? {}
436
+ : { aliases: options.aliases }),
437
+ limits: { unrestricted, partial: 0 },
438
+ ...(options.hasCycles
439
+ ? {}
440
+ : {
441
+ graph: {
442
+ cycles: [
443
+ {
444
+ name: "no-cycles",
445
+ message: "These files import each other, directly or through others. The tree had no cycle when this manifest was inferred; keep it that way.",
446
+ within: within.length === 1 ? (within[0] ?? "") : within,
447
+ },
448
+ ],
449
+ },
450
+ }),
451
+ tree,
452
+ };
453
+ return { manifest, nodes, files };
454
+ };
455
+ //# sourceMappingURL=infer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"infer.js","sourceRoot":"","sources":["../../../src/manifest/infer.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodbones/core",
3
- "version": "0.1.0-beta.3",
3
+ "version": "0.1.0-beta.4",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Architecture policy as one manifest of your repository, language-agnostic: the manifest schema, the evaluators for imports, exports, members, surface, structure and the import graph, the ports a language pack implements, and the loader that turns a manifest into a policy every rule of which has proven it can fire.",
package/src/core/graph.ts CHANGED
@@ -215,6 +215,18 @@ const stronglyConnected = (
215
215
  return components;
216
216
  };
217
217
 
218
+ // Every cycle in the graph, each as its sorted member set. What `infer` asks
219
+ // before it writes a no-cycles rule: a rule the tree fails on day one is a
220
+ // baseline entry, not a description of the tree.
221
+ export const cyclesIn = (graph: Graph): ReadonlyArray<ReadonlyArray<string>> =>
222
+ stronglyConnected(graph.files, graph).filter((component) => {
223
+ const [first] = component;
224
+ return (
225
+ first !== undefined &&
226
+ (component.length > 1 || (graph.edges.get(first) ?? []).includes(first))
227
+ );
228
+ });
229
+
218
230
  const evaluateCycles = (rule: CompiledGraphCycleRule, graph: Graph): ReadonlyArray<Violation> => {
219
231
  const nodes = graph.files.filter((file) => inScope(rule, file));
220
232
  const violations: Array<Violation> = [];
package/src/index.ts CHANGED
@@ -37,6 +37,7 @@ export {
37
37
  export {
38
38
  type CompiledGraph,
39
39
  compileGraphRules,
40
+ cyclesIn,
40
41
  EMPTY_GRAPH_RULES,
41
42
  evaluateGraph,
42
43
  type Graph,
@@ -134,7 +135,13 @@ export {
134
135
  type IncludeReader,
135
136
  type SourceDocument,
136
137
  } from "./infrastructure/manifest-include.js";
137
- export { listSourceFiles, type WalkedLanguage } from "./infrastructure/walk.js";
138
+ export {
139
+ listPackageRoots,
140
+ listSourceFiles,
141
+ type PackagedLanguage,
142
+ type PackageRoot,
143
+ type WalkedLanguage,
144
+ } from "./infrastructure/walk.js";
138
145
  export { type LoadedPolicy, loadPolicy, type LoadPolicyInput } from "./load/policy.js";
139
146
  export { type LoweredRules, lowerManifest, type ProbeLanguage } from "./manifest/compile.js";
140
147
  export {
@@ -145,6 +152,18 @@ export {
145
152
  originOf,
146
153
  type Substitution,
147
154
  } from "./manifest/expand.js";
155
+ export {
156
+ type Candidate,
157
+ candidatesOf,
158
+ type Generalization,
159
+ type InferInput,
160
+ inferManifest,
161
+ type InferOptions,
162
+ type InferPackage,
163
+ type Inferred,
164
+ type InferredTarget,
165
+ singularOf,
166
+ } from "./manifest/infer.js";
148
167
  export {
149
168
  MANIFEST_NODE_SCHEMA_ID,
150
169
  MANIFEST_SCHEMA_ID,
@@ -1,4 +1,4 @@
1
- import { readdirSync, statSync } from "node:fs";
1
+ import { existsSync, readdirSync, statSync } from "node:fs";
2
2
  import * as path from "node:path";
3
3
 
4
4
  import type { Language } from "../ports/language.js";
@@ -52,3 +52,72 @@ export const listSourceFiles = (
52
52
  }
53
53
  return found.sort();
54
54
  };
55
+
56
+ // What `infer` needs to know about a language beyond the walker's two: what
57
+ // marks a package of it, and where a package keeps its source.
58
+ export type PackagedLanguage = Pick<Language, "packageMarkers" | "sourceRoots">;
59
+
60
+ export type PackageRoot = {
61
+ // Repo-relative folder with forward slashes; `""` is the repository itself.
62
+ readonly root: string;
63
+ // The package's source folder, repo-relative, when one of the language's
64
+ // `sourceRoots` exists under it; otherwise `null`, and source sits at the root.
65
+ readonly source: string | null;
66
+ };
67
+
68
+ // The packages under (and above) `roots`: every folder holding one of the
69
+ // languages' marker files, the walk roots' own ancestors included, so a walk
70
+ // rooted at `src/` still finds the `package.json` beside it. Sorted by path.
71
+ export const listPackageRoots = (
72
+ repoRoot: string,
73
+ roots: ReadonlyArray<string>,
74
+ languages: ReadonlyArray<PackagedLanguage>,
75
+ ): ReadonlyArray<PackageRoot> => {
76
+ const markers = languages.flatMap((language) => language.packageMarkers);
77
+ const sourceRoots = languages.flatMap((language) => language.sourceRoots);
78
+ if (markers.length === 0) return [];
79
+
80
+ const isDirectory = (absolute: string): boolean => {
81
+ try {
82
+ return statSync(absolute).isDirectory();
83
+ } catch {
84
+ return false;
85
+ }
86
+ };
87
+ const relative = (absolute: string): string =>
88
+ path.relative(repoRoot, absolute).replaceAll(path.sep, "/");
89
+
90
+ const found = new Map<string, PackageRoot>();
91
+ const consider = (absolute: string): void => {
92
+ const key = relative(absolute);
93
+ if (found.has(key)) return;
94
+ if (!markers.some((marker) => existsSync(path.join(absolute, marker)))) return;
95
+ const source = sourceRoots.find((name) => isDirectory(path.join(absolute, name)));
96
+ found.set(key, {
97
+ root: key,
98
+ source: source === undefined ? null : relative(path.join(absolute, source)),
99
+ });
100
+ };
101
+ const walk = (absolute: string): void => {
102
+ consider(absolute);
103
+ for (const entry of readdirSync(absolute)) {
104
+ if (SKIPPED.has(entry)) continue;
105
+ const child = path.join(absolute, entry);
106
+ if (isDirectory(child)) walk(child);
107
+ }
108
+ };
109
+
110
+ const top = path.resolve(repoRoot);
111
+ for (const root of roots) {
112
+ let absolute = path.resolve(repoRoot, root);
113
+ if (!isDirectory(absolute)) absolute = path.dirname(absolute);
114
+ walk(absolute);
115
+ // The ancestors, up to the repository: a package is as often above the
116
+ // walk root as inside it.
117
+ for (let above = absolute; above.startsWith(top); above = path.dirname(above)) {
118
+ consider(above);
119
+ if (above === top) break;
120
+ }
121
+ }
122
+ return [...found.values()].sort((a, b) => a.root.localeCompare(b.root));
123
+ };