chore: harden release checks and package coverage
Quality / quality (windows-latest) (push) Waiting to run
Quality / quality (ubuntu-latest) (push) Failing after 9m57s

This commit is contained in:
2026-08-24 11:36:13 +05:30
parent 354082ebc3
commit e372ae571a
24 changed files with 563 additions and 279 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/captcha",
"version": "0.8.11",
"version": "0.8.13",
"description": "First-class CAPTCHA challenges, providers, verification guards, page gates, and WRNexusJS UI.",
"type": "module",
"sideEffects": false,
@@ -0,0 +1,50 @@
import { describe, expect, test } from "bun:test";
import {
captchaContext,
captchaResultResponse,
captchaTokenFrom,
verifyCaptchaOrThrow,
} from "../src/helpers.ts";
describe("CAPTCHA helper boundaries", () => {
test("extracts JSON request fallbacks without consuming the caller's request", async () => {
const request = new Request("https://example.test/verify", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ captchaToken: 12345 }),
});
expect(await captchaTokenFrom(request)).toBe("12345");
expect(await request.json()).toEqual({ captchaToken: 12345 });
});
test("turns verification failures into stable errors and no-store responses", async () => {
const provider = {
name: "custom" as const,
client: { responseField: "captchaToken" },
verify: async () => ({
success: false as const,
code: "expired",
message: "Try again",
provider: "custom" as const,
action: "login",
}),
};
await expect(
verifyCaptchaOrThrow(provider, { providerToken: "x", action: "login" }),
).rejects.toThrow("WRN-CAPTCHA-EXPIRED: Try again");
const response = captchaResultResponse({
success: false,
code: "invalid",
provider: "custom",
action: "login",
});
expect(response.status).toBe(403);
expect(response.headers.get("cache-control")).toBe("no-store");
expect(
captchaContext({
locals: { captcha: { success: true, provider: "custom", action: "login" } },
} as never),
).toEqual({ success: true, provider: "custom", action: "login" });
expect(captchaContext({ locals: { captcha: "forged" } } as never)).toBeNull();
});
});
+2 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/dev-server",
"version": "0.8.54",
"version": "0.8.56",
"type": "module",
"main": "src/index.ts",
"exports": {
@@ -25,6 +25,7 @@
"@wrnexus/uploader": "workspace:*",
"@wrnexus/plugin": "workspace:*",
"@wrnexus/queue": "workspace:*",
"@wrnexus/react": "workspace:*",
"@wrnexus/store": "workspace:*",
"@wrnexus/security": "workspace:*",
"@wrnexus/observability": "workspace:*",
+1 -3
View File
@@ -508,9 +508,7 @@ function validateConfiguredImports(_source: string, ast: PageAst, file: string):
]);
const missing = [...usedComponents].filter(
(name) =>
!compilerBuiltins.has(name) &&
!options.globalComponents.has(name) &&
!imported.has(name),
!compilerBuiltins.has(name) && !options.globalComponents.has(name) && !imported.has(name),
);
if (ast.layoutIsSymbol && ast.layout && !imported.has(ast.layout)) missing.push(ast.layout);
if (!missing.length) return;
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/dev-toolbar",
"version": "0.8.13",
"version": "0.8.15",
"private": true,
"type": "module",
"sideEffects": false,
@@ -0,0 +1,25 @@
import { expect, test } from "bun:test";
import { createServerIssue, issueFromError } from "../../src/server/issues.ts";
test("server issues have stable fingerprints and retain actionable error context", () => {
const input = {
ruleId: "server/query",
category: "server" as const,
severity: "warning" as const,
title: "Slow query",
message: "Query exceeded budget",
pathname: "/orders",
source: { file: "app/api/orders.ts", line: 12, column: 4 },
};
const first = createServerIssue(input);
const second = createServerIssue(input);
expect(first.id).not.toBe(second.id);
expect(first.fingerprint).toBe(second.fingerprint);
expect(first.metadata?.pathname).toBe("/orders");
const error = issueFromError(new Error("database offline"), { pathname: "/orders" });
expect(error.severity).toBe("error");
expect(error.message).toBe("database offline");
expect(error.metadata?.stack).toContain("database offline");
expect(error.recommendation).toContain("stack trace");
});
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/store",
"version": "0.8.9",
"version": "0.8.11",
"type": "module",
"main": "src/index.ts",
"exports": {
@@ -0,0 +1,32 @@
import { expect, test } from "bun:test";
import { StoreContainer, defineStore } from "../src/index.ts";
test("memory persistence includes only approved fields and migrates old state", async () => {
const original = defineStore({
name: "preferences-migration",
kind: "global" as const,
createSharedState: () => ({ theme: "dark", secret: "initial" }),
persist: { storage: "memory" as const, version: 1, include: ["theme"] },
actions: {
setTheme: {
runtime: "client" as const,
handler: ({ state }: any, theme: string) => void (state.theme = theme),
},
},
});
const first = await new StoreContainer({ runtime: "client" }).use(original);
await first.actions.setTheme("light");
const migrated = defineStore({
...original,
persist: {
storage: "memory" as const,
version: 2,
include: ["theme"],
migrate: (state: any) => ({ ...state, theme: `${state.theme}-v2` }),
},
});
const second = await new StoreContainer({ runtime: "client" }).use(migrated);
expect(second.state.theme).toBe("light-v2");
expect(second.state.secret).toBe("initial");
});
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/syntax",
"version": "0.8.13",
"version": "0.8.15",
"type": "module",
"main": "src/index.ts",
"exports": {
@@ -0,0 +1,28 @@
import { expect, test } from "bun:test";
import { hasRequestSection, parseApiEntries, parseApiSections } from "../src/api-sections.ts";
test("api section scanning ignores keywords in literals, comments, and nested objects", () => {
const source = `
// request { body { forged: string } }
response {
return { label: "request { not a section }", nested: { body: true } }
}
`;
expect(hasRequestSection(source)).toBe(false);
expect(parseApiSections(source)?.response).toContain("nested");
});
test("api entries normalize methods and reject malformed request fields", () => {
expect(
parseApiEntries(`lookup get /users/:id {
response { return data }
}`)[0],
).toMatchObject({
name: "lookup",
method: "GET",
path: "/users/:id",
});
expect(() =>
parseApiSections("request { body { missingType } } response { return data }"),
).toThrow('Expected "name: type"');
});
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/typecheck",
"version": "0.8.13",
"version": "0.8.15",
"type": "module",
"main": "src/index.ts",
"exports": {
+41 -5
View File
@@ -139,7 +139,7 @@ function functionDeclaration(fn: RuntimeFunctionDecl): string {
const params = fn.parameters
.map(
(param) =>
`${param.name}${param.optional ? "?" : ""}${param.valueType ? `: ${param.valueType}` : param.name === "event" ? ": CustomEvent<any> & { target: HTMLElement }" : ": any"}${param.default ? ` = ${param.default}` : ""}`,
`${param.name}${param.optional ? "?" : ""}${param.valueType ? `: ${param.valueType}` : param.name === "event" ? ": Event & { target: HTMLElement }" : ": any"}${param.default ? ` = ${param.default}` : ""}`,
)
.join(", ");
// An omitted WRN parameter type follows JavaScript semantics. Emitting it as
@@ -214,6 +214,31 @@ function safeBindingName(name: string): boolean {
return /^[A-Za-z_$][\w$]*$/.test(name) && !RESERVED_BINDING_NAMES.has(name);
}
function safeViewExpression(expression: string, reservedProps: ReadonlySet<string>): string {
if (!reservedProps.size) return expression;
const scanner = ts.createScanner(
ts.ScriptTarget.Latest,
false,
ts.LanguageVariant.Standard,
expression,
);
let result = "";
let copied = 0;
for (let token = scanner.scan(); token !== ts.SyntaxKind.EndOfFileToken; token = scanner.scan()) {
const start = scanner.getTokenPos();
const end = scanner.getTextPos();
const text = scanner.getTokenText();
if (!reservedProps.has(text)) continue;
const previous = expression.slice(0, start).trimEnd().at(-1);
const following = expression.slice(end).trimStart().at(0);
// Property access and object-literal keys are already valid TypeScript.
if (previous === "." || following === ":") continue;
result += expression.slice(copied, start) + `props.${text}`;
copied = end;
}
return result + expression.slice(copied);
}
export function virtualTypeScriptModule(
source: string,
filePath = "component.wrn",
@@ -348,6 +373,9 @@ export function virtualTypeScriptModule(
// misspelled state/function name cannot silently become `undefined` at
// runtime. Each/handler locals mirror the browser evaluator's bindings.
let bindingIndex = 0;
const reservedProps = new Set(
ast.props.map((prop) => prop.name).filter((name) => !safeBindingName(name)),
);
const appendViewBindings = (nodes: ViewNode[], locals: Set<string>): void => {
for (const node of nodes) {
if (node.type === "element") {
@@ -359,10 +387,15 @@ export function virtualTypeScriptModule(
if (!expression.trim()) continue;
const declarations = [
...[...locals].map((name) => `declare const ${name}: any;`),
...(attr.event ? ["declare const payload: any;", "declare const event: Event;"] : []),
...(attr.event
? [
"declare const payload: any;",
"declare const event: Event & { target: HTMLElement };",
]
: []),
].join(" ");
append(
`namespace __wrn_view_${bindingIndex++} { ${declarations} void (${expression}); }`,
`namespace __wrn_view_${bindingIndex++} { ${declarations} void (${safeViewExpression(expression, reservedProps)}); }`,
attr.value,
);
}
@@ -372,13 +405,16 @@ export function virtualTypeScriptModule(
for (const branch of node.branches) {
if (branch.cond)
append(
`namespace __wrn_view_${bindingIndex++} { void (${branch.cond}); }`,
`namespace __wrn_view_${bindingIndex++} { void (${safeViewExpression(branch.cond, reservedProps)}); }`,
branch.cond,
);
appendViewBindings(branch.body, locals);
}
} else if (node.type === "each") {
append(`namespace __wrn_view_${bindingIndex++} { void (${node.list}); }`, node.list);
append(
`namespace __wrn_view_${bindingIndex++} { void (${safeViewExpression(node.list, reservedProps)}); }`,
node.list,
);
const nested = new Set(locals);
nested.add(node.item);
if (node.index) nested.add(node.index);
@@ -1,5 +1,5 @@
import { expect, test } from "bun:test";
import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs";
import { mkdirSync, mkdtempSync, readFileSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { checkWrnSource } from "../src/index.ts";
@@ -82,6 +82,23 @@ page Home {
expect(diagnostics.some((item) => item.code === "WRN-COMPONENT-PROP-TYPE")).toBe(false);
expect(diagnostics.some((item) => item.code === "WRN-TYPE-1389")).toBe(false);
expect(diagnostics.some((item) => item.code === "WRN-TYPE-1005")).toBe(false);
expect(
checkWrnSource(readFileSync(componentPath, "utf8"), { appRoot: root, filePath: componentPath }),
).toEqual([]);
}, 15_000);
test("implicit DOM events satisfy untyped event handler parameters", () => {
const root = fixture();
const filePath = join(root, "app", "components", "Submit.wrn");
const diagnostics = checkWrnSource(
`component Submit {
outputs { submit(payload: { event: Event }) }
functions { client function submitForm(event) { output.submit({ event }) } }
view { <form @submit="submitForm(event)"></form> }
}`,
{ appRoot: root, filePath },
);
expect(diagnostics).toEqual([]);
}, 15_000);
test("does not treat UI component imports as JavaScript exports", () => {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/uploader",
"version": "0.8.10",
"version": "0.8.12",
"private": true,
"type": "module",
"main": "./src/index.ts",
@@ -0,0 +1,46 @@
import { describe, expect, test } from "bun:test";
import {
UploadPolicyError,
createSignedFileToken,
enforceUploadPolicy,
inspectUpload,
sniffContentType,
verifySignedFileToken,
} from "../src/security.ts";
describe("upload policy hardening", () => {
test("enforces size, type, filename, and checksum independently", async () => {
const inspection = await inspectUpload("report.pdf", new Uint8Array([0x25, 0x50, 0x44, 0x46]));
const cases: Array<[Parameters<typeof enforceUploadPolicy>[1], string | undefined, string]> = [
[{ maxBytes: 3 }, undefined, "UPLOAD_TOO_LARGE"],
[{ accept: ["image/png"] }, undefined, "UPLOAD_TYPE_REJECTED"],
[{ filenamePattern: /^invoice-/ }, undefined, "UPLOAD_FILENAME_REJECTED"],
[{ requireChecksum: true }, undefined, "UPLOAD_CHECKSUM_REQUIRED"],
[{}, "0".repeat(64), "UPLOAD_CHECKSUM_MISMATCH"],
];
for (const [policy, checksum, code] of cases) {
try {
enforceUploadPolicy(inspection, policy, checksum);
throw new Error("policy unexpectedly accepted upload");
} catch (error) {
expect(error).toBeInstanceOf(UploadPolicyError);
expect((error as UploadPolicyError).code).toBe(code);
}
}
enforceUploadPolicy(
inspection,
{ maxBytes: 4, accept: ["application/pdf"] },
inspection.sha256,
);
});
test("recognizes bounded signatures and rejects malformed signed-token inputs", async () => {
expect(sniffContentType(new Uint8Array([0xff, 0xd8, 0xff]))).toBe("image/jpeg");
expect(sniffContentType(new Uint8Array([0x50, 0x4b, 0x03, 0x04]))).toBe("application/zip");
expect(sniffContentType(new Uint8Array([1, 2, 3]))).toBeNull();
await expect(
createSignedFileToken({ store: "", key: "x", expiresAt: 1 }, "long-enough-secret"),
).rejects.toThrow("store");
expect(await verifySignedFileToken("not-a-token", "long-enough-secret")).toBeNull();
});
});