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
49 changes: 39 additions & 10 deletions .github/workflows/nodejs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ concurrency:
cancel-in-progress: true

jobs:
# Build the addon and prebuild assets with Bazel (the same path the npm
# release uses), then run the Node.js tests against the prebuild layout.
build-and-test:
permissions:
contents: read
Expand All @@ -27,12 +29,19 @@ jobs:
node-version: 24
- os: macos-15-intel
node-version: 24
- os: windows-2022
- os: windows-2025-vs2026
node-version: 24
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Best-effort Defender exclusions
if: runner.os == 'Windows'
shell: pwsh
continue-on-error: true
run: |
Add-MpPreference -ExclusionPath "$env:GITHUB_WORKSPACE" `
-ExclusionProcess "bazel.exe","bazelisk.exe","python.exe","cl.exe","link.exe"
- name: Compute version
id: version
shell: bash
Expand All @@ -45,17 +54,38 @@ jobs:
uses: actions/setup-node@v5
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm test
- name: Build Node.js prebuild
if: ${{ matrix.node-version == 24 }}
run: npm run prebuild
- uses: bazel-contrib/setup-bazel@0.19.0
with:
bazelisk-cache: true
repository-cache: true
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.12"
- name: Configure BuildBuddy
if: (github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository) && runner.os != 'Windows'
shell: bash
env:
BUILDBUDDY_API_KEY: ${{ secrets.BUILDBUDDY_API_KEY }}
run: |
if [ -n "${BUILDBUDDY_API_KEY}" ]; then
{
echo "build --config=buildbuddy"
echo "build --remote_header=x-buildbuddy-api-key=${BUILDBUDDY_API_KEY}"
echo "build --bes_header=x-buildbuddy-api-key=${BUILDBUDDY_API_KEY}"
} >> .bazelrc.user
echo "BuildBuddy enabled."
else
echo "BUILDBUDDY_API_KEY not set; skipping BuildBuddy config."
fi
- run: npm ci --omit=optional
- name: Build Node.js prebuild with Bazel
shell: bash
run: ./scripts/build-node-prebuild-bazel.sh
- name: Verify Node.js prebuild layout
if: ${{ matrix.node-version == 24 }}
run: |
node -e "const fs=require('fs'); const path=require('path'); const dirs=fs.readdirSync('prebuilds',{withFileTypes:true}).filter((entry)=>entry.isDirectory()).map((entry)=>entry.name); if(!dirs.includes('assets')) throw new Error('missing prebuilds/assets'); const platformDirs=dirs.filter((name)=>name!=='assets'); if(platformDirs.length!==1) throw new Error('expected one platform prebuild directory, found '+platformDirs.join(',')); const addon=path.join('prebuilds', platformDirs[0], 'opencc.node'); if(!fs.existsSync(addon)) throw new Error('missing '+addon); console.log('Verified '+addon+' with shared assets');"
- name: Test Node.js prebuild
if: ${{ matrix.node-version == 24 }}
- name: Test
env:
PREBUILDS_ONLY: 1
run: npm test
Expand All @@ -65,7 +95,6 @@ jobs:
with:
name: opencc-${{ env.VERSION }}-nodejs-${{ matrix.os }}-node-${{ matrix.node-version }}
path: |
node/build/**
prebuilds/**
**/npm-debug.log

Expand Down
12 changes: 8 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,17 +192,21 @@ ctest --test-dir build --output-on-failure

### Node.js 測試

Node.js binding 與 npm CLI 修改請執行:
Node.js binding 與 npm CLI 修改請執行(addon 以 Bazel 構建,源碼倉庫內
`npm install` 不會再觸發 node-gyp 編譯;如需舊的 node-gyp 構建,可用
`npm install --build-from-source`):

```bash
npm install
npm install --omit=optional
./scripts/build-node-prebuild-bazel.sh
npm test
```

驗證 npm prebuild 佈局時:
`build-node-prebuild-bazel.sh` 會產生 `prebuilds/<platform>-<arch>/opencc.node`
與共用資源 `prebuilds/assets/`,與 npm 發佈使用的佈局一致。如需確保測試只使用
prebuild 佈局(忽略本地 `build/Release`):

```bash
npm run prebuild
PREBUILDS_ONLY=1 npm test
```

Expand Down
3 changes: 1 addition & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,7 @@ benchmark:
(cd build/perf; ctest --verbose)

node:
node-gyp configure
node-gyp build
./scripts/build-node-prebuild-bazel.sh

node-test: node
npm test
Expand Down
5 changes: 4 additions & 1 deletion node/PUBLISHING.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,12 @@ Bazel:

```sh
npm install --omit=optional --ignore-scripts
./scripts/build-node-prebuild-bazel.sh "$(node -p 'process.platform + "-" + process.arch')"
./scripts/build-node-prebuild-bazel.sh
```

The script defaults to the host platform; pass an explicit target such as
`win32-x64` for the Bazel + Zig cross-compile path.

This creates:

```text
Expand Down
15 changes: 9 additions & 6 deletions node/opencc.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,15 @@ function resolveBindingPath() {
// sandbox), as a prebuild under prebuilds/<platform>-<arch>/, or via an
// @opencc/opencc-<platform>-<arch> scoped package. A direct filesystem lookup
// replaces node-gyp-build (OpenCC ships a single N-API build per platform).
const candidates = [
path.join(packageRoot, 'build', 'Release', 'opencc.node'),
path.join(
packageRoot, 'prebuilds', `${process.platform}-${process.arch}`, 'opencc.node'
),
];
// PREBUILDS_ONLY keeps its node-gyp-build meaning: skip the local
// build/Release directory so tests exercise the shipped prebuild layout.
const candidates = [];
if (!process.env.PREBUILDS_ONLY) {
candidates.push(path.join(packageRoot, 'build', 'Release', 'opencc.node'));
}
candidates.push(path.join(
packageRoot, 'prebuilds', `${process.platform}-${process.arch}`, 'opencc.node'
));
for (const candidate of candidates) {
if (fs.existsSync(candidate)) {
return candidate;
Expand Down
16 changes: 12 additions & 4 deletions scripts/build-node-prebuild-bazel.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#!/usr/bin/env bash
#
# Build opencc.node with Bazel and copy it to the npm prebuilds directory.
# Usage: ./scripts/build-node-prebuild-bazel.sh <target>
# Usage: ./scripts/build-node-prebuild-bazel.sh [<target>]
# With no argument, builds for the host platform.

set -euo pipefail

Expand All @@ -18,23 +19,30 @@ esac

usage() {
cat >&2 <<'EOF'
Usage: ./scripts/build-node-prebuild-bazel.sh <target>
Usage: ./scripts/build-node-prebuild-bazel.sh [<target>]

Supported targets:
darwin-arm64
darwin-x64
linux-arm64
linux-x64
win32-x64

With no argument, builds for the host platform.
EOF
}

if [[ $# -ne 1 ]]; then
if [[ $# -gt 1 ]]; then
usage
exit 2
fi

target="$1"
if [[ $# -eq 1 ]]; then
target="$1"
else
target="$(node -p "process.platform + '-' + process.arch")"
echo "No target specified; defaulting to host target $target."
fi
case "$target" in
darwin-arm64|darwin-x64|linux-arm64|linux-x64|win32-x64)
;;
Expand Down
25 changes: 24 additions & 1 deletion scripts/install.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,30 @@ const packageRoot = path.resolve(__dirname, '..');
const scopedPackageName = `@opencc/opencc-${process.platform}-${process.arch}`;
const isSourceCheckout = fs.existsSync(path.join(packageRoot, '.git'));

if (!isSourceCheckout) {
// In a source checkout, the native addon is built with Bazel, not node-gyp
// (see CONTRIBUTING.md), so the npm-install source build is skipped unless
// the standard npm escape hatch is set (`npm install --build-from-source`,
// or the npm_config_build_from_source environment variable, as used by the
// AppVeyor npm packaging job).
const buildFromSource = !['', 'false', '0'].includes(
String(process.env.npm_config_build_from_source || '').toLowerCase()
);
if (isSourceCheckout && !buildFromSource) {
console.log(
'opencc: source checkout detected; skipping the node-gyp install build.\n' +
'opencc: run ./scripts/build-node-prebuild-bazel.sh to build the addon ' +
'with Bazel before `npm test`,\n' +
'opencc: or re-run with `npm install --build-from-source` for the ' +
'node-gyp build.'
);
process.exit(0);
}

// An installed scoped binary package satisfies the install, but never in a
// source checkout (its published addon and dictionaries would shadow the
// checked-out sources) and never when build-from-source explicitly asks for
// a local compile.
if (!isSourceCheckout && !buildFromSource) {
try {
const scopedPackage = require(require.resolve(scopedPackageName, {
paths: [packageRoot],
Expand Down
2 changes: 1 addition & 1 deletion scripts/prepare-node-prebuild-artifacts.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ function prepareArtifacts(root = path.join(__dirname, '..')) {

if (jsonCount === 0 || dictionaryCount === 0) {
throw new Error(
'Could not prepare Node.js prebuild assets. Run node-gyp prebuild first, ' +
'Could not prepare Node.js prebuild assets. Run `npm run prebuild` first, ' +
'or run `bazel build -c opt //data/dictionary:binary_dictionaries` before this script.'
);
}
Expand Down
Loading