diff --git a/.github/workflows/npm-release.yml b/.github/workflows/npm-release.yml index 8df1088..31385db 100644 --- a/.github/workflows/npm-release.yml +++ b/.github/workflows/npm-release.yml @@ -190,8 +190,95 @@ jobs: if-no-files-found: error retention-days: 30 + build-native-musl: + needs: validate + name: build ${{ matrix.id }} + strategy: + fail-fast: false + matrix: + include: + - id: linux-x64-musl + runner: ubuntu-24.04 + - id: linux-arm64-musl + runner: ubuntu-24.04-arm + runs-on: ${{ matrix.runner }} + container: alpine:3.22 + steps: + - name: Install the musl build toolchain + shell: sh + run: >- + apk add --no-cache + nodejs npm python3 py3-pip bash git gcc g++ make cmake ninja + patch tar xz linux-headers + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 + - name: Bootstrap pinned native dependencies + shell: bash + run: >- + python3 tools/bootstrap_dependencies.py + --cache-dir .cache/dependencies + --platform-id "${{ matrix.id }}" + - name: Fetch the pinned PDF renderer for npm bundling + shell: bash + run: | + mkdir -p .cache/pdfium-package + cd .cache/pdfium-package + npm init --yes + npm install --ignore-scripts --no-audit --no-fund --package-lock=false \ + pdfium-native@0.6.1 + python "$GITHUB_WORKSPACE/tools/pdfium/prepare_renderer.py" \ + --pdfium-dir node_modules/pdfium-native \ + --font-cache "$GITHUB_WORKSPACE/.cache/pdf-fonts" \ + --download \ + --build + - name: Install Node development files + shell: bash + run: | + node_version="$(node -p process.versions.node)" + npx --yes node-gyp@11.4.2 install "$node_version" --devdir "$PWD/.cache/node-gyp" + echo "NODE_INCLUDE_DIR=$PWD/.cache/node-gyp/$node_version/include/node" >> "$GITHUB_ENV" + - name: Configure native package + shell: bash + run: >- + cmake -S . -B build-npm -G Ninja + -DCMAKE_BUILD_TYPE=Release + -DLIGHT_OCR_DEPENDENCY_CACHE_DIR="$PWD/.cache/dependencies" + -DLIGHT_OCR_ONNXRUNTIME_FLAVOR=cpu + -DLIGHT_OCR_TARGET_LIBC=musl + -DLIGHT_OCR_BUILD_NODE=ON + -DLIGHT_OCR_BUILD_TESTS=OFF + -DLIGHT_OCR_BUILD_TOOLS=OFF + -DLIGHT_OCR_NODE_INCLUDE_DIR="$NODE_INCLUDE_DIR" + -DLIGHT_OCR_NODE_EXECUTABLE="$(command -v node)" + - name: Build the addon and release metadata + shell: bash + run: | + cmake --build build-npm --config Release --parallel + python3 tools/generate_release_metadata.py \ + --build-dir build-npm \ + --output-dir "reports/npm/${{ matrix.id }}" \ + --platform-id "${{ matrix.id }}" \ + --configuration Release \ + --model-free + - name: Stage native package input + shell: bash + run: >- + python3 tools/npm_release.py stage-native + --platform-id "${{ matrix.id }}" + --build-dir build-npm + --configuration Release + --metadata-dir "reports/npm/${{ matrix.id }}" + --pdfium-dir .cache/pdfium-package/node_modules/pdfium-native + --output-dir "dist/native-input/${{ matrix.id }}" + --runtime-flavor cpu + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: native-${{ matrix.id }} + path: dist/native-input/${{ matrix.id }} + if-no-files-found: error + retention-days: 30 + assemble: - needs: build-native + needs: [build-native, build-native-musl] runs-on: ubuntu-24.04 env: RELEASE_VERSION: ${{ inputs.version }} @@ -324,8 +411,55 @@ jobs: node ../tools/npm/smoke-tier.cjs @arcships/light-ocr-medium light-ocr-medium \ ppocrv6-medium-onnx-20260722.1 + smoke-musl: + needs: assemble + name: smoke ${{ matrix.id }} + strategy: + fail-fast: false + matrix: + include: + - id: linux-x64-musl + runner: ubuntu-24.04 + - id: linux-arm64-musl + runner: ubuntu-24.04-arm + runs-on: ${{ matrix.runner }} + container: alpine:3.22 + env: + RELEASE_VERSION: ${{ inputs.version }} + steps: + - name: Install the musl runtime + shell: sh + run: apk add --no-cache nodejs npm bash + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 + with: + name: light-ocr-npm-${{ inputs.version }} + path: dist/release + - name: Install the local stable closure without scripts or network + shell: bash + run: | + mkdir package-smoke + cd package-smoke + npm init --yes + npm install --offline --ignore-scripts --no-audit --no-fund --package-lock=false \ + ../dist/release/arcships-light-ocr-model-ppocrv6-small-0.3.4.tgz \ + ../dist/release/arcships-light-ocr-${{ matrix.id }}-${RELEASE_VERSION}.tgz \ + ../dist/release/arcships-light-ocr-runtime-0.1.7.tgz \ + ../dist/release/arcships-light-ocr-${RELEASE_VERSION}.tgz + cp ../tools/npm/smoke.cjs . + - name: Run stable Small OCR + shell: bash + working-directory: package-smoke + env: + LIGHT_OCR_SMOKE_FIXTURE: ${{ github.workspace }}/corpus/fixtures/generated-hello-123 + run: node smoke.cjs + - name: Run built-in PDF OCR without scripts or network + shell: bash + working-directory: package-smoke + run: node ../tools/npm/document-smoke.cjs + publish: - needs: test-packages + needs: [test-packages, smoke-musl] if: inputs.publish_to_registry runs-on: ubuntu-24.04 environment: npm-release diff --git a/.github/workflows/onnxruntime-musl.yml b/.github/workflows/onnxruntime-musl.yml new file mode 100644 index 0000000..a435a90 --- /dev/null +++ b/.github/workflows/onnxruntime-musl.yml @@ -0,0 +1,166 @@ +name: onnxruntime musl runtime + +# Rebuilds the pinned CPU-only ONNX Runtime shared library for musl (Alpine) +# Linux and attaches the packaged archives to a "musl-runtime-" +# release. deps.lock.json and cmake/Dependencies.cmake pin these archives by +# SHA-256; run this workflow only when the pin is intentionally changed. +on: + workflow_dispatch: + inputs: + onnxruntime_version: + description: ONNX Runtime release tag version (without the v prefix) + required: true + default: "1.22.0" + type: string + release_tag: + description: Release tag that stores the musl runtime archives + required: true + default: "musl-runtime-1.22.0" + type: string + publish_to_release: + description: Attach the archives to the release tag + required: true + default: true + type: boolean + +permissions: + contents: read + +jobs: + build: + name: build ${{ matrix.id }} + strategy: + fail-fast: false + matrix: + include: + - id: linux-x64-musl + runner: ubuntu-24.04 + arch: x64 + - id: linux-arm64-musl + runner: ubuntu-24.04-arm + arch: arm64 + runs-on: ${{ matrix.runner }} + container: alpine:3.22 + env: + ONNXRUNTIME_VERSION: ${{ inputs.onnxruntime_version || '1.22.0' }} + RELEASE_TAG: ${{ inputs.release_tag || 'musl-runtime-1.22.0' }} + steps: + - name: Install the musl build toolchain + shell: sh + run: >- + apk add --no-cache + nodejs npm python3 py3-pip bash git gcc g++ make cmake ninja + patch tar xz zip unzip curl linux-headers + - name: Clone ONNX Runtime ${{ env.ONNXRUNTIME_VERSION }} + shell: bash + run: | + git clone --recursive --depth 1 --shallow-submodules \ + --branch "v${ONNXRUNTIME_VERSION}" \ + https://github.com/microsoft/onnxruntime onnxruntime + echo "ORT_SOURCE_SHA=$(git -C onnxruntime rev-parse HEAD)" | tee -a "$GITHUB_STEP_SUMMARY" + - name: Patch the musl execinfo dependency + shell: bash + run: | + # musl has no execinfo.h. The backtrace() body in stacktrace.cc is + # compiled out under NDEBUG (Release), but the include is + # unconditional in 1.22.0, so guard it as well. + python3 - <<'EOF' + from pathlib import Path + p = Path('onnxruntime/onnxruntime/core/platform/posix/stacktrace.cc') + s = p.read_text() + old = ('#if !defined(__ANDROID__) && !defined(__wasm__) ' + '&& !defined(_OPSCHEMA_LIB_) && !defined(_AIX)\n#include ') + new = ('#if !defined(__ANDROID__) && !defined(__wasm__) ' + '&& !defined(_OPSCHEMA_LIB_) && !defined(_AIX) && !defined(NDEBUG)\n' + '#include ') + if new not in s: + if s.count(old) != 1: + raise SystemExit('stacktrace.cc anchor not found') + p.write_text(s.replace(old, new)) + print('stacktrace.cc execinfo guard applied') + EOF + - name: Prefetch the pinned Eigen source + shell: bash + run: | + # onnxruntime's deps.txt pins the GitLab archive by SHA-1, but GitLab + # regenerated the archive bytes (same commit, new packaging), which + # invalidates the pin on every platform. Provide the source tree + # directly and bypass the download check. + mkdir -p eigen + curl -fsSL -o eigen.zip \ + "https://gitlab.com/libeigen/eigen/-/archive/1d8b82b0740839c0de7f1242a3585e3390ff5f33/eigen-1d8b82b0740839c0de7f1242a3585e3390ff5f33.zip" + unzip -q eigen.zip + mv eigen-1d8b82b0740839c0de7f1242a3585e3390ff5f33/* eigen/ + rmdir eigen-1d8b82b0740839c0de7f1242a3585e3390ff5f33 + rm eigen.zip + - name: Build the CPU shared library + shell: bash + run: | + cd onnxruntime + ./build.sh --allow_running_as_root --build_shared_lib \ + --config Release --skip_tests --parallel "$(nproc)" \ + --cmake_extra_defines \ + onnxruntime_BUILD_UNIT_TESTS=OFF \ + "FETCHCONTENT_SOURCE_DIR_EIGEN3=${GITHUB_WORKSPACE}/eigen" + - name: Package the NuGet-shaped archive + shell: bash + run: | + version="${ONNXRUNTIME_VERSION}" + name="onnxruntime-musl-${version}-linux-${{ matrix.arch }}" + mkdir -p "pkg/${name}/build/native/include" \ + "pkg/${name}/runtimes/linux-${{ matrix.arch }}-musl/native" + cp onnxruntime/LICENSE "pkg/${name}/LICENSE" + cp onnxruntime/include/onnxruntime/core/session/*.h \ + "pkg/${name}/build/native/include/" + cp -r onnxruntime/include/onnxruntime/core/providers \ + "pkg/${name}/build/native/include/providers" + cp -L "onnxruntime/build/Linux/Release/libonnxruntime.so.${version}" \ + "pkg/${name}/runtimes/linux-${{ matrix.arch }}-musl/native/libonnxruntime.so" + (cd "pkg/${name}" && zip -q -r "../${name}.zip" .) + sha256sum "${name}.zip" | tee "${name}.zip.sha256" + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: musl-runtime-${{ matrix.id }} + path: | + pkg/onnxruntime-musl-*.zip + pkg/onnxruntime-musl-*.zip.sha256 + if-no-files-found: error + retention-days: 30 + + publish: + needs: build + if: inputs.publish_to_release != false + runs-on: ubuntu-24.04 + permissions: + contents: write + env: + RELEASE_TAG: ${{ inputs.release_tag || 'musl-runtime-1.22.0' }} + steps: + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 + with: + pattern: musl-runtime-* + path: dist/musl-runtime + merge-multiple: true + - name: Attach the archives to the release + shell: bash + run: | + shopt -s nullglob + zips=(dist/musl-runtime/*.zip) + if [ "${#zips[@]}" -ne 2 ]; then + echo "expected two musl runtime archives" >&2 + exit 1 + fi + for zip in "${zips[@]}"; do + expected="$(cut -d' ' -f1 "${zip}.sha256")" + actual="$(sha256sum "$zip" | cut -d' ' -f1)" + if [ "$expected" != "$actual" ]; then + echo "checksum mismatch for $zip" >&2 + exit 1 + fi + done + gh release create "$RELEASE_TAG" --title "$RELEASE_TAG" \ + --notes "Pinned CPU-only ONNX Runtime musl builds. Archives are pinned by SHA-256 in models/deps.lock.json." \ + dist/musl-runtime/*.zip dist/musl-runtime/*.zip.sha256 2>/dev/null \ + || gh release upload "$RELEASE_TAG" dist/musl-runtime/*.zip dist/musl-runtime/*.zip.sha256 --clobber + env: + GH_TOKEN: ${{ github.token }} diff --git a/CHANGELOG.md b/CHANGELOG.md index a64309e..1caeb93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,30 @@ This file records user-visible changes to `light-ocr`. Published artifact detail ## [Unreleased] +### Added + +- Added CPU-only prebuilt platform packages for musl Linux + (`@arcships/light-ocr-linux-x64-musl` and + `@arcships/light-ocr-linux-arm64-musl`), so Alpine (musl) hosts install a + working engine directly. The musl ONNX Runtime 1.22.0 shared libraries are + built from source with a one-line musl execinfo guard, packaged in the NuGet + layout, and pinned by SHA-256 in `models/deps.lock.json` + ([musl-runtime-1.22.0](https://github.com/arcships/light-ocr/releases/tag/musl-runtime-1.22.0)); + `.github/workflows/onnxruntime-musl.yml` rebuilds them on demand. The npm + release pipeline builds and smokes both musl platforms in an Alpine 3.22 + container ([#62](https://github.com/arcships/light-ocr/issues/62)). + +### Changed + +- Relaxed `engines.node` in every published package from the enumerated + `^22.0.0 || ^24.0.0` range to the floor `>=22.0.0`. The addon uses the stable + Node-API ABI, so newer Node major versions (including 26) load the same + prebuilt binaries without a library release. Tier 1 support continues to + cover the 22/24 LTS lines; Current lines are validated by CI smoke rather + than blocked at install time. This also unblocks Yarn installs, which fail + hard on engines ranges that do not list the running Node major + ([#62](https://github.com/arcships/light-ocr/issues/62)). + ## [0.5.7] - 2026-08-05 ### Fixed diff --git a/README.md b/README.md index cfd8cad..1babc17 100644 --- a/README.md +++ b/README.md @@ -25,11 +25,13 @@ Windows—without postinstall or first-run downloads. | **Best for** | local OCR in Node.js apps, CLIs, desktop software, and native C++ integrations | | **Inputs** | JPEG, PNG, PDF, encoded bytes, or decoded pixel buffers | | **Outputs** | text, confidence, quadrilateral boxes, page metadata, and timing | -| **Distribution** | one npm install; CommonJS, ESM, TypeScript, and six prebuilt platforms | +| **Distribution** | one npm install; CommonJS, ESM, TypeScript, and eight prebuilt platforms | ## Quick start -Node.js 22 and 24 are supported. +Node.js 22 or newer is supported; the 22 and 24 LTS lines are Tier 1 +(fully tested), and the stable Node-API ABI lets newer Node versions load the +same prebuilt binaries without a library release. ```bash npm install @arcships/light-ocr @@ -158,7 +160,7 @@ for await (const page of recognizeDocument([buf1, buf2, buf3])) { ## Platform acceleration -The npm package provides the following six builds. The default `createEngine()` call uses Auto mode: +The npm package provides the following eight builds. The default `createEngine()` call uses Auto mode: | Platform | Auto mode | | --- | --- | @@ -166,6 +168,8 @@ The npm package provides the following six builds. The default `createEngine()` | macOS on Intel | CPU | | Linux x64 with glibc | WebGPU through Vulkan, then CPU | | Linux arm64 with glibc | CPU | +| Linux x64 with musl (Alpine) | CPU | +| Linux arm64 with musl (Alpine) | CPU | | Windows x64 | WebGPU through D3D12, then CPU | | Windows arm64 | CPU | diff --git a/README.zh-CN.md b/README.zh-CN.md index 81c439b..c36981c 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -22,11 +22,12 @@ Small 模型、PDFium、中文 fallback 字体,以及 macOS、Linux 和 Window | **适合场景** | Node.js 应用、CLI、桌面软件与原生 C++ 集成中的本地 OCR | | **输入** | JPEG、PNG、PDF、编码后的字节或解码后的像素 | | **输出** | 文字、置信度、四边形坐标、页面元数据与耗时 | -| **分发方式** | 一次 npm 安装;同时提供 CommonJS、ESM、TypeScript 与六个平台预编译包 | +| **分发方式** | 一次 npm 安装;同时提供 CommonJS、ESM、TypeScript 与八个平台预编译包 | ## 快速开始 -支持 Node.js 22 和 24。 +支持 Node.js 22 及以上;22/24 LTS 为 Tier 1 完整测试版本,Node-API 稳定 ABI +保证更新的 Node 版本无需等待库发版即可加载同一份预编译产物。 ```bash npm install @arcships/light-ocr @@ -151,7 +152,7 @@ for await (const page of recognizeDocument([buf1, buf2, buf3])) { ## 平台加速 -npm 包提供以下六个平台版本。默认的 `createEngine()` 使用 Auto 模式: +npm 包提供以下八个平台版本。默认的 `createEngine()` 使用 Auto 模式: | 平台 | Auto 模式 | | --- | --- | @@ -159,6 +160,8 @@ npm 包提供以下六个平台版本。默认的 `createEngine()` 使用 Auto | macOS / Intel | CPU | | Linux x64 glibc | 通过 Vulkan 使用 WebGPU,然后使用 CPU | | Linux arm64 glibc | CPU | +| Linux x64 musl(Alpine) | CPU | +| Linux arm64 musl(Alpine) | CPU | | Windows x64 | 通过 D3D12 使用 WebGPU,然后使用 CPU | | Windows arm64 | CPU | diff --git a/bindings/node/README.md b/bindings/node/README.md index 6a56169..44d11e9 100644 --- a/bindings/node/README.md +++ b/bindings/node/README.md @@ -182,11 +182,11 @@ TypeScript types are included in the shared runtime [`index.d.ts`](../../package jobs through the main package's document API - **EXIF orientation**: JPEG orientation tags 1–8 automatically corrected - **ROI**: pageSpace axis-aligned rectangle, coordinates offset back to full page -- **Providers**: CPU (all platforms), Apple Core ML (macOS arm64), WebGPU (Linux x64, Windows x64) +- **Providers**: CPU (all platforms), Apple Core ML (macOS arm64), WebGPU (Linux x64 glibc, Windows x64) - **Offline**: no network at install or runtime; model bundled in npm package - **Concurrency**: one engine = one worker thread, bounded FIFO queue, AbortSignal support -- **Platforms**: macOS arm64/x64, Linux x64/arm64 (glibc), Windows x64/arm64 -- **Node.js**: 22, 24 +- **Platforms**: macOS arm64/x64, Linux x64/arm64 (glibc and musl), Windows x64/arm64 +- **Node.js**: 22+ (22/24 LTS are Tier 1; newer majors load the same prebuilt binaries via the stable Node-API ABI) ### Not supported @@ -203,9 +203,10 @@ TypeScript types are included in the shared runtime [`index.d.ts`](../../package | --- | --- | --- | | macOS arm64 | Apple Core ML → CPU | FP16 detection + recognition via Neural Engine / GPU | | macOS x64 | CPU | Core ML parity not met on Intel | -| Linux x64 | WebGPU → CPU | FP32, bounded CPU partition for Concat/Gather/Slice | +| Linux x64 (glibc) | WebGPU → CPU | FP32, bounded CPU partition for Concat/Gather/Slice | | Windows x64 | WebGPU → CPU | FP32, D3D12 | -| Linux arm64 | CPU | WebGPU plugin has no arm64 binary | +| Linux arm64 (glibc) | CPU | WebGPU plugin has no arm64 binary | +| Linux x64/arm64 (musl) | CPU | CPU-only platform packages | | Windows arm64 | CPU | WebGPU not yet qualified | Use `light-ocr info --model-info` to inspect the actual provider chain, device, and qualification status. diff --git a/bindings/node/package.json b/bindings/node/package.json index 834d920..17ffc32 100644 --- a/bindings/node/package.json +++ b/bindings/node/package.json @@ -14,7 +14,7 @@ }, "type": "commonjs", "engines": { - "node": "^22.0.0 || ^24.0.0" + "node": ">=22.0.0" }, "scripts": { "test": "node --test --test-concurrency=1 test/*.test.cjs" diff --git a/cmake/Dependencies.cmake b/cmake/Dependencies.cmake index 930482c..d7127c4 100644 --- a/cmake/Dependencies.cmake +++ b/cmake/Dependencies.cmake @@ -128,23 +128,82 @@ function(light_ocr_configure_dependencies) "Build a qualification-only WebGPU runtime") endif() + # LIGHT_OCR_TARGET_LIBC follows the existing WebGpuRuntime.cmake vocabulary: + # "glibc" (default for the CPU flavor) or "musl". Validate only when set so + # the WebGPU flavor keeps LIGHT_OCR_TARGET_LIBC optional: injecting a default + # would shadow WebGpuRuntime.cmake's own glibc detection (CMP0126 keeps a + # normal variable in front of its cache entry). + if(DEFINED LIGHT_OCR_TARGET_LIBC AND NOT LIGHT_OCR_TARGET_LIBC STREQUAL "") + if(NOT LIGHT_OCR_TARGET_LIBC MATCHES "^(glibc|musl)$") + message(FATAL_ERROR + "LIGHT_OCR_TARGET_LIBC must be glibc or musl, got: ${LIGHT_OCR_TARGET_LIBC}") + endif() + if(LIGHT_OCR_ONNXRUNTIME_FLAVOR STREQUAL "webgpu" + AND LIGHT_OCR_TARGET_LIBC STREQUAL "musl") + message(FATAL_ERROR "the WebGPU flavor is not available for musl targets") + endif() + if(LIGHT_OCR_TARGET_LIBC STREQUAL "musl") + execute_process( + COMMAND ${CMAKE_CXX_COMPILER} -dumpmachine + RESULT_VARIABLE _light_ocr_dumpmachine_result + OUTPUT_VARIABLE _light_ocr_dumpmachine + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE + TIMEOUT 30) + if(NOT _light_ocr_dumpmachine_result EQUAL 0) + message(FATAL_ERROR + "LIGHT_OCR_TARGET_LIBC=musl requires a musl toolchain; the compiler " + "does not support -dumpmachine (result: ${_light_ocr_dumpmachine_result})") + endif() + if(NOT _light_ocr_dumpmachine MATCHES "musl") + message(FATAL_ERROR + "LIGHT_OCR_TARGET_LIBC=musl requires a musl toolchain; the compiler " + "reports: ${_light_ocr_dumpmachine}") + endif() + endif() + endif() + if(LIGHT_OCR_ONNXRUNTIME_FLAVOR STREQUAL "cpu") - light_ocr_archive_url(_ort_url microsoft.ml.onnxruntime.1.22.0.nupkg - https://api.nuget.org/v3-flatcontainer/microsoft.ml.onnxruntime/1.22.0/microsoft.ml.onnxruntime.1.22.0.nupkg) - if(EXISTS "${_ort_url}") - # CMake 3.31 chooses the extractor for local files from their suffix and - # does not recognize NuGet's .nupkg suffix. Use a hard-linked .zip alias. - set(_ort_archive_dir "${CMAKE_BINARY_DIR}/_light_ocr_archives") - set(_ort_archive_zip "${_ort_archive_dir}/microsoft.ml.onnxruntime.1.22.0.zip") - file(MAKE_DIRECTORY "${_ort_archive_dir}") - file(CREATE_LINK "${_ort_url}" "${_ort_archive_zip}" COPY_ON_ERROR) - set(_ort_url "${_ort_archive_zip}") + if(NOT DEFINED LIGHT_OCR_TARGET_LIBC OR LIGHT_OCR_TARGET_LIBC STREQUAL "") + set(LIGHT_OCR_TARGET_LIBC "glibc") + endif() + if(LIGHT_OCR_TARGET_LIBC STREQUAL "musl") + if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|amd64|AMD64)$") + set(_ort_musl_arch "x64") + set(_ort_musl_sha256 "1b5aaa7c54c0a575e44a09e424168a35e2b3d005349a6e87dad24f718c5b6ab9") + elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(aarch64|arm64|ARM64)$") + set(_ort_musl_arch "arm64") + set(_ort_musl_sha256 "50705761fb84c69c1c2ea0f31a968a58f0810c115e73d24db65fcb869abc4ac3") + else() + message(FATAL_ERROR + "musl ONNX Runtime is only prepared for linux x64 and arm64") + endif() + set(_ort_musl_filename "onnxruntime-musl-1.22.0-linux-${_ort_musl_arch}.zip") + light_ocr_archive_url(_ort_url "${_ort_musl_filename}" + "https://github.com/arcships/light-ocr/releases/download/musl-runtime-1.22.0/${_ort_musl_filename}") + FetchContent_Declare(onnxruntime_package + URL "${_ort_url}" + DOWNLOAD_NAME "${_ort_musl_filename}" + URL_HASH SHA256=${_ort_musl_sha256} + DOWNLOAD_EXTRACT_TIMESTAMP TRUE) + else() + light_ocr_archive_url(_ort_url microsoft.ml.onnxruntime.1.22.0.nupkg + https://api.nuget.org/v3-flatcontainer/microsoft.ml.onnxruntime/1.22.0/microsoft.ml.onnxruntime.1.22.0.nupkg) + if(EXISTS "${_ort_url}") + # CMake 3.31 chooses the extractor for local files from their suffix and + # does not recognize NuGet's .nupkg suffix. Use a hard-linked .zip alias. + set(_ort_archive_dir "${CMAKE_BINARY_DIR}/_light_ocr_archives") + set(_ort_archive_zip "${_ort_archive_dir}/microsoft.ml.onnxruntime.1.22.0.zip") + file(MAKE_DIRECTORY "${_ort_archive_dir}") + file(CREATE_LINK "${_ort_url}" "${_ort_archive_zip}" COPY_ON_ERROR) + set(_ort_url "${_ort_archive_zip}") + endif() + FetchContent_Declare(onnxruntime_package + URL "${_ort_url}" + DOWNLOAD_NAME microsoft.ml.onnxruntime.1.22.0.zip + URL_HASH SHA256=d571e63a2329baacb713f441e65ad75284de354db6e1ac435fe4bebbb417986a + DOWNLOAD_EXTRACT_TIMESTAMP TRUE) endif() - FetchContent_Declare(onnxruntime_package - URL "${_ort_url}" - DOWNLOAD_NAME microsoft.ml.onnxruntime.1.22.0.zip - URL_HASH SHA256=d571e63a2329baacb713f441e65ad75284de354db6e1ac435fe4bebbb417986a - DOWNLOAD_EXTRACT_TIMESTAMP TRUE) FetchContent_MakeAvailable(nlohmann_json clipper opencv onnxruntime_package) else() FetchContent_MakeAvailable(nlohmann_json clipper opencv) @@ -206,7 +265,11 @@ function(light_ocr_configure_dependencies) endif() set(_ort_runtime_files "${_ort_library}" "${_ort_versioned_library}") elseif(UNIX AND CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|amd64|AMD64)$") - set(_ort_runtime_dir "runtimes/linux-x64/native") + if(LIGHT_OCR_TARGET_LIBC STREQUAL "musl") + set(_ort_runtime_dir "runtimes/linux-x64-musl/native") + else() + set(_ort_runtime_dir "runtimes/linux-x64/native") + endif() set(_ort_library "${onnxruntime_package_SOURCE_DIR}/${_ort_runtime_dir}/libonnxruntime.so") set(_ort_soname_library "${onnxruntime_package_SOURCE_DIR}/${_ort_runtime_dir}/libonnxruntime.so.1") @@ -215,7 +278,11 @@ function(light_ocr_configure_dependencies) endif() set(_ort_runtime_files "${_ort_library}" "${_ort_soname_library}") elseif(UNIX AND CMAKE_SYSTEM_PROCESSOR MATCHES "^(aarch64|arm64|ARM64)$") - set(_ort_runtime_dir "runtimes/linux-arm64/native") + if(LIGHT_OCR_TARGET_LIBC STREQUAL "musl") + set(_ort_runtime_dir "runtimes/linux-arm64-musl/native") + else() + set(_ort_runtime_dir "runtimes/linux-arm64/native") + endif() set(_ort_library "${onnxruntime_package_SOURCE_DIR}/${_ort_runtime_dir}/libonnxruntime.so") set(_ort_soname_library "${onnxruntime_package_SOURCE_DIR}/${_ort_runtime_dir}/libonnxruntime.so.1") diff --git a/docs/build-and-release.md b/docs/build-and-release.md index 7812e87..01fce76 100644 --- a/docs/build-and-release.md +++ b/docs/build-and-release.md @@ -223,10 +223,10 @@ PR、main 与 release 使用分层门禁,避免同一提交在三个阶段重 `.github/workflows/npm-release.yml` 是仅允许从 `main` 手动触发的发布候选与发布流程。默认 `publish_to_registry=false`,所以第一次运行不会读取 `NPM_TOKEN` 或改动 npm registry: -- macOS arm64/x64、Linux x64/arm64 glibc 与 Windows x64/arm64 六个平台分别从锁定输入构建 Node-API addon、ONNX Runtime payload、PDFium addon、许可证清单与 SPDX SBOM。Linux x64 与 Windows x64 使用 production-qualified WebGPU runtime,其余平台使用 CPU runtime。 -- assemble 阶段生成六个 native 包、model-free runtime、Small/Tiny/Medium 三个 facade、Document compatibility facade,以及 Tiny/Medium 两个锁定 model 包,共 13 个 manifest 记录;已发布的 Small model `0.3.4` 另外取得 tarball 参与离线安装验证。 -- 六个平台均使用 Node.js 22 从本地 tarball 执行 `--ignore-scripts --offline` 安装、真实 Small 图片 OCR 与内置 PDF OCR;Linux x64 额外验证 Tiny/Medium preview。macOS 两个平台还以默认 Node 宿主和 ad-hoc 重签的 Node 宿主各运行一次 signed-artifact policy,覆盖不同身份拒绝与双方 ad-hoc 接受。 -- workflow 在进入六平台构建前查询 11 个新版本身份;任何目标版本已存在即失败,要求使用原始 release artifact 做 promotion,禁止重建或覆盖 npm 的不可变版本。Tiny/Medium model `0.1.0` 已存在时仅在 registry integrity 与候选完全一致时复用。 +- macOS arm64/x64、Linux x64/arm64 glibc 与 Windows x64/arm64 六个平台在系统 runner 构建;Linux x64/arm64 musl 两个平台在 `alpine:3.22` 容器构建。八个平台分别从锁定输入构建 Node-API addon、ONNX Runtime payload、PDFium addon、许可证清单与 SPDX SBOM。Linux x64 与 Windows x64 使用 production-qualified WebGPU runtime,其余平台使用 CPU runtime。 +- assemble 阶段生成八个 native 包、model-free runtime、Small/Tiny/Medium 三个 facade、Document compatibility facade,以及 Tiny/Medium 两个锁定 model 包,共 15 个 manifest 记录;已发布的 Small model `0.3.4` 另外取得 tarball 参与离线安装验证。 +- glibc 六平台在系统 runner 使用 Node.js 22 从本地 tarball 执行 `--ignore-scripts --offline` 安装、真实 Small 图片 OCR 与内置 PDF OCR;musl 两平台在 Alpine 容器使用发行版 Node.js 22 执行同一安装与 OCR/PDF 验证;Linux x64 额外验证 Tiny/Medium preview。macOS 两个平台还以默认 Node 宿主和 ad-hoc 重签的 Node 宿主各运行一次 signed-artifact policy,覆盖不同身份拒绝与双方 ad-hoc 接受。 +- workflow 在进入八平台构建前查询 15 个新版本身份;任何目标版本已存在即失败,要求使用原始 release artifact 做 promotion,禁止重建或覆盖 npm 的不可变版本。Tiny/Medium model `0.1.0` 已存在时仅在 registry integrity 与候选完全一致时复用。 - `publish_to_registry=true` 时,专用 `npm-release` environment 才读取 `NPM_TOKEN`:先把 native、runtime 与可复用 preview model 阶段发布到 `next`,确认 Small facade tarball 能从真实 registry 解析依赖后,再发布 Small/Tiny/Medium/Document 四个 facade;最后从 registry 回装稳定包并在禁网环境运行图片和 PDF OCR。 - `latest` 晋升始终由独立 promotion workflow 使用原 release run 保存的 `light-ocr-npm-` artifact 完成,不重新构建或发布 tarball。Tiny、Medium 与 Document 保持在 `next`。 @@ -296,7 +296,7 @@ npm release 按 [npm-packaging.md](npm-packaging.md) 生成 13 个受 manifest 3. 保存 parity、quality、benchmark、leak、Sanitizer、fuzz 和 offline 报告。 4. 将精确 bundle 文件打入 `@arcships/light-ocr-model-ppocrv6-small`,验证 sterile install,并记录 npm tarball SHA-256/integrity。独立 USTAR mirror 是非 npm 分发项,不阻塞 npm package release。 5. 为每个平台生成 manifest、许可证清单和 SBOM。 -6. 在隔离环境验证 13 个 manifest packages、Small model tarball、platform 选择、默认 `createEngine()` 和模型 payload hash;已安装后的运行测试必须禁网。 +6. 在隔离环境验证 15 个 manifest packages、Small model tarball、platform 选择、默认 `createEngine()` 和模型 payload hash;已安装后的运行测试必须禁网。 7. 对照 [implementation-status.md](implementation-status.md) 关闭所有 Pending 项。 registry 发布由专用 `npm-release` environment 执行;workflow 成功记录、registry metadata 与安装复验才构成完成证据。仓库可按维护策略为该 environment 增加 required reviewer。签名、公证、非 npm 公共下载地址和长期保留策略仍是独立的外部事项。 diff --git a/docs/monorepo-design.md b/docs/monorepo-design.md index 8447331..0233c45 100644 --- a/docs/monorepo-design.md +++ b/docs/monorepo-design.md @@ -87,7 +87,7 @@ light-ocr/ ├── bin: light-ocr-document(兼容) └── 转发主包 Document API;不含 renderer 或安装脚本 -六个平台 native package +八个平台 native package ├── OCR Node-API addon + ONNX Runtime ├── PDFium Node-API addon + 相邻共享库 └── npm tarball 自包含;无 postinstall 或运行时下载 diff --git a/docs/napi-design.md b/docs/napi-design.md index a54937f..20dbc57 100644 --- a/docs/napi-design.md +++ b/docs/napi-design.md @@ -13,7 +13,7 @@ Decision:[decisions.md](decisions.md) D101、D105、D111 第一版 Node.js 适配器是 C++ Core 的异步、无损映射: - 使用原始 Node-API C 接口,编译目标固定为 `NAPI_VERSION=8`。 -- 支持 Node.js 22 和 24;Node.js 26 先做前向兼容 smoke test,不在其进入 LTS 并通过完整矩阵前声明正式支持。 +- 支持 Node.js 22 及以上(`engines.node` 下限式 `>=22.0.0`);Tier 1 完整测试覆盖 22/24 LTS,Current 线(如 26)以 CI smoke 保障,Node-API 稳定 ABI 保证新大版本无需库发版即可加载同一份预编译产物。 - 每个 JavaScript `OcrEngine` 拥有一个 C++ Core `Engine` 和一条专用原生工作线程。 - 每个 engine 使用有界 FIFO 队列;不同 engine 可以并行,同一 engine 永不并发进入 Core。 - `createEngine`、`recognize` 和 `close` 都返回 Promise,OCR 推理不占用 JavaScript 线程,也不占用 Node.js/libuv 共享工作池。 @@ -602,12 +602,13 @@ JavaScript package 的正式支持矩阵是: | --- | --- | | Node.js 22.x | Tier 1,完整测试 | | Node.js 24.x | Tier 1,完整测试 | +| Node.js 25.x | 非 LTS 线,已 EOL;engines 下限式允许安装,不做支持承诺 | | Node.js 26.x | Current smoke;进入 LTS 后再提升为 Tier 1 | | Node.js 20 及更早 | 不支持 | | Electron | Windows addon host 重定向与 Electron 37 CPU smoke;完整 major/ASAR/worker/lifecycle matrix 完成前不提升为 Tier 1 | | Bun | 未声明;需按其 Node-API 实现做兼容验证 | -`package.json.engines.node` 在 v1 发布时写为 `^22.0.0 || ^24.0.0`;Node 26 提升为 Tier 1 后再加入,不能把 smoke test 写成正式承诺。 +`package.json.engines.node` 采用下限式 `>=22.0.0`,只声明真实支持的最低版本,不枚举 Node 大版本:Node-API 是稳定 ABI,`NAPI_VERSION=8` 的 addon 无需重编译即可在后续所有 Node 大版本(含 26 及未来版本)加载,因此新 Node 大版本发布不需要库跟发版本。各版本的实际支持程度由上表 Tier 矩阵声明、由 CI gate 保障,两者与 engines 分离。不枚举的另一原因:npm 默认对 engines 不匹配只警告,而 Yarn 系默认硬失败——枚举式意味着每个新 Current 版本发布当天 Yarn 用户无法安装,直到库发版。 ## 12. npm 与二进制布局 @@ -644,9 +645,11 @@ bindings/node/ @arcships/light-ocr-win32-arm64 addon + onnxruntime.dll + licenses @arcships/light-ocr-linux-x64-gnu addon + ONNX Runtime so + licenses @arcships/light-ocr-linux-arm64-gnu addon + ONNX Runtime so + licenses +@arcships/light-ocr-linux-x64-musl addon + musl ONNX Runtime so + licenses +@arcships/light-ocr-linux-arm64-musl addon + musl ONNX Runtime so + licenses ``` -Facade 同时提供 ESM 和 CommonJS exports,但两者加载同一个 environment-aware `.node` addon。native 子路径不作为 public export。model package 是 exact-version 普通 dependency;六个平台包是 exact-version optional dependencies,由 `os`、`cpu` 和 Linux `libc` metadata 筛选。 +Facade 同时提供 ESM 和 CommonJS exports,但两者加载同一个 environment-aware `.node` addon。native 子路径不作为 public export。model package 是 exact-version 普通 dependency;八个平台包是 exact-version optional dependencies,由 `os`、`cpu` 和 Linux `libc` metadata 筛选。 安装规则: @@ -658,7 +661,7 @@ Facade 同时提供 ESM 和 CommonJS exports,但两者加载同一个 environm - source build 是显式开发命令,不是 install fallback。 - 使用 `--omit=optional` 会缺少 native package;facade 必须返回可操作的 `package_load_failed`,不能尝试下载或编译。 -Node-API 解决 Node/V8 ABI 兼容,不消除 OS、architecture、libc、C++ runtime 和 ONNX Runtime 的平台差异。因此仍需六个平台的原生构建和加载测试。 +Node-API 解决 Node/V8 ABI 兼容,不消除 OS、architecture、libc、C++ runtime 和 ONNX Runtime 的平台差异。因此仍需八个平台的原生构建和加载测试。 ## 13. 构建边界 @@ -717,13 +720,13 @@ Node-API 解决 Node/V8 ABI 兼容,不消除 OS、architecture、libc、C++ ru ### 14.6 npm package contract -- 从八个本地 `.tgz` 在 sterile 临时目录安装;CJS、ESM 和 types 都只能使用 package 内容,不能回读仓库。 +- 从十个本地 `.tgz`(八个平台包、model、facade)在 sterile 临时目录安装;CJS、ESM 和 types 都只能使用 package 内容,不能回读仓库。 - `createEngine()`、`createEngine({})` 和 `model: "ppocrv6-small"` 都加载同一 bundle ID;显式 `bundlePath` 仍工作;`model` 与 `bundlePath` 同时提供时拒绝。 - model package 缺失、bundle ID 不匹配、支持平台 package 缺失、unsupported platform 和 `--omit=optional` 分别得到稳定、可操作的错误。 - `--ignore-scripts` 安装正常;已安装后禁网运行正常;没有 postinstall、下载、解压或源码编译副作用。 - `npm pack --dry-run` inventory、tarball hashes、model payload hashes、licenses、SBOM 和 package exact versions 全部核对。 -完整 release gate 在 macOS arm64、macOS x64、Windows x64、Linux x64 GNU 上分别运行 Node 22 和 24;Node 26 运行加载、创建、golden、close 和 worker teardown smoke。 +完整 release gate 在 macOS arm64、macOS x64、Windows x64、Linux x64 GNU 上分别运行 Node 22 和 24;Linux x64/arm64 musl 在 Alpine 容器以发行版 Node 22 运行同一安装与 OCR/PDF 验证;Node 26 运行加载、创建、golden、close 和 worker teardown smoke。 ## 15. 实施顺序 @@ -748,5 +751,6 @@ Node-API 解决 Node/V8 ABI 兼容,不消除 OS、architecture、libc、C++ ru | zero-copy/transfer | 延期 | 能证明 mutation、detachment、Worker 和 teardown 安全 | | running inference 硬中断 | 延期 | Core 或隔离层提供经过验证的安全 interruption | | Electron/Bun | 延期 | 独立 runtime/version/prebuild/lifecycle matrix 全绿 | -| Linux musl/arm64、Windows arm64 | 延期 | Core 和 addon 原生 Tier 1 证据齐全 | +| Windows arm64 WebGPU | 延期 | Core 和 addon 原生 Tier 1 证据齐全 | +| musl WebGPU | 延期 | musl CPU-only 平台已交付;Dawn/musl 需独立资格矩阵 | | GPU EP | 延期 | 独立 bundle、设备选择、并发、内存和发布策略 | diff --git a/docs/npm-packaging.md b/docs/npm-packaging.md index 41fb5fb..ef88699 100644 --- a/docs/npm-packaging.md +++ b/docs/npm-packaging.md @@ -1,7 +1,7 @@ # @arcships/light-ocr npm Package Design -状态:六包设计与 `0.3.0` Apple/WebGPU native superset bundle 已完成 lockstep 发布,并提升为 npm `latest`
-更新时间:2026-07-19
+状态:六包设计在 `0.3.0` 完成 lockstep 发布并提升为 npm `latest`;musl x64/arm64 平台包已加入,当前共十个公开 scoped packages
+更新时间:2026-09-24
Authority:npm 包名、包拆分、依赖关系、内置模型、版本与发布门槛
Node API:[napi-design.md](napi-design.md)
Model contract:[model-bundle.md](model-bundle.md)
@@ -43,7 +43,7 @@ const engine = await createEngine(); ## 2. 包集合与依赖图 -首个 release set 固定为八个公开 scoped packages: +当前 release set 固定为十个公开 scoped packages: | 包 | 类型 | 内容 | 安装关系 | | --- | --- | --- | --- | @@ -55,6 +55,8 @@ const engine = await createEngine(); | `@arcships/light-ocr-win32-arm64` | native | arm64 `.node`、`onnxruntime.dll`、licenses、SBOM、hashes | facade 的 optional dependency | | `@arcships/light-ocr-linux-x64-gnu` | native | glibc x64 `.node`、ONNX Runtime `.so`、licenses、SBOM、hashes | facade 的 optional dependency | | `@arcships/light-ocr-linux-arm64-gnu` | native | glibc arm64 `.node`、ONNX Runtime `.so`、licenses、SBOM、hashes | facade 的 optional dependency | +| `@arcships/light-ocr-linux-x64-musl` | native | musl x64 `.node`、源码编译的 musl ONNX Runtime `.so`、licenses、SBOM、hashes | facade 的 optional dependency | +| `@arcships/light-ocr-linux-arm64-musl` | native | musl arm64 `.node`、源码编译的 musl ONNX Runtime `.so`、licenses、SBOM、hashes | facade 的 optional dependency | 依赖方向只有两层: @@ -67,7 +69,9 @@ const engine = await createEngine(); ├── @arcships/light-ocr-win32-x64 ├── @arcships/light-ocr-win32-arm64 ├── @arcships/light-ocr-linux-x64-gnu - └── @arcships/light-ocr-linux-arm64-gnu + ├── @arcships/light-ocr-linux-arm64-gnu + ├── @arcships/light-ocr-linux-x64-musl + └── @arcships/light-ocr-linux-arm64-musl ``` 模型包不能是 optional dependency。否则 `--omit=optional`、平台筛选或安装器的可选依赖容错会让一次“成功安装”缺少默认模型,违背主包契约。native 包必须是 optional dependencies,因为同一机器只需要一个平台产物。 @@ -95,7 +99,7 @@ v1 不提供无模型的 `core`/`lite` 入口,也不允许用户单独拼装 f } }, "files": ["js/", "README.md", "LICENSE", "NOTICE"], - "engines": { "node": "^22.0.0 || ^24.0.0" }, + "engines": { "node": ">=22.0.0" }, "dependencies": { "@arcships/light-ocr-model-ppocrv6-small": "0.3.0" }, @@ -104,6 +108,8 @@ v1 不提供无模型的 `core`/`lite` 入口,也不允许用户单独拼装 f "@arcships/light-ocr-darwin-x64": "0.3.0", "@arcships/light-ocr-linux-x64-gnu": "0.3.0", "@arcships/light-ocr-linux-arm64-gnu": "0.3.0", + "@arcships/light-ocr-linux-x64-musl": "0.3.0", + "@arcships/light-ocr-linux-arm64-musl": "0.3.0", "@arcships/light-ocr-win32-x64": "0.3.0", "@arcships/light-ocr-win32-arm64": "0.3.0" }, @@ -170,7 +176,7 @@ Facade 在创建 engine 前读取 manifest,并核对 `bundleId` 是否等于 "cpu": ["x64"], "libc": ["glibc"], "files": ["native/", "licenses/", "sbom.spdx.json", "artifact-hashes.json", "README.md", "LICENSE", "NOTICE"], - "engines": { "node": "^22.0.0 || ^24.0.0" }, + "engines": { "node": ">=22.0.0" }, "publishConfig": { "access": "public" } } ``` @@ -187,6 +193,8 @@ Facade 只按固定映射加载 package: | `win32` | `arm64` | `@arcships/light-ocr-win32-arm64` | | `linux` + glibc | `x64` | `@arcships/light-ocr-linux-x64-gnu` | | `linux` + glibc | `arm64` | `@arcships/light-ocr-linux-arm64-gnu` | +| `linux` + musl | `x64` | `@arcships/light-ocr-linux-x64-musl` | +| `linux` + musl | `arm64` | `@arcships/light-ocr-linux-arm64-musl` | 未知组合以 `unsupported_platform` 拒绝 `createEngine()`。已支持组合但 native package 缺失时,以 `package_load_failed` 拒绝,并提示重新安装且不要使用 `--omit=optional`;不能静默源码编译或在线下载二进制。开发环境仍可显式设置 `LIGHT_OCR_NODE_BINARY`,但 published README 不把它当作生产配置。 @@ -248,24 +256,24 @@ export function createEngine(options?: CreateEngineOptions): Promise; ## 8. 构建与发布流程 -仓库不提交模型二进制副本。N2 release staging 从 workspace、两个锁定 preview bundle 和六个平台 native input 组装: +仓库不提交模型二进制副本。N2 release staging 从 workspace、两个锁定 preview bundle 和八个平台 native input 组装: ```text packages/runtime + packages/light-ocr{,-tiny,-medium} models/generated/ppocrv6-{tiny,medium}-onnx-* dist/native-input/ ↓ -dist/npm/ +dist/npm/ ``` `dist/npm` 是临时生成目录,不是源码 authority。打包器必须使用 `files` allowlist,并拒绝 source、test fixture、cache、绝对路径、symlink、额外动态库和未登记文件。 发布顺序保持可恢复,但不重复已经在资格工作流完成的验证: -1. 六个平台仅构建 addon/runtime payload 并生成 license、SBOM 与 descriptor,不在 release 内重跑 Core/qualification。 -2. assembly job 只生成 Tiny/Medium bundle 一次,复用已发布 Small model `0.3.4`,组装十二个 staging directories。 +1. 八个平台仅构建 addon/runtime payload 并生成 license、SBOM 与 descriptor,不在 release 内重跑 Core/qualification。 +2. assembly job 只生成 Tiny/Medium bundle 一次,复用已发布 Small model `0.3.4`,组装十五个 staging directories。 3. 每个目录执行一次 `npm pack --json --ignore-scripts`,核对 inventory,并记录 filename、bytes、SHA-256 和 npm integrity;不做无收益的第二次压包。 -4. 六个平台离线安装各自 native + runtime + Small facade 并跑真实 OCR;代表性 Linux x64 额外跑 Tiny/Medium。 +4. 八个平台离线安装各自 native + runtime + Small facade 并跑真实 OCR;代表性 Linux x64 额外跑 Tiny/Medium。 5. 先发布 native、runtime 和两个 preview model 到 `next`,registry 能解析稳定 facade 后再发布三个 facade。 6. promotion 只移动 native/runtime/Small 的 stable closure;Tiny/Medium 保持 `next`,直到 G2 有实际晋升证据。 @@ -283,7 +291,7 @@ dist/npm/ 一个 N2 npm release candidate 至少满足: -- 六个 native packages 分别在目标 OS/arch 原生构建并完成安装后真实 Small OCR;生命周期、sanitizer、fuzz 和 provider qualification 由 Core/qualification workflow 负责,不在 release 重复。 +- 八个 native packages 分别在目标 OS/arch 原生构建并完成安装后真实 Small OCR;生命周期、sanitizer、fuzz 和 provider qualification 由 Core/qualification workflow 负责,不在 release 重复。 - `npm pack` inventory 与 allowlist 完全一致;package 内没有源码缓存、测试图像、原始上游 archive 或绝对构建路径。 - Facade 的 ESM、CJS 和 TypeScript compile tests 均通过。 - Small、Tiny、Medium 都能在安装后的隔离目录中不传 `bundlePath` 完成真实 PP-OCRv6 识别;Tiny/Medium 只要求代表性平台 preview smoke。 @@ -291,7 +299,7 @@ dist/npm/ - 在网络禁用环境里,对已经安装好的 package 重复 create/recognize/close 成功。 - 模型 package 的 bundle 文件总字节、manifest、`SHA256SUMS` 和 bundle ID 与 `models/bundles.lock.json` 对应生成物一致。 - native package 的 addon、ORT library、artifact hashes、license inventory 和 SPDX SBOM 一致。 -- 从干净 release commit 生成并记录十二个 npm tarballs 的 SHA-256、registry integrity、dist-tag 和 CI artifact URL。 +- 从干净 release commit 生成并记录十五个 npm tarballs(外加复用的 Small model tarball)的 SHA-256、registry integrity、dist-tag 和 CI artifact URL。 - 仓库根 `LICENSE`/`NOTICE`、facade/native package 的 SPDX `license` 字段与 Apache-2.0 一致。 ## 10. `0.1.0` 首发时明确不做(历史约束) @@ -301,5 +309,5 @@ dist/npm/ - 将模型直接复制进六个 native packages;这会造成六份重复分发。 - 当时不做无模型 facade、按需语言包、tiny/medium、orientation 或 GPU packages;N2 的 runtime/tier 与 `0.3.0` provider 分发已由后续决策替代这些历史范围。 - 源码编译 fallback、`node-gyp` fallback 或消费者系统 ORT fallback。 -- Electron、Bun、Deno、Linux musl 支持声明。 +- Electron、Bun、Deno、Linux musl 支持声明。(Linux musl 的 CPU-only 平台包已由后续 PR 交付;Electron/Bun/Deno 仍不做支持声明。) - Yarn Plug'n'Play/zip archive 兼容声明;v1 release gate 以官方 npm 的物理安装目录为准。 diff --git a/models/deps.lock.json b/models/deps.lock.json index 24c66cc..1105ed6 100644 --- a/models/deps.lock.json +++ b/models/deps.lock.json @@ -24,6 +24,52 @@ "windows-arm64" ] }, + { + "name": "onnxruntime", + "version": "1.22.0", + "filename": "onnxruntime-musl-1.22.0-linux-x64.zip", + "source": "https://github.com/arcships/light-ocr/releases/download/musl-runtime-1.22.0/onnxruntime-musl-1.22.0-linux-x64.zip", + "bytes": 9202413, + "sha256": "1b5aaa7c54c0a575e44a09e424168a35e2b3d005349a6e87dad24f718c5b6ab9", + "license": "MIT", + "buildOptions": { + "executionProvider": "CPU", + "libc": "musl", + "buildImage": "alpine:3.22", + "unitTests": false + }, + "patches": [ + "musl-stacktrace-execinfo-guard" + ], + "id": "onnxruntime-cpu-musl-x64", + "runtimeFlavor": "cpu", + "platforms": [ + "linux-x64-musl" + ] + }, + { + "name": "onnxruntime", + "version": "1.22.0", + "filename": "onnxruntime-musl-1.22.0-linux-arm64.zip", + "source": "https://github.com/arcships/light-ocr/releases/download/musl-runtime-1.22.0/onnxruntime-musl-1.22.0-linux-arm64.zip", + "bytes": 8449435, + "sha256": "50705761fb84c69c1c2ea0f31a968a58f0810c115e73d24db65fcb869abc4ac3", + "license": "MIT", + "buildOptions": { + "executionProvider": "CPU", + "libc": "musl", + "buildImage": "alpine:3.22", + "unitTests": false + }, + "patches": [ + "musl-stacktrace-execinfo-guard" + ], + "id": "onnxruntime-cpu-musl-arm64", + "runtimeFlavor": "cpu", + "platforms": [ + "linux-arm64-musl" + ] + }, { "name": "opencv", "version": "4.10.0", diff --git a/package-lock.json b/package-lock.json index 4128631..65cf088 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1103,7 +1103,7 @@ "light-ocr": "src/cli.cjs" }, "engines": { - "node": "^22.0.0 || ^24.0.0" + "node": ">=22.0.0" } }, "packages/light-ocr-document": { @@ -1117,7 +1117,7 @@ "light-ocr-document": "src/cli.cjs" }, "engines": { - "node": "^22.0.0 || ^24.0.0" + "node": ">=22.0.0" } }, "packages/light-ocr-medium": { @@ -1132,7 +1132,7 @@ "light-ocr-medium": "src/cli.cjs" }, "engines": { - "node": "^22.0.0 || ^24.0.0" + "node": ">=22.0.0" } }, "packages/light-ocr-server": { @@ -1145,7 +1145,7 @@ "multer": "2.2.0" }, "engines": { - "node": "^22.0.0 || ^24.0.0" + "node": ">=22.0.0" } }, "packages/light-ocr-server/node_modules/@arcships/light-ocr": { @@ -1289,7 +1289,7 @@ "light-ocr-tiny": "src/cli.cjs" }, "engines": { - "node": "^22.0.0 || ^24.0.0" + "node": ">=22.0.0" } }, "packages/model-ppocrv6-medium": { @@ -1307,13 +1307,15 @@ "version": "0.1.7", "license": "Apache-2.0", "engines": { - "node": "^22.0.0 || ^24.0.0" + "node": ">=22.0.0" }, "optionalDependencies": { "@arcships/light-ocr-darwin-arm64": "0.5.7", "@arcships/light-ocr-darwin-x64": "0.5.7", "@arcships/light-ocr-linux-arm64-gnu": "0.5.7", + "@arcships/light-ocr-linux-arm64-musl": "0.5.7", "@arcships/light-ocr-linux-x64-gnu": "0.5.7", + "@arcships/light-ocr-linux-x64-musl": "0.5.7", "@arcships/light-ocr-win32-arm64": "0.5.7", "@arcships/light-ocr-win32-x64": "0.5.7" } diff --git a/packages/light-ocr-document/package.json b/packages/light-ocr-document/package.json index 64beb8d..9cb1d16 100644 --- a/packages/light-ocr-document/package.json +++ b/packages/light-ocr-document/package.json @@ -30,7 +30,7 @@ "NOTICE" ], "engines": { - "node": "^22.0.0 || ^24.0.0" + "node": ">=22.0.0" }, "dependencies": { "@arcships/light-ocr": "0.5.7" diff --git a/packages/light-ocr-medium/package.json b/packages/light-ocr-medium/package.json index 1ce143c..f08018b 100644 --- a/packages/light-ocr-medium/package.json +++ b/packages/light-ocr-medium/package.json @@ -25,7 +25,7 @@ "NOTICE" ], "engines": { - "node": "^22.0.0 || ^24.0.0" + "node": ">=22.0.0" }, "dependencies": { "@arcships/light-ocr-model-ppocrv6-medium": "0.1.0", diff --git a/packages/light-ocr-server/package.json b/packages/light-ocr-server/package.json index 980b077..27803dc 100644 --- a/packages/light-ocr-server/package.json +++ b/packages/light-ocr-server/package.json @@ -19,7 +19,7 @@ "Dockerfile" ], "engines": { - "node": "^22.0.0 || ^24.0.0" + "node": ">=22.0.0" }, "scripts": { "start": "node src/server.js", diff --git a/packages/light-ocr-tiny/package.json b/packages/light-ocr-tiny/package.json index 22f160d..f5e935c 100644 --- a/packages/light-ocr-tiny/package.json +++ b/packages/light-ocr-tiny/package.json @@ -25,7 +25,7 @@ "NOTICE" ], "engines": { - "node": "^22.0.0 || ^24.0.0" + "node": ">=22.0.0" }, "dependencies": { "@arcships/light-ocr-model-ppocrv6-tiny": "0.1.0", diff --git a/packages/light-ocr/README.md b/packages/light-ocr/README.md index 0c8dcf7..d93732f 100644 --- a/packages/light-ocr/README.md +++ b/packages/light-ocr/README.md @@ -1,7 +1,7 @@ # @arcships/light-ocr [![npm version](https://img.shields.io/npm/v/%40arcships%2Flight-ocr?color=CB3837)](https://www.npmjs.com/package/@arcships/light-ocr) -[![Node.js 22 and 24](https://img.shields.io/badge/Node.js-22%20%7C%2024-339933)](https://nodejs.org/) +[![Node.js 22+](https://img.shields.io/badge/Node.js-22%2B-339933)](https://nodejs.org/) [![Apache-2.0](https://img.shields.io/npm/l/%40arcships%2Flight-ocr)](https://github.com/arcships/light-ocr/blob/main/LICENSE) ![light-ocr](https://raw.githubusercontent.com/arcships/light-ocr/main/docs/assets/light-ocr-banner.png) @@ -18,8 +18,8 @@ cloud APIs, postinstall downloads, or local native compilation. | **Input** | JPEG, PNG, PDF, `Uint8Array`, or decoded pixel buffers | | **Output** | text lines in reading order, confidence, quadrilateral boxes, and timing | | **Runtime** | CommonJS + ESM + bundled TypeScript declarations | -| **Platforms** | macOS, Linux glibc, and Windows on x64 and ARM64 | -| **Node.js** | 22 and 24 | +| **Platforms** | macOS, Linux (glibc and musl), and Windows on x64 and ARM64 | +| **Node.js** | 22 or newer; the 22/24 LTS lines are Tier 1, and the stable Node-API ABI lets newer majors load the same prebuilt binaries | ## Install @@ -244,6 +244,8 @@ and ends with CPU as the stable fallback: | macOS on Intel | CPU | | Linux x64 with glibc | WebGPU through Vulkan, then CPU | | Linux arm64 with glibc | CPU | +| Linux x64 with musl (Alpine) | CPU | +| Linux arm64 with musl (Alpine) | CPU | | Windows x64 | WebGPU through D3D12, then CPU | | Windows arm64 | CPU | diff --git a/packages/light-ocr/package.json b/packages/light-ocr/package.json index da6d38b..b64a1cc 100644 --- a/packages/light-ocr/package.json +++ b/packages/light-ocr/package.json @@ -26,7 +26,7 @@ "NOTICE" ], "engines": { - "node": "^22.0.0 || ^24.0.0" + "node": ">=22.0.0" }, "dependencies": { "@arcships/light-ocr-model-ppocrv6-small": "0.3.4", diff --git a/packages/light-ocr/src/document.cjs b/packages/light-ocr/src/document.cjs index 0e39726..59701db 100644 --- a/packages/light-ocr/src/document.cjs +++ b/packages/light-ocr/src/document.cjs @@ -3,7 +3,7 @@ const fs = require('node:fs/promises'); const path = require('node:path'); -const { OcrError } = require('@arcships/light-ocr-runtime'); +const { OcrError, platformIdentity } = require('@arcships/light-ocr-runtime'); const DEFAULTS = Object.freeze({ dpi: 150, @@ -19,14 +19,16 @@ let defaultCreateEngine; function platformPdfiumPackage() { const packages = { - 'darwin-arm64': '@arcships/light-ocr-darwin-arm64', - 'darwin-x64': '@arcships/light-ocr-darwin-x64', + 'macos-arm64': '@arcships/light-ocr-darwin-arm64', + 'macos-x64': '@arcships/light-ocr-darwin-x64', 'linux-arm64': '@arcships/light-ocr-linux-arm64-gnu', 'linux-x64': '@arcships/light-ocr-linux-x64-gnu', - 'win32-arm64': '@arcships/light-ocr-win32-arm64', - 'win32-x64': '@arcships/light-ocr-win32-x64', + 'linux-arm64-musl': '@arcships/light-ocr-linux-arm64-musl', + 'linux-x64-musl': '@arcships/light-ocr-linux-x64-musl', + 'windows-arm64': '@arcships/light-ocr-win32-arm64', + 'windows-x64': '@arcships/light-ocr-win32-x64', }; - return packages[`${process.platform}-${process.arch}`]; + return packages[platformIdentity().id]; } function loadPdfium() { diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 683f273..cae44df 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -25,13 +25,15 @@ "NOTICE" ], "engines": { - "node": "^22.0.0 || ^24.0.0" + "node": ">=22.0.0" }, "optionalDependencies": { "@arcships/light-ocr-darwin-arm64": "0.5.7", "@arcships/light-ocr-darwin-x64": "0.5.7", "@arcships/light-ocr-linux-arm64-gnu": "0.5.7", + "@arcships/light-ocr-linux-arm64-musl": "0.5.7", "@arcships/light-ocr-linux-x64-gnu": "0.5.7", + "@arcships/light-ocr-linux-x64-musl": "0.5.7", "@arcships/light-ocr-win32-arm64": "0.5.7", "@arcships/light-ocr-win32-x64": "0.5.7" }, diff --git a/packages/runtime/src/index.cjs b/packages/runtime/src/index.cjs index 0c16b65..e6a2757 100644 --- a/packages/runtime/src/index.cjs +++ b/packages/runtime/src/index.cjs @@ -1,6 +1,6 @@ 'use strict'; -const { loadNative } = require('./load-native.cjs'); +const { loadNative, platformIdentity } = require('./load-native.cjs'); class OcrError extends Error { constructor(code, message, detail) { @@ -174,4 +174,4 @@ async function createEngine(options) { } } -module.exports = { createEngine, OcrError, loadNative }; +module.exports = { createEngine, OcrError, loadNative, platformIdentity }; diff --git a/packages/runtime/src/index.d.ts b/packages/runtime/src/index.d.ts index 05fb426..aead06c 100644 --- a/packages/runtime/src/index.d.ts +++ b/packages/runtime/src/index.d.ts @@ -289,4 +289,15 @@ export interface DetectionResult { readonly timingUs: TimingUs; } +/** Resolved host platform identity used to select the native package. */ +export interface PlatformIdentity { + readonly id: string; + readonly os: string; + readonly architecture: string; + /** Present for Linux hosts; `"glibc"` or `"musl"`. */ + readonly libc?: 'glibc' | 'musl'; +} + +export function platformIdentity(): PlatformIdentity; + export function createEngine(options: CreateEngineOptions): Promise; diff --git a/packages/runtime/src/index.mjs b/packages/runtime/src/index.mjs index ff86074..f0fac62 100644 --- a/packages/runtime/src/index.mjs +++ b/packages/runtime/src/index.mjs @@ -2,3 +2,4 @@ import cjs from './index.cjs'; export const createEngine = cjs.createEngine; export const OcrError = cjs.OcrError; +export const platformIdentity = cjs.platformIdentity; diff --git a/packages/runtime/src/load-native.cjs b/packages/runtime/src/load-native.cjs index 45fd5c1..dcae2d2 100644 --- a/packages/runtime/src/load-native.cjs +++ b/packages/runtime/src/load-native.cjs @@ -104,6 +104,22 @@ function adapterError(code, message, detail, cause) { return error; } +// Detect musl Linux (Alpine and friends). Only reached when the host does not +// report glibcVersionRuntime through process.report. The musl dynamic linker +// path is the primary signal; the ldd output check covers hosts with a +// non-standard loader location. +function isMuslLinux() { + if (process.platform !== 'linux') return false; + const loader = process.arch === 'arm64' + ? '/lib/ld-musl-aarch64.so.1' + : '/lib/ld-musl-x86_64.so.1'; + if (fs.existsSync(loader)) return true; + const ldd = spawnSync('ldd', ['--version'], { encoding: 'utf8', timeout: 5000 }); + if (ldd.error) return false; + const output = `${ldd.stdout || ''}${ldd.stderr || ''}`; + return output.toLowerCase().includes('musl'); +} + function platformIdentity() { const key = `${process.platform}-${process.arch}`; const identities = { @@ -117,9 +133,12 @@ function platformIdentity() { if (report?.header?.glibcVersionRuntime) { return { id: 'linux-x64', os: 'linux', architecture: 'x86_64', libc: 'glibc' }; } + if (isMuslLinux()) { + return { id: 'linux-x64-musl', os: 'linux', architecture: 'x86_64', libc: 'musl' }; + } throw adapterError( 'unsupported_platform', - 'light-ocr currently supports Linux x64 with glibc only', + 'light-ocr currently supports Linux x64 with glibc or musl only', key, ); } @@ -128,9 +147,12 @@ function platformIdentity() { if (report?.header?.glibcVersionRuntime) { return { id: 'linux-arm64', os: 'linux', architecture: 'arm64', libc: 'glibc' }; } + if (isMuslLinux()) { + return { id: 'linux-arm64-musl', os: 'linux', architecture: 'arm64', libc: 'musl' }; + } throw adapterError( 'unsupported_platform', - 'light-ocr currently supports Linux arm64 with glibc only', + 'light-ocr currently supports Linux arm64 with glibc or musl only', key, ); } @@ -149,6 +171,8 @@ function platformPackage() { 'windows-x64': '@arcships/light-ocr-win32-x64', 'linux-x64': '@arcships/light-ocr-linux-x64-gnu', 'linux-arm64': '@arcships/light-ocr-linux-arm64-gnu', + 'linux-x64-musl': '@arcships/light-ocr-linux-x64-musl', + 'linux-arm64-musl': '@arcships/light-ocr-linux-arm64-musl', }; return packages[platformIdentity().id]; } @@ -606,4 +630,4 @@ const macOSSignature = Object.freeze({ signerMatchesHost, }); -module.exports = { loadNative, validateRuntimeDescriptor, macOSSignature }; +module.exports = { loadNative, validateRuntimeDescriptor, macOSSignature, platformIdentity }; diff --git a/tests/python/test_bootstrap_dependencies.py b/tests/python/test_bootstrap_dependencies.py index 22c204f..5009381 100644 --- a/tests/python/test_bootstrap_dependencies.py +++ b/tests/python/test_bootstrap_dependencies.py @@ -99,6 +99,46 @@ def test_selects_common_and_one_matching_runtime(self) -> None: ) self.assertEqual([record["id"] for record in selected], ["ort-webgpu", "opencv"]) + def test_cache_complete_mode_selects_every_cpu_runtime(self) -> None: + # Without a platform id, bootstrap populates a shared dependency cache: + # every CPU runtime entry (the glibc NuGet and both musl archives) is + # fetched, while an exact platform selection still resolves to one. + lock = { + "dependencies": [ + {"id": "ort-cpu-gnu", "runtimeFlavor": "cpu", "platforms": ["linux-x64-gnu"]}, + {"id": "ort-cpu-musl-x64", "runtimeFlavor": "cpu", "platforms": ["linux-x64-musl"]}, + {"id": "ort-cpu-musl-arm64", "runtimeFlavor": "cpu", "platforms": ["linux-arm64-musl"]}, + {"id": "opencv", "runtimeFlavor": "common", "platforms": ["all"]}, + ] + } + selected = bootstrap_dependencies.select_dependencies( + lock, platform_id=None, runtime_flavor="cpu" + ) + self.assertEqual( + [record["id"] for record in selected], + ["ort-cpu-gnu", "ort-cpu-musl-x64", "ort-cpu-musl-arm64", "opencv"], + ) + exact = bootstrap_dependencies.select_dependencies( + lock, platform_id="linux-x64-musl", runtime_flavor="cpu" + ) + self.assertEqual( + [record["id"] for record in exact], ["ort-cpu-musl-x64", "opencv"] + ) + + def test_cache_complete_mode_still_rejects_webgpu(self) -> None: + # The webgpu flavor never resolves from deps.lock.json (external SDK + # boundary), including in cache-complete mode. + lock = { + "dependencies": [ + {"id": "ort-cpu-gnu", "runtimeFlavor": "cpu", "platforms": ["linux-x64-gnu"]}, + {"id": "opencv", "runtimeFlavor": "common", "platforms": ["all"]}, + ] + } + with self.assertRaisesRegex(RuntimeError, "externally verified SDK"): + bootstrap_dependencies.select_dependencies( + lock, platform_id=None, runtime_flavor="webgpu" + ) + def test_explains_external_webgpu_sdk_boundary(self) -> None: lock = { "dependencies": [ diff --git a/tests/python/test_ci_workflow_contracts.py b/tests/python/test_ci_workflow_contracts.py index 81fcaf9..0a90f09 100644 --- a/tests/python/test_ci_workflow_contracts.py +++ b/tests/python/test_ci_workflow_contracts.py @@ -34,6 +34,27 @@ def test_webgpu_native_metadata_is_model_free(self) -> None: self.assertIn("python tools/generate_release_metadata.py", metadata) self.assertIn("--model-free", metadata) + def test_musl_release_jobs_pin_the_target_and_dependency_selection(self) -> None: + source = (ROOT / ".github/workflows/npm-release.yml").read_text("utf-8") + musl_job = source[source.index("build-native-musl"):] + + self.assertIn("smoke-musl", musl_job) + self.assertIn("container: alpine:3.22", musl_job) + bootstrap = musl_job[musl_job.index("Bootstrap pinned native dependencies"):] + self.assertIn("--platform-id", bootstrap[:1200]) + configure = musl_job[musl_job.index("Configure native package"):] + self.assertIn("-DLIGHT_OCR_TARGET_LIBC=musl", configure[:2000]) + + def test_musl_runtime_rebuild_workflow_pins_the_expected_artifacts(self) -> None: + source = (ROOT / ".github/workflows/onnxruntime-musl.yml").read_text("utf-8") + + self.assertIn("musl-runtime-", source) + self.assertIn("onnxruntime_BUILD_UNIT_TESTS=OFF", source) + self.assertIn("FETCHCONTENT_SOURCE_DIR_EIGEN3", source) + self.assertIn( + "runtimes/linux-${{ matrix.arch }}-musl/native/libonnxruntime.so", source + ) + if __name__ == "__main__": unittest.main() diff --git a/tools/bootstrap_dependencies.py b/tools/bootstrap_dependencies.py index fe83f51..0fbc590 100644 --- a/tools/bootstrap_dependencies.py +++ b/tools/bootstrap_dependencies.py @@ -165,6 +165,10 @@ def select_dependencies( platform_id is None or platform_id in platforms ): runtime_matches.append(value) + if platform_id is None and runtime_flavor == "cpu": + # Cache-complete mode: fetch every CPU runtime entry (glibc NuGet and + # the musl builds) so one shared cache can serve any platform build. + return [*runtime_matches, *selected] if len(runtime_matches) != 1: identity = platform_id or "unspecified platform" if runtime_flavor == "webgpu" and not runtime_matches: diff --git a/tools/npm/document-smoke.cjs b/tools/npm/document-smoke.cjs index 99ae574..2c13341 100644 --- a/tools/npm/document-smoke.cjs +++ b/tools/npm/document-smoke.cjs @@ -1,6 +1,7 @@ 'use strict'; const assert = require('node:assert/strict'); +const fs = require('node:fs'); const path = require('node:path'); const { createRequire } = require('node:module'); @@ -14,16 +15,41 @@ const { createTextPdf, } = require('./pdf-fixture.cjs'); -function nativePackageName() { +// Self-contained host platform detection. This smoke tool runs against +// several installed closures, including published packages that predate the +// runtime's platformIdentity() export, so it cannot depend on that API. +function hostPlatformId() { const key = `${process.platform}-${process.arch}`; + const staticIds = { + 'darwin-arm64': 'macos-arm64', + 'darwin-x64': 'macos-x64', + 'win32-arm64': 'windows-arm64', + 'win32-x64': 'windows-x64', + }; + if (staticIds[key]) return staticIds[key]; + if (process.platform !== 'linux') return undefined; + const arm64 = process.arch === 'arm64'; + if (process.report?.getReport?.()?.header?.glibcVersionRuntime) { + return arm64 ? 'linux-arm64' : 'linux-x64'; + } + const loader = `/lib/ld-musl-${arm64 ? 'aarch64' : 'x86_64'}.so.1`; + if (fs.existsSync(loader)) { + return arm64 ? 'linux-arm64-musl' : 'linux-x64-musl'; + } + return undefined; +} + +function nativePackageName() { return { - 'darwin-arm64': '@arcships/light-ocr-darwin-arm64', - 'darwin-x64': '@arcships/light-ocr-darwin-x64', + 'macos-arm64': '@arcships/light-ocr-darwin-arm64', + 'macos-x64': '@arcships/light-ocr-darwin-x64', 'linux-arm64': '@arcships/light-ocr-linux-arm64-gnu', 'linux-x64': '@arcships/light-ocr-linux-x64-gnu', - 'win32-arm64': '@arcships/light-ocr-win32-arm64', - 'win32-x64': '@arcships/light-ocr-win32-x64', - }[key]; + 'linux-arm64-musl': '@arcships/light-ocr-linux-arm64-musl', + 'linux-x64-musl': '@arcships/light-ocr-linux-x64-musl', + 'windows-arm64': '@arcships/light-ocr-win32-arm64', + 'windows-x64': '@arcships/light-ocr-win32-x64', + }[hostPlatformId()]; } async function main() { diff --git a/tools/npm_release.py b/tools/npm_release.py index 6d3db8f..e99a8b7 100644 --- a/tools/npm_release.py +++ b/tools/npm_release.py @@ -104,6 +104,22 @@ "libc": ["glibc"], "runtime": "libonnxruntime.so.1", }, + "linux-x64-musl": { + "package": "@arcships/light-ocr-linux-x64-musl", + "os": ["linux"], + "cpu": ["x64"], + "architecture": "x86_64", + "libc": ["musl"], + "runtime": "libonnxruntime.so.1", + }, + "linux-arm64-musl": { + "package": "@arcships/light-ocr-linux-arm64-musl", + "os": ["linux"], + "cpu": ["arm64"], + "architecture": "arm64", + "libc": ["musl"], + "runtime": "libonnxruntime.so.1", + }, "windows-x64": { "package": "@arcships/light-ocr-win32-x64", "os": ["win32"], @@ -126,6 +142,8 @@ "macos-x64": "libpdfium.dylib", "linux-x64": "libpdfium.so", "linux-arm64": "libpdfium.so", + "linux-x64-musl": "libpdfium.so", + "linux-arm64-musl": "libpdfium.so", "windows-x64": "pdfium.dll", "windows-arm64": "pdfium.dll", } @@ -1194,7 +1212,7 @@ def assemble(arguments: argparse.Namespace) -> None: "LICENSE", "NOTICE", ], - "engines": {"node": "^22.0.0 || ^24.0.0"}, + "engines": {"node": ">=22.0.0"}, } ) if "libc" in platform: