Build and Release Packages #333
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
| # Build and release workflow for Backend.AI Desktop and WebUI bundle. | |
| # | |
| # Architecture: 4 parallel jobs (build_docs is fully independent, the others | |
| # share a web build step). | |
| # | |
| # build_web (ubuntu) ──┬──> build_mac (macos) → DMG x64/arm64 + local proxy | |
| # ├──> build_desktop (ubuntu) → Win/Linux ZIP x64/arm64 + local proxy | |
| # └──> upload web bundle | |
| # build_docs (ubuntu, independent) → User Guide PDFs (en/ko/ja/th) | |
| # | |
| # Key optimizations over the previous single-job approach: | |
| # 1. Parallel jobs: macOS + win/linux + docs builds run concurrently (~10 min saved) | |
| # 2. No double React build: publicPath patching replaces full rebuild (~5 min saved) | |
| # 3. Parallel local proxy compilation within each job (~3 min saved) | |
| # 4. Optimized ZIP compression level (-6 vs -9, marginal size diff, ~1 min saved) | |
| # 5. PDF generation reuses one Chromium instance and renders languages in | |
| # parallel; Playwright browsers are cached across runs. | |
| name: Build and Release Packages | |
| on: | |
| release: | |
| types: [published] | |
| workflow_dispatch: | |
| inputs: | |
| dry_run: | |
| description: 'Skip release asset upload (for testing the build pipeline)' | |
| type: boolean | |
| default: true | |
| env: | |
| NODE_OPTIONS: --max-old-space-size=4096 | |
| jobs: | |
| # ────────────────────────────────────────────────────────────────────── | |
| # Job 1: Build web assets and create the web bundle (ubuntu, ~8 min) | |
| # ────────────────────────────────────────────────────────────────────── | |
| build_web: | |
| permissions: | |
| contents: write | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Check out Git repository | |
| uses: actions/checkout@v5 | |
| - uses: pnpm/action-setup@v5 | |
| name: Install pnpm | |
| with: | |
| version: latest | |
| run_install: false | |
| - name: Install Node.js | |
| uses: actions/setup-node@v5 | |
| with: | |
| node-version-file: '.nvmrc' | |
| cache: 'pnpm' | |
| - name: Install Dependencies | |
| run: pnpm install --no-frozen-lockfile | |
| - name: Build web assets | |
| run: make dep_web | |
| - name: Create web bundle | |
| run: make bundle | |
| - name: Upload release bundle | |
| if: inputs.dry_run != true | |
| run: node upload-release.js app | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| # Share build artifacts with downstream desktop jobs | |
| - name: Upload build artifacts | |
| uses: actions/upload-artifact@v5 | |
| with: | |
| name: web-build | |
| path: | | |
| build/web/ | |
| src/wsproxy/dist/ | |
| retention-days: 1 | |
| compression-level: 3 | |
| # ────────────────────────────────────────────────────────────────────── | |
| # Job 2: Build macOS desktop apps — requires macOS for code signing, | |
| # notarization, and DMG creation (~10 min) | |
| # ────────────────────────────────────────────────────────────────────── | |
| build_mac: | |
| needs: build_web | |
| permissions: | |
| contents: write | |
| runs-on: macos-latest | |
| # Use the protected `app-packaging` environment for real releases (which | |
| # gates access to signing secrets). Dry runs use a separate unprotected | |
| # name because GitHub Actions rejects an empty environment value. | |
| environment: ${{ inputs.dry_run != true && 'app-packaging' || 'app-packaging-dryrun' }} | |
| steps: | |
| - name: Check out Git repository | |
| uses: actions/checkout@v5 | |
| - uses: pnpm/action-setup@v5 | |
| name: Install pnpm | |
| with: | |
| version: latest | |
| run_install: false | |
| - name: Install Node.js | |
| uses: actions/setup-node@v5 | |
| with: | |
| node-version-file: '.nvmrc' | |
| cache: 'pnpm' | |
| - name: Install Dependencies | |
| run: pnpm install --no-frozen-lockfile | |
| - name: Download web build artifacts | |
| uses: actions/download-artifact@v5 | |
| with: | |
| name: web-build | |
| - name: Prepare Electron app | |
| run: make dep_electron | |
| - name: Compile local proxies (parallel) | |
| run: | | |
| make compile_localproxy os=macos arch=x64 local_proxy_postfix= & | |
| make compile_localproxy os=macos arch=arm64 local_proxy_postfix= & | |
| wait | |
| - name: Package macOS Desktop Apps (signed) | |
| if: inputs.dry_run != true | |
| run: | | |
| make mac_x64 | |
| make mac_arm64 | |
| env: | |
| BAI_APP_SIGN: 1 | |
| BAI_APP_SIGN_APPLE_TEAM_ID: ${{ secrets.BAI_APP_SIGN_APPLE_TEAM_ID }} | |
| BAI_APP_SIGN_APPLE_ID: ${{ secrets.BAI_APP_SIGN_APPLE_ID }} | |
| BAI_APP_SIGN_APPLE_ID_PASSWORD: ${{ secrets.BAI_APP_SIGN_APPLE_ID_PASSWORD }} | |
| BAI_APP_SIGN_IDENTITY: ${{ secrets.BAI_APP_SIGN_IDENTITY }} | |
| BAI_APP_SIGN_KEYCHAIN_B64: ${{ secrets.BAI_APP_SIGN_KEYCHAIN_B64 }} | |
| BAI_APP_SIGN_KEYCHAIN_PASSWORD: ${{ secrets.BAI_APP_SIGN_KEYCHAIN_PASSWORD }} | |
| - name: Package macOS Desktop Apps (unsigned, dry run) | |
| if: inputs.dry_run == true | |
| run: | | |
| make mac_x64 | |
| make mac_arm64 | |
| - name: Upload macOS release assets | |
| if: inputs.dry_run != true | |
| run: node upload-release.js app | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| # ────────────────────────────────────────────────────────────────────── | |
| # Job 3: Build Windows + Linux desktop apps (ubuntu, ~8 min) | |
| # No code signing needed — can run on cheaper/faster ubuntu runners. | |
| # ────────────────────────────────────────────────────────────────────── | |
| build_desktop: | |
| needs: build_web | |
| permissions: | |
| contents: write | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Check out Git repository | |
| uses: actions/checkout@v5 | |
| - uses: pnpm/action-setup@v5 | |
| name: Install pnpm | |
| with: | |
| version: latest | |
| run_install: false | |
| - name: Install Node.js | |
| uses: actions/setup-node@v5 | |
| with: | |
| node-version-file: '.nvmrc' | |
| cache: 'pnpm' | |
| - name: Install Dependencies | |
| run: pnpm install --no-frozen-lockfile | |
| - name: Download web build artifacts | |
| uses: actions/download-artifact@v5 | |
| with: | |
| name: web-build | |
| - name: Prepare Electron app | |
| run: make dep_electron | |
| - name: Compile local proxies (parallel) | |
| run: | | |
| make compile_localproxy os=win arch=x64 local_proxy_postfix=.exe & | |
| make compile_localproxy os=win arch=arm64 local_proxy_postfix=.exe & | |
| make compile_localproxy os=linux arch=x64 local_proxy_postfix= & | |
| make compile_localproxy os=linux arch=arm64 local_proxy_postfix= & | |
| wait | |
| - name: Package Windows & Linux Desktop Apps | |
| run: | | |
| make win_x64 | |
| make win_arm64 | |
| make linux_x64 | |
| make linux_arm64 | |
| - name: Upload release assets | |
| if: inputs.dry_run != true | |
| run: node upload-release.js app | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| # ────────────────────────────────────────────────────────────────────── | |
| # Job 4: Build per-language User Guide PDFs (ubuntu, target ~5 min) | |
| # | |
| # Independent of the web/desktop jobs — only needs the docs sources under | |
| # `packages/backend.ai-webui-docs/src/`. Runs in parallel with build_mac | |
| # and build_desktop, so a healthy run does not extend the overall wall | |
| # clock (mac is the current critical path at ~10 min). | |
| # | |
| # Failure handling is scoped to the PDF build step itself (not the whole | |
| # job), so partial per-language failures degrade gracefully but a full | |
| # generator failure (all languages failed → generate-pdf.ts exits non- | |
| # zero) still fails the job. This prevents shipping a release with zero | |
| # PDF assets going unnoticed. | |
| # ────────────────────────────────────────────────────────────────────── | |
| build_docs: | |
| permissions: | |
| contents: write | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Check out Git repository | |
| uses: actions/checkout@v5 | |
| - uses: pnpm/action-setup@v5 | |
| name: Install pnpm | |
| with: | |
| version: latest | |
| run_install: false | |
| - name: Install Node.js | |
| uses: actions/setup-node@v5 | |
| with: | |
| node-version-file: '.nvmrc' | |
| cache: 'pnpm' | |
| - name: Install Dependencies | |
| run: pnpm install --no-frozen-lockfile | |
| # backend.ai-docs-toolkit ships its `docs-toolkit` CLI as the package | |
| # bin, but it lives at dist/cli.js which only exists after `tsc` runs. | |
| # On a fresh `pnpm install` the dist directory is empty, so pnpm | |
| # cannot create the .bin/docs-toolkit symlink and later `pdf:all` | |
| # fails with `spawn ENOENT`. Building the toolkit and re-running | |
| # install populates dist/ and lets pnpm wire the bin link. | |
| - name: Build docs-toolkit and link CLI | |
| run: | | |
| pnpm --filter backend.ai-docs-toolkit run build | |
| pnpm install --no-frozen-lockfile | |
| # Cache the Playwright browser binaries (~150 MB Chromium download). | |
| # The lockfile hash keys the cache so a Playwright version bump | |
| # naturally invalidates the cache without manual intervention. | |
| - name: Cache Playwright browsers | |
| id: playwright-cache | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.cache/ms-playwright | |
| key: playwright-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }} | |
| - name: Install Playwright Chromium | |
| run: pnpm --filter backend.ai-webui-docs exec playwright install --with-deps chromium | |
| # PDF rendering uses two distinct font paths that need different fonts: | |
| # | |
| # 1. Body text — rendered by Chromium from HTML. Resolves the CSS | |
| # font-family (see styles.ts) via fontconfig, so it understands | |
| # .ttc collections. `fonts-noto-cjk` provides "Noto Sans CJK KR/JP/TC" | |
| # which the CSS lists as the canonical CJK body font. | |
| # | |
| # 2. Header/footer — stamped post-render by pdf-lib, which cannot embed | |
| # .ttc collections. So we additionally install language-specific | |
| # single-face packages whose paths are listed in | |
| # DEFAULT_CJK_FONT_CANDIDATES (pdf-renderer.ts): | |
| # - fonts-nanum → /usr/share/fonts/truetype/nanum/NanumBarunGothic.ttf (ko) | |
| # - fonts-takao-gothic → /usr/share/fonts/truetype/takao-gothic/TakaoGothic.ttf (ja) | |
| # - fonts-thai-tlwg → /usr/share/fonts/opentype/tlwg/Loma.otf | Garuda.ttf (th) | |
| # | |
| # Languages whose pdf-lib font is missing will be reported as a failure by | |
| # generate-pdf.ts but will not abort the other language renders. | |
| - name: Install CJK + Thai fonts (best effort) | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y --no-install-recommends \ | |
| fonts-noto-cjk \ | |
| fonts-nanum fonts-takao-gothic fonts-thai-tlwg || true | |
| fc-cache -f || true | |
| # generate-pdf.ts continues past per-language failures and exits | |
| # non-zero only if *every* requested language failed. Combined with | |
| # `continue-on-error: true` on the job, a partial PDF run still | |
| # uploads the languages that succeeded. | |
| - name: Build PDFs (en/ko/ja/th) | |
| run: pnpm --filter backend.ai-webui-docs run pdf:all | |
| - name: Stage PDFs for upload | |
| run: | | |
| mkdir -p ./app | |
| if compgen -G "packages/backend.ai-webui-docs/dist/*.pdf" > /dev/null; then | |
| cp packages/backend.ai-webui-docs/dist/*.pdf ./app/ | |
| ls -lh ./app/*.pdf | |
| else | |
| echo "::warning::No PDFs were produced; nothing to upload." | |
| fi | |
| - name: Upload PDF release assets | |
| if: inputs.dry_run != true | |
| run: node upload-release.js app | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| # Always retain the built PDFs as a workflow artifact for inspection | |
| # (handy during dry runs and when debugging the release path). | |
| - name: Upload PDFs as workflow artifact | |
| if: always() | |
| uses: actions/upload-artifact@v5 | |
| with: | |
| name: webui-docs-pdfs | |
| path: packages/backend.ai-webui-docs/dist/*.pdf | |
| if-no-files-found: warn | |
| retention-days: 14 |