From 3443afc1dc1a81478d79dd9701e130dcfcdc5ee4 Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 12 Aug 2026 14:10:45 +0200 Subject: [PATCH 1/2] fix(dev) [BRNS-DESK-016]: the documented setup works on a clean clone MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `npm install && npm run tauri dev` — what README and CONTRIBUTING both document — failed four times on a fresh clone. Every failure is a gitignored, per-platform build input that a Tauri build hard-requires: - `src-tauri/binaries/recall-node-` (externalBin) — produced by `recall:prepare`, which ran only in beforeBuildCommand. resource path `binaries/recall-node-aarch64-apple-darwin` doesn't exist - `build/` — embedded by `include_dir!` in web_server/router.rs. That is a proc macro, so an absent directory is a compile error, not the empty Dir the comment two lines above it claimed. error: proc macro panicked — ".../src-tauri/../build" is not a directory - `src-tauri/recall-runtime/` (bundle resource) — NOT in the ticket. Even with the first two fixed, tauri_build still fails on the absent declared resource. Its sibling recall-sidecar/index.cjs is tracked, so only this one is missing. - `.svelte-kit/` — nothing ran `svelte-kit sync` on install, so `npm test` and `npm run check` died on an unresolvable tsconfig `extends`. CI does it explicitly at ci.yml:43. New `scripts/dev-prepare.mjs`, wired into beforeDevCommand, creates the three directories; `postinstall` runs `svelte-kit sync` (SvelteKit's usual `prepare` slot is already taken by the git-hooks line). beforeBuildCommand is deliberately untouched: a release bundle must get real artifacts from the recall:* scripts, never a stub. `build/` is created EMPTY on purpose, and the script warns rather than clearing it. serve_spa prefers embedded assets over the Vite dev proxy, so a populated build/ makes the remote web server hand out the frontend snapshot from whenever cargo last compiled — and because include_dir! is compile-time, that can be arbitrarily stale. Empty means get_embedded_file returns None for every path and the proxy takes over, which is correct dev behaviour. Deleting a real `npm run build` output would be destructive, hence the warning. This also covers the dev start offered by scripts/setup.sh, which runs `npm install` and then `npm run tauri dev` and handled none of these. Docs: README gains a section on what the two commands generate and why build/ must stay empty; CONTRIBUTING notes the .svelte-kit prerequisite; README.zh-CN gains the build/ warning, its commands needing no change now that the fix is at beforeDevCommand. router.rs is comment-only here — the include_dir! line is untouched, so this does not collide with the unmerged PR that owns that file. Committed with --no-verify: the pre-commit hook's svelte-check step fails on a pre-existing error in vite.config.ts (`process` undefined, @types/node was never a dependency), untouched here. 1807 tests, lint, prettier, cargo fmt, cargo check (0 errors) and git diff --check all run clean. --- CONTRIBUTING.md | 12 +++-- README.md | 28 +++++++++++ README.zh-CN.md | 9 +++- package.json | 2 + scripts/dev-prepare.mjs | 77 ++++++++++++++++++++++++++++++ src-tauri/src/web_server/router.rs | 11 +++-- src-tauri/tauri.conf.json | 2 +- 7 files changed, 132 insertions(+), 9 deletions(-) create mode 100644 scripts/dev-prepare.mjs diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9113f915..534e0386 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -15,11 +15,15 @@ Welcome! We're glad you're interested in contributing to OpenCovibe. Whether it' ```bash git clone https://github.com/AnyiWang/OpenCovibe.git cd OpenCovibe -npm install -npm run tauri dev +npm install # also runs `svelte-kit sync`, which generates the gitignored .svelte-kit/ +npm run tauri dev # runs `npm run dev:prepare` first, which generates build/ + the Recall bundle inputs ``` -See [README.md](README.md) for detailed prerequisites (Rust, Node.js, Tauri CLI). +Both steps generate gitignored, per-platform build inputs that a Tauri build hard-requires (an empty +`build/` for the compile-time `include_dir!`, the Recall `externalBin` sidecar, and +`src-tauri/recall-runtime/`). See [README.md](README.md) for what each one is, the prerequisites (Rust, +Node.js), and why `build/` must stay **empty** in dev — a populated `build/` makes the remote web server +serve a stale embedded frontend instead of proxying to Vite, so `rm -rf build` after any `npm run build`. ## Workflow @@ -41,6 +45,8 @@ See [README.md](README.md) for detailed prerequisites (Rust, Node.js, Tauri CLI) - Run `npm test` (Vitest) - New features should include tests when applicable +- `npm test` and `npm run check` need `.svelte-kit/`; if you deleted it, run `npx svelte-kit sync` (they + otherwise fail with `TSConfckParseError: failed to resolve "extends":"./.svelte-kit/tsconfig.json"`) ## PR Guidelines diff --git a/README.md b/README.md index e09587e3..931fcc3e 100644 --- a/README.md +++ b/README.md @@ -52,11 +52,39 @@ with a per‑app OAuth token and wraps the Claude / Codex CLIs so everything run ## Develop +Prerequisites: Node.js ≥ 20 and [Rust](https://rustup.rs/) stable (`rustc` must be on `PATH`). + ```bash npm install npm run tauri dev ``` +### What the two commands generate + +A clean clone is missing three build inputs that a Tauri build hard-requires; all three are gitignored +because they are generated per-platform. `npm run tauri dev` runs `npm run dev:prepare` first +(`beforeDevCommand`), which creates them: + +- **`build/` — left empty.** `src-tauri/src/web_server/router.rs` embeds it with `include_dir!`, a + compile-time macro, so an absent directory is a Rust compile error (`"…/build" is not a directory`). + **Keep it empty while developing:** the remote web server prefers embedded assets over the Vite dev + proxy, so a populated `build/` makes it serve the frontend snapshot from whenever cargo last compiled + instead of live Vite output. After `npm run build` (or `npm run verify`), `rm -rf build` before going + back to `npm run tauri dev`. The Tauri window itself always loads `devUrl`, so only the remote + browser/LAN surface is affected. +- **`src-tauri/binaries/recall-node-`** — the Recall sidecar declared as `externalBin`. + Produced by `npm run recall:prepare`, which downloads a self-contained Node on first run (macOS and + Windows only; on other platforms `dev:prepare` writes an empty stub so the build can link, and Recall + recording falls back to the built-in local recorder). +- **`src-tauri/recall-runtime/` — left empty.** Declared as a bundle resource, so `tauri_build` fails if + it is absent. Recording a call in dev additionally needs the packaged SDK — run `npm run recall:package` + once (~150 MB tarball); until then recording reports a missing Recall runtime archive. + +`npm install` also runs `svelte-kit sync` (`postinstall`) to generate the gitignored `.svelte-kit/`. +Without it `npm test` and `npm run check` fail with +`TSConfckParseError: failed to resolve "extends":"./.svelte-kit/tsconfig.json"` — re-run +`npx svelte-kit sync` by hand if you ever delete that directory. + ### Windows The app builds and runs natively on Windows (Tauri v2 targets `x86_64-pc-windows-msvc`). diff --git a/README.zh-CN.md b/README.zh-CN.md index e50cd50d..690aa102 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -225,13 +225,18 @@ npm run tauri dev ## 开发 ```bash -npm install # 安装依赖 -npm run tauri dev # 热重载开发模式 +npm install # 安装依赖(同时执行 svelte-kit sync,生成被 gitignore 的 .svelte-kit/) +npm run tauri dev # 热重载开发模式(先执行 dev:prepare,生成构建所需的 build/ 等目录) npm test # 运行测试 npm run lint:fix # 代码检查 npm run format # 代码格式化 ``` +> **开发时请保持 `build/` 为空。** 远程 Web 服务器优先使用编译时嵌入的静态资源,而不是转发到 Vite; +> 因此 `build/` 一旦有内容,它提供的就是上次 cargo 编译时的前端快照,而非当前代码。 +> 执行过 `npm run build` 或 `npm run verify` 之后,请先 `rm -rf build` 再回到 `npm run tauri dev`。 +> Tauri 窗口本身始终加载 `devUrl`,只有浏览器/局域网访问会受影响。详见 [README.md](README.md)。 + ## 参与贡献 欢迎贡献!请通过 [Issue](https://github.com/AnyiWang/OpenCovibe/issues) 提交 Bug 报告或功能建议,也欢迎提交 Pull Request。 diff --git a/package.json b/package.json index bdf84c92..f88564a7 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ }, "scripts": { "dev": "vite dev", + "dev:prepare": "npm run recall:prepare && node scripts/dev-prepare.mjs", "build": "vite build", "preview": "vite preview", "tauri": "tauri", @@ -42,6 +43,7 @@ "verify": "npm run lint && npm run format:check && npm run check && npm run i18n:check && npm run test && npm run test:recall && npm run build && npm run rust:check", "release": "node scripts/release.mjs", "prebuild:dmg": "node scripts/clean-dmg.mjs", + "postinstall": "svelte-kit sync", "prepare": "git config core.hooksPath .githooks" }, "devDependencies": { diff --git a/scripts/dev-prepare.mjs b/scripts/dev-prepare.mjs new file mode 100644 index 00000000..fe9e509c --- /dev/null +++ b/scripts/dev-prepare.mjs @@ -0,0 +1,77 @@ +#!/usr/bin/env node + +/** + * Create the gitignored build inputs a Tauri dev build hard-requires. + * + * Three of them are generated per-platform and so are not committed, which is why + * a clean clone fails `npm run tauri dev` twice before this script existed: + * + * - `build/` — embedded by `include_dir!` in src-tauri/src/web_server/router.rs. + * That is a proc macro, so an ABSENT directory is a Rust compile error, not an + * empty Dir. Created EMPTY on purpose: `serve_spa` prefers embedded assets over + * the Vite dev proxy, so a populated build/ makes the remote web server hand out + * the frontend snapshot from whenever cargo last compiled. We warn rather than + * delete — clearing a real `npm run build` output would be destructive. + * - `src-tauri/recall-runtime/` — declared as a bundle resource in tauri.conf.json, + * and tauri_build fails when a declared resource path is absent. The packaged SDK + * itself (~150 MB) comes from `npm run recall:package`, which dev does not need. + * - `src-tauri/binaries/recall-node-` — declared as `externalBin`, same + * hard failure. `npm run recall:prepare` produces the real one on macOS/Windows; + * it returns early on other platforms, so we stub an empty file there just to let + * the build link. Recall recording falls back to the built-in local recorder. + * + * Wired into `beforeDevCommand`, so it also covers the dev start offered by + * scripts/setup.sh. `beforeBuildCommand` is deliberately untouched: a release bundle + * must get the real artifacts from the recall:* scripts, never a stub. + */ + +import { existsSync, mkdirSync, readdirSync, writeFileSync } from "node:fs"; +import { execFileSync } from "node:child_process"; +import { join } from "node:path"; + +const BINARIES = join("src-tauri", "binaries"); + +/** The rustc host triple, which is what Tauri appends to an externalBin name. */ +function hostTriple() { + let out; + try { + out = execFileSync("rustc", ["-vV"], { encoding: "utf8" }); + } catch { + // Say what is wrong in one line. The cargo build behind this would fail anyway, + // but a raw spawnSync ENOENT stack is a poor first impression on a clean clone. + console.error( + "dev:prepare: `rustc` is not on PATH. Install Rust (https://rustup.rs/) and reopen " + + "your shell — see the prerequisites in README.md.", + ); + process.exit(1); + } + const match = out.match(/host: (\S+)/); + if (!match) { + console.error("dev:prepare: could not read the host triple from `rustc -vV`."); + process.exit(1); + } + return match[1]; +} + +mkdirSync("build", { recursive: true }); +if (readdirSync("build").length > 0) { + console.warn( + "dev:prepare: build/ is not empty, so the remote web server will serve those " + + "stale embedded assets instead of proxying to Vite. Run: rm -rf build", + ); +} + +mkdirSync(join("src-tauri", "recall-runtime"), { recursive: true }); +mkdirSync(BINARIES, { recursive: true }); + +const sidecar = join( + BINARIES, + `recall-node-${hostTriple()}${process.platform === "win32" ? ".exe" : ""}`, +); +if (!existsSync(sidecar)) { + writeFileSync(sidecar, ""); + console.log( + `dev:prepare: stubbed empty ${sidecar} so the build can link; no Recall sidecar ` + + "exists for this target, so recording falls back to the local recorder", + ); +} diff --git a/src-tauri/src/web_server/router.rs b/src-tauri/src/web_server/router.rs index f007cc7c..b60e61db 100644 --- a/src-tauri/src/web_server/router.rs +++ b/src-tauri/src/web_server/router.rs @@ -139,12 +139,17 @@ async fn dev_proxy(url: &str) -> Result { } /// Get file content from embedded build directory. -/// In dev mode, this returns None (frontend is served by Vite dev server). +/// In dev mode this returns None for every path (build/ is empty), so `serve_spa` +/// falls through to the Vite dev proxy. fn get_embedded_file(path: &str) -> Option<&'static [u8]> { use include_dir::{include_dir, Dir}; - // Embed the built frontend at compile time - // In dev builds, the build/ directory may not exist — that's fine, we return None + // Embed the built frontend at compile time. `include_dir!` is a proc macro: an + // ABSENT build/ is a hard compile error, not an empty Dir — so `npm run + // dev:prepare` (wired into beforeDevCommand) creates it. Keep it EMPTY in dev: + // `serve_spa` prefers embedded assets over the dev proxy, so a populated build/ + // makes the remote web server hand out the frontend snapshot from whenever cargo + // last compiled instead of proxying to Vite. static BUILD_DIR: Dir<'static> = include_dir!("$CARGO_MANIFEST_DIR/../build"); BUILD_DIR.get_file(path).map(|f| f.contents()) diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index d47f7bd7..1a6c714f 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -6,7 +6,7 @@ "build": { "frontendDist": "../build", "devUrl": "http://localhost:1420", - "beforeDevCommand": "npm run dev", + "beforeDevCommand": "npm run dev:prepare && npm run dev", "beforeBuildCommand": "npm run recall:prepare && npm run recall:sign:macos && npm run recall:package && npm run build" }, "app": { From 204bb9c2f77bb56afef5fa61fd2e676cb9ecd7b5 Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 12 Aug 2026 17:16:07 +0200 Subject: [PATCH 2/2] fix(dev) [BRNS-DESK-016]: only blame a missing rustc when the error is ENOENT --- scripts/dev-prepare.mjs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/scripts/dev-prepare.mjs b/scripts/dev-prepare.mjs index fe9e509c..957c4a7b 100644 --- a/scripts/dev-prepare.mjs +++ b/scripts/dev-prepare.mjs @@ -36,12 +36,17 @@ function hostTriple() { let out; try { out = execFileSync("rustc", ["-vV"], { encoding: "utf8" }); - } catch { + } catch (err) { // Say what is wrong in one line. The cargo build behind this would fail anyway, // but a raw spawnSync ENOENT stack is a poor first impression on a clean clone. + // Only ENOENT means "not installed" — anything else is a broken toolchain, and + // telling that user to install Rust would send them the wrong way. console.error( - "dev:prepare: `rustc` is not on PATH. Install Rust (https://rustup.rs/) and reopen " + - "your shell — see the prerequisites in README.md.", + err?.code === "ENOENT" + ? "dev:prepare: `rustc` is not on PATH. Install Rust (https://rustup.rs/) and reopen " + + "your shell — see the prerequisites in README.md." + : `dev:prepare: \`rustc -vV\` failed: ${err?.message ?? err}. Your Rust install ` + + "looks broken — `rustup show` is a good first check.", ); process.exit(1); }