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:
@@ -31,6 +31,10 @@ describe("e2e: core-package analytics", () => {
|
|||||||
!src.includes("node_modules") &&
|
!src.includes("node_modules") &&
|
||||||
!src.includes(".turbo") &&
|
!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") &&
|
||||||
!src.includes("packages/core-analytics"),
|
!src.includes("packages/core-analytics"),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
mkdtempSync,
|
mkdtempSync,
|
||||||
rmSync,
|
rmSync,
|
||||||
cpSync,
|
cpSync,
|
||||||
|
globSync,
|
||||||
readFileSync,
|
readFileSync,
|
||||||
writeFileSync,
|
writeFileSync,
|
||||||
} from "node:fs";
|
} from "node:fs";
|
||||||
@@ -58,12 +59,22 @@ describe("e2e: core-package audit", () => {
|
|||||||
!src.includes("node_modules") &&
|
!src.includes("node_modules") &&
|
||||||
!src.includes(".turbo") &&
|
!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") &&
|
||||||
!src.includes("packages/core-audit"),
|
!src.includes("packages/core-audit"),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Strip @repo/core-audit from apps/web-next/package.json so pnpm install
|
// Strip @repo/core-audit from EVERY workspace package.json so pnpm
|
||||||
// succeeds without the package being present (simulating the post-removal state).
|
// install succeeds without the package being present (simulating the
|
||||||
stripCoreAuditDep(join(tmp, "apps", "web-next", "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"),
|
||||||
|
)) {
|
||||||
|
stripCoreAuditDep(pkgJson);
|
||||||
|
}
|
||||||
|
|
||||||
execSync(`cd ${tmp} && pnpm install --frozen-lockfile=false`, {
|
execSync(`cd ${tmp} && pnpm install --frozen-lockfile=false`, {
|
||||||
stdio: "ignore",
|
stdio: "ignore",
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
mkdtempSync,
|
mkdtempSync,
|
||||||
rmSync,
|
rmSync,
|
||||||
cpSync,
|
cpSync,
|
||||||
|
globSync,
|
||||||
readFileSync,
|
readFileSync,
|
||||||
writeFileSync,
|
writeFileSync,
|
||||||
} from "node:fs";
|
} from "node:fs";
|
||||||
@@ -58,13 +59,22 @@ describe("e2e: core-package consent", () => {
|
|||||||
!src.includes("node_modules") &&
|
!src.includes("node_modules") &&
|
||||||
!src.includes(".turbo") &&
|
!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") &&
|
||||||
!src.includes("packages/core-consent"),
|
!src.includes("packages/core-consent"),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Strip @repo/core-consent from package.json files so pnpm install
|
// Strip @repo/core-consent from EVERY workspace package.json so pnpm
|
||||||
// succeeds without the package being present (simulating the post-removal state).
|
// install succeeds without the package being present (simulating the
|
||||||
stripCoreConsentDep(join(tmp, "packages", "core-api", "package.json"));
|
// post-removal state) — a hardcoded dependent list drifts as packages
|
||||||
stripCoreConsentDep(join(tmp, "packages", "core-ui", "package.json"));
|
// 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`, {
|
execSync(`cd ${tmp} && pnpm install --frozen-lockfile=false`, {
|
||||||
stdio: "ignore",
|
stdio: "ignore",
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
mkdtempSync,
|
mkdtempSync,
|
||||||
rmSync,
|
rmSync,
|
||||||
cpSync,
|
cpSync,
|
||||||
|
globSync,
|
||||||
readFileSync,
|
readFileSync,
|
||||||
writeFileSync,
|
writeFileSync,
|
||||||
} from "node:fs";
|
} from "node:fs";
|
||||||
@@ -58,12 +59,22 @@ describe("e2e: core-package dsr", () => {
|
|||||||
!src.includes("node_modules") &&
|
!src.includes("node_modules") &&
|
||||||
!src.includes(".turbo") &&
|
!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") &&
|
||||||
!src.includes("packages/core-dsr"),
|
!src.includes("packages/core-dsr"),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Strip @repo/core-dsr from package.json files so pnpm install
|
// Strip @repo/core-dsr from EVERY workspace package.json so pnpm
|
||||||
// succeeds without the package being present (simulating the post-removal state).
|
// install succeeds without the package being present (simulating the
|
||||||
stripCoreDsrDep(join(tmp, "packages", "core-api", "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"),
|
||||||
|
)) {
|
||||||
|
stripCoreDsrDep(pkgJson);
|
||||||
|
}
|
||||||
|
|
||||||
execSync(`cd ${tmp} && pnpm install --frozen-lockfile=false`, {
|
execSync(`cd ${tmp} && pnpm install --frozen-lockfile=false`, {
|
||||||
stdio: "ignore",
|
stdio: "ignore",
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
mkdtempSync,
|
mkdtempSync,
|
||||||
rmSync,
|
rmSync,
|
||||||
cpSync,
|
cpSync,
|
||||||
|
globSync,
|
||||||
readFileSync,
|
readFileSync,
|
||||||
writeFileSync,
|
writeFileSync,
|
||||||
} from "node:fs";
|
} from "node:fs";
|
||||||
@@ -58,24 +59,22 @@ describe("e2e: core-package events", () => {
|
|||||||
!src.includes("node_modules") &&
|
!src.includes("node_modules") &&
|
||||||
!src.includes(".turbo") &&
|
!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") &&
|
||||||
!src.includes("packages/core-events"),
|
!src.includes("packages/core-events"),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Strip @repo/core-events from feature package.json files so pnpm install
|
// Strip @repo/core-events from EVERY workspace package.json so pnpm
|
||||||
// succeeds without the package being present (simulating the post-removal state).
|
// install succeeds without the package being present (simulating the
|
||||||
const featurePackages = [
|
// post-removal state) — a hardcoded dependent list drifts as packages
|
||||||
"auth",
|
// gain or drop the dependency.
|
||||||
"blog",
|
for (const pkgJson of globSync(
|
||||||
"media",
|
join(tmp, "{apps,packages}", "*", "package.json"),
|
||||||
"marketing-pages",
|
)) {
|
||||||
"navigation",
|
|
||||||
];
|
|
||||||
for (const pkg of featurePackages) {
|
|
||||||
const pkgJson = join(tmp, "packages", pkg, "package.json");
|
|
||||||
stripCoreEventsDep(pkgJson);
|
stripCoreEventsDep(pkgJson);
|
||||||
}
|
}
|
||||||
// Also strip from apps/web-next
|
|
||||||
stripCoreEventsDep(join(tmp, "apps", "web-next", "package.json"));
|
|
||||||
|
|
||||||
execSync(`cd ${tmp} && pnpm install --frozen-lockfile=false`, {
|
execSync(`cd ${tmp} && pnpm install --frozen-lockfile=false`, {
|
||||||
stdio: "ignore",
|
stdio: "ignore",
|
||||||
|
|||||||
@@ -31,6 +31,10 @@ describe("e2e: core-package realtime", () => {
|
|||||||
!src.includes("node_modules") &&
|
!src.includes("node_modules") &&
|
||||||
!src.includes(".turbo") &&
|
!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") &&
|
||||||
!src.includes("packages/core-realtime"),
|
!src.includes("packages/core-realtime"),
|
||||||
});
|
});
|
||||||
execSync(`cd ${tmp} && pnpm install --frozen-lockfile=false`, {
|
execSync(`cd ${tmp} && pnpm install --frozen-lockfile=false`, {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
mkdtempSync,
|
mkdtempSync,
|
||||||
rmSync,
|
rmSync,
|
||||||
cpSync,
|
cpSync,
|
||||||
|
globSync,
|
||||||
readFileSync,
|
readFileSync,
|
||||||
writeFileSync,
|
writeFileSync,
|
||||||
} from "node:fs";
|
} from "node:fs";
|
||||||
@@ -58,21 +59,21 @@ describe("e2e: core-package trpc", () => {
|
|||||||
!src.includes("node_modules") &&
|
!src.includes("node_modules") &&
|
||||||
!src.includes(".turbo") &&
|
!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") &&
|
||||||
!src.includes("packages/core-trpc"),
|
!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
|
// install succeeds without the package being present (simulating the
|
||||||
// post-removal state). Apps list it directly; the blog, marketing-pages,
|
// post-removal state) — a hardcoded dependent list drifts as packages
|
||||||
// and navigation features depend on it for their ./ui tRPC hooks.
|
// gain or drop the dependency.
|
||||||
for (const pkgDir of [
|
for (const pkgJson of globSync(
|
||||||
["apps", "web-next"],
|
join(tmp, "{apps,packages}", "*", "package.json"),
|
||||||
["apps", "web-tanstack"],
|
)) {
|
||||||
["packages", "blog"],
|
stripCoreTrpcDep(pkgJson);
|
||||||
["packages", "marketing-pages"],
|
|
||||||
["packages", "navigation"],
|
|
||||||
]) {
|
|
||||||
stripCoreTrpcDep(join(tmp, ...pkgDir, "package.json"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
execSync(`cd ${tmp} && pnpm install --frozen-lockfile=false`, {
|
execSync(`cd ${tmp} && pnpm install --frozen-lockfile=false`, {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
mkdtempSync,
|
mkdtempSync,
|
||||||
rmSync,
|
rmSync,
|
||||||
cpSync,
|
cpSync,
|
||||||
|
globSync,
|
||||||
readFileSync,
|
readFileSync,
|
||||||
writeFileSync,
|
writeFileSync,
|
||||||
} from "node:fs";
|
} from "node:fs";
|
||||||
@@ -58,14 +59,22 @@ describe("e2e: core-package ui", () => {
|
|||||||
!src.includes("node_modules") &&
|
!src.includes("node_modules") &&
|
||||||
!src.includes(".turbo") &&
|
!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") &&
|
||||||
!src.includes("packages/core-ui"),
|
!src.includes("packages/core-ui"),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Strip @repo/core-ui from app package.json files so pnpm install
|
// Strip @repo/core-ui from EVERY workspace package.json so pnpm
|
||||||
// succeeds without the package being present (simulating the post-removal state).
|
// install succeeds without the package being present (simulating the
|
||||||
stripCoreUiDep(join(tmp, "apps", "web-next", "package.json"));
|
// post-removal state) — a hardcoded dependent list drifts as packages
|
||||||
stripCoreUiDep(join(tmp, "apps", "web-tanstack", "package.json"));
|
// gain or drop the dependency.
|
||||||
stripCoreUiDep(join(tmp, "apps", "storybook", "package.json"));
|
for (const pkgJson of globSync(
|
||||||
|
join(tmp, "{apps,packages}", "*", "package.json"),
|
||||||
|
)) {
|
||||||
|
stripCoreUiDep(pkgJson);
|
||||||
|
}
|
||||||
|
|
||||||
execSync(`cd ${tmp} && pnpm install --frozen-lockfile=false`, {
|
execSync(`cd ${tmp} && pnpm install --frozen-lockfile=false`, {
|
||||||
stdio: "ignore",
|
stdio: "ignore",
|
||||||
|
|||||||
@@ -38,7 +38,11 @@ describe("e2e: feature generator composition", () => {
|
|||||||
filter: (src) =>
|
filter: (src) =>
|
||||||
!src.includes("node_modules") &&
|
!src.includes("node_modules") &&
|
||||||
!src.includes(".turbo") &&
|
!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`, {
|
execSync(`cd ${tmp} && pnpm install --frozen-lockfile=false`, {
|
||||||
|
|||||||
@@ -1300,7 +1300,10 @@ function handlerBindBlock(a: {
|
|||||||
${containerVar}.unbind(${handlerSymbol});
|
${containerVar}.unbind(${handlerSymbol});
|
||||||
}
|
}
|
||||||
${containerVar}.bind(${handlerSymbol}).toConstantValue(${wrappedVar});
|
${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: {
|
function printConsumeNextSteps(a: {
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ import {
|
|||||||
// manifest when adjusting per-feature bands.
|
// manifest when adjusting per-feature bands.
|
||||||
export default mergeConfig(nodeVitestConfig, {
|
export default mergeConfig(nodeVitestConfig, {
|
||||||
test: {
|
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: {
|
coverage: {
|
||||||
exclude: [
|
exclude: [
|
||||||
// DI bootstrap — wires InversifyJS at app startup; not unit-testable
|
// DI bootstrap — wires InversifyJS at app startup; not unit-testable
|
||||||
|
|||||||
Reference in New Issue
Block a user