Skip to content

Commit 5569d1c

Browse files
test(cli): add matrix and generated build coverage (#1046)
1 parent a98f8bf commit 5569d1c

31 files changed

Lines changed: 2017 additions & 74 deletions

.github/workflows/test.yaml

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -44,17 +44,9 @@ jobs:
4444
- name: Build Template Generator
4545
run: cd packages/template-generator && bun run build
4646

47-
- name: Build and Test CLI
47+
- name: Test CLI
4848
working-directory: apps/cli
49-
run: bun run build && bun run test:ci
49+
run: bun run test:ci
5050
env:
5151
AGENT: 1
5252
BTS_TELEMETRY: 0
53-
54-
- name: Setup pnpm
55-
uses: pnpm/action-setup@v6
56-
with:
57-
version: latest
58-
59-
- name: Publish smoke test (npm, pnpm, bun)
60-
run: bun run smoke:publish

AGENTS.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,17 @@ This repo is a Bun + Turborepo monorepo.
6363
- linked issue (if applicable),
6464
- verification steps run (`bun run check`, relevant tests),
6565
- screenshots/GIFs for web UI changes.
66+
67+
## Agent skills
68+
69+
### Issue tracker
70+
71+
Issues are tracked in GitHub Issues for `AmanVarshney01/create-better-t-stack` using the `gh` CLI. See `docs/agents/issue-tracker.md`.
72+
73+
### Triage labels
74+
75+
The canonical triage roles map directly to same-named GitHub labels. See `docs/agents/triage-labels.md`.
76+
77+
### Domain docs
78+
79+
This repo uses a single-context domain doc layout. See `docs/agents/domain.md`.

CONTEXT.md

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# Better T Stack
2+
3+
Better T Stack is a project scaffolding CLI that turns a stack selection into a reproducible TypeScript starter project.
4+
5+
## Language
6+
7+
**Project Configuration**:
8+
A complete selection of stack choices used to scaffold one project.
9+
_Avoid_: Config blob, options bag
10+
11+
**Core Stack**:
12+
The primary framework and infrastructure choices that define the generated project's shape.
13+
_Avoid_: Main options, basics
14+
15+
**Addon**:
16+
An optional capability layered onto a generated project without changing the core stack identity.
17+
_Avoid_: Plugin, extra
18+
19+
**Example**:
20+
A generated sample feature included to demonstrate the selected stack.
21+
_Avoid_: Demo, sample app
22+
23+
**Exhaustive Matrix**:
24+
The set of all meaningful core-stack combinations after normalizing unordered selections.
25+
_Avoid_: All permutations
26+
27+
**Addon Compatibility Coverage**:
28+
Focused coverage proving addon rules, interactions, and representative generated output without crossing every addon subset through the exhaustive matrix.
29+
_Avoid_: Addon exhaustive product
30+
31+
**Input Shape**:
32+
The structural form of user-provided CLI input before it is normalized into a project configuration.
33+
_Avoid_: Edge case, weird input
34+
35+
**Virtual Generation**:
36+
Generation of the project tree in memory without writing the scaffolded project to disk.
37+
_Avoid_: Dry run
38+
39+
**Filesystem Scaffolding**:
40+
Creation of the generated project on disk.
41+
_Avoid_: Real generation
42+
43+
**Curated Build Set**:
44+
A small representative set of generated projects that are installed, typechecked, or built.
45+
_Avoid_: Build every matrix case
46+
47+
**Compatibility Oracle**:
48+
A test-owned model of which project configurations should be accepted or rejected.
49+
_Avoid_: Validator reuse
50+
51+
**Matrix Job**:
52+
A separately runnable test job that exercises large project-configuration matrices outside the default fast test suite.
53+
_Avoid_: Normal test run
54+
55+
**Default Suite**:
56+
The fast test suite that runs focused unit, integration, and regression tests during ordinary development.
57+
_Avoid_: Main matrix
58+
59+
**Matrix Smoke**:
60+
A representative matrix slice that runs in regular CI to catch broad compatibility regressions without running the full matrix.
61+
_Avoid_: Mini exhaustive test
62+
63+
**Full Matrix Job**:
64+
The sharded long-running matrix job that exercises the complete normalized core-stack matrix.
65+
_Avoid_: Default tests
66+
67+
**Create Path**:
68+
The CLI workflow that turns a project configuration into a new scaffolded project.
69+
_Avoid_: Init path, generation path
70+
71+
**Add Path**:
72+
The CLI workflow that adds capabilities to an existing Better T Stack project.
73+
_Avoid_: Update path, addon install path
74+
75+
**MCP Surface**:
76+
The Model Context Protocol server and tools that expose Better T Stack planning, creation, addon, guidance, and schema operations to agents.
77+
_Avoid_: MCP addon
78+
79+
## Relationships
80+
81+
- A **Project Configuration** contains one **Core Stack**
82+
- A **Project Configuration** may include zero or more **Addons**
83+
- A **Project Configuration** may include zero or more **Examples**
84+
- The **Exhaustive Matrix** covers **Core Stack** combinations
85+
- **Addon Compatibility Coverage** covers addon-specific rules outside the exhaustive matrix
86+
- **Input Shape** checks validate raw CLI input before project-configuration behavior is evaluated
87+
- **Virtual Generation** and **Filesystem Scaffolding** are two ways to exercise project generation
88+
- A **Curated Build Set** proves generated projects compile without building every matrix case
89+
- A **Compatibility Oracle** predicts expected pass/fail before production validation runs
90+
- A **Matrix Job** provides broad confidence without making the default test suite slow
91+
- The **Default Suite**, **Matrix Smoke**, and **Full Matrix Job** are the three testing tiers
92+
- The **Create Path** owns exhaustive project-configuration coverage
93+
- The **Add Path** has a focused suite over existing-project state and addon behavior
94+
- The **MCP Surface** is a critical CLI surface alongside the **Create Path** and **Add Path**
95+
- The **MCP Surface** uses focused contract coverage instead of participating in the **Full Matrix Job**
96+
97+
## Example dialogue
98+
99+
> **Dev:** "Should the exhaustive tests include every order of addons?"
100+
> **Domain expert:** "No. The **Exhaustive Matrix** covers normalized **Project Configurations**; addon ordering is an **Input Shape** concern."
101+
102+
## Flagged ambiguities
103+
104+
- "all possible combinations" was ambiguous between literal permutations and meaningful stack selections; resolved: use the **Exhaustive Matrix** for normalized core-stack combinations, with separate **Input Shape** tests for ordering, duplicates, and invalid `none` mixtures.
105+
- "all addon combinations" was ambiguous between rule coverage and a full addon-subset product; resolved: use **Addon Compatibility Coverage** instead of crossing every addon subset through the **Exhaustive Matrix**.
106+
- "all tests" was ambiguous between the default test suite and long-running exhaustive coverage; resolved: use a separate **Matrix Job** for broad matrix coverage.
107+
- "test the MCP" was ambiguous between matrix participation and protocol contract coverage; resolved: the **MCP Surface** gets focused contract coverage.
108+
- "complete CLI tests" was ambiguous between new-project creation and adding to existing projects; resolved: the **Create Path** gets exhaustive matrix coverage first, while the **Add Path** gets a focused complete suite.

apps/cli/bunfig.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
# Preload setup file for global setup/teardown
33
preload = ["./test/setup.ts"]
44

5-
# Per-test timeout (3 minutes for smoke tests)
6-
timeout = 180000
5+
# Default per-test timeout. Long-running smoke/build tests set explicit timeouts.
6+
timeout = 30000
77

88
# Skip test files from coverage reports
99
coverageSkipTestFiles = true

apps/cli/package.json

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,9 @@
6363
"build": "tsdown --publint",
6464
"dev": "tsdown --watch",
6565
"check-types": "tsc --noEmit",
66-
"test": "bun run build && bun test",
67-
"test:watch": "bun run build && bun test --watch",
68-
"test:coverage": "bun run build && bun test --coverage",
69-
"test:ci": "bun run build && CI=1 bun test --bail=5",
66+
"test": "bun test",
67+
"test:ci": "bun run build && BTS_SKIP_EXTERNAL_COMMANDS=1 BTS_TEST_MODE=1 bun test --bail=5",
68+
"test:complete": "cd ../.. && bun run build && cd apps/cli && BTS_MATRIX_MODE=smoke BTS_BUILD_SAMPLES=1 bun test --bail=5",
7069
"prepublishOnly": "npm run build"
7170
},
7271
"dependencies": {

apps/cli/src/utils/compatibility-rules.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,8 @@ export function isExampleTodoAllowed(
265265
}
266266

267267
export function isExampleAIAllowed(backend?: ProjectConfig["backend"], frontends: Frontend[] = []) {
268+
if (backend === "none") return false;
269+
268270
const includesSolid = frontends.includes("solid");
269271
const includesAstro = frontends.includes("astro");
270272
if (includesSolid || includesAstro) return false;
@@ -447,6 +449,14 @@ export function validateExamplesCompatibility(
447449
return validationErr("The 'ai' example is not compatible with the Solid frontend.");
448450
}
449451

452+
if (examplesArr.includes("ai") && (frontend ?? []).includes("astro")) {
453+
return validationErr("The 'ai' example is not compatible with the Astro frontend.");
454+
}
455+
456+
if (examplesArr.includes("ai") && backend === "none") {
457+
return validationErr("The 'ai' example requires a backend.");
458+
}
459+
450460
// Convex AI example only supports React-based frontends
451461
if (examplesArr.includes("ai") && backend === "convex") {
452462
const frontendArr = frontend ?? [];

apps/cli/test/addons.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -944,7 +944,7 @@ describe("Addon Configurations", () => {
944944
orm: "drizzle",
945945
auth: "better-auth",
946946
api: webCase.api,
947-
examples: ["todo", "ai"],
947+
examples: webCase.frontend === "astro" ? ["todo"] : ["todo", "ai"],
948948
dbSetup: "turso",
949949
webDeploy: "none",
950950
serverDeploy: "none",

apps/cli/test/basic-configurations.test.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -106,10 +106,7 @@ describe("Basic Configurations", () => {
106106
});
107107

108108
describe("Installation Options", () => {
109-
// Skip install test in CI to avoid timeouts
110-
const runInstallTest = process.env.CI ? it.skip : it;
111-
112-
runInstallTest(
109+
it.skipIf(Boolean(process.env.CI))(
113110
"should work with install enabled",
114111
async () => {
115112
const result = await runTRPCTest({

apps/cli/test/clerk-matrix.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ function needsServerClerkPublishableKey(backend: string, api: string) {
6363
}
6464

6565
describe("Clerk matrix", () => {
66-
it("should generate every supported Clerk combination", { timeout: 30_000 }, async () => {
66+
it("should generate every supported Clerk combination", async () => {
6767
const standardFrontendCombos = buildFrontendCombos(standardWeb);
6868
const selfFrontendCombos = buildFrontendCombos(selfWeb, { requireWeb: true });
6969

@@ -388,5 +388,5 @@ describe("Clerk matrix", () => {
388388

389389
expect(combos).toHaveLength(499);
390390
expect(failures).toEqual([]);
391-
});
391+
}, 30_000);
392392
});

apps/cli/test/examples.test.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,48 @@ describe("Example Configurations", () => {
195195
expectError(result, "The 'ai' example is not compatible with the Solid frontend");
196196
});
197197

198+
it("should fail with AI example + Astro frontend", async () => {
199+
const result = await runTRPCTest({
200+
projectName: "ai-astro-fail",
201+
examples: ["ai"],
202+
backend: "hono",
203+
runtime: "bun",
204+
database: "sqlite",
205+
orm: "drizzle",
206+
auth: "none",
207+
api: "orpc",
208+
frontend: ["astro"],
209+
addons: ["none"],
210+
dbSetup: "none",
211+
webDeploy: "none",
212+
serverDeploy: "none",
213+
expectError: true,
214+
});
215+
216+
expectError(result, "The 'ai' example is not compatible with the Astro frontend");
217+
});
218+
219+
it("should fail with AI example + no backend", async () => {
220+
const result = await runTRPCTest({
221+
projectName: "ai-no-backend-fail",
222+
examples: ["ai"],
223+
backend: "none",
224+
runtime: "none",
225+
database: "none",
226+
orm: "none",
227+
auth: "none",
228+
api: "none",
229+
frontend: ["tanstack-router"],
230+
addons: ["none"],
231+
dbSetup: "none",
232+
webDeploy: "none",
233+
serverDeploy: "none",
234+
expectError: true,
235+
});
236+
237+
expectError(result, "The 'ai' example requires a backend");
238+
});
239+
198240
it("should work with AI example + Convex + React frontend", async () => {
199241
const result = await runTRPCTest({
200242
projectName: "ai-convex-react",

0 commit comments

Comments
 (0)