repoview 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/CONTRIBUTING.md +4 -3
  3. package/DEVELOPMENT.md +70 -16
  4. package/README.md +36 -5
  5. package/dist/api.js +58 -0
  6. package/dist/api.js.map +1 -0
  7. package/dist/cli.js +224 -0
  8. package/dist/cli.js.map +1 -0
  9. package/dist/csv.js +64 -0
  10. package/dist/csv.js.map +1 -0
  11. package/dist/format.js +25 -0
  12. package/dist/format.js.map +1 -0
  13. package/dist/git.js +67 -0
  14. package/dist/git.js.map +1 -0
  15. package/dist/gitignore.js +34 -0
  16. package/dist/gitignore.js.map +1 -0
  17. package/dist/linkcheck.js +310 -0
  18. package/dist/linkcheck.js.map +1 -0
  19. package/dist/markdown.js +493 -0
  20. package/dist/markdown.js.map +1 -0
  21. package/dist/net.js +10 -0
  22. package/dist/net.js.map +1 -0
  23. package/dist/paths.js +59 -0
  24. package/dist/paths.js.map +1 -0
  25. package/dist/reload.js +36 -0
  26. package/dist/reload.js.map +1 -0
  27. package/dist/repo-context.js +73 -0
  28. package/dist/repo-context.js.map +1 -0
  29. package/dist/repo-router.js +801 -0
  30. package/dist/repo-router.js.map +1 -0
  31. package/dist/review-cli.js +228 -0
  32. package/dist/review-cli.js.map +1 -0
  33. package/dist/server.js +116 -0
  34. package/dist/server.js.map +1 -0
  35. package/dist/session.js +86 -0
  36. package/dist/session.js.map +1 -0
  37. package/dist/types.js +2 -0
  38. package/dist/types.js.map +1 -0
  39. package/dist/views.js +633 -0
  40. package/dist/views.js.map +1 -0
  41. package/package.json +22 -6
  42. package/public/app.css +842 -0
  43. package/public/app.js +35 -2
  44. package/public/review.js +587 -0
  45. package/public/session.js +61 -0
  46. package/src/cli.js +0 -73
  47. package/src/gitignore.js +0 -34
  48. package/src/linkcheck.js +0 -312
  49. package/src/markdown.js +0 -364
  50. package/src/server.js +0 -760
  51. package/src/views.js +0 -479
package/CHANGELOG.md ADDED
@@ -0,0 +1,53 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. This project adheres to
4
+ [Semantic Versioning](https://semver.org/).
5
+
6
+ ## 0.6.0 — 2026-06-04
7
+
8
+ ### Added
9
+ - **Shared multi-repo sessions (tmux-style).** Multiple `repoview` invocations on
10
+ the same port now join one server instead of each starting its own. The first
11
+ run starts the daemon; later runs register their repo and exit immediately — no
12
+ more remembering a port per repo. Each repo is served at `/r/<id>/…`.
13
+ - **Repo switcher** in the topbar and a **`/session` management page** to open,
14
+ add, and remove repos from the browser.
15
+ - **CLI session commands:** `repoview ls`, `repoview rm <id|path>`, `repoview stop`.
16
+ - **Control API:** `GET /api/session`, `GET/POST /api/repos`,
17
+ `DELETE /api/repos/:id`, `POST /api/shutdown` (mutations restricted to loopback).
18
+ - HTTP integration test suite (`npm test`, `node:test`) — runs without a browser.
19
+
20
+ ### Changed
21
+ - **Default port is now `7376`** ("REPO" on a phone keypad) instead of `3000`, to
22
+ avoid colliding with common dev servers. Override with `--port` or `$PORT`.
23
+ - **Migrated the codebase to TypeScript** (strict). Sources compile to `dist/`;
24
+ the published package ships `dist/` and `bin` points at `dist/cli.js`.
25
+ - `startServer` was refactored into a session + per-repo context/router design;
26
+ each repo owns its own git info, gitignore matcher, link scanner, reload hub,
27
+ and file watcher.
28
+ - Playwright moved to `npm run test:e2e`; `npm test` is the browser-free suite.
29
+
30
+ ### Security
31
+ - The session binds `0.0.0.0` by default, exposing every added repo to the
32
+ network. The server now **warns at startup** when bound to a non-loopback host,
33
+ and the `/session` page is **read-only** for non-loopback viewers. Use
34
+ `--host 127.0.0.1` to keep a session fully local.
35
+
36
+ ### Fixed
37
+ - Markdown content links are prefixed with the repo base exactly once (guards a
38
+ double-rewrite during sanitization).
39
+ - Repo switcher dropdown stays on-screen on narrow viewports (left-anchored).
40
+
41
+ ### Upgrade notes (0.5.x → 0.6.0)
42
+ - **Default port changed** from `3000` to `7376`. If you relied on the old
43
+ default, pass `--port 3000` (or set `$PORT`).
44
+ - **URLs are now repo-prefixed** (`/r/<id>/…`). Old bookmarks to `/tree/…`,
45
+ `/blob/…`, etc. still work — they redirect to the default repo — but the
46
+ canonical URL now includes the repo id.
47
+ - No data migration is needed; review threads under `.repoview/` are unchanged.
48
+
49
+ ## 0.5.1
50
+
51
+ - Render Markdown frontmatter as a styled header.
52
+ - Inline code review threads; Playwright end-to-end suite.
53
+ </content>
package/CONTRIBUTING.md CHANGED
@@ -11,13 +11,14 @@ npm install
11
11
  ## Run
12
12
 
13
13
  ```bash
14
- npm start -- --repo /path/to/repo --port 3000
14
+ npm start -- --repo /path/to/repo --port 7376
15
15
  ```
16
16
 
17
- ## Lint
17
+ ## Lint & test
18
18
 
19
19
  ```bash
20
- npm run lint
20
+ npm run lint # tsc --noEmit (strict type-check)
21
+ npm test # build + node:test HTTP integration suite
21
22
  ```
22
23
 
23
24
  ## What to work on
package/DEVELOPMENT.md CHANGED
@@ -4,43 +4,80 @@ This doc collects the “how it works” details so `README.md` can stay product
4
4
 
5
5
  ## Project layout
6
6
 
7
- - `src/server.js`: Express server + routes (`/tree`, `/blob`, `/raw`, `/diff`, `/events`)
8
- - `src/markdown.js`: Markdown rendering + link/image rewriting + sanitization
9
- - `src/linkcheck.js`: broken-link scanner (Markdown rendered HTML → internal link validation)
10
- - `src/gitignore.js`: `.gitignore` matcher (used for hiding + scanner noise reduction)
11
- - `src/views.js`: HTML templates (mobile-first top bar + GitHub-style Markdown shell)
12
- - `public/`: CSS + client JS (live reload, KaTeX render, Mermaid render, diff collapse, query preservation)
7
+ - `src/server.ts`: Express app wiring vendor static mounts, the control API, the `/r/:repoId` repos router, the `/session` page, and legacy redirects
8
+ - `src/session.ts`: a session owning multiple repos (add/remove/list, slug ids, default repo)
9
+ - `src/api.ts`: control API router (`/api/session`, `/api/repos`, `/api/shutdown`); mutations loopback-guarded
10
+ - `src/repo-context.ts`: per-repo runtime state (git info, gitignore matcher, link scanner, reload hub, file watcher)
11
+ - `src/repo-router.ts`: the per-repo routes (`/tree`, `/blob`, `/raw`, `/diff`, `/review`, `/events`, …) as a router that resolves `/r/:repoId` → a per-repo child router
12
+ - `src/net.ts`: loopback helpers (control-endpoint guard, bind-host check)
13
+ - `src/types.ts`: shared interfaces (`RepoContext`, `Session`/`RepoSummary`, `GitInfo`, `MarkdownRenderer`, `LinkScanner`, …)
14
+ - `src/git.ts` / `src/paths.ts` / `src/format.ts` / `src/csv.ts` / `src/reload.ts`: extracted helpers (git CLI, path safety, byte/date formatting, CSV parsing, SSE reload hub)
15
+ - `src/markdown.ts`: Markdown rendering + link/image rewriting (repo-prefixed) + sanitization
16
+ - `src/linkcheck.ts`: broken-link scanner (Markdown → rendered HTML → internal link validation)
17
+ - `src/gitignore.ts`: `.gitignore` matcher (used for hiding + scanner noise reduction)
18
+ - `src/views.ts`: HTML templates (mobile-first top bar + repo switcher + GitHub-style Markdown shell)
19
+ - `public/`: CSS + client JS (live reload, KaTeX render, Mermaid render, diff collapse, query preservation, session management)
20
+
21
+ > See `docs/multi-repo-session.md` for the full multi-repo session design.
22
+
23
+ ## TypeScript
24
+
25
+ The source is TypeScript (`src/*.ts`, `strict` mode). It compiles to `dist/` via
26
+ `tsc` and the published package ships `dist/` (the `bin` points at `dist/cli.js`).
27
+
28
+ ```bash
29
+ npm run build # tsc → dist/
30
+ npm run lint # tsc --noEmit (type-check only)
31
+ ```
32
+
33
+ `dist/` is git-ignored and rebuilt on `prepack` (so `npm pack` / `npm publish`
34
+ always ship a fresh build).
13
35
 
14
36
  ## Running locally
15
37
 
16
38
  ```bash
17
39
  npm install
18
- npm start -- --repo /path/to/repo --port 3000
40
+ npm start -- --repo /path/to/repo --port 7376 # runs src via tsx (no build needed)
41
+ # or, after a build:
42
+ node dist/cli.js --repo /path/to/repo --port 7376
19
43
  ```
20
44
 
45
+ `npm start` / `npm run dev` use `tsx` to run the TypeScript directly for fast
46
+ iteration; `npm run build` produces the runnable `dist/` for publishing.
47
+
21
48
  Useful flags:
22
49
  - `--watch` / `--no-watch` (watch is on by default)
23
50
  - `--host 127.0.0.1` to bind locally only
24
51
 
25
52
  ## Routes
26
53
 
54
+ Every repo is served behind a `/r/<id>` prefix (the repo's slug id). Legacy
55
+ non-prefixed paths (`/`, `/tree`, `/blob`, `/raw`, `/diff`, `/review`,
56
+ `/broken-links`, `/events`, `/rev`) redirect to the default (first) repo.
57
+
58
+ Per-repo routes (under `/r/<id>`):
27
59
  - `GET /tree/<path>`: directory listing (applies `.gitignore` by default; `?ignored=1` shows ignored)
28
60
  - `GET /blob/<path>`: file view (Markdown rendered; non-Markdown shown as highlighted text)
29
61
  - `GET /raw/<path>`: raw bytes (used for images and downloads)
30
- - `GET /events`: Server-Sent Events stream for live reload
62
+ - `GET /events`: Server-Sent Events stream for live reload (`GET /rev` is the polling fallback)
31
63
  - `GET /diff`: diff view — compare working tree against a base ref (`?base=HEAD` default; accepts branches, tags)
32
- - `GET /broken-links`: HTML report for broken internal links (Markdown docs)
33
- - `GET /broken-links.json`: report state + raw results
64
+ - `GET /review/…`: code review threads
65
+ - `GET /broken-links[.json]`: broken internal link report
66
+
67
+ Session-level routes:
68
+ - `GET /session`: manage repos (open / add / remove); read-only for non-loopback clients
69
+ - `GET /api/session`: session signature + repo list (also the join handshake)
70
+ - `GET /api/repos`, `POST /api/repos`, `DELETE /api/repos/:id`, `POST /api/shutdown`: control API (mutations are loopback-only)
34
71
 
35
72
  ## Link rewriting rules
36
73
 
37
- `src/markdown.js` rewrites relative Markdown links so they stay inside the repo UI:
74
+ `src/markdown.ts` rewrites relative Markdown links so they stay inside the repo UI:
38
75
 
39
- - Links → `/blob/<path>` (or `/tree/<path>` when the link ends with `/`)
40
- - Images → `/raw/<path>`
76
+ - Links → `<repoBase>/blob/<path>` (or `<repoBase>/tree/<path>` when the link ends with `/`), where `repoBase` is `/r/<id>`
77
+ - Images → `<repoBase>/raw/<path>`
41
78
  - Same rewriting is applied to HTML inside Markdown (`<a href>`, `<img src>`) after sanitization.
42
79
  - Paths that would escape the repo root (leading `../`) are clamped to the repo root (GitHub-like).
43
- - Already-internal links (`/blob/…`, `/tree/…`, `/raw/…`, `/static/…`) are not rewritten again.
80
+ - Already-internal links (`/blob/…`, `/tree/…`, `/raw/…`, `/static/…`, and anything already under `repoBase`) are not rewritten again.
44
81
 
45
82
  ## Markdown “GitHub-like” features
46
83
 
@@ -65,9 +102,26 @@ Notes:
65
102
  ## Lint
66
103
 
67
104
  ```bash
68
- npm run lint
105
+ npm run lint # tsc --noEmit — full strict type-check
106
+ ```
107
+
108
+ ## Tests
109
+
110
+ ```bash
111
+ npm test # build + node:test HTTP integration suite (no browser needed)
112
+ npm run test:e2e # Playwright browser suite (requires installed browsers)
69
113
  ```
70
114
 
115
+ - `tests/integration/*.test.mjs` boots the server in-process (from `dist/`) and
116
+ drives it over HTTP — covering the session API, `/r/<id>` routing, legacy
117
+ redirects, repo registration/idempotency/slug-disambiguation, the session
118
+ page, and the unknown-repo fallback. This is the default `npm test` and runs
119
+ anywhere.
120
+ - The Playwright suite (`tests/frontend.spec.js`) covers the rich browsing/review
121
+ UI in a real browser; it needs `npx playwright install` and is run as
122
+ `test:e2e`. New multi-repo flows are exercised via the Chrome DevTools MCP
123
+ (see `docs/multi-repo-session.md`).
124
+
71
125
  ## Release checklist
72
126
 
73
127
  Before publishing to npm, run a quick smoke test from a clean install context (this catches issues where the server accidentally serves assets from the *repo* instead of the installed package):
@@ -83,5 +137,5 @@ npm init -y
83
137
  npm install /path/to/repoview-*.tgz
84
138
 
85
139
  # serve any repo and verify vendor assets load (no ENOENT)
86
- node ./node_modules/.bin/repoview --repo /path/to/repo --port 3000
140
+ node ./node_modules/.bin/repoview --repo /path/to/repo --port 7376
87
141
  ```
package/README.md CHANGED
@@ -14,30 +14,61 @@ Not affiliated with GitHub.
14
14
  - Live reload when files change (SSE with polling fallback)
15
15
  - Broken internal link discovery for docs (`/broken-links`)
16
16
  - Respects `.gitignore` by default (toggleable)
17
+ - Shared sessions (like tmux): run `repoview` in several repos on the same port and browse them all from one server, switching between them in the UI
17
18
 
18
19
  ## Quick start (from source)
19
20
 
20
21
  ```bash
21
22
  npm install
22
- npm start -- --repo /path/to/your/repo --port 3000
23
+ npm start -- --repo /path/to/your/repo --port 7376
23
24
  ```
24
25
 
25
- Then open `http://localhost:3000`.
26
+ Then open `http://localhost:7376`.
26
27
 
27
28
  ## Quick start (npx)
28
29
 
29
30
  From anywhere:
30
31
 
31
32
  ```bash
32
- npx repoview --repo /path/to/your/repo --port 3000
33
+ npx repoview --repo /path/to/your/repo --port 7376
33
34
  ```
34
35
 
35
36
  By default, `repoview` binds to `0.0.0.0` (LAN-accessible). For localhost-only:
36
37
 
37
38
  ```bash
38
- npx repoview --repo /path/to/your/repo --host 127.0.0.1 --port 3000
39
+ npx repoview --repo /path/to/your/repo --host 127.0.0.1 --port 7376
39
40
  ```
40
41
 
42
+ ## Shared sessions (multi-repo)
43
+
44
+ The first `repoview` on a port starts a server; later runs on the **same port**
45
+ join it instead of failing — they register their repo and exit immediately
46
+ (no need to remember a port per repo):
47
+
48
+ ```bash
49
+ cd ~/work/api && repoview # starts the session on :7376
50
+ cd ~/work/web && repoview # joins :7376, registers, exits
51
+ cd ~/work/docs && repoview # joins :7376, registers, exits
52
+ ```
53
+
54
+ Each repo is served at `/r/<id>/…`; switch between them from the dropdown in the
55
+ top bar, or open **Manage repos…** (the `/session` page) to add/remove repos
56
+ from the browser. Manage the session from the CLI too:
57
+
58
+ ```bash
59
+ repoview ls # list repos in the session
60
+ repoview rm <id|path> # unregister a repo
61
+ repoview stop # shut the session down
62
+ ```
63
+
64
+ Use `--port` to run independent sessions side by side.
65
+
66
+ > **Note:** by default the session binds `0.0.0.0`, so **every repo you add is
67
+ > browsable by anyone on the network** (you'll see a warning at startup). Session
68
+ > control endpoints (register / remove / stop) are restricted to localhost, and
69
+ > the `/session` page is read-only for remote viewers. Use `--host 127.0.0.1` to
70
+ > keep a session fully local.
71
+
41
72
  ## Why
42
73
 
43
74
  - Keep GitHub as a remote, not your developer portal.
@@ -47,7 +78,7 @@ npx repoview --repo /path/to/your/repo --host 127.0.0.1 --port 3000
47
78
  ## Usage
48
79
 
49
80
  ```bash
50
- npm start -- [--repo /path/to/repo] [--host 0.0.0.0] [--port 3000] [--no-watch]
81
+ npm start -- [--repo /path/to/repo] [--host 0.0.0.0] [--port 7376] [--no-watch]
51
82
  ```
52
83
 
53
84
  Common flags:
package/dist/api.js ADDED
@@ -0,0 +1,58 @@
1
+ import express from "express";
2
+ import { isLoopbackAddress } from "./net.js";
3
+ /** Restrict a mutating control endpoint to loopback clients. */
4
+ function requireLoopback(req, res, next) {
5
+ if (isLoopbackAddress(req.socket.remoteAddress))
6
+ return next();
7
+ res.status(403).json({ error: "Control endpoints are restricted to localhost" });
8
+ }
9
+ /**
10
+ * The session control API. `GET /api/session` doubles as the join-handshake
11
+ * signature; mutating routes (register/unregister/shutdown) are loopback-only.
12
+ */
13
+ export function createApiRouter(session, { version, onShutdown }) {
14
+ const router = express.Router();
15
+ router.get("/session", (req, res) => {
16
+ res.json({ app: "repoview", version, repos: session.listRepos() });
17
+ });
18
+ router.get("/repos", (req, res) => {
19
+ res.json({ repos: session.listRepos() });
20
+ });
21
+ router.post("/repos", requireLoopback, express.json(), async (req, res) => {
22
+ try {
23
+ const repoRoot = req.body?.path;
24
+ if (!repoRoot || typeof repoRoot !== "string") {
25
+ return res.status(400).json({ error: "path is required" });
26
+ }
27
+ const watch = req.body?.watch !== false;
28
+ const ctx = await session.addRepo({ repoRoot, watch });
29
+ res.status(201).json({
30
+ id: ctx.id,
31
+ name: ctx.repoName,
32
+ path: ctx.repoRootReal,
33
+ url: `/r/${ctx.id}/tree/`,
34
+ });
35
+ }
36
+ catch (e) {
37
+ res.status(500).json({ error: e.message });
38
+ }
39
+ });
40
+ router.delete("/repos/:id", requireLoopback, async (req, res) => {
41
+ try {
42
+ const removed = await session.removeRepo(req.params.id);
43
+ if (!removed)
44
+ return res.status(404).json({ error: "Repo not found" });
45
+ res.json({ ok: true, id: removed.id });
46
+ }
47
+ catch (e) {
48
+ res.status(500).json({ error: e.message });
49
+ }
50
+ });
51
+ router.post("/shutdown", requireLoopback, (req, res) => {
52
+ res.json({ ok: true });
53
+ // Defer so the response flushes before the process tears down.
54
+ setTimeout(onShutdown, 50);
55
+ });
56
+ return router;
57
+ }
58
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAC;AAI9B,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAE7C,gEAAgE;AAChE,SAAS,eAAe,CAAC,GAAY,EAAE,GAAa,EAAE,IAAkB;IACtE,IAAI,iBAAiB,CAAC,GAAG,CAAC,MAAM,CAAC,aAAa,CAAC;QAAE,OAAO,IAAI,EAAE,CAAC;IAC/D,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,+CAA+C,EAAE,CAAC,CAAC;AACnF,CAAC;AAOD;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,OAAgB,EAAE,EAAE,OAAO,EAAE,UAAU,EAA0B;IAC/F,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAEhC,MAAM,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QAClC,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,UAAU,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;IACrE,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QAChC,GAAG,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,eAAe,EAAE,OAAO,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;QACxE,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC;YAChC,IAAI,CAAC,QAAQ,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;gBAC9C,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,kBAAkB,EAAE,CAAC,CAAC;YAC7D,CAAC;YACD,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,EAAE,KAAK,KAAK,KAAK,CAAC;YACxC,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC;YACvD,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;gBACnB,EAAE,EAAE,GAAG,CAAC,EAAE;gBACV,IAAI,EAAE,GAAG,CAAC,QAAQ;gBAClB,IAAI,EAAE,GAAG,CAAC,YAAY;gBACtB,GAAG,EAAE,MAAM,GAAG,CAAC,EAAE,QAAQ;aAC1B,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAG,CAAW,CAAC,OAAO,EAAE,CAAC,CAAC;QACxD,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,MAAM,CAAC,YAAY,EAAE,eAAe,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;QAC9D,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YACxD,IAAI,CAAC,OAAO;gBAAE,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC,CAAC;YACvE,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC;QACzC,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAG,CAAW,CAAC,OAAO,EAAE,CAAC,CAAC;QACxD,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,eAAe,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QACrD,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;QACvB,+DAA+D;QAC/D,UAAU,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IAC7B,CAAC,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC;AAChB,CAAC"}
package/dist/cli.js ADDED
@@ -0,0 +1,224 @@
1
+ #!/usr/bin/env node
2
+ import path from "node:path";
3
+ import process from "node:process";
4
+ import { startServer } from "./server.js";
5
+ import { handleReviewCommand } from "./review-cli.js";
6
+ function printHelp() {
7
+ // Keep this in sync with README.md
8
+ process.stdout.write(`repoview
9
+
10
+ Serve local Git repositories as a GitHub-like website. Multiple invocations on
11
+ the same port join one shared session (like tmux) — the first run starts the
12
+ daemon, later runs register their repo and exit.
13
+
14
+ Usage:
15
+ npx repoview [--repo /path/to/repo] [--host 0.0.0.0] [--port 7376] [--no-watch]
16
+ repoview [--repo /path/to/repo] [--host 0.0.0.0] [--port 7376] [--no-watch]
17
+
18
+ Options:
19
+ --repo <path> Repository root (default: REPO_ROOT or current dir)
20
+ --host <host> Bind address (default: 0.0.0.0)
21
+ --port <port> Bind/session port (default: 7376)
22
+ --watch Enable live reload (default)
23
+ --no-watch Disable live reload
24
+ -h, --help Show this help
25
+
26
+ Session subcommands (target the daemon on --port):
27
+ repoview ls List repos in the session
28
+ repoview rm <id|path> Unregister a repo from the session
29
+ repoview stop Shut the session daemon down
30
+
31
+ Review subcommands:
32
+ repoview review new --title "Title" Create a new review thread
33
+ repoview review post <id> --role agent --body "…" Post a message to a thread
34
+ repoview review post <id> --role agent --file f Post from file
35
+ repoview review read <id> Read thread messages + comments
36
+ repoview review list List all threads
37
+
38
+ Environment:
39
+ REPO_ROOT, HOST, PORT
40
+ `);
41
+ }
42
+ function parseArgs(argv) {
43
+ const args = { watch: true };
44
+ const rest = [];
45
+ for (let i = 0; i < argv.length; i++) {
46
+ const value = argv[i];
47
+ if (value === "-h" || value === "--help")
48
+ args.help = true;
49
+ if (value === "--watch")
50
+ args.watch = true;
51
+ else if (value === "--no-watch")
52
+ args.watch = false;
53
+ else if (value === "--repo")
54
+ args.repo = argv[++i];
55
+ else if (value === "--port")
56
+ args.port = Number(argv[++i]);
57
+ else if (value === "--host")
58
+ args.host = argv[++i];
59
+ else
60
+ rest.push(value);
61
+ }
62
+ return { ...args, rest };
63
+ }
64
+ /** Address to connect to when joining a local daemon (0.0.0.0 isn't routable). */
65
+ function connectHost(host) {
66
+ if (host === "0.0.0.0" || host === "::" || host === "")
67
+ return "127.0.0.1";
68
+ return host;
69
+ }
70
+ async function fetchJson(url, init) {
71
+ try {
72
+ const res = await fetch(url, init);
73
+ if (!res.ok) {
74
+ const body = (await res.json().catch(() => ({})));
75
+ throw new Error(body.error || `HTTP ${res.status}`);
76
+ }
77
+ return await res.json();
78
+ }
79
+ catch (e) {
80
+ if (e instanceof Error && /HTTP \d|error/i.test(e.message) && !/fetch failed|ECONNREFUSED/i.test(e.message)) {
81
+ throw e;
82
+ }
83
+ return null;
84
+ }
85
+ }
86
+ async function probeSession(base) {
87
+ return (await fetchJson(`${base}/api/session`));
88
+ }
89
+ async function registerRepo(base, repoRoot, watch) {
90
+ const result = await fetchJson(`${base}/api/repos`, {
91
+ method: "POST",
92
+ headers: { "Content-Type": "application/json" },
93
+ body: JSON.stringify({ path: repoRoot, watch }),
94
+ });
95
+ if (!result)
96
+ throw new Error("Failed to register repo with the session");
97
+ return result;
98
+ }
99
+ function printRepoTable(repos, port, host) {
100
+ if (!repos.length) {
101
+ process.stdout.write("(no repos registered)\n");
102
+ return;
103
+ }
104
+ const open = connectHost(host);
105
+ for (const r of repos) {
106
+ process.stdout.write(`${r.id.padEnd(20)} ${(r.branch || "no-git").padEnd(16)} ${r.path}\n` +
107
+ `${" ".repeat(20)} http://${open}:${port}/r/${r.id}/tree/\n`);
108
+ }
109
+ }
110
+ async function runSubcommand(sub, args, base, port, host) {
111
+ if (sub === "ls") {
112
+ const info = await probeSession(base);
113
+ if (!info) {
114
+ process.stderr.write(`No repoview session on ${base}\n`);
115
+ return 1;
116
+ }
117
+ printRepoTable(info.repos || [], port, host);
118
+ return 0;
119
+ }
120
+ if (sub === "stop") {
121
+ const info = await probeSession(base);
122
+ if (!info) {
123
+ process.stderr.write(`No repoview session on ${base}\n`);
124
+ return 1;
125
+ }
126
+ await fetchJson(`${base}/api/shutdown`, { method: "POST" });
127
+ process.stdout.write(`Stopped session on port ${port}\n`);
128
+ return 0;
129
+ }
130
+ if (sub === "rm") {
131
+ const target = args.rest[1];
132
+ if (!target) {
133
+ process.stderr.write("Usage: repoview rm <id|path>\n");
134
+ return 1;
135
+ }
136
+ const info = await probeSession(base);
137
+ if (!info) {
138
+ process.stderr.write(`No repoview session on ${base}\n`);
139
+ return 1;
140
+ }
141
+ const repos = info.repos || [];
142
+ let real = target;
143
+ try {
144
+ real = path.resolve(target);
145
+ }
146
+ catch {
147
+ // keep as-is
148
+ }
149
+ const match = repos.find((r) => r.id === target || r.path === real || r.path === target);
150
+ if (!match) {
151
+ process.stderr.write(`Repo not found in session: ${target}\n`);
152
+ return 1;
153
+ }
154
+ const res = await fetchJson(`${base}/api/repos/${encodeURIComponent(match.id)}`, {
155
+ method: "DELETE",
156
+ });
157
+ if (!res) {
158
+ process.stderr.write(`Failed to remove ${match.id}\n`);
159
+ return 1;
160
+ }
161
+ process.stdout.write(`Removed ${match.id}\n`);
162
+ return 0;
163
+ }
164
+ process.stderr.write(`Unknown command: ${sub}\n`);
165
+ printHelp();
166
+ return 1;
167
+ }
168
+ const parsed = parseArgs(process.argv.slice(2));
169
+ const { repo, port: portArg, host: hostArg, watch, help } = parsed;
170
+ if (help) {
171
+ printHelp();
172
+ process.exit(0);
173
+ }
174
+ // Review subcommand (pure filesystem, no daemon).
175
+ if (parsed.rest[0] === "review") {
176
+ const repoRootForReview = repo ?? process.env.REPO_ROOT ?? process.cwd();
177
+ await handleReviewCommand(parsed.rest.slice(1), repoRootForReview);
178
+ process.exit(0);
179
+ }
180
+ if (portArg != null && !Number.isFinite(portArg)) {
181
+ process.stderr.write("Invalid --port value\n");
182
+ process.exit(2);
183
+ }
184
+ const port = portArg || Number(process.env.PORT) || 7376;
185
+ const host = hostArg || process.env.HOST || "0.0.0.0";
186
+ const base = `http://${connectHost(host)}:${port}`;
187
+ // Session-management subcommands target the running daemon.
188
+ if (["ls", "rm", "stop"].includes(parsed.rest[0])) {
189
+ const code = await runSubcommand(parsed.rest[0], parsed, base, port, host);
190
+ process.exit(code);
191
+ }
192
+ const repoRoot = repo ?? process.env.REPO_ROOT ?? process.cwd();
193
+ const repoRootAbs = path.resolve(repoRoot);
194
+ async function joinExisting() {
195
+ const info = await probeSession(base);
196
+ if (!info)
197
+ return false;
198
+ if (info.app !== "repoview") {
199
+ process.stderr.write(`Port ${port} is in use by something that isn't repoview. Use --port to pick another.\n`);
200
+ process.exit(1);
201
+ }
202
+ const registered = await registerRepo(base, repoRootAbs, watch);
203
+ process.stdout.write(`Joined repoview session on port ${port}\n` +
204
+ `${registered.name} → http://${connectHost(host)}:${port}${registered.url}\n`);
205
+ return true;
206
+ }
207
+ // Try to join an existing session first; otherwise become the daemon.
208
+ if (await joinExisting()) {
209
+ process.exit(0);
210
+ }
211
+ try {
212
+ await startServer({ repoRoot: repoRootAbs, port, host, watch });
213
+ }
214
+ catch (e) {
215
+ if (e.code === "EADDRINUSE") {
216
+ // Lost a startup race — another daemon just bound the port. Join it.
217
+ if (await joinExisting())
218
+ process.exit(0);
219
+ process.stderr.write(`Port ${port} is already in use.\n`);
220
+ process.exit(1);
221
+ }
222
+ throw e;
223
+ }
224
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
package/dist/csv.js ADDED
@@ -0,0 +1,64 @@
1
+ export function parseCsv(text, delimiter = ",") {
2
+ const rows = [];
3
+ let current = [];
4
+ let cell = "";
5
+ let inQuotes = false;
6
+ for (let i = 0; i < text.length; i++) {
7
+ const ch = text[i];
8
+ if (inQuotes) {
9
+ if (ch === '"' && text[i + 1] === '"') {
10
+ cell += '"';
11
+ i++;
12
+ }
13
+ else if (ch === '"') {
14
+ inQuotes = false;
15
+ }
16
+ else {
17
+ cell += ch;
18
+ }
19
+ }
20
+ else {
21
+ if (ch === '"') {
22
+ inQuotes = true;
23
+ }
24
+ else if (ch === delimiter) {
25
+ current.push(cell);
26
+ cell = "";
27
+ }
28
+ else if (ch === "\n" || (ch === "\r" && text[i + 1] === "\n")) {
29
+ if (ch === "\r")
30
+ i++;
31
+ current.push(cell);
32
+ rows.push(current);
33
+ current = [];
34
+ cell = "";
35
+ }
36
+ else if (ch === "\r") {
37
+ current.push(cell);
38
+ rows.push(current);
39
+ current = [];
40
+ cell = "";
41
+ }
42
+ else {
43
+ cell += ch;
44
+ }
45
+ }
46
+ }
47
+ if (cell || current.length) {
48
+ current.push(cell);
49
+ rows.push(current);
50
+ }
51
+ return rows;
52
+ }
53
+ export function renderCsvTable(rows, escFn) {
54
+ if (!rows.length)
55
+ return "<p>Empty file</p>";
56
+ const header = rows[0];
57
+ const body = rows.slice(1);
58
+ const ths = header.map((h) => `<th>${escFn(h)}</th>`).join("");
59
+ const trs = body
60
+ .map((row) => `<tr>${row.map((c) => `<td>${escFn(c)}</td>`).join("")}</tr>`)
61
+ .join("\n");
62
+ return `<div class="csv-table-wrap"><table class="csv-table"><thead><tr>${ths}</tr></thead><tbody>${trs}</tbody></table></div>`;
63
+ }
64
+ //# sourceMappingURL=csv.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"csv.js","sourceRoot":"","sources":["../src/csv.ts"],"names":[],"mappings":"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"}