Skip to content

Commit 4e7eb9a

Browse files
feat: add pnpm + Turborepo workspace example with routing smoke guard (#42)
Adds examples/pnpm-turbo-workspace: a two-package pnpm monorepo (@example/api depends on @example/utils) with separate test/typecheck scripts per package and Turborepo task wiring at the root. Two checked-in tasks prove nearest-package routing: a currency-rounding task routes to 'pnpm --dir packages/utils run test' and a discount-code task routes to 'pnpm --dir apps/api run test' instead of the root 'turbo run test'. Full generated reports are checked in under examples/reports/ (outside the example root so reports never feed back into their own scan). scripts/smoke-workspace.mjs runs in 'npm run smoke': asserts the nearest-package route and context file for both tasks and fails when the checked-in reports drift; --update regenerates them. Fixes #14 Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent d919c8b commit 4e7eb9a

16 files changed

Lines changed: 268 additions & 1 deletion

File tree

examples/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,9 @@ Expected shape:
2121
- The report should include an authentication risk note.
2222

2323
See `reports/password-reset.md` for a sample report.
24+
25+
## pnpm + Turborepo Workspace
26+
27+
`pnpm-turbo-workspace` is a two-package pnpm monorepo with Turborepo task wiring. It proves that test routing picks the nearest package script (`pnpm --dir packages/utils run test` or `pnpm --dir apps/api run test`) depending on the task, instead of always suggesting the root `turbo run test`.
28+
29+
The input tasks and full expected reports are checked in; `npm run smoke` asserts the routing and fails on report drift. See [`pnpm-turbo-workspace/README.md`](pnpm-turbo-workspace/README.md).
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# pnpm + Turborepo Workspace Example
2+
3+
A minimal pnpm workspace with Turborepo task wiring, used to prove workspace-aware script routing on a realistic monorepo shape:
4+
5+
```text
6+
apps/api @example/api (test, typecheck scripts)
7+
packages/utils @example/utils (test, typecheck scripts)
8+
package.json root turbo run test / typecheck
9+
```
10+
11+
`apps/api` depends on `@example/utils`, and both packages own separate `test` and `typecheck` scripts, so the right routing answer changes with the task.
12+
13+
## Tasks and expected routing
14+
15+
From the repository root:
16+
17+
```bash
18+
npm install
19+
npm run build
20+
node packages/cli/dist/cli.js plan --issue "roundToCents keeps fractions of a cent when formatting currency" --repo examples/pnpm-turbo-workspace
21+
```
22+
23+
- Context should rank `packages/utils/src/currency.ts`.
24+
- The first test route should be `pnpm --dir packages/utils run test` — the nearest package, not the root `turbo run test`.
25+
26+
```bash
27+
node packages/cli/dist/cli.js plan --issue "orderTotal ignores unknown discountCode values" --repo examples/pnpm-turbo-workspace
28+
```
29+
30+
- Context should rank `apps/api/src/orders.ts` first.
31+
- The first test route should switch to `pnpm --dir apps/api run test`.
32+
33+
The full generated reports are checked in under [`../reports/`](../reports) (outside this example so the reports never feed back into their own scan):
34+
35+
- [`../reports/workspace-utils-rounding.md`](../reports/workspace-utils-rounding.md)
36+
- [`../reports/workspace-api-discount.md`](../reports/workspace-api-discount.md)
37+
38+
## CI guard
39+
40+
`npm run smoke` runs [`scripts/smoke-workspace.mjs`](../../scripts/smoke-workspace.mjs), which asserts the nearest-package routing for both tasks and fails if the checked-in reports drift from generated output. After an intentional ranking change, refresh them with:
41+
42+
```bash
43+
node scripts/smoke-workspace.mjs --update
44+
```
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"name": "@example/api",
3+
"version": "0.0.0",
4+
"private": true,
5+
"type": "module",
6+
"scripts": {
7+
"test": "vitest run",
8+
"typecheck": "tsc --noEmit"
9+
},
10+
"dependencies": {
11+
"@example/utils": "workspace:*"
12+
},
13+
"devDependencies": {
14+
"typescript": "^6.0.0",
15+
"vitest": "^4.0.0"
16+
}
17+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { applyDiscount } from "@example/utils";
2+
3+
export type Order = {
4+
total: number;
5+
discountCode?: string;
6+
};
7+
8+
export function orderTotal(order: Order): number {
9+
if (order.discountCode === "SAVE10") {
10+
return applyDiscount(order.total, 10);
11+
}
12+
return order.total;
13+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { expect, it } from "vitest";
2+
import { orderTotal } from "../src/orders.js";
3+
4+
it("applies the SAVE10 discount code", () => {
5+
expect(orderTotal({ total: 20, discountCode: "SAVE10" })).toBe(18);
6+
});
7+
8+
it("charges the full total without a discount code", () => {
9+
expect(orderTotal({ total: 20 })).toBe(20);
10+
});
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"name": "pnpm-turbo-workspace-example",
3+
"version": "0.0.0",
4+
"private": true,
5+
"scripts": {
6+
"test": "turbo run test",
7+
"typecheck": "turbo run typecheck"
8+
},
9+
"devDependencies": {
10+
"turbo": "^2.5.0"
11+
}
12+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"name": "@example/utils",
3+
"version": "0.0.0",
4+
"private": true,
5+
"type": "module",
6+
"scripts": {
7+
"test": "vitest run",
8+
"typecheck": "tsc --noEmit"
9+
},
10+
"devDependencies": {
11+
"typescript": "^6.0.0",
12+
"vitest": "^4.0.0"
13+
}
14+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export function applyDiscount(total: number, percent: number): number {
2+
const discounted = total * (1 - percent / 100);
3+
return roundToCents(discounted);
4+
}
5+
6+
export function roundToCents(amount: number): number {
7+
return Math.round(amount * 100) / 100;
8+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { expect, it } from "vitest";
2+
import { applyDiscount, roundToCents } from "../src/currency.js";
3+
4+
it("applies a percentage discount", () => {
5+
expect(applyDiscount(20, 10)).toBe(18);
6+
});
7+
8+
it("rounds amounts to whole cents", () => {
9+
expect(roundToCents(17.999)).toBe(18);
10+
});

examples/pnpm-turbo-workspace/pnpm-lock.yaml

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)