You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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).
pnpm typecheck # tsc --noEmit for both root and web tsconfigs
15
+
pnpm typecheck # tsc --noEmit across every package (`pnpm -r typecheck`)
17
16
```
18
17
19
18
The package manager is pinned via `packageManager` in `package.json`. Use `corepack enable` if pnpm isn't on your PATH.
20
19
21
20
### Database (Drizzle ORM + SQLite)
22
21
23
22
```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
25
24
```
26
25
27
26
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.
28
27
29
28
### Adding UI Components
30
29
31
30
```bash
32
-
npx shadcn@latest add <component>
31
+
cd packages/web &&npx shadcn@latest add <component>
33
32
```
34
33
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`.
36
35
37
36
## Architecture
38
37
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.
40
39
41
40
```
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
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
56
67
```
57
68
58
-
### CLI (`src/index.ts`)
69
+
### CLI (`packages/cli/src/index.ts`)
59
70
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/`.
61
72
62
-
### Local Server (`src/server.ts`)
73
+
### Local Server (`packages/cli/src/server.ts`)
63
74
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.
65
76
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`).
67
78
- 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.
68
79
- The server picks the first free port starting at `5391`. Don't hard-code ports in callers.
69
80
70
-
### Database Layer (`src/db/`)
81
+
### Database Layer (`packages/cli/src/db/`)
71
82
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).
75
86
- Prefer Drizzle's Relational Queries API over the SQL-like query builder unless you need aggregations, custom column selections, or complex joins.
76
87
77
-
### Web UI (`web/`)
88
+
### Shared Types (`packages/types/`)
78
89
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.
80
97
81
98
### Key Technologies
82
99
@@ -103,7 +120,7 @@ A `pre-commit` hook (husky + lint-staged) runs `biome check --write` against sta
103
120
104
121
## Package Naming
105
122
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.
@@ -44,9 +44,9 @@ Narrow exception: tests for keyboard navigation, focus management, and form beha
44
44
**Constraints:**
45
45
- Must mock zero or one external boundary (the CLI's `/api/*` fetch calls)
46
46
- 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
48
48
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.
50
50
51
51
## What to Test
52
52
@@ -92,7 +92,7 @@ If a test needs 2+ internal mocks, the test is testing the mock setup, not the a
92
92
3.**Never mock more than one external-service boundary** per test file.
93
93
4.**Never mock more than one internal module** per test file.
94
94
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.
96
96
7.**One clear behavior per test.** Name by behavior, not method name.
97
97
8.**Arrange-Act-Assert.** One clear action per test.
98
98
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
0 commit comments