name: Release - Node.js on: workflow_dispatch: inputs: version: description: "Version to release (e.g. 2.1.0)" required: true type: string beta: description: "Publish as beta (npm tag: beta instead of latest)" type: boolean default: false dry-run: description: "Dry run (build but do not publish)" type: boolean default: false permissions: contents: write id-token: write env: CARGO_TERM_COLOR: always jobs: validate: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Validate version run: | PKG_VERSION=$(node -p "require('./packages/node/package.json').version") if [ "$PKG_VERSION" != "${{ inputs.version }}" ]; then echo "::error::Version mismatch: package.json has $PKG_VERSION, expected ${{ inputs.version }}" exit 1 fi echo "Version validated: ${{ inputs.version }}" build-bindings: needs: validate strategy: fail-fast: false matrix: include: - os: macos-latest target: aarch64-apple-darwin node-arch: darwin-arm64 pdfium-dylib: libpdfium.dylib - os: macos-26-intel target: x86_64-apple-darwin node-arch: darwin-x64 pdfium-dylib: libpdfium.dylib - os: ubuntu-22.04 target: x86_64-unknown-linux-gnu node-arch: linux-x64-gnu pdfium-dylib: libpdfium.so - os: ubuntu-22.04-arm target: aarch64-unknown-linux-gnu node-arch: linux-arm64-gnu pdfium-dylib: libpdfium.so - os: ubuntu-22.04 target: x86_64-unknown-linux-musl node-arch: linux-x64-musl pdfium-dylib: libpdfium.so use-container: true - os: windows-latest target: x86_64-pc-windows-msvc node-arch: win32-x64-msvc pdfium-dylib: pdfium.dll - os: windows-11-arm target: aarch64-pc-windows-msvc node-arch: win32-arm64-msvc pdfium-dylib: pdfium.dll runs-on: ${{ matrix.os }} name: Build ${{ matrix.node-arch }} steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 - uses: dtolnay/rust-toolchain@1.95.0 with: targets: ${{ matrix.target }} - name: Install system dependencies (Linux glibc) if: runner.os == 'Linux' && !matrix.use-container run: | sudo apt-get update sudo apt-get install -y libtesseract-dev libleptonica-dev - name: Force Ninja generator (Windows) if: runner.os == 'Windows' run: echo "CMAKE_GENERATOR=Ninja" >> $GITHUB_ENV shell: bash - uses: actions/cache@v4 with: path: | ~/.cargo/registry ~/.cargo/git ~/.cache/pdfium-rs ~/Library/Caches/pdfium-rs ${{ runner.os == 'Windows' && format('{0}\pdfium-rs', env.LOCALAPPDATA) || '' }} target key: ${{ matrix.os }}-${{ matrix.target }}-cargo-node-${{ hashFiles('**/Cargo.lock') }} - name: Install dependencies working-directory: packages/node run: npm install --ignore-scripts - name: Build native module if: ${{ !matrix.use-container }} working-directory: packages/node shell: bash run: npx napi build --cargo-cwd ../../crates/liteparse-napi --platform --release --js false --dts native.d.ts --target ${{ matrix.target }} . - name: Build native module (musl container) if: ${{ matrix.use-container }} run: | chmod +x scripts/build-musl-node.sh docker run --rm \ -v "$GITHUB_WORKSPACE":/work \ -v "$HOME/.cargo/registry":/root/.cargo/registry \ -v "$HOME/.cargo/git":/root/.cargo/git \ -v "$HOME/.cache/pdfium-rs":/root/.cache/pdfium-rs \ -w /work/packages/node \ node:20-alpine \ /work/scripts/build-musl-node.sh - name: Copy pdfium dylib (Unix) if: runner.os != 'Windows' working-directory: packages/node run: bash scripts/copy-pdfium.sh . - name: Copy pdfium dylib (Windows) if: runner.os == 'Windows' working-directory: packages/node shell: pwsh run: | $cacheBase = "$env:LOCALAPPDATA\pdfium-rs" if (-not (Test-Path $cacheBase)) { $cacheBase = "$env:USERPROFILE\.cache\pdfium-rs" } $dll = Get-ChildItem -Path $cacheBase -Recurse -Filter "pdfium.dll" | Select-Object -First 1 if ($dll) { Copy-Item $dll.FullName -Destination ".\pdfium.dll" Write-Host "Copied $($dll.FullName) -> .\pdfium.dll" } else { Write-Error "Could not find pdfium.dll in cache" exit 1 } - name: Build TypeScript working-directory: packages/node run: npx tsc - name: Upload artifacts uses: actions/upload-artifact@v4 with: name: bindings-${{ matrix.node-arch }} path: | packages/node/*.node packages/node/${{ matrix.pdfium-dylib }} packages/node/libc++.so.1 packages/node/libc++abi.so.1 packages/node/libunwind.so.1 packages/node/libgcc_s.so.1 if-no-files-found: warn test-bindings: needs: build-bindings strategy: fail-fast: false matrix: include: - os: macos-latest node-arch: darwin-arm64 - os: macos-26-intel node-arch: darwin-x64 - os: ubuntu-22.04 node-arch: linux-x64-gnu - os: windows-latest node-arch: win32-x64-msvc - os: windows-11-arm node-arch: win32-arm64-msvc runs-on: ${{ matrix.os }} name: Test ${{ matrix.node-arch }} steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 - name: Install dependencies working-directory: packages/node run: npm install --ignore-scripts - name: Download artifacts uses: actions/download-artifact@v4 with: name: bindings-${{ matrix.node-arch }} path: packages/node - name: Build TypeScript working-directory: packages/node run: npx tsc - name: Smoke test working-directory: packages/node run: | node -e " import('./dist/lib.js').then(async ({ LiteParse }) => { const p = new LiteParse({ ocrEnabled: false, quiet: true }); console.log('Config:', JSON.stringify(p.getConfig())); console.log('Native module loaded successfully'); }); " build-cli: needs: validate strategy: matrix: include: - target: x86_64-unknown-linux-gnu os: ubuntu-22.04 artifact: lit-linux-x64 - target: aarch64-unknown-linux-gnu os: ubuntu-22.04 artifact: lit-linux-arm64 use-cross: true - target: aarch64-apple-darwin os: macos-latest artifact: lit-darwin-arm64 - target: x86_64-apple-darwin os: macos-26-intel artifact: lit-darwin-x64 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@1.95.0 with: targets: ${{ matrix.target }} - name: Install system dependencies (Linux native) if: runner.os == 'Linux' && !matrix.use-cross run: | sudo apt-get update sudo apt-get install -y libtesseract-dev libleptonica-dev - name: Install cross-compilation tools (Linux ARM64) if: matrix.use-cross run: | sudo apt-get update sudo apt-get install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc" >> $GITHUB_ENV - name: Install system dependencies (macOS) if: runner.os == 'macOS' run: brew install tesseract leptonica - uses: actions/cache@v4 with: path: | ~/.cargo/registry ~/.cargo/git ~/.cache/pdfium-rs ~/Library/Caches/pdfium-rs target key: ${{ matrix.os }}-${{ matrix.target }}-cargo-cli-${{ hashFiles('**/Cargo.lock') }} - name: Build run: | if [ "${{ matrix.use-cross }}" = "true" ]; then cargo build --release --target ${{ matrix.target }} --no-default-features -p liteparse else cargo build --release --target ${{ matrix.target }} -p liteparse fi - name: Package binary run: | mkdir -p dist cp target/${{ matrix.target }}/release/lit dist/${{ matrix.artifact }} chmod +x dist/${{ matrix.artifact }} tar -czf dist/${{ matrix.artifact }}.tar.gz -C dist ${{ matrix.artifact }} - name: Upload artifact uses: actions/upload-artifact@v4 with: name: ${{ matrix.artifact }} path: dist/${{ matrix.artifact }}.tar.gz publish: needs: [test-bindings, build-cli] runs-on: ubuntu-latest name: Publish & Release steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 registry-url: https://registry.npmjs.org - name: Install dependencies working-directory: packages/node run: npm install --ignore-scripts - name: Build TypeScript working-directory: packages/node run: npx tsc - name: Download all binding artifacts uses: actions/download-artifact@v4 with: pattern: bindings-* path: artifacts - name: Download all CLI artifacts uses: actions/download-artifact@v4 with: pattern: lit-* path: cli-artifacts - name: Prepare platform packages working-directory: packages/node run: | declare -A PLATFORMS=( ["darwin-arm64"]="aarch64-apple-darwin" ["darwin-x64"]="x86_64-apple-darwin" ["linux-x64-gnu"]="x86_64-unknown-linux-gnu" ["linux-x64-musl"]="x86_64-unknown-linux-musl" ["linux-arm64-gnu"]="aarch64-unknown-linux-gnu" ["win32-x64-msvc"]="x86_64-pc-windows-msvc" ["win32-arm64-msvc"]="aarch64-pc-windows-msvc" ) for ARCH in "${!PLATFORMS[@]}"; do PKG_NAME="@llamaindex/liteparse-${ARCH}" PKG_DIR="npm/${ARCH}" ARTIFACT_DIR="../../artifacts/bindings-${ARCH}" if [ ! -d "$ARTIFACT_DIR" ]; then echo "Skipping ${ARCH} - no artifacts found" continue fi mkdir -p "$PKG_DIR" cp "$ARTIFACT_DIR"/*.node "$PKG_DIR/" 2>/dev/null || true cp "$ARTIFACT_DIR"/*.dylib "$PKG_DIR/" 2>/dev/null || true cp "$ARTIFACT_DIR"/*.so "$PKG_DIR/" 2>/dev/null || true cp "$ARTIFACT_DIR"/*.dll "$PKG_DIR/" 2>/dev/null || true LIBC="" case "$ARCH" in darwin-arm64) OS="darwin"; CPU="arm64" ;; darwin-x64) OS="darwin"; CPU="x64" ;; win32-x64-msvc) OS="win32"; CPU="x64" ;; win32-arm64-msvc) OS="win32"; CPU="arm64" ;; linux-x64-gnu) OS="linux"; CPU="x64"; LIBC="glibc" ;; linux-x64-musl) OS="linux"; CPU="x64"; LIBC="musl" ;; linux-arm64-gnu) OS="linux"; CPU="arm64"; LIBC="glibc" ;; esac VERSION=${{ inputs.version }} LIBC_LINE="" if [ -n "$LIBC" ]; then LIBC_LINE="\"libc\": [\"${LIBC}\"]," fi cat > "$PKG_DIR/package.json" << PKGJSON { "name": "${PKG_NAME}", "version": "${VERSION}", "os": ["${OS}"], "cpu": ["${CPU}"], ${LIBC_LINE} "main": "liteparse.${ARCH}.node", "files": ["*.node", "*.dylib", "*.so", "*.dll"], "license": "Apache-2.0", "repository": { "type": "git", "url": "https://github.com/run-llama/liteparse.git" } } PKGJSON echo "Created platform package: ${PKG_NAME}" ls -la "$PKG_DIR/" done - name: Publish platform packages if: ${{ !inputs.dry-run }} working-directory: packages/node env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} NPM_TAG: ${{ inputs.beta && 'beta' || 'latest' }} run: | for dir in npm/*/; do if [ -f "$dir/package.json" ]; then echo "Publishing $(node -p "require('./${dir}/package.json').name")..." cd "$dir" npm publish --access public --provenance --tag "$NPM_TAG" || true cd ../../ fi done - name: Publish main package if: ${{ !inputs.dry-run }} working-directory: packages/node env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} NPM_TAG: ${{ inputs.beta && 'beta' || 'latest' }} run: npm publish --access public --provenance --tag "$NPM_TAG" - name: Create git tag if: ${{ !inputs.dry-run }} run: | git tag "node-v${{ inputs.version }}" git push origin "node-v${{ inputs.version }}" - name: Create GitHub Release if: ${{ !inputs.dry-run }} uses: softprops/action-gh-release@v2 with: tag_name: node-v${{ inputs.version }} name: "Node.js v${{ inputs.version }}" generate_release_notes: true files: cli-artifacts/**/*.tar.gz - name: Summary (dry run) if: ${{ inputs.dry-run }} run: | echo "## Dry Run Summary" >> $GITHUB_STEP_SUMMARY echo "Would release node-v${{ inputs.version }}" >> $GITHUB_STEP_SUMMARY echo "### Platform packages:" >> $GITHUB_STEP_SUMMARY for dir in packages/node/npm/*/; do if [ -f "$dir/package.json" ]; then echo "- $(node -p "require('./${dir}/package.json').name")" >> $GITHUB_STEP_SUMMARY fi done echo "### CLI artifacts:" >> $GITHUB_STEP_SUMMARY find cli-artifacts -name "*.tar.gz" -exec echo "- {}" \; >> $GITHUB_STEP_SUMMARY