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
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@ change directly affects build, packaging, or CI cannot protect the path.
| Installer frontend or i18n runtime without packaging changes | `pnpm --dir BitFun-Installer run type-check` |
| Installer Tauri/Rust changes | `cargo check --manifest-path BitFun-Installer/src-tauri/Cargo.toml` |
| Installer packaging, payload, install/uninstall flow, or native bundling | `pnpm run installer:build` |
| Build scripts or prerequisite changes | `pnpm run check:build-prereqs`, plus `node --test scripts/check-build-prereqs.test.mjs` when the check logic changed |

## Agent-doc priority

Expand Down
24 changes: 24 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,30 @@ you intentionally use `pnpm run desktop:dev:raw`. In that case, run
`scripts/ci/setup-openssl-windows.ps1`, or set `OPENSSL_DIR` to a pre-built x64
OpenSSL directory and set `OPENSSL_STATIC=1`.

#### Build Prerequisites Check

When `cargo check --workspace`, `cargo check -p bitfun-desktop`, or pnpm build
commands fail with confusing errors (e.g., "resource path doesn't exist" or
sherpa-onnx download failures), run the preflight check to identify missing
prerequisites and get actionable fix commands:

```bash
pnpm run check:build-prereqs # check only
pnpm run check:build-prereqs -- --fix # attempt to fix missing prerequisites
```

The check detects:

- Missing `node_modules` (fix: `pnpm install`)
- Missing `src/mobile-web/dist` (fix: `pnpm run prepare:mobile-web` — the
bitfun-desktop Tauri build script references this directory as a resource,
so `cargo check -p bitfun-desktop` and `cargo check --workspace` fail
without it)
- Missing sherpa-onnx prebuilt libs (the sherpa-onnx-sys build script
downloads from GitHub at build time; if the download fails on poor
connectivity, set `SHERPA_ONNX_LIB_DIR` to the prebuilt lib directory
under `target/sherpa-onnx-prebuilt/` to use the local copy)

### Install dependencies

```bash
Expand Down
22 changes: 22 additions & 0 deletions CONTRIBUTING_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,28 @@ GitHub Actions 升级使用的是兼容 Node.js 24 的 action runtime,但项
时才需要手动处理。此时运行 `scripts/ci/setup-openssl-windows.ps1`,或将
`OPENSSL_DIR` 指向预编译的 x64 OpenSSL 目录,并设置 `OPENSSL_STATIC=1`。

#### 构建前置检查

当 `cargo check --workspace`、`cargo check -p bitfun-desktop` 或 pnpm 构建
命令报出难以理解的错误(如 "resource path doesn't exist" 或 sherpa-onnx
下载失败)时,运行前置检查以识别缺失的依赖并获取可操作的修复命令:

```bash
pnpm run check:build-prereqs # 仅检查
pnpm run check:build-prereqs -- --fix # 尝试自动修复缺失的前置依赖
```

检查项包括:

- 缺少 `node_modules`(修复:`pnpm install`)
- 缺少 `src/mobile-web/dist`(修复:`pnpm run prepare:mobile-web` —
bitfun-desktop 的 Tauri 构建脚本将该目录作为资源引用,缺失时
`cargo check -p bitfun-desktop` 和 `cargo check --workspace` 会失败)
- 缺少 sherpa-onnx 预编译库(sherpa-onnx-sys 构建脚本会在构建时从
GitHub 下载;若网络连通性差导致下载失败,设置
`SHERPA_ONNX_LIB_DIR` 指向 `target/sherpa-onnx-prebuilt/` 下的预编译
lib 目录以使用本地副本)

### 安装依赖

```bash
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"theme:color-audit:test": "node --test scripts/audit-theme-colors.test.mjs scripts/audit-cli-theme-colors.test.mjs",
"theme:visual-contract": "node scripts/validate-theme-visual-contract.mjs",
"check:repo-hygiene": "node scripts/check-repo-hygiene.mjs",
"check:build-prereqs": "node scripts/check-build-prereqs.mjs",
"check:core-boundaries": "node scripts/check-core-boundaries.mjs",
"check:core-boundaries:test": "node --test scripts/check-core-boundaries.test.mjs",
"check:github-config": "pnpm --dir src/web-ui exec node ../../scripts/check-github-config.mjs && node --test scripts/check-github-config.test.mjs",
Expand Down
170 changes: 170 additions & 0 deletions scripts/check-build-prereqs.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
#!/usr/bin/env node

/**
* Build prerequisite preflight check.
*
* Detects missing build prerequisites that cause confusing cargo/pnpm failures:
*
* - Root node_modules missing → pnpm scripts fail with "node_modules missing,
* did you mean to install?"
* - src/mobile-web/dist missing → cargo check -p bitfun-desktop and
* cargo check --workspace fail with "resource path '../../mobile-web/dist'
* doesn't exist" in the bitfun-desktop build script
* - sherpa-onnx prebuilt libs missing → sherpa-onnx-sys build script attempts
* a network download from GitHub that fails on poor connectivity
*
* Usage:
* node scripts/check-build-prereqs.mjs # check only
* node scripts/check-build-prereqs.mjs --fix # attempt to fix missing prereqs
*
* When cargo check or pnpm build fails with a confusing error about missing
* resources or sherpa-onnx download failures, run this check first.
*/

import { execFileSync } from 'node:child_process';
import { existsSync } from 'node:fs';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = dirname(fileURLToPath(import.meta.url));
const SCRIPT_DIR = __dirname;
const DEFAULT_ROOT = join(SCRIPT_DIR, '..');
const ROOT_DIR = process.env.BITFUN_BUILD_PREREQS_TEST_ROOT || DEFAULT_ROOT;
const FIX = process.argv.includes('--fix');

// --- Check logic (extracted for re-use and testing) ---

function runChecks(rootDir) {
const errors = [];
const warnings = [];

// --- Check 1: Root node_modules ---
if (!existsSync(join(rootDir, 'node_modules'))) {
errors.push({
name: 'root node_modules',
message: 'Root node_modules is missing. pnpm scripts will not work.',
fix: ['pnpm', 'install'],
});
}

// --- Check 2: mobile-web dist (required by bitfun-desktop build script) ---
if (!existsSync(join(rootDir, 'src', 'mobile-web', 'dist', 'index.html'))) {
errors.push({
name: 'mobile-web dist',
message:
"src/mobile-web/dist is missing. The bitfun-desktop Tauri build script references '../../mobile-web/dist' as a resource, so 'cargo check -p bitfun-desktop' and 'cargo check --workspace' will fail with \"resource path doesn't exist\".",
fix: ['pnpm', 'run', 'prepare:mobile-web'],
});
}

// --- Check 3: sherpa-onnx prebuilt libs ---
// sherpa-onnx-sys build.rs auto-detects target/sherpa-onnx-prebuilt/<version>/lib/
// and returns immediately without downloading. Only warn for the first-build
// scenario where no prebuilt cache exists yet.
const sherpaLibDirEnv = process.env.SHERPA_ONNX_LIB_DIR;
const sherpaPrebuiltDir = join(rootDir, 'target', 'sherpa-onnx-prebuilt');

if (sherpaLibDirEnv) {
if (!existsSync(sherpaLibDirEnv)) {
warnings.push({
name: 'sherpa-onnx env var',
message: `SHERPA_ONNX_LIB_DIR is set to "${sherpaLibDirEnv}" but the path does not exist.`,
});
}
} else if (!existsSync(sherpaPrebuiltDir)) {
warnings.push({
name: 'sherpa-onnx',
message:
'No prebuilt sherpa-onnx cache found. The first build will download from GitHub. If connectivity is poor and the download fails, you can set SHERPA_ONNX_ARCHIVE_DIR to a directory containing the pre-downloaded archive.',
});
}
// If prebuilt dir exists, build.rs auto-detects it — no warning needed.

return { errors, warnings };
}

function collectPendingFixes(errors) {
return errors
.filter((e) => e.fix)
.map((e) => ({ name: e.name, fix: e.fix }));
}

function reportResults({ errors, warnings }) {
if (errors.length > 0) {
console.error('Build prerequisite check failed:\n');
for (const e of errors) {
console.error(` [FAIL] ${e.name}: ${e.message}`);
if (e.fix) {
console.error(` Fix: ${e.fix.join(' ')}`);
}
}
console.error();
}

if (warnings.length > 0) {
console.warn('Build prerequisite warnings:\n');
for (const w of warnings) {
console.warn(` [WARN] ${w.name}: ${w.message}`);
}
console.warn();
}
}

function runFixes(pendingFixes, rootDir) {
let allSucceeded = true;
for (const { name, fix } of pendingFixes) {
const [cmd, ...args] = fix;
console.log(`$ ${fix.join(' ')}`);
try {
execFileSync(cmd, args, { stdio: 'inherit', cwd: rootDir });
} catch {
console.error(`Fix command failed: ${fix.join(' ')}\n`);
allSucceeded = false;
}
console.log();
}
return allSucceeded;
}

// --- Main ---

const firstResult = runChecks(ROOT_DIR);

if (firstResult.errors.length === 0 && firstResult.warnings.length === 0) {
console.log('Build prerequisite check passed.');
process.exit(0);
}

reportResults(firstResult);

if (firstResult.errors.length > 0) {
const pendingFixes = collectPendingFixes(firstResult.errors);

if (FIX && pendingFixes.length > 0) {
console.log('Attempting fixes...\n');
const allSucceeded = runFixes(pendingFixes, ROOT_DIR);

if (allSucceeded) {
console.log('Re-checking prerequisites...\n');
const secondResult = runChecks(ROOT_DIR);
reportResults(secondResult);

if (secondResult.errors.length === 0) {
console.log('All errors resolved after fix.');
process.exit(0);
}
console.error('Some errors remain after fix.');
process.exit(1);
}
console.error('Some fix attempts failed. See errors above.');
process.exit(1);
}

console.error(
'Run with --fix to attempt automatic fixes for missing prerequisites.',
);
process.exit(1);
}

// Only warnings, no errors
process.exit(0);
Loading