diff --git a/lang/de.json b/lang/de.json index 7a02f8a..efb5579 100644 --- a/lang/de.json +++ b/lang/de.json @@ -42,7 +42,8 @@ }, "Notice": { "Title": "Hinweise", - "InactiveModuleReferences": "Die aktive Welt referenziert Dateien aus inaktiven Modulen. Diese Modulziele wurden nicht als unverankert gewertet: {modules}" + "InactiveModuleReferences": "Die aktive Welt referenziert Dateien aus inaktiven Modulen: {modules}", + "MissingModuleReferences": "Die aktive Welt referenziert Dateien aus Modulordnern, die von Foundry derzeit nicht als Module erkannt werden: {modules}" }, "Summary": { "NoAnalysis": "Noch keine Analyse ausgeführt.", diff --git a/lang/en.json b/lang/en.json index 0f9566a..55c493d 100644 --- a/lang/en.json +++ b/lang/en.json @@ -42,7 +42,8 @@ }, "Notice": { "Title": "Notes", - "InactiveModuleReferences": "The active world references files from inactive modules. These module targets were not treated as unanchored: {modules}" + "InactiveModuleReferences": "The active world references files from inactive modules: {modules}", + "MissingModuleReferences": "The active world references files from module folders that Foundry does not currently recognize as installed modules: {modules}" }, "Summary": { "NoAnalysis": "No analysis has been run yet.", diff --git a/module.json b/module.json index c17420b..d5b4b9a 100644 --- a/module.json +++ b/module.json @@ -2,7 +2,7 @@ "id": "kosmos-storage-audit", "title": "Kosmos Storage Audit", "description": "Analyzes media references and risky storage locations across Foundry data and public roots.", - "version": "0.0.24", + "version": "0.0.25", "compatibility": { "minimum": "13", "verified": "13" diff --git a/scripts/core/finding-engine.js b/scripts/core/finding-engine.js index bff930c..da30a6f 100644 --- a/scripts/core/finding-engine.js +++ b/scripts/core/finding-engine.js @@ -44,6 +44,7 @@ export function buildFindings({ files, references, i18n, packageActivity }={}) { const wildcardReferences = []; const findings = []; const inactiveModuleReferenceIds = new Set(); + const missingModuleReferenceIds = new Set(); for (const reference of resolvedReferences) { const normalized = reference.normalized; @@ -85,6 +86,10 @@ export function buildFindings({ files, references, i18n, packageActivity }={}) { const ownerRelation = compareOwner(reference.sourceScope, file.ownerHint); if (ownerRelation === "non-package-to-package") { + if (isMissingModuleTarget(file, packageActivity)) { + missingModuleReferenceIds.add(file.ownerHint.ownerId); + continue; + } if (isInactiveModuleTarget(file, packageActivity)) { inactiveModuleReferenceIds.add(file.ownerHint.ownerId); continue; @@ -144,7 +149,7 @@ export function buildFindings({ files, references, i18n, packageActivity }={}) { return { findings, - notices: createNotices({ inactiveModuleReferenceIds, packageActivity, i18n }) + notices: createNotices({ inactiveModuleReferenceIds, missingModuleReferenceIds, packageActivity, i18n }) }; } @@ -239,19 +244,42 @@ function isInactiveModuleTarget(file, packageActivity) { return active === false; } -function createNotices({ inactiveModuleReferenceIds, packageActivity, i18n }) { - const moduleIds = [...inactiveModuleReferenceIds].sort((a, b) => a.localeCompare(b)); - if (!moduleIds.length) return []; - const moduleLabels = moduleIds.map(id => packageActivity?.moduleLabels?.get?.(id) ?? id); - return [{ - kind: "inactive-module-references", - severity: "info", - moduleIds, - moduleLabels, - message: format(i18n, "KSA.Notice.InactiveModuleReferences", { - modules: moduleLabels.join(", ") - }) - }]; +function isMissingModuleTarget(file, packageActivity) { + if (file.ownerHint?.ownerType !== "module") return false; + return packageActivity?.modules?.has?.(file.ownerHint.ownerId) === false; +} + +function createNotices({ inactiveModuleReferenceIds, missingModuleReferenceIds, packageActivity, i18n }) { + const notices = []; + + const inactiveModuleIds = [...inactiveModuleReferenceIds].sort((a, b) => a.localeCompare(b)); + if (inactiveModuleIds.length) { + const moduleLabels = inactiveModuleIds.map(id => packageActivity?.moduleLabels?.get?.(id) ?? id); + notices.push({ + kind: "inactive-module-references", + severity: "info", + moduleIds: inactiveModuleIds, + moduleLabels, + message: format(i18n, "KSA.Notice.InactiveModuleReferences", { + modules: moduleLabels.join(", ") + }) + }); + } + + const missingModuleIds = [...missingModuleReferenceIds].sort((a, b) => a.localeCompare(b)); + if (missingModuleIds.length) { + notices.push({ + kind: "missing-module-references", + severity: "info", + moduleIds: missingModuleIds, + moduleLabels: missingModuleIds, + message: format(i18n, "KSA.Notice.MissingModuleReferences", { + modules: missingModuleIds.join(", ") + }) + }); + } + + return notices; } function isDerivedSceneThumbnail(file) { diff --git a/tests/missing-module-reference-test.mjs b/tests/missing-module-reference-test.mjs new file mode 100644 index 0000000..05e7821 --- /dev/null +++ b/tests/missing-module-reference-test.mjs @@ -0,0 +1,40 @@ +import assert from "node:assert/strict"; +import { buildFindings, createFileRecord } from "../scripts/core/finding-engine.js"; +import { createFileLocator } from "../scripts/core/path-utils.js"; + +const files = [ + createFileRecord(createFileLocator("data", "modules/legacy-module/icons/token.webp"), 1234) +]; + +const references = [ + { + sourceType: "world-document", + sourceScope: { ownerType: "world", ownerId: "demo-world", systemId: "demo-system", subtype: "actors" }, + sourceLabel: "Actor demo", + normalized: { + ...createFileLocator("data", "modules/legacy-module/icons/token.webp"), + targetKind: "local-file" + } + } +]; + +const packageActivity = { + modules: new Map(), + moduleLabels: new Map() +}; + +const result = buildFindings({ files, references, packageActivity, i18n: { format: (key, data={}) => `${key}:${data.modules ?? ""}` } }); + +assert.equal( + result.findings.some(finding => finding.kind === "non-package-to-package-reference"), + false, + "world references into module folders that Foundry does not recognize should not be reported as unanchored package targets" +); + +assert.equal( + result.notices.some(notice => notice.kind === "missing-module-references" && notice.moduleIds.includes("legacy-module")), + true, + "missing module references should create a dedicated report notice" +); + +console.log("missing-module-reference-test: ok");