@trackunit/repo-checks 0.0.0-stage → 0.0.2

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 (46) hide show
  1. package/CHANGELOG.md +3 -0
  2. package/LICENSE.txt +191 -0
  3. package/README.md +319 -2
  4. package/bin/index.d.ts +2 -0
  5. package/bin/index.js +34 -0
  6. package/migrations/entry.js +3 -0
  7. package/migrations.json +3 -0
  8. package/package.json +34 -3
  9. package/src/cli.d.ts +27 -0
  10. package/src/cli.js +109 -0
  11. package/src/index.d.ts +8 -0
  12. package/src/index.js +21 -0
  13. package/src/lib/config-error.d.ts +8 -0
  14. package/src/lib/config-error.js +16 -0
  15. package/src/lib/config.d.ts +22 -0
  16. package/src/lib/config.js +61 -0
  17. package/src/lib/doc-references/case-fix.d.ts +21 -0
  18. package/src/lib/doc-references/case-fix.js +66 -0
  19. package/src/lib/doc-references/check-doc-references.d.ts +28 -0
  20. package/src/lib/doc-references/check-doc-references.js +147 -0
  21. package/src/lib/doc-references/classify.d.ts +49 -0
  22. package/src/lib/doc-references/classify.js +59 -0
  23. package/src/lib/doc-references/discover.d.ts +6 -0
  24. package/src/lib/doc-references/discover.js +84 -0
  25. package/src/lib/doc-references/extract.d.ts +29 -0
  26. package/src/lib/doc-references/extract.js +46 -0
  27. package/src/lib/doc-references/finding.d.ts +24 -0
  28. package/src/lib/doc-references/finding.js +11 -0
  29. package/src/lib/doc-references/fs-errors.d.ts +14 -0
  30. package/src/lib/doc-references/fs-errors.js +25 -0
  31. package/src/lib/doc-references/headings.d.ts +79 -0
  32. package/src/lib/doc-references/headings.js +219 -0
  33. package/src/lib/doc-references/parse.d.ts +72 -0
  34. package/src/lib/doc-references/parse.js +72 -0
  35. package/src/lib/doc-references/placeholders.d.ts +74 -0
  36. package/src/lib/doc-references/placeholders.js +176 -0
  37. package/src/lib/doc-references/repo-root.d.ts +18 -0
  38. package/src/lib/doc-references/repo-root.js +36 -0
  39. package/src/lib/doc-references/resolver.d.ts +96 -0
  40. package/src/lib/doc-references/resolver.js +430 -0
  41. package/src/lib/doc-references/settings.d.ts +163 -0
  42. package/src/lib/doc-references/settings.js +167 -0
  43. package/src/lib/doc-references/suppressions.d.ts +8 -0
  44. package/src/lib/doc-references/suppressions.js +24 -0
  45. package/src/lib/format.d.ts +13 -0
  46. package/src/lib/format.js +33 -0
@@ -0,0 +1,430 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveReference = exports.MAX_VISITED_DIRECTORIES = exports.referenceTarget = exports.hasFileExtension = exports.referenceFragment = exports.splitReference = exports.isIgnoredPath = exports.canonicalCase = exports.RACY_WINDOW_MS = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const fs = tslib_1.__importStar(require("fs"));
6
+ const path = tslib_1.__importStar(require("path"));
7
+ const picomatch_1 = tslib_1.__importDefault(require("picomatch"));
8
+ const fs_errors_1 = require("./fs-errors");
9
+ const placeholders_1 = require("./placeholders");
10
+ /** Per-process listing cache, so each directory is listed once per run; revalidated by directory mtime. */
11
+ const listingCache = new Map();
12
+ /**
13
+ * A listing (or parsed file) read within this long of its mtime is never reused: a change in the
14
+ * same timestamp tick leaves the mtime unchanged. Covers the coarsest common granularity (FAT, 2s).
15
+ */
16
+ exports.RACY_WINDOW_MS = 2000;
17
+ /**
18
+ * The listing of `directory`, or `undefined` when it doesn't exist or isn't a directory.
19
+ *
20
+ * @throws {ReadError} when `directory` exists but can't be stat'ed or listed
21
+ */
22
+ const listDirectory = (directory) => {
23
+ let mtimeMs;
24
+ try {
25
+ const stats = fs.statSync(directory);
26
+ if (!stats.isDirectory()) {
27
+ listingCache.delete(directory);
28
+ return undefined;
29
+ }
30
+ mtimeMs = stats.mtimeMs;
31
+ }
32
+ catch (error) {
33
+ listingCache.delete(directory);
34
+ if ((0, fs_errors_1.isNotFoundError)(error))
35
+ return undefined;
36
+ throw new fs_errors_1.ReadError(`Cannot read directory ${directory}: ${(0, fs_errors_1.errorMessage)(error)}`);
37
+ }
38
+ const cached = listingCache.get(directory);
39
+ if (cached?.mtimeMs === mtimeMs && cached.listedAtMs - mtimeMs > exports.RACY_WINDOW_MS) {
40
+ return cached;
41
+ }
42
+ try {
43
+ const listedAtMs = Date.now();
44
+ const names = fs.readdirSync(directory);
45
+ const byLowerCase = new Map();
46
+ const ambiguousLowerCase = new Set();
47
+ for (const name of names) {
48
+ const lowerCase = name.toLowerCase();
49
+ if (byLowerCase.has(lowerCase)) {
50
+ ambiguousLowerCase.add(lowerCase);
51
+ }
52
+ else {
53
+ byLowerCase.set(lowerCase, name);
54
+ }
55
+ }
56
+ const listing = {
57
+ mtimeMs,
58
+ listedAtMs,
59
+ names: new Set(names),
60
+ sortedNames: [...names].sort(),
61
+ byLowerCase,
62
+ ambiguousLowerCase,
63
+ };
64
+ listingCache.set(directory, listing);
65
+ return listing;
66
+ }
67
+ catch (error) {
68
+ listingCache.delete(directory);
69
+ if ((0, fs_errors_1.isNotFoundError)(error))
70
+ return undefined;
71
+ throw new fs_errors_1.ReadError(`Cannot read directory ${directory}: ${(0, fs_errors_1.errorMessage)(error)}`);
72
+ }
73
+ };
74
+ /** The listed entry named `segment`, exactly if possible, otherwise case-insensitively. */
75
+ const findListedEntry = (listing, segment) => {
76
+ if (listing.names.has(segment)) {
77
+ return { name: segment, ambiguous: false };
78
+ }
79
+ const lowerCase = segment.toLowerCase();
80
+ const name = listing.byLowerCase.get(lowerCase);
81
+ return name === undefined ? undefined : { name, ambiguous: listing.ambiguousLowerCase.has(lowerCase) };
82
+ };
83
+ const findEntry = (directory, segment) => {
84
+ const listing = listDirectory(directory);
85
+ return listing && findListedEntry(listing, segment);
86
+ };
87
+ const isWithin = (absolutePath, directory) => absolutePath === directory || absolutePath.startsWith(directory + path.sep);
88
+ const segmentsBelow = (base, absolutePath) => path
89
+ .relative(base, absolutePath)
90
+ .split(path.sep)
91
+ .filter(segment => segment !== "");
92
+ /**
93
+ * What a reference's target may name: a trailing `/` needs a directory, a file extension a file,
94
+ * and anything else either. A stemless dot-name (`.github`, `.gitignore`) has no extension here,
95
+ * since it is as often a directory as a file. A multi-dot name (`apps/socket.io`) does, so it must
96
+ * name a file.
97
+ */
98
+ const expectedKind = (target) => {
99
+ if (target.endsWith("/"))
100
+ return "directory";
101
+ const lastSegment = target.slice(target.lastIndexOf("/") + 1);
102
+ if (/^\.[^.]+$/.test(lastSegment))
103
+ return "any";
104
+ return (0, exports.hasFileExtension)(target) ? "file" : "any";
105
+ };
106
+ /**
107
+ * Whether `absolutePath` exists, following symlinks, and is of the `expected` kind.
108
+ *
109
+ * @throws {ReadError} when `absolutePath` exists but can't be stat'ed
110
+ */
111
+ const isExpectedKind = (absolutePath, expected) => {
112
+ let stats;
113
+ try {
114
+ stats = fs.statSync(absolutePath);
115
+ }
116
+ catch (error) {
117
+ if ((0, fs_errors_1.isNotFoundError)(error))
118
+ return false; // e.g. a dangling symlink, which a directory listing still names
119
+ throw new fs_errors_1.ReadError(`Cannot read ${absolutePath}: ${(0, fs_errors_1.errorMessage)(error)}`);
120
+ }
121
+ return expected === "any" || stats.isDirectory() === (expected === "directory");
122
+ };
123
+ /**
124
+ * Walk `absolutePath` segment by segment below `trustedBase` (whose casing the caller has already
125
+ * canonicalised), comparing each name exactly against its parent's listing. The entry it ends on
126
+ * must be of the `expected` kind, or the reference is `missing` whatever its casing.
127
+ */
128
+ const lookupCaseExact = (absolutePath, trustedBase, expected) => {
129
+ const start = isWithin(absolutePath, trustedBase) ? trustedBase : path.parse(absolutePath).root;
130
+ let actualPath = start;
131
+ let caseDiffers = false;
132
+ let ambiguous = false;
133
+ for (const segment of segmentsBelow(start, absolutePath)) {
134
+ const entry = findEntry(actualPath, segment);
135
+ if (entry === undefined) {
136
+ return { kind: "missing" };
137
+ }
138
+ caseDiffers || (caseDiffers = entry.name !== segment);
139
+ ambiguous || (ambiguous = entry.ambiguous);
140
+ actualPath = path.join(actualPath, entry.name);
141
+ }
142
+ if (!isExpectedKind(actualPath, expected)) {
143
+ return { kind: "missing" };
144
+ }
145
+ return caseDiffers ? { kind: "caseMismatch", actualPath, ambiguous } : { kind: "exact", path: absolutePath };
146
+ };
147
+ /**
148
+ * The on-disk casing of an absolute path, without following symlinks. Segments from the first one
149
+ * that doesn't exist, or whose parent can't be listed, onward are returned as given. Callers can be
150
+ * handed a differently cased path on case-insensitive filesystems; canonicalising the document's own
151
+ * path keeps its relative references from all reading as case mismatches.
152
+ */
153
+ const canonicalCase = (absolutePath) => {
154
+ const root = path.parse(absolutePath).root;
155
+ const segments = segmentsBelow(root, absolutePath);
156
+ let actualPath = root;
157
+ for (const [index, segment] of segments.entries()) {
158
+ let entry;
159
+ try {
160
+ entry = findEntry(actualPath, segment);
161
+ }
162
+ catch {
163
+ // Ancestors outside the repository may be searchable but not listable (e.g. a `--x` home directory).
164
+ entry = undefined;
165
+ }
166
+ if (entry === undefined) {
167
+ return path.join(actualPath, ...segments.slice(index));
168
+ }
169
+ actualPath = path.join(actualPath, entry.name);
170
+ }
171
+ return actualPath;
172
+ };
173
+ exports.canonicalCase = canonicalCase;
174
+ const compiledIgnores = new WeakMap();
175
+ /** Whether a repo-relative path (either separator) matches any of the ignore globs. */
176
+ const isIgnoredPath = (repoRelativePath, ignore) => {
177
+ let matchers = compiledIgnores.get(ignore);
178
+ if (!matchers) {
179
+ matchers = ignore.map(glob => (0, picomatch_1.default)(glob, { dot: true }));
180
+ compiledIgnores.set(ignore, matchers);
181
+ }
182
+ const normalized = repoRelativePath.split(path.sep).join("/");
183
+ return matchers.some(matcher => matcher(normalized));
184
+ };
185
+ exports.isIgnoredPath = isIgnoredPath;
186
+ /**
187
+ * Split a reference into its path part and suffix. A URL's suffix starts at its `?query` or
188
+ * `#fragment`, whichever comes first. Inline code only drops a `#fragment`, since a `?` in a code
189
+ * span may be a literal part of the path.
190
+ */
191
+ const splitReference = (reference, referenceKind) => {
192
+ const index = reference.search(referenceKind === "url" ? /[?#]/ : /#/);
193
+ return index === -1
194
+ ? { pathPart: reference, suffix: "" }
195
+ : { pathPart: reference.slice(0, index), suffix: reference.slice(index) };
196
+ };
197
+ exports.splitReference = splitReference;
198
+ /** A reference's `#fragment` without the `#`, still percent-encoded for URLs; `""` when there is none. */
199
+ const referenceFragment = (reference) => {
200
+ const index = reference.indexOf("#");
201
+ return index === -1 ? "" : reference.slice(index + 1);
202
+ };
203
+ exports.referenceFragment = referenceFragment;
204
+ const EXTENSION_RE = /\.[A-Za-z0-9]{1,8}$/;
205
+ /** Whether a reference target ends in a file extension. One ending in `/` never does. */
206
+ const hasFileExtension = (target) => EXTENSION_RE.test(target);
207
+ exports.hasFileExtension = hasFileExtension;
208
+ /** mdast keeps `url` percent-encoded. A malformed escape is checked as written. */
209
+ const decodeUrlPath = (value) => {
210
+ try {
211
+ return decodeURIComponent(value);
212
+ }
213
+ catch {
214
+ return value;
215
+ }
216
+ };
217
+ /** The path a reference names: its path part (see `splitReference`), percent-decoded for URLs. */
218
+ const referenceTarget = (reference, referenceKind) => {
219
+ const { pathPart } = (0, exports.splitReference)(reference, referenceKind);
220
+ return referenceKind === "url" ? decodeUrlPath(pathPart) : pathPart;
221
+ };
222
+ exports.referenceTarget = referenceTarget;
223
+ const candidatePaths = ({ referenceKind, documentDir, repoRoot }, target) => {
224
+ if (target.startsWith("/")) {
225
+ return [{ base: repoRoot, path: path.resolve(repoRoot, target.replace(/^\/+/, "")) }];
226
+ }
227
+ const relativeToDocument = { base: documentDir, path: path.resolve(documentDir, target) };
228
+ if (referenceKind === "url") {
229
+ return [relativeToDocument];
230
+ }
231
+ const relativeToRoot = { base: repoRoot, path: path.resolve(repoRoot, target) };
232
+ return relativeToRoot.path === relativeToDocument.path ? [relativeToDocument] : [relativeToDocument, relativeToRoot];
233
+ };
234
+ /** Hard cap on directory listings per placeholder reference; hitting it skips the reference. */
235
+ exports.MAX_VISITED_DIRECTORIES = 10000;
236
+ const compiledDirectoryIgnores = new WeakMap();
237
+ /**
238
+ * Whether every descendant of a directory is ignored, so a wildcard walk must not list it. That
239
+ * holds exactly when some glob is `P/**` and the directory matches `P`: `**\/dist/**` prunes
240
+ * `libs/dist`, while `dist/*` or `**\/*.zip` never prune anything.
241
+ */
242
+ const isIgnoredDirectory = (directory, { repoRoot, ignore }) => {
243
+ let matchers = compiledDirectoryIgnores.get(ignore);
244
+ if (!matchers) {
245
+ matchers = ignore.flatMap(glob => {
246
+ const prefix = glob.endsWith("/**") ? glob.slice(0, -"/**".length) : "";
247
+ return prefix === "" || prefix.startsWith("!") ? [] : [(0, picomatch_1.default)(prefix, { dot: true })];
248
+ });
249
+ compiledDirectoryIgnores.set(ignore, matchers);
250
+ }
251
+ const normalized = path.relative(repoRoot, directory).split(path.sep).join("/");
252
+ return normalized !== "" && matchers.some(matcher => matcher(normalized));
253
+ };
254
+ /**
255
+ * Whether a resolved path is ignored: it matches an ignore glob, or the reference may name a
256
+ * directory and this one is ignored entirely, so `dist/` counts as ignored generated output even
257
+ * before a build creates it.
258
+ */
259
+ const isIgnoredTarget = (absolutePath, expected, input) => (0, exports.isIgnoredPath)(path.relative(input.repoRoot, absolutePath), input.ignore) ||
260
+ (expected !== "file" && isIgnoredDirectory(absolutePath, input));
261
+ const lookupLiteral = (candidate, expected, input) => isIgnoredTarget(candidate.path, expected, input)
262
+ ? { kind: "ignored" }
263
+ : lookupCaseExact(candidate.path, input.repoRoot, expected);
264
+ const CASE_EXACT = { caseDiffers: false, ambiguous: false };
265
+ /** List `directory` for the walk, unless it is ignored or the budget is spent. */
266
+ const visit = ({ input, budget }, directory) => {
267
+ if (budget.exhausted || isIgnoredDirectory(directory, input))
268
+ return undefined;
269
+ if (budget.remaining <= 0) {
270
+ budget.exhausted = true;
271
+ return undefined;
272
+ }
273
+ const listing = listDirectory(directory);
274
+ if (listing)
275
+ budget.remaining--;
276
+ return listing;
277
+ };
278
+ /** Entry names a wildcard step may take; `.git` is never entered. */
279
+ const wildcardNames = (listing) => listing.sortedNames.filter(name => name !== ".git");
280
+ /**
281
+ * Depth-first, backtracking walk of `walk.steps` from `directory` (already matching the first
282
+ * `index` steps).
283
+ *
284
+ * @yields every existing, non-ignored match of the expected kind, in sorted entry-name order
285
+ */
286
+ function* walkMatches(walk, directory, index, bindings, matchCase) {
287
+ const step = walk.steps[index];
288
+ if (step === undefined) {
289
+ if (isExpectedKind(directory, walk.expected) && !isIgnoredTarget(directory, walk.expected, walk.input)) {
290
+ yield { ...matchCase, path: directory };
291
+ }
292
+ return;
293
+ }
294
+ const listing = visit(walk, directory);
295
+ if (!listing)
296
+ return;
297
+ if (step.kind === "literal") {
298
+ const entry = findListedEntry(listing, step.value);
299
+ if (entry) {
300
+ yield* walkMatches(walk, path.join(directory, entry.name), index + 1, bindings, {
301
+ caseDiffers: matchCase.caseDiffers || entry.name !== step.value,
302
+ ambiguous: matchCase.ambiguous || entry.ambiguous,
303
+ });
304
+ }
305
+ }
306
+ else {
307
+ const matchName = (0, placeholders_1.createSegmentMatcher)(step, bindings);
308
+ for (const name of wildcardNames(listing)) {
309
+ const bound = matchName(name);
310
+ if (bound)
311
+ yield* walkMatches(walk, path.join(directory, name), index + 1, bound, matchCase);
312
+ }
313
+ }
314
+ }
315
+ /**
316
+ * The walk's steps: the candidate base's segments as literals, then the compiled reference, with
317
+ * `.`, `..`, and empty segments applied lexically exactly as `path.resolve` would.
318
+ */
319
+ const wildcardSteps = (base, compiled) => {
320
+ const steps = segmentsBelow(path.parse(base).root, base).map(value => ({
321
+ kind: "literal",
322
+ value,
323
+ }));
324
+ for (const segment of compiled.segments) {
325
+ if (segment.kind === "literal" && (segment.value === "" || segment.value === "."))
326
+ continue;
327
+ if (segment.kind === "literal" && segment.value === "..") {
328
+ steps.pop();
329
+ continue;
330
+ }
331
+ steps.push(segment);
332
+ }
333
+ return steps;
334
+ };
335
+ /**
336
+ * Resolve a placeholder reference against one candidate base. A reference whose path as written
337
+ * matches an ignore glob (`{name}/app.zip`) is `ignored`, and so is one whose literal prefix before
338
+ * the first placeholder is an ignored location, as a literal reference there would be. Beyond it the walk never lists an ignored directory or `.git`,
339
+ * so matches only there count as none. The first case-exact match is `exact`; otherwise a match
340
+ * whose literal steps differ only in case is a `caseMismatch`. A match must be of the `expected`
341
+ * kind, exactly as for a literal reference. Hitting the visit cap is `ignored`.
342
+ */
343
+ const lookupWildcard = (candidate, compiled, { expected, budget }, input) => {
344
+ if (isIgnoredTarget(candidate.path, expected, input))
345
+ return { kind: "ignored" };
346
+ const steps = wildcardSteps(candidate.base, compiled);
347
+ const root = path.parse(candidate.base).root;
348
+ const literalCount = steps.findIndex(step => step.kind !== "literal");
349
+ if (literalCount === -1)
350
+ return lookupLiteral(candidate, expected, input);
351
+ const prefixSegments = steps.slice(0, literalCount).flatMap(step => (step.kind === "literal" ? [step.value] : []));
352
+ const prefix = path.join(root, ...prefixSegments);
353
+ if (isIgnoredDirectory(prefix, input))
354
+ return { kind: "ignored" };
355
+ const start = isWithin(prefix, input.repoRoot) ? input.repoRoot : root;
356
+ const walk = { input, steps, expected, budget };
357
+ const matches = walkMatches(walk, start, segmentsBelow(root, start).length, new Map(), CASE_EXACT);
358
+ let caseMismatch;
359
+ for (const match of matches) {
360
+ if (!match.caseDiffers)
361
+ return { kind: "exact", path: match.path };
362
+ caseMismatch ?? (caseMismatch = match);
363
+ }
364
+ if (budget.exhausted)
365
+ return { kind: "ignored" };
366
+ return caseMismatch
367
+ ? { kind: "caseMismatch", actualPath: caseMismatch.path, ambiguous: caseMismatch.ambiguous }
368
+ : { kind: "missing" };
369
+ };
370
+ /**
371
+ * Resolve a reference to `exact`, `caseMismatch`, `ignored`, or `missing`. Candidates inside an
372
+ * ignored location are never looked up. Across candidates, an exact match wins, then an ignored
373
+ * candidate, then the first case mismatch. A reference with placeholders (under
374
+ * `input.placeholders`) matches wildcards against the tree; see `lookupWildcard`. An illustrative
375
+ * `path/to/` reference, or one with adjacent placeholders, is `ignored` without any lookup.
376
+ *
377
+ * The reference as written says what it names: a trailing `/` must resolve to a directory, a file
378
+ * extension to a file (so a directory named `foo.md` is `missing`), and anything else to either. An
379
+ * entry of the wrong kind is `missing`, even when its name differs only in case.
380
+ *
381
+ * Only the repository is looked at: a candidate outside `repoRoot` is `missing` whether or not it
382
+ * exists on this machine, and a leading `..` in a root-relative reference stays at the root, as on
383
+ * GitHub (`/../x` is `/x`).
384
+ */
385
+ const resolveReference = (input) => {
386
+ const written = (0, exports.referenceTarget)(input.reference, input.referenceKind);
387
+ const target = written.startsWith("/") ? path.posix.normalize(written) : written;
388
+ const pathPart = target.replace(/^\/+/, "");
389
+ const expected = expectedKind(written);
390
+ if ((0, placeholders_1.isIllustrativeReference)(pathPart, input.placeholders)) {
391
+ return { kind: "ignored" };
392
+ }
393
+ const compiled = (0, placeholders_1.compileReference)(pathPart, input.placeholders);
394
+ if (compiled && (0, placeholders_1.hasAdjacentSlots)(compiled)) {
395
+ return { kind: "ignored" };
396
+ }
397
+ const budget = { remaining: input.maxVisitedDirectories ?? exports.MAX_VISITED_DIRECTORIES, exhausted: false };
398
+ let ignored = false;
399
+ let caseMismatch;
400
+ for (const candidate of candidatePaths(input, target)) {
401
+ if (!isWithin(candidate.path, input.repoRoot))
402
+ continue;
403
+ const lookup = compiled
404
+ ? lookupWildcard(candidate, compiled, { expected, budget }, input)
405
+ : lookupLiteral(candidate, expected, input);
406
+ if (lookup.kind === "ignored") {
407
+ ignored = true;
408
+ continue;
409
+ }
410
+ if (lookup.kind === "exact") {
411
+ return compiled ? { kind: "exact", path: lookup.path, placeholder: true } : { kind: "exact", path: lookup.path };
412
+ }
413
+ if (lookup.kind === "caseMismatch" && !caseMismatch) {
414
+ caseMismatch = {
415
+ kind: "caseMismatch",
416
+ path: candidate.path,
417
+ actualPath: lookup.actualPath,
418
+ base: candidate.base,
419
+ ambiguous: lookup.ambiguous,
420
+ placeholder: compiled !== undefined,
421
+ };
422
+ }
423
+ }
424
+ if (ignored) {
425
+ return { kind: "ignored" };
426
+ }
427
+ return caseMismatch ?? { kind: "missing" };
428
+ };
429
+ exports.resolveReference = resolveReference;
430
+ //# sourceMappingURL=resolver.js.map
@@ -0,0 +1,163 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Configuration for the doc-references check, validated with zod so a bad config fails with a
4
+ * message naming the offending key. Every default-bearing list takes `{ extend: [...] }` (add to
5
+ * the defaults) or `{ replace: [...] }` (swap them out, `[]` empties). A bare array is rejected
6
+ * because it can't say which of the two the author meant.
7
+ */
8
+ /** Conventional generated-output locations, matched against the repo-relative path. */
9
+ export declare const DEFAULT_IGNORE: ReadonlyArray<string>;
10
+ /** Which markdown constructs are checked: `links`, `images`, `definitions`, and `inlineCode` spans. */
11
+ export interface ConstructToggles {
12
+ readonly links: boolean;
13
+ readonly images: boolean;
14
+ readonly definitions: boolean;
15
+ readonly inlineCode: boolean;
16
+ }
17
+ /**
18
+ * Links, images, and definitions are checked; inline code only on opt-in, since measured on manager
19
+ * about 96% of inline-code findings were false positives, mostly paths relative to an implied
20
+ * project root. A `constructs` setting overrides only the keys it names.
21
+ */
22
+ export declare const DEFAULT_CONSTRUCTS: ConstructToggles;
23
+ /** Provisional: whether extensionless references with a `/` (`libs/react/components/`) are checked. */
24
+ export declare const DEFAULT_DIRECTORIES = false;
25
+ /**
26
+ * Provisional: whether inline code without a `/` (`` `README.md` ``) is checked. Link, image, and
27
+ * definition URLs without a `/` (`[x](README.md)`) are always checked; see `classify.ts`.
28
+ */
29
+ export declare const DEFAULT_BARE_FILENAMES = false;
30
+ /**
31
+ * A strict object whose unknown keys are reported as `unknown <noun> 'key'; expected <valid keys>`,
32
+ * and whose wrong type as `expected <expected>`.
33
+ */
34
+ export declare const strictObjectNamingKeys: <TShape extends z.ZodRawShape>(shape: TShape, { noun, expected }: {
35
+ readonly noun: string;
36
+ readonly expected: string;
37
+ }) => z.ZodObject<TShape, "strict">;
38
+ /** The `docReferences` section of `repo-checks.config.json`, as written. */
39
+ export declare const docReferencesConfigSchema: z.ZodObject<{
40
+ rootDir: z.ZodOptional<z.ZodString>;
41
+ ignore: z.ZodOptional<z.ZodEffects<z.ZodObject<{
42
+ extend: z.ZodOptional<z.ZodArray<z.ZodType<string, z.ZodTypeDef, string>, "many">>;
43
+ replace: z.ZodOptional<z.ZodArray<z.ZodType<string, z.ZodTypeDef, string>, "many">>;
44
+ }, "strict", z.ZodTypeAny, {
45
+ extend?: string[] | undefined;
46
+ replace?: string[] | undefined;
47
+ }, {
48
+ extend?: string[] | undefined;
49
+ replace?: string[] | undefined;
50
+ }>, {
51
+ extend?: string[] | undefined;
52
+ replace?: string[] | undefined;
53
+ }, {
54
+ extend?: string[] | undefined;
55
+ replace?: string[] | undefined;
56
+ }>>;
57
+ exclude: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
58
+ placeholders: z.ZodOptional<z.ZodEffects<z.ZodObject<{
59
+ extend: z.ZodOptional<z.ZodArray<z.ZodType<string, z.ZodTypeDef, string>, "many">>;
60
+ replace: z.ZodOptional<z.ZodArray<z.ZodType<string, z.ZodTypeDef, string>, "many">>;
61
+ }, "strict", z.ZodTypeAny, {
62
+ extend?: string[] | undefined;
63
+ replace?: string[] | undefined;
64
+ }, {
65
+ extend?: string[] | undefined;
66
+ replace?: string[] | undefined;
67
+ }>, {
68
+ extend?: string[] | undefined;
69
+ replace?: string[] | undefined;
70
+ }, {
71
+ extend?: string[] | undefined;
72
+ replace?: string[] | undefined;
73
+ }>>;
74
+ constructs: z.ZodOptional<z.ZodObject<{
75
+ links: z.ZodOptional<z.ZodBoolean>;
76
+ images: z.ZodOptional<z.ZodBoolean>;
77
+ definitions: z.ZodOptional<z.ZodBoolean>;
78
+ inlineCode: z.ZodOptional<z.ZodBoolean>;
79
+ }, "strict", z.ZodTypeAny, {
80
+ inlineCode?: boolean | undefined;
81
+ links?: boolean | undefined;
82
+ images?: boolean | undefined;
83
+ definitions?: boolean | undefined;
84
+ }, {
85
+ inlineCode?: boolean | undefined;
86
+ links?: boolean | undefined;
87
+ images?: boolean | undefined;
88
+ definitions?: boolean | undefined;
89
+ }>>;
90
+ directories: z.ZodOptional<z.ZodBoolean>;
91
+ bareFilenames: z.ZodOptional<z.ZodBoolean>;
92
+ }, "strict", z.ZodTypeAny, {
93
+ rootDir?: string | undefined;
94
+ ignore?: {
95
+ extend?: string[] | undefined;
96
+ replace?: string[] | undefined;
97
+ } | undefined;
98
+ exclude?: string[] | undefined;
99
+ placeholders?: {
100
+ extend?: string[] | undefined;
101
+ replace?: string[] | undefined;
102
+ } | undefined;
103
+ constructs?: {
104
+ inlineCode?: boolean | undefined;
105
+ links?: boolean | undefined;
106
+ images?: boolean | undefined;
107
+ definitions?: boolean | undefined;
108
+ } | undefined;
109
+ directories?: boolean | undefined;
110
+ bareFilenames?: boolean | undefined;
111
+ }, {
112
+ rootDir?: string | undefined;
113
+ ignore?: {
114
+ extend?: string[] | undefined;
115
+ replace?: string[] | undefined;
116
+ } | undefined;
117
+ exclude?: string[] | undefined;
118
+ placeholders?: {
119
+ extend?: string[] | undefined;
120
+ replace?: string[] | undefined;
121
+ } | undefined;
122
+ constructs?: {
123
+ inlineCode?: boolean | undefined;
124
+ links?: boolean | undefined;
125
+ images?: boolean | undefined;
126
+ definitions?: boolean | undefined;
127
+ } | undefined;
128
+ directories?: boolean | undefined;
129
+ bareFilenames?: boolean | undefined;
130
+ }>;
131
+ export type DocReferencesConfig = z.infer<typeof docReferencesConfigSchema>;
132
+ /** Every issue as `path: message`, `;`-separated, with `(root)` for the top level. */
133
+ export declare const formatConfigIssues: (error: z.ZodError) => string;
134
+ export interface DocsSettings {
135
+ /** Repository root override. When unset, the nearest `.git` above the checked document is used. */
136
+ readonly rootDir: string | undefined;
137
+ /** Picomatch globs, matched against the repo-relative path, whose references are never reported. */
138
+ readonly ignore: ReadonlyArray<string>;
139
+ /**
140
+ * Picomatch globs, matched against the repo-relative path, of documents never read or checked.
141
+ * They stay valid targets: references to them, and anchors into them, are checked as usual. It has
142
+ * no defaults (discovery already skips gitignored files), so it is a plain list, not `{ extend }`
143
+ * or `{ replace }`.
144
+ */
145
+ readonly exclude: ReadonlyArray<string>;
146
+ /** Placeholder conventions (see `placeholders.ts`) that make a reference a wildcard pattern. */
147
+ readonly placeholders: ReadonlyArray<string>;
148
+ /** Which markdown constructs are checked. */
149
+ readonly constructs: ConstructToggles;
150
+ /** Check extensionless references with a `/` as file-or-directory references (see `classify.ts`). */
151
+ readonly directories: boolean;
152
+ /** Check inline code without a `/`, such as `` `README.md` `` (see `classify.ts`). */
153
+ readonly bareFilenames: boolean;
154
+ }
155
+ /** The resolved settings when no config is given. */
156
+ export declare const DEFAULT_DOCS_SETTINGS: DocsSettings;
157
+ /**
158
+ * Validate a raw doc-references config and resolve it onto the defaults. `undefined` yields
159
+ * {@link DEFAULT_DOCS_SETTINGS}.
160
+ *
161
+ * @throws {ConfigError} naming every offending key, when the config is invalid
162
+ */
163
+ export declare const parseDocsSettings: (raw: unknown) => DocsSettings;