import { describe, expect, test } from "bun:test"; import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { createPluginRunner, discoverPlugins, resolvePlugins } from "../src/index.ts"; describe("plugin ordering", () => { test("orders pre, normal, and post plugins", () => { expect( resolvePlugins([ { name: "post", enforce: "post" }, { name: "normal" }, { name: "pre", enforce: "pre" }, ]).map((plugin) => plugin.name), ).toEqual(["pre", "normal", "post"]); }); test("respects explicit dependencies", () => { expect( resolvePlugins([{ name: "b", after: ["a"] }, { name: "a" }]).map((plugin) => plugin.name), ).toEqual(["a", "b"]); }); }); function context(root: string) { return { root, mode: "development" as const, command: "dev" as const, metadata: new Map(), warn() {}, }; } test("normalizes package runtimes, assets, routes, middleware, and migrations", async () => { const root = mkdtempSync(join(tmpdir(), "wrnexus-plugin-")); try { const runner = createPluginRunner( { name: "system", clientRuntimes: [{ id: "system", source: "window.system = true" }], assets: [{ id: "system-data", source: "{}", contentType: "application/json" }], routeEntries: [{ kind: "api", path: "/api/system", entry: join(root, "route.ts") }], middleware: [join(root, "middleware.ts")], migrations: [{ id: "system-schema", source: "-- +up\nSELECT 1;" }], }, context(root), ); const contributions = await runner.contributions(); expect(contributions.clientRuntimes[0]?.publicPath).toBe("/__wrnexus/assets/system.js"); expect(contributions.assets[0]?.publicPath).toBe("/__wrnexus/assets/system-data.json"); expect(contributions.routes[0]?.path).toBe("/api/system"); expect(contributions.middleware).toEqual([join(root, "middleware.ts")]); expect(contributions.migrations[0]?.id).toBe("system-schema"); } finally { rmSync(root, { recursive: true, force: true }); } }); test("auto-discovers package.json contributions from workspace dependencies", async () => { const root = mkdtempSync(join(tmpdir(), "wrnexus-discovery-")); const app = join(root, "apps", "web"); const pkg = join(root, "packages", "example-system"); try { mkdirSync(join(app, "app", "pages"), { recursive: true }); mkdirSync(join(pkg, "components"), { recursive: true }); mkdirSync(join(pkg, "assets"), { recursive: true }); writeFileSync( join(root, "package.json"), JSON.stringify({ private: true, workspaces: ["apps/*", "packages/*"] }), ); writeFileSync( join(app, "package.json"), JSON.stringify({ name: "web", dependencies: { "@wrnexus/example-system": "workspace:*" } }), ); writeFileSync( join(pkg, "components", "Example.wrn"), "component Example { view {
Example
} }", ); writeFileSync(join(pkg, "assets", "runtime.js"), "window.example = true;"); writeFileSync( join(pkg, "package.json"), JSON.stringify({ name: "@wrnexus/example-system", version: "0.4.0", wrnexus: { components: ["./components"], clientRuntimes: [{ id: "example-system", entry: "./assets/runtime.js" }], migrations: [{ id: "example-schema", source: "-- +up\nSELECT 1;" }], }, }), ); const discovered = await discoverPlugins(app, undefined); const runner = createPluginRunner(discovered, context(app)); const contributions = await runner.contributions(); expect(runner.plugins.map((plugin) => plugin.name)).toContain( "@wrnexus/example-system/manifest", ); expect(contributions.componentDirs).toEqual([join(pkg, "components")]); expect(contributions.clientRuntimes[0]?.entry).toBe(join(pkg, "assets", "runtime.js")); expect(contributions.migrations[0]?.id).toBe("example-schema"); } finally { rmSync(root, { recursive: true, force: true }); } }); test("rejects duplicate runtime paths and migration ids", async () => { const runner = createPluginRunner( [ { name: "one", clientRuntimes: [{ id: "one", source: "", publicPath: "/same.js" }], migrations: [{ id: "schema", source: "SELECT 1" }], }, { name: "two", clientRuntimes: [{ id: "two", source: "", publicPath: "/same.js" }], migrations: [{ id: "schema", source: "SELECT 2" }], }, ], context("."), ); await expect(runner.contributions()).rejects.toThrow("WRN-PLUGIN-RUNTIME-PATH-DUPLICATE"); }); test("rejects client-runtime and package-asset path collisions", async () => { const runner = createPluginRunner( [ { name: "runtime-owner", clientRuntimes: [{ id: "runtime", source: "", publicPath: "/shared.js" }], }, { name: "asset-owner", assets: [ { id: "asset", source: "window.asset = true;", contentType: "text/javascript", publicPath: "/shared.js", }, ], }, ], context("."), ); await expect(runner.contributions()).rejects.toThrow("WRN-PLUGIN-PUBLIC-PATH-DUPLICATE"); }); test("strict discovery surfaces invalid package plugin exports", async () => { const root = mkdtempSync(join(tmpdir(), "wrnexus-strict-discovery-")); const app = join(root, "apps", "web"); const pkg = join(root, "packages", "broken-system"); try { mkdirSync(join(app, "app", "pages"), { recursive: true }); mkdirSync(pkg, { recursive: true }); writeFileSync( join(root, "package.json"), JSON.stringify({ private: true, workspaces: ["apps/*", "packages/*"] }), ); writeFileSync( join(app, "package.json"), JSON.stringify({ name: "web", dependencies: { "@wrnexus/broken-system": "workspace:*" }, }), ); writeFileSync(join(pkg, "plugin.mjs"), "export const unrelated = true;\n"); writeFileSync( join(pkg, "package.json"), JSON.stringify({ name: "@wrnexus/broken-system", version: "0.4.0", wrnexus: { plugin: "./plugin.mjs" }, }), ); await expect(discoverPlugins(app, undefined, { strict: true })).rejects.toThrow( "WRN-PLUGIN-DISCOVERY", ); } finally { rmSync(root, { recursive: true, force: true }); } }); test("orders package style contributions by pre, normal, and post", async () => { const runner = createPluginRunner( { name: "styles", styleSources: [ { id: "post", source: "post", order: "post" }, { id: "normal", source: "normal" }, { id: "pre", source: "pre", order: "pre" }, { id: "normal-two", source: "normal-two", order: "normal" }, ], }, context("."), ); expect((await runner.contributions()).styles.map((style) => style.id)).toEqual([ "pre", "normal", "normal-two", "post", ]); });