diff --git a/.agents/agents/code-architecture-reviewer.md b/.agents/agents/code-architecture-reviewer.md index 26ffaa4..708f9ed 100644 --- a/.agents/agents/code-architecture-reviewer.md +++ b/.agents/agents/code-architecture-reviewer.md @@ -13,7 +13,7 @@ You have comprehensive understanding of: - The established coding standards and patterns documented in `AGENTS.md` - The testing strategy in `TESTING.md` - Common pitfalls and anti-patterns to avoid -- Performance, security, and maintainability considerations — especially for the path-traversal guard in `src/server.ts` +- Performance, security, and maintainability considerations — especially for the path-traversal guard in `packages/cli/src/server.ts` **Documentation References**: - Check `AGENTS.md` for architecture overview, code style, and implementation-quality principles @@ -36,21 +36,21 @@ When reviewing code, you will: - Identify potential technical debt or future maintenance issues 3. **Verify System Integration**: - - Ensure new code properly integrates with the local HTTP server in `src/server.ts` and the route compilation it provides + - Ensure new code properly integrates with the local HTTP server in `packages/cli/src/server.ts` and the route compilation it provides - Check that database operations use Drizzle correctly (Relational Queries API by default; query builder only when needed) - - Confirm migrations land in `drizzle/` and the schema is re-exported from `src/db/schema/index.ts` - - Verify the path-traversal guard in `src/server.ts` is preserved when touching static-file serving + - Confirm migrations land in `packages/cli/drizzle/` and the schema is re-exported from `packages/cli/src/db/schema/index.ts` + - Verify the path-traversal guard in `packages/cli/src/server.ts` is preserved when touching static-file serving - Verify any new web UI fetches go to `/api/*` rather than reaching outside the local server 4. **Assess Architectural Fit**: - - Evaluate if the code belongs in `src/` (CLI/server) or `web/` (React UI) - - Check for proper separation of concerns: routes in `src/routes/`, DB code in `src/db/`, schemas in `src/schema.ts` - - Ensure module boundaries are respected — no `web/` imports from `src/` or vice versa - - Validate that shared types live with their feature, not in a sprawling global types directory + - Evaluate which workspace package the code belongs in: `packages/cli` (CLI/server), `packages/web` (React UI), or `packages/types` (wire-format types shared between them) + - Check for proper separation of concerns: routes in `packages/cli/src/routes/`, DB code in `packages/cli/src/db/`, ingestion schemas in `packages/cli/src/schema.ts` + - Ensure module boundaries are respected — `packages/web` and `packages/cli` may depend on `@stage-cli/types`, but never on each other + - Validate that shared wire-format types live in `packages/types`, not duplicated across the CLI and web packages 5. **Review Specific Technologies**: - For React: Verify functional components, proper hook usage, and Tailwind 4 / shadcn/ui patterns; follow "You Might Not Need an Effect" - - For API: Ensure new routes follow the existing pattern in `src/routes/` and register through `startServer()`; no direct port hard-coding + - For API: Ensure new routes follow the existing pattern in `packages/cli/src/routes/` and register through `startServer()`; no direct port hard-coding - For Database: Confirm Drizzle best practices and avoid raw SQL except in migrations - For State: Check appropriate use of React state; no premature global stores diff --git a/.agents/agents/frontend-error-fixer.md b/.agents/agents/frontend-error-fixer.md index 0fc2877..84c6ba9 100644 --- a/.agents/agents/frontend-error-fixer.md +++ b/.agents/agents/frontend-error-fixer.md @@ -46,7 +46,7 @@ You are an expert frontend debugging specialist with deep knowledge of modern we 5. **Verification**: - Confirm the error is resolved - Check for any new errors introduced by the fix - - Ensure the build passes with `pnpm build` (CLI) and `pnpm build:web` (web UI), and that `pnpm typecheck` is clean + - Ensure the build passes with `pnpm build` (builds the SPA and bundles the CLI) and that `pnpm typecheck` is clean - Test the affected functionality **Common Error Patterns You Handle:** diff --git a/.claude/agents/auto-error-resolver.md b/.claude/agents/auto-error-resolver.md index 35ff7eb..63f3abf 100644 --- a/.claude/agents/auto-error-resolver.md +++ b/.claude/agents/auto-error-resolver.md @@ -14,7 +14,7 @@ You are a specialized TypeScript error resolution agent. Your primary job is to - Get TSC commands at: `~/.claude/tsc-cache/[session_id]/tsc-commands.txt` 2. **Reproduce locally**: - - Run `npx tsc --noEmit` (root) and `npx tsc --noEmit -p web/tsconfig.json` (web UI) + - Run `pnpm typecheck` to typecheck every workspace package - For runtime errors during dev, run `pnpm dev:web` and watch the Vite output 3. **Analyze the errors** systematically: @@ -74,16 +74,16 @@ cat ~/.claude/tsc-cache/*/tsc-commands.txt # (Edit the ButtonProps interface to include onClick) # 5. Verify the fix -npx tsc --noEmit # CLI / src -npx tsc --noEmit -p web/tsconfig.json # web UI +pnpm typecheck # runs tsc --noEmit across every workspace package ``` ## TypeScript Commands -This is a single Node package (managed with pnpm) with two `tsconfig.json` files: -- **CLI / src**: `npx tsc --noEmit` (root tsconfig) -- **Web UI**: `npx tsc --noEmit -p web/tsconfig.json` +This is a pnpm workspace with three packages, each with its own `tsconfig.json`: +- **CLI** (`packages/cli`): `pnpm --filter stagereview typecheck` +- **Types** (`packages/types`): `pnpm --filter @stage-cli/types typecheck` +- **Web UI** (`packages/web`): `pnpm --filter @stage-cli/web typecheck` -If a hook has saved a command at `~/.claude/tsc-cache/*/tsc-commands.txt`, prefer that. Otherwise, run both of the commands above. +`pnpm typecheck` from the workspace root runs all three (`pnpm -r typecheck`). If a hook has saved a command at `~/.claude/tsc-cache/*/tsc-commands.txt`, prefer that. Report completion with a summary of what was fixed. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d99c1a2..ef0aa0a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,7 +35,7 @@ jobs: - name: Check Drizzle migrations are up to date run: | pnpm db:generate - git diff --exit-code drizzle/ || (echo "::error::Drizzle migrations are out of date. Run 'pnpm db:generate' and commit." && exit 1) + git diff --exit-code packages/cli/drizzle/ || (echo "::error::Drizzle migrations are out of date. Run 'pnpm db:generate' and commit." && exit 1) - name: Lint run: pnpm lint @@ -47,6 +47,4 @@ jobs: run: pnpm test - name: Build - run: | - pnpm build - pnpm build:web + run: pnpm build diff --git a/.gitignore b/.gitignore index cd4cb91..2d32e98 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,9 @@ node_modules dist web-dist +# Generated at publish time by packages/cli prepack — root README is the source of truth. +packages/cli/README.md + # TypeScript incremental builds *.tsbuildinfo diff --git a/AGENTS.md b/AGENTS.md index a1e1d10..afd1c59 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -7,13 +7,12 @@ This file provides guidance to coding agents working in this repository, includi ```bash pnpm install # Install dependencies (also installs husky pre-commit hook) pnpm dev:web # Start the web UI in Vite dev mode -pnpm build # Bundle the CLI with tsdown into dist/ -pnpm build:web # Build the web UI with Vite into web-dist/ -pnpm test # Run tests (Vitest) +pnpm build # Build SPA, then bundle the CLI (writes packages/cli/{dist,web-dist}) +pnpm test # Run tests (Vitest, from root) pnpm lint # Biome check (lint + format) — fails on warnings pnpm lint:fix # Biome check with auto-fix pnpm format # Format code with Biome -pnpm typecheck # tsc --noEmit for both root and web tsconfigs +pnpm typecheck # tsc --noEmit across every package (`pnpm -r typecheck`) ``` The package manager is pinned via `packageManager` in `package.json`. Use `corepack enable` if pnpm isn't on your PATH. @@ -21,7 +20,7 @@ The package manager is pinned via `packageManager` in `package.json`. Use `corep ### Database (Drizzle ORM + SQLite) ```bash -pnpm db:generate # Generate a new migration into drizzle/ from schema changes +pnpm db:generate # Generate a new migration into packages/cli/drizzle/ from schema changes ``` 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. @@ -29,54 +28,72 @@ The CLI uses an embedded SQLite database via `better-sqlite3`. There is no separ ### Adding UI Components ```bash -npx shadcn@latest add +cd packages/web && npx shadcn@latest add ``` -Components land under `web/src/components/ui/` per `components.json`. +Components land under `packages/web/src/components/ui/` per `packages/web/components.json`. ## Architecture -**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. +**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. ``` -src/ # CLI + local HTTP server (Node, ESM) - index.ts # CLI entry (Commander) - show.ts # `stage-cli show ` implementation - server.ts # Plain Node http server with regex-compiled routes - routes/ # API route handlers (one file per resource) - runs/ # Chapter run import + processing - db/ # Drizzle client, path resolution, schema/ - schema.ts # Zod schemas for chapter JSON ingestion - __tests__/ # Vitest tests -drizzle/ # Generated SQL migrations + meta journal -web/ # Vite + React 19 + Tailwind 4 frontend (built into web-dist/) - src/components/ # UI components (shadcn/ui under components/ui/) - src/lib/ # Frontend utilities - src/styles/ # Tailwind globals +pnpm-workspace.yaml # packages: ["packages/*"] +packages/ + cli/ # stagereview — published npm package + src/ # CLI + local HTTP server (Node, ESM) + index.ts # CLI entry (Commander) + show.ts # `stage-cli show ` implementation + server.ts # Plain Node http server with regex-compiled routes + routes/ # API route handlers (one file per resource) + runs/ # Chapter run import + processing + db/ # Drizzle client, path resolution, schema/ + schema.ts # Zod schemas for chapter JSON ingestion (strict) + __tests__/ # Vitest tests + drizzle/ # Generated SQL migrations + meta journal + drizzle.config.ts # Drizzle Kit config + tsdown.config.ts # CLI bundler config (inlines @stage-cli/types) + types/ # @stage-cli/types (private, TS-native) + src/chapters.ts # Wire-format chapter/key-change schemas + shared HunkReference/LineRef + src/view-state.ts # Wire-format view-state schema + src/index.ts # Barrel re-export + web/ # @stage-cli/web (private) — built into ../cli/web-dist + src/components/ # UI components (shadcn/ui under components/ui/) + src/lib/ # Frontend utilities + tests + src/routes/ # SPA route components + src/styles/ # Tailwind globals + vite.config.ts # outDir → ../cli/web-dist + components.json # shadcn config ``` -### CLI (`src/index.ts`) +### CLI (`packages/cli/src/index.ts`) -Uses [Commander](https://github.com/tj/commander.js) for subcommand parsing. Add new subcommands by chaining `.command(...)` and delegating to a module under `src/`. +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/`. -### Local Server (`src/server.ts`) +### Local Server (`packages/cli/src/server.ts`) -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. +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. -- Route handlers live in `src/routes/` — one file per resource (`runs.ts`, `view-state.ts`, `json.ts`). +- Route handlers live in `packages/cli/src/routes/` — one file per resource (`runs.ts`, `view-state.ts`, `json.ts`). - 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. - The server picks the first free port starting at `5391`. Don't hard-code ports in callers. -### Database Layer (`src/db/`) +### Database Layer (`packages/cli/src/db/`) -- **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/`. -- **Schemas:** `src/db/schema/*.ts`, re-exported from `src/db/schema/index.ts`. Pass `* as schema` into `drizzle()` so relational queries work. -- **Path:** `src/db/path.ts` decides where the SQLite file lives (per-OS app data dir). +- **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/`. +- **Schemas:** `db/schema/*.ts`, re-exported from `db/schema/index.ts`. Pass `* as schema` into `drizzle()` so relational queries work. +- **Path:** `db/path.ts` decides where the SQLite file lives (per-OS app data dir). - Prefer Drizzle's Relational Queries API over the SQL-like query builder unless you need aggregations, custom column selections, or complex joins. -### Web UI (`web/`) +### Shared Types (`packages/types/`) -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. +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`. + +Building blocks like `HunkReference`, `LineRef`, and `DIFF_SIDE` live here; the strict ingestion schema (`ChaptersFileSchema`) stays in `packages/cli/src/schema.ts` and re-exports them. + +### Web UI (`packages/web/`) + +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. ### Key Technologies @@ -103,7 +120,7 @@ A `pre-commit` hook (husky + lint-staged) runs `biome check --write` against sta ## Package Naming -This is a single-package repo published as `stagereview`. The CLI binary is `stage-cli`. There is no monorepo or workspace scope. +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. ## Testing diff --git a/TESTING.md b/TESTING.md index cc94324..da4c54d 100644 --- a/TESTING.md +++ b/TESTING.md @@ -22,20 +22,20 @@ Test API route handlers through the real `startServer()` HTTP boundary, hitting **This is the highest-ROI test layer.** Most logic worth testing is request handling, schema validation, and database state transitions. Examples: -- `src/__tests__/runs.routes.test.ts` — exercises run/chapter routes against a real server + SQLite -- `src/__tests__/view-state.routes.test.ts` — exercises view-state routes end-to-end -- `src/__tests__/server.test.ts` — covers the static-file fallback, route compilation, and path-traversal guard +- `packages/cli/src/__tests__/runs.routes.test.ts` — exercises run/chapter routes against a real server + SQLite +- `packages/cli/src/__tests__/view-state.routes.test.ts` — exercises view-state routes end-to-end +- `packages/cli/src/__tests__/server.test.ts` — covers the static-file fallback, route compilation, and path-traversal guard -Use the helpers in `src/__tests__/fixtures.ts` to spin up a temp DB and the server. +Use the helpers in `packages/cli/src/__tests__/fixtures.ts` to spin up a temp DB and the server. ### 3. Pure Logic Unit Tests Schemas, parsers, and pure helpers. No mocks needed — these are pure functions. Examples: -- `src/__tests__/schema.test.ts` — Zod chapter-import schemas -- `src/__tests__/path.test.ts` — DB path resolution -- `src/__tests__/import-chapters.test.ts` — chapter import transformation +- `packages/cli/src/__tests__/schema.test.ts` — Zod chapter-import schemas +- `packages/cli/src/__tests__/path.test.ts` — DB path resolution +- `packages/cli/src/__tests__/import-chapters.test.ts` — chapter import transformation ### 4. Web UI Component Tests @@ -44,9 +44,9 @@ Narrow exception: tests for keyboard navigation, focus management, and form beha **Constraints:** - Must mock zero or one external boundary (the CLI's `/api/*` fetch calls) - Must mock at most one internal module -- If a test needs more, lift the logic out of the component into `web/src/lib/` and test it there +- If a test needs more, lift the logic out of the component into `packages/web/src/lib/` and test it there -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. +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. ## What to Test @@ -92,7 +92,7 @@ If a test needs 2+ internal mocks, the test is testing the mock setup, not the a 3. **Never mock more than one external-service boundary** per test file. 4. **Never mock more than one internal module** per test file. 5. **Never test that a component "renders without crashing"** — TypeScript already guarantees this. -6. **Factory functions over inline object literals.** Use `make*` or `create*` helpers in `src/__tests__/fixtures.ts` (or alongside the test file) with overrides. +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. 7. **One clear behavior per test.** Name by behavior, not method name. 8. **Arrange-Act-Assert.** One clear action per test. 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 | Path-resolution / static-file change | Route/server integration | Required | | Visual-only UI change | None | N/A | | New parser / transformer | Pure unit | Required | -| New React component (logic-heavy) | Extract logic to `web/src/lib/`, test there | Optional | +| New React component (logic-heavy) | Extract logic to `packages/web/src/lib/`, test there | Optional | | New React component (display-only) | None | N/A | | New schema migration | Route integration that exercises new columns | Required | diff --git a/biome.json b/biome.json index e72d647..7a085d1 100644 --- a/biome.json +++ b/biome.json @@ -17,7 +17,7 @@ "!**/node_modules", "!**/dist", "!**/web-dist", - "!drizzle" + "!**/drizzle" ] }, "linter": { diff --git a/package.json b/package.json index beda6f3..a1374be 100644 --- a/package.json +++ b/package.json @@ -1,18 +1,8 @@ { "name": "stagereview", "version": "0.1.0", - "description": "Chapter-style code review against your local git branch.", - "keywords": [ - "code-review", - "cli", - "chapters", - "git", - "claude-code", - "cursor", - "codex", - "gemini", - "opencode" - ], + "private": true, + "description": "pnpm workspace root for stagereview (packages/cli) and its supporting packages.", "homepage": "https://github.com/ReviewStage/stage-cli#readme", "bugs": { "url": "https://github.com/ReviewStage/stage-cli/issues" @@ -25,29 +15,18 @@ "author": "Stage", "type": "module", "packageManager": "pnpm@10.24.0", - "main": "./dist/index.js", - "bin": { - "stage-cli": "./dist/index.js" - }, - "files": [ - "dist", - "drizzle", - "web-dist", - "skills" - ], "engines": { "node": ">=20" }, "scripts": { - "build": "tsdown", - "build:web": "vite build --config web/vite.config.ts", - "dev:web": "vite --config web/vite.config.ts", + "build": "pnpm --filter @stage-cli/web build && pnpm --filter stagereview build", + "dev:web": "pnpm --filter @stage-cli/web dev", "test": "vitest run", "lint": "biome check .", "lint:fix": "biome check --write .", "format": "biome format --write .", - "typecheck": "tsc --noEmit && tsc --noEmit -p web/tsconfig.json", - "db:generate": "drizzle-kit generate", + "typecheck": "pnpm -r typecheck", + "db:generate": "pnpm --filter stagereview db:generate", "prepare": "husky" }, "lint-staged": { @@ -57,50 +36,12 @@ }, "devDependencies": { "@biomejs/biome": "^2.3.10", - "@pierre/diffs": "^1.0.11", - "@radix-ui/react-checkbox": "^1.3.3", - "@radix-ui/react-collapsible": "^1.1.12", - "@radix-ui/react-progress": "^1.1.8", - "@radix-ui/react-separator": "^1.1.8", - "@radix-ui/react-slot": "^1.2.4", - "@radix-ui/react-tooltip": "^1.2.8", - "@tailwindcss/vite": "^4.1.18", - "@tanstack/react-query": "^5.100.7", "@testing-library/react": "^16.3.2", - "@types/better-sqlite3": "^7.6.13", "@types/node": "^25.6.0", - "@types/react": "^19.2.5", - "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^5.1.2", - "class-variance-authority": "^0.7.1", - "clsx": "^2.1.1", - "drizzle-kit": "^0.31.10", "happy-dom": "^20.9.0", "husky": "^9.1.7", "lint-staged": "^16.2.7", - "lucide-react": "^0.562.0", - "react": "^19.2.3", - "react-dom": "^19.2.3", - "tailwind-merge": "^3.3.1", - "tailwindcss": "^4.1.18", - "tsdown": "^0.21.10", - "tw-animate-css": "^1.4.0", "typescript": "^5.6.3", - "vite": "^7.3.1", - "vite-tsconfig-paths": "^6.1.1", "vitest": "^4.1.5" - }, - "dependencies": { - "better-sqlite3": "^12.9.0", - "commander": "^14.0.3", - "drizzle-orm": "^0.45.2", - "open": "^11.0.0", - "zod": "^4.3.6" - }, - "pnpm": { - "onlyBuiltDependencies": [ - "better-sqlite3", - "esbuild" - ] } } diff --git a/drizzle.config.ts b/packages/cli/drizzle.config.ts similarity index 100% rename from drizzle.config.ts rename to packages/cli/drizzle.config.ts diff --git a/drizzle/0000_initial.sql b/packages/cli/drizzle/0000_initial.sql similarity index 100% rename from drizzle/0000_initial.sql rename to packages/cli/drizzle/0000_initial.sql diff --git a/drizzle/0001_view_state.sql b/packages/cli/drizzle/0001_view_state.sql similarity index 100% rename from drizzle/0001_view_state.sql rename to packages/cli/drizzle/0001_view_state.sql diff --git a/drizzle/meta/0000_snapshot.json b/packages/cli/drizzle/meta/0000_snapshot.json similarity index 100% rename from drizzle/meta/0000_snapshot.json rename to packages/cli/drizzle/meta/0000_snapshot.json diff --git a/drizzle/meta/0001_snapshot.json b/packages/cli/drizzle/meta/0001_snapshot.json similarity index 100% rename from drizzle/meta/0001_snapshot.json rename to packages/cli/drizzle/meta/0001_snapshot.json diff --git a/drizzle/meta/_journal.json b/packages/cli/drizzle/meta/_journal.json similarity index 100% rename from drizzle/meta/_journal.json rename to packages/cli/drizzle/meta/_journal.json diff --git a/packages/cli/package.json b/packages/cli/package.json new file mode 100644 index 0000000..5fb2110 --- /dev/null +++ b/packages/cli/package.json @@ -0,0 +1,64 @@ +{ + "name": "stagereview", + "version": "0.1.0", + "description": "Chapter-style code review against your local git branch.", + "keywords": [ + "code-review", + "cli", + "chapters", + "git", + "claude-code", + "cursor", + "codex", + "gemini", + "opencode" + ], + "homepage": "https://github.com/ReviewStage/stage-cli#readme", + "bugs": { + "url": "https://github.com/ReviewStage/stage-cli/issues" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/ReviewStage/stage-cli.git", + "directory": "packages/cli" + }, + "license": "MIT", + "author": "Stage", + "type": "module", + "main": "./dist/index.js", + "bin": { + "stage-cli": "./dist/index.js" + }, + "files": [ + "dist", + "drizzle", + "web-dist", + "skills" + ], + "engines": { + "node": ">=20" + }, + "scripts": { + "build": "tsdown", + "test": "vitest run", + "typecheck": "tsc --noEmit", + "db:generate": "drizzle-kit generate", + "prepack": "node -e \"require('node:fs').copyFileSync('../../README.md','./README.md')\"" + }, + "dependencies": { + "better-sqlite3": "^12.9.0", + "commander": "^14.0.3", + "drizzle-orm": "^0.45.2", + "open": "^11.0.0", + "zod": "^4.3.6" + }, + "devDependencies": { + "@stage-cli/types": "workspace:*", + "@types/better-sqlite3": "^7.6.13", + "@types/node": "^25.6.0", + "drizzle-kit": "^0.31.10", + "tsdown": "^0.21.10", + "typescript": "^5.6.3", + "vitest": "^4.1.5" + } +} diff --git a/src/__tests__/fixtures.ts b/packages/cli/src/__tests__/fixtures.ts similarity index 100% rename from src/__tests__/fixtures.ts rename to packages/cli/src/__tests__/fixtures.ts diff --git a/src/__tests__/import-chapters.test.ts b/packages/cli/src/__tests__/import-chapters.test.ts similarity index 100% rename from src/__tests__/import-chapters.test.ts rename to packages/cli/src/__tests__/import-chapters.test.ts diff --git a/src/__tests__/path.test.ts b/packages/cli/src/__tests__/path.test.ts similarity index 100% rename from src/__tests__/path.test.ts rename to packages/cli/src/__tests__/path.test.ts diff --git a/src/__tests__/runs.routes.test.ts b/packages/cli/src/__tests__/runs.routes.test.ts similarity index 100% rename from src/__tests__/runs.routes.test.ts rename to packages/cli/src/__tests__/runs.routes.test.ts diff --git a/src/__tests__/schema.test.ts b/packages/cli/src/__tests__/schema.test.ts similarity index 100% rename from src/__tests__/schema.test.ts rename to packages/cli/src/__tests__/schema.test.ts diff --git a/src/__tests__/server.test.ts b/packages/cli/src/__tests__/server.test.ts similarity index 100% rename from src/__tests__/server.test.ts rename to packages/cli/src/__tests__/server.test.ts diff --git a/src/__tests__/view-state.routes.test.ts b/packages/cli/src/__tests__/view-state.routes.test.ts similarity index 100% rename from src/__tests__/view-state.routes.test.ts rename to packages/cli/src/__tests__/view-state.routes.test.ts diff --git a/src/db/client.ts b/packages/cli/src/db/client.ts similarity index 100% rename from src/db/client.ts rename to packages/cli/src/db/client.ts diff --git a/src/db/local-user.ts b/packages/cli/src/db/local-user.ts similarity index 100% rename from src/db/local-user.ts rename to packages/cli/src/db/local-user.ts diff --git a/src/db/path.ts b/packages/cli/src/db/path.ts similarity index 100% rename from src/db/path.ts rename to packages/cli/src/db/path.ts diff --git a/src/db/schema/chapter-run.ts b/packages/cli/src/db/schema/chapter-run.ts similarity index 100% rename from src/db/schema/chapter-run.ts rename to packages/cli/src/db/schema/chapter-run.ts diff --git a/src/db/schema/chapter-view.ts b/packages/cli/src/db/schema/chapter-view.ts similarity index 100% rename from src/db/schema/chapter-view.ts rename to packages/cli/src/db/schema/chapter-view.ts diff --git a/src/db/schema/chapter.ts b/packages/cli/src/db/schema/chapter.ts similarity index 100% rename from src/db/schema/chapter.ts rename to packages/cli/src/db/schema/chapter.ts diff --git a/src/db/schema/columns.ts b/packages/cli/src/db/schema/columns.ts similarity index 100% rename from src/db/schema/columns.ts rename to packages/cli/src/db/schema/columns.ts diff --git a/src/db/schema/index.ts b/packages/cli/src/db/schema/index.ts similarity index 100% rename from src/db/schema/index.ts rename to packages/cli/src/db/schema/index.ts diff --git a/src/db/schema/key-change-view.ts b/packages/cli/src/db/schema/key-change-view.ts similarity index 100% rename from src/db/schema/key-change-view.ts rename to packages/cli/src/db/schema/key-change-view.ts diff --git a/src/db/schema/key-change.ts b/packages/cli/src/db/schema/key-change.ts similarity index 100% rename from src/db/schema/key-change.ts rename to packages/cli/src/db/schema/key-change.ts diff --git a/src/index.ts b/packages/cli/src/index.ts similarity index 100% rename from src/index.ts rename to packages/cli/src/index.ts diff --git a/src/routes/json.ts b/packages/cli/src/routes/json.ts similarity index 100% rename from src/routes/json.ts rename to packages/cli/src/routes/json.ts diff --git a/src/routes/runs.ts b/packages/cli/src/routes/runs.ts similarity index 96% rename from src/routes/runs.ts rename to packages/cli/src/routes/runs.ts index f48bff1..e66bcb9 100644 --- a/src/routes/runs.ts +++ b/packages/cli/src/routes/runs.ts @@ -1,8 +1,8 @@ +import type { Chapter, ChapterRun, KeyChange } from "@stage-cli/types/chapters"; import { asc, eq, inArray } from "drizzle-orm"; import type { StageDb } from "../db/client.js"; import { chapter, chapterRun, keyChange } from "../db/schema/index.js"; import type { Route } from "../server.js"; -import type { Chapter, ChapterRun, KeyChange } from "../types/chapters.js"; import { writeJson } from "./json.js"; type ChapterRow = typeof chapter.$inferSelect; diff --git a/src/routes/view-state.ts b/packages/cli/src/routes/view-state.ts similarity index 100% rename from src/routes/view-state.ts rename to packages/cli/src/routes/view-state.ts diff --git a/src/runs/import-chapters.ts b/packages/cli/src/runs/import-chapters.ts similarity index 100% rename from src/runs/import-chapters.ts rename to packages/cli/src/runs/import-chapters.ts diff --git a/src/schema.ts b/packages/cli/src/schema.ts similarity index 72% rename from src/schema.ts rename to packages/cli/src/schema.ts index 7d2e95e..0de1b0f 100644 --- a/src/schema.ts +++ b/packages/cli/src/schema.ts @@ -1,10 +1,8 @@ +import { hunkReferenceSchema, lineRefSchema } from "@stage-cli/types/chapters"; import { z } from "zod"; -export const DIFF_SIDE = { - ADDITIONS: "additions", - DELETIONS: "deletions", -} as const; -export type DiffSide = (typeof DIFF_SIDE)[keyof typeof DIFF_SIDE]; +export type { DiffSide, HunkReference, LineRef } from "@stage-cli/types/chapters"; +export { DIFF_SIDE, hunkReferenceSchema, lineRefSchema } from "@stage-cli/types/chapters"; export const SCOPE_KIND = { COMMITTED: "committed", @@ -21,25 +19,6 @@ export type WorkingTreeRef = (typeof WORKING_TREE_REF)[keyof typeof WORKING_TREE const fullShaSchema = z.string().regex(/^[0-9a-f]{40}$/, "Expected a full commit SHA"); -export const hunkReferenceSchema = z.strictObject({ - filePath: z.string().min(1), - oldStart: z.number().int().nonnegative(), -}); -export type HunkReference = z.infer; - -export const lineRefSchema = z - .strictObject({ - filePath: z.string().min(1), - side: z.enum(DIFF_SIDE), - startLine: z.number().int().positive(), - endLine: z.number().int().positive(), - }) - .refine((v) => v.startLine <= v.endLine, { - message: "endLine must be greater than or equal to startLine", - path: ["endLine"], - }); -export type LineRef = z.infer; - export const keyChangeSchema = z.strictObject({ /** A judgment-call question for a human reviewer, not source code. */ content: z.string().min(1), diff --git a/src/server.ts b/packages/cli/src/server.ts similarity index 100% rename from src/server.ts rename to packages/cli/src/server.ts diff --git a/src/show.ts b/packages/cli/src/show.ts similarity index 100% rename from src/show.ts rename to packages/cli/src/show.ts diff --git a/tsconfig.json b/packages/cli/tsconfig.json similarity index 74% rename from tsconfig.json rename to packages/cli/tsconfig.json index 2aa344c..6124e77 100644 --- a/tsconfig.json +++ b/packages/cli/tsconfig.json @@ -1,8 +1,8 @@ { "compilerOptions": { "target": "ES2022", - "module": "NodeNext", - "moduleResolution": "NodeNext", + "module": "ESNext", + "moduleResolution": "bundler", "lib": ["ES2022"], "outDir": "./dist", "rootDir": "./src", @@ -14,11 +14,10 @@ "forceConsistentCasingInFileNames": true, "skipLibCheck": true, "resolveJsonModule": true, - "declaration": true, - "declarationMap": true, - "sourceMap": true, "isolatedModules": true, - "verbatimModuleSyntax": true + "verbatimModuleSyntax": true, + "noEmit": true, + "allowImportingTsExtensions": true }, "include": ["src/**/*"], "exclude": ["node_modules", "dist", "web-dist"] diff --git a/tsdown.config.ts b/packages/cli/tsdown.config.ts similarity index 58% rename from tsdown.config.ts rename to packages/cli/tsdown.config.ts index 7405568..851dcd2 100644 --- a/tsdown.config.ts +++ b/packages/cli/tsdown.config.ts @@ -10,4 +10,7 @@ export default defineConfig({ dts: false, shims: true, outExtensions: () => ({ js: ".js" }), + // Inline workspace deps — they won't exist in the published package's + // node_modules, so the bundle has to carry their source. + deps: { alwaysBundle: [/^@stage-cli\//] }, }); diff --git a/packages/types/package.json b/packages/types/package.json new file mode 100644 index 0000000..89d0fe1 --- /dev/null +++ b/packages/types/package.json @@ -0,0 +1,24 @@ +{ + "name": "@stage-cli/types", + "version": "0.1.0", + "private": true, + "description": "Shared wire-format types for the stage-cli server and SPA.", + "type": "module", + "exports": { + ".": "./src/index.ts", + "./chapters": "./src/chapters.ts", + "./view-state": "./src/view-state.ts" + }, + "files": [ + "src" + ], + "scripts": { + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "zod": "^4.3.6" + }, + "devDependencies": { + "typescript": "^5.6.3" + } +} diff --git a/packages/types/src/chapters.ts b/packages/types/src/chapters.ts new file mode 100644 index 0000000..784e5db --- /dev/null +++ b/packages/types/src/chapters.ts @@ -0,0 +1,58 @@ +import { z } from "zod"; + +export const DIFF_SIDE = { + ADDITIONS: "additions", + DELETIONS: "deletions", +} as const; +export type DiffSide = (typeof DIFF_SIDE)[keyof typeof DIFF_SIDE]; + +export const hunkReferenceSchema = z.strictObject({ + filePath: z.string().min(1), + oldStart: z.number().int().nonnegative(), +}); +export type HunkReference = z.infer; + +export const lineRefSchema = z + .strictObject({ + filePath: z.string().min(1), + side: z.enum(DIFF_SIDE), + startLine: z.number().int().positive(), + endLine: z.number().int().positive(), + }) + .refine((v) => v.startLine <= v.endLine, { + message: "endLine must be greater than or equal to startLine", + path: ["endLine"], + }); +export type LineRef = z.infer; + +// Non-strict (vs. ingestion's z.strictObject in packages/cli/src/schema.ts) so the server +// can add fields the SPA doesn't yet read without rejecting the whole response. +export const KeyChangeSchema = z.object({ + id: z.string(), + externalId: z.string(), + content: z.string(), + lineRefs: z.array(lineRefSchema), +}); +export type KeyChange = z.infer; + +export const ChapterSchema = z.object({ + id: z.string(), + externalId: z.string(), + order: z.number().int(), + title: z.string(), + summary: z.string(), + hunkRefs: z.array(hunkReferenceSchema), + keyChanges: z.array(KeyChangeSchema), +}); +export type Chapter = z.infer; + +export const ChapterRunSchema = z.object({ + id: z.string(), +}); +export type ChapterRun = z.infer; + +export const ChaptersResponseSchema = z.object({ + run: ChapterRunSchema, + chapters: z.array(ChapterSchema), +}); +export type ChaptersResponse = z.infer; diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts new file mode 100644 index 0000000..6bcb6ca --- /dev/null +++ b/packages/types/src/index.ts @@ -0,0 +1,2 @@ +export * from "./chapters.ts"; +export * from "./view-state.ts"; diff --git a/src/types/view-state.ts b/packages/types/src/view-state.ts similarity index 100% rename from src/types/view-state.ts rename to packages/types/src/view-state.ts diff --git a/packages/types/tsconfig.json b/packages/types/tsconfig.json new file mode 100644 index 0000000..dc1819e --- /dev/null +++ b/packages/types/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "lib": ["ES2022"], + "strict": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + "noFallthroughCasesInSwitch": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "isolatedModules": true, + "verbatimModuleSyntax": true, + "noEmit": true, + "allowImportingTsExtensions": true + }, + "include": ["src/**/*"] +} diff --git a/components.json b/packages/web/components.json similarity index 90% rename from components.json rename to packages/web/components.json index f08d5f8..c12f503 100644 --- a/components.json +++ b/packages/web/components.json @@ -5,7 +5,7 @@ "tsx": true, "tailwind": { "config": "", - "css": "web/src/styles/globals.css", + "css": "src/styles/globals.css", "baseColor": "zinc", "cssVariables": true, "prefix": "" diff --git a/web/index.html b/packages/web/index.html similarity index 100% rename from web/index.html rename to packages/web/index.html diff --git a/packages/web/package.json b/packages/web/package.json new file mode 100644 index 0000000..8827907 --- /dev/null +++ b/packages/web/package.json @@ -0,0 +1,44 @@ +{ + "name": "@stage-cli/web", + "version": "0.1.0", + "private": true, + "description": "stage-cli SPA bundled into the published CLI's web-dist/.", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "test": "vitest run", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@stage-cli/types": "workspace:*", + "@radix-ui/react-checkbox": "^1.3.3", + "@radix-ui/react-collapsible": "^1.1.12", + "@radix-ui/react-progress": "^1.1.8", + "@radix-ui/react-separator": "^1.1.8", + "@radix-ui/react-slot": "^1.2.4", + "@radix-ui/react-tooltip": "^1.2.8", + "@tanstack/react-query": "^5.100.7", + "@pierre/diffs": "^1.0.11", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "lucide-react": "^0.562.0", + "react": "^19.2.3", + "react-dom": "^19.2.3", + "tailwind-merge": "^3.3.1", + "tailwindcss": "^4.1.18", + "tw-animate-css": "^1.4.0", + "zod": "^4.3.6" + }, + "devDependencies": { + "@tailwindcss/vite": "^4.1.18", + "@testing-library/react": "^16.3.2", + "@types/react": "^19.2.5", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^5.1.2", + "happy-dom": "^20.9.0", + "typescript": "^5.6.3", + "vite": "^7.3.1", + "vitest": "^4.1.5" + } +} diff --git a/web/src/App.tsx b/packages/web/src/App.tsx similarity index 100% rename from web/src/App.tsx rename to packages/web/src/App.tsx diff --git a/web/src/components/chapter/file-header.tsx b/packages/web/src/components/chapter/file-header.tsx similarity index 100% rename from web/src/components/chapter/file-header.tsx rename to packages/web/src/components/chapter/file-header.tsx diff --git a/web/src/components/chapter/file-view-row.tsx b/packages/web/src/components/chapter/file-view-row.tsx similarity index 100% rename from web/src/components/chapter/file-view-row.tsx rename to packages/web/src/components/chapter/file-view-row.tsx diff --git a/web/src/components/chapter/hunk-highlight-overlay.tsx b/packages/web/src/components/chapter/hunk-highlight-overlay.tsx similarity index 100% rename from web/src/components/chapter/hunk-highlight-overlay.tsx rename to packages/web/src/components/chapter/hunk-highlight-overlay.tsx diff --git a/web/src/components/chapter/index.ts b/packages/web/src/components/chapter/index.ts similarity index 100% rename from web/src/components/chapter/index.ts rename to packages/web/src/components/chapter/index.ts diff --git a/web/src/components/chapter/pierre-diff-viewer.tsx b/packages/web/src/components/chapter/pierre-diff-viewer.tsx similarity index 100% rename from web/src/components/chapter/pierre-diff-viewer.tsx rename to packages/web/src/components/chapter/pierre-diff-viewer.tsx diff --git a/web/src/components/chapter/rendered-line-target.ts b/packages/web/src/components/chapter/rendered-line-target.ts similarity index 100% rename from web/src/components/chapter/rendered-line-target.ts rename to packages/web/src/components/chapter/rendered-line-target.ts diff --git a/web/src/components/chapter/text-selection-popup.tsx b/packages/web/src/components/chapter/text-selection-popup.tsx similarity index 100% rename from web/src/components/chapter/text-selection-popup.tsx rename to packages/web/src/components/chapter/text-selection-popup.tsx diff --git a/web/src/components/pull-request/section-label.tsx b/packages/web/src/components/pull-request/section-label.tsx similarity index 100% rename from web/src/components/pull-request/section-label.tsx rename to packages/web/src/components/pull-request/section-label.tsx diff --git a/web/src/components/ui/button.tsx b/packages/web/src/components/ui/button.tsx similarity index 100% rename from web/src/components/ui/button.tsx rename to packages/web/src/components/ui/button.tsx diff --git a/web/src/components/ui/checkbox.tsx b/packages/web/src/components/ui/checkbox.tsx similarity index 100% rename from web/src/components/ui/checkbox.tsx rename to packages/web/src/components/ui/checkbox.tsx diff --git a/web/src/components/ui/collapsible.tsx b/packages/web/src/components/ui/collapsible.tsx similarity index 100% rename from web/src/components/ui/collapsible.tsx rename to packages/web/src/components/ui/collapsible.tsx diff --git a/web/src/components/ui/progress.tsx b/packages/web/src/components/ui/progress.tsx similarity index 100% rename from web/src/components/ui/progress.tsx rename to packages/web/src/components/ui/progress.tsx diff --git a/web/src/components/ui/separator.tsx b/packages/web/src/components/ui/separator.tsx similarity index 100% rename from web/src/components/ui/separator.tsx rename to packages/web/src/components/ui/separator.tsx diff --git a/web/src/components/ui/skeleton.tsx b/packages/web/src/components/ui/skeleton.tsx similarity index 100% rename from web/src/components/ui/skeleton.tsx rename to packages/web/src/components/ui/skeleton.tsx diff --git a/web/src/components/ui/tooltip.tsx b/packages/web/src/components/ui/tooltip.tsx similarity index 100% rename from web/src/components/ui/tooltip.tsx rename to packages/web/src/components/ui/tooltip.tsx diff --git a/web/src/lib/__tests__/fixtures.tsx b/packages/web/src/lib/__tests__/fixtures.tsx similarity index 98% rename from web/src/lib/__tests__/fixtures.tsx rename to packages/web/src/lib/__tests__/fixtures.tsx index f39f468..d8e2a8d 100644 --- a/web/src/lib/__tests__/fixtures.tsx +++ b/packages/web/src/lib/__tests__/fixtures.tsx @@ -2,7 +2,7 @@ // __tests__/ as a sibling of the test files so the cross-file scope rule // (per-file mock budget) stays obvious. -import type { ViewState } from "@cli/types/view-state"; +import type { ViewState } from "@stage-cli/types/view-state"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import type { ReactElement, ReactNode } from "react"; import { vi } from "vitest"; diff --git a/web/src/lib/__tests__/use-hash-run-id.test.tsx b/packages/web/src/lib/__tests__/use-hash-run-id.test.tsx similarity index 100% rename from web/src/lib/__tests__/use-hash-run-id.test.tsx rename to packages/web/src/lib/__tests__/use-hash-run-id.test.tsx diff --git a/web/src/lib/__tests__/use-view-state-reads.test.tsx b/packages/web/src/lib/__tests__/use-view-state-reads.test.tsx similarity index 97% rename from web/src/lib/__tests__/use-view-state-reads.test.tsx rename to packages/web/src/lib/__tests__/use-view-state-reads.test.tsx index 9e17dc1..30fd83f 100644 --- a/web/src/lib/__tests__/use-view-state-reads.test.tsx +++ b/packages/web/src/lib/__tests__/use-view-state-reads.test.tsx @@ -1,6 +1,6 @@ // @vitest-environment happy-dom -import type { ViewState } from "@cli/types/view-state"; +import type { ViewState } from "@stage-cli/types/view-state"; import { act, renderHook, waitFor } from "@testing-library/react"; import { afterEach, describe, expect, it, vi } from "vitest"; import { useViewState, viewStateQueryKey } from "../use-view-state"; diff --git a/web/src/lib/__tests__/use-view-state-writes.test.tsx b/packages/web/src/lib/__tests__/use-view-state-writes.test.tsx similarity index 100% rename from web/src/lib/__tests__/use-view-state-writes.test.tsx rename to packages/web/src/lib/__tests__/use-view-state-writes.test.tsx diff --git a/web/src/lib/diff-types.ts b/packages/web/src/lib/diff-types.ts similarity index 100% rename from web/src/lib/diff-types.ts rename to packages/web/src/lib/diff-types.ts diff --git a/web/src/lib/file-status.ts b/packages/web/src/lib/file-status.ts similarity index 100% rename from web/src/lib/file-status.ts rename to packages/web/src/lib/file-status.ts diff --git a/web/src/lib/syntax-themes.ts b/packages/web/src/lib/syntax-themes.ts similarity index 100% rename from web/src/lib/syntax-themes.ts rename to packages/web/src/lib/syntax-themes.ts diff --git a/web/src/lib/use-diff-settings.tsx b/packages/web/src/lib/use-diff-settings.tsx similarity index 100% rename from web/src/lib/use-diff-settings.tsx rename to packages/web/src/lib/use-diff-settings.tsx diff --git a/web/src/lib/use-hash-run-id.ts b/packages/web/src/lib/use-hash-run-id.ts similarity index 100% rename from web/src/lib/use-hash-run-id.ts rename to packages/web/src/lib/use-hash-run-id.ts diff --git a/web/src/lib/use-is-mac.ts b/packages/web/src/lib/use-is-mac.ts similarity index 100% rename from web/src/lib/use-is-mac.ts rename to packages/web/src/lib/use-is-mac.ts diff --git a/web/src/lib/use-local-storage.ts b/packages/web/src/lib/use-local-storage.ts similarity index 100% rename from web/src/lib/use-local-storage.ts rename to packages/web/src/lib/use-local-storage.ts diff --git a/web/src/lib/use-view-state.ts b/packages/web/src/lib/use-view-state.ts similarity index 98% rename from web/src/lib/use-view-state.ts rename to packages/web/src/lib/use-view-state.ts index dd318cc..40cf2fe 100644 --- a/web/src/lib/use-view-state.ts +++ b/packages/web/src/lib/use-view-state.ts @@ -1,4 +1,4 @@ -import { type ViewState, ViewStateSchema } from "@cli/types/view-state"; +import { type ViewState, ViewStateSchema } from "@stage-cli/types/view-state"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useMemo } from "react"; diff --git a/web/src/lib/utils.ts b/packages/web/src/lib/utils.ts similarity index 100% rename from web/src/lib/utils.ts rename to packages/web/src/lib/utils.ts diff --git a/web/src/main.tsx b/packages/web/src/main.tsx similarity index 100% rename from web/src/main.tsx rename to packages/web/src/main.tsx diff --git a/web/src/routes/chapters-index-page.tsx b/packages/web/src/routes/chapters-index-page.tsx similarity index 98% rename from web/src/routes/chapters-index-page.tsx rename to packages/web/src/routes/chapters-index-page.tsx index 3b5bdea..c92badd 100644 --- a/web/src/routes/chapters-index-page.tsx +++ b/packages/web/src/routes/chapters-index-page.tsx @@ -1,4 +1,4 @@ -import type { Chapter, HunkRef } from "@cli/types/chapters"; +import type { Chapter, HunkReference } from "@stage-cli/types/chapters"; import { ChevronRight, Circle, CircleCheck } from "lucide-react"; import { useCallback, useEffect, useMemo, useState } from "react"; import { FileViewRow } from "@/components/chapter"; @@ -141,7 +141,7 @@ function ChapterEntry({ ); } -function distinctFilePaths(hunkRefs: HunkRef[]): string[] { +function distinctFilePaths(hunkRefs: HunkReference[]): string[] { const seen = new Set(); const out: string[] = []; for (const h of hunkRefs) { diff --git a/web/src/routes/pull-request-layout.tsx b/packages/web/src/routes/pull-request-layout.tsx similarity index 99% rename from web/src/routes/pull-request-layout.tsx rename to packages/web/src/routes/pull-request-layout.tsx index 71bdadc..0992c42 100644 --- a/web/src/routes/pull-request-layout.tsx +++ b/packages/web/src/routes/pull-request-layout.tsx @@ -1,4 +1,4 @@ -import { type ChaptersResponse, ChaptersResponseSchema } from "@cli/types/chapters"; +import { type ChaptersResponse, ChaptersResponseSchema } from "@stage-cli/types/chapters"; import { useQuery } from "@tanstack/react-query"; import { BookOpen, FileText } from "lucide-react"; import { useMemo, useState } from "react"; diff --git a/web/src/styles/globals.css b/packages/web/src/styles/globals.css similarity index 100% rename from web/src/styles/globals.css rename to packages/web/src/styles/globals.css diff --git a/web/tsconfig.json b/packages/web/tsconfig.json similarity index 81% rename from web/tsconfig.json rename to packages/web/tsconfig.json index 477fe76..3e8b231 100644 --- a/web/tsconfig.json +++ b/packages/web/tsconfig.json @@ -20,9 +20,8 @@ "useDefineForClassFields": true, "allowImportingTsExtensions": true, "paths": { - "@/*": ["./src/*"], - "@cli/types/*": ["../src/types/*"] + "@/*": ["./src/*"] } }, - "include": ["src/**/*.ts", "src/**/*.tsx", "vite.config.ts", "../src/types/**/*.ts"] + "include": ["src/**/*.ts", "src/**/*.tsx", "vite.config.ts"] } diff --git a/web/vite.config.ts b/packages/web/vite.config.ts similarity index 55% rename from web/vite.config.ts rename to packages/web/vite.config.ts index 35aae1e..885a99a 100644 --- a/web/vite.config.ts +++ b/packages/web/vite.config.ts @@ -3,17 +3,19 @@ import { fileURLToPath } from "node:url"; import tailwindcss from "@tailwindcss/vite"; import react from "@vitejs/plugin-react"; import { defineConfig } from "vite"; -import tsconfigPaths from "vite-tsconfig-paths"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); export default defineConfig({ root: path.resolve(__dirname), - // `tsconfigPaths` reads the `paths` block in web/tsconfig.json so vite, - // vitest, and tsc all resolve aliases from one source of truth. - plugins: [tsconfigPaths(), react(), tailwindcss()], + resolve: { + alias: { + "@": path.resolve(__dirname, "src"), + }, + }, + plugins: [react(), tailwindcss()], build: { - outDir: path.resolve(__dirname, "../web-dist"), + outDir: path.resolve(__dirname, "../cli/web-dist"), emptyOutDir: true, }, }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 60bc00a..96c3089 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,6 +7,33 @@ settings: importers: .: + devDependencies: + '@biomejs/biome': + specifier: ^2.3.10 + version: 2.4.14 + '@testing-library/react': + specifier: ^16.3.2 + version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@types/node': + specifier: ^25.6.0 + version: 25.6.0 + happy-dom: + specifier: ^20.9.0 + version: 20.9.0 + husky: + specifier: ^9.1.7 + version: 9.1.7 + lint-staged: + specifier: ^16.2.7 + version: 16.4.0 + typescript: + specifier: ^5.6.3 + version: 5.9.3 + vitest: + specifier: ^4.1.5 + version: 4.1.5(@types/node@25.6.0)(happy-dom@20.9.0)(vite@7.3.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)(yaml@2.8.4)) + + packages/cli: dependencies: better-sqlite3: specifier: ^12.9.0 @@ -24,9 +51,40 @@ importers: specifier: ^4.3.6 version: 4.4.2 devDependencies: - '@biomejs/biome': - specifier: ^2.3.10 - version: 2.4.14 + '@stage-cli/types': + specifier: workspace:* + version: link:../types + '@types/better-sqlite3': + specifier: ^7.6.13 + version: 7.6.13 + '@types/node': + specifier: ^25.6.0 + version: 25.6.0 + drizzle-kit: + specifier: ^0.31.10 + version: 0.31.10 + tsdown: + specifier: ^0.21.10 + version: 0.21.10(typescript@5.9.3) + typescript: + specifier: ^5.6.3 + version: 5.9.3 + vitest: + specifier: ^4.1.5 + version: 4.1.5(@types/node@25.6.0)(happy-dom@20.9.0)(vite@7.3.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)(yaml@2.8.4)) + + packages/types: + dependencies: + zod: + specifier: ^4.3.6 + version: 4.4.2 + devDependencies: + typescript: + specifier: ^5.6.3 + version: 5.9.3 + + packages/web: + dependencies: '@pierre/diffs': specifier: ^1.0.11 version: 1.1.20(react-dom@19.2.5(react@19.2.5))(react@19.2.5) @@ -48,48 +106,18 @@ importers: '@radix-ui/react-tooltip': specifier: ^1.2.8 version: 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@tailwindcss/vite': - specifier: ^4.1.18 - version: 4.2.4(vite@7.3.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)(yaml@2.8.4)) + '@stage-cli/types': + specifier: workspace:* + version: link:../types '@tanstack/react-query': specifier: ^5.100.7 version: 5.100.8(react@19.2.5) - '@testing-library/react': - specifier: ^16.3.2 - version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@types/better-sqlite3': - specifier: ^7.6.13 - version: 7.6.13 - '@types/node': - specifier: ^25.6.0 - version: 25.6.0 - '@types/react': - specifier: ^19.2.5 - version: 19.2.14 - '@types/react-dom': - specifier: ^19.2.3 - version: 19.2.3(@types/react@19.2.14) - '@vitejs/plugin-react': - specifier: ^5.1.2 - version: 5.2.0(vite@7.3.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)(yaml@2.8.4)) class-variance-authority: specifier: ^0.7.1 version: 0.7.1 clsx: specifier: ^2.1.1 version: 2.1.1 - drizzle-kit: - specifier: ^0.31.10 - version: 0.31.10 - happy-dom: - specifier: ^20.9.0 - version: 20.9.0 - husky: - specifier: ^9.1.7 - version: 9.1.7 - lint-staged: - specifier: ^16.2.7 - version: 16.4.0 lucide-react: specifier: ^0.562.0 version: 0.562.0(react@19.2.5) @@ -105,21 +133,37 @@ importers: tailwindcss: specifier: ^4.1.18 version: 4.2.4 - tsdown: - specifier: ^0.21.10 - version: 0.21.10(typescript@5.9.3) tw-animate-css: specifier: ^1.4.0 version: 1.4.0 + zod: + specifier: ^4.3.6 + version: 4.4.2 + devDependencies: + '@tailwindcss/vite': + specifier: ^4.1.18 + version: 4.2.4(vite@7.3.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)(yaml@2.8.4)) + '@testing-library/react': + specifier: ^16.3.2 + version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@types/react': + specifier: ^19.2.5 + version: 19.2.14 + '@types/react-dom': + specifier: ^19.2.3 + version: 19.2.3(@types/react@19.2.14) + '@vitejs/plugin-react': + specifier: ^5.1.2 + version: 5.2.0(vite@7.3.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)(yaml@2.8.4)) + happy-dom: + specifier: ^20.9.0 + version: 20.9.0 typescript: specifier: ^5.6.3 version: 5.9.3 vite: specifier: ^7.3.1 version: 7.3.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)(yaml@2.8.4) - vite-tsconfig-paths: - specifier: ^6.1.1 - version: 6.1.1(typescript@5.9.3)(vite@7.3.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)(yaml@2.8.4)) vitest: specifier: ^4.1.5 version: 4.1.5(@types/node@25.6.0)(happy-dom@20.9.0)(vite@7.3.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)(yaml@2.8.4)) @@ -1941,9 +1985,6 @@ packages: github-from-package@0.0.0: resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} - globrex@0.1.2: - resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} - graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} @@ -2428,16 +2469,6 @@ packages: trim-lines@3.0.1: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} - tsconfck@3.1.6: - resolution: {integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==} - engines: {node: ^18 || >=20} - hasBin: true - peerDependencies: - typescript: ^5.0.0 - peerDependenciesMeta: - typescript: - optional: true - tsdown@0.21.10: resolution: {integrity: sha512-3wk73yBhZe/wX7REqSdivNQ84TDs1mJ+IlnzrrEREP70xlJ/AEIzqaI04l/TzMKVIdkTdC3CPaADn2Lk/0SkdA==} engines: {node: '>=20.19.0'} @@ -2531,11 +2562,6 @@ packages: vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} - vite-tsconfig-paths@6.1.1: - resolution: {integrity: sha512-2cihq7zliibCCZ8P9cKJrQBkfgdvcFkOOc3Y02o3GWUDLgqjWsZudaoiuOwO/gzTzy17cS5F7ZPo4bsnS4DGkg==} - peerDependencies: - vite: '*' - vite@7.3.2: resolution: {integrity: sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -4082,8 +4108,6 @@ snapshots: github-from-package@0.0.0: {} - globrex@0.1.2: {} - graceful-fs@4.2.11: {} happy-dom@20.9.0: @@ -4600,10 +4624,6 @@ snapshots: trim-lines@3.0.1: {} - tsconfck@3.1.6(typescript@5.9.3): - optionalDependencies: - typescript: 5.9.3 - tsdown@0.21.10(typescript@5.9.3): dependencies: ansis: 4.2.0 @@ -4701,16 +4721,6 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vite-tsconfig-paths@6.1.1(typescript@5.9.3)(vite@7.3.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)(yaml@2.8.4)): - dependencies: - debug: 4.4.3 - globrex: 0.1.2 - tsconfck: 3.1.6(typescript@5.9.3) - vite: 7.3.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)(yaml@2.8.4) - transitivePeerDependencies: - - supports-color - - typescript - vite@7.3.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)(yaml@2.8.4): dependencies: esbuild: 0.27.7 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..dc61eae --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,6 @@ +packages: + - "packages/*" + +onlyBuiltDependencies: + - better-sqlite3 + - esbuild diff --git a/src/types/chapters.ts b/src/types/chapters.ts deleted file mode 100644 index 531196a..0000000 --- a/src/types/chapters.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { z } from "zod"; -import { hunkReferenceSchema, lineRefSchema } from "../schema.js"; - -export const HunkRefSchema = hunkReferenceSchema; -export type HunkRef = z.infer; - -export const LineRefSchema = lineRefSchema; -export type LineRef = z.infer; - -// Non-strict (vs. z.strictObject in src/schema.ts) so the server can add fields -// the SPA doesn't yet read without rejecting the whole response. -export const KeyChangeSchema = z.object({ - id: z.string(), - externalId: z.string(), - content: z.string(), - lineRefs: z.array(LineRefSchema), -}); -export type KeyChange = z.infer; - -export const ChapterSchema = z.object({ - id: z.string(), - externalId: z.string(), - order: z.number().int(), - title: z.string(), - summary: z.string(), - hunkRefs: z.array(HunkRefSchema), - keyChanges: z.array(KeyChangeSchema), -}); -export type Chapter = z.infer; - -export const ChapterRunSchema = z.object({ - id: z.string(), -}); -export type ChapterRun = z.infer; - -export const ChaptersResponseSchema = z.object({ - run: ChapterRunSchema, - chapters: z.array(ChapterSchema), -}); -export type ChaptersResponse = z.infer; diff --git a/src/types/index.ts b/src/types/index.ts deleted file mode 100644 index c998745..0000000 --- a/src/types/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./chapters.js"; -export * from "./view-state.js"; diff --git a/vitest.config.ts b/vitest.config.ts index 1ec7d29..90bdf21 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,13 +1,16 @@ import path from "node:path"; import { fileURLToPath } from "node:url"; -import tsconfigPaths from "vite-tsconfig-paths"; import { defineConfig } from "vitest/config"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); export default defineConfig({ - // SPA modules under test resolve `@/*` and `@cli/types/*` from web/tsconfig.json. - // CLI tests don't use either alias, so pointing the plugin at web/'s tsconfig - // covers the only consumer. - plugins: [tsconfigPaths({ projects: [path.resolve(__dirname, "web", "tsconfig.json")] })], + resolve: { + // `@/*` is the SPA-local alias declared in packages/web/tsconfig.json. + // Mirroring it here lets vitest resolve web tests without dragging in + // vite-tsconfig-paths just for one alias. + alias: { + "@": path.resolve(__dirname, "packages/web/src"), + }, + }, });