fix(generators): run UI tests, guard optional bus, harden e2e clones

Three generator fixes:
- templates/feature/vitest.config.ts.hbs lacked an include for
  .test.{ts,tsx}; the node base only includes .test.ts, so scaffolded
  UI component tests never executed
- gen event consume emitted an unguarded bus.subscribe although
  ctx.bus is optional in BindContext — now wrapped in if (bus) {}
- e2e repo clones now exclude /dist and /.next build outputs, and
  every dep-stripping e2e strips the scaffolded package from EVERY
  workspace package.json via globSync instead of a hardcoded dependent
  list that drifts as packages gain or drop the dependency

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-10 16:42:46 +02:00
parent 1e3220aef5
commit a17b984675
11 changed files with 100 additions and 41 deletions

View File

@@ -31,6 +31,10 @@ describe("e2e: core-package analytics", () => {
!src.includes("node_modules") &&
!src.includes(".turbo") &&
!src.includes(".pnpm-store") &&
// Build outputs are large and irrelevant to the scaffold — cloning
// dist/.next fills the tmp dir and slows every e2e run.
!src.includes("/dist") &&
!src.includes("/.next") &&
!src.includes("packages/core-analytics"),
});

View File

@@ -3,6 +3,7 @@ import {
mkdtempSync,
rmSync,
cpSync,
globSync,
readFileSync,
writeFileSync,
} from "node:fs";
@@ -58,12 +59,22 @@ describe("e2e: core-package audit", () => {
!src.includes("node_modules") &&
!src.includes(".turbo") &&
!src.includes(".pnpm-store") &&
// Build outputs are large and irrelevant to the scaffold — cloning
// dist/.next fills the tmp dir and slows every e2e run.
!src.includes("/dist") &&
!src.includes("/.next") &&
!src.includes("packages/core-audit"),
});
// Strip @repo/core-audit from apps/web-next/package.json so pnpm install
// succeeds without the package being present (simulating the post-removal state).
stripCoreAuditDep(join(tmp, "apps", "web-next", "package.json"));
// Strip @repo/core-audit from EVERY workspace package.json so pnpm
// install succeeds without the package being present (simulating the
// post-removal state) — a hardcoded dependent list drifts as packages
// gain or drop the dependency.
for (const pkgJson of globSync(
join(tmp, "{apps,packages}", "*", "package.json"),
)) {
stripCoreAuditDep(pkgJson);
}
execSync(`cd ${tmp} && pnpm install --frozen-lockfile=false`, {
stdio: "ignore",

View File

@@ -3,6 +3,7 @@ import {
mkdtempSync,
rmSync,
cpSync,
globSync,
readFileSync,
writeFileSync,
} from "node:fs";
@@ -58,13 +59,22 @@ describe("e2e: core-package consent", () => {
!src.includes("node_modules") &&
!src.includes(".turbo") &&
!src.includes(".pnpm-store") &&
// Build outputs are large and irrelevant to the scaffold — cloning
// dist/.next fills the tmp dir and slows every e2e run.
!src.includes("/dist") &&
!src.includes("/.next") &&
!src.includes("packages/core-consent"),
});
// Strip @repo/core-consent from package.json files so pnpm install
// succeeds without the package being present (simulating the post-removal state).
stripCoreConsentDep(join(tmp, "packages", "core-api", "package.json"));
stripCoreConsentDep(join(tmp, "packages", "core-ui", "package.json"));
// Strip @repo/core-consent from EVERY workspace package.json so pnpm
// install succeeds without the package being present (simulating the
// post-removal state) — a hardcoded dependent list drifts as packages
// gain or drop the dependency.
for (const pkgJson of globSync(
join(tmp, "{apps,packages}", "*", "package.json"),
)) {
stripCoreConsentDep(pkgJson);
}
execSync(`cd ${tmp} && pnpm install --frozen-lockfile=false`, {
stdio: "ignore",

View File

@@ -3,6 +3,7 @@ import {
mkdtempSync,
rmSync,
cpSync,
globSync,
readFileSync,
writeFileSync,
} from "node:fs";
@@ -58,12 +59,22 @@ describe("e2e: core-package dsr", () => {
!src.includes("node_modules") &&
!src.includes(".turbo") &&
!src.includes(".pnpm-store") &&
// Build outputs are large and irrelevant to the scaffold — cloning
// dist/.next fills the tmp dir and slows every e2e run.
!src.includes("/dist") &&
!src.includes("/.next") &&
!src.includes("packages/core-dsr"),
});
// Strip @repo/core-dsr from package.json files so pnpm install
// succeeds without the package being present (simulating the post-removal state).
stripCoreDsrDep(join(tmp, "packages", "core-api", "package.json"));
// Strip @repo/core-dsr from EVERY workspace package.json so pnpm
// install succeeds without the package being present (simulating the
// post-removal state) — a hardcoded dependent list drifts as packages
// gain or drop the dependency.
for (const pkgJson of globSync(
join(tmp, "{apps,packages}", "*", "package.json"),
)) {
stripCoreDsrDep(pkgJson);
}
execSync(`cd ${tmp} && pnpm install --frozen-lockfile=false`, {
stdio: "ignore",

View File

@@ -3,6 +3,7 @@ import {
mkdtempSync,
rmSync,
cpSync,
globSync,
readFileSync,
writeFileSync,
} from "node:fs";
@@ -58,24 +59,22 @@ describe("e2e: core-package events", () => {
!src.includes("node_modules") &&
!src.includes(".turbo") &&
!src.includes(".pnpm-store") &&
// Build outputs are large and irrelevant to the scaffold — cloning
// dist/.next fills the tmp dir and slows every e2e run.
!src.includes("/dist") &&
!src.includes("/.next") &&
!src.includes("packages/core-events"),
});
// Strip @repo/core-events from feature package.json files so pnpm install
// succeeds without the package being present (simulating the post-removal state).
const featurePackages = [
"auth",
"blog",
"media",
"marketing-pages",
"navigation",
];
for (const pkg of featurePackages) {
const pkgJson = join(tmp, "packages", pkg, "package.json");
// Strip @repo/core-events from EVERY workspace package.json so pnpm
// install succeeds without the package being present (simulating the
// post-removal state) — a hardcoded dependent list drifts as packages
// gain or drop the dependency.
for (const pkgJson of globSync(
join(tmp, "{apps,packages}", "*", "package.json"),
)) {
stripCoreEventsDep(pkgJson);
}
// Also strip from apps/web-next
stripCoreEventsDep(join(tmp, "apps", "web-next", "package.json"));
execSync(`cd ${tmp} && pnpm install --frozen-lockfile=false`, {
stdio: "ignore",

View File

@@ -31,6 +31,10 @@ describe("e2e: core-package realtime", () => {
!src.includes("node_modules") &&
!src.includes(".turbo") &&
!src.includes(".pnpm-store") &&
// Build outputs are large and irrelevant to the scaffold — cloning
// dist/.next fills the tmp dir and slows every e2e run.
!src.includes("/dist") &&
!src.includes("/.next") &&
!src.includes("packages/core-realtime"),
});
execSync(`cd ${tmp} && pnpm install --frozen-lockfile=false`, {

View File

@@ -3,6 +3,7 @@ import {
mkdtempSync,
rmSync,
cpSync,
globSync,
readFileSync,
writeFileSync,
} from "node:fs";
@@ -58,21 +59,21 @@ describe("e2e: core-package trpc", () => {
!src.includes("node_modules") &&
!src.includes(".turbo") &&
!src.includes(".pnpm-store") &&
// Build outputs are large and irrelevant to the scaffold — cloning
// dist/.next fills the tmp dir and slows every e2e run.
!src.includes("/dist") &&
!src.includes("/.next") &&
!src.includes("packages/core-trpc"),
});
// Strip @repo/core-trpc from every package that references it so pnpm
// Strip @repo/core-trpc from EVERY workspace package.json so pnpm
// install succeeds without the package being present (simulating the
// post-removal state). Apps list it directly; the blog, marketing-pages,
// and navigation features depend on it for their ./ui tRPC hooks.
for (const pkgDir of [
["apps", "web-next"],
["apps", "web-tanstack"],
["packages", "blog"],
["packages", "marketing-pages"],
["packages", "navigation"],
]) {
stripCoreTrpcDep(join(tmp, ...pkgDir, "package.json"));
// post-removal state) — a hardcoded dependent list drifts as packages
// gain or drop the dependency.
for (const pkgJson of globSync(
join(tmp, "{apps,packages}", "*", "package.json"),
)) {
stripCoreTrpcDep(pkgJson);
}
execSync(`cd ${tmp} && pnpm install --frozen-lockfile=false`, {

View File

@@ -3,6 +3,7 @@ import {
mkdtempSync,
rmSync,
cpSync,
globSync,
readFileSync,
writeFileSync,
} from "node:fs";
@@ -58,14 +59,22 @@ describe("e2e: core-package ui", () => {
!src.includes("node_modules") &&
!src.includes(".turbo") &&
!src.includes(".pnpm-store") &&
// Build outputs are large and irrelevant to the scaffold — cloning
// dist/.next fills the tmp dir and slows every e2e run.
!src.includes("/dist") &&
!src.includes("/.next") &&
!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"));
// Strip @repo/core-ui from EVERY workspace package.json so pnpm
// install succeeds without the package being present (simulating the
// post-removal state) — a hardcoded dependent list drifts as packages
// gain or drop the dependency.
for (const pkgJson of globSync(
join(tmp, "{apps,packages}", "*", "package.json"),
)) {
stripCoreUiDep(pkgJson);
}
execSync(`cd ${tmp} && pnpm install --frozen-lockfile=false`, {
stdio: "ignore",

View File

@@ -38,7 +38,11 @@ describe("e2e: feature generator composition", () => {
filter: (src) =>
!src.includes("node_modules") &&
!src.includes(".turbo") &&
!src.includes(".pnpm-store"),
!src.includes(".pnpm-store") &&
// Build outputs are large and irrelevant to the scaffold — cloning
// dist/.next fills the tmp dir and slows every e2e run.
!src.includes("/dist") &&
!src.includes("/.next"),
});
execSync(`cd ${tmp} && pnpm install --frozen-lockfile=false`, {

View File

@@ -1300,7 +1300,10 @@ function handlerBindBlock(a: {
${containerVar}.unbind(${handlerSymbol});
}
${containerVar}.bind(${handlerSymbol}).toConstantValue(${wrappedVar});
bus.subscribe(${eventConst}, "${a.feature}", ${wrappedVar});`;
// ctx.bus is an OPTIONAL core (guard per CLAUDE.md binder conventions).
if (bus) {
bus.subscribe(${eventConst}, "${a.feature}", ${wrappedVar});
}`;
}
function printConsumeNextSteps(a: {

View File

@@ -12,6 +12,9 @@ import {
// manifest when adjusting per-feature bands.
export default mergeConfig(nodeVitestConfig, {
test: {
// The node base only includes .test.ts; UI tests are .test.tsx files
// that opt into jsdom per-file via the @vitest-environment docblock.
include: ["src/**/*.test.{ts,tsx}"],
coverage: {
exclude: [
// DI bootstrap — wires InversifyJS at app startup; not unit-testable