From d42d0e5c5c8005e3a42b4346abcf7a81aeba671f Mon Sep 17 00:00:00 2001 From: Danijel Martinek Date: Sat, 9 May 2026 14:19:22 +0200 Subject: [PATCH] feat(generators): ui byte-identical snapshot + dispatch entry + e2e test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Adds ui.snapshot.json (28 entries) computed from packages/core-ui - Wires CORE_PACKAGE_GENERATORS["ui"]: guard + emitTemplateTree + addToTranspilePackages + printUiNextSteps (web-tanstack/storybook wiring is printed, not generated — resists templating) - Adds "ui" to the core-package generator choices list - Adds core-package-ui.e2e.test.ts (byte-identical reconstruction) — passes in 14.6 s Co-Authored-By: Claude Sonnet 4.6 --- .../core-package/ui.snapshot.json | 114 ++++++++++++++++++ .../__tests__/core-package-ui.e2e.test.ts | 52 ++++++++ turbo/generators/config.ts | 45 ++++++- 3 files changed, 210 insertions(+), 1 deletion(-) create mode 100644 turbo/generators/__snapshots__/core-package/ui.snapshot.json create mode 100644 turbo/generators/__tests__/core-package-ui.e2e.test.ts diff --git a/turbo/generators/__snapshots__/core-package/ui.snapshot.json b/turbo/generators/__snapshots__/core-package/ui.snapshot.json new file mode 100644 index 0000000..93e6ad9 --- /dev/null +++ b/turbo/generators/__snapshots__/core-package/ui.snapshot.json @@ -0,0 +1,114 @@ +[ + { + "path": "AGENTS.md", + "sha256": "ea4a4eeea6cb08364fb7d9cf5c5757147442df1e9b1907b9118fcd313cd1c4a7" + }, + { + "path": "eslint.config.js", + "sha256": "64a85c158e386417d855793d256b9c59be38635ce2febcb5a6a15b17f4143745" + }, + { + "path": "package.json", + "sha256": "8ffd2e9c8adb1e0eab863a9abc81bb90a05afdbd625d1348e0f2ba7874d0680f" + }, + { + "path": "src/atoms/button/button.stories.tsx", + "sha256": "5ea74a9a4b2431b2a4dbae456bcce6d051842caad19e83ca8e15d99f79d36b5d" + }, + { + "path": "src/atoms/button/button.test.tsx", + "sha256": "e768f2c1ebd7e120d543a95c67a2a703a85c5ad6847b7e7f8420fa472771fb82" + }, + { + "path": "src/atoms/button/button.tsx", + "sha256": "4d62a3a9703721f64d397e468cdd90225b1d6dd739fd8c17b90962c8e9a7b2c3" + }, + { + "path": "src/atoms/button/index.ts", + "sha256": "fb0a14a0c15536e05a58d6d03f41afcee85fb6a24c5ca6910cb172ae26fbcc34" + }, + { + "path": "src/atoms/index.ts", + "sha256": "9ec29d14a7a7729b4eca9ba95b4016812a4c1c2cc9a46510d5f2d019744852b3" + }, + { + "path": "src/atoms/input/index.ts", + "sha256": "490ba122bc3161dc59d24b274372608b8b319f5c0270699fcdcc3fb6d5d4cffd" + }, + { + "path": "src/atoms/input/input.stories.tsx", + "sha256": "c95b51b7b3c4ad42f7b118a9a6569ad6d6a3200f6bf1f2d8f029a92bc307c53e" + }, + { + "path": "src/atoms/input/input.test.tsx", + "sha256": "b0fdb584cc9def4c1c7394e6f524c5d614ac74284a50d298ee48254dd4a80162" + }, + { + "path": "src/atoms/input/input.tsx", + "sha256": "bd2f477b3a79a29549d18db9605e1a34290fa31ea97d6292d6be77d2e95729f4" + }, + { + "path": "src/atoms/label/index.ts", + "sha256": "d9a205b2fd3133823a74c265e09b01dcd5068ce21b82217386d6166bbec68fb6" + }, + { + "path": "src/atoms/label/label.test.tsx", + "sha256": "ac8135a7a05a61e34ec9346148d9305190b5f8ec3dd3a5d15df2b2841e24caac" + }, + { + "path": "src/atoms/label/label.tsx", + "sha256": "6c6f13125fbc96b55806b9ff2230472e8d24ba45c7cd39e58573460691e23885" + }, + { + "path": "src/index.ts", + "sha256": "56303fd5e5d7fe574855887defcb974d2f86667dc926ea690f3d3757bbcc4617" + }, + { + "path": "src/lib/utils.ts", + "sha256": "d1f1e0d62cb8d8d1e04c26e14de842d8a151f75812d81b046c65b5d1fe8e4b27" + }, + { + "path": "src/molecules/form-field/form-field.stories.tsx", + "sha256": "914c842de560d80091970e3e1ae15c214c396893cc735b6f25460620528e5157" + }, + { + "path": "src/molecules/form-field/form-field.test.tsx", + "sha256": "a63244a6a00f7fdba01223c0697a9cfe93a52917e7d09b577e9ffa6547b1399c" + }, + { + "path": "src/molecules/form-field/form-field.tsx", + "sha256": "f51a6c4e9550fccaff90a932b9ad3906e8973c82faf0237cf07e1c864536f5f6" + }, + { + "path": "src/molecules/form-field/index.ts", + "sha256": "7fe955a7b2c65b1732ee799812be4ac3150346c5bdc91ea6afe0ea7c1034d2cc" + }, + { + "path": "src/molecules/index.ts", + "sha256": "eeb24cfbb8b6d2c1a5f5c144eae4db37f6829ad0f62eb163f428a666bc742774" + }, + { + "path": "src/organisms/index.ts", + "sha256": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881" + }, + { + "path": "src/styles/globals.css", + "sha256": "58c6e64ac0b26f3897f9a76f86a35dfff1acc7bae8e7e761bca0c8035763cfd2" + }, + { + "path": "src/templates/index.ts", + "sha256": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881" + }, + { + "path": "tsconfig.json", + "sha256": "7bbc28ad21b936ca80c7a3ec21094d34012d624ca1d4e63a6f3c3120abba551f" + }, + { + "path": "turbo.json", + "sha256": "ae544305202c960399110a31fe976e2c38577956c5aa738989f5d885f156d130" + }, + { + "path": "vitest.config.ts", + "sha256": "053df54e347b147d85632286d44e6587aa6c3ae39677fbc1a8599e89ae48a81c" + } +] diff --git a/turbo/generators/__tests__/core-package-ui.e2e.test.ts b/turbo/generators/__tests__/core-package-ui.e2e.test.ts new file mode 100644 index 0000000..e46c39b --- /dev/null +++ b/turbo/generators/__tests__/core-package-ui.e2e.test.ts @@ -0,0 +1,52 @@ +import { describe, it, expect } from "vitest"; +import { mkdtempSync, cpSync, readFileSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { execSync } from "node:child_process"; +import { join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import { computeSnapshot } from "../lib/snapshot"; +import expectedSnapshot from "../__snapshots__/core-package/ui.snapshot.json"; + +// Repo root is 2 levels up from turbo/generators/__tests__ +const REPO_ROOT = resolve(fileURLToPath(import.meta.url), "..", "..", "..", ".."); + +/** + * Strip "@repo/core-ui" from a package.json file in the tmp tree. + * Required when simulating a fresh scaffold: core-ui does not exist yet + * but the snapshot was captured before removal. Apps in the current tree + * still list it as a dependency. + */ +function stripCoreUiDep(pkgJsonPath: string): void { + const raw = readFileSync(pkgJsonPath, "utf8"); + const parsed = JSON.parse(raw) as Record>; + for (const section of ["dependencies", "devDependencies", "peerDependencies"] as const) { + if (parsed[section]?.["@repo/core-ui"]) { + delete parsed[section]["@repo/core-ui"]; + } + } + writeFileSync(pkgJsonPath, JSON.stringify(parsed, null, 2) + "\n"); +} + +describe("e2e: core-package ui", () => { + it("byte-identical reconstruction matches snapshot", { timeout: 120_000 }, () => { + const tmp = mkdtempSync(join(tmpdir(), "e2e-ui-")); + cpSync(REPO_ROOT, tmp, { + recursive: true, + filter: (src) => + !src.includes("node_modules") && + !src.includes(".turbo") && + !src.includes("packages/core-ui"), + }); + + // Strip @repo/core-ui from app package.json files so pnpm install + // succeeds without the package being present (simulating the post-removal state). + stripCoreUiDep(join(tmp, "apps", "web-next", "package.json")); + stripCoreUiDep(join(tmp, "apps", "web-tanstack", "package.json")); + stripCoreUiDep(join(tmp, "apps", "storybook", "package.json")); + + execSync(`cd ${tmp} && pnpm install --frozen-lockfile=false`, { stdio: "ignore" }); + execSync(`cd ${tmp} && pnpm turbo gen core-package --args ui`, { stdio: "ignore" }); + const result = computeSnapshot(join(tmp, "packages/core-ui")); + expect(result).toEqual(expectedSnapshot); + }); +}); diff --git a/turbo/generators/config.ts b/turbo/generators/config.ts index 8f92713..8d1c1c9 100644 --- a/turbo/generators/config.ts +++ b/turbo/generators/config.ts @@ -654,6 +654,18 @@ import noRealtimeHandlerReexport from "./rules/no-realtime-handler-reexport.js"; }, printTrpcNextSteps, ], + ui: () => [ + () => { + assertOptionalPackageNotPresent("core-ui"); + return "Guard passed — packages/core-ui does not exist yet."; + }, + ...emitTemplateTree("core-package/ui", "packages/core-ui"), + () => { + addToTranspilePackages("apps/web-next/next.config.mjs", "@repo/core-ui"); + return "Added @repo/core-ui to transpilePackages."; + }, + printUiNextSteps, + ], }; plop.setGenerator("core-package", { @@ -663,7 +675,7 @@ import noRealtimeHandlerReexport from "./rules/no-realtime-handler-reexport.js"; type: "list", name: "name", message: "Which optional core package?", - choices: ["realtime", "events", "trpc"], + choices: ["realtime", "events", "trpc", "ui"], }, ], actions: (answers) => { @@ -1187,6 +1199,37 @@ function printTrpcNextSteps(): string { ].join("\n"); } +function printUiNextSteps(): string { + return [ + "─────────────────────────────────────────────────────────────", + "@repo/core-ui scaffolded.", + "", + "Next steps (manual wiring — not generated):", + "", + " 1. pnpm install # link the new workspace package", + "", + " 2. apps/web-next/package.json:", + ' - add "@repo/core-ui": "workspace:*" to dependencies', + "", + " 3. apps/storybook/.storybook/main.ts:", + ' - add "../../../packages/core-ui/src/**/*.stories.@(ts|tsx)" to the stories array', + "", + " 4. apps/storybook/.storybook/preview.ts:", + ' - add: import "@repo/core-ui/styles/globals.css";', + "", + " 5. apps/storybook/package.json:", + ' - add "@repo/core-ui": "workspace:*" to dependencies', + "", + " 6. apps/web-tanstack/package.json:", + ' - add "@repo/core-ui": "workspace:*" to dependencies', + " - when TanStack Start gains a real vite.config.ts, add @repo/core-ui", + " to optimizeDeps.include", + "", + " 7. pnpm typecheck && pnpm lint && pnpm test", + "─────────────────────────────────────────────────────────────", + ].join("\n"); +} + function printEventsNextSteps(): string { return [ "─────────────────────────────────────────────────────────────",