Skip to content

Commit 93c5ee9

Browse files
committed
chore(PLA-118): migrate to pnpm workspace (packages/cli, packages/types, packages/web)
Restructure stage-cli into a pnpm workspace mirroring diffity's layout. Drops the @cli/types path-alias indirection in favor of a real workspace package (@stage-cli/types) that tsdown inlines into the published bundle and vite resolves natively for the SPA. Pure structural change — every gate that passed before still passes (typecheck, lint, test, build, and publish --dry-run).
1 parent ec6aad3 commit 93c5ee9

88 files changed

Lines changed: 415 additions & 284 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ jobs:
3535
- name: Check Drizzle migrations are up to date
3636
run: |
3737
pnpm db:generate
38-
git diff --exit-code drizzle/ || (echo "::error::Drizzle migrations are out of date. Run 'pnpm db:generate' and commit." && exit 1)
38+
git diff --exit-code packages/cli/drizzle/ || (echo "::error::Drizzle migrations are out of date. Run 'pnpm db:generate' and commit." && exit 1)
3939
4040
- name: Lint
4141
run: pnpm lint
@@ -47,6 +47,4 @@ jobs:
4747
run: pnpm test
4848

4949
- name: Build
50-
run: |
51-
pnpm build
52-
pnpm build:web
50+
run: pnpm build

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ node_modules
55
dist
66
web-dist
77

8+
# Generated at publish time by packages/cli prepack — root README is the source of truth.
9+
packages/cli/README.md
10+
811
# TypeScript incremental builds
912
*.tsbuildinfo
1013

AGENTS.md

Lines changed: 51 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -7,76 +7,93 @@ This file provides guidance to coding agents working in this repository, includi
77
```bash
88
pnpm install # Install dependencies (also installs husky pre-commit hook)
99
pnpm dev:web # Start the web UI in Vite dev mode
10-
pnpm build # Bundle the CLI with tsdown into dist/
11-
pnpm build:web # Build the web UI with Vite into web-dist/
12-
pnpm test # Run tests (Vitest)
10+
pnpm build # Build SPA, then bundle the CLI (writes packages/cli/{dist,web-dist})
11+
pnpm test # Run tests (Vitest, from root)
1312
pnpm lint # Biome check (lint + format) — fails on warnings
1413
pnpm lint:fix # Biome check with auto-fix
1514
pnpm format # Format code with Biome
16-
pnpm typecheck # tsc --noEmit for both root and web tsconfigs
15+
pnpm typecheck # tsc --noEmit across every package (`pnpm -r typecheck`)
1716
```
1817

1918
The package manager is pinned via `packageManager` in `package.json`. Use `corepack enable` if pnpm isn't on your PATH.
2019

2120
### Database (Drizzle ORM + SQLite)
2221

2322
```bash
24-
pnpm db:generate # Generate a new migration into drizzle/ from schema changes
23+
pnpm db:generate # Generate a new migration into packages/cli/drizzle/ from schema changes
2524
```
2625

2726
The CLI uses an embedded SQLite database via `better-sqlite3`. There is no separate dev database to start — `getDb()` opens (or creates) the local SQLite file and runs pending migrations on first use.
2827

2928
### Adding UI Components
3029

3130
```bash
32-
npx shadcn@latest add <component>
31+
cd packages/web && npx shadcn@latest add <component>
3332
```
3433

35-
Components land under `web/src/components/ui/` per `components.json`.
34+
Components land under `packages/web/src/components/ui/` per `packages/web/components.json`.
3635

3736
## Architecture
3837

39-
**Single npm package**, published as `stagereview` with the `stage-cli` binary. The CLI starts a local-loopback HTTP server that serves the prebuilt React UI and a small JSON API.
38+
**pnpm workspace.** Three packages with real boundaries — no path-alias indirection. The published unit is `packages/cli` (npm name `stagereview`, binary `stage-cli`); the rest are private workspace deps that get inlined at build time.
4039

4140
```
42-
src/ # CLI + local HTTP server (Node, ESM)
43-
index.ts # CLI entry (Commander)
44-
show.ts # `stage-cli show <path>` implementation
45-
server.ts # Plain Node http server with regex-compiled routes
46-
routes/ # API route handlers (one file per resource)
47-
runs/ # Chapter run import + processing
48-
db/ # Drizzle client, path resolution, schema/
49-
schema.ts # Zod schemas for chapter JSON ingestion
50-
__tests__/ # Vitest tests
51-
drizzle/ # Generated SQL migrations + meta journal
52-
web/ # Vite + React 19 + Tailwind 4 frontend (built into web-dist/)
53-
src/components/ # UI components (shadcn/ui under components/ui/)
54-
src/lib/ # Frontend utilities
55-
src/styles/ # Tailwind globals
41+
pnpm-workspace.yaml # packages: ["packages/*"]
42+
packages/
43+
cli/ # stagereview — published npm package
44+
src/ # CLI + local HTTP server (Node, ESM)
45+
index.ts # CLI entry (Commander)
46+
show.ts # `stage-cli show <path>` implementation
47+
server.ts # Plain Node http server with regex-compiled routes
48+
routes/ # API route handlers (one file per resource)
49+
runs/ # Chapter run import + processing
50+
db/ # Drizzle client, path resolution, schema/
51+
schema.ts # Zod schemas for chapter JSON ingestion (strict)
52+
__tests__/ # Vitest tests
53+
drizzle/ # Generated SQL migrations + meta journal
54+
drizzle.config.ts # Drizzle Kit config
55+
tsdown.config.ts # CLI bundler config (inlines @stage-cli/types)
56+
types/ # @stage-cli/types (private, TS-native)
57+
src/chapters.ts # Wire-format chapter/key-change schemas + shared HunkRef/LineRef
58+
src/view-state.ts # Wire-format view-state schema
59+
src/index.ts # Barrel re-export
60+
web/ # @stage-cli/web (private) — built into ../cli/web-dist
61+
src/components/ # UI components (shadcn/ui under components/ui/)
62+
src/lib/ # Frontend utilities + tests
63+
src/routes/ # SPA route components
64+
src/styles/ # Tailwind globals
65+
vite.config.ts # outDir → ../cli/web-dist
66+
components.json # shadcn config
5667
```
5768

58-
### CLI (`src/index.ts`)
69+
### CLI (`packages/cli/src/index.ts`)
5970

60-
Uses [Commander](https://github.com/tj/commander.js) for subcommand parsing. Add new subcommands by chaining `.command(...)` and delegating to a module under `src/`.
71+
Uses [Commander](https://github.com/tj/commander.js) for subcommand parsing. Add new subcommands by chaining `.command(...)` and delegating to a module under `packages/cli/src/`.
6172

62-
### Local Server (`src/server.ts`)
73+
### Local Server (`packages/cli/src/server.ts`)
6374

64-
Plain Node `http` server bound to `127.0.0.1`. Route patterns use `:name` placeholders and are compiled to regexes at startup. The server resolves `/api/*` against registered routes and otherwise serves static files from `web-dist/` with an `index.html` SPA fallback.
75+
Plain Node `http` server bound to `127.0.0.1`. Route patterns use `:name` placeholders and are compiled to regexes at startup. The server resolves `/api/*` against registered routes and otherwise serves static files from `web-dist/` (next to the bundled CLI) with an `index.html` SPA fallback.
6576

66-
- Route handlers live in `src/routes/` — one file per resource (`runs.ts`, `view-state.ts`, `json.ts`).
77+
- Route handlers live in `packages/cli/src/routes/` — one file per resource (`runs.ts`, `view-state.ts`, `json.ts`).
6778
- Path traversal is blocked by computing `path.relative(webDist, resolved)` and rejecting any result that escapes the root. **Don't bypass that check** when adding static-serving features.
6879
- The server picks the first free port starting at `5391`. Don't hard-code ports in callers.
6980

70-
### Database Layer (`src/db/`)
81+
### Database Layer (`packages/cli/src/db/`)
7182

72-
- **Client:** `getDb()` in `src/db/client.ts` is a singleton wrapped around `better-sqlite3`. It enables WAL + foreign keys and auto-runs migrations from `drizzle/`.
73-
- **Schemas:** `src/db/schema/*.ts`, re-exported from `src/db/schema/index.ts`. Pass `* as schema` into `drizzle()` so relational queries work.
74-
- **Path:** `src/db/path.ts` decides where the SQLite file lives (per-OS app data dir).
83+
- **Client:** `getDb()` in `db/client.ts` is a singleton wrapped around `better-sqlite3`. It enables WAL + foreign keys and auto-runs migrations from `packages/cli/drizzle/`.
84+
- **Schemas:** `db/schema/*.ts`, re-exported from `db/schema/index.ts`. Pass `* as schema` into `drizzle()` so relational queries work.
85+
- **Path:** `db/path.ts` decides where the SQLite file lives (per-OS app data dir).
7586
- Prefer Drizzle's Relational Queries API over the SQL-like query builder unless you need aggregations, custom column selections, or complex joins.
7687

77-
### Web UI (`web/`)
88+
### Shared Types (`packages/types/`)
7889

79-
Vite app with React 19, Tailwind 4, and shadcn/ui (new-york style, zinc base, lucide icons). Built to `web-dist/`, which is bundled into the published npm package and served by the CLI's local HTTP server.
90+
Wire-format types shared between the CLI's HTTP routes and the SPA. The package exports `.ts` source directly (no compile step) — `tsdown` and `vite` resolve TypeScript natively. The CLI bundle inlines this package via `deps.alwaysBundle` in `tsdown.config.ts`, so the published tarball never has a runtime require for `@stage-cli/types`.
91+
92+
Building blocks like `HunkRef`, `LineRef`, and `DIFF_SIDE` live here; the strict ingestion schema (`ChaptersFileSchema`) stays in `packages/cli/src/schema.ts` and re-exports them.
93+
94+
### Web UI (`packages/web/`)
95+
96+
Vite app with React 19, Tailwind 4, and shadcn/ui (new-york style, zinc base, lucide icons). Builds into `../cli/web-dist`, which is bundled into the published npm package and served by the CLI's local HTTP server.
8097

8198
### Key Technologies
8299

@@ -103,7 +120,7 @@ A `pre-commit` hook (husky + lint-staged) runs `biome check --write` against sta
103120

104121
## Package Naming
105122

106-
This is a single-package repo published as `stagereview`. The CLI binary is `stage-cli`. There is no monorepo or workspace scope.
123+
The published npm package is `stagereview` (lives in `packages/cli`); the CLI binary is `stage-cli`. Internal workspace packages use the `@stage-cli/*` scope (`@stage-cli/types`, `@stage-cli/web`) — they are private and never published.
107124

108125
## Testing
109126

TESTING.md

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,20 @@ Test API route handlers through the real `startServer()` HTTP boundary, hitting
2222
**This is the highest-ROI test layer.** Most logic worth testing is request handling, schema validation, and database state transitions.
2323

2424
Examples:
25-
- `src/__tests__/runs.routes.test.ts` — exercises run/chapter routes against a real server + SQLite
26-
- `src/__tests__/view-state.routes.test.ts` — exercises view-state routes end-to-end
27-
- `src/__tests__/server.test.ts` — covers the static-file fallback, route compilation, and path-traversal guard
25+
- `packages/cli/src/__tests__/runs.routes.test.ts` — exercises run/chapter routes against a real server + SQLite
26+
- `packages/cli/src/__tests__/view-state.routes.test.ts` — exercises view-state routes end-to-end
27+
- `packages/cli/src/__tests__/server.test.ts` — covers the static-file fallback, route compilation, and path-traversal guard
2828

29-
Use the helpers in `src/__tests__/fixtures.ts` to spin up a temp DB and the server.
29+
Use the helpers in `packages/cli/src/__tests__/fixtures.ts` to spin up a temp DB and the server.
3030

3131
### 3. Pure Logic Unit Tests
3232

3333
Schemas, parsers, and pure helpers. No mocks needed — these are pure functions.
3434

3535
Examples:
36-
- `src/__tests__/schema.test.ts` — Zod chapter-import schemas
37-
- `src/__tests__/path.test.ts` — DB path resolution
38-
- `src/__tests__/import-chapters.test.ts` — chapter import transformation
36+
- `packages/cli/src/__tests__/schema.test.ts` — Zod chapter-import schemas
37+
- `packages/cli/src/__tests__/path.test.ts` — DB path resolution
38+
- `packages/cli/src/__tests__/import-chapters.test.ts` — chapter import transformation
3939

4040
### 4. Web UI Component Tests
4141

@@ -44,9 +44,9 @@ Narrow exception: tests for keyboard navigation, focus management, and form beha
4444
**Constraints:**
4545
- Must mock zero or one external boundary (the CLI's `/api/*` fetch calls)
4646
- Must mock at most one internal module
47-
- If a test needs more, lift the logic out of the component into `web/src/lib/` and test it there
47+
- If a test needs more, lift the logic out of the component into `packages/web/src/lib/` and test it there
4848

49-
There are no web UI tests today. If you add the first one, set up a JSDOM Vitest project under `web/` rather than mixing it into the Node test config.
49+
Web tests live alongside their modules under `packages/web/src/lib/__tests__/` and use happy-dom (set per-file via the `// @vitest-environment happy-dom` directive). Vitest runs from the workspace root and picks up tests in any package.
5050

5151
## What to Test
5252

@@ -92,7 +92,7 @@ If a test needs 2+ internal mocks, the test is testing the mock setup, not the a
9292
3. **Never mock more than one external-service boundary** per test file.
9393
4. **Never mock more than one internal module** per test file.
9494
5. **Never test that a component "renders without crashing"** — TypeScript already guarantees this.
95-
6. **Factory functions over inline object literals.** Use `make*` or `create*` helpers in `src/__tests__/fixtures.ts` (or alongside the test file) with overrides.
95+
6. **Factory functions over inline object literals.** Use `make*` or `create*` helpers in `packages/cli/src/__tests__/fixtures.ts` (or alongside the test file) with overrides.
9696
7. **One clear behavior per test.** Name by behavior, not method name.
9797
8. **Arrange-Act-Assert.** One clear action per test.
9898
9. **Use a real DB, not a mock.** Spin up a temp SQLite via the existing fixtures. Drizzle/better-sqlite3 are fast enough that mocking them is never the right call.
@@ -135,6 +135,6 @@ When modifying code that is covered by a slop test (a test that violates the moc
135135
| Path-resolution / static-file change | Route/server integration | Required |
136136
| Visual-only UI change | None | N/A |
137137
| New parser / transformer | Pure unit | Required |
138-
| New React component (logic-heavy) | Extract logic to `web/src/lib/`, test there | Optional |
138+
| New React component (logic-heavy) | Extract logic to `packages/web/src/lib/`, test there | Optional |
139139
| New React component (display-only) | None | N/A |
140140
| New schema migration | Route integration that exercises new columns | Required |

biome.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"!**/node_modules",
1818
"!**/dist",
1919
"!**/web-dist",
20-
"!drizzle"
20+
"!**/drizzle"
2121
]
2222
},
2323
"linter": {

package.json

Lines changed: 7 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,8 @@
11
{
2-
"name": "stagereview",
2+
"name": "stagereview-monorepo",
33
"version": "0.1.0",
4-
"description": "Chapter-style code review against your local git branch.",
5-
"keywords": [
6-
"code-review",
7-
"cli",
8-
"chapters",
9-
"git",
10-
"claude-code",
11-
"cursor",
12-
"codex",
13-
"gemini",
14-
"opencode"
15-
],
4+
"private": true,
5+
"description": "pnpm workspace root for stagereview (packages/cli) and its supporting packages.",
166
"homepage": "https://github.com/ReviewStage/stage-cli#readme",
177
"bugs": {
188
"url": "https://github.com/ReviewStage/stage-cli/issues"
@@ -25,29 +15,18 @@
2515
"author": "Stage",
2616
"type": "module",
2717
"packageManager": "pnpm@10.24.0",
28-
"main": "./dist/index.js",
29-
"bin": {
30-
"stage-cli": "./dist/index.js"
31-
},
32-
"files": [
33-
"dist",
34-
"drizzle",
35-
"web-dist",
36-
"skills"
37-
],
3818
"engines": {
3919
"node": ">=20"
4020
},
4121
"scripts": {
42-
"build": "tsdown",
43-
"build:web": "vite build --config web/vite.config.ts",
44-
"dev:web": "vite --config web/vite.config.ts",
22+
"build": "pnpm --filter @stage-cli/web build && pnpm --filter stagereview build",
23+
"dev:web": "pnpm --filter @stage-cli/web dev",
4524
"test": "vitest run",
4625
"lint": "biome check .",
4726
"lint:fix": "biome check --write .",
4827
"format": "biome format --write .",
49-
"typecheck": "tsc --noEmit && tsc --noEmit -p web/tsconfig.json",
50-
"db:generate": "drizzle-kit generate",
28+
"typecheck": "pnpm -r typecheck",
29+
"db:generate": "pnpm --filter stagereview db:generate",
5130
"prepare": "husky"
5231
},
5332
"lint-staged": {
@@ -57,50 +36,12 @@
5736
},
5837
"devDependencies": {
5938
"@biomejs/biome": "^2.3.10",
60-
"@pierre/diffs": "^1.0.11",
61-
"@radix-ui/react-checkbox": "^1.3.3",
62-
"@radix-ui/react-collapsible": "^1.1.12",
63-
"@radix-ui/react-progress": "^1.1.8",
64-
"@radix-ui/react-separator": "^1.1.8",
65-
"@radix-ui/react-slot": "^1.2.4",
66-
"@radix-ui/react-tooltip": "^1.2.8",
67-
"@tailwindcss/vite": "^4.1.18",
68-
"@tanstack/react-query": "^5.100.7",
6939
"@testing-library/react": "^16.3.2",
70-
"@types/better-sqlite3": "^7.6.13",
7140
"@types/node": "^25.6.0",
72-
"@types/react": "^19.2.5",
73-
"@types/react-dom": "^19.2.3",
74-
"@vitejs/plugin-react": "^5.1.2",
75-
"class-variance-authority": "^0.7.1",
76-
"clsx": "^2.1.1",
77-
"drizzle-kit": "^0.31.10",
7841
"happy-dom": "^20.9.0",
7942
"husky": "^9.1.7",
8043
"lint-staged": "^16.2.7",
81-
"lucide-react": "^0.562.0",
82-
"react": "^19.2.3",
83-
"react-dom": "^19.2.3",
84-
"tailwind-merge": "^3.3.1",
85-
"tailwindcss": "^4.1.18",
86-
"tsdown": "^0.21.10",
87-
"tw-animate-css": "^1.4.0",
8844
"typescript": "^5.6.3",
89-
"vite": "^7.3.1",
90-
"vite-tsconfig-paths": "^6.1.1",
9145
"vitest": "^4.1.5"
92-
},
93-
"dependencies": {
94-
"better-sqlite3": "^12.9.0",
95-
"commander": "^14.0.3",
96-
"drizzle-orm": "^0.45.2",
97-
"open": "^11.0.0",
98-
"zod": "^4.3.6"
99-
},
100-
"pnpm": {
101-
"onlyBuiltDependencies": [
102-
"better-sqlite3",
103-
"esbuild"
104-
]
10546
}
10647
}
File renamed without changes.

0 commit comments

Comments
 (0)