first commit

This commit is contained in:
2026-07-12 15:55:18 +05:30
commit ee98026cc5
404 changed files with 44522 additions and 0 deletions
+30
View File
@@ -0,0 +1,30 @@
import { test, expect } from "bun:test";
import { mkdtempSync, existsSync, readFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { generateDocker } from "../src/docker.ts";
test("generateDocker scaffolds Dockerfile, .dockerignore, compose", () => {
const dir = mkdtempSync(join(tmpdir(), "wire-docker-"));
generateDocker(dir);
expect(existsSync(join(dir, "Dockerfile"))).toBe(true);
expect(existsSync(join(dir, ".dockerignore"))).toBe(true);
expect(existsSync(join(dir, "docker-compose.yml"))).toBe(true);
const dockerfile = readFileSync(join(dir, "Dockerfile"), "utf8");
expect(dockerfile).toContain("FROM oven/bun:1 AS build");
expect(dockerfile).toContain("bun run build");
expect(dockerfile).toContain("/healthz"); // health check uses the app endpoint
expect(dockerfile).toContain('CMD ["bun", "dist/server.js"]');
expect(readFileSync(join(dir, "docker-compose.yml"), "utf8")).toContain("postgres:16-alpine");
});
test("generateDocker does not overwrite existing files", () => {
const dir = mkdtempSync(join(tmpdir(), "wire-docker-"));
generateDocker(dir);
const before = readFileSync(join(dir, "Dockerfile"), "utf8");
// second run must leave the file untouched
generateDocker(dir);
expect(readFileSync(join(dir, "Dockerfile"), "utf8")).toBe(before);
});
+21
View File
@@ -0,0 +1,21 @@
import { expect, test } from "bun:test";
import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { inspectProject } from "../src/doctor.ts";
test("doctor reports a healthy minimal project", () => {
const root = mkdtempSync(join(tmpdir(), "wrnexus-doctor-"));
mkdirSync(join(root, "app", "pages"), { recursive: true });
writeFileSync(join(root, "package.json"), JSON.stringify({ name: "app" }));
writeFileSync(join(root, "wrnexus.config.ts"), "export default {};");
const checks = inspectProject(root);
expect(checks.every((check) => check.ok)).toBe(true);
});
test("doctor returns actionable missing-project checks", () => {
const root = mkdtempSync(join(tmpdir(), "wrnexus-doctor-"));
const checks = inspectProject(root);
expect(checks.find((check) => check.name === "package.json")?.ok).toBe(false);
expect(checks.find((check) => check.name === "app/pages")?.detail).toBe("Create app/pages");
});
+34
View File
@@ -0,0 +1,34 @@
import { test, expect } from "bun:test";
import { scaffold } from "../src/generate.ts";
test("scaffold page: kebab file, PascalCase page name", () => {
const f = scaffold("page", "about-us");
expect(f.path).toBe("pages/about-us.wrn");
expect(f.content).toContain("page AboutUs {");
expect(f.content).toContain('layout = "public"');
});
test("scaffold component: props + class", () => {
const f = scaffold("component", "user-card");
expect(f.path).toBe("components/user-card.wrn");
expect(f.content).toContain("component UserCard {");
expect(f.content).toContain('class="wire-user-card"');
});
test("scaffold api: nested path keeps directories, exports GET", () => {
const f = scaffold("api", "users/list");
expect(f.path).toBe("api/users/list.ts");
expect(f.content).toContain("export async function GET");
});
test("scaffold schema: validation object", () => {
const f = scaffold("schema", "signup");
expect(f.path).toBe("schemas/signup.ts");
expect(f.content).toContain('import { v } from "@wrnexus/validation"');
expect(f.content).toContain("export default v.object(");
});
test("scaffold strips extensions from the given name", () => {
expect(scaffold("page", "home.wrn").path).toBe("pages/home.wrn");
expect(scaffold("api", "ping.ts").path).toBe("api/ping.ts");
});
+100
View File
@@ -0,0 +1,100 @@
import { expect, test } from "bun:test";
import { existsSync, mkdirSync, mkdtempSync, readFileSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { generateMobile, mobileOptions } from "../src/mobile.ts";
import { runMobileCommand } from "../src/mobile-command.ts";
import { nativeCatalog, runNativeCommand } from "../src/native-command.ts";
test("generateMobile scaffolds a configurable Capacitor project", async () => {
const dir = mkdtempSync(join(tmpdir(), "wire-mobile-"));
writeFileSync(join(dir, "package.json"), JSON.stringify({ name: "@acme/store-front" }));
await generateMobile(dir, {
appId: "com.acme.store",
appName: "Acme Store",
serverUrl: "https://store.example.com",
});
const config = readFileSync(join(dir, "mobile", "capacitor.config.ts"), "utf8");
expect(config).toContain('appId: mobile.appId ?? "com.acme.store"');
expect(config).toContain('appName: mobile.appName ?? "Acme Store"');
expect(config).toContain("...(mobile.capacitor ?? {})");
expect(config).not.toContain("errorPath:");
expect(config).toContain('"https://store.example.com"');
expect(existsSync(join(dir, "mobile", "web", "index.html"))).toBe(true);
const pkg = JSON.parse(readFileSync(join(dir, "mobile", "package.json"), "utf8"));
expect(pkg.dependencies["@capacitor/android"]).toBe("^8.0.0");
expect(pkg.scripts["open:ios"]).toBe("cap open ios");
});
test("generateMobile derives defaults and does not overwrite files", async () => {
const dir = mkdtempSync(join(tmpdir(), "wire-mobile-"));
writeFileSync(join(dir, "package.json"), JSON.stringify({ name: "demo-app" }));
await generateMobile(dir);
const path = join(dir, "mobile", "capacitor.config.ts");
const before = readFileSync(path, "utf8");
expect(before).toContain('appId: mobile.appId ?? "com.example.demoapp"');
expect(before).toContain('"http://localhost:3000"');
await generateMobile(dir, { appName: "Changed" });
expect(readFileSync(path, "utf8")).toBe(before);
});
test("mobileOptions parses generator flags", () => {
expect(
mobileOptions(["--app-id=com.acme.app", "--app-name=Acme", "--url=https://app.test"]),
).toEqual({
appId: "com.acme.app",
appName: "Acme",
serverUrl: "https://app.test",
mode: undefined,
});
});
test("generateMobile scaffolds a WebView-free native project from config", async () => {
const dir = mkdtempSync(join(tmpdir(), "wire-native-"));
writeFileSync(join(dir, "package.json"), JSON.stringify({ name: "native-demo" }));
writeFileSync(
join(dir, "wrnexus.config.mjs"),
`export default { mobile: { mode: "native", appId: "com.acme.native", apiUrl: "https://api.example.com" } }`,
);
await generateMobile(dir);
const pkg = JSON.parse(readFileSync(join(dir, "mobile", "package.json"), "utf8"));
expect(pkg.wrnexus.mode).toBe("native");
expect(pkg.dependencies.expo).toBeDefined();
expect(existsSync(join(dir, "mobile", "app", "index.tsx"))).toBe(true);
expect(readFileSync(join(dir, "mobile", "src", "wrnexus.ts"), "utf8")).toContain(
"https://api.example.com",
);
expect(existsSync(join(dir, "mobile", "capacitor.config.ts"))).toBe(false);
});
test("mobile compile turns wrn pages into Expo routes", async () => {
const dir = mkdtempSync(join(tmpdir(), "wire-native-compile-"));
writeFileSync(join(dir, "package.json"), JSON.stringify({ name: "native-compile" }));
writeFileSync(join(dir, "wrnexus.config.mjs"), `export default { mobile: { mode: "native" } }`);
await generateMobile(dir);
const pages = join(dir, "app", "pages");
const nested = join(pages, "account");
mkdirSync(nested, { recursive: true });
writeFileSync(
join(nested, "profile.wrn"),
"page Profile { view { <main><h1>Profile</h1></main> } }",
);
await runMobileCommand(dir, "compile");
const output = readFileSync(join(dir, "mobile", "app", "account", "profile.tsx"), "utf8");
expect(output).toContain("function Profile()");
expect(output).toContain("<View><Text>Profile</Text></View>");
});
test("native catalog uses built-in React Native sharing without installing expo-sharing", async () => {
expect(nativeCatalog.share?.expo).toBeUndefined();
const dir = mkdtempSync(join(tmpdir(), "wire-native-share-"));
writeFileSync(join(dir, "package.json"), JSON.stringify({ name: "native-share" }));
writeFileSync(join(dir, "wrnexus.config.mjs"), `export default { mobile: { mode: "native" } }`);
await generateMobile(dir);
await runNativeCommand(dir, "add", ["share"]);
const pkg = JSON.parse(readFileSync(join(dir, "mobile", "package.json"), "utf8"));
expect(pkg.dependencies["expo-sharing"]).toBeUndefined();
});