Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 136 additions & 2 deletions .github/workflows/npm-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand Down Expand Up @@ -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
Expand Down
166 changes: 166 additions & 0 deletions .github/workflows/onnxruntime-musl.yml
Original file line number Diff line number Diff line change
@@ -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-<version>"
# 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 <execinfo.h>')
new = ('#if !defined(__ANDROID__) && !defined(__wasm__) '
'&& !defined(_OPSCHEMA_LIB_) && !defined(_AIX) && !defined(NDEBUG)\n'
'#include <execinfo.h>')
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 }}
24 changes: 24 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -158,14 +160,16 @@ 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 |
| --- | --- |
| macOS on Apple Silicon | Core ML on macOS 15+, then CPU |
| 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 |

Expand Down
Loading
Loading