mirror of
https://github.com/morluto/rea.git
synced 2026-11-12 17:59:08 +00:00
* fix(website): keep the HTML validator when the Markdown route is absent A caller that prefers Markdown gets the HTML probe with its conditional headers deleted, so the Markdown route can be evaluated on its own. When the Markdown sidecar is missing the worker answers with that probe, which never saw the caller's `If-None-Match`, so a matching HTML ETag returned 200 instead of 304. Ask the asset service again with the caller's own request before answering with HTML. The decision moves into `htmlFallback` so the request handler keeps its complexity below the repository bound. Verified by executing `node --test website/worker.test.mjs`: without the change the new case fails with 200 instead of 304, and all four cases pass with it. `oxlint` reports no findings and `oxfmt --check` passes. The website worker test is not part of a CI workflow, so this suite runs locally only. * fix(scripts): resolve package-map imports in the module-boundary guard The guard resolved only relative, absolute and `file:` specifiers, so every `#`-prefixed package-map import returned no target and was skipped. A pure-layer file importing `#javascript-analysis-worker` passed, while the same import written relatively failed with a `pure-layer` diagnostic: the gate failed open on the spelling the package map exists to encourage. Resolve a package-map specifier to the source file that produces its built output, then apply the same ownership rules. The package map is read once per repository root. Verified with the new `scripts/lib/module-boundaries.test.mjs` (2 pass; the package-map case failed before the change) and by running the complete guard on this checkout, which still reports 4138 files with no findings, so the new rule adds no false positive. `oxfmt --check` passes; `oxlint` reports the same single pre-existing `failedBoundary` complexity warning it reported before this change. The script tests are not part of a CI workflow, so this suite runs locally only. * fix(scripts): normalize package targets as file URLs --------- Co-authored-by: 吴佳浩 <wujiahao@supremind.com>
166 lines
5.5 KiB
JavaScript
166 lines
5.5 KiB
JavaScript
import { readFileSync } from "node:fs";
|
|
import { isAbsolute, relative, resolve } from "node:path";
|
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
|
|
import { readImportSpecifiers } from "./import-specifiers.mjs";
|
|
|
|
const PURE_LAYERS = new Set(["domain", "contracts"]);
|
|
const MIGRATED_PROVIDER_ROOTS = new Set([
|
|
"sqlite",
|
|
"evm",
|
|
"go",
|
|
"il2cpp",
|
|
"model",
|
|
"gpu",
|
|
"python",
|
|
"rust",
|
|
"wasm",
|
|
"captures",
|
|
"signals",
|
|
"android",
|
|
"firmware",
|
|
"ghidra",
|
|
"hopper",
|
|
"ida",
|
|
"inspector",
|
|
"javascript",
|
|
]);
|
|
const CENTRALIZED_CONSTRUCTOR_OWNER =
|
|
/^src\/(?:frida\/FridaInstrumentationManager|cutter\/CutterBridge(?:Client|Service)|gdb\/GdbSessionManager|rizin\/RizinDebugSessionManager)\.(?:js|ts)$/u;
|
|
const repositoryPath = (root, path) =>
|
|
relative(root, path).replaceAll("\\", "/");
|
|
|
|
const packageImports = new Map();
|
|
|
|
/** Read the package map once per repository root. */
|
|
const readPackageImports = (root) => {
|
|
if (!packageImports.has(root)) {
|
|
try {
|
|
const manifest = JSON.parse(
|
|
readFileSync(resolve(root, "package.json"), "utf8"),
|
|
);
|
|
packageImports.set(root, manifest.imports ?? {});
|
|
} catch (cause) {
|
|
throw new Error(
|
|
`Cannot read package imports from ${resolve(root, "package.json")}: ${cause.message}`,
|
|
{ cause },
|
|
);
|
|
}
|
|
}
|
|
return packageImports.get(root);
|
|
};
|
|
|
|
/**
|
|
* The own source file a package-map specifier names.
|
|
*
|
|
* The map points at built output under `dist/`, so the same ownership rule is
|
|
* applied to the source file that produces it. Without this the gate silently
|
|
* accepts every `#`-prefixed import, which is the spelling the package map exists
|
|
* to encourage.
|
|
*/
|
|
const packageMapTarget = (root, specifier) => {
|
|
const target = readPackageImports(root)[specifier];
|
|
if (typeof target !== "string")
|
|
throw new Error(
|
|
`Cannot resolve package import ${JSON.stringify(specifier)}: this boundary check supports exact string mappings only; use a relative source import for conditional, array, pattern, or undeclared mappings.`,
|
|
);
|
|
const source = target.startsWith("./")
|
|
? repositoryPath(
|
|
root,
|
|
fileURLToPath(
|
|
new URL(target, pathToFileURL(resolve(root, "package.json"))),
|
|
),
|
|
)
|
|
.replace(/^dist\//u, "src/")
|
|
.replace(/\.(?:js|mjs|cjs)$/u, ".ts")
|
|
: undefined;
|
|
if (source === undefined || !source.startsWith("src/"))
|
|
throw new Error(
|
|
`Cannot resolve package import ${JSON.stringify(specifier)} to a source owner from ${JSON.stringify(target)}; use a relative source import for this boundary check.`,
|
|
);
|
|
return resolve(root, source);
|
|
};
|
|
|
|
const sourceTarget = (root, file, specifier) => {
|
|
if (specifier.startsWith("file:")) return fileURLToPath(specifier);
|
|
if (specifier.startsWith("#")) return packageMapTarget(root, specifier);
|
|
if (isAbsolute(specifier)) return resolve(specifier);
|
|
return specifier.startsWith(".")
|
|
? fileURLToPath(new URL(specifier, pathToFileURL(file)))
|
|
: undefined;
|
|
};
|
|
|
|
const failedBoundary = (file, target, typeOnly) => {
|
|
if (!file.startsWith("src/") || !target.startsWith("src/")) return undefined;
|
|
const layer = file.split("/")[1];
|
|
const targetLayer = target.split("/")[1];
|
|
if (
|
|
file.startsWith("src/process/capture/") &&
|
|
(["application", "composition", "server", "cli", "main"].includes(
|
|
targetLayer,
|
|
) ||
|
|
/^src\/(?:cli|main)\./u.test(target))
|
|
)
|
|
return "process-capture";
|
|
if (
|
|
file.startsWith("src/application/javascript/") &&
|
|
/^src\/artifacts\/(?:Asar|Directory)ArtifactReader\.(?:js|ts)$/u.test(
|
|
target,
|
|
)
|
|
)
|
|
return "provider-construction";
|
|
if (
|
|
(file.startsWith("src/artifacts/javascript/") ||
|
|
file.startsWith("src/artifacts/apple/") ||
|
|
file.startsWith("src/artifacts/inventory/") ||
|
|
file.startsWith("src/artifacts/extraction/") ||
|
|
/^src\/artifacts\/ArtifactHash\.(?:js|ts)$/u.test(file)) &&
|
|
(["application", "composition", "server", "cli", "main"].includes(
|
|
targetLayer,
|
|
) ||
|
|
/^src\/(?:cli|main)\./u.test(target))
|
|
)
|
|
return "artifact-acquisition";
|
|
if (PURE_LAYERS.has(layer)) {
|
|
if (
|
|
(/^src\/[^/]+\//u.test(target) && !PURE_LAYERS.has(targetLayer)) ||
|
|
/^src\/(?:cli|main)\./u.test(target)
|
|
)
|
|
return "pure-layer";
|
|
}
|
|
if (layer === "application" && targetLayer === "composition")
|
|
return "application-composition";
|
|
if (
|
|
(layer === "application" || layer === "server") &&
|
|
!typeOnly &&
|
|
CENTRALIZED_CONSTRUCTOR_OWNER.test(target)
|
|
)
|
|
return "provider-construction";
|
|
if (
|
|
(layer === "application" || layer === "server") &&
|
|
(MIGRATED_PROVIDER_ROOTS.has(targetLayer) ||
|
|
/^src\/(?:artifacts\/ArtifactProvider|dotnet\/ManagedStaticProvider|native\/NativeMacOSProvider)\.[^/]+$/u.test(
|
|
target,
|
|
) ||
|
|
/^src\/browser\/[^/]*Provider\.[^/]+$/u.test(target))
|
|
)
|
|
return "provider-construction";
|
|
return undefined;
|
|
};
|
|
|
|
/** Check resolved source ownership, including type imports and reexports. */
|
|
export const inspectModuleBoundaries = (file, source, root) => {
|
|
const absoluteFile = resolve(root, file);
|
|
const importer = repositoryPath(root, absoluteFile);
|
|
const violations = [];
|
|
for (const { specifier, line, typeOnly } of readImportSpecifiers(source)) {
|
|
const resolved = sourceTarget(root, absoluteFile, specifier);
|
|
if (resolved === undefined) continue;
|
|
const target = repositoryPath(root, resolved);
|
|
const boundary = failedBoundary(importer, target, typeOnly);
|
|
if (boundary !== undefined)
|
|
violations.push({ file: importer, line, specifier, target, boundary });
|
|
}
|
|
return violations;
|
|
};
|