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, scaffoldFrameworkRange } from "../src/create.ts"; import { currentCliVersion } from "../src/update-notifier.ts"; test("scaffoldApp creates a comprehensive .gitignore", () => { const parent = mkdtempSync(join(tmpdir(), "wrnexus-create-")); const root = join(parent, "demo-app"); try { scaffoldApp(root, "demo-app"); const file = join(root, ".gitignore"); expect(existsSync(file)).toBe(true); const contents = readFileSync(file, "utf8"); for (const entry of [ "node_modules/", "dist/", ".wrnexus/", ".env.*", "!.env.example", "!.env.*.example", "*.log", "*.db", "uploads/", "coverage/", "mobile/android/", ".vscode/", "*.tsbuildinfo", ]) { expect(contents).toContain(entry); } } finally { rmSync(parent, { recursive: true, force: true }); } }); test("scaffoldApp includes production build and start scripts", () => { const parent = mkdtempSync(join(tmpdir(), "wrnexus-create-")); const root = join(parent, "production-app"); try { scaffoldApp(root, "production-app"); const pkg = JSON.parse(readFileSync(join(root, "package.json"), "utf8")); expect(pkg.scripts.build).toBe("wrnexus build ."); expect(pkg.scripts.start).toBe("bun dist/server.js"); expect(pkg.scripts.production).toBe("bun run build && bun run start"); expect(pkg.scripts.typecheck).toBe("tsc --noEmit"); expect(pkg.scripts.test).toBe("wrnexus test ."); expect(pkg.scripts.check).toBe( "bun run typecheck && bun run lint && bun run test && bun run format:check", ); } finally { rmSync(parent, { recursive: true, force: true }); } }); test("scaffoldApp includes the complete v0.8 configuration and starter structure", () => { const parent = mkdtempSync(join(tmpdir(), "wrnexus-create-")); const root = join(parent, "complete-app"); try { scaffoldApp(root, "complete-app"); const pkg = JSON.parse(readFileSync(join(root, "package.json"), "utf8")); const config = readFileSync(join(root, "wrnexus.config.ts"), "utf8"); const documentLayout = readFileSync(join(root, "app/layouts/document.wrn"), "utf8"); expect(documentLayout).toContain('runtime = "server"'); expect(documentLayout).not.toContain("cookies ="); for (const packageName of [ "@wrnexus/auth", "@wrnexus/captcha", "@wrnexus/db", "@wrnexus/encryption", "@wrnexus/i18n", "@wrnexus/image", "@wrnexus/jwt", "@wrnexus/observability", "@wrnexus/realtime", "@wrnexus/security", "@wrnexus/store", "@wrnexus/ui", "@wrnexus/uploader", "@wrnexus/validation", ]) { expect(pkg.dependencies[packageName]).toBe(scaffoldFrameworkRange); } for (const block of [ "plugins:", "imports:", "types:", "stores:", "performance:", "observability:", "tenancy:", "build:", "navigation:", "devToolbar:", "theme:", "i18n:", "db:", "databases:", "storage:", "realtime:", "profiles:", ]) { expect(config).toContain(block); } for (const relative of [ ".env.example", ".env.test.example", "app/locales/en.json", "app/db/migrations/0001_init.sql", "app/db/seed.ts", "app/schemas/contact.ts", "app/realtime/chat.ts", "test/smoke.test.ts", ]) { expect(existsSync(join(root, relative))).toBe(true); } } finally { rmSync(parent, { recursive: true, force: true }); } }); test("scaffoldApp uses compatible framework packages and pins the current CLI", () => { const parent = mkdtempSync(join(tmpdir(), "wrnexus-create-")); const root = join(parent, "current-app"); try { scaffoldApp(root, "current-app"); const pkg = JSON.parse(readFileSync(join(root, "package.json"), "utf8")); const version = currentCliVersion(); expect(pkg.wrnexus.version).toBe(version); expect(pkg.dependencies["@wrnexus/helpers"]).toBe(scaffoldFrameworkRange); for (const dependency of Object.values(pkg.dependencies) as string[]) { expect(dependency).toBe(scaffoldFrameworkRange); } expect(pkg.devDependencies["@wrnexus/cli"]).toBe(version); expect(pkg.devDependencies["@iconify/tailwind4"]).toBe("^1.2.3"); expect(pkg.devDependencies["@iconify-json/lucide"]).toBe("^1.2.123"); const globalCss = readFileSync(join(root, "app", "styles", "global.css"), "utf8"); expect(globalCss).toContain('@plugin "@iconify/tailwind4";'); expect(globalCss).toContain('"Plus Jakarta Sans"'); expect(globalCss).toContain("--wrn-font-sans"); const appConfig = readFileSync(join(root, "wrnexus.config.ts"), "utf8"); expect(appConfig).toContain('family: "Plus Jakarta Sans"'); expect(appConfig).toContain("weights: [400, 500, 600, 700]"); 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"', ); expect(existsSync(join(root, "app", "pages", "about.wrn"))).toBe(true); } finally { 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 }); } });