Files
rea/src/domain/referenceSourcePathIdentity.ts
T
morluto ec271121e4 fix(artifacts): preserve path identity and exclusion applicability
Normalize path facts once at the owning filesystem boundary, preserve caller spelling and unavailable inventory members, and retain typed read failures. Record exclusion entry kinds so only proven directories can establish descendant symlink targets; omitted legacy kinds stay unknown.

Reviewed fixes include #1969, #2642, #2759, and #2994.

Validation: 26 reference graph source tests passed; prior artifact checks passed 275 tests with one Windows-only skip. Updated compiled import workflows are included in final validation after regeneration.
2026-10-14 08:31:16 +08:00

51 lines
1.8 KiB
TypeScript

import { posix } from "node:path";
export interface ReferenceSourcePathLookup {
readonly resolve: (path: string) => string | undefined;
readonly resolveCandidates: (paths: readonly string[]) => string | undefined;
/** Resolve a path to the closest recorded parent identity. */
readonly resolveAncestor: (path: string) => string | undefined;
}
/**
* Reference inventories retain filesystem spelling. Prefer that exact identity;
* canonical Unicode equivalence is a fallback only when it identifies one entry.
*/
export const createReferenceSourcePathLookup = (
paths: Iterable<string>,
): ReferenceSourcePathLookup => {
const exact = new Set(paths);
const canonical = new Map<string, string | null>();
for (const path of exact) {
const key = path.normalize("NFC");
canonical.set(key, canonical.has(key) ? null : path);
}
const equivalent = (path: string): string | undefined =>
canonical.get(path.normalize("NFC")) ?? undefined;
const resolve = (path: string): string | undefined =>
exact.has(path) ? path : equivalent(path);
return {
resolve,
resolveCandidates: (candidates) =>
candidates.find((path) => exact.has(path)) ??
candidates.map(equivalent).find((path) => path !== undefined),
resolveAncestor: (path) => {
for (
let candidate = path;
candidate !== "." && candidate !== "/";
candidate = posix.dirname(candidate)
) {
const found = resolve(candidate);
if (found !== undefined) return found;
}
return undefined;
},
};
};
/** Symlink evidence can originate on a different OS from the graph reader. */
export const isPortableAbsoluteReferenceTarget = (target: string): boolean =>
target.startsWith("/") ||
/^[A-Za-z]:[\\/]/u.test(target) ||
/^\\\\[^\\/]+[\\/][^\\/]+/u.test(target);