Release 0.0.2
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { classifyRisk, detectMediaKind, inferOwnerHint, isCorePublicPath, isStorageAreaPath } from "./path-utils.js";
|
||||
import { classifyRisk, createCanonicalLocator, detectMediaKind, inferOwnerHint, isCorePublicPath, isStorageAreaPath } from "./path-utils.js";
|
||||
|
||||
function compareOwner(sourceScope, targetOwner) {
|
||||
if (!sourceScope) return "unknown";
|
||||
@@ -33,32 +33,49 @@ function compareOwner(sourceScope, targetOwner) {
|
||||
}
|
||||
|
||||
export function buildFindings({ files, references }) {
|
||||
const fileByLocator = new Map(files.map(file => [file.locator, file]));
|
||||
const fileByLocator = new Map(files.map(file => [createCanonicalLocator(file.storage, file.path), file]));
|
||||
const fileLocators = files.map(file => ({
|
||||
storage: file.storage,
|
||||
path: file.path,
|
||||
locator: createCanonicalLocator(file.storage, file.path)
|
||||
}));
|
||||
const refsByLocator = new Map();
|
||||
const wildcardReferences = [];
|
||||
const findings = [];
|
||||
|
||||
for (const reference of references) {
|
||||
const normalized = reference.normalized;
|
||||
if (!normalized) continue;
|
||||
const bucket = refsByLocator.get(normalized.locator) ?? [];
|
||||
if (normalized.targetKind === "wildcard") {
|
||||
wildcardReferences.push(reference);
|
||||
continue;
|
||||
}
|
||||
const normalizedLocator = createCanonicalLocator(normalized.storage, normalized.path);
|
||||
const bucket = refsByLocator.get(normalizedLocator) ?? [];
|
||||
bucket.push(reference);
|
||||
refsByLocator.set(normalized.locator, bucket);
|
||||
refsByLocator.set(normalizedLocator, bucket);
|
||||
}
|
||||
|
||||
for (const reference of references) {
|
||||
const normalized = reference.normalized;
|
||||
if (!normalized) continue;
|
||||
const normalizedLocator = createCanonicalLocator(normalized.storage, normalized.path);
|
||||
|
||||
const file = fileByLocator.get(normalized.locator);
|
||||
const file = fileByLocator.get(normalizedLocator);
|
||||
if (!file) {
|
||||
if ((normalized.targetKind === "wildcard") && wildcardMatchesAny(normalized, fileLocators)) continue;
|
||||
if (reference.sourceScope?.ownerType !== "world") continue;
|
||||
findings.push({
|
||||
kind: "broken-reference",
|
||||
severity: reference.sourceScope?.ownerType === "world" ? "high" : "warning",
|
||||
target: normalized,
|
||||
target: { ...normalized, locator: normalizedLocator },
|
||||
source: reference,
|
||||
reason: `Referenced file ${normalized.locator} does not exist in the scanned roots.`,
|
||||
recommendation: "Check whether the file was moved, deleted, or should be copied into a stable location.",
|
||||
reason: normalized.targetKind === "wildcard"
|
||||
? `Wildcard reference ${normalized.locator} did not match any files in the scanned roots.`
|
||||
: `Referenced file ${normalized.locator} does not exist in the scanned roots.`,
|
||||
recommendation: normalized.targetKind === "wildcard"
|
||||
? "Check whether the wildcard pattern is still correct and whether matching files still exist."
|
||||
: "Check whether the file was moved, deleted, or should be copied into a stable location.",
|
||||
confidence: "high"
|
||||
});
|
||||
continue;
|
||||
@@ -66,11 +83,11 @@ export function buildFindings({ files, references }) {
|
||||
|
||||
const ownerRelation = compareOwner(reference.sourceScope, file.ownerHint);
|
||||
if (ownerRelation === "non-package-to-package") {
|
||||
if (isAnchoredInOwningPackage(file, refsByLocator.get(normalized.locator) ?? [])) continue;
|
||||
if (isAnchoredInOwningPackage(file, matchingReferencesForFile(file, refsByLocator, wildcardReferences))) continue;
|
||||
findings.push({
|
||||
kind: "non-package-to-package-reference",
|
||||
severity: "high",
|
||||
target: normalized,
|
||||
target: { ...normalized, locator: normalizedLocator },
|
||||
source: reference,
|
||||
reason: `${reference.sourceScope.ownerType}:${reference.sourceScope.ownerId} references package-owned storage ${normalized.locator}, but the asset is not visibly referenced by its owning package.`,
|
||||
recommendation: "Review whether the file was manually placed into the package folder. If the package does not use it itself, prefer moving it into user-controlled storage.",
|
||||
@@ -81,7 +98,7 @@ export function buildFindings({ files, references }) {
|
||||
findings.push({
|
||||
kind: "risky-public-reference",
|
||||
severity: "high",
|
||||
target: normalized,
|
||||
target: { ...normalized, locator: normalizedLocator },
|
||||
source: reference,
|
||||
reason: `${reference.sourceScope.ownerType}:${reference.sourceScope.ownerId} references release-public storage ${normalized.locator}.`,
|
||||
recommendation: "Prefer copying the asset into world or user-controlled storage.",
|
||||
@@ -92,7 +109,7 @@ export function buildFindings({ files, references }) {
|
||||
|
||||
for (const file of files) {
|
||||
if (detectMediaKind(file.path) === "other") continue;
|
||||
const refs = refsByLocator.get(file.locator) ?? [];
|
||||
const refs = matchingReferencesForFile(file, refsByLocator, wildcardReferences);
|
||||
if (refs.length) continue;
|
||||
if (!shouldReportOrphan(file, references)) continue;
|
||||
|
||||
@@ -126,6 +143,31 @@ function isAnchoredInOwningPackage(file, references) {
|
||||
);
|
||||
}
|
||||
|
||||
function matchingReferencesForFile(file, refsByLocator, wildcardReferences) {
|
||||
const exactRefs = refsByLocator.get(createCanonicalLocator(file.storage, file.path)) ?? [];
|
||||
const matchingWildcardRefs = wildcardReferences.filter(reference => wildcardMatchesFile(reference.normalized, file));
|
||||
return [...exactRefs, ...matchingWildcardRefs];
|
||||
}
|
||||
|
||||
function wildcardMatchesAny(target, fileLocators) {
|
||||
const matcher = wildcardToRegExp(target.path);
|
||||
return fileLocators.some(file => file.storage === target.storage && matcher.test(file.path));
|
||||
}
|
||||
|
||||
function wildcardMatchesFile(target, file) {
|
||||
if (!target || (target.targetKind !== "wildcard")) return false;
|
||||
if (target.storage !== file.storage) return false;
|
||||
return wildcardToRegExp(target.path).test(file.path);
|
||||
}
|
||||
|
||||
function wildcardToRegExp(pattern) {
|
||||
const escaped = String(pattern)
|
||||
.replace(/[.+^${}()|\\]/g, "\\$&")
|
||||
.replace(/\*/g, ".*")
|
||||
.replace(/\?/g, ".");
|
||||
return new RegExp(`^${escaped}$`, "u");
|
||||
}
|
||||
|
||||
function shouldReportOrphan(file, references) {
|
||||
if (file.riskClass === "stable-world") {
|
||||
const worldId = file.ownerHint.ownerId;
|
||||
|
||||
Reference in New Issue
Block a user