Files
agentic-dev/turbo/generators/lib/core-package-utils.test.ts
Danijel Martinek b593bea8ca fix(generators): wire lint+typecheck into pipeline and fix uncovered errors
The turbo/generators package shipped without `lint` or `typecheck` scripts,
so `pnpm lint` / `pnpm typecheck` at the root silently skipped it. This
masked 2 ESLint errors (unused imports) and 11 TypeScript errors (relative
imports missing the `.js` extension required by `moduleResolution: NodeNext`,
plus JSON imports missing the `with { type: "json" }` attribute).

- Add `lint` and `typecheck` scripts to turbo/generators/package.json so the
  turbo pipeline picks them up (lint: 14/14, was 13/13).
- Add `.js` extensions to 7 relative imports across config.test.ts,
  lib/core-package-utils.test.ts, lib/snapshot.test.ts, and the 4 e2e tests.
- Add `with { type: "json" }` attributes to 4 snapshot JSON imports in the
  e2e tests.
- Remove unused `existsSync` and `splicePluginImportsAt` imports from
  lib/core-package-utils.test.ts.
- Declare `@repo/core-typescript` + `typescript` devDependencies so the
  generators package can run `tsc --noEmit` for typecheck.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 08:09:17 +02:00

147 lines
5.4 KiB
TypeScript

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/<name>/ 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/<name>/ 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// <gen:realtime-rules>\nafter\n`,
);
splicePluginRulesAt(path, "realtime-rules", "INSERTED_BLOCK");
const result = readFileSync(path, "utf8");
expect(result).toContain("// <gen:realtime-rules>\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,
`// <gen:realtime-rules>\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");
});
});