release: WRNexusJS 0.3.0

This commit is contained in:
2026-07-22 17:29:08 +05:30
parent 13dfa31d19
commit 07d8fb59d6
145 changed files with 9664 additions and 3881 deletions
+147 -14
View File
@@ -1,26 +1,99 @@
import { existsSync, readFileSync } from "node:fs";
import { join, resolve } from "node:path";
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
import { extname, join, resolve } from "node:path";
import { diagnose } from "@wrnexus/syntax";
import { buildRouter, findRouteConflicts } from "@wrnexus/router";
import { loadAppConfig, validateAppConfig } from "@wrnexus/styles";
export interface DoctorCheck {
name: string;
ok: boolean;
detail: string;
level?: "error" | "warning";
}
function parseVersion(value: string): [number, number, number] {
const match = /^(\d+)\.(\d+)\.(\d+)/.exec(value.replace(/^[^\d]*/, ""));
return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : [0, 0, 0];
}
function versionAtLeast(value: string, minimum: string): boolean {
const left = parseVersion(value);
const right = parseVersion(minimum);
for (let i = 0; i < 3; i++) {
if (left[i] !== right[i]) return left[i]! > right[i]!;
}
return true;
}
function walk(dir: string, extension: string): string[] {
if (!existsSync(dir)) return [];
const files: string[] = [];
for (const entry of readdirSync(dir)) {
if (["node_modules", "dist", ".git", ".wrnexus"].includes(entry)) continue;
const path = join(dir, entry);
const stat = statSync(path);
if (stat.isDirectory()) files.push(...walk(path, extension));
else if (stat.isFile() && extname(path) === extension) files.push(path);
}
return files;
}
function frameworkRanges(pkg: Record<string, unknown>): Map<string, string[]> {
const ranges = new Map<string, string[]>();
for (const field of ["dependencies", "devDependencies", "peerDependencies"]) {
const deps = pkg[field] as Record<string, string> | undefined;
for (const [name, range] of Object.entries(deps ?? {})) {
if (!name.startsWith("@wrnexus/")) continue;
const values = ranges.get(range) ?? [];
values.push(name);
ranges.set(range, values);
}
}
return ranges;
}
export function inspectProject(appRoot: string): DoctorCheck[] {
const root = resolve(appRoot);
const checks: DoctorCheck[] = [];
const pkgPath = join(root, "package.json");
const bunVersion = typeof Bun !== "undefined" ? String(Bun.version) : "";
checks.push({
name: "Bun runtime",
ok: typeof Bun !== "undefined",
detail: typeof Bun !== "undefined" ? `v${Bun.version}` : "Bun is required",
ok: !!bunVersion && versionAtLeast(bunVersion, "1.3.0"),
detail: bunVersion ? `v${bunVersion} (minimum 1.3.0)` : "Bun 1.3.0 or newer is required",
});
checks.push({
name: "package.json",
ok: existsSync(pkgPath),
detail: existsSync(pkgPath) ? pkgPath : "Run this command from a WrNexus project root",
detail: existsSync(pkgPath) ? pkgPath : "Run this command from a WRNexus project root",
});
if (existsSync(pkgPath)) {
try {
const pkg = JSON.parse(readFileSync(pkgPath, "utf8")) as Record<string, unknown>;
const ranges = frameworkRanges(pkg);
checks.push({
name: "framework package versions",
ok: ranges.size <= 1,
detail:
ranges.size <= 1
? ([...ranges.keys()][0] ?? "No @wrnexus packages declared")
: `version skew: ${[...ranges.entries()]
.map(([range, names]) => `${range} (${names.join(", ")})`)
.join("; ")}`,
});
const marker = (pkg.wrnexus as { version?: string } | undefined)?.version;
checks.push({
name: "update marker",
ok: !marker || versionAtLeast(marker, "0.3.0"),
detail: marker ? `project last migrated to ${marker}` : "missing; run `wrnexus update`",
level: "warning",
});
} catch {
checks.push({ name: "package JSON", ok: false, detail: "package.json is invalid JSON" });
}
}
const app = join(root, "app");
checks.push({
name: "app/pages",
@@ -34,13 +107,51 @@ export function inspectProject(appRoot: string): DoctorCheck[] {
name: "configuration",
ok: !!config,
detail: config ?? "No wrnexus.config file; framework defaults will be used",
level: "warning",
});
const wrnFiles = walk(app, ".wrn");
let syntaxErrors = 0;
let syntaxWarnings = 0;
for (const file of wrnFiles) {
const diagnostics = diagnose(readFileSync(file, "utf8"), { file, accessibility: true });
syntaxErrors += diagnostics.filter((item) => item.severity === "error").length;
syntaxWarnings += diagnostics.filter((item) => item.severity !== "error").length;
}
checks.push({
name: "WRN language",
ok: syntaxErrors === 0,
detail: `${wrnFiles.length} files, ${syntaxErrors} errors, ${syntaxWarnings} warnings`,
});
if (existsSync(join(app, "pages"))) {
try {
const router = buildRouter(app);
const conflicts = [
...findRouteConflicts(router.pages),
...findRouteConflicts(router.api),
...findRouteConflicts(router.realtime),
];
checks.push({
name: "route manifest",
ok: conflicts.length === 0,
detail: conflicts.length
? conflicts.map((conflict) => conflict.raw).join(", ")
: `${router.pages.length} pages, ${router.api.length} API, ${router.realtime.length} realtime`,
});
} catch (error) {
checks.push({
name: "route manifest",
ok: false,
detail: error instanceof Error ? error.message : String(error),
});
}
}
const mobilePkg = join(root, "mobile", "package.json");
if (existsSync(mobilePkg)) {
try {
const mobile = JSON.parse(readFileSync(mobilePkg, "utf8")) as {
wrnexus?: { mode?: string };
};
const mobile = JSON.parse(readFileSync(mobilePkg, "utf8")) as { wrnexus?: { mode?: string } };
checks.push({
name: "mobile project",
ok: mobile.wrnexus?.mode === "webview" || mobile.wrnexus?.mode === "native",
@@ -57,12 +168,34 @@ export function inspectProject(appRoot: string): DoctorCheck[] {
return checks;
}
export function runDoctor(appRoot: string): boolean {
const checks = inspectProject(appRoot);
console.log("WrNexus doctor\n");
for (const check of checks)
console.log(` ${check.ok ? "✓" : "✗"} ${check.name}: ${check.detail}`);
export async function runDoctor(appRoot: string): Promise<boolean> {
const root = resolve(appRoot);
const checks = inspectProject(root);
try {
const config = await loadAppConfig(root);
const issues = validateAppConfig(config);
checks.push({
name: "resolved configuration",
ok: issues.every((issue) => issue.severity !== "error"),
detail: issues.length
? issues.map((issue) => `${issue.path}: ${issue.message}`).join("; ")
: "valid",
level: issues.some((issue) => issue.severity === "error") ? "error" : "warning",
});
} catch (error) {
checks.push({
name: "resolved configuration",
ok: false,
detail: error instanceof Error ? error.message : String(error),
});
}
console.log("WRNexus doctor\n");
for (const check of checks) {
const optional = check.level === "warning";
console.log(` ${check.ok ? "✓" : optional ? "⚠" : "✗"} ${check.name}: ${check.detail}`);
}
console.log("\n Security dependencies: run `bun audit`");
console.log(" Complete verification: run `bun run check`");
return checks.every((check) => check.ok || check.name === "configuration");
return checks.every((check) => check.ok || check.level === "warning");
}