209 lines
6.9 KiB
TypeScript
209 lines
6.9 KiB
TypeScript
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<string, unknown>(),
|
|
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 { <div>Example</div> } }",
|
|
);
|
|
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",
|
|
]);
|
|
});
|