fix(release): two bugs in the v1.7.1 build that need a rebuild #27
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 & Release | |
| on: | |
| push: | |
| tags: | |
| - 'v*' | |
| permissions: | |
| contents: write | |
| actions: read | |
| id-token: write # Required for SignPath Trusted Build System authentication | |
| jobs: | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # JOB 1 — BUILD (matrix: Windows, macOS, Linux) | |
| # Produces unsigned installers. Does NOT publish to GitHub — signing job | |
| # owns the final upload so the release only ever contains signed artifacts. | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| build: | |
| name: Build (${{ matrix.os }}) | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - os: windows-latest | |
| target_flag: --win | |
| - os: macos-latest | |
| target_flag: --mac | |
| - os: ubuntu-latest | |
| target_flag: --linux | |
| runs-on: ${{ matrix.os }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '20' | |
| cache: 'npm' | |
| - name: Install libfuse2 (Linux only) | |
| if: matrix.os == 'ubuntu-latest' | |
| run: sudo apt-get update && sudo apt-get install -y libfuse2 | |
| - name: Install dependencies (with retry on network flakes) | |
| shell: bash | |
| run: | | |
| for attempt in 1 2 3; do | |
| echo "::group::npm ci attempt $attempt" | |
| if npm ci; then | |
| echo "::endgroup::" | |
| echo "npm ci succeeded on attempt $attempt" | |
| exit 0 | |
| fi | |
| echo "::endgroup::" | |
| echo "npm ci failed on attempt $attempt; cleaning and retrying" | |
| rm -rf node_modules | |
| sleep $((attempt * 10)) | |
| done | |
| echo "npm ci failed after 3 attempts" | |
| exit 1 | |
| - name: Build whiteboard iframe bundle | |
| run: npm run build:wb | |
| - name: Build unsigned installers (no publish) | |
| run: npx electron-builder ${{ matrix.target_flag }} --publish never | |
| env: | |
| # Skip local code-signing — SignPath handles signing in the next job. | |
| CSC_IDENTITY_AUTO_DISCOVERY: false | |
| - name: Upload unsigned Windows artifacts | |
| if: matrix.os == 'windows-latest' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: unsigned-windows | |
| path: | | |
| dist/*.exe | |
| dist/*.zip | |
| if-no-files-found: error | |
| - name: Upload unsigned macOS artifacts | |
| if: matrix.os == 'macos-latest' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: unsigned-macos | |
| path: | | |
| dist/*.dmg | |
| dist/*.zip | |
| if-no-files-found: error | |
| - name: Upload unsigned Linux artifacts | |
| if: matrix.os == 'ubuntu-latest' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: unsigned-linux | |
| path: | | |
| dist/*.AppImage | |
| dist/*.deb | |
| if-no-files-found: error | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # JOB 2 — SIGN (SignPath) | |
| # Downloads unsigned artifacts, submits each platform to SignPath, and | |
| # re-uploads the signed versions for the release job to consume. | |
| # | |
| # Prerequisites — configure once in GitHub repository settings: | |
| # Secrets → SIGNPATH_API_TOKEN (Settings → Secrets → Actions) | |
| # Variables → SIGNPATH_ORGANIZATION_ID (Settings → Variables → Actions) | |
| # SIGNPATH_PROJECT_SLUG | |
| # | |
| # In the SignPath dashboard you must also create: | |
| # • One Artifact Configuration per platform (windows-installer, macos-dmg, | |
| # linux-packages) that selects the files to sign and the certificate. | |
| # • One Signing Policy per platform that references those configurations. | |
| # • Optional: add this repo as a Trusted Build System (uses the | |
| # id-token: write permission above for keyless OIDC auth, eliminating | |
| # the need for SIGNPATH_API_TOKEN). | |
| # | |
| # macOS note: SignPath handles the Developer ID code-signing step. Apple | |
| # notarization (xcrun notarytool) is a separate process — add it here | |
| # after signing if you want full Gatekeeper trust without right-click → Open. | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| sign: | |
| name: Sign (SignPath) | |
| needs: build | |
| runs-on: ubuntu-latest | |
| steps: | |
| # SignPath requires a GitHub artifact ID (not a local path). Resolve each | |
| # artifact's numeric ID by name from the current workflow run. | |
| - name: Resolve artifact IDs | |
| id: artifact-ids | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| WIN_ID=$(gh api repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts \ | |
| --jq '.artifacts[] | select(.name == "unsigned-windows") | .id') | |
| MAC_ID=$(gh api repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts \ | |
| --jq '.artifacts[] | select(.name == "unsigned-macos") | .id') | |
| LNX_ID=$(gh api repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts \ | |
| --jq '.artifacts[] | select(.name == "unsigned-linux") | .id') | |
| echo "windows=$WIN_ID" >> $GITHUB_OUTPUT | |
| echo "macos=$MAC_ID" >> $GITHUB_OUTPUT | |
| echo "linux=$LNX_ID" >> $GITHUB_OUTPUT | |
| # ── Windows (Authenticode) ─────────────────────────────────────────────── | |
| - name: Sign Windows installer | |
| uses: SignPath/github-action-submit-signing-request@v1 | |
| with: | |
| api-token: ${{ secrets.SIGNPATH_API_TOKEN }} | |
| organization-id: ${{ vars.SIGNPATH_ORGANIZATION_ID }} | |
| project-slug: ${{ vars.SIGNPATH_PROJECT_SLUG }} | |
| signing-policy-slug: release-signing | |
| artifact-configuration-slug: windows-installer | |
| github-artifact-id: ${{ steps.artifact-ids.outputs.windows }} | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| wait-for-completion: true | |
| output-artifact-directory: ./signed/windows/ | |
| # ── macOS (Apple Developer ID) ─────────────────────────────────────────── | |
| # Requires an Apple Developer ID Application certificate loaded in SignPath. | |
| - name: Sign macOS disk image | |
| uses: SignPath/github-action-submit-signing-request@v1 | |
| with: | |
| api-token: ${{ secrets.SIGNPATH_API_TOKEN }} | |
| organization-id: ${{ vars.SIGNPATH_ORGANIZATION_ID }} | |
| project-slug: ${{ vars.SIGNPATH_PROJECT_SLUG }} | |
| signing-policy-slug: release-signing | |
| artifact-configuration-slug: macos-dmg | |
| github-artifact-id: ${{ steps.artifact-ids.outputs.macos }} | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| wait-for-completion: true | |
| output-artifact-directory: ./signed/macos/ | |
| # ── Linux (GPG) ────────────────────────────────────────────────────────── | |
| # Requires a GPG key configured in SignPath. | |
| - name: Sign Linux packages | |
| uses: SignPath/github-action-submit-signing-request@v1 | |
| with: | |
| api-token: ${{ secrets.SIGNPATH_API_TOKEN }} | |
| organization-id: ${{ vars.SIGNPATH_ORGANIZATION_ID }} | |
| project-slug: ${{ vars.SIGNPATH_PROJECT_SLUG }} | |
| signing-policy-slug: release-signing | |
| artifact-configuration-slug: linux-packages | |
| github-artifact-id: ${{ steps.artifact-ids.outputs.linux }} | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| wait-for-completion: true | |
| output-artifact-directory: ./signed/linux/ | |
| - name: Upload signed Windows artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: signed-windows | |
| path: ./signed/windows/ | |
| if-no-files-found: error | |
| - name: Upload signed macOS artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: signed-macos | |
| path: ./signed/macos/ | |
| if-no-files-found: error | |
| - name: Upload signed Linux artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: signed-linux | |
| path: ./signed/linux/ | |
| if-no-files-found: error | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # JOB 3 — PUBLISH & FINALISE RELEASE | |
| # Downloads signed artifacts, regenerates the electron-updater manifests | |
| # (latest*.yml) with correct sha512 hashes for the signed files, then | |
| # uploads everything to GitHub and marks the release as Latest. | |
| # | |
| # Why regenerate manifests? Signing changes file content, so the sha512 | |
| # hashes electron-builder embedded in the original latest*.yml no longer | |
| # match. electron-updater would reject a signed update whose hash differs | |
| # from the manifest — regenerating here keeps auto-update working correctly. | |
| # | |
| # Blockmaps: signing invalidates existing .blockmap files. We omit blockmap | |
| # references from the regenerated manifests; electron-updater gracefully | |
| # falls back to a full download instead of a binary delta. | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| release: | |
| name: Publish & Finalise Release | |
| needs: sign | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Download signed Windows artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: signed-windows | |
| path: ./signed/windows/ | |
| - name: Download signed macOS artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: signed-macos | |
| path: ./signed/macos/ | |
| - name: Download signed Linux artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: signed-linux | |
| path: ./signed/linux/ | |
| - name: Set up Node.js (for manifest generation) | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '20' | |
| - name: Regenerate electron-updater manifests from signed files | |
| shell: node {0} | |
| run: | | |
| const crypto = require('crypto'); | |
| const fs = require('fs'); | |
| const path = require('path'); | |
| const version = process.env.GITHUB_REF_NAME; | |
| const releaseDate = new Date().toISOString(); | |
| function sha512b64(p) { | |
| return crypto.createHash('sha512').update(fs.readFileSync(p)).digest('base64'); | |
| } | |
| function byExt(dir, ext) { | |
| if (!fs.existsSync(dir)) return []; | |
| return fs.readdirSync(dir).filter(f => f.endsWith(ext)).map(f => path.join(dir, f)); | |
| } | |
| function entry(p) { | |
| return ` - url: ${path.basename(p)}\n sha512: ${sha512b64(p)}\n size: ${fs.statSync(p).size}\n`; | |
| } | |
| function write(file, primary, files) { | |
| const entries = files.map(entry).join(''); | |
| fs.writeFileSync(file, | |
| `version: ${version}\nfiles:\n${entries}` + | |
| `path: ${path.basename(primary)}\nsha512: ${sha512b64(primary)}\nreleaseDate: '${releaseDate}'\n` | |
| ); | |
| console.log(`Generated ${file}`); | |
| } | |
| // latest.yml — Windows (.exe) | |
| const exes = byExt('./signed/windows', '.exe'); | |
| if (exes.length) write('latest.yml', exes[0], exes); | |
| // latest-mac.yml — macOS (.dmg + .zip) | |
| const dmgs = byExt('./signed/macos', '.dmg'); | |
| if (dmgs.length) write('latest-mac.yml', dmgs[0], [...dmgs, ...byExt('./signed/macos', '.zip')]); | |
| // latest-linux.yml — Linux (.AppImage; .deb listed as supplementary) | |
| const imgs = byExt('./signed/linux', '.AppImage'); | |
| if (imgs.length) write('latest-linux.yml', imgs[0], [...imgs, ...byExt('./signed/linux', '.deb')]); | |
| - name: Publish signed artifacts + manifests to GitHub release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| name: Note++ ${{ github.ref_name }} | |
| tag_name: ${{ github.ref_name }} | |
| draft: false | |
| prerelease: false | |
| make_latest: 'true' | |
| generate_release_notes: true | |
| files: | | |
| signed/windows/*.exe | |
| signed/windows/*.zip | |
| signed/macos/*.dmg | |
| signed/macos/*.zip | |
| signed/linux/*.AppImage | |
| signed/linux/*.deb | |
| latest.yml | |
| latest-mac.yml | |
| latest-linux.yml | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |