release: WRNexusJS 0.8.0
This commit is contained in:
@@ -0,0 +1,243 @@
|
||||
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 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 !== "0.8.0")
|
||||
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: "0.8.0",
|
||||
summary: {
|
||||
packageCount: packages.length,
|
||||
helperPackageCount,
|
||||
componentPackageCount: packages.filter((entry) => entry.kind === "component-kit").length,
|
||||
componentCount,
|
||||
failures: failures.length,
|
||||
},
|
||||
packages,
|
||||
failures,
|
||||
};
|
||||
|
||||
const markdown = [
|
||||
"# WRNexusJS 0.8.0 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;
|
||||
@@ -9,6 +9,7 @@ import { secureJsonStringify } from "../packages/security/src/serialization.ts";
|
||||
import { parse } from "../packages/syntax/src/index.ts";
|
||||
|
||||
const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const frameworkVersion = JSON.parse(readFileSync(join(root, "package.json"), "utf8")).version;
|
||||
const componentSource = readFileSync(
|
||||
join(root, "packages", "ui", "components", "button.wrn"),
|
||||
"utf8",
|
||||
@@ -55,13 +56,16 @@ for (const benchmark of benchmarks) {
|
||||
|
||||
const report = {
|
||||
schemaVersion: 1,
|
||||
frameworkVersion: "0.7.0",
|
||||
frameworkVersion,
|
||||
environment: { runtime: process.version, platform: process.platform, arch: process.arch },
|
||||
benchmarks: benchmarks.map(({ result, budget }) => ({ result, budget })),
|
||||
};
|
||||
const reportDir = join(root, ".wrnexus", "reports");
|
||||
mkdirSync(reportDir, { recursive: true });
|
||||
writeFileSync(join(reportDir, "benchmark-0.7.0.json"), `${JSON.stringify(report, null, 2)}\n`);
|
||||
writeFileSync(
|
||||
join(reportDir, `benchmark-${frameworkVersion}.json`),
|
||||
`${JSON.stringify(report, null, 2)}\n`,
|
||||
);
|
||||
console.log(
|
||||
JSON.stringify(
|
||||
report.benchmarks.map(({ result }) => ({
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
||||
import { existsSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
||||
import { createRequire } from "node:module";
|
||||
import { dirname, join, relative, resolve, sep } from "node:path";
|
||||
import process from "node:process";
|
||||
@@ -128,7 +128,18 @@ module.exports = __load("packages/compiler/src/index.ts");
|
||||
`;
|
||||
|
||||
const destination = join(root, "editors", "vscode", "src", "compiler.cjs");
|
||||
writeFileSync(destination, output, "utf8");
|
||||
process.stdout.write(
|
||||
`Built ${relative(root, destination)} from ${sourceFiles.length} TypeScript modules.\n`,
|
||||
);
|
||||
if (process.argv.includes("--check")) {
|
||||
if (!existsSync(destination) || readFileSync(destination, "utf8") !== output) {
|
||||
throw new Error(
|
||||
`${relative(root, destination)} is stale. Run bun run --cwd editors/vscode build and commit the result.`,
|
||||
);
|
||||
}
|
||||
process.stdout.write(
|
||||
`Verified ${relative(root, destination)} matches ${sourceFiles.length} TypeScript modules.\n`,
|
||||
);
|
||||
} else {
|
||||
writeFileSync(destination, output, "utf8");
|
||||
process.stdout.write(
|
||||
`Built ${relative(root, destination)} from ${sourceFiles.length} TypeScript modules.\n`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import console from "node:console";
|
||||
import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join, relative, resolve } from "node:path";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import process from "node:process";
|
||||
|
||||
const root = resolve(import.meta.dirname, "..");
|
||||
const source = join(root, "editors", "vscode", "src", "extension.js");
|
||||
const destination = join(root, "editors", "vscode", "src", "extension.bundle.cjs");
|
||||
const check = process.argv.includes("--check");
|
||||
const temporary = check ? mkdtempSync(join(tmpdir(), "wrnexus-editor-extension-")) : null;
|
||||
const output = temporary ? join(temporary, "extension.bundle.cjs") : destination;
|
||||
try {
|
||||
const built = spawnSync(
|
||||
process.env.WRNEXUS_BUN_BINARY || "bun",
|
||||
["build", source, "--target=node", "--format=cjs", "--external=vscode", `--outfile=${output}`],
|
||||
{ cwd: root, encoding: "utf8" },
|
||||
);
|
||||
if (built.status !== 0)
|
||||
throw new Error(built.stderr || built.stdout || "extension bundle failed");
|
||||
if (check) {
|
||||
if (!existsSync(destination) || readFileSync(destination).compare(readFileSync(output)) !== 0)
|
||||
throw new Error(
|
||||
`${relative(root, destination)} is stale. Run bun run --cwd editors/vscode build.`,
|
||||
);
|
||||
console.log(`Verified ${relative(root, destination)} matches extension sources.`);
|
||||
} else console.log(`Built ${relative(root, destination)}.`);
|
||||
} finally {
|
||||
if (temporary) rmSync(temporary, { recursive: true, force: true });
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import console from "node:console";
|
||||
import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join, relative, resolve } from "node:path";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import process from "node:process";
|
||||
|
||||
const root = resolve(import.meta.dirname, "..");
|
||||
const source = join(root, "packages", "language-server", "src", "server.ts");
|
||||
const destination = join(root, "editors", "vscode", "src", "language-server.cjs");
|
||||
const check = process.argv.includes("--check");
|
||||
const temporary = check ? mkdtempSync(join(tmpdir(), "wrnexus-editor-lsp-")) : null;
|
||||
const output = temporary ? join(temporary, "language-server.cjs") : destination;
|
||||
try {
|
||||
const built = spawnSync(
|
||||
process.env.WRNEXUS_BUN_BINARY || "bun",
|
||||
["build", source, "--target=node", "--format=cjs", `--outfile=${output}`],
|
||||
{ cwd: root, encoding: "utf8" },
|
||||
);
|
||||
if (built.status !== 0) throw new Error(built.stderr || built.stdout || "LSP bundle failed");
|
||||
if (check) {
|
||||
if (!existsSync(destination) || readFileSync(destination).compare(readFileSync(output)) !== 0)
|
||||
throw new Error(
|
||||
`${relative(root, destination)} is stale. Run bun run --cwd editors/vscode build.`,
|
||||
);
|
||||
console.log(`Verified ${relative(root, destination)} matches the language server sources.`);
|
||||
} else console.log(`Built ${relative(root, destination)}.`);
|
||||
} finally {
|
||||
if (temporary) rmSync(temporary, { recursive: true, force: true });
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { readdirSync, readFileSync, statSync } from "node:fs";
|
||||
import { join, relative, resolve } from "node:path";
|
||||
import { diagnose, formatWrn } from "../packages/syntax/src/index.ts";
|
||||
|
||||
const root = resolve(import.meta.dirname, "..");
|
||||
const examples = join(root, "examples");
|
||||
function walk(directory: string): string[] {
|
||||
return readdirSync(directory).flatMap((name) => {
|
||||
if (["node_modules", ".wrnexus", "dist", "build"].includes(name)) return [];
|
||||
const path = join(directory, name);
|
||||
return statSync(path).isDirectory() ? walk(path) : path.endsWith(".wrn") ? [path] : [];
|
||||
});
|
||||
}
|
||||
|
||||
const failures: string[] = [];
|
||||
const files = walk(examples);
|
||||
for (const file of files) {
|
||||
const source = readFileSync(file, "utf8");
|
||||
for (const diagnostic of diagnose(source, { file, accessibility: true })) {
|
||||
if (diagnostic.severity !== "info")
|
||||
failures.push(`${relative(root, file)}: ${diagnostic.code} ${diagnostic.message}`);
|
||||
}
|
||||
const formatted = formatWrn(source);
|
||||
if (formatWrn(formatted) !== formatted)
|
||||
failures.push(`${relative(root, file)}: formatter is not idempotent`);
|
||||
}
|
||||
|
||||
if (failures.length) throw new Error(`Example page validation failed:\n${failures.join("\n")}`);
|
||||
console.log(
|
||||
`Validated ${files.length} example .wrn pages with compiler, accessibility, and formatter checks.`,
|
||||
);
|
||||
@@ -0,0 +1,82 @@
|
||||
import { existsSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
||||
import console from "node:console";
|
||||
import { dirname, join, relative, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import process from "node:process";
|
||||
import ts from "typescript";
|
||||
|
||||
const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const baselinePath = join(root, "docs", "public-api-0.8.json");
|
||||
|
||||
function flattenTargets(value) {
|
||||
if (typeof value === "string") return [value];
|
||||
if (Array.isArray(value)) return value.flatMap(flattenTargets);
|
||||
if (value && typeof value === "object") return Object.values(value).flatMap(flattenTargets);
|
||||
return [];
|
||||
}
|
||||
|
||||
const entries = [];
|
||||
for (const directory of readdirSync(join(root, "packages"), { withFileTypes: true })) {
|
||||
if (!directory.isDirectory()) continue;
|
||||
const packageRoot = join(root, "packages", directory.name);
|
||||
const manifestPath = join(packageRoot, "package.json");
|
||||
if (!existsSync(manifestPath)) continue;
|
||||
const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
||||
const exportsMap =
|
||||
manifest.exports && typeof manifest.exports === "object"
|
||||
? manifest.exports
|
||||
: { ".": manifest.main ?? "./src/index.ts" };
|
||||
for (const [subpath, value] of Object.entries(exportsMap)) {
|
||||
const target = flattenTargets(value).find((candidate) => /\.[cm]?[jt]sx?$/.test(candidate));
|
||||
if (!target) continue;
|
||||
const absolute = resolve(packageRoot, target);
|
||||
if (existsSync(absolute)) entries.push({ package: manifest.name, subpath, absolute });
|
||||
}
|
||||
}
|
||||
|
||||
const config = ts.parseJsonConfigFileContent(
|
||||
ts.readConfigFile(join(root, "tsconfig.json"), ts.sys.readFile).config,
|
||||
ts.sys,
|
||||
root,
|
||||
);
|
||||
const program = ts.createProgram(
|
||||
[...new Set(entries.map(({ absolute }) => absolute))],
|
||||
config.options,
|
||||
);
|
||||
const checker = program.getTypeChecker();
|
||||
const packages = {};
|
||||
for (const entry of entries.sort(
|
||||
(left, right) =>
|
||||
left.package.localeCompare(right.package) || left.subpath.localeCompare(right.subpath),
|
||||
)) {
|
||||
const source = program.getSourceFile(entry.absolute);
|
||||
if (!source)
|
||||
throw new Error(`Public API entry was not loaded: ${relative(root, entry.absolute)}`);
|
||||
const symbol = checker.getSymbolAtLocation(source);
|
||||
const names = symbol
|
||||
? checker
|
||||
.getExportsOfModule(symbol)
|
||||
.map((item) => item.getName())
|
||||
.filter((name) => name !== "__esModule")
|
||||
.sort()
|
||||
: [];
|
||||
(packages[entry.package] ??= {})[entry.subpath] = names;
|
||||
}
|
||||
|
||||
const report = { schemaVersion: 1, releaseLine: "0.8", packages };
|
||||
const output = `${JSON.stringify(report, null, 2)}\n`;
|
||||
if (process.argv.includes("--write")) {
|
||||
writeFileSync(baselinePath, output, "utf8");
|
||||
console.log(`Wrote ${relative(root, baselinePath)}`);
|
||||
} else {
|
||||
if (!existsSync(baselinePath)) {
|
||||
throw new Error("Public API baseline is missing. Run `bun run generate:public-api`.");
|
||||
}
|
||||
const expected = readFileSync(baselinePath, "utf8");
|
||||
if (expected !== output) {
|
||||
throw new Error(
|
||||
"Public API changed. Review compatibility, then run `bun run generate:public-api` intentionally.",
|
||||
);
|
||||
}
|
||||
console.log(`Public API baseline matches ${Object.keys(packages).length} packages.`);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import console from "node:console";
|
||||
import { resolve } from "node:path";
|
||||
import process from "node:process";
|
||||
|
||||
const root = resolve(import.meta.dirname, "..");
|
||||
const mapPath = resolve(root, "docs/SECURITY-ASVS-5.md");
|
||||
const source = readFileSync(mapPath, "utf8");
|
||||
const requiredIds = [
|
||||
"v5.0.0-1.1.2",
|
||||
"v5.0.0-1.2.1",
|
||||
"v5.0.0-1.2.3",
|
||||
"v5.0.0-1.2.4",
|
||||
"v5.0.0-1.3.6",
|
||||
"v5.0.0-3.3.1",
|
||||
"v5.0.0-3.3.2",
|
||||
"v5.0.0-3.3.4",
|
||||
"v5.0.0-3.4.1",
|
||||
"v5.0.0-3.4.2",
|
||||
"v5.0.0-3.4.6",
|
||||
"v5.0.0-3.5.1",
|
||||
"v5.0.0-3.7.2",
|
||||
"v5.0.0-16.2.5",
|
||||
"v5.0.0-16.3.1",
|
||||
"v5.0.0-16.3.2",
|
||||
];
|
||||
|
||||
const missingIds = requiredIds.filter((id) => !source.includes(`\`${id}\``));
|
||||
const evidence = [...source.matchAll(/`((?:packages|scripts)\/[\w./-]+)`/g)].map(
|
||||
(match) => match[1],
|
||||
);
|
||||
const missingEvidence = evidence.filter((path) => !existsSync(resolve(root, path)));
|
||||
if (missingIds.length || missingEvidence.length) {
|
||||
if (missingIds.length) console.error(`Missing ASVS mappings: ${missingIds.join(", ")}`);
|
||||
if (missingEvidence.length)
|
||||
console.error(`Missing security evidence: ${missingEvidence.join(", ")}`);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(
|
||||
`Security map verified: ${requiredIds.length} ASVS requirements, ${evidence.length} evidence files.`,
|
||||
);
|
||||
@@ -0,0 +1,34 @@
|
||||
import console from "node:console";
|
||||
import { createHash } from "node:crypto";
|
||||
import { existsSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
||||
import { dirname, join, relative, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import process from "node:process";
|
||||
|
||||
const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const uiRoot = join(root, "packages", "ui");
|
||||
const baseline = join(root, "docs", "ui-visual-contract-0.8.json");
|
||||
const files = [
|
||||
join(uiRoot, "ui.css"),
|
||||
...readdirSync(join(uiRoot, "components"))
|
||||
.filter((file) => file.endsWith(".wrn"))
|
||||
.map((file) => join(uiRoot, "components", file)),
|
||||
].sort();
|
||||
const artifacts = Object.fromEntries(
|
||||
files.map((file) => [
|
||||
relative(root, file).replace(/\\/g, "/"),
|
||||
createHash("sha256").update(readFileSync(file)).digest("hex"),
|
||||
]),
|
||||
);
|
||||
const output = `${JSON.stringify({ schemaVersion: 1, releaseLine: "0.8", artifacts }, null, 2)}\n`;
|
||||
if (process.argv.includes("--write")) {
|
||||
writeFileSync(baseline, output, "utf8");
|
||||
console.log(`Wrote ${relative(root, baseline)}`);
|
||||
} else {
|
||||
if (!existsSync(baseline) || readFileSync(baseline, "utf8") !== output) {
|
||||
throw new Error(
|
||||
"UI visual contract changed. Review rendered components, then run `bun run generate:ui-visual`.",
|
||||
);
|
||||
}
|
||||
console.log(`UI visual contract matches ${files.length} CSS/component artifacts.`);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { afterAll } from "bun:test";
|
||||
|
||||
const originalWarn = console.warn;
|
||||
const warnings: string[] = [];
|
||||
const allowed = (process.env.WRNEXUS_TEST_WARNING_ALLOWLIST ?? "")
|
||||
.split(",")
|
||||
.map((value) => value.trim())
|
||||
.filter(Boolean);
|
||||
|
||||
console.warn = (...values: unknown[]) => {
|
||||
const message = values
|
||||
.map((value) => (typeof value === "string" ? value : JSON.stringify(value)))
|
||||
.join(" ");
|
||||
if (!allowed.some((pattern) => message.includes(pattern))) warnings.push(message);
|
||||
originalWarn(...values);
|
||||
};
|
||||
|
||||
afterAll(() => {
|
||||
console.warn = originalWarn;
|
||||
if (warnings.length) {
|
||||
throw new Error(
|
||||
`WRN-TEST-UNEXPECTED-WARNING: ${warnings.length} warning(s) emitted:\n${warnings
|
||||
.map((warning) => `- ${warning}`)
|
||||
.join("\n")}`,
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
import console from "node:console";
|
||||
import { createHash } from "node:crypto";
|
||||
import { readFileSync, readdirSync, writeFileSync } from "node:fs";
|
||||
import { mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
||||
import { dirname, join, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
@@ -107,6 +107,8 @@ const document = {
|
||||
},
|
||||
components,
|
||||
};
|
||||
const file = join(root, `SBOM-${version}.cdx.json`);
|
||||
const reportDir = join(root, ".wrnexus", "reports");
|
||||
mkdirSync(reportDir, { recursive: true });
|
||||
const file = join(reportDir, `SBOM-${version}.cdx.json`);
|
||||
writeFileSync(file, `${JSON.stringify(document, null, 2)}\n`);
|
||||
console.log(`Generated ${file} with ${components.length} workspace and transitive components.`);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import { readdirSync, readFileSync } from "node:fs";
|
||||
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
||||
import { basename, join, relative } from "node:path";
|
||||
|
||||
export interface StagedFileIntegrity {
|
||||
@@ -17,7 +17,7 @@ export interface StagedPackageIntegrity {
|
||||
const forbiddenStageName =
|
||||
/^(?:\.env(?:\..*)?|focus-shims\.d\.ts|tsconfig\.focus\.json|id_rsa|id_ed25519|.*\.(?:pem|key|p12|pfx))$/i;
|
||||
const highConfidenceSecret =
|
||||
/-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----|\bAKIA[0-9A-Z]{16}\b|\bnpm_[A-Za-z0-9]{30,}\b|\bgh[pousr]_[A-Za-z0-9]{30,}\b/;
|
||||
/-----BEGIN ((?:RSA |EC |OPENSSH )?PRIVATE KEY)-----\r?\n[A-Za-z0-9+/=\r\n]{80,}\r?\n-----END \1-----|\bAKIA[0-9A-Z]{16}\b|\bnpm_[A-Za-z0-9]{30,}\b|\bgh[pousr]_[A-Za-z0-9]{30,}\b/;
|
||||
|
||||
function listFiles(root: string): string[] {
|
||||
const files: string[] = [];
|
||||
@@ -32,11 +32,73 @@ function listFiles(root: string): string[] {
|
||||
});
|
||||
}
|
||||
|
||||
function manifestTargets(value: unknown): string[] {
|
||||
if (typeof value === "string") return [value];
|
||||
if (!value || typeof value !== "object") return [];
|
||||
return Object.values(value).flatMap(manifestTargets);
|
||||
}
|
||||
|
||||
export function validateStagedManifest(stage: string, manifest: Record<string, unknown>): void {
|
||||
const name = String(manifest.name ?? "unknown package");
|
||||
const requiredStrings = ["name", "version", "description", "license", "main", "types"];
|
||||
for (const field of requiredStrings) {
|
||||
if (typeof manifest[field] !== "string" || !String(manifest[field]).trim()) {
|
||||
throw new Error(`${name} staged manifest requires a non-empty ${field}.`);
|
||||
}
|
||||
}
|
||||
if (!/^@wrnexus\/[a-z0-9-]+$/.test(name)) throw new Error(`${name} has an invalid package name.`);
|
||||
if (manifest.private !== undefined) throw new Error(`${name} staged manifest must omit private.`);
|
||||
if ((manifest.publishConfig as any)?.registry !== "https://registry.npmjs.org/") {
|
||||
throw new Error(`${name} staged manifest has an unexpected registry.`);
|
||||
}
|
||||
if (!["restricted", "public"].includes(String((manifest.publishConfig as any)?.access))) {
|
||||
throw new Error(`${name} staged manifest requires explicit public or restricted access.`);
|
||||
}
|
||||
if ((manifest.engines as any)?.bun !== ">=1.3.0") {
|
||||
throw new Error(`${name} staged manifest requires the supported Bun engine.`);
|
||||
}
|
||||
for (const field of ["repository", "homepage", "bugs", "keywords", "files", "exports"]) {
|
||||
if (manifest[field] === undefined)
|
||||
throw new Error(`${name} staged manifest requires ${field}.`);
|
||||
}
|
||||
for (const ranges of [
|
||||
manifest.dependencies,
|
||||
manifest.optionalDependencies,
|
||||
manifest.peerDependencies,
|
||||
]) {
|
||||
if (!ranges || typeof ranges !== "object") continue;
|
||||
for (const [dependency, range] of Object.entries(ranges)) {
|
||||
if (String(range).startsWith("workspace:")) {
|
||||
throw new Error(`${name} leaked workspace range for ${dependency}.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const targets = [
|
||||
...manifestTargets(manifest.main),
|
||||
...manifestTargets(manifest.module),
|
||||
...manifestTargets(manifest.types),
|
||||
...manifestTargets(manifest.bin),
|
||||
...manifestTargets(manifest.exports),
|
||||
];
|
||||
for (const target of new Set(targets)) {
|
||||
if (!target.startsWith("./")) throw new Error(`${name} has unsafe package target ${target}.`);
|
||||
const normalized = target.slice(2);
|
||||
const concrete = normalized.includes("*")
|
||||
? normalized.slice(0, normalized.indexOf("*"))
|
||||
: normalized;
|
||||
if (!existsSync(join(stage, concrete))) {
|
||||
throw new Error(`${name} package target does not exist: ${target}.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function validateAndHashStage(
|
||||
stage: string,
|
||||
manifest: Record<string, unknown>,
|
||||
): StagedPackageIntegrity {
|
||||
const name = String(manifest.name ?? "unknown package");
|
||||
validateStagedManifest(stage, manifest);
|
||||
const files = listFiles(stage);
|
||||
if (!files.length) throw new Error(`${name} staged no files.`);
|
||||
const integrity: StagedFileIntegrity[] = [];
|
||||
|
||||
@@ -39,6 +39,8 @@ const REGISTRY = "https://registry.npmjs.org/";
|
||||
// npm org to exist and be on a paid plan (private scoped packages need Teams).
|
||||
const ACCESS = process.env.WRNEXUS_NPM_ACCESS === "public" ? "public" : "restricted";
|
||||
const LICENSE = "MIT";
|
||||
const REPOSITORY = "https://git.workroot.in/WorkRoot/WRNexusJS.git";
|
||||
const HOMEPAGE = "https://wrnexusjs.dev";
|
||||
|
||||
/** Extra (non-code) files each package must ship, relative to the package root. */
|
||||
const ASSETS: Record<string, string[]> = {
|
||||
@@ -171,6 +173,15 @@ function publishManifest(
|
||||
type: "module",
|
||||
description: m.description ?? `${m.name} — part of the WrNexus framework.`,
|
||||
license: LICENSE,
|
||||
repository: {
|
||||
type: "git",
|
||||
url: REPOSITORY,
|
||||
directory: `packages/${String(m.name).replace("@wrnexus/", "")}`,
|
||||
},
|
||||
homepage: `${HOMEPAGE}/packages/${String(m.name).replace("@wrnexus/", "")}`,
|
||||
bugs: { url: `${REPOSITORY.replace(/\.git$/, "")}/issues` },
|
||||
keywords: ["wrnexus", "bun", "typescript", String(m.name).replace("@wrnexus/", "")],
|
||||
sideEffects: m.sideEffects ?? false,
|
||||
main: m.main ? js(m.main) : "./dist/index.js",
|
||||
module: m.main ? js(m.main) : "./dist/index.js",
|
||||
types: m.main ? dts(m.main) : "./dist/index.d.ts",
|
||||
@@ -215,7 +226,7 @@ function publishManifest(
|
||||
}
|
||||
}
|
||||
|
||||
const files = new Set(["dist"]);
|
||||
const files = new Set(["dist", "README.md"]);
|
||||
for (const a of packageAssets(m)) files.add(a);
|
||||
out.files = [...files];
|
||||
|
||||
|
||||
+3
-2
@@ -225,12 +225,13 @@ function prepare(all: PackageInfo[], version: string) {
|
||||
|
||||
run(process.execPath, ["run", "format"]);
|
||||
run(process.execPath, ["run", "check:workspace"]);
|
||||
run(process.execPath, ["run", "validate:0.7"]);
|
||||
run(process.execPath, ["run", "validate:0.8"]);
|
||||
run(process.execPath, ["run", "security:framework"]);
|
||||
run(process.execPath, ["run", "sbom"]);
|
||||
run(process.execPath, ["run", "benchmark:framework"]);
|
||||
run(process.execPath, ["run", "check"]);
|
||||
run(process.execPath, ["run", "scripts/publish-packages.ts"]);
|
||||
run(process.execPath, ["run", "test:staged-consumers"]);
|
||||
validateStaging(all, version);
|
||||
console.log(
|
||||
`\n✓ Release ${version} is prepared. Review changes, commit and push the framework repository, then run: bun run release:private\n`,
|
||||
@@ -302,7 +303,7 @@ function publish(all: PackageInfo[], version: string) {
|
||||
if (!existsSync(docsRoot)) throw new Error(`Docs repository not found: ${docsRoot}`);
|
||||
requireMigration(version);
|
||||
run(process.execPath, ["run", "check:workspace"]);
|
||||
run(process.execPath, ["run", "validate:0.7"]);
|
||||
run(process.execPath, ["run", "validate:0.8"]);
|
||||
run(process.execPath, ["run", "security:framework"]);
|
||||
requireCleanAndPushed(root, "Framework");
|
||||
requireCleanAndPushed(docsRoot, "Docs");
|
||||
|
||||
@@ -6,6 +6,7 @@ import { fileURLToPath } from "node:url";
|
||||
import process from "node:process";
|
||||
|
||||
const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const frameworkVersion = JSON.parse(readFileSync(join(root, "package.json"), "utf8")).version;
|
||||
const issues = [];
|
||||
const warnings = [];
|
||||
const checks = [];
|
||||
@@ -59,8 +60,8 @@ const manifests = trackedFiles
|
||||
.filter(({ value }) => value.name?.startsWith("@wrnexus/"));
|
||||
addCheck(
|
||||
"SEC-PACKAGE-VERSION",
|
||||
manifests.every(({ value }) => value.version === "0.7.0"),
|
||||
"Every @wrnexus package must use version 0.7.0.",
|
||||
manifests.every(({ value }) => value.version === frameworkVersion),
|
||||
`Every @wrnexus package must use version ${frameworkVersion}.`,
|
||||
);
|
||||
addCheck(
|
||||
"SEC-PRIVATE-PUBLISH",
|
||||
@@ -154,12 +155,19 @@ addCheck(
|
||||
dynamicCode.join(", ") || "No dynamic code execution in security foundation packages.",
|
||||
);
|
||||
|
||||
const runtimePath = join(root, "packages", "csr", "src", "reactive-runtime.ts");
|
||||
addCheck(
|
||||
"PERF-RUNTIME-SIZE",
|
||||
statSync(runtimePath).size < 250_000,
|
||||
`Reactive runtime source is ${statSync(runtimePath).size} bytes.`,
|
||||
);
|
||||
const runtimeBudgets = {
|
||||
"reactive-runtime.ts": 150_000,
|
||||
"nav-runtime.ts": 25_000,
|
||||
"realtime-runtime.ts": 15_000,
|
||||
};
|
||||
for (const [file, budget] of Object.entries(runtimeBudgets)) {
|
||||
const bytes = statSync(join(root, "packages", "csr", "src", file)).size;
|
||||
addCheck(
|
||||
`PERF-RUNTIME-SIZE-${file.replace(/-runtime\.ts$/, "").toUpperCase()}`,
|
||||
bytes <= budget,
|
||||
`${file} is ${bytes} bytes (budget ${budget}).`,
|
||||
);
|
||||
}
|
||||
addCheck(
|
||||
"PERF-ZERO-JS-ANALYSIS",
|
||||
existsSync(join(root, "packages", "compiler", "src", "analysis.ts")),
|
||||
@@ -185,19 +193,21 @@ addCheck(
|
||||
|
||||
const report = {
|
||||
schemaVersion: 2,
|
||||
frameworkVersion: "0.7.0",
|
||||
frameworkVersion,
|
||||
generatedAt: new Date().toISOString(),
|
||||
passed: issues.length === 0,
|
||||
checks,
|
||||
warnings,
|
||||
issues,
|
||||
};
|
||||
const reportDir = join(root, ".wrnexus", "reports");
|
||||
mkdirSync(reportDir, { recursive: true });
|
||||
writeFileSync(
|
||||
join(reportDir, "security-performance-0.7.0.json"),
|
||||
JSON.stringify(report, null, 2) + "\n",
|
||||
);
|
||||
if (process.argv.includes("--write")) {
|
||||
const reportDir = join(root, ".wrnexus", "reports");
|
||||
mkdirSync(reportDir, { recursive: true });
|
||||
writeFileSync(
|
||||
join(reportDir, `security-performance-${frameworkVersion}.json`),
|
||||
JSON.stringify(report, null, 2) + "\n",
|
||||
);
|
||||
}
|
||||
for (const check of checks)
|
||||
console.log(`${check.passed ? "ok" : "FAIL"} ${check.name} — ${check.detail}`);
|
||||
for (const warning of warnings) console.warn(`warn ${warning.code} — ${warning.detail}`);
|
||||
|
||||
@@ -5,7 +5,23 @@ import { join } from "node:path";
|
||||
import { validateAndHashStage } from "./lib/package-integrity.ts";
|
||||
|
||||
const root = mkdtempSync(join(tmpdir(), "wrnexus-stage-integrity-"));
|
||||
const manifest = { name: "@wrnexus/probe", version: "0.7.0" };
|
||||
const manifest = {
|
||||
name: "@wrnexus/probe",
|
||||
version: "0.8.0",
|
||||
description: "Integrity probe.",
|
||||
license: "MIT",
|
||||
main: "./dist/index.js",
|
||||
module: "./dist/index.js",
|
||||
types: "./dist/index.d.ts",
|
||||
exports: { ".": { import: "./dist/index.js", types: "./dist/index.d.ts" } },
|
||||
files: ["dist", "README.md"],
|
||||
repository: { type: "git", url: "https://example.test/repository.git" },
|
||||
homepage: "https://example.test",
|
||||
bugs: { url: "https://example.test/issues" },
|
||||
keywords: ["probe"],
|
||||
engines: { bun: ">=1.3.0" },
|
||||
publishConfig: { registry: "https://registry.npmjs.org/", access: "restricted" },
|
||||
};
|
||||
try {
|
||||
const safe = join(root, "safe");
|
||||
mkdirSync(join(safe, "dist"), { recursive: true });
|
||||
@@ -50,8 +66,12 @@ try {
|
||||
if (!rejected) throw new Error("Source map was not rejected.");
|
||||
|
||||
const leaked = join(root, "leaked-secret");
|
||||
mkdirSync(leaked);
|
||||
writeFileSync(join(leaked, "index.js"), 'export const key = "-----BEGIN PRIVATE KEY-----";\n');
|
||||
mkdirSync(join(leaked, "dist"), { recursive: true });
|
||||
writeFileSync(
|
||||
join(leaked, "dist", "index.js"),
|
||||
`-----BEGIN PRIVATE KEY-----\n${"A".repeat(64)}\n${"B".repeat(64)}\n-----END PRIVATE KEY-----\n`,
|
||||
);
|
||||
writeFileSync(join(leaked, "dist", "index.d.ts"), "export {};\n");
|
||||
rejected = false;
|
||||
try {
|
||||
validateAndHashStage(leaked, manifest);
|
||||
@@ -60,6 +80,15 @@ try {
|
||||
}
|
||||
if (!rejected) throw new Error("High-confidence secret content was not rejected.");
|
||||
|
||||
const keyHandlingSource = join(root, "key-handling-source");
|
||||
mkdirSync(join(keyHandlingSource, "dist"), { recursive: true });
|
||||
writeFileSync(
|
||||
join(keyHandlingSource, "dist", "index.js"),
|
||||
'export const header = "-----BEGIN PRIVATE KEY-----\\n" + generatedBytes;\n',
|
||||
);
|
||||
writeFileSync(join(keyHandlingSource, "dist", "index.d.ts"), "export {};\n");
|
||||
validateAndHashStage(keyHandlingSource, manifest);
|
||||
|
||||
console.log("Package staging integrity probes passed.");
|
||||
} finally {
|
||||
rmSync(root, { recursive: true, force: true });
|
||||
|
||||
@@ -0,0 +1,228 @@
|
||||
/* global FormData, Headers, console */
|
||||
|
||||
import assert from "node:assert/strict";
|
||||
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import {
|
||||
createKeyring,
|
||||
createMemoryReplayStore,
|
||||
encryptHttpBody,
|
||||
decryptHttpBody,
|
||||
createEncryptedRequest,
|
||||
encryptResponse,
|
||||
decryptEncryptedResponse,
|
||||
} from "../packages/encryption/src/index.ts";
|
||||
import {
|
||||
createAccessToken,
|
||||
createRefreshToken,
|
||||
verifyAccessToken,
|
||||
verifyRefreshToken,
|
||||
hasScopes,
|
||||
jwtCookie,
|
||||
createTokenPair,
|
||||
readJwtCookie,
|
||||
} from "../packages/jwt/src/index.ts";
|
||||
import { createDb, createRepository, batch, databaseHealth } from "../packages/db/src/index.ts";
|
||||
import {
|
||||
loadLocales,
|
||||
resolveI18n,
|
||||
makeT,
|
||||
resolveLang,
|
||||
renderI18nData,
|
||||
} from "../packages/i18n/src/index.ts";
|
||||
import {
|
||||
createPicture,
|
||||
createBlurPlaceholder,
|
||||
imageCacheKey,
|
||||
} from "../packages/image/src/index.ts";
|
||||
import {
|
||||
createRealtimeMessage,
|
||||
parseRealtimeMessage,
|
||||
roomMemberSummary,
|
||||
} from "../packages/realtime/src/index.ts";
|
||||
import { captchaFields, captchaHeaders, captchaTokenFrom } from "../packages/captcha/src/index.ts";
|
||||
import { uploaderAttributes, formatFileSize } from "../packages/uploader/src/index.ts";
|
||||
import { v, parseOrThrow, validationSummary } from "../packages/validation/src/index.ts";
|
||||
import { authRoute, authFailure } from "../packages/auth/src/index.ts";
|
||||
|
||||
const keyring = createKeyring([
|
||||
{ id: "active", secret: "MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA=", active: true },
|
||||
]);
|
||||
const replayStore = createMemoryReplayStore(() => 1_000);
|
||||
const envelope = await encryptHttpBody(
|
||||
{ value: 42 },
|
||||
{
|
||||
keyring,
|
||||
method: "POST",
|
||||
url: "https://example.test/api/items",
|
||||
requestId: "request-1",
|
||||
timestamp: 1_000,
|
||||
},
|
||||
);
|
||||
const decrypted = await decryptHttpBody(envelope, {
|
||||
keyring,
|
||||
method: "POST",
|
||||
url: "https://example.test/api/items",
|
||||
replayStore,
|
||||
now: () => 1_000,
|
||||
});
|
||||
assert.deepEqual(decrypted.body, { value: 42 });
|
||||
await assert.rejects(
|
||||
() =>
|
||||
decryptHttpBody(envelope, {
|
||||
keyring,
|
||||
method: "POST",
|
||||
url: "https://example.test/api/items",
|
||||
replayStore,
|
||||
now: () => 1_000,
|
||||
}),
|
||||
/REPLAY/,
|
||||
);
|
||||
await assert.rejects(
|
||||
() =>
|
||||
decryptHttpBody(envelope, {
|
||||
keyring,
|
||||
method: "GET",
|
||||
url: "https://example.test/api/items",
|
||||
now: () => 1_000,
|
||||
}),
|
||||
/CONTEXT/,
|
||||
);
|
||||
const encryptedRequest = await createEncryptedRequest(
|
||||
"https://example.test/api/items",
|
||||
{ value: 42 },
|
||||
{ keyring, method: "POST", requestId: "request-2" },
|
||||
);
|
||||
const encryptedResponse = await encryptResponse({ ok: true }, encryptedRequest, { keyring });
|
||||
assert.equal(
|
||||
(await decryptEncryptedResponse(encryptedResponse, encryptedRequest, { keyring })).body.ok,
|
||||
true,
|
||||
);
|
||||
|
||||
const secret = "a-secure-jwt-secret-for-package-kit-tests";
|
||||
const access = await createAccessToken("user-1", secret, {
|
||||
scopes: ["read", "write"],
|
||||
now: 100,
|
||||
expiresIn: 60,
|
||||
});
|
||||
const refresh = await createRefreshToken("user-1", secret, {
|
||||
family: "family-1",
|
||||
now: 100,
|
||||
expiresIn: 600,
|
||||
});
|
||||
const accessClaims = await verifyAccessToken(access, secret, { now: 120 });
|
||||
const refreshClaims = await verifyRefreshToken(refresh, secret, { now: 120 });
|
||||
assert.equal(accessClaims.sub, "user-1");
|
||||
assert.equal(refreshClaims.type, "refresh");
|
||||
assert.equal(hasScopes(accessClaims, ["read", "write"]), true);
|
||||
assert.match(jwtCookie(access), /^__Host-wrn_token=/);
|
||||
assert.equal(readJwtCookie(`__Host-wrn_token=${access}`), access);
|
||||
const tokenPair = await createTokenPair("user-1", {
|
||||
accessSecret: secret,
|
||||
accessOptions: { now: 100 },
|
||||
refreshOptions: { now: 100 },
|
||||
});
|
||||
assert.equal((await verifyAccessToken(tokenPair.accessToken, secret, { now: 120 })).sub, "user-1");
|
||||
|
||||
const queries = [];
|
||||
const driver = {
|
||||
dialect: "sqlite",
|
||||
async query(sql, params = []) {
|
||||
queries.push({ sql, params });
|
||||
if (/COUNT/.test(sql)) return [{ count: 2 }];
|
||||
if (/SELECT 1 AS healthy/.test(sql)) return [{ healthy: 1 }];
|
||||
return [{ id: 1, name: "One" }];
|
||||
},
|
||||
async exec(sql, params = []) {
|
||||
queries.push({ sql, params });
|
||||
return { changes: 1, lastInsertId: 1 };
|
||||
},
|
||||
async transaction(fn) {
|
||||
return fn(this);
|
||||
},
|
||||
close() {},
|
||||
};
|
||||
const db = createDb(driver);
|
||||
const repository = createRepository(db, { table: "items", allowedColumns: ["name"] });
|
||||
assert.equal((await repository.find(1)).name, "One");
|
||||
await repository.create({ name: "Two" });
|
||||
await repository.update(1, { name: "Changed" });
|
||||
assert.equal(await repository.count(), 2);
|
||||
assert.deepEqual(batch([1, 2, 3, 4, 5], 2), [[1, 2], [3, 4], [5]]);
|
||||
assert.equal((await databaseHealth(db)).ok, true);
|
||||
assert.ok(queries.some((entry) => /INSERT INTO items/.test(entry.sql)));
|
||||
assert.throws(() => batch([1, 2], Number.NaN), /finite/);
|
||||
|
||||
const localeDir = mkdtempSync(join(tmpdir(), "wrn-i18n-"));
|
||||
try {
|
||||
writeFileSync(join(localeDir, "en.json"), JSON.stringify({ common: { hello: "Hello {name}" } }));
|
||||
mkdirSync(join(localeDir, "fr"));
|
||||
writeFileSync(join(localeDir, "fr", "common.json"), JSON.stringify({ hello: "Bonjour {name}" }));
|
||||
const i18n = resolveI18n(loadLocales(localeDir, { strict: true }), {
|
||||
default: "en",
|
||||
locales: ["en", "fr"],
|
||||
});
|
||||
assert.equal(makeT(i18n, "fr")("common.hello", { name: "Ajay" }), "Bonjour Ajay");
|
||||
assert.equal(resolveLang(i18n, undefined, "fr;q=0.9,en;q=0.8"), "fr");
|
||||
assert.equal(resolveLang(i18n, undefined, "*"), "en");
|
||||
assert.match(renderI18nData(i18n, "fr"), /Bonjour/);
|
||||
} finally {
|
||||
rmSync(localeDir, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
const picture = createPicture({
|
||||
src: "/hero.jpg",
|
||||
alt: "Hero",
|
||||
width: 1200,
|
||||
height: 630,
|
||||
widths: [480, 768, 1200],
|
||||
formats: ["avif", "webp"],
|
||||
});
|
||||
assert.equal(picture.sources.length, 2);
|
||||
assert.match(createBlurPlaceholder(), /^data:image\/svg\+xml/);
|
||||
assert.equal(
|
||||
imageCacheKey({ src: "/hero.jpg", width: 480 }),
|
||||
imageCacheKey({ src: "/hero.jpg", width: 480 }),
|
||||
);
|
||||
|
||||
const message = createRealtimeMessage({
|
||||
type: "message",
|
||||
room: "support",
|
||||
data: { text: "Hello" },
|
||||
});
|
||||
assert.equal(
|
||||
parseRealtimeMessage(JSON.stringify(message), { room: "support", allowedTypes: ["message"] })
|
||||
.room,
|
||||
"support",
|
||||
);
|
||||
assert.throws(
|
||||
() => parseRealtimeMessage(JSON.stringify(message), { room: "other" }),
|
||||
/does not match/,
|
||||
);
|
||||
assert.deepEqual(
|
||||
roomMemberSummary([
|
||||
{ userId: "1", status: "online" },
|
||||
{ userId: "2", status: "away" },
|
||||
]),
|
||||
{ total: 2, online: 1, away: 1, busy: 0 },
|
||||
);
|
||||
|
||||
const form = new FormData();
|
||||
form.set("wrn-captcha-response", "token");
|
||||
assert.equal(captchaTokenFrom(form), "token");
|
||||
assert.deepEqual(captchaFields("token"), { "wrn-captcha-response": "token" });
|
||||
assert.equal(new Headers(captchaHeaders("token")).get("x-wrn-captcha-token"), "token");
|
||||
|
||||
assert.equal(uploaderAttributes({ store: "public", multiple: true })["data-uploader"], "public");
|
||||
assert.match(formatFileSize(1024), /KB/);
|
||||
|
||||
const schema = v.object({ email: v.string().email() });
|
||||
assert.deepEqual(parseOrThrow(schema, { email: "user@example.com" }), {
|
||||
email: "user@example.com",
|
||||
});
|
||||
assert.deepEqual(validationSummary({ email: "Invalid" }), [{ field: "email", message: "Invalid" }]);
|
||||
assert.equal(authRoute("signIn", { basePath: "/auth" }), "/auth/sign-in");
|
||||
assert.equal((await authFailure("invalid", "Invalid").json()).ok, false);
|
||||
|
||||
console.log("Package kit runtime probes passed.");
|
||||
@@ -0,0 +1,147 @@
|
||||
import console from "node:console";
|
||||
import {
|
||||
existsSync,
|
||||
mkdirSync,
|
||||
mkdtempSync,
|
||||
readFileSync,
|
||||
readdirSync,
|
||||
rmSync,
|
||||
writeFileSync,
|
||||
} from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { dirname, join, resolve } from "node:path";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import process from "node:process";
|
||||
|
||||
const root = resolve(import.meta.dirname, "..");
|
||||
const stageRoot = join(root, ".publish");
|
||||
const bun = process.env.WRNEXUS_BUN_BINARY || "bun";
|
||||
const npmCli = join(dirname(process.execPath), "node_modules", "npm", "bin", "npm-cli.js");
|
||||
if (!existsSync(npmCli)) throw new Error(`Unable to locate the npm CLI at ${npmCli}.`);
|
||||
const integrityPath = join(stageRoot, "PACKAGE-INTEGRITY.json");
|
||||
if (!existsSync(integrityPath)) {
|
||||
throw new Error("No staged packages found. Run the package staging command first.");
|
||||
}
|
||||
const integrity = JSON.parse(readFileSync(integrityPath, "utf8"));
|
||||
const expectedPackageNames = new Set(
|
||||
(Array.isArray(integrity.packages) ? integrity.packages : []).map((entry) => entry.name),
|
||||
);
|
||||
|
||||
const packages = readdirSync(stageRoot, { withFileTypes: true })
|
||||
.filter((entry) => entry.isDirectory() && existsSync(join(stageRoot, entry.name, "package.json")))
|
||||
.map((entry) => ({
|
||||
directory: entry.name,
|
||||
manifest: JSON.parse(readFileSync(join(stageRoot, entry.name, "package.json"), "utf8")),
|
||||
}))
|
||||
.sort((left, right) => left.manifest.name.localeCompare(right.manifest.name));
|
||||
|
||||
const stagedPackageNames = new Set(packages.map((pkg) => pkg.manifest.name));
|
||||
const missingPackages = [...expectedPackageNames].filter((name) => !stagedPackageNames.has(name));
|
||||
const unexpectedPackages = [...stagedPackageNames].filter(
|
||||
(name) => !expectedPackageNames.has(name),
|
||||
);
|
||||
if (
|
||||
expectedPackageNames.size === 0 ||
|
||||
packages.length !== expectedPackageNames.size ||
|
||||
missingPackages.length > 0 ||
|
||||
unexpectedPackages.length > 0
|
||||
) {
|
||||
throw new Error(
|
||||
`Staged package set does not match PACKAGE-INTEGRITY.json (expected ${expectedPackageNames.size}, received ${packages.length}, missing: ${missingPackages.join(", ") || "none"}, unexpected: ${unexpectedPackages.join(", ") || "none"}).`,
|
||||
);
|
||||
}
|
||||
|
||||
const consumer = mkdtempSync(join(tmpdir(), "wrnexus-staged-consumer-"));
|
||||
try {
|
||||
const tarballRoot = join(consumer, "tarballs");
|
||||
mkdirSync(tarballRoot, { recursive: true });
|
||||
const dependencies = {};
|
||||
for (const pkg of packages) {
|
||||
const packed = spawnSync(
|
||||
process.execPath,
|
||||
[npmCli, "pack", join(stageRoot, pkg.directory), "--pack-destination", tarballRoot, "--json"],
|
||||
{ cwd: consumer, encoding: "utf8" },
|
||||
);
|
||||
if (packed.status !== 0) {
|
||||
throw new Error(
|
||||
`Unable to pack ${pkg.manifest.name}:\n${packed.error?.message || packed.stderr || packed.stdout || `exit ${packed.status}`}`,
|
||||
);
|
||||
}
|
||||
const result = JSON.parse(packed.stdout);
|
||||
const filename = result[0]?.filename;
|
||||
if (typeof filename !== "string") {
|
||||
throw new Error(`npm pack returned no tarball for ${pkg.manifest.name}.`);
|
||||
}
|
||||
dependencies[pkg.manifest.name] = `file:${join(tarballRoot, filename).replace(/\\/g, "/")}`;
|
||||
}
|
||||
writeFileSync(
|
||||
join(consumer, "package.json"),
|
||||
JSON.stringify(
|
||||
{ name: "wrnexus-staged-consumer", private: true, type: "module", dependencies },
|
||||
null,
|
||||
2,
|
||||
) + "\n",
|
||||
);
|
||||
const install = spawnSync(
|
||||
process.execPath,
|
||||
[npmCli, "install", "--ignore-scripts", "--no-audit", "--no-fund"],
|
||||
{
|
||||
cwd: consumer,
|
||||
encoding: "utf8",
|
||||
env: { ...process.env, NO_COLOR: "1" },
|
||||
},
|
||||
);
|
||||
if (install.status !== 0) {
|
||||
throw new Error(
|
||||
`Unable to install staged package tarballs:\n${install.stderr || install.stdout}`,
|
||||
);
|
||||
}
|
||||
|
||||
const importable = packages.filter((pkg) => pkg.manifest.name !== "@wrnexus/cli");
|
||||
writeFileSync(
|
||||
join(consumer, "probe.mjs"),
|
||||
`${importable
|
||||
.map(
|
||||
(pkg) =>
|
||||
`if (!await import(${JSON.stringify(pkg.manifest.name)})) throw new Error(${JSON.stringify(`Unable to import ${pkg.manifest.name}`)});`,
|
||||
)
|
||||
.join(
|
||||
"\n",
|
||||
)}\nconsole.log(${JSON.stringify(`Imported ${importable.length} staged package roots.`)});\n`,
|
||||
);
|
||||
const probe = spawnSync(bun, ["probe.mjs"], {
|
||||
cwd: consumer,
|
||||
encoding: "utf8",
|
||||
env: { ...process.env, NO_COLOR: "1" },
|
||||
});
|
||||
if (probe.status !== 0)
|
||||
throw new Error(`Staged package import probe failed:\n${probe.stderr || probe.stdout}`);
|
||||
if (probe.stdout) process.stdout.write(probe.stdout);
|
||||
|
||||
writeFileSync(
|
||||
join(consumer, "browser-probe.js"),
|
||||
'import "@wrnexus/csr";\nimport "@wrnexus/reactive";\n',
|
||||
);
|
||||
const browser = spawnSync(
|
||||
bun,
|
||||
["build", "browser-probe.js", "--target=browser", "--outfile=browser-probe.bundle.js"],
|
||||
{ cwd: consumer, encoding: "utf8", env: { ...process.env, NO_COLOR: "1" } },
|
||||
);
|
||||
if (browser.status !== 0 || !existsSync(join(consumer, "browser-probe.bundle.js"))) {
|
||||
throw new Error(`Staged browser package build failed:\n${browser.stderr || browser.stdout}`);
|
||||
}
|
||||
|
||||
const cli = spawnSync(
|
||||
bun,
|
||||
[join(consumer, "node_modules", "@wrnexus", "cli", "dist", "index.js"), "--help"],
|
||||
{ cwd: consumer, encoding: "utf8", env: { ...process.env, NO_COLOR: "1" } },
|
||||
);
|
||||
if (cli.status !== 0 || !/wrnexus/i.test(`${cli.stdout}\n${cli.stderr}`)) {
|
||||
throw new Error(`Staged CLI smoke test failed:\n${cli.stderr || cli.stdout}`);
|
||||
}
|
||||
console.log(
|
||||
"Staged package tarballs, browser runtime bundle, and CLI passed their isolated consumer smoke tests.",
|
||||
);
|
||||
} finally {
|
||||
rmSync(consumer, { recursive: true, force: true });
|
||||
}
|
||||
@@ -70,9 +70,6 @@ for (const path of [
|
||||
"packages/csr/src/server-client.ts",
|
||||
"packages/ssr/src/store-context.ts",
|
||||
"packages/ssr/src/rpc.ts",
|
||||
"UPGRADE-0.6.0.md",
|
||||
"ROLLBACK-0.6.0.md",
|
||||
"RELEASE_NOTES-0.6.0.md",
|
||||
]) {
|
||||
if (!existsSync(join(root, path))) fail(`Missing required v0.6 file: ${path}`);
|
||||
}
|
||||
|
||||
@@ -48,7 +48,12 @@ const manifests = packageDirs
|
||||
}))
|
||||
.filter(({ manifest }) => manifest.name?.startsWith("@wrnexus/"));
|
||||
|
||||
check("root version is 0.7.0", JSON.parse(text("package.json")).version === "0.7.0");
|
||||
const rootVersion = JSON.parse(text("package.json")).version;
|
||||
check(
|
||||
"root version is compatible with the 0.7 security foundation",
|
||||
/^0\.(?:7|8)\./.test(rootVersion),
|
||||
rootVersion,
|
||||
);
|
||||
const temporaryTypecheckFiles = ["focus-shims.d.ts", "tsconfig.focus.json"].filter((path) =>
|
||||
existsSync(join(root, path)),
|
||||
);
|
||||
@@ -78,7 +83,7 @@ check(
|
||||
);
|
||||
check(
|
||||
"all framework package versions align",
|
||||
manifests.every(({ manifest }) => manifest.version === "0.7.0"),
|
||||
manifests.every(({ manifest }) => manifest.version === rootVersion),
|
||||
[...new Set(manifests.map(({ manifest }) => manifest.version))].join(", "),
|
||||
);
|
||||
for (const name of ["security", "cache", "image", "observability", "benchmark"]) {
|
||||
|
||||
@@ -0,0 +1,236 @@
|
||||
import console from "node:console";
|
||||
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
||||
import { dirname, join, resolve } from "node:path";
|
||||
import process from "node:process";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { spawnSync } from "node:child_process";
|
||||
|
||||
const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
const failures = [];
|
||||
function check(name, condition, detail = "") {
|
||||
if (condition) {
|
||||
passed++;
|
||||
console.log(` ok ${name}`);
|
||||
return;
|
||||
}
|
||||
failed++;
|
||||
failures.push(detail ? `${name}: ${detail}` : name);
|
||||
console.error(` FAIL ${name}${detail ? ` — ${detail}` : ""}`);
|
||||
}
|
||||
function text(path) {
|
||||
return readFileSync(join(root, path), "utf8");
|
||||
}
|
||||
function has(path, value) {
|
||||
const source = text(path);
|
||||
return typeof value === "string" ? source.includes(value) : value.test(source);
|
||||
}
|
||||
|
||||
const rootManifest = JSON.parse(text("package.json"));
|
||||
check(
|
||||
"repository governance documents exist",
|
||||
["LICENSE", "SECURITY.md", "CONTRIBUTING.md", "CHANGELOG.md", "CODE_OF_CONDUCT.md"].every(
|
||||
(file) => existsSync(join(root, file)),
|
||||
),
|
||||
);
|
||||
const packageManifests = readdirSync(join(root, "packages"), { withFileTypes: true })
|
||||
.filter(
|
||||
(entry) =>
|
||||
entry.isDirectory() && existsSync(join(root, "packages", entry.name, "package.json")),
|
||||
)
|
||||
.map((entry) => JSON.parse(text(`packages/${entry.name}/package.json`)));
|
||||
const workspaceManifests = ["packages", "examples", "services"].flatMap((directory) =>
|
||||
readdirSync(join(root, directory), { withFileTypes: true })
|
||||
.filter(
|
||||
(entry) =>
|
||||
entry.isDirectory() && existsSync(join(root, directory, entry.name, "package.json")),
|
||||
)
|
||||
.map((entry) => JSON.parse(text(`${directory}/${entry.name}/package.json`))),
|
||||
);
|
||||
check(
|
||||
"package manifests avoid floating latest dependency ranges",
|
||||
![rootManifest, ...workspaceManifests].some((manifest) =>
|
||||
[manifest.dependencies, manifest.devDependencies, manifest.peerDependencies].some((group) =>
|
||||
Object.values(group ?? {}).includes("latest"),
|
||||
),
|
||||
),
|
||||
);
|
||||
check("root version is 0.8.0", rootManifest.version === "0.8.0");
|
||||
check(
|
||||
"all framework packages use 0.8.0",
|
||||
packageManifests.every((manifest) => manifest.version === "0.8.0"),
|
||||
);
|
||||
check(
|
||||
"standalone realtime package exists",
|
||||
existsSync(join(root, "packages/realtime/src/index.ts")) &&
|
||||
existsSync(join(root, "packages/realtime/components/RealtimeRoom.wrn")),
|
||||
);
|
||||
check(
|
||||
"auth provides helper and UI block kits",
|
||||
existsSync(join(root, "packages/auth/src/helpers.ts")) &&
|
||||
existsSync(join(root, "packages/auth/components/AuthShell.wrn")) &&
|
||||
has("packages/auth/package.json", '"@wrnexus/ui": "workspace:*"'),
|
||||
);
|
||||
check(
|
||||
"captcha provides helper and UI wrapper kits",
|
||||
existsSync(join(root, "packages/captcha/src/helpers.ts")) &&
|
||||
existsSync(join(root, "packages/captcha/components/CaptchaField.wrn")),
|
||||
);
|
||||
check(
|
||||
"database repository helpers exist",
|
||||
has("packages/db/src/helpers.ts", "createRepository") &&
|
||||
has("packages/db/src/helpers.ts", "retryTransaction"),
|
||||
);
|
||||
check(
|
||||
"database repositories use dialect-aware placeholders",
|
||||
has("packages/db/src/helpers.ts", 'dialect === "postgres" ? `$${index}` : "?"') &&
|
||||
has("packages/db/test/helpers.test.ts", "VALUES ($1)"),
|
||||
);
|
||||
check(
|
||||
"encrypted HTTP envelope is context and replay bound",
|
||||
has("packages/encryption/src/http.ts", "WRN-ENCRYPTION-HTTP-REPLAY") &&
|
||||
has("packages/encryption/src/http.ts", "payload.path !== normalizePath"),
|
||||
);
|
||||
check(
|
||||
"encrypted responses are request-id bound and application errors propagate",
|
||||
has("packages/encryption/src/http.ts", "expectedRequestId: requestId") &&
|
||||
has("packages/encryption/src/http.ts", "const response = await next();"),
|
||||
);
|
||||
check(
|
||||
"encryption documentation preserves HTTPS boundary",
|
||||
has("packages/encryption/README.md", /TLS|HTTPS/) &&
|
||||
has("packages/encryption/README.md", /browser|end user/i),
|
||||
);
|
||||
check(
|
||||
"JWT access, refresh, scope, cookie, and pair helpers exist",
|
||||
[
|
||||
"createAccessToken",
|
||||
"createRefreshToken",
|
||||
"requireScopes",
|
||||
"jwtCookie",
|
||||
"readJwtCookie",
|
||||
"createTokenPair",
|
||||
].every((name) => has("packages/jwt/src/helpers.ts", name)),
|
||||
);
|
||||
check(
|
||||
"i18n supports nested locales and quality negotiation",
|
||||
has("packages/i18n/src/index.ts", "localeFiles") &&
|
||||
has("packages/i18n/src/index.ts", "parseAcceptLanguage"),
|
||||
);
|
||||
check(
|
||||
"i18n safely resolves namespace collisions and wildcard language ranges",
|
||||
has("packages/i18n/src/index.ts", 'tag?.trim() === "*"') &&
|
||||
has("packages/i18n/test/package-kit.test.ts", "primitive namespace collisions"),
|
||||
);
|
||||
check(
|
||||
"i18n runtime data is injected into rendered documents",
|
||||
has("packages/dev-server/src/runtime.ts", "renderI18nData(deps.i18n, language)") &&
|
||||
has("packages/dev-server/src/runtime.ts", "deps.i18n.cookie.name"),
|
||||
);
|
||||
check(
|
||||
"image package provides picture, loaders, placeholder, and preload helpers",
|
||||
["createPicture", "createCdnImageLoader", "createBlurPlaceholder", "imagePreload"].every((name) =>
|
||||
has("packages/image/src/index.ts", name),
|
||||
),
|
||||
);
|
||||
check(
|
||||
"image helpers validate finite dimensions and preserve URL fragments",
|
||||
has("packages/image/src/index.ts", "Image width bounds must be finite") &&
|
||||
has("packages/image/test/package-kit.test.ts", "URL fragments"),
|
||||
);
|
||||
check(
|
||||
"realtime messages are size, room, type, and payload bounded",
|
||||
has("packages/realtime/src/messages.ts", "maxBytes") &&
|
||||
has("packages/realtime/src/messages.ts", "Unsafe realtime payload key"),
|
||||
);
|
||||
check(
|
||||
"auth shell uses statically discoverable Tailwind width classes",
|
||||
!has("packages/auth/components/AuthShell.wrn", "max-w-{maxWidth}") &&
|
||||
has("packages/auth/components/AuthShell.wrn", "class:max-w-md"),
|
||||
);
|
||||
check(
|
||||
"uploader package provides UI blocks and helpers",
|
||||
existsSync(join(root, "packages/uploader/components/UploadDropzone.wrn")) &&
|
||||
existsSync(join(root, "packages/uploader/src/helpers.ts")),
|
||||
);
|
||||
check(
|
||||
"validation package provides UI blocks and helpers",
|
||||
existsSync(join(root, "packages/validation/components/ValidationSummary.wrn")) &&
|
||||
existsSync(join(root, "packages/validation/src/helpers.ts")),
|
||||
);
|
||||
check(
|
||||
"validation schemas infer helper output types",
|
||||
has("packages/validation/src/index.ts", "export type InferSchema") &&
|
||||
has("packages/validation/src/helpers.ts", "schema: ObjectSchema<T>") &&
|
||||
has("packages/validation/test/helpers.test.ts", "const email: string = value.email"),
|
||||
);
|
||||
check(
|
||||
"validation oneOf preserves literal union types",
|
||||
has("packages/validation/src/index.ts", "class StringSchema<TValue extends string") &&
|
||||
has("packages/validation/src/index.ts", "TValues extends readonly [string, ...string[]]") &&
|
||||
has("packages/validation/test/helpers.test.ts", "const schema: ObjectSchema<ContactInput>"),
|
||||
);
|
||||
check("0.8 migration exists", has("packages/cli/src/update.ts", 'id: "0.8.0-01-package-kits"'));
|
||||
check(
|
||||
"package kit audit exists",
|
||||
existsSync(join(root, "scripts/audit-package-kits.mjs")) &&
|
||||
rootManifest.scripts?.["audit:packages"] === "node scripts/audit-package-kits.mjs",
|
||||
);
|
||||
check(
|
||||
"audit validators are read-only and reports use explicit generators",
|
||||
rootManifest.scripts?.["security:framework"] === "node scripts/security-performance-audit.mjs" &&
|
||||
rootManifest.scripts?.["generate:security-report"] ===
|
||||
"node scripts/security-performance-audit.mjs --write" &&
|
||||
rootManifest.scripts?.["generate:package-audit"] ===
|
||||
"node scripts/audit-package-kits.mjs --write" &&
|
||||
has("scripts/security-performance-audit.mjs", 'process.argv.includes("--write")') &&
|
||||
has("scripts/audit-package-kits.mjs", 'process.argv.includes("--write")'),
|
||||
);
|
||||
const publicApi = spawnSync(process.execPath, ["scripts/check-public-api.mjs"], {
|
||||
cwd: root,
|
||||
encoding: "utf8",
|
||||
});
|
||||
if (publicApi.stdout) process.stdout.write(publicApi.stdout);
|
||||
if (publicApi.stderr) process.stderr.write(publicApi.stderr);
|
||||
check(
|
||||
"public package exports match the reviewed API baseline",
|
||||
publicApi.status === 0 && existsSync(join(root, "docs/public-api-0.8.json")),
|
||||
);
|
||||
const audit = spawnSync(process.execPath, ["scripts/audit-package-kits.mjs"], {
|
||||
cwd: root,
|
||||
encoding: "utf8",
|
||||
});
|
||||
if (audit.stdout) process.stdout.write(audit.stdout);
|
||||
if (audit.stderr) process.stderr.write(audit.stderr);
|
||||
check("all package kits pass the executable audit", audit.status === 0);
|
||||
const runtimeProbe = spawnSync(
|
||||
process.execPath,
|
||||
["--experimental-transform-types", "scripts/test-package-kits.mjs"],
|
||||
{ cwd: root, encoding: "utf8" },
|
||||
);
|
||||
if (runtimeProbe.stdout) process.stdout.write(runtimeProbe.stdout);
|
||||
if (runtimeProbe.stderr) process.stderr.write(runtimeProbe.stderr);
|
||||
check("package helper runtime probes pass", runtimeProbe.status === 0);
|
||||
check(
|
||||
"package helper APIs have dedicated tests",
|
||||
[
|
||||
"packages/encryption/test/http.test.ts",
|
||||
"packages/jwt/test/helpers.test.ts",
|
||||
"packages/db/test/helpers.test.ts",
|
||||
"packages/i18n/test/package-kit.test.ts",
|
||||
"packages/image/test/package-kit.test.ts",
|
||||
"packages/captcha/test/helpers.test.ts",
|
||||
"packages/uploader/test/helpers.test.ts",
|
||||
"packages/validation/test/helpers.test.ts",
|
||||
"packages/realtime/test/realtime.test.ts",
|
||||
"packages/auth/test/package-components.test.ts",
|
||||
].every((path) => existsSync(join(root, path))),
|
||||
);
|
||||
console.log(`\n${passed} passed`);
|
||||
console.log(`${failed} failed`);
|
||||
if (failed) {
|
||||
console.error("\nFailures:");
|
||||
for (const value of failures) console.error(`- ${value}`);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
Reference in New Issue
Block a user