chore(FR-2612): optimize release workflow with parallel jobs and eliminate double React build#6765
Conversation
How to use the Graphite Merge QueueAdd either label to this PR to merge it via the merge queue:
You must have a Graphite account in order to use the merge queue. Sign up using this link. An organization admin has required the Graphite Merge Queue in this repository. Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue. This stack of pull requests is managed by Graphite. Learn more about stacking. |
Coverage report for
|
St.❔ |
Category | Percentage | Covered / Total |
|---|---|---|---|
| 🔴 | Statements | 8.6% | 1757/20437 |
| 🔴 | Branches | 7.89% | 1131/14338 |
| 🔴 | Functions | 5.14% | 285/5545 |
| 🔴 | Lines | 8.32% | 1649/19831 |
Test suite run success
856 tests passing in 39 suites.
Report generated by 🧪jest coverage report action from b1127f6
438eca6 to
2f5ca73
Compare
There was a problem hiding this comment.
Pull request overview
Optimizes the release/build pipeline for Backend.AI WebUI/Desktop by splitting packaging into parallel GitHub Actions jobs and avoiding a second React build for Electron by patching the built output in-place.
Changes:
- Split release workflow into
build_web,build_mac, andbuild_desktopjobs with artifact sharing. - Replace the second Electron-specific React build with a post-build publicPath patch script.
- Parallelize local proxy builds and speed up ZIP packaging; upload release assets concurrently.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
.github/workflows/package.yml |
Parallelized CI packaging jobs and added workflow_dispatch dry-run support. |
Makefile |
Added dep_web/dep_electron, parallel local proxy compilation, package-only targets, and faster zip compression. |
scripts/patch-electron-publicpath.js |
New script to patch build output publicPath for Electron (es6://). |
upload-release.js |
Upload assets concurrently in batches to reduce wall time. |
react/package.json |
Add explicit NODE_OPTIONS to reduce webpack OOM risk in CI builds. |
2f5ca73 to
559fcd4
Compare
Merge activity
|
…inate double React build (#6765) Resolves #6815 ([FR-2612](https://lablup.atlassian.net/browse/FR-2612)) ## Summary The release pipeline previously ran everything sequentially on a single macOS runner (~30+ min). This PR introduces several optimizations that cut release time roughly in half. ### Dry run test result | Job | Duration | Runner | |-----|----------|--------| | `build_web` | 3m 30s | ubuntu | | `build_mac` (unsigned) | 3m 54s | macos | | `build_desktop` | 2m 54s | ubuntu | | **Total wall time** | **~7m 24s** | | Previous release (v26.4.3): **26m 35s** → **72% reduction** in wall time. Actual release with code signing/notarization is expected to be ~10-12 min. - Dry run: https://github.com/lablup/backend.ai-webui/actions/runs/24537688533 ### Changes 1. **Split CI into 3 parallel jobs** (`package.yml`) - `build_web` (ubuntu): Builds web assets + uploads bundle - `build_mac` (macos): macOS desktop builds with code signing/notarization - `build_desktop` (ubuntu): Windows + Linux desktop builds - macOS and win/linux jobs run **concurrently** after web build completes 2. **Eliminate double React build** (`scripts/patch-electron-publicpath.js`, `Makefile`) - Previously the entire React app was rebuilt for Electron just to change `publicPath` from `/` to `es6://` - Now a lightweight post-build script patches the already-built files instead 3. **Parallelize local proxy compilation** (`Makefile`) - All platform targets now compile concurrently within each job via the new `compile_all_localproxy` target - `compile_localproxy` is now **idempotent**: skips when the output ZIP already exists, so downstream targets (`mac_x64`, `win_x64`, ...) reuse the cached artifact without recompiling. Set `FORCE_COMPILE_LOCALPROXY=1` to force a rebuild. - `dep_electron` is likewise idempotent: skips when `build/electron-app/app/index.html` already carries the patched `es6://static/js/main` marker. Set `FORCE_DEP_ELECTRON=1` to force a re-sync. - This means the original single set of `mac_x64`/`mac_arm64`/`win_*`/`linux_*` targets can be reused across both ad-hoc local builds and the new parallel CI flow — no duplicated `*_package` targets required. 4. **Optimize ZIP compression** (`Makefile`) - Changed from `-9` (max) to `-6` (default) -- marginal size difference, measurably faster 5. **Concurrent release asset uploads** (`upload-release.js`) - Uploads up to 4 files simultaneously instead of sequentially 6. **Explicit `NODE_OPTIONS`** (`react/package.json`) - Added `--max-old-space-size=4096` to `build:only` script to prevent OOM during webpack builds 7. **`workflow_dispatch` trigger** (`package.yml`) - Enables manual dry-run testing of the build pipeline on any branch - `dry_run=true` skips release uploads and code signing ### Backward Compatibility - Existing `make mac`, `make win`, `make linux`, `make dep` targets work unchanged - `make mac_x64`, `make mac_arm64`, `make win_x64`, `make win_arm64`, `make linux_x64`, `make linux_arm64` targets work unchanged — no new `*_package` variants introduced - `make all` now uses the optimized parallel path internally (pre-builds all local proxies via `compile_all_localproxy`, then calls the original platform targets which become cheap thanks to idempotent prerequisites) - `make bundle` no longer requires full `dep` (just `dep_web`) ## Test plan - [x] Verify `scripts/patch-electron-publicpath.js` correctly patches `es6://` references (local mock test) - [x] Verify `build_web` job builds web assets and uploads artifact - [x] Verify `build_mac` job downloads artifact, prepares Electron, packages DMG - [x] Verify `build_desktop` job packages Win/Linux ZIPs - [x] Verify all 3 jobs complete successfully in parallel - [x] Verify `compile_localproxy` skips when output ZIP exists and rebuilds with `FORCE_COMPILE_LOCALPROXY=1` - [x] Verify `dep_electron` skips when `build/electron-app/app/index.html` already patched and resyncs with `FORCE_DEP_ELECTRON=1` - [ ] Verify actual release (non-dry-run) uploads assets correctly Generated with [Claude Code](https://claude.com/claude-code) [FR-2612]: https://lablup.atlassian.net/browse/FR-2612?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
245813b to
b1127f6
Compare
Resolves the gap between PR #6871 (Vite spike for `es6://` publicPath via `renderBuiltUrl`) and PR #6765 / FR-2612 (single React build + post-patch policy). The post-patch script was still searching for CRA-era `/static/...` paths after the Vite migration emitted `/assets/...`, so `make dep` produced an Electron staging dir that referenced root-relative `/assets/...` that Electron could not serve via the `es6://` scheme handler. - `bash scripts/verify.sh` → Relay / Lint / Format / TypeScript ALL PASS - `make clean && make dep` → "Patched 2 file(s). Verification passed: index.html contains es6://assets/" - `make dep` run twice → idempotent skip ("Electron app already prepared, skipping") - `pnpm run electron:d` → renderer spawns with `es6://` scheme registered, login UI renders, all stylesheets load (rule counts > 0), body bg = rgb(247, 247, 246), `did-fail-load` / `webRequest err` / 404s = 0 - `make mac_arm64` → `compile_localproxy` + `electron-packager` (.app) produced cleanly. (DMG step fails on a local `NODE_MODULE_VERSION` mismatch in a globally-installed `electron-installer-dmg`; unrelated to this fix and does not affect CI which uses `npx`.) - Replace CRA-era patch patterns (`/static/js`, `/static/css`, `asset-manifest.json`, webpack runtime `.p='/'` rewrites) with Vite-aware patterns. - Patch `index.html`: rewrite `="/assets/` → `="es6://assets/` on `src`/`href` attributes only. - Patch CSS files under `assets/`: `url(/assets/...)` → `url(es6://assets/...)` in all quote styles. - Drop dead Webpack-specific code paths (`asset-manifest.json`, `static/{js,css}`, webpack runtime `publicPath` assignment) that no longer exist in Vite output. - Update verification marker from `es6://static/js/main` to `es6://assets/`. - Leave `<base href="/">` untouched: the renderer loads `index.html` via `file://`, and rewriting `<base>` to `es6://` would break `window.location`/origin alignment (history API, same-origin checks). Webpack-era patch script also did not touch `<base>`. - Verification failure now exits non-zero so the Makefile recipe aborts instead of silently continuing. - Update `dep_electron` idempotency marker from `es6://static/js/main` to `es6://assets/` to match the new patch script. - Replace `;` chaining with `&&` between the `dep_electron` commands so a failed patch script aborts the recipe. - Update inline comment that referenced the old marker. - Remove the `BUILD_TARGET=electron` + `experimental.renderBuiltUrl` branch (PR #6871 spike). FR-2612 mandates a single web build with post-patch; the `renderBuiltUrl` path would require a second `vite build` per release and was never wired into the Makefile, making it dead code with regression risk if anyone toggled `BUILD_TARGET=electron`. - Drop the now-unused `command` parameter from `defineConfig`. - Remove three unused CRA-era devDependencies: `@svgr/webpack` (Vite uses `vite-plugin-svgr`), `react-scripts` (CRA core, retired in FR-2611), `react-dev-utils` (no imports anywhere). All three were verified to have zero references in source. Sits on top of #7113 (FR-2606 residual build-time warnings). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Resolves the gap between PR #6871 (Vite spike for `es6://` publicPath via `renderBuiltUrl`) and PR #6765 / FR-2612 (single React build + post-patch policy). The post-patch script was still searching for CRA-era `/static/...` paths after the Vite migration emitted `/assets/...`, so `make dep` produced an Electron staging dir that referenced root-relative `/assets/...` that Electron could not serve via the `es6://` scheme handler. ## Verification (PR #7113 head, this fix applied) - `bash scripts/verify.sh` → Relay / Lint / Format / TypeScript ALL PASS - `make clean && make dep` → "Patched 2 file(s). Verification passed: index.html contains es6://assets/" - `make dep` run twice → idempotent skip ("Electron app already prepared, skipping") - `pnpm run electron:d` → renderer spawns with `es6://` scheme registered, login UI renders, all stylesheets load (rule counts > 0), body bg = rgb(247, 247, 246), `did-fail-load` / `webRequest err` / 404s = 0 - `make mac_arm64` → `compile_localproxy` + `electron-packager` (.app) produced cleanly. (DMG step fails on a local `NODE_MODULE_VERSION` mismatch in a globally-installed `electron-installer-dmg`; unrelated to this fix and does not affect CI which uses `npx`.) ## Changes ### `scripts/patch-electron-publicpath.js` - Replace CRA-era patch patterns (`/static/js`, `/static/css`, `asset-manifest.json`, webpack runtime `.p='/'` rewrites) with Vite-aware patterns. - Patch `index.html`: rewrite `="/assets/` → `="es6://assets/` on `src`/`href` attributes only. - Patch CSS files under `assets/`: `url(/assets/...)` → `url(es6://assets/...)` in all quote styles. - Drop dead Webpack-specific code paths (`asset-manifest.json`, `static/{js,css}`, webpack runtime `publicPath` assignment) that no longer exist in Vite output. - Update verification marker from `es6://static/js/main` to `es6://assets/`. - Leave `<base href="/">` untouched: the renderer loads `index.html` via `file://`, and rewriting `<base>` to `es6://` would break `window.location`/origin alignment (history API, same-origin checks). Webpack-era patch script also did not touch `<base>`. - Verification failure now exits non-zero so the Makefile recipe aborts instead of silently continuing. ### `Makefile` - Update `dep_electron` idempotency marker from `es6://static/js/main` to `es6://assets/` to match the new patch script. - Replace `;` chaining with `&&` between the `dep_electron` commands so a failed patch script aborts the recipe. - Update inline comment that referenced the old marker. ### `react/vite.config.ts` - Remove the `BUILD_TARGET=electron` + `experimental.renderBuiltUrl` branch (PR #6871 spike). FR-2612 mandates a single web build with post-patch; the `renderBuiltUrl` path would require a second `vite build` per release and was never wired into the Makefile, making it dead code with regression risk if anyone toggled `BUILD_TARGET=electron`. - Drop the now-unused `command` parameter from `defineConfig`. ### `react/package.json` - Remove three unused CRA-era devDependencies: `@svgr/webpack` (Vite uses `vite-plugin-svgr`), `react-scripts` (CRA core, retired in FR-2611), `react-dev-utils` (no imports anywhere). All three were verified to have zero references in source. ## Stack Sits on top of #7113 (FR-2606 residual build-time warnings).

Resolves #6815 (FR-2612)
Summary
The release pipeline previously ran everything sequentially on a single macOS runner (~30+ min). This PR introduces several optimizations that cut release time roughly in half.
Dry run test result
build_webbuild_mac(unsigned)build_desktopPrevious release (v26.4.3): 26m 35s → 72% reduction in wall time.
Actual release with code signing/notarization is expected to be ~10-12 min.
Changes
Split CI into 3 parallel jobs (
package.yml)build_web(ubuntu): Builds web assets + uploads bundlebuild_mac(macos): macOS desktop builds with code signing/notarizationbuild_desktop(ubuntu): Windows + Linux desktop buildsEliminate double React build (
scripts/patch-electron-publicpath.js,Makefile)publicPathfrom/toes6://Parallelize local proxy compilation (
Makefile)compile_all_localproxytargetcompile_localproxyis now idempotent: skips when the output ZIP already exists, so downstream targets (mac_x64,win_x64, ...) reuse the cached artifact without recompiling. SetFORCE_COMPILE_LOCALPROXY=1to force a rebuild.dep_electronis likewise idempotent: skips whenbuild/electron-app/app/index.htmlalready carries the patchedes6://static/js/mainmarker. SetFORCE_DEP_ELECTRON=1to force a re-sync.mac_x64/mac_arm64/win_*/linux_*targets can be reused across both ad-hoc local builds and the new parallel CI flow — no duplicated*_packagetargets required.Optimize ZIP compression (
Makefile)-9(max) to-6(default) -- marginal size difference, measurably fasterConcurrent release asset uploads (
upload-release.js)Explicit
NODE_OPTIONS(react/package.json)--max-old-space-size=4096tobuild:onlyscript to prevent OOM during webpack buildsworkflow_dispatchtrigger (package.yml)dry_run=trueskips release uploads and code signingBackward Compatibility
make mac,make win,make linux,make deptargets work unchangedmake mac_x64,make mac_arm64,make win_x64,make win_arm64,make linux_x64,make linux_arm64targets work unchanged — no new*_packagevariants introducedmake allnow uses the optimized parallel path internally (pre-builds all local proxies viacompile_all_localproxy, then calls the original platform targets which become cheap thanks to idempotent prerequisites)make bundleno longer requires fulldep(justdep_web)Test plan
scripts/patch-electron-publicpath.jscorrectly patcheses6://references (local mock test)build_webjob builds web assets and uploads artifactbuild_macjob downloads artifact, prepares Electron, packages DMGbuild_desktopjob packages Win/Linux ZIPscompile_localproxyskips when output ZIP exists and rebuilds withFORCE_COMPILE_LOCALPROXY=1dep_electronskips whenbuild/electron-app/app/index.htmlalready patched and resyncs withFORCE_DEP_ELECTRON=1Generated with Claude Code