import { describe, it, expect } from "vitest"; import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { assertOptionalPackageNotPresent, addToTranspilePackages, splicePluginRulesAt, addBoundariesEntry, emitTemplateTree, } from "./core-package-utils.js"; describe("assertOptionalPackageNotPresent", () => { it("throws if packages// exists in cwd", () => { const tmp = mkdtempSync(join(tmpdir(), "core-pkg-")); mkdirSync(join(tmp, "packages", "core-foo"), { recursive: true }); expect(() => assertOptionalPackageNotPresent("core-foo", tmp)).toThrow(/already exists/); }); it("returns silently if packages// is absent", () => { const tmp = mkdtempSync(join(tmpdir(), "core-pkg-")); expect(() => assertOptionalPackageNotPresent("core-foo", tmp)).not.toThrow(); }); }); describe("addToTranspilePackages", () => { it("inserts package name alphabetically into transpilePackages array", () => { const tmp = mkdtempSync(join(tmpdir(), "core-pkg-")); const cfgPath = join(tmp, "next.config.mjs"); writeFileSync( cfgPath, `const nextConfig = { transpilePackages: [ "@repo/core-cms", "@repo/core-shared", ], }; `, ); addToTranspilePackages(cfgPath, "@repo/core-realtime"); const result = readFileSync(cfgPath, "utf8"); // Order should be: core-cms, core-realtime, core-shared const order = result.match(/@repo\/core-\w+/g) ?? []; expect(order).toEqual(["@repo/core-cms", "@repo/core-realtime", "@repo/core-shared"]); }); it("is idempotent — duplicate insertion is a no-op", () => { const tmp = mkdtempSync(join(tmpdir(), "core-pkg-")); const cfgPath = join(tmp, "next.config.mjs"); writeFileSync( cfgPath, `const nextConfig = { transpilePackages: ["@repo/core-realtime"], }; `, ); addToTranspilePackages(cfgPath, "@repo/core-realtime"); const result = readFileSync(cfgPath, "utf8"); expect(result.match(/@repo\/core-realtime/g)?.length).toBe(1); }); }); describe("splicePluginRulesAt", () => { it("inserts rule block at the named anchor", () => { const tmp = mkdtempSync(join(tmpdir(), "core-pkg-")); const path = join(tmp, "base.js"); writeFileSync( path, `before\n// \nafter\n`, ); splicePluginRulesAt(path, "realtime-rules", "INSERTED_BLOCK"); const result = readFileSync(path, "utf8"); expect(result).toContain("// \nINSERTED_BLOCK\nafter"); }); it("is idempotent — re-inserting same block at anchor is a no-op", () => { const tmp = mkdtempSync(join(tmpdir(), "core-pkg-")); const path = join(tmp, "base.js"); writeFileSync( path, `// \nINSERTED_BLOCK\nrest\n`, ); splicePluginRulesAt(path, "realtime-rules", "INSERTED_BLOCK"); const result = readFileSync(path, "utf8"); expect(result.match(/INSERTED_BLOCK/g)?.length).toBe(1); }); }); describe("addBoundariesEntry", () => { it("inserts entry before the packages/core-* wildcard", () => { const tmp = mkdtempSync(join(tmpdir(), "core-pkg-")); const path = join(tmp, "base.js"); writeFileSync( path, `"boundaries/elements": [ { type: "core-composition", pattern: "packages/core-cms" }, { type: "core", pattern: "packages/core-*" }, { type: "feature", pattern: "packages/!(core-*)" }, ],`, ); addBoundariesEntry(path, "packages/core-realtime", { mode: "folder" }); const result = readFileSync(path, "utf8"); // The new entry must appear BEFORE the packages/core-* wildcard const newIdx = result.indexOf(`pattern: "packages/core-realtime"`); const wildcardIdx = result.indexOf(`pattern: "packages/core-*"`); expect(newIdx).toBeGreaterThan(0); expect(newIdx).toBeLessThan(wildcardIdx); }); it("is idempotent — re-inserting same entry is a no-op", () => { const tmp = mkdtempSync(join(tmpdir(), "core-pkg-")); const path = join(tmp, "base.js"); writeFileSync( path, `"boundaries/elements": [ { type: "core", pattern: "packages/core-realtime", mode: "folder" }, { type: "core", pattern: "packages/core-*" }, ],`, ); addBoundariesEntry(path, "packages/core-realtime", { mode: "folder" }); const result = readFileSync(path, "utf8"); expect(result.match(/packages\/core-realtime/g)?.length).toBe(1); }); }); describe("emitTemplateTree", () => { it("produces an `add` plop action per .hbs file in the template directory", () => { // emitTemplateTree reads from turbo/generators/templates/ — the test uses a // temp template directory injected via the `templatesRoot` arg. const tmpTemplates = mkdtempSync(join(tmpdir(), "tpl-")); mkdirSync(join(tmpTemplates, "core-package", "demo", "src"), { recursive: true }); writeFileSync(join(tmpTemplates, "core-package", "demo", "package.json.hbs"), "{}"); writeFileSync(join(tmpTemplates, "core-package", "demo", "src", "index.ts.hbs"), "export {};"); const actions = emitTemplateTree("core-package/demo", "packages/demo", { templatesRoot: tmpTemplates }); expect(actions).toHaveLength(2); expect(actions[0]!.type).toBe("add"); const paths = actions.map((a) => (a as { path: string }).path); expect(paths).toContain("packages/demo/package.json"); expect(paths).toContain("packages/demo/src/index.ts"); }); });