Normalize downloaded Windows prebuild artifact #36
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build & Publish tagged release | |
| on: | |
| push: | |
| tags: | |
| - v* | |
| env: | |
| REQUIRED_ELECTRON_VERSION: 40.8.0 | |
| permissions: | |
| id-token: write | |
| contents: write | |
| jobs: | |
| build: | |
| name: Build ${{ matrix.target }} | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - os: macos-14 | |
| target: darwin-arm64 | |
| node_arch: arm64 | |
| build_for_arch: arm64 | |
| - os: macos-14 | |
| target: darwin-x64 | |
| node_arch: x64 | |
| build_for_arch: x64 | |
| - os: windows-latest | |
| target: win32-x64 | |
| node_arch: x64 | |
| build_for_arch: x64 | |
| - os: ubuntu-latest | |
| target: linux-x64 | |
| node_arch: x64 | |
| build_for_arch: x64 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| submodules: recursive | |
| - name: Setup Rosetta (macOS x64) | |
| if: runner.os == 'macOS' && matrix.target == 'darwin-x64' | |
| run: softwareupdate --install-rosetta --agree-to-license || true | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 20 | |
| architecture: ${{ matrix.node_arch }} | |
| cache: npm | |
| - name: Setup Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.11' | |
| - name: Setup Windows compiler tools | |
| if: runner.os == 'Windows' | |
| uses: ilammy/msvc-dev-cmd@v1 | |
| - name: Install dependencies | |
| run: npm ci --ignore-scripts | |
| - name: Build native dependencies | |
| env: | |
| BUILD_FOR_ARCH: ${{ matrix.build_for_arch }} | |
| run: | | |
| npm run clean-tesseract | |
| npm run build-tesseract | |
| - name: Build prebuilds | |
| run: npm run prebuildify | |
| - name: Verify target prebuild exists | |
| run: | | |
| node -e "const fs=require('fs'); const path=require('path'); const dir=path.join('prebuilds','${{ matrix.target }}'); if(!fs.existsSync(dir)) throw new Error('Missing prebuild dir: '+dir); const files=fs.readdirSync(dir).filter(name => name.endsWith('.node')); if(files.length===0) throw new Error('No .node prebuild found in '+dir); console.log('Found prebuilds:', files.join(', '));" | |
| - name: Verify colocated Windows prebuild DLLs | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| run: | | |
| $prebuildDir = "$env:GITHUB_WORKSPACE\prebuilds\win32-x64" | |
| $addonCount = (Get-ChildItem -Path $prebuildDir -Filter *.node -File).Count | |
| $dllCount = (Get-ChildItem -Path $prebuildDir -Filter *.dll -File).Count | |
| if ($addonCount -eq 0) { | |
| throw "No Windows prebuild addon found in $prebuildDir" | |
| } | |
| if ($dllCount -eq 0) { | |
| throw "No DLLs were copied next to the Windows prebuild in $prebuildDir" | |
| } | |
| if (Get-ChildItem -Path $prebuildDir -Filter tesseract.exe -File -ErrorAction SilentlyContinue) { | |
| throw "Unexpected tesseract.exe next to Windows prebuild in $prebuildDir" | |
| } | |
| Write-Host "Windows prebuild directory contents:" | |
| Get-ChildItem -Path $prebuildDir | Select-Object Name,Length | |
| - name: Verify required Electron target compatibility | |
| run: node ./scripts/verify-electron-target.js "${{ env.REQUIRED_ELECTRON_VERSION }}" | |
| - name: Upload prebuilds artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: prebuilds-${{ matrix.target }} | |
| path: prebuilds/ | |
| retention-days: 1 | |
| verify_windows_prebuild: | |
| needs: build | |
| runs-on: windows-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 20 | |
| cache: npm | |
| - name: Install dependencies | |
| run: npm ci --ignore-scripts | |
| - name: Remove checked-in prebuilds | |
| shell: pwsh | |
| run: | | |
| if (Test-Path prebuilds) { | |
| Remove-Item -Recurse -Force prebuilds | |
| } | |
| - name: Download Windows prebuild artifact | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: prebuilds-win32-x64 | |
| path: downloaded-prebuilds | |
| - name: Normalize Windows prebuild artifact layout | |
| shell: pwsh | |
| run: | | |
| $downloadRoot = Join-Path $env:GITHUB_WORKSPACE 'downloaded-prebuilds' | |
| $candidateDirs = Get-ChildItem -Path $downloadRoot -Directory -Recurse | Where-Object { | |
| (Get-ChildItem -Path $_.FullName -Filter *.node -File -ErrorAction SilentlyContinue).Count -gt 0 | |
| } | |
| if ($candidateDirs.Count -eq 0) { | |
| throw "Could not find downloaded Windows prebuild directory under $downloadRoot" | |
| } | |
| $sourceDir = $candidateDirs[0].FullName | |
| $targetRoot = Join-Path $env:GITHUB_WORKSPACE 'prebuilds' | |
| $targetDir = Join-Path $targetRoot 'win32-x64' | |
| New-Item -ItemType Directory -Path $targetRoot -Force | Out-Null | |
| if (Test-Path $targetDir) { | |
| Remove-Item -Recurse -Force $targetDir | |
| } | |
| Copy-Item -Path $sourceDir -Destination $targetDir -Recurse | |
| Write-Host "Normalized Windows prebuild artifact from $sourceDir to $targetDir" | |
| - name: Sanitize PATH for clean prebuild load | |
| shell: pwsh | |
| run: | | |
| $blocked = @( | |
| "$env:GITHUB_WORKSPACE\tesseract\build\bin\bin", | |
| "$env:GITHUB_WORKSPACE\leptonica\build\bin\bin", | |
| "$env:GITHUB_WORKSPACE\libtiff\build\bin\bin", | |
| "$env:GITHUB_WORKSPACE\libpng\build\bin\bin", | |
| "$env:GITHUB_WORKSPACE\libjpeg\build\bin\bin", | |
| "$env:GITHUB_WORKSPACE\zlib\build\bin\bin", | |
| "$env:GITHUB_WORKSPACE\runtime\win32-x64" | |
| ) | ForEach-Object { | |
| $_.TrimEnd('\\').ToLowerInvariant() | |
| } | |
| $filtered = @() | |
| foreach ($entry in ($env:PATH -split ';')) { | |
| $normalized = $entry.Trim().TrimEnd('\\').ToLowerInvariant() | |
| if ($normalized -and $blocked -contains $normalized) { | |
| Write-Host "Removing PATH entry for clean prebuild load: $entry" | |
| continue | |
| } | |
| $filtered += $entry | |
| } | |
| "PATH=$($filtered -join ';')" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append | |
| - name: Verify Windows prebuild loads in clean process | |
| shell: pwsh | |
| run: | | |
| node -e "const fs=require('fs'); const path=require('path'); const dir=path.join(process.cwd(),'prebuilds','win32-x64'); if(!fs.existsSync(dir)) throw new Error('Missing prebuild dir: '+dir); const files=fs.readdirSync(dir); if(!files.some(name => name.toLowerCase().endsWith('.node'))) throw new Error('Missing .node file in '+dir); if(!files.some(name => name.toLowerCase().endsWith('.dll'))) throw new Error('Missing colocated DLL in '+dir); const bindings=require('node-gyp-build')(process.cwd()); if(typeof bindings.recognize!=='function') throw new Error('Loaded bindings missing recognize export'); console.log('Loaded Windows prebuild from clean process.');" | |
| publish: | |
| needs: [build, verify_windows_prebuild] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 20 | |
| registry-url: 'https://registry.npmjs.org' | |
| - name: Upgrade npm for trusted publishing | |
| run: npm install -g npm@latest && npm --version | |
| - name: Download all prebuilds | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: prebuilds-all | |
| pattern: prebuilds-* | |
| merge-multiple: true | |
| - name: Merge prebuilds into package | |
| run: | | |
| mkdir -p prebuilds | |
| cp -r prebuilds-all/* prebuilds/ | |
| ls -la prebuilds/ | |
| find prebuilds -name '*.node' -type f | |
| - name: Verify Windows release prebuild DLLs | |
| run: | | |
| node -e "const fs=require('fs'); const path=require('path'); const prebuildDir=path.join('prebuilds','win32-x64'); if(!fs.existsSync(prebuildDir)) throw new Error('Missing prebuild dir: '+prebuildDir); const files=fs.readdirSync(prebuildDir); const nodeCount=files.filter(name => name.toLowerCase().endsWith('.node')).length; const dllCount=files.filter(name => name.toLowerCase().endsWith('.dll')).length; if(nodeCount===0) throw new Error('No .node files found in prebuilds/win32-x64'); if(dllCount===0) throw new Error('No DLL files found next to the Windows prebuild'); if(files.some(name => name.toLowerCase() === 'tesseract.exe')) throw new Error('Unexpected prebuilds/win32-x64/tesseract.exe in native-only package'); console.log('prebuild files:', files.join(', '));" | |
| - name: Verify all release prebuild targets are present | |
| run: | | |
| node -e "const fs=require('fs'); const path=require('path'); const targets=['darwin-arm64','darwin-x64','win32-x64','linux-x64']; for (const target of targets) { const dir=path.join('prebuilds', target); if (!fs.existsSync(dir)) throw new Error('Missing prebuild dir: '+dir); const files=fs.readdirSync(dir).filter(name => name.endsWith('.node')); if (files.length === 0) throw new Error('No .node prebuild found in '+dir); } console.log('All required prebuild targets exist.');" | |
| - name: Install dependencies (skip scripts) | |
| run: npm ci --ignore-scripts | |
| - name: Align package version with tag | |
| env: | |
| TAG_VERSION: ${{ github.ref_name }} | |
| run: | | |
| node -e "const fs=require('fs'); const tag=process.env.TAG_VERSION || ''; const version=tag.startsWith('v') ? tag.slice(1) : tag; if(!version){throw new Error('Missing tag version');} const pkgPath='package.json'; const pkg=JSON.parse(fs.readFileSync(pkgPath,'utf8')); if(pkg.version!==version){pkg.version=version; fs.writeFileSync(pkgPath, JSON.stringify(pkg,null,2)+'\\n'); console.log('Updated package version to', version);} else {console.log('Package version already', version);}" | |
| - name: Show package contents | |
| run: npm pack --dry-run | |
| - name: Verify packed tarball contains colocated Windows prebuild DLLs | |
| run: | | |
| TARBALL=$(node -e "const pkg=require('./package.json'); const tarName=(pkg.name + '-' + pkg.version).replace('@','').replace('/','-') + '.tgz'; process.stdout.write(tarName);") | |
| npm pack --quiet > /dev/null | |
| test -f "$TARBALL" | |
| node -e "const {execFileSync}=require('child_process'); const tarball=process.argv[1]; const listing=execFileSync('tar',['-tzf',tarball],{encoding:'utf8'}).split(/\r?\n/).filter(Boolean); const hasRuntimeDir=listing.some(name => /^package\/runtime\/win32-x64\//i.test(name)); const hasExe=listing.includes('package/prebuilds/win32-x64/tesseract.exe'); const hasDll=listing.some(name => /^package\/prebuilds\/win32-x64\/.*\.dll$/i.test(name)); if(hasRuntimeDir){throw new Error('Packed tarball unexpectedly contains package/runtime/win32-x64');} if(hasExe){throw new Error('Packed tarball unexpectedly contains prebuilds/win32-x64/tesseract.exe');} if(!hasDll){throw new Error('Packed tarball missing prebuilds/win32-x64/*.dll');} console.log('Verified colocated Windows prebuild DLL entries in packed tarball.');" "$TARBALL" | |
| - name: Pack release tarball | |
| run: npm pack | |
| - name: Prepare release assets | |
| shell: bash | |
| run: | | |
| mkdir -p release-assets | |
| cp ./*.tgz release-assets/ | |
| for target in prebuilds/*; do | |
| [ -d "$target" ] || continue | |
| target_name=$(basename "$target") | |
| (cd prebuilds && zip -r "../release-assets/prebuilds-${target_name}.zip" "$target_name") | |
| done | |
| ls -la release-assets | |
| - name: Create GitHub release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| files: | | |
| release-assets/* | |
| - name: Publish tagged release | |
| run: npm publish --provenance |