Files
WRNexusJS/scripts/audit-package-kits.mjs
T
Clintchiz 3c6b659f36
Quality / quality (ubuntu-latest) (push) Failing after 13m28s
Quality / quality (windows-latest) (push) Canceled after 0s
release: WRNexusJS 0.8.1
2026-08-03 01:34:56 +05:30

245 lines
9.6 KiB
JavaScript

import console from "node:console";
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
import { createRequire } from "node:module";
import { dirname, join, relative, resolve } from "node:path";
import process from "node:process";
import { fileURLToPath } from "node:url";
const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const releaseVersion = JSON.parse(readFileSync(join(root, "package.json"), "utf8")).version;
const require = createRequire(import.meta.url);
const componentPackages = new Set([
"auth",
"captcha",
"i18n",
"image",
"realtime",
"ui",
"uploader",
"validation",
]);
const requestedHelperFiles = {
auth: "src/helpers.ts",
captcha: "src/helpers.ts",
db: "src/helpers.ts",
encryption: "src/http.ts",
i18n: "src/index.ts",
image: "src/index.ts",
jwt: "src/helpers.ts",
realtime: "src/messages.ts",
uploader: "src/helpers.ts",
validation: "src/helpers.ts",
};
function writeIfChanged(path, source) {
if (existsSync(path) && readFileSync(path, "utf8") === source) return false;
writeFileSync(path, source, "utf8");
return true;
}
function walkFiles(directory, pattern) {
const files = [];
if (!existsSync(directory)) return files;
const visit = (current) => {
for (const entry of readdirSync(current, { withFileTypes: true })) {
const path = join(current, entry.name);
if (entry.isDirectory()) visit(path);
else if (entry.isFile() && pattern.test(entry.name)) files.push(path);
}
};
visit(directory);
return files.sort();
}
function helperIsPublic(entrySource, helperFile, main) {
if (!helperFile || helperFile === main) return true;
const relativeHelper = `./${relative(dirname(main), helperFile).replace(/\\/g, "/").replace(/\.ts$/, ".ts")}`;
const withoutExtension = relativeHelper.replace(/\.ts$/, "");
return entrySource.includes(relativeHelper) || entrySource.includes(withoutExtension);
}
const compiler = require(join(root, "editors", "vscode", "src", "compiler.cjs"));
const uiCatalog = JSON.parse(
readFileSync(join(root, "packages", "ui", "component-catalog.json"), "utf8"),
);
const uiComponentNames = new Set(uiCatalog.components.map((entry) => entry.name));
const packageDirectories = readdirSync(join(root, "packages"), { withFileTypes: true })
.filter(
(entry) =>
entry.isDirectory() && existsSync(join(root, "packages", entry.name, "package.json")),
)
.map((entry) => entry.name)
.sort();
const packages = [];
const failures = [];
let componentCount = 0;
let helperPackageCount = 0;
for (const directory of packageDirectories) {
const packageRoot = join(root, "packages", directory);
const manifest = JSON.parse(readFileSync(join(packageRoot, "package.json"), "utf8"));
const main = String(manifest.main ?? manifest.exports?.["."] ?? "src/index.ts").replace(
/^\.\//,
"",
);
const entry = join(packageRoot, main);
const entrySource = existsSync(entry) ? readFileSync(entry, "utf8") : "";
const componentsDir = join(packageRoot, "components");
const components = existsSync(componentsDir)
? readdirSync(componentsDir)
.filter((name) => name.endsWith(".wrn"))
.sort()
: [];
const tests = walkFiles(join(packageRoot, "test"), /\.(?:test|spec)\.[cm]?[jt]s$/).map((path) =>
relative(join(packageRoot, "test"), path).replace(/\\/g, "/"),
);
const sourceFiles = walkFiles(join(packageRoot, "src"), /\.[cm]?ts$/);
const allSource = sourceFiles.map((path) => readFileSync(path, "utf8")).join("\n");
const exported = [
...allSource.matchAll(
/\bexport\s+(?:declare\s+)?(?:async\s+)?(?:function|class|const|let|var|interface|type|enum)\s+([A-Za-z_$][\w$]*)/g,
),
].map((match) => match[1]);
const helperFile = requestedHelperFiles[directory];
const hasHelperKit = helperFile
? existsSync(join(packageRoot, helperFile))
: exported.length > 0 ||
/export\s+\{/.test(allSource) ||
/export\s+\*/.test(allSource) ||
directory === "cli";
const publicHelper = helperIsPublic(entrySource, helperFile, main);
if (hasHelperKit) helperPackageCount++;
componentCount += components.length;
if (manifest.version !== releaseVersion)
failures.push(`${manifest.name}: version is ${manifest.version}`);
if (!existsSync(entry)) failures.push(`${manifest.name}: missing public entry ${main}`);
if (!existsSync(join(packageRoot, "README.md")))
failures.push(`${manifest.name}: missing README.md`);
if (!tests.length) failures.push(`${manifest.name}: missing package test`);
if (!hasHelperKit) failures.push(`${manifest.name}: no public helper/API surface found`);
if (!publicHelper) failures.push(`${manifest.name}: ${helperFile} is not exported by ${main}`);
const usedUiComponents = new Set();
const componentFailures = [];
for (const component of components) {
const file = join(componentsDir, component);
const source = readFileSync(file, "utf8");
for (const match of source.matchAll(/<([A-Z][A-Za-z0-9]*)\b/g)) {
if (uiComponentNames.has(match[1])) usedUiComponents.add(match[1]);
}
try {
compiler.compileWireFile(source);
} catch (error) {
componentFailures.push(
`${component}: ${error instanceof Error ? error.message : String(error)}`,
);
}
}
if (componentPackages.has(directory)) {
if (!components.length)
failures.push(`${manifest.name}: component package has no .wrn components`);
if (directory !== "ui" && manifest.dependencies?.["@wrnexus/ui"] !== "workspace:*") {
failures.push(`${manifest.name}: component package must depend on @wrnexus/ui`);
}
if (directory !== "ui" && usedUiComponents.size === 0) {
failures.push(`${manifest.name}: package blocks do not compose any @wrnexus/ui component`);
}
if (directory !== "ui" && !manifest.exports?.["./plugin"]) {
failures.push(`${manifest.name}: component package must export ./plugin`);
}
}
for (const issue of componentFailures) failures.push(`${manifest.name}: ${issue}`);
packages.push({
name: manifest.name,
directory,
version: manifest.version,
kind: componentPackages.has(directory) ? "component-kit" : "helper-kit",
entry: relative(root, entry).replace(/\\/g, "/"),
helperFile: helperFile ?? null,
helperIsPublic: publicHelper,
publicExportCount: exported.length,
components,
uiComponents: [...usedUiComponents].sort(),
tests,
readme: existsSync(join(packageRoot, "README.md")),
usesUi: directory === "ui" || manifest.dependencies?.["@wrnexus/ui"] === "workspace:*",
});
}
const authSource = readdirSync(join(root, "packages", "auth", "components"))
.filter((name) => name.endsWith(".wrn"))
.map((name) => readFileSync(join(root, "packages", "auth", "components", name), "utf8"))
.join("\n");
if (/<(?:button|select|textarea)\b/.test(authSource)) {
failures.push("@wrnexus/auth: visible native controls must use @wrnexus/ui components");
}
for (const match of authSource.matchAll(/<input\b[\s\S]*?>/g)) {
if (!/\btype=(?:"hidden"|'hidden')/i.test(match[0])) {
failures.push(
"@wrnexus/auth: visible native input must use @wrnexus/ui Input/PinInput components",
);
break;
}
}
const report = {
version: releaseVersion,
summary: {
packageCount: packages.length,
helperPackageCount,
componentPackageCount: packages.filter((entry) => entry.kind === "component-kit").length,
componentCount,
failures: failures.length,
},
packages,
failures,
};
const markdown = [
`# WRNexusJS ${releaseVersion} Package Kits`,
"",
"Every framework package has a public API/helper surface, package tests, and documentation. Packages that directly render developer-facing UI additionally provide package-owned `.wrn` blocks composed from `@wrnexus/ui`.",
"",
`- Packages: ${report.summary.packageCount}`,
`- Helper/API kits: ${report.summary.helperPackageCount}`,
`- Component kits: ${report.summary.componentPackageCount}`,
`- Package-owned components: ${report.summary.componentCount}`,
"",
"| Package | Kit | Helper/API entry | Components | UI composition | Tests |",
"|---|---|---|---:|---|---:|",
...packages.map(
(entry) =>
`| \`${entry.name}\` | ${entry.kind} | ${entry.helperFile ? `\`${entry.helperFile}\`` : `\`${entry.entry}\``} | ${entry.components.length} | ${entry.uiComponents.length ? entry.uiComponents.map((name) => `\`${name}\``).join(", ") : "—"} | ${entry.tests.length} |`,
),
"",
"## Component-kit rule",
"",
"Infrastructure-only packages remain helper/API kits and do not pull browser UI into server code. UI-facing packages own complete blocks and register them through their package plugin.",
"",
"## Security boundary for encrypted HTTP bodies",
"",
"`@wrnexus/encryption` encrypted HTTP envelopes add application-layer confidentiality, replay checks, and method/path/request binding. They do not replace HTTPS and cannot hide data from a browser user who receives the decryption key. Use them for service-to-service, native/mobile, controlled agents, or field-level protection with server-managed keys.",
"",
failures.length
? "## Failures\n\n" + failures.map((failure) => `- ${failure}`).join("\n") + "\n"
: "## Audit result\n\nAll package-kit requirements pass.\n",
].join("\n");
if (process.argv.includes("--write")) {
const reportDir = join(root, ".wrnexus", "reports");
mkdirSync(reportDir, { recursive: true });
writeIfChanged(
join(reportDir, "PACKAGE-KITS-0.8.0.json"),
`${JSON.stringify(report, null, 2)}\n`,
);
writeIfChanged(join(reportDir, "PACKAGE-KITS-0.8.0.md"), `${markdown}\n`);
}
console.log(JSON.stringify(report.summary, null, 2));
for (const failure of failures) console.error(`FAIL ${failure}`);
if (failures.length) process.exitCode = 1;