Electron Release #8
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: Electron Release | |
| on: | |
| push: | |
| tags: | |
| - "v*" | |
| workflow_dispatch: | |
| inputs: | |
| tag: | |
| description: "Release tag (e.g. v0.0.1)" | |
| required: true | |
| default: "v0.0.1" | |
| binary_version: | |
| description: "acestep.cpp release tag to bundle (e.g. v0.0.1)" | |
| required: false | |
| default: "v0.0.1" | |
| concurrency: | |
| group: electron-release-${{ github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| NODE_VERSION: "20" | |
| # acestep.cpp binary release to bundle; override via workflow_dispatch input. | |
| BINARY_VERSION: ${{ github.event.inputs.binary_version || 'v0.0.1' }} | |
| # ────────────────────────────────────────────────────────────────────────────── | |
| # Shared setup steps are defined as a reusable composite action inline via | |
| # `run` steps repeated in each job. Each job is self-contained so the CI | |
| # log is easy to read and debug per platform. | |
| # | |
| # Archive layout (flat tarball — all files at ./): | |
| # bin/<binary> ace-qwen3, dit-vae, neural-codec, … | |
| # bin/lib<name>.so Linux shared libraries (unversioned names) | |
| # bin/lib<name>.dylib macOS dylibs (versioned + symlink chain) | |
| # ────────────────────────────────────────────────────────────────────────────── | |
| jobs: | |
| # ──────────────────────────────────────────────────────────────────────────── | |
| # macOS — Apple Silicon → ACE-Step UI-*.dmg | |
| # ──────────────────────────────────────────────────────────────────────────── | |
| build-mac: | |
| name: Build — macOS arm64 | |
| runs-on: macos-14 | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| cache: "npm" | |
| - name: Install root dependencies | |
| run: npm install | |
| - name: Install server dependencies | |
| run: npm install | |
| working-directory: server | |
| - name: Build frontend | |
| run: npm run build | |
| - name: Build server | |
| run: npm run build | |
| working-directory: server | |
| - name: Download & extract acestep.cpp binaries | |
| shell: bash | |
| run: | | |
| ARCHIVE="acestep-macos-arm64-metal.tar.gz" | |
| URL="https://github.com/audiohacking/acestep.cpp/releases/download/${BINARY_VERSION}/${ARCHIVE}" | |
| echo "Downloading ${ARCHIVE} from ${URL} …" | |
| curl -fsSL --retry 3 "${URL}" -o "${ARCHIVE}" | |
| mkdir -p bin | |
| tar -xzf "${ARCHIVE}" -C bin/ | |
| echo "bin/ contents:" | |
| ls -lh bin/ | |
| - name: Verify macOS dylibs | |
| shell: bash | |
| run: | | |
| # macOS dylibs ship as versioned files + two-level symlink chain: | |
| # lib<name>.0.9.7.dylib → lib<name>.0.dylib → lib<name>.dylib | |
| GGML_VER="0.9.7" | |
| warn=0 | |
| for base in libggml libggml-base libggml-metal libggml-cpu libggml-blas; do | |
| for name in "${base}.${GGML_VER}.dylib" "${base}.0.dylib" "${base}.dylib"; do | |
| if [ -e "bin/${name}" ]; then echo "✅ bin/${name}" | |
| else echo "⚠️ bin/${name} — missing"; warn=1; fi | |
| done | |
| done | |
| for bin in ace-qwen3 dit-vae neural-codec; do | |
| if [ -f "bin/${bin}" ] && [ -x "bin/${bin}" ]; then echo "✅ bin/${bin}" | |
| else echo "⚠️ bin/${bin} — not found or not executable"; warn=1; fi | |
| done | |
| [ "$warn" = "0" ] || echo "⚠️ Some files missing — verify BINARY_VERSION=${BINARY_VERSION}" | |
| - name: Resolve release tag | |
| id: tag | |
| shell: bash | |
| run: | | |
| TAG="${GITHUB_REF_NAME:-}" | |
| [[ "$TAG" == v* ]] || TAG="${{ github.event.inputs.tag || 'v0.0.1-electron' }}" | |
| echo "tag=$TAG" >> "$GITHUB_OUTPUT" | |
| - name: Rebuild native modules for Electron | |
| run: npx @electron/rebuild --module-dir server --only better-sqlite3 | |
| - name: Build Electron app bundle (unpacked) | |
| # Build the unpacked .app directory so we can sign it before packaging. | |
| # electron-builder --dir skips DMG creation; we create the DMG ourselves | |
| # after signing so the delivered image contains a properly signed bundle. | |
| run: npm run electron:build:mac -- --dir | |
| env: | |
| CSC_IDENTITY_AUTO_DISCOVERY: false # we sign manually below | |
| npm_package_version: ${{ steps.tag.outputs.tag }} | |
| - name: Code sign the app bundle | |
| shell: bash | |
| run: | | |
| APP="$(find release/mac-arm64 -maxdepth 1 -name '*.app' | head -1)" | |
| if [ -z "$APP" ]; then | |
| echo "❌ No .app bundle found in release/mac-arm64/" | |
| ls -lh release/mac-arm64/ || true | |
| exit 1 | |
| fi | |
| echo "App bundle: $APP" | |
| chmod +x codesign.sh | |
| ./codesign.sh "$APP" | |
| env: | |
| MACOS_SIGNING_IDENTITY: ${{ secrets.MACOS_SIGNING_IDENTITY || '-' }} | |
| - name: Create DMG | |
| shell: bash | |
| run: | | |
| APP="$(find release/mac-arm64 -maxdepth 1 -name '*.app' | head -1)" | |
| APP_NAME="$(basename "$APP" .app)" | |
| VERSION="${{ steps.tag.outputs.tag }}" | |
| DMG_NAME="${APP_NAME}-${VERSION}-arm64.dmg" | |
| echo "Creating DMG: ${DMG_NAME}" | |
| mkdir -p dist_dmg | |
| cp -R "$APP" dist_dmg/ | |
| # Add Applications symlink for drag-and-drop install | |
| ln -sf /Applications dist_dmg/Applications | |
| hdiutil create \ | |
| -volname "${APP_NAME}" \ | |
| -srcfolder dist_dmg \ | |
| -ov \ | |
| -format UDZO \ | |
| "release/${DMG_NAME}" | |
| echo "✅ DMG: release/${DMG_NAME}" | |
| ls -lh "release/${DMG_NAME}" | |
| - name: Upload macOS artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: electron-macos-arm64 | |
| path: release/**/*.dmg | |
| if-no-files-found: warn | |
| retention-days: 7 | |
| # ──────────────────────────────────────────────────────────────────────────── | |
| # Linux — x86_64 → ACE-Step UI-*.AppImage + ACE-Step UI-*.snap | |
| # | |
| # The Linux ELFs have a hardcoded RUNPATH pointing to the CI build tree. | |
| # At runtime electron/main.js prepends BIN_DIR to LD_LIBRARY_PATH so the | |
| # bundled shared libraries are found regardless. | |
| # | |
| # The archive ships unversioned .so names (libggml.so) but ELFs link against | |
| # versioned sonames (libggml.so.0). We create the missing symlinks before | |
| # packaging so electron-builder includes them in extraResources. | |
| # ──────────────────────────────────────────────────────────────────────────── | |
| build-linux: | |
| name: Build — Linux x64 | |
| runs-on: ubuntu-22.04 | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| cache: "npm" | |
| - name: Install root dependencies | |
| run: npm install | |
| - name: Install server dependencies | |
| run: npm install | |
| working-directory: server | |
| - name: Build frontend | |
| run: npm run build | |
| - name: Build server | |
| run: npm run build | |
| working-directory: server | |
| - name: Download & extract acestep.cpp binaries | |
| shell: bash | |
| run: | | |
| ARCHIVE="acestep-linux-x64.tar.gz" | |
| URL="https://github.com/audiohacking/acestep.cpp/releases/download/${BINARY_VERSION}/${ARCHIVE}" | |
| echo "Downloading ${ARCHIVE} from ${URL} …" | |
| curl -fsSL --retry 3 "${URL}" -o "${ARCHIVE}" | |
| mkdir -p bin | |
| tar -xzf "${ARCHIVE}" -C bin/ | |
| echo "bin/ contents:" | |
| ls -lh bin/ | |
| - name: Create versioned soname symlinks | |
| shell: bash | |
| run: | | |
| # ELFs link against libggml.so.0 / libggml-base.so.0 (sonames) but | |
| # the archive ships the unversioned names. Create the missing links. | |
| cd bin | |
| for pair in "libggml.so:libggml.so.0" "libggml-base.so:libggml-base.so.0"; do | |
| real="${pair%%:*}" | |
| soname="${pair##*:}" | |
| if [ -f "$real" ] && [ ! -e "$soname" ]; then | |
| ln -sv "$real" "$soname" | |
| fi | |
| done | |
| echo "Symlinks:" | |
| ls -la | grep " -> " || echo "(none)" | |
| - name: Verify Linux binaries & libraries | |
| shell: bash | |
| run: | | |
| warn=0 | |
| for bin in ace-qwen3 dit-vae neural-codec; do | |
| if [ -f "bin/${bin}" ] && [ -x "bin/${bin}" ]; then echo "✅ bin/${bin}" | |
| else echo "⚠️ bin/${bin} — not found or not executable"; warn=1; fi | |
| done | |
| for lib in libggml.so libggml-base.so libggml.so.0 libggml-base.so.0; do | |
| if [ -e "bin/${lib}" ]; then echo "✅ bin/${lib}" | |
| else echo "⚠️ bin/${lib} — missing"; warn=1; fi | |
| done | |
| [ "$warn" = "0" ] || echo "⚠️ Some files missing — verify BINARY_VERSION=${BINARY_VERSION}" | |
| - name: Rebuild native modules for Electron | |
| run: npx @electron/rebuild --module-dir server --only better-sqlite3 | |
| - name: Install snapcraft | |
| run: sudo snap install snapcraft --classic | |
| - name: Resolve release tag | |
| id: tag | |
| shell: bash | |
| run: | | |
| TAG="${GITHUB_REF_NAME:-}" | |
| [[ "$TAG" == v* ]] || TAG="${{ github.event.inputs.tag || 'v0.0.1-electron' }}" | |
| echo "tag=$TAG" >> "$GITHUB_OUTPUT" | |
| - name: Build Electron package (Linux) | |
| run: npm run electron:build:linux -- --publish=never | |
| env: | |
| SNAPCRAFT_STORE_CREDENTIALS: "" # offline / no store upload | |
| npm_package_version: ${{ steps.tag.outputs.tag }} | |
| - name: Upload Linux artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: electron-linux-x64 | |
| path: | | |
| release/**/*.AppImage | |
| release/**/*.snap | |
| if-no-files-found: warn | |
| retention-days: 7 | |
| # ──────────────────────────────────────────────────────────────────────────── | |
| # Publish a GitHub Release with all platform artifacts attached. | |
| # ──────────────────────────────────────────────────────────────────────────── | |
| publish: | |
| name: Publish GitHub Release | |
| needs: [build-mac, build-linux] | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - uses: actions/download-artifact@v4 | |
| with: | |
| pattern: electron-* | |
| path: release-artifacts | |
| merge-multiple: true | |
| - name: List release artifacts | |
| run: find release-artifacts -type f | sort | |
| - name: Resolve release tag | |
| id: tag | |
| shell: bash | |
| run: | | |
| TAG="${GITHUB_REF_NAME:-}" | |
| [[ "$TAG" == v* ]] || TAG="${{ github.event.inputs.tag || 'v0.0.1-electron' }}" | |
| echo "tag=$TAG" >> "$GITHUB_OUTPUT" | |
| - name: Upload to release | |
| if: steps.tag.outputs.tag | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| gh release upload "${{ steps.tag.outputs.tag }}" \ | |
| release-artifacts/*.snap \ | |
| release-artifacts/*.dmg \ | |
| --clobber |