180 lines
6.5 KiB
TypeScript
180 lines
6.5 KiB
TypeScript
import { test, expect, afterEach } from "bun:test";
|
|
import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { loadAppConfig, resolveProfile, loadEnv, renderStyles } from "../src/index.ts";
|
|
|
|
afterEach(() => {
|
|
delete process.env.WRNEXUS_PROFILE;
|
|
});
|
|
|
|
test("resolveProfile: explicit > WRNEXUS_PROFILE > mode default", () => {
|
|
delete process.env.WRNEXUS_PROFILE;
|
|
expect(resolveProfile({ mode: "development" })).toBe("development");
|
|
expect(resolveProfile({ mode: "production" })).toBe("production");
|
|
expect(resolveProfile({ explicit: "uat", mode: "production" })).toBe("uat");
|
|
process.env.WRNEXUS_PROFILE = "test";
|
|
expect(resolveProfile({ mode: "production" })).toBe("test");
|
|
expect(resolveProfile({ explicit: "uat" })).toBe("uat"); // explicit still wins
|
|
});
|
|
|
|
test("loadAppConfig deep-merges the active profile and strips `profiles`", async () => {
|
|
const dir = mkdtempSync(join(tmpdir(), "wire-cfg-"));
|
|
writeFileSync(
|
|
join(dir, "wrnexus.config.mjs"),
|
|
`export default {
|
|
port: 3000,
|
|
db: { driver: "sqlite", url: "file:./dev.db" },
|
|
seo: { title: "Base", siteName: "App" },
|
|
profiles: {
|
|
prod: { db: { driver: "postgres", url: "postgres://prod" }, port: 8080 },
|
|
uat: { seo: { title: "UAT" } },
|
|
},
|
|
};`,
|
|
);
|
|
|
|
const dev = await loadAppConfig(dir, "development");
|
|
expect(dev.db).toEqual({ driver: "sqlite", url: "file:./dev.db" });
|
|
expect((dev as { profiles?: unknown }).profiles).toBeUndefined();
|
|
|
|
const prod = await loadAppConfig(dir, "prod");
|
|
expect(prod.db).toEqual({ driver: "postgres", url: "postgres://prod" });
|
|
expect(prod.port).toBe(8080);
|
|
expect(prod.seo).toEqual({ title: "Base", siteName: "App" }); // untouched
|
|
|
|
const uat = await loadAppConfig(dir, "uat");
|
|
expect(uat.seo).toEqual({ title: "UAT", siteName: "App" }); // deep-merged
|
|
expect(uat.db!.driver).toBe("sqlite");
|
|
});
|
|
|
|
test("loadEnv layers .env files by precedence; real env always wins", () => {
|
|
const dir = mkdtempSync(join(tmpdir(), "wire-env-"));
|
|
writeFileSync(join(dir, ".env"), 'BASE=1\nSHARED=base\n# a comment\nQUOTED="hi there"\n');
|
|
writeFileSync(join(dir, ".env.prod"), "SHARED=prod\nPROD_ONLY=yes\nREALVAR=fromfile\n");
|
|
writeFileSync(join(dir, ".env.prod.local"), "SHARED=local\n");
|
|
|
|
process.env.REALVAR = "real"; // simulate a real environment variable
|
|
try {
|
|
const loaded = loadEnv(dir, "prod");
|
|
expect(loaded.BASE).toBe("1");
|
|
expect(process.env.BASE).toBe("1");
|
|
expect(process.env.QUOTED).toBe("hi there"); // quotes stripped
|
|
expect(process.env.SHARED).toBe("local"); // .env.prod.local beats .env.prod beats .env
|
|
expect(process.env.PROD_ONLY).toBe("yes");
|
|
expect(process.env.REALVAR).toBe("real"); // real env NOT overridden by a file
|
|
} finally {
|
|
for (const k of ["BASE", "SHARED", "PROD_ONLY", "QUOTED", "REALVAR"]) delete process.env[k];
|
|
}
|
|
});
|
|
|
|
test("loadAppConfig loads env before evaluating the config module", async () => {
|
|
const dir = mkdtempSync(join(tmpdir(), "wire-config-env-"));
|
|
writeFileSync(join(dir, ".env"), "DATABASE_URL=postgres://pms-from-env\n");
|
|
writeFileSync(
|
|
join(dir, "wrnexus.config.mjs"),
|
|
`export default {
|
|
db: { driver: "postgres", url: process.env.DATABASE_URL },
|
|
};`,
|
|
);
|
|
|
|
try {
|
|
delete process.env.DATABASE_URL;
|
|
const config = await loadAppConfig(dir, "development");
|
|
expect(config.db?.url).toBe("postgres://pms-from-env");
|
|
} finally {
|
|
delete process.env.DATABASE_URL;
|
|
rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("package-aware styles add relative imports and Tailwind scan sources", async () => {
|
|
const root = mkdtempSync(join(tmpdir(), "wrnexus-styles-"));
|
|
try {
|
|
const appDir = join(root, "app");
|
|
const entry = join(appDir, "styles", "global.css");
|
|
const packageSource = join(root, "packages", "system", "components");
|
|
const packageCss = join(root, "packages", "system", "system.css");
|
|
mkdirSync(join(appDir, "styles"), { recursive: true });
|
|
mkdirSync(packageSource, { recursive: true });
|
|
writeFileSync(entry, '@import "tailwindcss";\n');
|
|
writeFileSync(packageCss, ".system{}\n");
|
|
let wrapper = "";
|
|
const css = await renderStyles(
|
|
{
|
|
entryPath: entry,
|
|
appDir,
|
|
appRoot: root,
|
|
mode: "development",
|
|
sources: [packageSource],
|
|
entries: [packageCss],
|
|
},
|
|
{
|
|
process: async ({ entryPath, originalEntryPath, sources }) => {
|
|
expect(originalEntryPath).toBe(entry);
|
|
expect(sources).toEqual([packageSource]);
|
|
wrapper = readFileSync(entryPath!, "utf8");
|
|
return ".compiled{}";
|
|
},
|
|
},
|
|
);
|
|
expect(css).toBe(".compiled{}");
|
|
expect(wrapper).toContain('@import "../../app/styles/global.css";');
|
|
expect(wrapper).toContain('@source "../../packages/system/components";');
|
|
expect(wrapper).not.toContain(root.replace(/\\/g, "/"));
|
|
} finally {
|
|
rmSync(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("production style processors fail closed by default", async () => {
|
|
const root = mkdtempSync(join(tmpdir(), "wrnexus-styles-"));
|
|
try {
|
|
const entry = join(root, "global.css");
|
|
writeFileSync(entry, "body{}\n");
|
|
await expect(
|
|
renderStyles(
|
|
{ entryPath: entry, appDir: root, appRoot: root, mode: "production" },
|
|
{
|
|
process: () => {
|
|
throw new Error("processor unavailable");
|
|
},
|
|
},
|
|
),
|
|
).rejects.toThrow("Stylesheet processing failed");
|
|
} finally {
|
|
rmSync(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("package CSS entries build without an application stylesheet", async () => {
|
|
const root = mkdtempSync(join(tmpdir(), "wrnexus-package-styles-"));
|
|
try {
|
|
const packageCss = join(root, "packages", "system", "system.css");
|
|
mkdirSync(join(root, "packages", "system"), { recursive: true });
|
|
writeFileSync(packageCss, ".system{}\n");
|
|
let wrapper = "";
|
|
|
|
const css = await renderStyles(
|
|
{
|
|
entryPath: null,
|
|
appDir: join(root, "app"),
|
|
appRoot: root,
|
|
mode: "development",
|
|
entries: [packageCss],
|
|
},
|
|
{
|
|
process: async ({ entryPath, originalEntryPath }) => {
|
|
expect(originalEntryPath).toBeNull();
|
|
wrapper = readFileSync(entryPath!, "utf8");
|
|
return ".package-compiled{}";
|
|
},
|
|
},
|
|
);
|
|
|
|
expect(css).toBe(".package-compiled{}");
|
|
expect(wrapper).toContain('@import "../../packages/system/system.css";');
|
|
} finally {
|
|
rmSync(root, { recursive: true, force: true });
|
|
}
|
|
});
|