diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..0cd515b --- /dev/null +++ b/.gitattributes @@ -0,0 +1,38 @@ +# Auto-detect text files, normalize to LF on commit +* text=auto + +# Source code — always LF +*.ts text eol=lf +*.tsx text eol=lf +*.js text eol=lf +*.mjs text eol=lf +*.cjs text eol=lf +*.json text eol=lf +*.toml text eol=lf +*.yaml text eol=lf +*.yml text eol=lf +*.md text eol=lf +*.css text eol=lf +*.html text eol=lf +*.svg text eol=lf +*.astro text eol=lf +*.jsonc text eol=lf +*.plist text eol=lf +*.td text eol=lf + +# Shell scripts — always LF (won't run with CRLF) +*.sh text eol=lf +*.bash text eol=lf + +# Binary files — don't touch line endings +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.ico binary +*.icns binary +*.woff binary +*.woff2 binary +*.eot binary +*.ttf binary +*.lockb binary diff --git a/README.md b/README.md index 4cecf8b..bd96f33 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,25 @@ bun run dev > **Requirements:** [Codex CLI](https://github.com/openai/codex), Node.js 24+ or Bun, Git 2.30+, modern browser. +#### From Source - Windows + +On Windows, the server must run on Node.js because Bun does not yet implement ConPTY. The `dev` command handles this automatically. After cloning: + +```powershell +# Install dependencies (use public registry if you have a private npm registry configured) +bun install --registry https://registry.npmjs.org + +# Build the server (one-time) +bun run build + +# Start the dev environment +bun run dev +``` + +Then open **`http://localhost:5733`** in your browser. + +> `bun run dev` auto-detects Windows and runs the Node.js server alongside the Vite frontend — same command as macOS/Linux. + ## Features ### Multi-Agent, One Interface @@ -148,6 +167,22 @@ bun run dist:desktop:linux # Linux AppImage bun run dist:desktop:win # Windows installer ``` +### Windows Development + +`bun run dev` auto-detects Windows and runs the server on Node.js instead of Bun. The workflow is the same as macOS/Linux: + +```powershell +# First-time: build the server +bun run build + +# Start both frontend and backend +bun run dev +``` + +Open **`http://localhost:5733`**. After editing server source, rebuild with `bun run build` and restart. + +> On Windows, `bun run dev` spawns Vite (via Bun) and the Node.js server as two coordinated processes — no manual terminal juggling or environment variables required. + ### Isolated Development Run alongside an existing Peak Code instance without port conflicts: diff --git a/README.zh.md b/README.zh.md index daab6d7..dbb84d0 100644 --- a/README.zh.md +++ b/README.zh.md @@ -67,6 +67,25 @@ bun run dev > **要求:** [Codex CLI](https://github.com/openai/codex)、Node.js 24+ 或 Bun、Git 2.30+、现代浏览器。 +#### 从源码构建 - Windows + +Windows 上服务器需用 Node.js 运行(Bun 尚未实现 ConPTY)。`dev` 命令已自动处理此问题。克隆项目后: + +```powershell +# 安装依赖(如果配了私有 npm 仓库,需指定公网 registry) +bun install --registry https://registry.npmjs.org + +# 构建服务器(一次性) +bun run build + +# 启动开发环境 +bun run dev +``` + +然后浏览器打开 **`http://localhost:5733`**。 + +> `bun run dev` 自动检测 Windows 并用 Node.js 运行服务器,同时启动 Vite 前端——和 macOS/Linux 同一个命令。 + ## 功能特性 ### 多代理,统一界面 @@ -147,6 +166,22 @@ bun run dist:desktop:linux # Linux AppImage bun run dist:desktop:win # Windows 安装包 ``` +### Windows 开发 + +`bun run dev` 自动检测 Windows 并用 Node.js 运行服务器,工作流与 macOS/Linux 一致: + +```powershell +# 首次:构建服务器 +bun run build + +# 一键启动前后端 +bun run dev +``` + +打开 **`http://localhost:5733`**。修改服务器源码后,运行 `bun run build` 重新构建并重启。 + +> Windows 上 `bun run dev` 会将 Vite(Bun)和 Node.js 服务器作为两个协调进程启动——无需手动开终端或配环境变量。 + ### 隔离开发 与现有 Peak Code 实例并行运行,避免端口冲突: diff --git a/package.json b/package.json index bb96b22..f9d8386 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "dev:web": "node scripts/dev-runner.ts dev:web", "dev:marketing": "turbo run dev --filter=@peakcode/marketing", "dev:desktop": "node scripts/dev-runner.ts dev:desktop", + "dev:win": "node scripts/dev-runner.ts dev:win", "electron": "bun run start:desktop", "electron:dev": "node scripts/dev-runner.ts dev:desktop --home-dir ./.peakcode/electron-dev", "start": "turbo run start --filter=peakcode", diff --git a/scripts/dev-runner.ts b/scripts/dev-runner.ts index a85c923..1e8186b 100644 --- a/scripts/dev-runner.ts +++ b/scripts/dev-runner.ts @@ -37,6 +37,14 @@ const MODE_ARGS = { "--filter=@peakcode/web", "--parallel", ], + "dev:win": [ + "run", + "dev", + "--ui=tui", + "--filter=@peakcode/contracts", + "--filter=@peakcode/web", + "--parallel", + ], } as const satisfies Record>; type DevMode = keyof typeof MODE_ARGS; @@ -192,7 +200,7 @@ export function createDevRunnerEnv({ delete output.PEAKCODE_LOG_WS_EVENTS; } - if (mode === "dev") { + if (mode === "dev" || mode === "dev:win") { output.PEAKCODE_MODE = "web"; delete output.PEAKCODE_DESKTOP_WS_URL; } @@ -451,30 +459,79 @@ export function runDevRunnerWithInput(input: DevRunnerCliInput) { return; } - const child = yield* ChildProcess.make( - "turbo", - [...MODE_ARGS[input.mode], ...input.turboArgs], - { - stdin: "inherit", - stdout: "inherit", - stderr: "inherit", + // On Windows the server must run on Node.js because Bun does not + // implement ConPTY. When the user runs `dev` on Windows — or explicitly + // runs `dev:win` — we split into two processes: turbo drives contracts + + // Vite, and Node.js runs the pre-built server directly. + const useWinSplit = + input.mode === "dev:win" || + (input.mode === "dev" && process.platform === "win32"); + + if (useWinSplit) { + const pathService = yield* Path.Path; + const serverCwd = pathService.join(process.cwd(), "apps", "server"); + const serverScript = pathService.join(serverCwd, "dist", "index.mjs"); + + const common = { + stdin: "inherit" as const, + stdout: "inherit" as const, + stderr: "inherit" as const, env, - extendEnv: false, - // Windows needs shell mode to resolve .cmd shims (e.g. bun.cmd). - shell: process.platform === "win32", - // Keep turbo in the same process group so terminal signals (Ctrl+C) - // reach it directly. Effect defaults to detached: true on non-Windows, - // which would put turbo in a new group and require manual forwarding. - detached: false, - forceKillAfter: "1500 millis", - }, - ); - - const exitCode = yield* child.exitCode; - if (exitCode !== 0) { - return yield* new DevRunnerError({ - message: `turbo exited with code ${exitCode}`, + extendEnv: false as const, + detached: false as const, + forceKillAfter: "1500 millis" as const, + }; + + const turboChild = yield* ChildProcess.make( + "turbo", + [...MODE_ARGS["dev:win"], ...input.turboArgs], + { ...common, shell: process.platform === "win32" }, + ); + + const serverChild = yield* ChildProcess.make("node", [serverScript], { + ...common, + cwd: serverCwd, }); + + const [turboExit, serverExit] = yield* Effect.all( + [turboChild.exitCode, serverChild.exitCode], + { concurrency: "unbounded" }, + ); + + if (turboExit !== 0 || serverExit !== 0) { + const parts: Array = []; + if (turboExit !== 0) parts.push(`turbo exited with code ${turboExit}`); + if (serverExit !== 0) parts.push(`server exited with code ${serverExit}`); + return yield* new DevRunnerError({ + message: parts.join("; "), + }); + } + } else { + const child = yield* ChildProcess.make( + "turbo", + [...MODE_ARGS[input.mode], ...input.turboArgs], + { + stdin: "inherit", + stdout: "inherit", + stderr: "inherit", + env, + extendEnv: false, + // Windows needs shell mode to resolve .cmd shims (e.g. bun.cmd). + shell: process.platform === "win32", + // Keep turbo in the same process group so terminal signals (Ctrl+C) + // reach it directly. Effect defaults to detached: true on non-Windows, + // which would put turbo in a new group and require manual forwarding. + detached: false, + forceKillAfter: "1500 millis", + }, + ); + + const exitCode = yield* child.exitCode; + if (exitCode !== 0) { + return yield* new DevRunnerError({ + message: `turbo exited with code ${exitCode}`, + }); + } } }).pipe( Effect.mapError((cause) =>