From 3d84479b8ea4db251ae5089a5c59c711e6f69dd0 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 15 Jul 2026 00:52:32 +0000 Subject: [PATCH 1/9] Remove the node-gyp source-build path from the npm package The opencc npm package now installs its native addon exclusively through the Bazel-built @opencc/opencc-- scoped packages. The binding.gyp source-build fallback, its gypi definitions, and the npm gyp dependency chain are removed; the tarball no longer ships C++ sources and shrinks from several megabytes to ~600 KB. Platforms without a scoped binary package are no longer supported by npm install. Detailed Changes: - **package.json / package-lock.json**: - Drop the install and prebuild scripts and the node-gyp, node-gyp-build, node-addon-api, and prebuildify dependencies (node-addon-api comes from the Bazel Central Registry for the Bazel build). - Prune the files whitelist to the JavaScript API, CLI, typings, prebuilds/assets, and the two publish verification scripts. - Drop the overrides block; the overridden packages left the tree with the gyp dependency chain. - **Deleted files**: - binding.gyp, node/global.gypi, node/configs.gypi, node/dicts.gypi, node/node_opencc.gypi, node/dict.js, scripts/install.js. - **scripts/verify-node-main-package.js**: - Replace the gyp source and #include closure walk with a static required-files check against the files whitelist. - **.github/workflows/nodejs.yml**: - Replace the pack-source-install job with pack-install: pack the main tarball plus the Bazel-built scoped binary package for the host platform, install both, and smoke test. - **.appveyor.yml**: - The npm packaging job builds the win32-x64 prebuild with Bazel (bazelisk via choco) on a VS2022 worker instead of the node-gyp install build and prebuildify, and tests with PREBUILDS_ONLY=1. - **CMakeLists.txt**: - Drop the /binding.gyp source-package exclusion. - **Documentation**: - Update README.md, node/README.md, node/README.zh.md, node/PUBLISHING.md, CONTRIBUTING.md, and AGENTS.md to describe the prebuilt-only install model; record the change in NEWS.md; refresh comments in node/opencc.js, node/BUILD.bazel, node/test/BUILD.bazel, and node/test/test_entry.js that referenced node-gyp-build. --- .appveyor.yml | 16 +- .github/workflows/nodejs.yml | 60 ++- AGENTS.md | 4 +- CMakeLists.txt | 1 - CONTRIBUTING.md | 4 +- NEWS.md | 5 + README.md | 7 +- binding.gyp | 8 - node/BUILD.bazel | 3 +- node/PUBLISHING.md | 47 +- node/README.md | 8 +- node/README.zh.md | 7 +- node/configs.gypi | 27 -- node/dict.js | 6 - node/dicts.gypi | 267 ----------- node/global.gypi | 50 -- node/node_opencc.gypi | 59 --- node/opencc.js | 13 +- node/test/BUILD.bazel | 7 +- node/test/test_entry.js | 2 +- package-lock.json | 516 +-------------------- package.json | 105 +---- scripts/install.js | 55 --- scripts/prepare-node-prebuild-artifacts.js | 4 +- scripts/verify-node-main-package.js | 85 +--- 25 files changed, 140 insertions(+), 1226 deletions(-) delete mode 100644 binding.gyp delete mode 100644 node/configs.gypi delete mode 100644 node/dict.js delete mode 100644 node/dicts.gypi delete mode 100644 node/global.gypi delete mode 100644 node/node_opencc.gypi delete mode 100644 scripts/install.js diff --git a/.appveyor.yml b/.appveyor.yml index b27085de3..c2ee31cf2 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -13,8 +13,11 @@ init: environment: matrix: - # Build Node.js + # Build Node.js. The native addon is built with Bazel (there is no + # node-gyp source build anymore); use a VS2022 worker for the Bazel + # MSVC toolchain. - nodejs_version: stable + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022 # Build plain C++ - nodejs_version: none @@ -32,15 +35,16 @@ for: install: - ps: Install-Product node $env:nodejs_version $env:platform - npm -g i npm@latest + - appveyor-retry choco install bazelisk -y --no-progress build_script: - - set npm_config_build_from_source=true&& appveyor-retry call npm install + - appveyor-retry call npm install --omit=optional + - echo [opencc npm package] building opencc win32-x64 prebuild with Bazel + - bash scripts/build-node-prebuild-bazel.sh win32-x64 test_script: - - npm test + - set PREBUILDS_ONLY=1&& npm test after_test: - chcp 65001 - - echo [opencc npm package] building opencc win32-x64 prebuild - - if exist prebuilds rmdir /S /Q prebuilds - - npm run prebuild + - echo [opencc npm package] verifying opencc win32-x64 prebuild layout - if exist prebuilds\darwin-arm64 exit /B 1 - if exist prebuilds\linux-x64 exit /B 1 - if exist prebuilds\linux-arm64 exit /B 1 diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index aaeff9ee9..9ef9f861e 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -98,11 +98,13 @@ jobs: prebuilds/** **/npm-debug.log - # Install the packed npm tarball without prebuilt binaries, so the - # node-gyp source build path is exercised exactly as it runs on platforms - # without a scoped binary package (issue #1409). - pack-source-install: - name: Source build from npm tarball (${{ matrix.os }}) + # Pack the main npm tarball plus the Bazel-built scoped binary package for + # the host platform, install both together, and smoke test — mirroring how + # a release install resolves the native addon. There is no source-build + # fallback anymore: platforms without a scoped binary package are not + # supported by npm install. + pack-install: + name: Install packed npm tarballs (${{ matrix.os }}) permissions: contents: read runs-on: ${{ matrix.os }} @@ -119,28 +121,58 @@ jobs: uses: actions/setup-node@v5 with: node-version: 24 - - run: npm ci - - name: Build prebuild assets - run: npm run prebuild - - name: Pack npm tarball + - 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 + 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: Pack npm tarballs shell: bash run: | + npm run prepare:scoped-packages + target="$(node -p "process.platform + '-' + process.arch")" mkdir -p dist/npm-artifacts npm pack --pack-destination dist/npm-artifacts - - name: Install tarball from source and smoke test + npm pack "dist/scoped-packages/@opencc/opencc-${target}" \ + --pack-destination dist/npm-artifacts + - name: Install tarballs and smoke test shell: bash run: | - tarball="$(ls "$PWD"/dist/npm-artifacts/opencc-*.tgz)" + main_tarball="$(ls "$PWD"/dist/npm-artifacts/opencc-[0-9]*.tgz)" + scoped_tarball="$(ls "$PWD"/dist/npm-artifacts/opencc-opencc-*.tgz)" install_dir="$(mktemp -d)" cd "$install_dir" npm init -y - npm install --omit=optional --foreground-scripts "$tarball" - test -f node_modules/opencc/build/Release/opencc.node + npm install --omit=optional --foreground-scripts \ + "$main_tarball" "$scoped_tarball" node -e ' const assert = require("assert"); const OpenCC = require("opencc"); const converter = new OpenCC("s2t.json"); const converted = converter.convertSync("汉字"); assert.strictEqual(converted, "漢字"); - console.log("Source-built opencc converts 汉字 ->", converted); + console.log("Packed opencc converts 汉字 ->", converted); ' diff --git a/AGENTS.md b/AGENTS.md index a2e4b749b..8c5a8648e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -28,10 +28,10 @@ This document compiles the Open Chinese Convert (OpenCC) project information to - The command-line tool `opencc_dict` supports `text ↔ ocd2` (and optionally `ocd`) conversion. When adding or adjusting dictionaries, first edit `.txt`, then run the tool to generate the target format. ## Development and Testing -- The top-level build system supports CMake, Bazel, Node.js `binding.gyp`, Python `pyproject.toml`, with cross-platform CI integration. +- The top-level build system supports CMake, Bazel, Python `pyproject.toml`, with cross-platform CI integration. The Node.js native addon is built with Bazel (`//node:opencc`). - `src/*Test.cpp`, `data/config/*Test.cpp`, `plugins/jieba/tests/`, `test/`, and `test/golden/` contain tests covering dictionary matching, conversion chains, configuration validation, plugin segmentation, CLI behavior, and golden conversion outputs. - Tools `opencc_dict`, `opencc_phrase_extract` (`src/tools/`) help developers convert dictionary formats and extract phrases. -- Node.js tests live in `node/test.js`. Official npm prebuilt binaries are built with Bazel via `scripts/build-node-prebuild-bazel.sh` (see `node/PUBLISHING.md`); `npm run prebuild` (prebuildify/node-gyp) is no longer the release path, and the `binding.gyp` source fallback is planned for removal. +- Node.js tests live in `node/test.js`. npm prebuilt binaries are built with Bazel via `scripts/build-node-prebuild-bazel.sh` (see `node/PUBLISHING.md`). The `binding.gyp`/node-gyp source-build fallback has been removed: the native addon is installed exclusively through `@opencc/opencc--` scoped packages, and platforms without one are not supported by `npm install`. ### C++ ABI Versioning - When a change introduces an ABI-incompatible modification to the public C++ interface, bump `OPENCC_ABI_VERSION` in `CMakeLists.txt` so downstream libraries and applications relink instead of silently loading an incompatible `libopencc` shared library. diff --git a/CMakeLists.txt b/CMakeLists.txt index 95b6f62b9..ae5573caa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -118,7 +118,6 @@ set(_OPENCC_TRIMMED_SOURCE_PACKAGE_IGNORE_FILES "/Makefile" "/OpenCC.egg-info/" "/PUBLICATIONS.md" - "/binding.gyp" "/build.cmd" "/data/icon/" "/deps/google-benchmark/" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0a1a86812..d87cc84e8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -192,9 +192,7 @@ ctest --test-dir build --output-on-failure ### Node.js 測試 -Node.js binding 與 npm CLI 修改請執行(addon 以 Bazel 構建,源碼倉庫內 -`npm install` 不會再觸發 node-gyp 編譯;如需舊的 node-gyp 構建,可用 -`npm install --build-from-source`): +Node.js binding 與 npm CLI 修改請執行(addon 以 Bazel 構建): ```bash npm install --omit=optional diff --git a/NEWS.md b/NEWS.md index b5a0fcea5..9d4fbed63 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,10 @@ # Change History of OpenCC +## Unreleased + +* **Node.js**: + * 移除 `binding.gyp` / node-gyp 源碼編譯 fallback:`opencc` npm 套件的原生 addon 現在只透過 `@opencc/opencc--` 預編譯 scoped 套件安裝(涵蓋 macOS x64/arm64、Linux x64/arm64、Windows x64,均以 Bazel 構建)。沒有預編譯二進位套件的平台不再支援 `npm install`。npm tarball 隨之不再攜帶 C++ 源碼、`deps/` 與字典源檔,體積大幅縮小;`node-gyp`、`node-gyp-build`、`node-addon-api`、`prebuildify` 依賴與 `install` / `prebuild` npm scripts 一併移除。 + ## Version 1.4.1 2026年7月12日 diff --git a/README.md b/README.md index 8f03c0a32..820c28a5d 100644 --- a/README.md +++ b/README.md @@ -78,9 +78,10 @@ https://opencc.js.org/converter?config=s2t `npm install opencc` -The npm package supports Node.js `>=20.17`. It uses bundled Node-API -prebuilds when available and falls back to a local `node-gyp` build when the -current platform does not have a matching prebuild. +The npm package supports Node.js `>=20.17`. The native addon is installed +through prebuilt `@opencc/opencc--` packages covering macOS +(x64/arm64), Linux (x64/arm64), and Windows (x64). Platforms without a +prebuilt binary package are not supported by `npm install`. Bun and Deno can also use the npm package through their npm compatibility support. diff --git a/binding.gyp b/binding.gyp deleted file mode 100644 index ccc7a0fa6..000000000 --- a/binding.gyp +++ /dev/null @@ -1,8 +0,0 @@ -{ - "includes": [ - "node/global.gypi", - "node/configs.gypi", - "node/dicts.gypi", - "node/node_opencc.gypi", - ] -} diff --git a/node/BUILD.bazel b/node/BUILD.bazel index 1c0806461..ade6c346d 100644 --- a/node/BUILD.bazel +++ b/node/BUILD.bazel @@ -37,8 +37,7 @@ cc_header_pick( # Node C headers itself (the rules_nodejs toolchain on Unix; the windows_node_api # import library on Windows). # -# node-gyp and Bazel both compile the same binding source. node-gyp lists the -# OpenCC and marisa sources directly, while Bazel links against //src:opencc_lib. +# The binding source is compiled here and linked against //src:opencc_lib. node_addon( name = "opencc", srcs = ["opencc.cc"], diff --git a/node/PUBLISHING.md b/node/PUBLISHING.md index a67c5cf31..2937d4187 100644 --- a/node/PUBLISHING.md +++ b/node/PUBLISHING.md @@ -5,14 +5,14 @@ family. The current npm layout is split by platform: -- `opencc` contains the JavaScript API, CLI, TypeScript declarations, source - fallback, and runtime dictionary/config assets. +- `opencc` contains the JavaScript API, CLI, TypeScript declarations, and + runtime dictionary/config assets. - `@opencc/opencc--` contains exactly one native `opencc.node` binary for one platform. -The split-package layout is experimental. It keeps the main npm package smaller -while preserving source-build fallback for platforms without a scoped binary -package. +The native addon comes exclusively from the scoped binary packages. There is +no source-build fallback: platforms without a scoped binary package are not +supported by `npm install`. ## Package names @@ -34,8 +34,8 @@ binary refresh, publish all scoped packages at the same version as `opencc`. - npm account access to publish `opencc`. - npm organization access to publish public packages under `@opencc`. -- Node.js, npm, Python, and a native C++ build toolchain for the target - platform. +- Node.js, npm, Python, Bazel (bazelisk), and a native C++ build toolchain + for the target platform. - GitHub Actions or another set of machines covering each supported target platform. @@ -64,11 +64,10 @@ prebuilds/assets/*.ocd2 prebuilds/-/opencc.node ``` -The release workflow runs the Bazel build script separately on the runner for -each scoped binary target. The main `opencc` package still keeps `binding.gyp` -and source files so installs on platforms without a scoped binary package can -fall back to `node-gyp-build`. This fallback is planned for removal in a -future release, after which all Node builds will use Bazel exclusively. +The release workflow (`release-npm-binaries`) runs the Bazel build script +separately on the runner for each scoped binary target. All Node builds use +Bazel exclusively; the former `binding.gyp`/node-gyp source-build fallback has +been removed. ## Prepare scoped binary packages @@ -105,8 +104,9 @@ Dry-run the main package: npm pack --dry-run ``` -The main package tarball should contain `node/`, `src/`, `data/`, source build -files, and `prebuilds/assets/`, but not `prebuilds//opencc.node`. +The main package tarball should contain the JavaScript API and CLI under +`node/`, the TypeScript declarations, and `prebuilds/assets/`, but no C++ +sources and not `prebuilds//opencc.node`. Dry-run each scoped package: @@ -167,15 +167,18 @@ node -e "const OpenCC = require('opencc'); const cc = new OpenCC('s2twp'); conso The install should pull exactly one matching `@opencc/opencc--` optional package for the current machine. If no scoped binary package is -available, the install script falls back to `node-gyp-build`. +available, requiring `opencc` fails with an error explaining where the addon +was looked up. -## Future CI shape +## CI shape -The intended CI release flow is: +The CI release flow is: -1. Build each scoped binary package on its matching platform or runner. -2. Upload scoped package artifacts. -3. Publish all scoped binary packages with the chosen version and dist-tag. -4. Publish the main `opencc` package after the scoped packages exist. +1. `release-npm-binaries` (workflow_dispatch) builds each scoped binary + package on its matching platform or runner and publishes them. +2. Pushing a `ver.*` tag creates the draft GitHub release. +3. Publishing the release triggers `release-npm`, which packs and publishes + the main `opencc` package after verifying the scoped packages exist. -Until that CI flow exists, the local flow above is the source of truth. +The local flow above remains available for building and publishing a single +platform by hand. diff --git a/node/README.md b/node/README.md index 3e71c6b5e..1ee636e42 100644 --- a/node/README.md +++ b/node/README.md @@ -12,10 +12,10 @@ npm install opencc The package requires Node.js `>=20.17`. -OpenCC ships native bindings. When a matching prebuilt binary is available, -installation uses it automatically. Otherwise npm builds the native addon from -source, which requires a working C++ toolchain and Python supported by -`node-gyp`. +OpenCC ships native bindings as prebuilt binaries. Installation automatically +selects the matching `@opencc/opencc--` package, covering +macOS (x64/arm64), Linux (x64/arm64), and Windows (x64). Platforms without a +prebuilt binary package are not supported by `npm install`. ## Basic Usage diff --git a/node/README.zh.md b/node/README.zh.md index e80e83c30..25aec4a85 100644 --- a/node/README.zh.md +++ b/node/README.zh.md @@ -11,9 +11,10 @@ npm install opencc 本套件需要 Node.js `>=20.17`。 -OpenCC 會隨套件提供原生綁定。當有符合目前平台的預編譯二進位檔時, -安裝時會自動使用;否則 npm 會從原始碼建置原生 addon,這需要可用的 -C++ 工具鏈,以及 `node-gyp` 支援的 Python。 +OpenCC 以預編譯二進位檔提供原生綁定。安裝時會自動選取符合目前平台的 +`@opencc/opencc--` 套件,涵蓋 macOS(x64/arm64)、 +Linux(x64/arm64)與 Windows(x64)。沒有預編譯二進位套件的平台 +不支援 `npm install`。 ## 基本用法 diff --git a/node/configs.gypi b/node/configs.gypi deleted file mode 100644 index b36b69d4f..000000000 --- a/node/configs.gypi +++ /dev/null @@ -1,27 +0,0 @@ -{ - "targets": [{ - "target_name": "configs", - "type": "none", - "copies": [{ - "destination": "<(PRODUCT_DIR)", - "files": [ - "../data/config/hk2s.json", - "../data/config/hk2sp.json", - "../data/config/hk2t.json", - "../data/config/jp2t.json", - "../data/config/s2hk.json", - "../data/config/s2hkp.json", - "../data/config/s2t.json", - "../data/config/s2tw.json", - "../data/config/s2twp.json", - "../data/config/t2hk.json", - "../data/config/t2jp.json", - "../data/config/t2s.json", - "../data/config/t2tw.json", - "../data/config/tw2s.json", - "../data/config/tw2sp.json", - "../data/config/tw2t.json", - ] - }] - }] -} diff --git a/node/dict.js b/node/dict.js deleted file mode 100644 index ef29d7ca3..000000000 --- a/node/dict.js +++ /dev/null @@ -1,6 +0,0 @@ -const OpenCC = require('./opencc'); - -const input = process.argv[2]; -const output = process.argv[3]; - -OpenCC.generateDict(input, output, "text", "ocd2"); diff --git a/node/dicts.gypi b/node/dicts.gypi deleted file mode 100644 index c76e31f42..000000000 --- a/node/dicts.gypi +++ /dev/null @@ -1,267 +0,0 @@ -{ - "targets": [{ - "target_name": "dicts", - "type": "none", - "variables": { - "cmd": "<(module_root_dir)/node/dict.js", - "dict_extract_tofu_risk": "<(module_root_dir)/data/scripts/extract_tofu_risk.py", - "dict_generate_st_phrases": "<(module_root_dir)/data/scripts/generate_st_phrases_from_regional_phrases.py", - "dict_reverse": "<(module_root_dir)/data/scripts/reverse.py", - "input_prefix": "<(module_root_dir)/data/dictionary/", - "output_prefix": "<(PRODUCT_DIR)/", - "python_cmd": "python3" - }, - "conditions": [ - [ - "OS==\"win\"", - { - "variables": { - "python_cmd": "python.exe" - } - } - ] - ], - "actions": [{ - "action_name": "STCharacters", - "variables": { - "input": "<(input_prefix)STCharacters.txt", - }, - "inputs": ["<(input)"], - "outputs": ["<(output_prefix)STCharacters.ocd2"], - "action": ["node", "<(cmd)", "<(input)", "<@(_outputs)"] - }, { - "action_name": "STPhrases", - "variables": { - "input": "<(input_prefix)STPhrases.txt", - }, - "inputs": ["<(input)"], - "outputs": ["<(output_prefix)STPhrases.ocd2"], - "action": ["node", "<(cmd)", "<(input)", "<@(_outputs)"] - }, { - "action_name": "TSCharacters", - "variables": { - "input": "<(input_prefix)TSCharacters.txt", - }, - "inputs": ["<(input)"], - "outputs": ["<(output_prefix)TSCharacters.ocd2"], - "action": ["node", "<(cmd)", "<(input)", "<@(_outputs)"] - }, { - "action_name": "TSCharactersExt.txt", - "variables": { - "input": "<(input_prefix)TSCharacters.txt", - }, - "inputs": ["<(input)"], - "outputs": ["<(output_prefix)TSCharactersExt.txt"], - "action": ["<(python_cmd)", "<(dict_extract_tofu_risk)", "<(input)", "<@(_outputs)"] - }, { - "action_name": "TSCharactersExt", - "variables": { - "input": "<(output_prefix)TSCharactersExt.txt", - }, - "inputs": ["<(input)"], - "outputs": ["<(output_prefix)TSCharactersExt.ocd2"], - "action": ["node", "<(cmd)", "<(input)", "<@(_outputs)"] - }, { - "action_name": "TSPhrases", - "variables": { - "input": "<(input_prefix)TSPhrases.txt", - }, - "inputs": ["<(input)"], - "outputs": ["<(output_prefix)TSPhrases.ocd2"], - "action": ["node", "<(cmd)", "<(input)", "<@(_outputs)"] - }, { - "action_name": "CJK_Compatibility_Ideographs", - "variables": { - "input": "<(input_prefix)CJK_Compatibility_Ideographs.txt", - }, - "inputs": ["<(input)"], - "outputs": ["<(output_prefix)CJK_Compatibility_Ideographs.ocd2"], - "action": ["node", "<(cmd)", "<(input)", "<@(_outputs)"] - }, { - "action_name": "STPhrases_GeneratedFromRegionalPhrases.txt", - "inputs": [ - "<(dict_generate_st_phrases)", - "<(input_prefix)HKPhrases.txt", - "<(input_prefix)TWPhrases.txt", - "<(module_root_dir)/data/config/t2s.json", - "<(output_prefix)CJK_Compatibility_Ideographs.ocd2", - "<(output_prefix)TSCharacters.ocd2", - "<(output_prefix)TSCharactersExt.ocd2", - "<(output_prefix)TSPhrases.ocd2", - "<(output_prefix)opencc.node" - ], - "outputs": ["<(output_prefix)STPhrases_GeneratedFromRegionalPhrases.txt"], - "action": [ - "<(python_cmd)", - "<(dict_generate_st_phrases)", - "--input", - "<(input_prefix)HKPhrases.txt", - "--input", - "<(input_prefix)TWPhrases.txt", - "--output", - "<@(_outputs)", - "--node-binding", - "<(output_prefix)opencc.node", - "--config", - "<(module_root_dir)/data/config/t2s.json", - "--dict-dir", - "<(PRODUCT_DIR)" - ] - }, { - "action_name": "STPhrases_GeneratedFromRegionalPhrases", - "variables": { - "input": "<(output_prefix)STPhrases_GeneratedFromRegionalPhrases.txt", - }, - "inputs": ["<(input)"], - "outputs": ["<(output_prefix)STPhrases_GeneratedFromRegionalPhrases.ocd2"], - "action": ["node", "<(cmd)", "<(input)", "<@(_outputs)"] - }, { - "action_name": "TWVariantsPhrases", - "variables": { - "input": "<(input_prefix)TWVariantsPhrases.txt", - }, - "inputs": ["<(input)"], - "outputs": ["<(output_prefix)TWVariantsPhrases.ocd2"], - "action": ["node", "<(cmd)", "<(input)", "<@(_outputs)"] - }, { - "action_name": "TWVariants", - "variables": { - "input": "<(input_prefix)TWVariants.txt", - }, - "inputs": ["<(input)"], - "outputs": ["<(output_prefix)TWVariants.ocd2"], - "action": ["node", "<(cmd)", "<(input)", "<@(_outputs)"] - }, { - "action_name": "TWVariantsRevPhrases", - "variables": { - "input": "<(input_prefix)TWVariantsRevPhrases.txt", - }, - "inputs": ["<(input)"], - "outputs": ["<(output_prefix)TWVariantsRevPhrases.ocd2"], - "action": ["node", "<(cmd)", "<(input)", "<@(_outputs)"] - }, { - "action_name": "JPShinjitaiCharactersRev.txt", - "variables": { - "input": "<(input_prefix)JPShinjitaiCharacters.txt", - }, - "inputs": ["<(input)"], - "outputs": ["<(output_prefix)JPShinjitaiCharactersRev.txt"], - "action": ["<(python_cmd)", "<(dict_reverse)", "<(input)", "<@(_outputs)"] - }, { - "action_name": "JPShinjitaiCharactersRev", - "variables": { - "input": "<(output_prefix)JPShinjitaiCharactersRev.txt", - }, - "inputs": ["<(input)"], - "outputs": ["<(output_prefix)JPShinjitaiCharactersRev.ocd2"], - "action": ["node", "<(cmd)", "<(input)", "<@(_outputs)"] - }, { - "action_name": "TWVariantsRev.txt", - "variables": { - "input": "<(input_prefix)TWVariants.txt", - }, - "inputs": ["<(input)"], - "outputs": ["<(output_prefix)TWVariantsRev.txt"], - "action": ["<(python_cmd)", "<(dict_reverse)", "<(input)", "<@(_outputs)"] - }, { - "action_name": "TWPhrases", - "variables": { - "input": "<(input_prefix)TWPhrases.txt", - }, - "inputs": ["<(input)"], - "outputs": ["<(output_prefix)TWPhrases.ocd2"], - "action": ["node", "<(cmd)", "<(input)", "<@(_outputs)"] - }, { - "action_name": "TWVariantsRev", - "variables": { - "input": "<(output_prefix)TWVariantsRev.txt", - }, - "inputs": ["<(input)"], - "outputs": ["<(output_prefix)TWVariantsRev.ocd2"], - "action": ["node", "<(cmd)", "<(input)", "<@(_outputs)"] - }, { - "action_name": "TWPhrasesRev", - "variables": { - "input": "<(input_prefix)TWPhrasesRev.txt", - }, - "inputs": ["<(input)"], - "outputs": ["<(output_prefix)TWPhrasesRev.ocd2"], - "action": ["node", "<(cmd)", "<(input)", "<@(_outputs)"] - }, { - "action_name": "HKVariantsPhrases", - "variables": { - "input": "<(input_prefix)HKVariantsPhrases.txt", - }, - "inputs": ["<(input)"], - "outputs": ["<(output_prefix)HKVariantsPhrases.ocd2"], - "action": ["node", "<(cmd)", "<(input)", "<@(_outputs)"] - }, { - "action_name": "HKVariants", - "variables": { - "input": "<(input_prefix)HKVariants.txt", - }, - "inputs": ["<(input)"], - "outputs": ["<(output_prefix)HKVariants.ocd2"], - "action": ["node", "<(cmd)", "<(input)", "<@(_outputs)"] - }, { - "action_name": "HKVariantsRevPhrases", - "variables": { - "input": "<(input_prefix)HKVariantsRevPhrases.txt", - }, - "inputs": ["<(input)"], - "outputs": ["<(output_prefix)HKVariantsRevPhrases.ocd2"], - "action": ["node", "<(cmd)", "<(input)", "<@(_outputs)"] - }, { - "action_name": "HKPhrases", - "variables": { - "input": "<(input_prefix)HKPhrases.txt", - }, - "inputs": ["<(input)"], - "outputs": ["<(output_prefix)HKPhrases.ocd2"], - "action": ["node", "<(cmd)", "<(input)", "<@(_outputs)"] - }, { - "action_name": "HKVariantsRev.txt", - "variables": { - "input": "<(input_prefix)HKVariants.txt", - }, - "inputs": ["<(input)"], - "outputs": ["<(output_prefix)HKVariantsRev.txt"], - "action": ["<(python_cmd)", "<(dict_reverse)", "<(input)", "<@(_outputs)"] - }, { - "action_name": "HKVariantsRev", - "variables": { - "input": "<(output_prefix)HKVariantsRev.txt", - }, - "inputs": ["<(input)"], - "outputs": ["<(output_prefix)HKVariantsRev.ocd2"], - "action": ["node", "<(cmd)", "<(input)", "<@(_outputs)"] - }, { - "action_name": "HKPhrasesRev", - "variables": { - "input": "<(input_prefix)HKPhrasesRev.txt", - }, - "inputs": ["<(input)"], - "outputs": ["<(output_prefix)HKPhrasesRev.ocd2"], - "action": ["node", "<(cmd)", "<(input)", "<@(_outputs)"] - }, { - "action_name": "JPShinjitaiCharacters", - "variables": { - "input": "<(input_prefix)JPShinjitaiCharacters.txt", - }, - "inputs": ["<(input)"], - "outputs": ["<(output_prefix)JPShinjitaiCharacters.ocd2"], - "action": ["node", "<(cmd)", "<(input)", "<@(_outputs)"] - }, { - "action_name": "JPShinjitaiPhrases", - "variables": { - "input": "<(input_prefix)JPShinjitaiPhrases.txt", - }, - "inputs": ["<(input)"], - "outputs": ["<(output_prefix)JPShinjitaiPhrases.ocd2"], - "action": ["node", "<(cmd)", "<(input)", "<@(_outputs)"] - }], - "dependencies": [ - "opencc" - ] - }] -} diff --git a/node/global.gypi b/node/global.gypi deleted file mode 100644 index 98b91b6d2..000000000 --- a/node/global.gypi +++ /dev/null @@ -1,50 +0,0 @@ -{ - "variables": { - "opencc_version": "1.4.1" - }, - "target_defaults": { - "defines": [ - "OPENCC_VERSION=\"<(opencc_version)\"" - ], - "conditions": [ - [ - "OS==\"linux\"", - { - "cflags": [ - "-std=c++20" - ], - "cflags!": [ - "-fno-exceptions" - ], - "cflags_cc!": [ - "-fno-exceptions" - ] - } - ], - [ - "OS==\"mac\"", - { - "xcode_settings": { - "GCC_ENABLE_CPP_EXCEPTIONS": "YES", - "MACOSX_DEPLOYMENT_TARGET": "10.7", - "OTHER_CPLUSPLUSFLAGS": [ - "-std=c++20", - "-stdlib=libc++" - ], - "OTHER_LDFLAGS": [ - "-stdlib=libc++" - ] - } - } - ], - [ - "OS==\"win\"", - { - "defines": [ - "Opencc_BUILT_AS_STATIC" - ] - } - ] - ] - } -} diff --git a/node/node_opencc.gypi b/node/node_opencc.gypi deleted file mode 100644 index ba64e3132..000000000 --- a/node/node_opencc.gypi +++ /dev/null @@ -1,59 +0,0 @@ -{ - "targets": [ - { - "target_name": "opencc", - "sources": [ - "../node/opencc.cc", - "../src/BinaryDict.cpp", - "../src/Config.cpp", - "../src/Conversion.cpp", - "../src/ConversionChain.cpp", - "../src/Converter.cpp", - "../src/DartsDict.cpp", - "../src/Dict.cpp", - "../src/DictConverter.cpp", - "../src/DictEntry.cpp", - "../src/DictGroup.cpp", - "../src/Lexicon.cpp", - "../src/MarisaDict.cpp", - "../src/MaxMatchSegmentation.cpp", - "../src/PipelineConverter.cpp", - "../src/PluginSegmentation.cpp", - "../src/PrefixMatch.cpp", - "../src/ResourceProvider.cpp", - "../src/Segmentation.cpp", - "../src/SerializableDict.cpp", - "../src/SerializedValues.cpp", - "../src/SingleStageConverter.cpp", - "../src/TextDict.cpp", - "../src/UTF8Util.cpp", - "../deps/marisa-0.3.1/lib/marisa/agent.cc", - "../deps/marisa-0.3.1/lib/marisa/grimoire/io/mapper.cc", - "../deps/marisa-0.3.1/lib/marisa/grimoire/io/reader.cc", - "../deps/marisa-0.3.1/lib/marisa/grimoire/io/writer.cc", - "../deps/marisa-0.3.1/lib/marisa/grimoire/trie/louds-trie.cc", - "../deps/marisa-0.3.1/lib/marisa/grimoire/trie/tail.cc", - "../deps/marisa-0.3.1/lib/marisa/grimoire/vector/bit-vector.cc", - "../deps/marisa-0.3.1/lib/marisa/keyset.cc", - "../deps/marisa-0.3.1/lib/marisa/trie.cc" - ], - "include_dirs": [ - "..", - "../node", - "../src", - "../deps/rapidjson-1.1.0", - "../deps/darts-clone-0.32h/include", - "../deps/marisa-0.3.1/include", - "../deps/marisa-0.3.1/lib", - "-/, or via an - // @opencc/opencc-- scoped package. A direct filesystem lookup - // replaces node-gyp-build (OpenCC ships a single N-API build per platform). - // PREBUILDS_ONLY keeps its node-gyp-build meaning: skip the local - // build/Release directory so tests exercise the shipped prebuild layout. + // (build/Release, used by the Bazel test sandbox and local development), as + // a prebuild under prebuilds/-/, or via an + // @opencc/opencc-- scoped package (OpenCC ships a single + // N-API build per platform). PREBUILDS_ONLY skips 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')); @@ -342,7 +341,7 @@ OpenCC._parseJSON = parseJSON; // Resolved native addon path and its adjacent assets (config/dictionary) // directory, exported so tooling/tests can locate them without re-deriving the -// lookup (and without depending on node-gyp-build). +// lookup. OpenCC._bindingPath = bindingPath; OpenCC._assetsPath = assetsPath; diff --git a/node/test/BUILD.bazel b/node/test/BUILD.bazel index 55a6ae924..115e219f6 100644 --- a/node/test/BUILD.bazel +++ b/node/test/BUILD.bazel @@ -7,8 +7,8 @@ package(default_visibility = ["//visibility:public"]) # computes its package root from its own location and looks for the addon under # /build/Release, so the addon, its JS API, the config/dictionary # assets, and the testcases are assembled into one tree here. The addon is -# located by a plain filesystem lookup (no node-gyp-build), so the test needs no -# npm packages at all. +# located by a plain filesystem lookup, so the test needs no npm packages at +# all. copy_to_directory( name = "node_test_release", srcs = [ @@ -34,7 +34,8 @@ js_library( # Run node/test.js with Node's built-in test runner against the Bazel-built # addon. node:test ships with the Node runtime and opencc.js locates the addon -# without node-gyp-build, so the test has no npm dependencies (no pnpm-lock). +# with a plain filesystem lookup, so the test has no npm dependencies (no +# pnpm-lock). js_test( name = "node_test", size = "medium", diff --git a/node/test/test_entry.js b/node/test/test_entry.js index 9f2cb5f69..4c10f203d 100644 --- a/node/test/test_entry.js +++ b/node/test/test_entry.js @@ -2,7 +2,7 @@ // with Node's built-in test runner (node:test) against the self-contained // sandbox assembled next to this file. The test has no npm dependencies at all: // node:test ships with Node, and node/opencc.js locates the addon with a plain -// filesystem lookup (no node-gyp-build). +// filesystem lookup. // // The sandbox mirrors an npm-install layout (node/*.js + build/Release/ + // assets + test/testcases + scripts), so node/opencc.js resolves the addon and diff --git a/package-lock.json b/package-lock.json index 15877a1ee..ca4300a22 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,19 +7,12 @@ "": { "name": "opencc", "version": "1.4.1", - "hasInstallScript": true, "license": "Apache-2.0", - "dependencies": { - "node-addon-api": "^8.8.0", - "node-gyp": "^12.3.0", - "node-gyp-build": "^4.8.4" - }, "bin": { "opencc": "node/cli.js" }, "devDependencies": { - "json-schema-to-typescript": "^15.0.4", - "prebuildify": "^6.0.1" + "json-schema-to-typescript": "^15.0.4" }, "engines": { "node": ">=20.17" @@ -50,18 +43,6 @@ "url": "https://github.com/sponsors/philsturgeon" } }, - "node_modules/@isaacs/fs-minipass": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", - "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", - "license": "ISC", - "dependencies": { - "minipass": "^7.0.4" - }, - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/@jsdevtools/ono": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", @@ -148,15 +129,6 @@ "dev": true, "license": "MIT" }, - "node_modules/abbrev": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-4.0.0.tgz", - "integrity": "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==", - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -164,100 +136,11 @@ "dev": true, "license": "Python-2.0" }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "dev": true, - "license": "ISC" - }, - "node_modules/end-of-stream": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", - "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", - "dev": true, - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/exponential-backoff": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", - "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", - "license": "Apache-2.0" - }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12.0.0" @@ -271,47 +154,6 @@ } } }, - "node_modules/fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "dev": true, - "license": "MIT" - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "license": "ISC" - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -399,167 +241,11 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/minipass": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", - "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/minizlib": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", - "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", - "license": "MIT", - "dependencies": { - "minipass": "^7.1.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "dev": true, - "license": "MIT" - }, - "node_modules/node-abi": { - "version": "3.89.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.89.0.tgz", - "integrity": "sha512-6u9UwL0HlAl21+agMN3YAMXcKByMqwGx+pq+P76vii5f7hTPtKDp08/H9py6DY+cfDw7kQNTGEj/rly3IgbNQA==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-addon-api": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.8.0.tgz", - "integrity": "sha512-c5Ko1fZJIJmzhFIkhRN76WTq+fC6tWnGy9CXA0fA+XygsWZmEwG8vmbkNqxMyoaa0Tin4djul49NzdVcJJcjeA==", - "license": "MIT", - "engines": { - "node": "^18 || ^20 || >= 21" - } - }, - "node_modules/node-gyp": { - "version": "12.3.0", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.3.0.tgz", - "integrity": "sha512-QNcUWM+HgJplcPzBvFBZ9VXacyGZ4+VTOb80PwWR+TlVzoHbRKULNEzpRsnaoxG3Wzr7Qh7BYxGDU3CbKib2Yg==", - "license": "MIT", - "dependencies": { - "env-paths": "^2.2.0", - "exponential-backoff": "^3.1.1", - "graceful-fs": "^4.2.6", - "nopt": "^9.0.0", - "proc-log": "^6.0.0", - "semver": "^7.3.5", - "tar": "^7.5.4", - "tinyglobby": "^0.2.12", - "undici": "^6.25.0", - "which": "^6.0.0" - }, - "bin": { - "node-gyp": "bin/node-gyp.js" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/node-gyp-build": { - "version": "4.8.4", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", - "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", - "license": "MIT", - "bin": { - "node-gyp-build": "bin.js", - "node-gyp-build-optional": "optional.js", - "node-gyp-build-test": "build-test.js" - } - }, - "node_modules/node-gyp/node_modules/isexe": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", - "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=20" - } - }, - "node_modules/node-gyp/node_modules/which": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", - "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", - "license": "ISC", - "dependencies": { - "isexe": "^4.0.0" - }, - "bin": { - "node-which": "bin/which.js" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/nopt": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-9.0.0.tgz", - "integrity": "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==", - "license": "ISC", - "dependencies": { - "abbrev": "^4.0.0" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm-run-path": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-3.1.0.tgz", - "integrity": "sha512-Dbl4A/VfiVGLgQv29URL9xshU8XDY1GeLy+fsaZ1AA8JDSfjvr5P5+pzRbWqRSBxk6/DW7MIh8lTM/PaGnP2kg==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -568,24 +254,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/prebuildify": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/prebuildify/-/prebuildify-6.0.1.tgz", - "integrity": "sha512-8Y2oOOateom/s8dNBsGIcnm6AxPmLH4/nanQzL5lQMU+sC0CMhzARZHizwr36pUPLdvBnOkCNQzxg4djuFSgIw==", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.5", - "mkdirp-classic": "^0.5.3", - "node-abi": "^3.3.0", - "npm-run-path": "^3.1.0", - "pump": "^3.0.0", - "tar-fs": "^2.1.0" - }, - "bin": { - "prebuildify": "bin.js" - } - }, "node_modules/prettier": { "version": "3.9.5", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.5.tgz", @@ -602,143 +270,11 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "node_modules/proc-log": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", - "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/pump": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", - "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/tar": { - "version": "7.5.16", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.16.tgz", - "integrity": "sha512-56adEpPMouktRlBLXiaYFFzZ/3+JXa8P9n7WbR+ibIjtviN55mEaOkiysCnPnWm+7kkui1Dn8J9l+g6zV8731w==", - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.1.0", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/tar-fs": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", - "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/tar/node_modules/chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, "node_modules/tinyglobby": { - "version": "0.2.16", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", - "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", @@ -750,38 +286,6 @@ "funding": { "url": "https://github.com/sponsors/SuperchupuDev" } - }, - "node_modules/undici": { - "version": "6.27.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.27.0.tgz", - "integrity": "sha512-YmfV3YnEDzXRC5lZ2jWtWWHKGUm1zIt8AhesR1tens+HTNv+YZlN/dp6G727LOvMJ8xjP9Be7Y2Sdr96LDm+pg==", - "license": "MIT", - "engines": { - "node": ">=18.17" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true, - "license": "MIT" - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } } } } diff --git a/package.json b/package.json index 5622dae7d..b927ec7e2 100644 --- a/package.json +++ b/package.json @@ -26,35 +26,15 @@ "scripts": { "test": "node --test --test-reporter=spec node/test.js && node node/esm-test.mjs", "generate:node-config-types": "node scripts/generate-node-opencc-config-types.js", - "prebuild": "prebuildify --napi --strip --postinstall \"node scripts/prepare-node-prebuild-artifacts.js\"", "prepare:scoped-packages": "node scripts/prepare-node-scoped-packages.js", "prepack": "node scripts/verify-node-main-package.js", - "prepublishOnly": "node scripts/verify-node-scoped-packages-published.js", - "install": "node scripts/install.js" + "prepublishOnly": "node scripts/verify-node-scoped-packages-published.js" }, "engines": { "node": ">=20.17" }, "files": [ - "binding.gyp", - "data/config/*.json", - "data/dictionary/*.txt", - "data/scripts/common.py", - "data/scripts/extract_tofu_risk.py", - "data/scripts/generate_st_phrases_from_regional_phrases.py", - "data/scripts/reverse.py", - "deps/marisa-0.3.1/include", - "deps/marisa-0.3.1/lib", - "deps/darts-clone-0.32h/COPYING.md", - "deps/darts-clone-0.32h/include", - "deps/rapidjson-1.1.0/rapidjson", "node/cli.js", - "node/configs.gypi", - "node/dict.js", - "node/dicts.gypi", - "node/global.gypi", - "node/node_opencc.gypi", - "node/opencc.cc", "node/opencc.d.cts", "node/opencc.d.mts", "node/opencc.d.ts", @@ -62,76 +42,8 @@ "node/opencc.mjs", "prebuilds/assets/*.json", "prebuilds/assets/*.ocd2", - "scripts/prepare-node-prebuild-artifacts.js", - "scripts/install.js", - "scripts/generate-node-opencc-config-types.js", - "scripts/prepare-node-scoped-packages.js", "scripts/verify-node-main-package.js", - "scripts/verify-node-scoped-packages-published.js", - "src/BinaryDict.cpp", - "src/BinaryDict.hpp", - "src/CmdLineOutput.hpp", - "src/Common.hpp", - "src/Config.cpp", - "src/Config.hpp", - "src/ConfigBasedConverter.hpp", - "src/Conversion.cpp", - "src/Conversion.hpp", - "src/ConversionChain.cpp", - "src/ConversionChain.hpp", - "src/ConversionInspection.hpp", - "src/Converter.cpp", - "src/Converter.hpp", - "src/DartsDict.cpp", - "src/DartsDict.hpp", - "src/Dict.cpp", - "src/Dict.hpp", - "src/DictConverter.cpp", - "src/DictConverter.hpp", - "src/DictEntry.cpp", - "src/DictEntry.hpp", - "src/DictGroup.cpp", - "src/DictGroup.hpp", - "src/Exception.hpp", - "src/Export.hpp", - "src/Lexicon.cpp", - "src/Lexicon.hpp", - "src/MarisaDict.cpp", - "src/MarisaDict.hpp", - "src/MaxMatchSegmentation.cpp", - "src/MaxMatchSegmentation.hpp", - "src/opencc.h", - "src/opencc_config_schema.inc", - "src/Optional.hpp", - "src/PhraseExtract.cpp", - "src/PhraseExtract.hpp", - "src/PipelineConverter.cpp", - "src/PipelineConverter.hpp", - "src/plugin/OpenCCPlugin.h", - "src/PluginSegmentation.cpp", - "src/PluginSegmentation.hpp", - "src/PrefixMatch.cpp", - "src/PrefixMatch.hpp", - "src/ResourceProvider.cpp", - "src/ResourceProvider.hpp", - "src/Segmentation.cpp", - "src/Segmentation.hpp", - "src/Segments.hpp", - "src/SerializableDict.cpp", - "src/SerializableDict.hpp", - "src/SerializedValues.cpp", - "src/SerializedValues.hpp", - "src/SimpleConverter.cpp", - "src/SimpleConverter.hpp", - "src/SingleStageConverter.cpp", - "src/SingleStageConverter.hpp", - "src/TextDict.cpp", - "src/TextDict.hpp", - "src/UTF8StringSlice.cpp", - "src/UTF8StringSlice.hpp", - "src/UTF8Util.cpp", - "src/UTF8Util.hpp", - "src/WinUtil.hpp" + "scripts/verify-node-scoped-packages-published.js" ], "repository": { "type": "git", @@ -150,13 +62,7 @@ "Traditional Chinese" ], "devDependencies": { - "json-schema-to-typescript": "^15.0.4", - "prebuildify": "^6.0.1" - }, - "dependencies": { - "node-addon-api": "^8.8.0", - "node-gyp": "^12.3.0", - "node-gyp-build": "^4.8.4" + "json-schema-to-typescript": "^15.0.4" }, "optionalDependencies": { "@opencc/opencc-darwin-arm64": "1.4.1", @@ -164,10 +70,5 @@ "@opencc/opencc-linux-arm64": "1.4.1", "@opencc/opencc-linux-x64": "1.4.1", "@opencc/opencc-win32-x64": "1.4.1" - }, - "overrides": { - "brace-expansion": "^2.0.3", - "diff": "^8.0.3", - "serialize-javascript": "^7.0.5" } } diff --git a/scripts/install.js b/scripts/install.js deleted file mode 100644 index c7ecf0ed9..000000000 --- a/scripts/install.js +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/env node - -const childProcess = require('child_process'); -const fs = require('fs'); -const path = require('path'); - -const packageRoot = path.resolve(__dirname, '..'); -const scopedPackageName = `@opencc/opencc-${process.platform}-${process.arch}`; -const isSourceCheckout = fs.existsSync(path.join(packageRoot, '.git')); - -// 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], - })); - if (scopedPackage && scopedPackage.binaryPath) { - process.exit(0); - } - } catch (error) { - if (!error || error.code !== 'MODULE_NOT_FOUND') { - throw error; - } - } -} - -const nodeGypBuildBin = require.resolve('node-gyp-build/bin.js'); -const result = childProcess.spawnSync(process.execPath, [nodeGypBuildBin], { - cwd: packageRoot, - stdio: 'inherit', -}); - -process.exit(result.status === null ? 1 : result.status); diff --git a/scripts/prepare-node-prebuild-artifacts.js b/scripts/prepare-node-prebuild-artifacts.js index b39ac23a9..7ec1122a3 100644 --- a/scripts/prepare-node-prebuild-artifacts.js +++ b/scripts/prepare-node-prebuild-artifacts.js @@ -58,8 +58,8 @@ function prepareArtifacts(root = path.join(__dirname, '..')) { if (jsonCount === 0 || dictionaryCount === 0) { throw new Error( - '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.' + 'Could not prepare Node.js prebuild assets. Run ' + + '`bazel build -c opt //data/dictionary:binary_dictionaries` before this script.' ); } } diff --git a/scripts/verify-node-main-package.js b/scripts/verify-node-main-package.js index 28369ad3b..624c7bc6b 100644 --- a/scripts/verify-node-main-package.js +++ b/scripts/verify-node-main-package.js @@ -6,8 +6,11 @@ const path = require('path'); const packageRoot = path.resolve(__dirname, '..'); const packageJson = require(path.join(packageRoot, 'package.json')); +// The npm tarball ships only the JavaScript API, CLI, typings, and the +// prebuilt config/dictionary assets. The native addon comes exclusively from +// the @opencc/opencc-- scoped packages (built with Bazel); +// there is no source-build fallback. const requiredFiles = [ - 'binding.gyp', 'node/cli.js', 'node/opencc.js', 'node/opencc.mjs', @@ -36,62 +39,6 @@ function fail(message) { process.exitCode = 1; } -// The npm tarball must contain every file the node-gyp source build compiles -// or includes, so that installs without a prebuilt binary (issue #1409) can -// fall back to building from source. Walk the gyp source list and its -// #include closure and check each file against the "files" whitelist. -function parseGypSourcesAndIncludeDirs() { - const gypiPath = path.join(packageRoot, 'node', 'node_opencc.gypi'); - const gypi = fs.readFileSync(gypiPath, 'utf8'); - const sources = []; - const includeDirs = []; - for (const match of gypi.matchAll(/"\.\.(\/[^"]*)?"/g)) { - const relativePath = match[1] ? match[1].slice(1) : '.'; - if (/\.(cpp|cc)$/.test(relativePath)) { - sources.push(relativePath); - } else if ( - fs.existsSync(path.join(packageRoot, relativePath)) && - fs.statSync(path.join(packageRoot, relativePath)).isDirectory() - ) { - includeDirs.push(relativePath); - } - } - return { sources, includeDirs }; -} - -function collectSourceBuildClosure() { - const { sources, includeDirs } = parseGypSourcesAndIncludeDirs(); - const closure = new Set(); - const queue = [...sources]; - while (queue.length > 0) { - const relativePath = queue.pop(); - if (closure.has(relativePath)) { - continue; - } - const filePath = path.join(packageRoot, relativePath); - if (!fs.existsSync(filePath)) { - fail(`Source build references a file missing from the repository: ${relativePath}`); - continue; - } - closure.add(relativePath); - const content = fs.readFileSync(filePath, 'utf8'); - for (const match of content.matchAll(/^\s*#\s*include\s+"([^"]+)"/gm)) { - const include = match[1]; - const candidates = [ - path.posix.join(path.posix.dirname(relativePath), include), - ...includeDirs.map((dir) => path.posix.normalize(path.posix.join(dir, include))), - ]; - for (const candidate of candidates) { - if (fs.existsSync(path.join(packageRoot, candidate))) { - queue.push(candidate); - break; - } - } - } - } - return closure; -} - function isPackedByFilesWhitelist(relativePath, filesList) { for (const pattern of filesList) { if (pattern === relativePath) { @@ -114,24 +61,17 @@ function isPackedByFilesWhitelist(relativePath, filesList) { return false; } -function verifySourceBuildFilesPacked() { - const filesList = packageJson.files || []; - const missing = [...collectSourceBuildClosure()] - .filter((relativePath) => !isPackedByFilesWhitelist(relativePath, filesList)) - .sort(); - for (const relativePath of missing) { - fail( - `Source build requires ${relativePath} but it is not covered by ` + - 'the "files" whitelist in package.json' - ); - } -} - for (const relativePath of requiredFiles) { const filePath = path.join(packageRoot, relativePath); if (!fs.existsSync(filePath)) { fail(`Missing required package file: ${relativePath}`); } + if (!isPackedByFilesWhitelist(relativePath, packageJson.files || [])) { + fail( + `Required file ${relativePath} is not covered by the "files" ` + + 'whitelist in package.json' + ); + } } for (const packageName of expectedOptionalPackages) { @@ -142,11 +82,10 @@ for (const packageName of expectedOptionalPackages) { } } -verifySourceBuildFilesPacked(); - if (process.exitCode) { console.error( - 'opencc package is not ready. Run npm run prebuild before publishing.' + 'opencc package is not ready. Run ' + + './scripts/build-node-prebuild-bazel.sh before publishing.' ); } else { console.log(`opencc@${packageJson.version} package files are ready.`); From aa7d7831e902bcc12bb295718a915852980b35d1 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 15 Jul 2026 02:29:36 +0000 Subject: [PATCH 2/9] Fix Bazel invocation in the AppVeyor npm job Install bazelisk via npm (@bazel/bazelisk provides both `bazel` and `bazelisk` on the npm global bin directory, which is already on PATH, unlike choco shims installed mid-build), verify with `bazel --version`, and invoke the build script through Git Bash explicitly since bare `bash` may resolve to the WSL launcher on AppVeyor Windows images. --- .appveyor.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index c2ee31cf2..22afd9d05 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -35,11 +35,17 @@ for: install: - ps: Install-Product node $env:nodejs_version $env:platform - npm -g i npm@latest - - appveyor-retry choco install bazelisk -y --no-progress + # @bazel/bazelisk provides both `bazel` and `bazelisk` commands on the + # npm global bin directory, which is already on PATH (unlike choco shims + # installed mid-build). + - appveyor-retry npm -g i @bazel/bazelisk + - bazel --version build_script: - appveyor-retry call npm install --omit=optional - echo [opencc npm package] building opencc win32-x64 prebuild with Bazel - - bash scripts/build-node-prebuild-bazel.sh win32-x64 + # Use Git Bash explicitly: bare `bash` may resolve to the WSL launcher on + # AppVeyor Windows images. + - '"C:\Program Files\Git\bin\bash.exe" scripts/build-node-prebuild-bazel.sh win32-x64' test_script: - set PREBUILDS_ONLY=1&& npm test after_test: From 35ab6cc41dfa10b0f7bea7ab02c89faf8c5ad12c Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 15 Jul 2026 08:08:32 +0000 Subject: [PATCH 3/9] Make the AppVeyor Bazel build work without symlink privileges The dictionary-generation py_binary genrules failed on AppVeyor because rules_python's venv bootstrap calls os.symlink, which needs Developer Mode (enabled on GitHub runner images but not AppVeyor workers). Enable the Developer Mode registry key like the GitHub images do, and pin bootstrap_impl=system_python via .bazelrc.user so py_binary targets run without creating a venv at all. --- .appveyor.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.appveyor.yml b/.appveyor.yml index 22afd9d05..1a322254b 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -40,8 +40,15 @@ for: # installed mid-build). - appveyor-retry npm -g i @bazel/bazelisk - bazel --version + # Enable unprivileged symlink creation (Developer Mode), like the GitHub + # Actions runner images do; Bazel and rules_python rely on it. + - reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock" /t REG_DWORD /f /v AllowDevelopmentWithoutDevLicense /d 1 build_script: - appveyor-retry call npm install --omit=optional + # Belt and suspenders for workers without symlink support: the legacy + # system_python bootstrap runs py_binary genrules (dictionary generation) + # without creating a venv via os.symlink. + - echo build --@rules_python//python/config_settings:bootstrap_impl=system_python> .bazelrc.user - echo [opencc npm package] building opencc win32-x64 prebuild with Bazel # Use Git Bash explicitly: bare `bash` may resolve to the WSL launcher on # AppVeyor Windows images. From fbb91ed9e19b560d7758673c3ee2bea8e19aa210 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 15 Jul 2026 08:17:44 +0000 Subject: [PATCH 4/9] Address Copilot review feedback on wording and prepack checks npm install itself succeeds on platforms without a scoped binary package (there is no install script anymore); the failure surfaces when requiring opencc. Reword the READMEs, NEWS.md, PUBLISHING.md, AGENTS.md, and the workflow comment accordingly, and add TWPhrases.ocd2 / TWVariants.ocd2 (referenced by the spot-checked s2twp.json) to the prepack required-files list. --- .github/workflows/nodejs.yml | 4 ++-- AGENTS.md | 2 +- NEWS.md | 2 +- README.md | 4 +++- node/PUBLISHING.md | 4 ++-- node/README.md | 3 ++- node/README.zh.md | 5 +++-- scripts/verify-node-main-package.js | 2 ++ 8 files changed, 16 insertions(+), 10 deletions(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 9ef9f861e..b0f4b2b52 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -101,8 +101,8 @@ jobs: # Pack the main npm tarball plus the Bazel-built scoped binary package for # the host platform, install both together, and smoke test — mirroring how # a release install resolves the native addon. There is no source-build - # fallback anymore: platforms without a scoped binary package are not - # supported by npm install. + # fallback anymore: on platforms without a scoped binary package, npm + # install succeeds but requiring opencc fails at load time. pack-install: name: Install packed npm tarballs (${{ matrix.os }}) permissions: diff --git a/AGENTS.md b/AGENTS.md index 8c5a8648e..531abac3b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -31,7 +31,7 @@ This document compiles the Open Chinese Convert (OpenCC) project information to - The top-level build system supports CMake, Bazel, Python `pyproject.toml`, with cross-platform CI integration. The Node.js native addon is built with Bazel (`//node:opencc`). - `src/*Test.cpp`, `data/config/*Test.cpp`, `plugins/jieba/tests/`, `test/`, and `test/golden/` contain tests covering dictionary matching, conversion chains, configuration validation, plugin segmentation, CLI behavior, and golden conversion outputs. - Tools `opencc_dict`, `opencc_phrase_extract` (`src/tools/`) help developers convert dictionary formats and extract phrases. -- Node.js tests live in `node/test.js`. npm prebuilt binaries are built with Bazel via `scripts/build-node-prebuild-bazel.sh` (see `node/PUBLISHING.md`). The `binding.gyp`/node-gyp source-build fallback has been removed: the native addon is installed exclusively through `@opencc/opencc--` scoped packages, and platforms without one are not supported by `npm install`. +- Node.js tests live in `node/test.js`. npm prebuilt binaries are built with Bazel via `scripts/build-node-prebuild-bazel.sh` (see `node/PUBLISHING.md`). The `binding.gyp`/node-gyp source-build fallback has been removed: the native addon is installed exclusively through `@opencc/opencc--` scoped packages, and platforms without one are not supported by `npm install` (installation succeeds but requiring `opencc` fails at load time). ### C++ ABI Versioning - When a change introduces an ABI-incompatible modification to the public C++ interface, bump `OPENCC_ABI_VERSION` in `CMakeLists.txt` so downstream libraries and applications relink instead of silently loading an incompatible `libopencc` shared library. diff --git a/NEWS.md b/NEWS.md index 9d4fbed63..b9a9fc5c8 100644 --- a/NEWS.md +++ b/NEWS.md @@ -3,7 +3,7 @@ ## Unreleased * **Node.js**: - * 移除 `binding.gyp` / node-gyp 源碼編譯 fallback:`opencc` npm 套件的原生 addon 現在只透過 `@opencc/opencc--` 預編譯 scoped 套件安裝(涵蓋 macOS x64/arm64、Linux x64/arm64、Windows x64,均以 Bazel 構建)。沒有預編譯二進位套件的平台不再支援 `npm install`。npm tarball 隨之不再攜帶 C++ 源碼、`deps/` 與字典源檔,體積大幅縮小;`node-gyp`、`node-gyp-build`、`node-addon-api`、`prebuildify` 依賴與 `install` / `prebuild` npm scripts 一併移除。 + * 移除 `binding.gyp` / node-gyp 源碼編譯 fallback:`opencc` npm 套件的原生 addon 現在只透過 `@opencc/opencc--` 預編譯 scoped 套件安裝(涵蓋 macOS x64/arm64、Linux x64/arm64、Windows x64,均以 Bazel 構建)。沒有預編譯二進位套件的平台不再受支援:`npm install` 本身仍會成功,但載入 `opencc` 或執行 CLI 時會因找不到原生 addon 而報錯。npm tarball 隨之不再攜帶 C++ 源碼、`deps/` 與字典源檔,體積大幅縮小;`node-gyp`、`node-gyp-build`、`node-addon-api`、`prebuildify` 依賴與 `install` / `prebuild` npm scripts 一併移除。 ## Version 1.4.1 diff --git a/README.md b/README.md index 820c28a5d..de3d07ad6 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,9 @@ https://opencc.js.org/converter?config=s2t The npm package supports Node.js `>=20.17`. The native addon is installed through prebuilt `@opencc/opencc--` packages covering macOS (x64/arm64), Linux (x64/arm64), and Windows (x64). Platforms without a -prebuilt binary package are not supported by `npm install`. +prebuilt binary package are unsupported: `npm install` itself succeeds +there, but loading `opencc` fails with an error listing the addon lookup +locations. Bun and Deno can also use the npm package through their npm compatibility support. diff --git a/node/PUBLISHING.md b/node/PUBLISHING.md index 2937d4187..fa8e5291c 100644 --- a/node/PUBLISHING.md +++ b/node/PUBLISHING.md @@ -11,8 +11,8 @@ The current npm layout is split by platform: `opencc.node` binary for one platform. The native addon comes exclusively from the scoped binary packages. There is -no source-build fallback: platforms without a scoped binary package are not -supported by `npm install`. +no source-build fallback: on platforms without a scoped binary package, +`npm install` succeeds but requiring `opencc` fails at load time. ## Package names diff --git a/node/README.md b/node/README.md index 1ee636e42..a5c51ac73 100644 --- a/node/README.md +++ b/node/README.md @@ -15,7 +15,8 @@ The package requires Node.js `>=20.17`. OpenCC ships native bindings as prebuilt binaries. Installation automatically selects the matching `@opencc/opencc--` package, covering macOS (x64/arm64), Linux (x64/arm64), and Windows (x64). Platforms without a -prebuilt binary package are not supported by `npm install`. +prebuilt binary package are unsupported: `npm install` itself succeeds there, +but loading `opencc` fails with an error listing the addon lookup locations. ## Basic Usage diff --git a/node/README.zh.md b/node/README.zh.md index 25aec4a85..032311e8c 100644 --- a/node/README.zh.md +++ b/node/README.zh.md @@ -13,8 +13,9 @@ npm install opencc OpenCC 以預編譯二進位檔提供原生綁定。安裝時會自動選取符合目前平台的 `@opencc/opencc--` 套件,涵蓋 macOS(x64/arm64)、 -Linux(x64/arm64)與 Windows(x64)。沒有預編譯二進位套件的平台 -不支援 `npm install`。 +Linux(x64/arm64)與 Windows(x64)。沒有預編譯二進位套件的平台不受 +支援:`npm install` 本身仍會成功,但載入 `opencc` 或執行 CLI 時會因 +找不到原生 addon 而報錯。 ## 基本用法 diff --git a/scripts/verify-node-main-package.js b/scripts/verify-node-main-package.js index 624c7bc6b..2b0be6aee 100644 --- a/scripts/verify-node-main-package.js +++ b/scripts/verify-node-main-package.js @@ -23,6 +23,8 @@ const requiredFiles = [ 'prebuilds/assets/STCharacters.ocd2', 'prebuilds/assets/STPhrases.ocd2', 'prebuilds/assets/STPhrases_GeneratedFromRegionalPhrases.ocd2', + 'prebuilds/assets/TWPhrases.ocd2', + 'prebuilds/assets/TWVariants.ocd2', 'prebuilds/assets/TWVariantsPhrases.ocd2', ]; From c833a501a74c2ed62d8853cf204bd82f58e434dd Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 15 Jul 2026 08:34:54 +0000 Subject: [PATCH 5/9] Build AppVeyor npm assets with CMake instead of Bazel dictionaries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Bazel dictionary genrules run rules_python py_binary tools whose venv bootstrap calls os.symlink, which keeps failing on AppVeyor workers (WinError 87) even with the Developer Mode registry key, and the bootstrap_impl override does not reach the exec configuration. Build only the addon (//node:opencc) with Bazel, move the jieba CMake build from after_test to build_script, and assemble prebuilds/assets from its install tree, where data/scripts run on the system Python directly — the same dictionary pipeline the AppVeyor winget and Python jobs already use. --- .appveyor.yml | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 1a322254b..d47fe4019 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -40,19 +40,26 @@ for: # installed mid-build). - appveyor-retry npm -g i @bazel/bazelisk - bazel --version - # Enable unprivileged symlink creation (Developer Mode), like the GitHub - # Actions runner images do; Bazel and rules_python rely on it. - - reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock" /t REG_DWORD /f /v AllowDevelopmentWithoutDevLicense /d 1 build_script: - appveyor-retry call npm install --omit=optional - # Belt and suspenders for workers without symlink support: the legacy - # system_python bootstrap runs py_binary genrules (dictionary generation) - # without creating a venv via os.symlink. - - echo build --@rules_python//python/config_settings:bootstrap_impl=system_python> .bazelrc.user - - echo [opencc npm package] building opencc win32-x64 prebuild with Bazel - # Use Git Bash explicitly: bare `bash` may resolve to the WSL launcher on - # AppVeyor Windows images. - - '"C:\Program Files\Git\bin\bash.exe" scripts/build-node-prebuild-bazel.sh win32-x64' + # Only the addon itself is built with Bazel here. The Bazel dictionary + # genrules run rules_python py_binary tools whose venv bootstrap needs + # symlink support that AppVeyor workers lack, so dictionaries and configs + # come from the CMake install below (also used for the jieba plugin + # packaging), where data/scripts run on the system Python directly. + - echo [opencc npm package] building opencc win32-x64 addon with Bazel + - bazel build -c opt //node:opencc + - echo [opencc npm package] building dictionaries and jieba plugin with CMake + - cmake -A%platform% -S. -Bbuild-npm-jieba -DCMAKE_INSTALL_PREFIX:PATH=%CD%\build-npm-jieba-install -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS:BOOL=OFF -DBUILD_OPENCC_JIEBA_PLUGIN:BOOL=ON -DENABLE_GTEST:BOOL=OFF -DENABLE_BENCHMARK:BOOL=OFF + - cmake --build build-npm-jieba --config Release --target install + - ps: | + New-Item -ItemType Directory -Force prebuilds\assets | Out-Null + New-Item -ItemType Directory -Force prebuilds\win32-x64 | Out-Null + Copy-Item build-npm-jieba-install\share\opencc\*.ocd2 prebuilds\assets\ + Get-ChildItem build-npm-jieba-install\share\opencc\*.json | + Where-Object { $_.Name -notlike '*jieba*' -and $_.Name -notlike '*.schema.json' } | + Copy-Item -Destination prebuilds\assets\ + Copy-Item bazel-bin\node\opencc.node prebuilds\win32-x64\opencc.node test_script: - set PREBUILDS_ONLY=1&& npm test after_test: @@ -69,9 +76,7 @@ for: - echo [opencc npm package] packing opencc win32-x64 scoped binary package - npm pack dist\scoped-packages\@opencc\opencc-win32-x64 - for %%F in (opencc-opencc-win32-x64-*.tgz) do set OPENCC_WIN32_TGZ=%%~fF - - echo [opencc npm package] building opencc-jieba win32-x64 package files - - cmake -A%platform% -S. -Bbuild-npm-jieba -DCMAKE_INSTALL_PREFIX:PATH=%CD%\build-npm-jieba-install -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS:BOOL=OFF -DBUILD_OPENCC_JIEBA_PLUGIN:BOOL=ON -DENABLE_GTEST:BOOL=OFF -DENABLE_BENCHMARK:BOOL=OFF - - cmake --build build-npm-jieba --config Release --target install + - echo [opencc npm package] assembling opencc-jieba win32-x64 package files - mkdir plugins\jieba\node\prebuilds\win32-x64 - mkdir plugins\jieba\node\data - copy build-npm-jieba-install\share\opencc\*jieba.json plugins\jieba\node\data\ From 989a35360ace902f0737129037d7ce7c891b83d0 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 15 Jul 2026 16:17:16 +0000 Subject: [PATCH 6/9] Compile the addon with Bazel at npm install time as the source fallback On platforms without an @opencc/opencc-- scoped binary package, npm install now builds //node:opencc from source with Bazel and stages it under prebuilds/-/, instead of leaving the package unusable. Bazel is taken from PATH (bazel or bazelisk) or fetched automatically through npx; dictionaries always ship prebuilt in the tarball, so the source build compiles only the addon and needs no Python. Detailed Changes: - **scripts/install.js**: - Reinstate the install script: skip in source checkouts, accept an installed scoped binary package, otherwise run `bazel --max_idle_secs=10 build -c opt //node:opencc` and stage the addon; `npm install --build-from-source` forces the local compile. - **package.json**: - Restore the install script and ship the Bazel workspace in the tarball: .bazelignore/.bazelrc/.bazelversion, MODULE.bazel(.lock), patches/, the whole node/ and src/ directories (all C++ dependencies come from the Bazel Central Registry, so deps/ stays excluded). - **scripts/verify-node-main-package.js**: - Require the source-build files in the prepack check. - **.github/workflows/nodejs.yml**: - Add a pack-source-install job installing the packed tarball without any scoped package, exercising the Bazel install-time build on ubuntu/macos/macos-intel. - **Documentation**: - Update README.md, node/README.md, node/README.zh.md, node/PUBLISHING.md, AGENTS.md, and the NEWS.md entry to describe the Bazel source-build fallback. --- .github/workflows/nodejs.yml | 63 +++++++++++++- AGENTS.md | 2 +- NEWS.md | 2 +- README.md | 9 +- node/PUBLISHING.md | 20 +++-- node/README.md | 9 +- node/README.zh.md | 7 +- package.json | 20 +++-- scripts/install.js | 122 ++++++++++++++++++++++++++++ scripts/verify-node-main-package.js | 24 +++++- 10 files changed, 242 insertions(+), 36 deletions(-) create mode 100644 scripts/install.js diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index b0f4b2b52..6807d5592 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -100,9 +100,8 @@ jobs: # Pack the main npm tarball plus the Bazel-built scoped binary package for # the host platform, install both together, and smoke test — mirroring how - # a release install resolves the native addon. There is no source-build - # fallback anymore: on platforms without a scoped binary package, npm - # install succeeds but requiring opencc fails at load time. + # a release install resolves the native addon on platforms with a prebuilt + # binary. pack-install: name: Install packed npm tarballs (${{ matrix.os }}) permissions: @@ -176,3 +175,61 @@ jobs: assert.strictEqual(converted, "漢字"); console.log("Packed opencc converts 汉字 ->", converted); ' + + # Install the packed npm tarball without any scoped binary package, so the + # Bazel source-build fallback in scripts/install.js is exercised exactly as + # it runs on platforms without a prebuilt binary (the addon is compiled + # from source; the dictionary assets ship prebuilt in the tarball). + pack-source-install: + name: Bazel source build from npm tarball (${{ matrix.os }}) + permissions: + contents: read + runs-on: ${{ matrix.os }} + + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, macos-15-intel] + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + - name: Use Node.js + uses: actions/setup-node@v5 + with: + node-version: 24 + - 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" + - run: npm ci --omit=optional + - name: Build prebuild assets + shell: bash + run: ./scripts/build-node-prebuild-bazel.sh + - name: Pack npm tarball + shell: bash + run: | + mkdir -p dist/npm-artifacts + npm pack --pack-destination dist/npm-artifacts + - name: Install tarball from source and smoke test + shell: bash + run: | + tarball="$(ls "$PWD"/dist/npm-artifacts/opencc-[0-9]*.tgz)" + target="$(node -p "process.platform + '-' + process.arch")" + install_dir="$(mktemp -d)" + cd "$install_dir" + npm init -y + npm install --omit=optional --foreground-scripts "$tarball" + test -f "node_modules/opencc/prebuilds/${target}/opencc.node" + node -e ' + const assert = require("assert"); + const OpenCC = require("opencc"); + const converter = new OpenCC("s2t.json"); + const converted = converter.convertSync("汉字"); + assert.strictEqual(converted, "漢字"); + console.log("Source-built opencc converts 汉字 ->", converted); + ' diff --git a/AGENTS.md b/AGENTS.md index 531abac3b..858d50841 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -31,7 +31,7 @@ This document compiles the Open Chinese Convert (OpenCC) project information to - The top-level build system supports CMake, Bazel, Python `pyproject.toml`, with cross-platform CI integration. The Node.js native addon is built with Bazel (`//node:opencc`). - `src/*Test.cpp`, `data/config/*Test.cpp`, `plugins/jieba/tests/`, `test/`, and `test/golden/` contain tests covering dictionary matching, conversion chains, configuration validation, plugin segmentation, CLI behavior, and golden conversion outputs. - Tools `opencc_dict`, `opencc_phrase_extract` (`src/tools/`) help developers convert dictionary formats and extract phrases. -- Node.js tests live in `node/test.js`. npm prebuilt binaries are built with Bazel via `scripts/build-node-prebuild-bazel.sh` (see `node/PUBLISHING.md`). The `binding.gyp`/node-gyp source-build fallback has been removed: the native addon is installed exclusively through `@opencc/opencc--` scoped packages, and platforms without one are not supported by `npm install` (installation succeeds but requiring `opencc` fails at load time). +- Node.js tests live in `node/test.js`. npm prebuilt binaries are built with Bazel via `scripts/build-node-prebuild-bazel.sh` (see `node/PUBLISHING.md`). The `binding.gyp`/node-gyp source-build fallback has been replaced by a Bazel one: the native addon comes from `@opencc/opencc--` scoped packages, and on platforms without one `scripts/install.js` compiles `//node:opencc` with Bazel at install time (dictionaries always ship prebuilt in the tarball). ### C++ ABI Versioning - When a change introduces an ABI-incompatible modification to the public C++ interface, bump `OPENCC_ABI_VERSION` in `CMakeLists.txt` so downstream libraries and applications relink instead of silently loading an incompatible `libopencc` shared library. diff --git a/NEWS.md b/NEWS.md index b9a9fc5c8..9ae31f46c 100644 --- a/NEWS.md +++ b/NEWS.md @@ -3,7 +3,7 @@ ## Unreleased * **Node.js**: - * 移除 `binding.gyp` / node-gyp 源碼編譯 fallback:`opencc` npm 套件的原生 addon 現在只透過 `@opencc/opencc--` 預編譯 scoped 套件安裝(涵蓋 macOS x64/arm64、Linux x64/arm64、Windows x64,均以 Bazel 構建)。沒有預編譯二進位套件的平台不再受支援:`npm install` 本身仍會成功,但載入 `opencc` 或執行 CLI 時會因找不到原生 addon 而報錯。npm tarball 隨之不再攜帶 C++ 源碼、`deps/` 與字典源檔,體積大幅縮小;`node-gyp`、`node-gyp-build`、`node-addon-api`、`prebuildify` 依賴與 `install` / `prebuild` npm scripts 一併移除。 + * npm 套件的源碼編譯 fallback 從 node-gyp 改為 Bazel:原生 addon 優先透過 `@opencc/opencc--` 預編譯 scoped 套件安裝(涵蓋 macOS x64/arm64、Linux x64/arm64、Windows x64,均以 Bazel 構建);沒有預編譯二進位套件的平台,`npm install` 會以 Bazel 現場編譯 `//node:opencc`(`bazel` / `bazelisk` 取自 PATH,或經 `npx` 自動下載 bazelisk),字典一律隨包預編譯、安裝時無需生成。`binding.gyp` 與 `.gypi` 構建定義刪除,`node-gyp`、`node-gyp-build`、`node-addon-api`、`prebuildify` 依賴與 `prebuild` npm script 一併移除;tarball 不再攜帶 `deps/` 與字典源檔,改為攜帶 Bazel workspace 與 `src/` C++ 源碼。 ## Version 1.4.1 diff --git a/README.md b/README.md index de3d07ad6..8f91c4215 100644 --- a/README.md +++ b/README.md @@ -80,10 +80,11 @@ https://opencc.js.org/converter?config=s2t The npm package supports Node.js `>=20.17`. The native addon is installed through prebuilt `@opencc/opencc--` packages covering macOS -(x64/arm64), Linux (x64/arm64), and Windows (x64). Platforms without a -prebuilt binary package are unsupported: `npm install` itself succeeds -there, but loading `opencc` fails with an error listing the addon lookup -locations. +(x64/arm64), Linux (x64/arm64), and Windows (x64). On platforms without a +prebuilt binary package, `npm install` compiles the addon from source with +Bazel, which requires a C++ toolchain and network access; Bazel is found on +PATH (`bazel` or `bazelisk`) or fetched automatically via `npx`. +Dictionaries always ship prebuilt in the package. Bun and Deno can also use the npm package through their npm compatibility support. diff --git a/node/PUBLISHING.md b/node/PUBLISHING.md index fa8e5291c..7fdb48195 100644 --- a/node/PUBLISHING.md +++ b/node/PUBLISHING.md @@ -10,9 +10,10 @@ The current npm layout is split by platform: - `@opencc/opencc--` contains exactly one native `opencc.node` binary for one platform. -The native addon comes exclusively from the scoped binary packages. There is -no source-build fallback: on platforms without a scoped binary package, -`npm install` succeeds but requiring `opencc` fails at load time. +The native addon normally comes from the scoped binary packages. On platforms +without one, `npm install` falls back to compiling the addon from source with +Bazel (`scripts/install.js`); the tarball ships the Bazel workspace and C++ +sources for this, and the dictionary assets are always prebuilt. ## Package names @@ -66,8 +67,8 @@ prebuilds/-/opencc.node The release workflow (`release-npm-binaries`) runs the Bazel build script separately on the runner for each scoped binary target. All Node builds use -Bazel exclusively; the former `binding.gyp`/node-gyp source-build fallback has -been removed. +Bazel exclusively — including the install-time source-build fallback, which +replaced the former `binding.gyp`/node-gyp build. ## Prepare scoped binary packages @@ -105,8 +106,9 @@ npm pack --dry-run ``` The main package tarball should contain the JavaScript API and CLI under -`node/`, the TypeScript declarations, and `prebuilds/assets/`, but no C++ -sources and not `prebuilds//opencc.node`. +`node/`, the TypeScript declarations, `prebuilds/assets/`, and the Bazel +workspace plus `src/` C++ sources for the install-time source build, but not +`prebuilds//opencc.node`. Dry-run each scoped package: @@ -167,8 +169,8 @@ node -e "const OpenCC = require('opencc'); const cc = new OpenCC('s2twp'); conso The install should pull exactly one matching `@opencc/opencc--` optional package for the current machine. If no scoped binary package is -available, requiring `opencc` fails with an error explaining where the addon -was looked up. +available, the install script compiles the addon from source with Bazel and +stages it under `prebuilds/-/`. ## CI shape diff --git a/node/README.md b/node/README.md index a5c51ac73..0b8d9095a 100644 --- a/node/README.md +++ b/node/README.md @@ -14,9 +14,12 @@ The package requires Node.js `>=20.17`. OpenCC ships native bindings as prebuilt binaries. Installation automatically selects the matching `@opencc/opencc--` package, covering -macOS (x64/arm64), Linux (x64/arm64), and Windows (x64). Platforms without a -prebuilt binary package are unsupported: `npm install` itself succeeds there, -but loading `opencc` fails with an error listing the addon lookup locations. +macOS (x64/arm64), Linux (x64/arm64), and Windows (x64). On platforms without +a prebuilt binary package, `npm install` compiles the addon from source with +Bazel: this requires a C++ toolchain and network access, and uses `bazel` or +`bazelisk` from PATH (or fetches bazelisk automatically via `npx`). +Dictionaries always ship prebuilt in the package, so the source build only +compiles the addon itself. ## Basic Usage diff --git a/node/README.zh.md b/node/README.zh.md index 032311e8c..42f6e78fd 100644 --- a/node/README.zh.md +++ b/node/README.zh.md @@ -13,9 +13,10 @@ npm install opencc OpenCC 以預編譯二進位檔提供原生綁定。安裝時會自動選取符合目前平台的 `@opencc/opencc--` 套件,涵蓋 macOS(x64/arm64)、 -Linux(x64/arm64)與 Windows(x64)。沒有預編譯二進位套件的平台不受 -支援:`npm install` 本身仍會成功,但載入 `opencc` 或執行 CLI 時會因 -找不到原生 addon 而報錯。 +Linux(x64/arm64)與 Windows(x64)。沒有預編譯二進位套件的平台, +`npm install` 會以 Bazel 從原始碼編譯 addon:需要可用的 C++ 工具鏈與 +網路;`bazel` 或 `bazelisk` 取自 PATH,找不到時會透過 `npx` 自動下載 +bazelisk。字典一律隨套件預編譯提供,源碼編譯只需編譯 addon 本身。 ## 基本用法 diff --git a/package.json b/package.json index b927ec7e2..203b80780 100644 --- a/package.json +++ b/package.json @@ -28,22 +28,26 @@ "generate:node-config-types": "node scripts/generate-node-opencc-config-types.js", "prepare:scoped-packages": "node scripts/prepare-node-scoped-packages.js", "prepack": "node scripts/verify-node-main-package.js", - "prepublishOnly": "node scripts/verify-node-scoped-packages-published.js" + "prepublishOnly": "node scripts/verify-node-scoped-packages-published.js", + "install": "node scripts/install.js" }, "engines": { "node": ">=20.17" }, "files": [ - "node/cli.js", - "node/opencc.d.cts", - "node/opencc.d.mts", - "node/opencc.d.ts", - "node/opencc.js", - "node/opencc.mjs", + ".bazelignore", + ".bazelrc", + ".bazelversion", + "MODULE.bazel", + "MODULE.bazel.lock", + "node", + "patches", "prebuilds/assets/*.json", "prebuilds/assets/*.ocd2", + "scripts/install.js", "scripts/verify-node-main-package.js", - "scripts/verify-node-scoped-packages-published.js" + "scripts/verify-node-scoped-packages-published.js", + "src" ], "repository": { "type": "git", diff --git a/scripts/install.js b/scripts/install.js new file mode 100644 index 000000000..33af12562 --- /dev/null +++ b/scripts/install.js @@ -0,0 +1,122 @@ +#!/usr/bin/env node + +// npm install script: make sure the native addon is available. +// +// 1. In a source checkout, skip: developers build with Bazel explicitly +// (see CONTRIBUTING.md). `npm install --build-from-source` overrides. +// 2. If the matching @opencc/opencc-- scoped binary package +// is installed, nothing to do. +// 3. Otherwise compile the addon from source with Bazel and stage it under +// prebuilds/-/opencc.node. Only //node:opencc is built: +// the config/dictionary assets ship prebuilt in the npm tarball +// (prebuilds/assets), so the source build needs no Python and generates +// no dictionaries. + +const childProcess = require('child_process'); +const fs = require('fs'); +const path = require('path'); + +const packageRoot = path.resolve(__dirname, '..'); +const target = `${process.platform}-${process.arch}`; +const scopedPackageName = `@opencc/opencc-${target}`; +const isSourceCheckout = fs.existsSync(path.join(packageRoot, '.git')); +const runInShell = process.platform === 'win32'; + +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 install-time build.\n' + + 'opencc: run ./scripts/build-node-prebuild-bazel.sh to build the addon ' + + 'and assets with Bazel before `npm test`,\n' + + 'opencc: or re-run with `npm install --build-from-source` to build the ' + + 'addon here.' + ); + process.exit(0); +} + +// An installed scoped binary package satisfies the install, but never when +// build-from-source explicitly asks for a local compile. +if (!buildFromSource) { + try { + const scopedPackage = require(require.resolve(scopedPackageName, { + paths: [packageRoot], + })); + if (scopedPackage && scopedPackage.binaryPath) { + process.exit(0); + } + } catch (error) { + if (!error || error.code !== 'MODULE_NOT_FOUND') { + throw error; + } + } +} + +function commandWorks(command, args) { + const result = childProcess.spawnSync(command, args, { + stdio: 'ignore', + shell: runInShell, + }); + return !result.error && result.status === 0; +} + +// Prefer a Bazel already on PATH; otherwise fetch bazelisk through npx. +// bazelisk downloads the Bazel version pinned in .bazelversion. +function findBazel() { + for (const command of ['bazel', 'bazelisk']) { + if (commandWorks(command, ['--version'])) { + return { command, prefixArgs: [] }; + } + } + if (commandWorks('npx', ['--version'])) { + console.log( + 'opencc: bazel/bazelisk not found on PATH; using `npx @bazel/bazelisk`.' + ); + return { command: 'npx', prefixArgs: ['--yes', '@bazel/bazelisk'] }; + } + return null; +} + +const bazel = findBazel(); +if (!bazel) { + console.error( + `opencc: no prebuilt binary for ${target} and Bazel is not available.\n` + + 'opencc: install bazelisk (e.g. `npm install -g @bazel/bazelisk`, see ' + + 'https://bazel.build/install/bazelisk) and a C++ toolchain,\n' + + 'opencc: then re-run npm install.' + ); + process.exit(1); +} + +console.log( + `opencc: no prebuilt binary for ${target}; building the native addon ` + + 'from source with Bazel...' +); +const build = childProcess.spawnSync( + bazel.command, + // --max_idle_secs is a startup option: let the Bazel server exit shortly + // after the install instead of lingering as a daemon. + [...bazel.prefixArgs, '--max_idle_secs=10', 'build', '-c', 'opt', '//node:opencc'], + { cwd: packageRoot, stdio: 'inherit', shell: runInShell } +); +if (build.error || build.status !== 0) { + console.error('opencc: Bazel build of //node:opencc failed.'); + process.exit(build.status === null || build.status === undefined ? 1 : build.status); +} + +const builtAddon = path.join(packageRoot, 'bazel-bin', 'node', 'opencc.node'); +if (!fs.existsSync(builtAddon)) { + console.error(`opencc: expected Bazel output missing: ${builtAddon}`); + process.exit(1); +} + +const destDir = path.join(packageRoot, 'prebuilds', target); +const dest = path.join(destDir, 'opencc.node'); +fs.mkdirSync(destDir, { recursive: true }); +fs.rmSync(dest, { force: true }); +fs.copyFileSync(builtAddon, dest); +// Bazel outputs are read-only; make the staged copy writable for reinstalls. +fs.chmodSync(dest, 0o755); +console.log(`opencc: staged source-built addon at prebuilds/${target}/opencc.node`); diff --git a/scripts/verify-node-main-package.js b/scripts/verify-node-main-package.js index 2b0be6aee..6399a0a1d 100644 --- a/scripts/verify-node-main-package.js +++ b/scripts/verify-node-main-package.js @@ -6,17 +6,33 @@ const path = require('path'); const packageRoot = path.resolve(__dirname, '..'); const packageJson = require(path.join(packageRoot, 'package.json')); -// The npm tarball ships only the JavaScript API, CLI, typings, and the -// prebuilt config/dictionary assets. The native addon comes exclusively from -// the @opencc/opencc-- scoped packages (built with Bazel); -// there is no source-build fallback. +// The npm tarball ships the JavaScript API, CLI, typings, the prebuilt +// config/dictionary assets, and the Bazel workspace needed to compile the +// native addon (//node:opencc) from source when no +// @opencc/opencc-- scoped binary package matches the +// platform (see scripts/install.js). Dictionaries are never rebuilt at +// install time; they ship in prebuilds/assets. const requiredFiles = [ + '.bazelignore', + '.bazelrc', + '.bazelversion', + 'MODULE.bazel', + 'MODULE.bazel.lock', + 'node/BUILD.bazel', + 'node/cc_headers_files.bzl', 'node/cli.js', + 'node/deps.bzl', + 'node/opencc.cc', 'node/opencc.js', 'node/opencc.mjs', 'node/opencc.d.ts', 'node/opencc.d.cts', 'node/opencc.d.mts', + 'patches/rules-python-write-unstamped-build-data.patch', + 'scripts/install.js', + 'src/BUILD.bazel', + 'src/SimpleConverter.cpp', + 'src/opencc_config_schema.inc', 'prebuilds/assets/s2t.json', 'prebuilds/assets/s2twp.json', 'prebuilds/assets/CJK_Compatibility_Ideographs.ocd2', From 5ba754a80b5ada540e8a99c5a36acaf82ecad4ac Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 15 Jul 2026 17:05:03 +0000 Subject: [PATCH 7/9] Regenerate dictionaries in the install-time source build The Bazel source-build fallback now mirrors a full prebuild instead of compiling only the addon: npm install builds //data/dictionary:binary_dictionaries alongside //node:opencc and refreshes prebuilds/assets from the freshly generated dictionaries, with Bazel providing the hermetic Python toolchain for the generation scripts. The tarball ships data/config, data/dictionary, and data/scripts for this; prebuilt assets still ship for the scoped-package fast path. --- .github/workflows/nodejs.yml | 4 ++-- AGENTS.md | 2 +- NEWS.md | 2 +- README.md | 9 ++++---- node/PUBLISHING.md | 17 ++++++++------ node/README.md | 10 ++++----- node/README.zh.md | 7 +++--- package.json | 5 +++++ scripts/install.js | 35 +++++++++++++++++++++-------- scripts/verify-node-main-package.js | 8 +++++++ 10 files changed, 67 insertions(+), 32 deletions(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 6807d5592..23d53ed10 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -178,8 +178,8 @@ jobs: # Install the packed npm tarball without any scoped binary package, so the # Bazel source-build fallback in scripts/install.js is exercised exactly as - # it runs on platforms without a prebuilt binary (the addon is compiled - # from source; the dictionary assets ship prebuilt in the tarball). + # it runs on platforms without a prebuilt binary: the addon is compiled + # from source and the dictionaries are regenerated into prebuilds/assets. pack-source-install: name: Bazel source build from npm tarball (${{ matrix.os }}) permissions: diff --git a/AGENTS.md b/AGENTS.md index 858d50841..5158bf9cd 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -31,7 +31,7 @@ This document compiles the Open Chinese Convert (OpenCC) project information to - The top-level build system supports CMake, Bazel, Python `pyproject.toml`, with cross-platform CI integration. The Node.js native addon is built with Bazel (`//node:opencc`). - `src/*Test.cpp`, `data/config/*Test.cpp`, `plugins/jieba/tests/`, `test/`, and `test/golden/` contain tests covering dictionary matching, conversion chains, configuration validation, plugin segmentation, CLI behavior, and golden conversion outputs. - Tools `opencc_dict`, `opencc_phrase_extract` (`src/tools/`) help developers convert dictionary formats and extract phrases. -- Node.js tests live in `node/test.js`. npm prebuilt binaries are built with Bazel via `scripts/build-node-prebuild-bazel.sh` (see `node/PUBLISHING.md`). The `binding.gyp`/node-gyp source-build fallback has been replaced by a Bazel one: the native addon comes from `@opencc/opencc--` scoped packages, and on platforms without one `scripts/install.js` compiles `//node:opencc` with Bazel at install time (dictionaries always ship prebuilt in the tarball). +- Node.js tests live in `node/test.js`. npm prebuilt binaries are built with Bazel via `scripts/build-node-prebuild-bazel.sh` (see `node/PUBLISHING.md`). The `binding.gyp`/node-gyp source-build fallback has been replaced by a Bazel one: the native addon comes from `@opencc/opencc--` scoped packages, and on platforms without one `scripts/install.js` runs a full Bazel source build at install time (compiles `//node:opencc`, regenerates `//data/dictionary:binary_dictionaries`, and refreshes `prebuilds/assets`; prebuilt assets still ship in the tarball for the scoped-package path). ### C++ ABI Versioning - When a change introduces an ABI-incompatible modification to the public C++ interface, bump `OPENCC_ABI_VERSION` in `CMakeLists.txt` so downstream libraries and applications relink instead of silently loading an incompatible `libopencc` shared library. diff --git a/NEWS.md b/NEWS.md index 9ae31f46c..b314a9dab 100644 --- a/NEWS.md +++ b/NEWS.md @@ -3,7 +3,7 @@ ## Unreleased * **Node.js**: - * npm 套件的源碼編譯 fallback 從 node-gyp 改為 Bazel:原生 addon 優先透過 `@opencc/opencc--` 預編譯 scoped 套件安裝(涵蓋 macOS x64/arm64、Linux x64/arm64、Windows x64,均以 Bazel 構建);沒有預編譯二進位套件的平台,`npm install` 會以 Bazel 現場編譯 `//node:opencc`(`bazel` / `bazelisk` 取自 PATH,或經 `npx` 自動下載 bazelisk),字典一律隨包預編譯、安裝時無需生成。`binding.gyp` 與 `.gypi` 構建定義刪除,`node-gyp`、`node-gyp-build`、`node-addon-api`、`prebuildify` 依賴與 `prebuild` npm script 一併移除;tarball 不再攜帶 `deps/` 與字典源檔,改為攜帶 Bazel workspace 與 `src/` C++ 源碼。 + * npm 套件的源碼編譯 fallback 從 node-gyp 改為 Bazel:原生 addon 優先透過 `@opencc/opencc--` 預編譯 scoped 套件安裝(涵蓋 macOS x64/arm64、Linux x64/arm64、Windows x64,均以 Bazel 構建,字典隨包預編譯);沒有預編譯二進位套件的平台,`npm install` 會以 Bazel 完整地從源碼構建——編譯 `//node:opencc` 並重新生成字典(`bazel` / `bazelisk` 取自 PATH,或經 `npx` 自動下載 bazelisk;字典生成腳本的 Python 由 Bazel hermetic 工具鏈提供)。`binding.gyp` 與 `.gypi` 構建定義刪除,`node-gyp`、`node-gyp-build`、`node-addon-api`、`prebuildify` 依賴與 `prebuild` npm script 一併移除;tarball 不再攜帶 `deps/`(C++ 三方依賴改由 Bazel Central Registry 提供),改為攜帶 Bazel workspace、`src/` C++ 源碼與 `data/` 字典源檔。 ## Version 1.4.1 diff --git a/README.md b/README.md index 8f91c4215..76f4ce440 100644 --- a/README.md +++ b/README.md @@ -81,10 +81,11 @@ https://opencc.js.org/converter?config=s2t The npm package supports Node.js `>=20.17`. The native addon is installed through prebuilt `@opencc/opencc--` packages covering macOS (x64/arm64), Linux (x64/arm64), and Windows (x64). On platforms without a -prebuilt binary package, `npm install` compiles the addon from source with -Bazel, which requires a C++ toolchain and network access; Bazel is found on -PATH (`bazel` or `bazelisk`) or fetched automatically via `npx`. -Dictionaries always ship prebuilt in the package. +prebuilt binary package, `npm install` builds from source with Bazel — +compiling the addon and regenerating the dictionaries — which requires a +C++ toolchain and network access; Bazel is found on PATH (`bazel` or +`bazelisk`) or fetched automatically via `npx`, and Bazel downloads its own +hermetic Python toolchain for the dictionary generation. Bun and Deno can also use the npm package through their npm compatibility support. diff --git a/node/PUBLISHING.md b/node/PUBLISHING.md index 7fdb48195..198bbc269 100644 --- a/node/PUBLISHING.md +++ b/node/PUBLISHING.md @@ -11,9 +11,11 @@ The current npm layout is split by platform: `opencc.node` binary for one platform. The native addon normally comes from the scoped binary packages. On platforms -without one, `npm install` falls back to compiling the addon from source with -Bazel (`scripts/install.js`); the tarball ships the Bazel workspace and C++ -sources for this, and the dictionary assets are always prebuilt. +without one, `npm install` falls back to a full Bazel source build +(`scripts/install.js`): it compiles the addon, regenerates the dictionaries, +and refreshes `prebuilds/assets`. The tarball ships the Bazel workspace, the +C++ sources, and the dictionary sources for this; prebuilt assets are still +included for the scoped-package fast path. ## Package names @@ -107,8 +109,8 @@ npm pack --dry-run The main package tarball should contain the JavaScript API and CLI under `node/`, the TypeScript declarations, `prebuilds/assets/`, and the Bazel -workspace plus `src/` C++ sources for the install-time source build, but not -`prebuilds//opencc.node`. +workspace plus the `src/` C++ sources and `data/` dictionary sources for the +install-time source build, but not `prebuilds//opencc.node`. Dry-run each scoped package: @@ -169,8 +171,9 @@ node -e "const OpenCC = require('opencc'); const cc = new OpenCC('s2twp'); conso The install should pull exactly one matching `@opencc/opencc--` optional package for the current machine. If no scoped binary package is -available, the install script compiles the addon from source with Bazel and -stages it under `prebuilds/-/`. +available, the install script builds from source with Bazel: it compiles the +addon, stages it under `prebuilds/-/`, and regenerates the +dictionaries into `prebuilds/assets`. ## CI shape diff --git a/node/README.md b/node/README.md index 0b8d9095a..d11961849 100644 --- a/node/README.md +++ b/node/README.md @@ -15,11 +15,11 @@ The package requires Node.js `>=20.17`. OpenCC ships native bindings as prebuilt binaries. Installation automatically selects the matching `@opencc/opencc--` package, covering macOS (x64/arm64), Linux (x64/arm64), and Windows (x64). On platforms without -a prebuilt binary package, `npm install` compiles the addon from source with -Bazel: this requires a C++ toolchain and network access, and uses `bazel` or -`bazelisk` from PATH (or fetches bazelisk automatically via `npx`). -Dictionaries always ship prebuilt in the package, so the source build only -compiles the addon itself. +a prebuilt binary package, `npm install` builds from source with Bazel, +compiling the addon and regenerating the dictionaries: this requires a C++ +toolchain and network access, and uses `bazel` or `bazelisk` from PATH (or +fetches bazelisk automatically via `npx`); Bazel downloads its own hermetic +Python toolchain for the dictionary generation scripts. ## Basic Usage diff --git a/node/README.zh.md b/node/README.zh.md index 42f6e78fd..44f86df01 100644 --- a/node/README.zh.md +++ b/node/README.zh.md @@ -14,9 +14,10 @@ npm install opencc OpenCC 以預編譯二進位檔提供原生綁定。安裝時會自動選取符合目前平台的 `@opencc/opencc--` 套件,涵蓋 macOS(x64/arm64)、 Linux(x64/arm64)與 Windows(x64)。沒有預編譯二進位套件的平台, -`npm install` 會以 Bazel 從原始碼編譯 addon:需要可用的 C++ 工具鏈與 -網路;`bazel` 或 `bazelisk` 取自 PATH,找不到時會透過 `npx` 自動下載 -bazelisk。字典一律隨套件預編譯提供,源碼編譯只需編譯 addon 本身。 +`npm install` 會以 Bazel 從原始碼構建:編譯 addon 並重新生成字典。 +這需要可用的 C++ 工具鏈與網路;`bazel` 或 `bazelisk` 取自 PATH, +找不到時會透過 `npx` 自動下載 bazelisk,字典生成腳本所需的 Python +由 Bazel 自動下載 hermetic 工具鏈,無需系統安裝。 ## 基本用法 diff --git a/package.json b/package.json index 203b80780..fea206195 100644 --- a/package.json +++ b/package.json @@ -40,11 +40,16 @@ ".bazelversion", "MODULE.bazel", "MODULE.bazel.lock", + "data/BUILD.bazel", + "data/config", + "data/dictionary", + "data/scripts", "node", "patches", "prebuilds/assets/*.json", "prebuilds/assets/*.ocd2", "scripts/install.js", + "scripts/prepare-node-prebuild-artifacts.js", "scripts/verify-node-main-package.js", "scripts/verify-node-scoped-packages-published.js", "src" diff --git a/scripts/install.js b/scripts/install.js index 33af12562..9e41558e2 100644 --- a/scripts/install.js +++ b/scripts/install.js @@ -5,12 +5,14 @@ // 1. In a source checkout, skip: developers build with Bazel explicitly // (see CONTRIBUTING.md). `npm install --build-from-source` overrides. // 2. If the matching @opencc/opencc-- scoped binary package -// is installed, nothing to do. -// 3. Otherwise compile the addon from source with Bazel and stage it under -// prebuilds/-/opencc.node. Only //node:opencc is built: -// the config/dictionary assets ship prebuilt in the npm tarball -// (prebuilds/assets), so the source build needs no Python and generates -// no dictionaries. +// is installed, nothing to do (the prebuilt config/dictionary assets ship +// in the tarball under prebuilds/assets). +// 3. Otherwise build from source with Bazel, exactly like a full prebuild: +// compile //node:opencc, regenerate the dictionaries +// (//data/dictionary:binary_dictionaries), stage the addon under +// prebuilds/-/opencc.node, and refresh prebuilds/assets +// from the freshly generated dictionaries. Bazel fetches its own hermetic +// Python toolchain for the dictionary generation scripts. const childProcess = require('child_process'); const fs = require('fs'); @@ -92,13 +94,20 @@ if (!bazel) { console.log( `opencc: no prebuilt binary for ${target}; building the native addon ` + - 'from source with Bazel...' + 'and dictionaries from source with Bazel...' ); const build = childProcess.spawnSync( bazel.command, // --max_idle_secs is a startup option: let the Bazel server exit shortly // after the install instead of lingering as a daemon. - [...bazel.prefixArgs, '--max_idle_secs=10', 'build', '-c', 'opt', '//node:opencc'], + [ + ...bazel.prefixArgs, + '--max_idle_secs=10', + 'build', + '-c', 'opt', + '//data/dictionary:binary_dictionaries', + '//node:opencc', + ], { cwd: packageRoot, stdio: 'inherit', shell: runInShell } ); if (build.error || build.status !== 0) { @@ -119,4 +128,12 @@ fs.rmSync(dest, { force: true }); fs.copyFileSync(builtAddon, dest); // Bazel outputs are read-only; make the staged copy writable for reinstalls. fs.chmodSync(dest, 0o755); -console.log(`opencc: staged source-built addon at prebuilds/${target}/opencc.node`); + +// Replace the shipped prebuilds/assets with the freshly generated +// dictionaries (plus the config JSONs from data/config). +require('./prepare-node-prebuild-artifacts').prepareArtifacts(packageRoot); + +console.log( + `opencc: staged source-built addon at prebuilds/${target}/opencc.node ` + + 'and regenerated prebuilds/assets' +); diff --git a/scripts/verify-node-main-package.js b/scripts/verify-node-main-package.js index 6399a0a1d..ed4f1f4d6 100644 --- a/scripts/verify-node-main-package.js +++ b/scripts/verify-node-main-package.js @@ -18,6 +18,13 @@ const requiredFiles = [ '.bazelversion', 'MODULE.bazel', 'MODULE.bazel.lock', + 'data/config/BUILD.bazel', + 'data/config/t2s.json', + 'data/dictionary/BUILD.bazel', + 'data/dictionary/STPhrases.txt', + 'data/scripts/BUILD.bazel', + 'data/scripts/generate_st_phrases_from_regional_phrases.py', + 'data/scripts/reverse.py', 'node/BUILD.bazel', 'node/cc_headers_files.bzl', 'node/cli.js', @@ -30,6 +37,7 @@ const requiredFiles = [ 'node/opencc.d.mts', 'patches/rules-python-write-unstamped-build-data.patch', 'scripts/install.js', + 'scripts/prepare-node-prebuild-artifacts.js', 'src/BUILD.bazel', 'src/SimpleConverter.cpp', 'src/opencc_config_schema.inc', From 392199b99d97d8f5885b227bf5a76537cdfeb71e Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 15 Jul 2026 17:10:24 +0000 Subject: [PATCH 8/9] Use BCR rapidjson for the CLI and config tests The install-time source build failed from the npm tarball because //src/tools:command_line_main_lib (in the dictionary-generation graph via //src/tools:command_line) still depended on the vendored //deps/rapidjson-1.1.0 package, which the tarball does not ship. Switch it and the data/config validation tests to @rapidjson from the Bazel Central Registry, matching //src:config_lib; the only remaining vendored-deps references in Bazel are the Windows-Zig genrule sources. --- data/config/BUILD.bazel | 4 ++-- src/tools/BUILD.bazel | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/data/config/BUILD.bazel b/data/config/BUILD.bazel index 493486ad9..667995bc7 100644 --- a/data/config/BUILD.bazel +++ b/data/config/BUILD.bazel @@ -47,7 +47,7 @@ filegroup( "//src:simple_converter_lib", "@bazel_tools//tools/cpp/runfiles", "@googletest//:gtest_main", - "//deps/rapidjson-1.1.0:rapidjson", + "@rapidjson//:rapidjson", ], ) for config in CONFIGS @@ -77,7 +77,7 @@ test_suite( deps = [ "@bazel_tools//tools/cpp/runfiles", "@googletest//:gtest_main", - "//deps/rapidjson-1.1.0:rapidjson", + "@rapidjson//:rapidjson", ], ) for config in CONFIGS diff --git a/src/tools/BUILD.bazel b/src/tools/BUILD.bazel index dcf340dd9..ac26c2430 100644 --- a/src/tools/BUILD.bazel +++ b/src/tools/BUILD.bazel @@ -17,13 +17,13 @@ cc_library( hdrs = ["CommandLineMain.hpp"], deps = [ ":platform_io_lib", - "//deps/rapidjson-1.1.0:rapidjson", "//src:cmd_line_output_lib", "//src:config_lib", "//src:conversion_inspection_lib", "//src:converter_lib", "//src:exception_lib", "//src:segments_lib", + "@rapidjson//:rapidjson", ], ) From dc163658ebf32aad6e44ddaa4279fe18079192b0 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 15 Jul 2026 19:40:07 +0000 Subject: [PATCH 9/9] Sync package-lock hasInstallScript and document the dictionary rebuild Regenerate package-lock.json so the root entry carries hasInstallScript: true, matching the reinstated install script, and note in scripts/install.js why the source build regenerates dictionaries: the .ocd2 format is endianness-dependent (marisa trie dump and SerializedValues both use native-endian integers), so the shipped little-endian assets cannot serve big-endian fallback platforms. --- package-lock.json | 1 + scripts/install.js | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/package-lock.json b/package-lock.json index ca4300a22..589b7dfc6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,7 @@ "": { "name": "opencc", "version": "1.4.1", + "hasInstallScript": true, "license": "Apache-2.0", "bin": { "opencc": "node/cli.js" diff --git a/scripts/install.js b/scripts/install.js index 9e41558e2..01ec05209 100644 --- a/scripts/install.js +++ b/scripts/install.js @@ -13,6 +13,13 @@ // prebuilds/-/opencc.node, and refresh prebuilds/assets // from the freshly generated dictionaries. Bazel fetches its own hermetic // Python toolchain for the dictionary generation scripts. +// +// The dictionary rebuild is NOT redundant with the shipped +// prebuilds/assets: .ocd2 files are endianness-dependent (both the +// marisa trie dump and SerializedValues use native-endian integers), and +// the shipped assets are little-endian. Platforms that reach this +// fallback include the big-endian ones (e.g. s390x), where the shipped +// dictionaries would not load. const childProcess = require('child_process'); const fs = require('fs');