release: WRNexusJS 0.2.78

This commit is contained in:
2026-07-22 01:53:18 +05:30
parent 7ff5b3e8c5
commit c6fc0f1f63
60 changed files with 228 additions and 96 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/ai",
"version": "0.2.77",
"version": "0.2.78",
"private": true,
"type": "module",
"description": "Zero-dependency Claude (Anthropic) client for WrNexus apps.",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/authz",
"version": "0.2.77",
"version": "0.2.78",
"private": true,
"type": "module",
"main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/cli",
"version": "0.2.77",
"version": "0.2.78",
"type": "module",
"main": "src/index.ts",
"exports": {
+26 -5
View File
@@ -767,6 +767,14 @@ const MIGRATIONS: Migration[] = [
// Language and shared UI behavior improve automatically after updating.
},
},
{
version: "0.2.78",
id: "ui-components-fix",
description: "Dev toolbar for optimization and developer.",
apply() {
// Language and shared UI behavior improve automatically after updating.
},
},
];
/** Release tooling uses this to require an explicit migration entry per version. */
@@ -893,6 +901,20 @@ export function updateApp(appRoot: string, target: string, dryRun: boolean): Upd
return { root: appRoot, packagePath: pkgPath, pkg };
}
export function verificationCommands(scripts: Record<string, string>): string[][] {
const commands: string[][] = [];
// Update migrations can rewrite TypeScript, JSON, and framework configuration
// files. Normalize those edits with the project's own formatter before running
// the read-only format check. This prevents a successful migration from failing
// only because wrnexus.config.ts or another touched file needs Prettier output.
if (scripts.format) commands.push(["run", "format"]);
if (scripts.check) commands.push(["run", "check"]);
if (scripts.build) commands.push(["run", "build"]);
return commands;
}
function verifyApp(app: UpdatedApp): boolean {
const health = inspectProject(app.root);
const failed = health.filter((check) => !check.ok && check.name !== "configuration");
@@ -900,12 +922,11 @@ function verifyApp(app: UpdatedApp): boolean {
for (const check of failed) console.error(`${check.name}: ${check.detail}`);
return false;
}
const scripts = (app.pkg.scripts ?? {}) as Record<string, string>;
const commands: string[][] = [];
if (scripts.check) commands.push(["run", "check"]);
if (scripts.build) commands.push(["run", "build"]);
for (const args of commands) {
console.log(`\n Verifying ${app.pkg.name ?? app.root}: bun ${args.join(" ")}`);
for (const args of verificationCommands(scripts)) {
const action = args[1] === "format" ? "Formatting" : "Verifying";
console.log(`\n ${action} ${app.pkg.name ?? app.root}: bun ${args.join(" ")}`);
const result = spawnSync(process.execPath, args, { cwd: app.root, stdio: "inherit" });
if (result.status !== 0) return false;
}
+19 -1
View File
@@ -2,7 +2,7 @@ import { expect, test } from "bun:test";
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { updateApp } from "../src/update.ts";
import { updateApp, verificationCommands } from "../src/update.ts";
test("updateApp migrates project files without marking an unverified update complete", () => {
const root = mkdtempSync(join(tmpdir(), "wrnexus-update-"));
@@ -150,3 +150,21 @@ test("0.2.20 migration repairs only the legacy generated recommendations layout"
rmSync(root, { recursive: true, force: true });
}
});
test("update verification formats before checking and building", () => {
expect(
verificationCommands({
format: "prettier . --write",
check: "bun run lint && bun run format:check",
build: "wrnexus build .",
}),
).toEqual([
["run", "format"],
["run", "check"],
["run", "build"],
]);
});
test("update verification skips unavailable scripts", () => {
expect(verificationCommands({ check: "bun run lint" })).toEqual([["run", "check"]]);
});
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/compiler",
"version": "0.2.77",
"version": "0.2.78",
"type": "module",
"main": "src/index.ts",
"exports": {
+45 -1
View File
@@ -1144,6 +1144,50 @@ function serverLoopLocalsAttribute(ctx: CompCtx): string {
return ` data-wrn-loop-locals="\${__wrnexusEncodeLoopLocals({ ${entries} })}"`;
}
function unwrapDirectiveExpression(raw: string): string {
const value = raw.trim();
if (!value.startsWith("{") || !value.endsWith("}")) {
return value;
}
let depth = 0;
let quote: '"' | "'" | "`" | null = null;
let escaped = false;
for (let index = 0; index < value.length; index++) {
const char = value[index]!;
if (escaped) {
escaped = false;
continue;
}
if (quote) {
if (char === "\\") {
escaped = true;
} else if (char === quote) {
quote = null;
}
continue;
}
if (char === '"' || char === "'" || char === "`") {
quote = char;
continue;
}
if (char === "{") depth++;
if (char === "}") depth--;
if (depth === 0 && index < value.length - 1) {
return value;
}
}
return depth === 0 ? value.slice(1, -1).trim() : value;
}
/** Render a component view node into template-literal-ready source. */
function renderComponentNode(node: ViewNode, ctx: CompCtx): string {
if (node.type === "text") return compileText(node.value, ctx);
@@ -1185,7 +1229,7 @@ function renderComponentNode(node: ViewNode, ctx: CompCtx): string {
if (!attr.event && attr.name.startsWith("class:")) {
conditionalClasses.push({
className: attr.name.slice("class:".length),
expression: attr.value,
expression: unwrapDirectiveExpression(attr.value),
});
}
}
+27
View File
@@ -788,6 +788,33 @@ component PageHeader {
expect(output).toContain("dark:bg-white/10");
});
test("class directives accept single-quoted brace expressions", () => {
const output = generate(
parse(`
component MarketingSectionHeader {
props {
align = "start"
}
view {
<header
class="max-w-3xl"
class:mx-auto='{align === "center"}'
class:text-center='{align === "center"}'
>
Header
</header>
}
}
`),
);
expect(output).toContain('${(align === "center") ? " mx-auto" : ""}');
expect(output).toContain('${(align === "center") ? " text-center" : ""}');
expect(output).not.toContain('({align === "center"})');
expect(output).toContain("&quot;align === \\\\&quot;center\\\\&quot;&quot;");
});
test("components support each blocks", () => {
const output = generate(
parse(`
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/core",
"version": "0.2.77",
"version": "0.2.78",
"type": "module",
"main": "src/index.ts",
"exports": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/csr",
"version": "0.2.77",
"version": "0.2.78",
"type": "module",
"main": "src/index.ts",
"exports": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/db",
"version": "0.2.77",
"version": "0.2.78",
"private": true,
"type": "module",
"main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/dev-server",
"version": "0.2.77",
"version": "0.2.78",
"type": "module",
"main": "src/index.ts",
"exports": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/dev-toolbar",
"version": "0.2.77",
"version": "0.2.78",
"private": true,
"type": "module",
"sideEffects": false,
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/encryption",
"version": "0.2.77",
"version": "0.2.78",
"private": true,
"type": "module",
"main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/helpers",
"version": "0.2.77",
"version": "0.2.78",
"private": true,
"type": "module",
"description": "Safe convenience helpers for WrNexus request contexts and common application flows.",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/i18n",
"version": "0.2.77",
"version": "0.2.78",
"private": true,
"type": "module",
"main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/jwt",
"version": "0.2.77",
"version": "0.2.78",
"private": true,
"type": "module",
"main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/mobile",
"version": "0.2.77",
"version": "0.2.78",
"type": "module",
"main": "src/index.ts",
"exports": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/native",
"version": "0.2.77",
"version": "0.2.78",
"type": "module",
"main": "src/index.ts",
"exports": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/oauth",
"version": "0.2.77",
"version": "0.2.78",
"private": true,
"type": "module",
"main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/pubsub",
"version": "0.2.77",
"version": "0.2.78",
"private": true,
"type": "module",
"main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/queue",
"version": "0.2.77",
"version": "0.2.78",
"private": true,
"type": "module",
"main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/reactive",
"version": "0.2.77",
"version": "0.2.78",
"type": "module",
"main": "src/index.ts",
"exports": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/router",
"version": "0.2.77",
"version": "0.2.78",
"type": "module",
"main": "src/index.ts",
"exports": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/ssr",
"version": "0.2.77",
"version": "0.2.78",
"type": "module",
"main": "src/index.ts",
"exports": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/styles",
"version": "0.2.77",
"version": "0.2.78",
"type": "module",
"main": "src/index.ts",
"exports": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/test",
"version": "0.2.77",
"version": "0.2.78",
"private": true,
"type": "module",
"main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/tracking",
"version": "0.2.77",
"version": "0.2.78",
"private": true,
"type": "module",
"main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/ui",
"version": "0.2.77",
"version": "0.2.78",
"private": true,
"type": "module",
"main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/uploader",
"version": "0.2.77",
"version": "0.2.78",
"private": true,
"type": "module",
"main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/validation",
"version": "0.2.77",
"version": "0.2.78",
"private": true,
"type": "module",
"main": "src/index.ts",