Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 27 additions & 27 deletions .specs/features/web-console/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -245,28 +245,28 @@ Each acceptance criterion has one requirement ID and maps to the task that imple

| Requirement ID | Story | Phase | Status | Task |
| --- | --- | --- | --- | --- |
| WEB-01 | P1: Shared local server and home page | P1 | In Tasks | T1 |
| WEB-02 | P1: Shared local server and home page | P1 | In Tasks | T1, T7 |
| WEB-03 | P1: Shared local server and home page | P1 | In Tasks | T1 |
| WEB-04 | P1: Shared local server and home page | P1 | In Tasks | T1 |
| WEB-05 | P1: Shared local server and home page | P1 | In Tasks | T3 |
| WEB-06 | P1: Shared local server and home page | P1 | In Tasks | T3 |
| WEB-07 | P1: Shared local server and home page | P1 | In Tasks | T3 |
| WEB-01 | P1: Shared local server and home page | P1 | Implemented | T1 |
| WEB-02 | P1: Shared local server and home page | P1 | Implemented | T1, T7 |
| WEB-03 | P1: Shared local server and home page | P1 | Implemented | T1 |
| WEB-04 | P1: Shared local server and home page | P1 | Implemented | T1 |
| WEB-05 | P1: Shared local server and home page | P1 | Implemented | T3 |
| WEB-06 | P1: Shared local server and home page | P1 | Implemented | T3 |
| WEB-07 | P1: Shared local server and home page | P1 | Implemented | T3 |
| WEB-08 | P1: Shared local server and home page | P1 | In Tasks | T2, T4, T5, T13, T18 |
| WEB-09 | P2: Local request security | P2 | In Tasks | T7, T12, T17 |
| WEB-10 | P2: Local request security | P2 | In Tasks | T6, T7 |
| WEB-11 | P2: Local request security | P2 | In Tasks | T6 |
| WEB-12 | P2: Local request security | P2 | In Tasks | T6, T7 |
| WEB-13 | P2: Local request security | P2 | In Tasks | T6, T7 |
| WEB-14 | P3: Shared setup planning | P3 | In Tasks | T8 |
| WEB-15 | P3: Shared setup planning | P3 | In Tasks | T8 |
| WEB-16 | P3: Shared setup planning | P3 | In Tasks | T8 |
| WEB-17 | P3: Shared setup planning | P3 | In Tasks | T8 |
| WEB-18 | P3: Shared setup planning | P3 | In Tasks | T8 |
| WEB-19 | P3: Shared setup planning | P3 | In Tasks | T8 |
| WEB-10 | P2: Local request security | P2 | Implemented | T6, T7 |
| WEB-11 | P2: Local request security | P2 | Implemented | T6 |
| WEB-12 | P2: Local request security | P2 | Implemented | T6, T7 |
| WEB-13 | P2: Local request security | P2 | Implemented | T6, T7 |
| WEB-14 | P3: Shared setup planning | P3 | Implemented | T8 |
| WEB-15 | P3: Shared setup planning | P3 | Implemented | T8 |
| WEB-16 | P3: Shared setup planning | P3 | Implemented | T8 |
| WEB-17 | P3: Shared setup planning | P3 | Implemented | T8 |
| WEB-18 | P3: Shared setup planning | P3 | Implemented | T8 |
| WEB-19 | P3: Shared setup planning | P3 | Implemented | T8 |
| WEB-20 | P4: Browser setup | P4 | In Tasks | T11 |
| WEB-21 | P4: Browser setup | P4 | In Tasks | T11 |
| WEB-22 | P3: Shared setup planning | P3 | In Tasks | T9 |
| WEB-22 | P3: Shared setup planning | P3 | Implemented | T9 |
| WEB-23 | P4: Browser setup | P4 | In Tasks | T10 |
| WEB-24 | P4: Browser setup | P4 | In Tasks | T11 |
| WEB-25 | P4: Browser setup | P4 | In Tasks | T10 |
Expand Down Expand Up @@ -301,11 +301,11 @@ Each acceptance criterion has one requirement ID and maps to the task that imple
| WEB-54 | P5: Browser usage analytics | P5 | In Tasks | T17 |
| WEB-55 | P5: Browser usage analytics | P5 | In Tasks | T17 |
| WEB-56 | P5: Browser usage analytics | P5 | In Tasks | T17 |
| WEB-57 | P1: Shared local server and home page | P1 | In Tasks | T1 |
| WEB-57 | P1: Shared local server and home page | P1 | Implemented | T1 |
| WEB-59 | P4: Browser setup | P4 | In Tasks | T11 |
| WEB-60 | P5: Browser usage analytics | P5 | In Tasks | T16 |
| WEB-61 | P3: Shared setup planning | P3 | In Tasks | T8 |
| WEB-62 | P3: Shared setup planning | P3 | In Tasks | T8 |
| WEB-61 | P3: Shared setup planning | P3 | Implemented | T8 |
| WEB-62 | P3: Shared setup planning | P3 | Implemented | T8 |
| WEB-63 | P4: Browser setup | P4 | In Tasks | T10, T11 |
| WEB-64 | P4: Browser setup | P4 | In Tasks | T10 |
| WEB-65 | P4: Browser setup | P4 | In Tasks | T10, T11 |
Expand All @@ -314,11 +314,11 @@ Each acceptance criterion has one requirement ID and maps to the task that imple
| WEB-68 | P4: Browser setup | P4 | In Tasks | T8, T10 |
| WEB-69 | P4: Browser setup | P4 | In Tasks | T12 |
| WEB-70 | P4: Browser setup | P4 | In Tasks | T12 |
| WEB-71 | P2: Local request security | P2 | In Tasks | T6, T7 |
| WEB-72 | P2: Local request security | P2 | In Tasks | T6, T7 |
| WEB-71 | P2: Local request security | P2 | Implemented | T6, T7 |
| WEB-72 | P2: Local request security | P2 | Implemented | T6, T7 |
| WEB-73 | P2: Local request security | P2 | In Tasks | T10 |
| WEB-74 | P2: Local request security | P2 | In Tasks | T6, T7 |
| WEB-75 | P1: Shared local server and home page | P1 | In Tasks | T1, T7 |
| WEB-74 | P2: Local request security | P2 | Implemented | T6, T7 |
| WEB-75 | P1: Shared local server and home page | P1 | Implemented | T1, T7 |
| WEB-76 | P4: Browser setup | P4 | In Tasks | T11 |
| WEB-77 | P4: Browser setup | P4 | In Tasks | T11 |
| WEB-78 | P5: Browser usage analytics | P5 | In Tasks | T15, T16 |
Expand All @@ -328,8 +328,8 @@ Each acceptance criterion has one requirement ID and maps to the task that imple
| WEB-83 | P5: Browser usage analytics | P5 | In Tasks | T17 |
| WEB-84 | P4: Browser setup | P4 | In Tasks | T11 |
| WEB-85 | P4: Browser setup | P4 | In Tasks | T11 |
| WEB-86 | P3: Shared setup planning | P3 | In Tasks | T8 |
| WEB-87 | P3: Shared setup planning | P3 | In Tasks | T8 |
| WEB-86 | P3: Shared setup planning | P3 | Implemented | T8 |
| WEB-87 | P3: Shared setup planning | P3 | Implemented | T8 |
| WEB-88 | P4: Browser setup | P4 | In Tasks | T10, T11 |
| WEB-89 | P4: Browser setup | P4 | In Tasks | T10, T11 |
| WEB-90 | P4: Browser setup | P4 | In Tasks | T10, T11 |
Expand Down
2 changes: 1 addition & 1 deletion .specs/features/web-console/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Implement these tasks with the tlc-spec-driven skill. Keep tests in the task tha

**Design**: .specs/features/web-console/design.md

**Status**: Draft
**Status**: In progress. Done: T1-T9, T14 (feat/web-console-foundation)

## Test Coverage Matrix

Expand Down
72 changes: 25 additions & 47 deletions src/cli/commands/review.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,27 @@
import http from "node:http";
import { spawn } from "node:child_process";
import { InvalidArgumentError, type Command } from "commander";
import type { Command } from "commander";
import { REVIEW_PAGE } from "../../web/review-page.js";
import { getLocalReview } from "../../git/review.js";
import { DEFAULT_WEB_PORT, openBrowser, parseWebPort, startWebServer, type WebRoute } from "../../web/server.js";

export const DEFAULT_REVIEW_PORT = 3100;
export const DEFAULT_REVIEW_PORT = DEFAULT_WEB_PORT;

export function parseReviewPort(raw: string | undefined): number {
if (raw === undefined) return DEFAULT_REVIEW_PORT;
if (!/^\d+$/.test(raw)) {
throw new InvalidArgumentError("--port must be a positive integer");
}
const port = Number(raw);
if (!Number.isInteger(port) || port <= 0 || port > 65535) {
throw new InvalidArgumentError("--port must be a positive integer");
}
return port;
return parseWebPort(raw);
}

export function openBrowser(url: string): boolean {
const opener =
process.platform === "darwin"
? "open"
: process.platform === "win32"
? "cmd"
: "xdg-open";
const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
try {
const child = spawn(opener, args, { detached: true, stdio: "ignore" });
child.unref();
return true;
} catch {
return false;
}
}
export { openBrowser };

export interface ReviewDeps {
/** Repo root for /api/review. Defaults to process.cwd() at request time. */
root?: string;
loadReview?: (root: string, ref: string, file?: string) => Promise<unknown>;
}

export interface ReviewCommandDependencies extends ReviewDeps {
startServer?: typeof startWebServer;
}

function sendJson(res: http.ServerResponse, code: number, value: unknown): void {
res.writeHead(code, { "content-type": "application/json; charset=utf-8" });
res.end(JSON.stringify(value));
Expand Down Expand Up @@ -91,7 +72,16 @@ export interface ReviewCommandOptions {
open?: boolean;
}

export function registerReviewCommand(program: Command): void {
export function createReviewRoutes(reviewDeps?: ReviewDeps): WebRoute[] {
const handler = createReviewHandler(reviewDeps);
return [
{ path: "/", handler, kind: "page" },
{ path: "/review", handler, kind: "page", label: "Review" },
{ path: "/api/review", handler, kind: "api" },
];
}

export function registerReviewCommand(program: Command, dependencies: ReviewCommandDependencies = {}): void {
program
.command("review")
.description("Open a local review of the current git changes")
Expand All @@ -107,29 +97,17 @@ export function registerReviewCommand(program: Command): void {
return;
}

const server = http.createServer(createReviewHandler());
try {
await new Promise<void>((resolve, reject) => {
server.once("error", reject);
server.listen(port, "127.0.0.1", () => resolve());
await (dependencies.startServer ?? startWebServer)({
routes: createReviewRoutes(dependencies),
port,
initialPath: "/",
title: "CodeDeck review",
open: opts.open,
});
} catch (error) {
console.error(`Failed to listen on 127.0.0.1:${port}: ${error instanceof Error ? error.message : String(error)}`);
process.exitCode = 1;
return;
}

const url = `http://127.0.0.1:${port}/`;
console.log(`CodeDeck review on ${url}`);
if (opts.open !== false && !openBrowser(url)) {
console.log(`Could not open a browser, visit ${url} manually.`);
}

const shutdown = () => {
server.close(() => process.exit(0));
setTimeout(() => process.exit(0), 1000).unref?.();
};
process.on("SIGINT", shutdown);
process.on("SIGTERM", shutdown);
});
}
Loading
Loading