Skip to content
Open
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
12 changes: 9 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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

Expand Down
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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-<target-triple>`** — 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`).
Expand Down
9 changes: 7 additions & 2 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -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。
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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": {
Expand Down
82 changes: 82 additions & 0 deletions scripts/dev-prepare.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#!/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-<host-triple>` — 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 (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(
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);
}
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",
);
}
11 changes: 8 additions & 3 deletions src-tauri/src/web_server/router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,12 +139,17 @@ async fn dev_proxy(url: &str) -> Result<axum::response::Response, String> {
}

/// 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())
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
Loading