feat: restore gateway HMR and add WRN formatting

This commit is contained in:
2026-07-13 14:54:03 +05:30
parent 3b46c69601
commit cff420a29e
71 changed files with 727 additions and 119 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/cli",
"version": "0.2.18",
"version": "0.2.19",
"type": "module",
"main": "src/index.ts",
"exports": {
+35 -3
View File
@@ -51,7 +51,9 @@ mobile/.expo/
# Editors and operating systems
.idea/
.vscode/
.vscode/*
!.vscode/settings.json
!.vscode/extensions.json
*.swp
*.swo
.DS_Store
@@ -132,7 +134,14 @@ const tsconfigRootDir = dirname(fileURLToPath(import.meta.url));
export default tseslint.config(
{
ignores: ["node_modules/**", "dist/**", ".wrnexus/**", "**/.wrnexus/**", "mobile/android/**", "mobile/ios/**"],
ignores: [
"node_modules/**",
"dist/**",
".wrnexus/**",
"**/.wrnexus/**",
"mobile/android/**",
"mobile/ios/**",
],
},
{
languageOptions: {
@@ -187,6 +196,23 @@ indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
`,
".vscode/settings.json": `{
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"prettier.requireConfig": true,
"[wrn]": {
"editor.defaultFormatter": "wrnexus.wrnexus",
"editor.formatOnSave": true
}
}
`,
".vscode/extensions.json": `{
"recommendations": ["wrnexus.wrnexus", "esbenp.prettier-vscode", "dbaeumer.vscode-eslint"]
}
`,
"wrnexus.config.ts": `import type { AppConfig } from "@wrnexus/styles";
@@ -274,7 +300,13 @@ Allow: /
@custom-variant dark (&:where([data-theme="dark"], [data-theme="dark"] *));
body {
font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
font-family:
ui-sans-serif,
system-ui,
-apple-system,
"Segoe UI",
Roboto,
sans-serif;
}
`,
"app/pages/index.wrn": `// Home page (route: /). SSR-first: the view is server-rendered, then components
+84
View File
@@ -172,6 +172,87 @@ const MIGRATIONS: Migration[] = [
if (!ctx.dryRun) writeFileSync(file, JSON.stringify(pkg, null, 2) + "\n", "utf8");
},
},
{
version: "0.2.19",
id: "vscode-formatting",
description: "Add safe VS Code format-on-save defaults and extension recommendations",
apply(ctx) {
const vscode = join(ctx.appRoot, ".vscode");
const settings = join(vscode, "settings.json");
const extensions = join(vscode, "extensions.json");
const prettierIgnore = join(ctx.appRoot, ".prettierignore");
const gitignore = join(ctx.appRoot, ".gitignore");
const files: Array<[string, string, string]> = [
[
settings,
JSON.stringify(
{
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": { "source.fixAll.eslint": "explicit" },
"prettier.requireConfig": true,
"[wrn]": {
"editor.defaultFormatter": "wrnexus.wrnexus",
"editor.formatOnSave": true,
},
},
null,
2,
) + "\n",
".vscode/settings.json",
],
[
extensions,
JSON.stringify(
{
recommendations: [
"wrnexus.wrnexus",
"esbenp.prettier-vscode",
"dbaeumer.vscode-eslint",
],
},
null,
2,
) + "\n",
".vscode/extensions.json",
],
];
for (const [file, contents, label] of files) {
if (existsSync(file)) continue;
ctx.log(`+ ${label}`);
if (!ctx.dryRun) {
mkdirSync(vscode, { recursive: true });
writeFileSync(file, contents, "utf8");
}
}
const prettier = existsSync(prettierIgnore) ? readFileSync(prettierIgnore, "utf8") : "";
if (!new Set(prettier.split(/\r?\n/).map((line) => line.trim())).has("CLAUDE.md")) {
ctx.log("+ .prettierignore: CLAUDE.md");
if (!ctx.dryRun) {
writeFileSync(prettierIgnore, `${prettier.replace(/\s*$/, "")}\nCLAUDE.md\n`, "utf8");
}
}
if (existsSync(gitignore)) {
const current = readFileSync(gitignore, "utf8");
const lines = current.split(/\r?\n/);
const vscodeIgnore = lines.findIndex((line) => line.trim() === ".vscode/");
const needed = [".vscode/*", "!.vscode/settings.json", "!.vscode/extensions.json"];
if (vscodeIgnore >= 0 || needed.some((entry) => !lines.includes(entry))) {
ctx.log("~ .gitignore: track shared VS Code configuration");
if (!ctx.dryRun) {
const filtered = lines.filter((line) => line.trim() !== ".vscode/");
const have = new Set(filtered.map((line) => line.trim()));
for (const entry of needed) if (!have.has(entry)) filtered.push(entry);
writeFileSync(gitignore, filtered.join("\n").replace(/\n*$/, "\n"), "utf8");
}
}
}
},
},
];
/** Release tooling uses this to require an explicit migration entry per version. */
@@ -246,6 +327,9 @@ function backupProjectFiles(appRoot: string, from: string, target: string): stri
"wrnexus.config.mjs",
"tsconfig.json",
".gitignore",
".prettierignore",
".vscode/settings.json",
".vscode/extensions.json",
"CLAUDE.md",
"public/llms.txt",
]) {
+41
View File
@@ -2,6 +2,7 @@ import { expect, test } from "bun:test";
import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { format } from "prettier";
import { scaffoldApp } from "../src/create.ts";
import { currentCliVersion } from "../src/update-notifier.ts";
@@ -69,6 +70,15 @@ test("scaffoldApp pins the current framework and exposes llms.txt publicly", ()
expect(existsSync(join(root, "public", "llms.txt"))).toBe(true);
expect(existsSync(join(root, "llms.txt"))).toBe(false);
expect(readFileSync(join(root, ".prettierignore"), "utf8")).toContain("CLAUDE.md");
expect(readFileSync(join(root, ".vscode", "settings.json"), "utf8")).toContain(
'"editor.formatOnSave": true',
);
expect(readFileSync(join(root, ".vscode", "settings.json"), "utf8")).toContain(
'"editor.defaultFormatter": "wrnexus.wrnexus"',
);
expect(readFileSync(join(root, ".vscode", "extensions.json"), "utf8")).toContain(
"wrnexus.wrnexus",
);
expect(readFileSync(join(root, "app", "pages", "index.wrn"), "utf8")).toContain(
'href="/api/hello"',
);
@@ -77,3 +87,34 @@ test("scaffoldApp pins the current framework and exposes llms.txt publicly", ()
rmSync(parent, { recursive: true, force: true });
}
});
test("scaffoldApp emits Prettier-compliant editor and framework files", async () => {
const parent = mkdtempSync(join(tmpdir(), "wrnexus-create-"));
const root = join(parent, "formatted-app");
try {
scaffoldApp(root, "formatted-app");
for (const relative of [
"app/styles/global.css",
"eslint.config.js",
".vscode/settings.json",
".vscode/extensions.json",
]) {
const file = join(root, relative);
const source = readFileSync(file, "utf8");
const formatted = await format(source, {
filepath: file,
printWidth: 100,
tabWidth: 2,
useTabs: false,
semi: true,
singleQuote: false,
trailingComma: "all",
endOfLine: "lf",
});
expect(formatted).toBe(source);
}
} finally {
rmSync(parent, { recursive: true, force: true });
}
});
+32
View File
@@ -61,3 +61,35 @@ test("0.2.18 migration adds helpers to existing apps and is idempotent", () => {
rmSync(root, { recursive: true, force: true });
}
});
test("0.2.19 migration adds VS Code formatting defaults without overwriting settings", () => {
const root = mkdtempSync(join(tmpdir(), "wrnexus-update-"));
mkdirSync(join(root, "app", "pages"), { recursive: true });
mkdirSync(join(root, ".vscode"), { recursive: true });
writeFileSync(
join(root, "package.json"),
JSON.stringify({ name: "editor-app", wrnexus: { version: "0.2.18" } }),
);
writeFileSync(join(root, ".gitignore"), ".vscode/\n");
writeFileSync(join(root, ".prettierignore"), "node_modules/\n");
writeFileSync(join(root, ".vscode", "settings.json"), '{"editor.tabSize":4}\n');
try {
updateApp(root, "0.2.19", false);
updateApp(root, "0.2.19", false);
expect(readFileSync(join(root, ".vscode", "settings.json"), "utf8")).toBe(
'{"editor.tabSize":4}\n',
);
expect(readFileSync(join(root, ".vscode", "extensions.json"), "utf8")).toContain(
"wrnexus.wrnexus",
);
expect(readFileSync(join(root, ".prettierignore"), "utf8")).toBe("node_modules/\nCLAUDE.md\n");
const gitignore = readFileSync(join(root, ".gitignore"), "utf8");
expect(gitignore).not.toContain(".vscode/\n");
expect(gitignore).toContain("!.vscode/settings.json");
expect(gitignore).toContain("!.vscode/extensions.json");
} finally {
rmSync(root, { recursive: true, force: true });
}
});