diff --git a/.appveyor.yml b/.appveyor.yml index b27085de3..d47fe4019 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,36 @@ for: install: - ps: Install-Product node $env:nodejs_version $env:platform - npm -g i npm@latest + # @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: - - set npm_config_build_from_source=true&& appveyor-retry call npm install + - appveyor-retry call npm install --omit=optional + # 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: - - 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 @@ -52,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\ diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index aaeff9ee9..23d53ed10 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -98,11 +98,90 @@ 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 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 on platforms with a prebuilt + # binary. + pack-install: + name: Install packed npm tarballs (${{ 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" + - 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 + npm pack "dist/scoped-packages/@opencc/opencc-${target}" \ + --pack-destination dist/npm-artifacts + - name: Install tarballs and smoke test + shell: bash + run: | + 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 \ + "$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("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 and the dictionaries are regenerated into prebuilds/assets. pack-source-install: - name: Source build from npm tarball (${{ matrix.os }}) + name: Bazel source build from npm tarball (${{ matrix.os }}) permissions: contents: read runs-on: ${{ matrix.os }} @@ -119,9 +198,18 @@ jobs: uses: actions/setup-node@v5 with: node-version: 24 - - run: npm ci + - 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 - run: npm run prebuild + shell: bash + run: ./scripts/build-node-prebuild-bazel.sh - name: Pack npm tarball shell: bash run: | @@ -130,12 +218,13 @@ jobs: - name: Install tarball from source and smoke test shell: bash run: | - tarball="$(ls "$PWD"/dist/npm-artifacts/opencc-*.tgz)" + 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/build/Release/opencc.node + test -f "node_modules/opencc/prebuilds/${target}/opencc.node" node -e ' const assert = require("assert"); const OpenCC = require("opencc"); diff --git a/AGENTS.md b/AGENTS.md index a2e4b749b..5158bf9cd 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 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/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..b314a9dab 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,10 @@ # Change History of OpenCC +## 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;字典生成腳本的 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 2026年7月12日 diff --git a/README.md b/README.md index 8f03c0a32..76f4ce440 100644 --- a/README.md +++ b/README.md @@ -78,9 +78,14 @@ 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). On platforms without a +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/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/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/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..198bbc269 100644 --- a/node/PUBLISHING.md +++ b/node/PUBLISHING.md @@ -5,14 +5,17 @@ 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 normally comes from the scoped binary packages. On platforms +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 @@ -34,8 +37,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 +67,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 — including the install-time source-build fallback, which +replaced the former `binding.gyp`/node-gyp build. ## Prepare scoped binary packages @@ -105,8 +107,10 @@ 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, `prebuilds/assets/`, and the Bazel +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: @@ -167,15 +171,19 @@ 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, the install script builds from source with Bazel: it compiles the +addon, stages it under `prebuilds/-/`, and regenerates the +dictionaries into `prebuilds/assets`. -## 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..d11961849 100644 --- a/node/README.md +++ b/node/README.md @@ -12,10 +12,14 @@ 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). On platforms without +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 e80e83c30..44f86df01 100644 --- a/node/README.zh.md +++ b/node/README.zh.md @@ -11,9 +11,13 @@ 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` 會以 Bazel 從原始碼構建:編譯 addon 並重新生成字典。 +這需要可用的 C++ 工具鏈與網路;`bazel` 或 `bazelisk` 取自 PATH, +找不到時會透過 `npx` 自動下載 bazelisk,字典生成腳本所需的 Python +由 Bazel 自動下載 hermetic 工具鏈,無需系統安裝。 ## 基本用法 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..589b7dfc6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,17 +9,11 @@ "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 +44,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 +130,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 +137,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 +155,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 +242,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 +255,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 +271,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 +287,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..fea206195 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,6 @@ "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", @@ -36,102 +35,24 @@ "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", - "node/opencc.js", - "node/opencc.mjs", + ".bazelignore", + ".bazelrc", + ".bazelversion", + "MODULE.bazel", + "MODULE.bazel.lock", + "data/BUILD.bazel", + "data/config", + "data/dictionary", + "data/scripts", + "node", + "patches", "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/prepare-node-prebuild-artifacts.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" + "src" ], "repository": { "type": "git", @@ -150,13 +71,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 +79,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 index c7ecf0ed9..01ec05209 100644 --- a/scripts/install.js +++ b/scripts/install.js @@ -1,37 +1,54 @@ #!/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 (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. +// +// 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'); const path = require('path'); const packageRoot = path.resolve(__dirname, '..'); -const scopedPackageName = `@opencc/opencc-${process.platform}-${process.arch}`; +const target = `${process.platform}-${process.arch}`; +const scopedPackageName = `@opencc/opencc-${target}`; const isSourceCheckout = fs.existsSync(path.join(packageRoot, '.git')); +const runInShell = process.platform === 'win32'; -// 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: source checkout detected; skipping the install-time 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.' + '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 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) { +// 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], @@ -46,10 +63,84 @@ if (!isSourceCheckout && !buildFromSource) { } } -const nodeGypBuildBin = require.resolve('node-gyp-build/bin.js'); -const result = childProcess.spawnSync(process.execPath, [nodeGypBuildBin], { - cwd: packageRoot, - stdio: 'inherit', -}); +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 ` + + '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', + '//data/dictionary:binary_dictionaries', + '//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); -process.exit(result.status === null ? 1 : result.status); +// 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/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..ed4f1f4d6 100644 --- a/scripts/verify-node-main-package.js +++ b/scripts/verify-node-main-package.js @@ -6,20 +6,49 @@ const path = require('path'); const packageRoot = path.resolve(__dirname, '..'); const packageJson = require(path.join(packageRoot, 'package.json')); +// 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 = [ - 'binding.gyp', + '.bazelignore', + '.bazelrc', + '.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', + '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', + 'scripts/prepare-node-prebuild-artifacts.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', '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', ]; @@ -36,62 +65,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 +87,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 +108,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.`); 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", ], )