fix(release): publish binaries despite partial build failures
The Binary Release workflow stopped producing any GitHub Release assets because every release had at least one failing build leg, and the github-release job (needs: all four builds, no if:) was skipped whenever any leg failed — suppressing even successfully-built platforms. Root causes fixed: - github-release: add `if: !cancelled()` + zero-artifact guard so a single failing leg yields a partial release instead of none. - setup-node-pnpm cache key: add runner.arch. runner.os is only Linux/macOS/Windows, so arm64 runners restored x64 node_modules missing native deps (@rollup/rollup-linux-arm64-gnu), crashing `pnpm build`. - macOS CLI sign step: guard on APPLE_CERTIFICATE_BASE64 so unsigned binaries still publish when certs are absent; add timeout-minutes: 30 to build-binaries to avoid 24h runner hangs. - dependency-graph plugin: replace unix cp/mkdir -p (failed on Windows cmd.exe) with a cross-platform node copy script. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
7
.github/actions/setup-node-pnpm/action.yml
vendored
7
.github/actions/setup-node-pnpm/action.yml
vendored
@@ -30,7 +30,10 @@ runs:
|
|||||||
cache: pnpm
|
cache: pnpm
|
||||||
registry-url: ${{ inputs.registry-url }}
|
registry-url: ${{ inputs.registry-url }}
|
||||||
|
|
||||||
# Cache node_modules only for the exact pnpm-lock.yaml hash and Node/OS tuple.
|
# Cache node_modules only for the exact pnpm-lock.yaml hash and Node/OS/arch tuple.
|
||||||
|
# runner.arch is essential: pnpm only installs the current platform's optional
|
||||||
|
# native deps (e.g. @rollup/rollup-linux-arm64-gnu), so an x64 cache restored on
|
||||||
|
# an arm64 runner (same runner.os) would be missing native binaries and break builds.
|
||||||
# Intentionally no restore-keys fallback: partial restores can create inconsistent trees.
|
# Intentionally no restore-keys fallback: partial restores can create inconsistent trees.
|
||||||
- name: Cache node_modules
|
- name: Cache node_modules
|
||||||
id: node-modules-cache
|
id: node-modules-cache
|
||||||
@@ -41,7 +44,7 @@ runs:
|
|||||||
node_modules
|
node_modules
|
||||||
**/node_modules
|
**/node_modules
|
||||||
!**/.cache
|
!**/.cache
|
||||||
key: node-modules-${{ runner.os }}-node${{ inputs.node-version }}-${{ hashFiles('pnpm-lock.yaml') }}
|
key: node-modules-${{ runner.os }}-${{ runner.arch }}-node${{ inputs.node-version }}-${{ hashFiles('pnpm-lock.yaml') }}
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
if: ${{ inputs.skip-install != 'true' && steps.node-modules-cache.outputs.cache-hit != 'true' }}
|
if: ${{ inputs.skip-install != 'true' && steps.node-modules-cache.outputs.cache-hit != 'true' }}
|
||||||
|
|||||||
24
.github/workflows/release.yml
vendored
24
.github/workflows/release.yml
vendored
@@ -24,6 +24,11 @@ jobs:
|
|||||||
build-binaries:
|
build-binaries:
|
||||||
name: Build ${{ matrix.target }}
|
name: Build ${{ matrix.target }}
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
timeout-minutes: 30
|
||||||
|
# Job-level env so the macOS signing step's `if:` can detect whether the
|
||||||
|
# Apple certificate secret is configured (secrets can't be read in `if:` directly).
|
||||||
|
env:
|
||||||
|
APPLE_CERTIFICATE_BASE64: ${{ secrets.APPLE_CERTIFICATE_BASE64 }}
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@@ -65,9 +70,10 @@ jobs:
|
|||||||
run: test -f packages/cli/dist/${{ matrix.binary }}
|
run: test -f packages/cli/dist/${{ matrix.binary }}
|
||||||
|
|
||||||
- name: Sign macOS binary
|
- name: Sign macOS binary
|
||||||
if: runner.os == 'macOS'
|
# Skip when the Apple certificate secret is absent so unsigned binaries
|
||||||
|
# still publish, mirroring the desktop-macos unsigned fallback path.
|
||||||
|
if: ${{ runner.os == 'macOS' && env.APPLE_CERTIFICATE_BASE64 != '' }}
|
||||||
env:
|
env:
|
||||||
APPLE_CERTIFICATE_BASE64: ${{ secrets.APPLE_CERTIFICATE_BASE64 }}
|
|
||||||
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||||
APPLE_IDENTITY: ${{ secrets.APPLE_IDENTITY }}
|
APPLE_IDENTITY: ${{ secrets.APPLE_IDENTITY }}
|
||||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||||
@@ -349,6 +355,10 @@ jobs:
|
|||||||
github-release:
|
github-release:
|
||||||
name: Create GitHub Release
|
name: Create GitHub Release
|
||||||
needs: [build-binaries, build-desktop-windows, build-desktop-macos, build-desktop-linux]
|
needs: [build-binaries, build-desktop-windows, build-desktop-macos, build-desktop-linux]
|
||||||
|
# Run as long as the workflow wasn't cancelled, even if some build legs failed.
|
||||||
|
# Without this, a single failing matrix leg skips the release entirely and no
|
||||||
|
# binaries are published — including the ones that built successfully.
|
||||||
|
if: ${{ !cancelled() }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
@@ -360,13 +370,23 @@ jobs:
|
|||||||
path: artifacts
|
path: artifacts
|
||||||
|
|
||||||
- name: Collect release files
|
- name: Collect release files
|
||||||
|
id: collect
|
||||||
run: |
|
run: |
|
||||||
mkdir release-files
|
mkdir release-files
|
||||||
find artifacts -type f \( -name "fn-*" -o -name "*.sha256" -o -name "*.asc" -o -name "*.exe" -o -name "*.exe.sha256" -o -name "*.blockmap" -o -name "*.dmg" -o -name "*.dmg.sha256" -o -name "*.zip" -o -name "*.zip.sha256" -o -name "*.AppImage" -o -name "*.AppImage.sha256" -o -name "*.deb" -o -name "*.deb.sha256" -o -name "*.tar.gz" -o -name "*.tar.gz.sha256" -o -name "latest*.yml" \) -exec cp {} release-files/ \;
|
find artifacts -type f \( -name "fn-*" -o -name "*.sha256" -o -name "*.asc" -o -name "*.exe" -o -name "*.exe.sha256" -o -name "*.blockmap" -o -name "*.dmg" -o -name "*.dmg.sha256" -o -name "*.zip" -o -name "*.zip.sha256" -o -name "*.AppImage" -o -name "*.AppImage.sha256" -o -name "*.deb" -o -name "*.deb.sha256" -o -name "*.tar.gz" -o -name "*.tar.gz.sha256" -o -name "latest*.yml" \) -exec cp {} release-files/ \;
|
||||||
ls -la release-files/
|
ls -la release-files/
|
||||||
|
count=$(find release-files -type f | wc -l | tr -d ' ')
|
||||||
|
echo "count=$count" >> "$GITHUB_OUTPUT"
|
||||||
|
if [ "$count" -eq 0 ]; then
|
||||||
|
echo "::error::No release artifacts were produced by any build job; skipping release creation." >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Only create the release if at least one artifact exists. A failed build leg
|
||||||
|
# yields a partial release rather than none; a total wipeout fails loudly.
|
||||||
- name: Create GitHub Release
|
- name: Create GitHub Release
|
||||||
|
if: ${{ steps.collect.outputs.count != '0' }}
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v2
|
||||||
with:
|
with:
|
||||||
generate_release_notes: true
|
generate_release_notes: true
|
||||||
|
fail_on_unmatched_files: true
|
||||||
files: release-files/*
|
files: release-files/*
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc && cp src/*.css dist/ && mkdir -p dist/styles && cp src/styles/*.css dist/styles/",
|
"build": "tsc && node scripts/copy-css.mjs",
|
||||||
"pretest": "node ../../scripts/ensure-test-artifacts.mjs",
|
"pretest": "node ../../scripts/ensure-test-artifacts.mjs",
|
||||||
"test": "vitest run --silent=passed-only --reporter=dot"
|
"test": "vitest run --silent=passed-only --reporter=dot"
|
||||||
},
|
},
|
||||||
|
|||||||
25
plugins/fusion-plugin-dependency-graph/scripts/copy-css.mjs
Normal file
25
plugins/fusion-plugin-dependency-graph/scripts/copy-css.mjs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
// Cross-platform replacement for `cp src/*.css dist/ && mkdir -p dist/styles && cp src/styles/*.css dist/styles/`.
|
||||||
|
// The unix commands fail on Windows (cmd.exe), which broke the desktop EXE release build.
|
||||||
|
// Copies every .css file under src/ to the mirrored path under dist/, creating dirs as needed.
|
||||||
|
import { cp, mkdir, readdir } from "node:fs/promises";
|
||||||
|
import { dirname, join, relative } from "node:path";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
|
const root = dirname(fileURLToPath(import.meta.url)) + "/..";
|
||||||
|
const srcDir = join(root, "src");
|
||||||
|
const distDir = join(root, "dist");
|
||||||
|
|
||||||
|
async function* cssFiles(dir) {
|
||||||
|
for (const entry of await readdir(dir, { withFileTypes: true })) {
|
||||||
|
const full = join(dir, entry.name);
|
||||||
|
if (entry.isDirectory()) yield* cssFiles(full);
|
||||||
|
else if (entry.isFile() && entry.name.endsWith(".css")) yield full;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for await (const file of cssFiles(srcDir)) {
|
||||||
|
const dest = join(distDir, relative(srcDir, file));
|
||||||
|
await mkdir(dirname(dest), { recursive: true });
|
||||||
|
await cp(file, dest);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user