mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-07-25 17:03:52 +00:00
SignPath renames files (spaces become dots), causing duplicate assets on GitHub releases. Normalize filenames by stripping spaces, dots, and hyphens to match signed files back to their originals, then overwrite in-place to preserve the correct asset names for website download links.
444 lines
18 KiB
YAML
444 lines
18 KiB
YAML
name: Build All & Release
|
|
on:
|
|
push:
|
|
branches: [master, release/*, test/git-actions]
|
|
tags:
|
|
- v*
|
|
|
|
permissions:
|
|
contents: write
|
|
|
|
jobs:
|
|
create-release:
|
|
runs-on: ubuntu-latest
|
|
if: startsWith(github.ref, 'refs/tags/v')
|
|
steps:
|
|
- name: Create Draft Release
|
|
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
|
|
with:
|
|
tag_name: ${{ github.ref_name }}
|
|
draft: true
|
|
prerelease: ${{ contains(github.ref, 'RC') || contains(github.ref, 'beta') || contains(github.ref, 'alpha') }}
|
|
|
|
linux-bin-and-snap-release:
|
|
runs-on: ubuntu-latest
|
|
env:
|
|
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }}
|
|
UNSPLASH_KEY: ${{ secrets.UNSPLASH_KEY }}
|
|
UNSPLASH_CLIENT_ID: ${{ secrets.UNSPLASH_CLIENT_ID }}
|
|
|
|
steps:
|
|
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6
|
|
with:
|
|
node-version: 22
|
|
- name: Check out Git repository
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
with:
|
|
persist-credentials: false
|
|
# work around for npm installs from git+https://github.com/johannesjo/J2M.git
|
|
- name: Reconfigure git to use HTTP authentication
|
|
run: >
|
|
git config --global url."https://github.com/".insteadOf
|
|
ssh://git@github.com/
|
|
|
|
- name: Get npm cache directory
|
|
id: npm-cache-dir
|
|
run: |
|
|
echo "::set-output name=dir::$(npm config get cache)"
|
|
- uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5
|
|
id: npm-cache # use this to check for `cache-hit` ==> if: steps.npm-cache.outputs.cache-hit != 'true'
|
|
with:
|
|
path: ${{ steps.npm-cache-dir.outputs.dir }}
|
|
key: ${{ runner.os }}-node22-${{ hashFiles('**/package-lock.json') }}
|
|
restore-keys: |
|
|
${{ runner.os }}-node22-
|
|
|
|
- name: Install npm Packages
|
|
# if: steps.npm-cache.outputs.cache-hit != 'true'
|
|
run: npm i
|
|
|
|
- name: Install Playwright Browsers
|
|
run: |
|
|
npx playwright install --with-deps chromium
|
|
npx playwright install-deps chromium
|
|
|
|
- run: npm run env # Generate env.generated.ts
|
|
|
|
- name: Lint
|
|
run: npm run lint
|
|
|
|
- name: Test Unit
|
|
run: npm run test
|
|
|
|
- name: Build Frontend for E2E
|
|
run: npm run buildFrontend:e2e
|
|
|
|
- name: Verify Build Output
|
|
run: |
|
|
test -f .tmp/angular-dist/browser/index.html || (echo "Build output missing" && exit 1)
|
|
test -f .tmp/angular-dist/browser/assets/bundled-plugins/api-test-plugin/manifest.json || (echo "Plugin assets missing from build" && exit 1)
|
|
|
|
- name: Test E2E
|
|
run: npm run e2e:ci
|
|
|
|
- name: 'Upload E2E results on failure'
|
|
if: ${{ failure() }}
|
|
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
|
with:
|
|
name: e2eResults
|
|
path: .tmp/e2e-test-results/**/*.*
|
|
retention-days: 14
|
|
|
|
- name: Build Frontend & Electron
|
|
run: npm run build
|
|
|
|
- name: Install Snapcraft
|
|
uses: samuelmeuli/action-snapcraft@fceeb3c308e76f3487e72ef608618de625fb7fe8 # v3
|
|
|
|
- name: Build/Release Electron app
|
|
uses: johannesjo/action-electron-builder@9ea9e2d991c97668843d57337848e3e2b1ffab3d # v1
|
|
with:
|
|
build_script_name: empty
|
|
github_token: ${{ secrets.github_token }}
|
|
release: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
|
|
|
# rename to possibly fix release issue
|
|
- run: find ./.tmp/app-builds -type f -name '*.snap' -exec sh -c 'x="{}"; mv "$x" ".tmp/app-builds/sp.snap"' \;
|
|
# Release to edge if no tag and to candidate if tag
|
|
- #otherwise it would be executed twice
|
|
if: false == startsWith(github.ref, 'refs/tags/v')
|
|
uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 # v3
|
|
with:
|
|
max_attempts: 2
|
|
timeout_minutes: 11
|
|
command: snapcraft upload .tmp/app-builds/sp.snap --release edge
|
|
# - run: snapcraft push .tmp/app-builds/sp.snap --release candidate
|
|
# if: startsWith(github.ref, 'refs/tags/v')
|
|
# - run: snapcraft promote superproductivity --from-channel latest/edge --to-channel latest/candidate --yes
|
|
# if: startsWith(github.ref, 'refs/tags/v')
|
|
|
|
mac-bin:
|
|
needs: [create-release]
|
|
runs-on: macos-latest
|
|
if: startsWith(github.ref, 'refs/tags/v')
|
|
env:
|
|
UNSPLASH_KEY: ${{ secrets.UNSPLASH_KEY }}
|
|
UNSPLASH_CLIENT_ID: ${{ secrets.UNSPLASH_CLIENT_ID }}
|
|
|
|
steps:
|
|
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6
|
|
with:
|
|
node-version: 22
|
|
- name: Echo is Release
|
|
run: echo "IS_RELEASE $IS_RELEASE, ${{ startsWith(github.ref, 'refs/tags/v') }}"
|
|
env:
|
|
IS_RELEASE: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
|
|
|
- name: Check out Git repository
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
with:
|
|
persist-credentials: false
|
|
# work around for npm installs from git+https://github.com/johannesjo/J2M.git
|
|
- name: Reconfigure git to use HTTP authentication
|
|
run: >
|
|
git config --global url."https://github.com/".insteadOf
|
|
ssh://git@github.com/
|
|
|
|
- name: Get npm cache directory
|
|
id: npm-cache-dir
|
|
run: |
|
|
echo "::set-output name=dir::$(npm config get cache)"
|
|
- uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5
|
|
id: npm-cache # use this to check for `cache-hit` ==> if: steps.npm-cache.outputs.cache-hit != 'true'
|
|
with:
|
|
path: ${{ steps.npm-cache-dir.outputs.dir }}
|
|
key: ${{ runner.os }}-node22-${{ hashFiles('**/package-lock.json') }}
|
|
restore-keys: |
|
|
${{ runner.os }}-node22-
|
|
|
|
- name: Workaround for nx issue and dmg licence issue
|
|
run: npm install @nx/nx-darwin-arm64 dmg-license
|
|
|
|
- name: Install npm Packages
|
|
# if: steps.npm-cache.outputs.cache-hit != 'true'
|
|
run: npm i
|
|
|
|
- run: 'echo "$PROVISION_PROFILE" | base64 --decode > embedded.provisionprofile'
|
|
shell: bash
|
|
env:
|
|
PROVISION_PROFILE: ${{secrets.dl_provision_profile}}
|
|
|
|
- name: Prepare for app notarization
|
|
# Import Apple API key for app notarization on macOS
|
|
run: |
|
|
mkdir -p ~/private_keys/
|
|
echo '${{ secrets.mac_api_key }}' > ~/private_keys/AuthKey_${{ secrets.mac_api_key_id }}.p8
|
|
|
|
- run: npm run env # Generate env.generated.ts
|
|
|
|
- name: Lint
|
|
run: npm run lint
|
|
|
|
- name: Test Unit
|
|
run: npm run test
|
|
env:
|
|
NODE_OPTIONS: '--max-old-space-size=4096'
|
|
|
|
# - uses: browser-actions/setup-chrome@v2
|
|
# id: setup-chrome
|
|
# - run: |
|
|
# echo Installed chromium version: ${{ steps.setup-chrome.outputs.chrome-version }}
|
|
# ${{ steps.setup-chrome.outputs.chrome-path }} --version
|
|
# Disabled because not working atm: https://github.com/super-productivity/super-productivity/actions/runs/5924016145/job/16060737982
|
|
# - name: Test E2E
|
|
# run: npm run e2e
|
|
|
|
- name: Build Frontend & Electron
|
|
run: npm run build
|
|
|
|
- name: Build/Release Electron app
|
|
uses: johannesjo/action-electron-builder@9ea9e2d991c97668843d57337848e3e2b1ffab3d # v1
|
|
with:
|
|
build_script_name: empty
|
|
github_token: ${{ secrets.github_token }}
|
|
mac_certs: ${{ secrets.mac_certs }}
|
|
mac_certs_password: ${{ secrets.mac_certs_password }}
|
|
release: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
|
# macOS notarization API key
|
|
env:
|
|
API_KEY_ID: ${{ secrets.mac_api_key_id }}
|
|
API_KEY_ISSUER_ID: ${{ secrets.mac_api_key_issuer_id }}
|
|
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
|
APPLE_ID: ${{ secrets.APPLE_ID }}
|
|
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
|
|
|
windows-bin:
|
|
needs: [create-release]
|
|
runs-on: windows-latest
|
|
if: startsWith(github.ref, 'refs/tags/v')
|
|
permissions:
|
|
contents: write
|
|
actions: read # Required by SignPath action
|
|
id-token: write # Required for SignPath origin verification
|
|
env:
|
|
UNSPLASH_KEY: ${{ secrets.UNSPLASH_KEY }}
|
|
UNSPLASH_CLIENT_ID: ${{ secrets.UNSPLASH_CLIENT_ID }}
|
|
|
|
steps:
|
|
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6
|
|
with:
|
|
node-version: 22
|
|
# required because setting via env.TZ does not work on windows
|
|
- name: Set timezone to Europe Standard Time
|
|
uses: szenius/set-timezone@1f9716b0f7120e344f0c62bb7b1ee98819aefd42 # v2.0
|
|
with:
|
|
timezoneWindows: 'W. Europe Standard Time'
|
|
|
|
- name: Check out Git repository
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
with:
|
|
persist-credentials: false
|
|
# work around for npm installs from git+https://github.com/johannesjo/J2M.git
|
|
- name: Reconfigure git to use HTTP authentication
|
|
run: >
|
|
git config --global url."https://github.com/".insteadOf
|
|
ssh://git@github.com/
|
|
|
|
- name: Get npm cache directory
|
|
id: npm-cache-dir
|
|
run: |
|
|
echo "::set-output name=dir::$(npm config get cache)"
|
|
- uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5
|
|
id: npm-cache # use this to check for `cache-hit` ==> if: steps.npm-cache.outputs.cache-hit != 'true'
|
|
with:
|
|
path: ${{ steps.npm-cache-dir.outputs.dir }}
|
|
key: ${{ runner.os }}-node22-${{ hashFiles('**/package-lock.json') }}
|
|
restore-keys: |
|
|
${{ runner.os }}-node22-
|
|
|
|
- name: Workaround for nx issue
|
|
run: npm install @nx/nx-win32-x64-msvc
|
|
|
|
- name: Install npm Packages
|
|
# if: steps.npm-cache.outputs.cache-hit != 'true'
|
|
run: npm i
|
|
|
|
- name: Setup Chrome
|
|
id: setup-chrome
|
|
uses: browser-actions/setup-chrome@4f8e94349a351df0f048634f25fec36c3c91eded # v2
|
|
|
|
- name: Export Chrome path for Karma
|
|
shell: pwsh
|
|
run: echo "CHROME_BIN=${{ steps.setup-chrome.outputs.chrome-path }}" >> $env:GITHUB_ENV
|
|
|
|
- run: npm run env # Generate env.generated.ts
|
|
|
|
- name: Lint
|
|
run: npm run lint
|
|
|
|
- name: Test Unit
|
|
run: npm run test
|
|
|
|
- name: Build Frontend & Electron
|
|
run: npm run build
|
|
|
|
- name: Build/Release Electron app
|
|
uses: johannesjo/action-electron-builder@9ea9e2d991c97668843d57337848e3e2b1ffab3d # v1
|
|
with:
|
|
build_script_name: empty
|
|
github_token: ${{ secrets.github_token }}
|
|
release: false # Don't publish unsigned; we sign first, then publish
|
|
|
|
# Publish unsigned Windows binaries first so the release is never missing Windows assets
|
|
- name: Publish unsigned Windows binaries to GitHub Release
|
|
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
|
|
with:
|
|
token: ${{ secrets.GITHUB_TOKEN }}
|
|
files: |
|
|
.tmp/app-builds/*.exe
|
|
.tmp/app-builds/*.blockmap
|
|
.tmp/app-builds/latest*.yml
|
|
draft: true
|
|
prerelease: ${{ contains(github.ref, 'RC') || contains(github.ref, 'beta') || contains(github.ref, 'alpha') }}
|
|
|
|
# --- Code signing (best-effort: signing failure won't block the release) ---
|
|
|
|
- name: Upload unsigned executables for SignPath
|
|
id: upload-unsigned
|
|
continue-on-error: true
|
|
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
|
with:
|
|
name: unsigned-windows-executables
|
|
path: .tmp/app-builds/*.exe
|
|
if-no-files-found: error
|
|
retention-days: 1
|
|
|
|
- name: Sign Windows executables with SignPath
|
|
id: signpath
|
|
if: steps.upload-unsigned.outcome == 'success'
|
|
continue-on-error: true
|
|
uses: signpath/github-action-submit-signing-request@3f9250c56651ff692d6729a2fbb0603a42d7d322 # v2
|
|
with:
|
|
api-token: ${{ secrets.SIGNPATH_API_TOKEN }}
|
|
organization-id: ${{ secrets.SIGNPATH_ORGANIZATION_ID }}
|
|
project-slug: super-productivity
|
|
signing-policy-slug: 'release-signing'
|
|
artifact-configuration-slug: 'github-zip-pe'
|
|
github-artifact-id: ${{ steps.upload-unsigned.outputs.artifact-id }}
|
|
output-artifact-directory: '.tmp/app-builds-signed/'
|
|
wait-for-completion: true
|
|
wait-for-completion-timeout-in-seconds: 600
|
|
|
|
- name: Move signed executables back to build directory
|
|
if: steps.signpath.outcome == 'success'
|
|
shell: pwsh
|
|
run: |
|
|
$signed = Get-ChildItem -Path .tmp/app-builds-signed -Filter *.exe -Recurse
|
|
if ($signed.Count -eq 0) {
|
|
Write-Error "No signed executables found in SignPath output directory"
|
|
exit 1
|
|
}
|
|
# Build a map of original filenames for matching
|
|
# SignPath may replace spaces with dots in filenames, so we normalize
|
|
# spaces, dots, and hyphens to find the corresponding original file
|
|
$originals = Get-ChildItem -Path .tmp/app-builds -Filter *.exe
|
|
$signed | ForEach-Object {
|
|
$signedName = $_.Name
|
|
$normalizedSigned = $signedName -replace '[.\-\s]', ''
|
|
$match = $originals | Where-Object { ($_.Name -replace '[.\-\s]', '') -eq $normalizedSigned }
|
|
if ($match -is [array]) {
|
|
Write-Error "Ambiguous match for $signedName — matched multiple originals: $($match.Name -join ', ')"
|
|
exit 1
|
|
}
|
|
if ($match) {
|
|
Write-Host "Replacing $($match.Name) with signed version (was: $signedName)"
|
|
Move-Item -Force $_.FullName $match.FullName
|
|
} else {
|
|
Write-Error "No matching original found for signed file: $signedName"
|
|
exit 1
|
|
}
|
|
}
|
|
Write-Host "Final executables:"
|
|
Get-ChildItem -Path .tmp/app-builds -Filter *.exe | Format-Table -AutoSize
|
|
|
|
- name: Verify code signatures
|
|
if: steps.signpath.outcome == 'success'
|
|
shell: pwsh
|
|
run: |
|
|
$exeFiles = Get-ChildItem -Path .tmp/app-builds -Filter *.exe
|
|
foreach ($exe in $exeFiles) {
|
|
$sig = Get-AuthenticodeSignature $exe.FullName
|
|
Write-Host "$($exe.Name): $($sig.Status)"
|
|
if ($sig.Status -ne 'Valid') {
|
|
Write-Error "Signature verification failed for $($exe.Name): $($sig.Status)"
|
|
exit 1
|
|
}
|
|
}
|
|
|
|
- name: Regenerate blockmaps and update latest.yml for signed executables
|
|
if: steps.signpath.outcome == 'success'
|
|
shell: pwsh
|
|
run: |
|
|
$buildDir = ".tmp/app-builds"
|
|
$exeFiles = Get-ChildItem -Path $buildDir -Filter "*.exe"
|
|
|
|
foreach ($exe in $exeFiles) {
|
|
$blockmapPath = "$($exe.FullName).blockmap"
|
|
Write-Host "Regenerating blockmap for $($exe.Name)..."
|
|
$output = npx --yes app-builder-bin blockmap --input="$($exe.FullName)" --output="$blockmapPath" --compression=gzip
|
|
Write-Host " blockmap output: $output"
|
|
}
|
|
|
|
# Update latest.yml with new sha512 hashes and sizes from signed executables
|
|
node -e "
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const yaml = require('js-yaml');
|
|
const crypto = require('crypto');
|
|
|
|
const buildDir = '.tmp/app-builds';
|
|
const ymlFiles = fs.readdirSync(buildDir).filter(f => f.startsWith('latest') && f.endsWith('.yml'));
|
|
|
|
for (const ymlFile of ymlFiles) {
|
|
const ymlPath = path.join(buildDir, ymlFile);
|
|
const ymlData = yaml.load(fs.readFileSync(ymlPath, 'utf8'));
|
|
|
|
if (ymlData.files) {
|
|
for (const fileEntry of ymlData.files) {
|
|
const filePath = path.join(buildDir, fileEntry.url);
|
|
if (fs.existsSync(filePath)) {
|
|
const fileBuffer = fs.readFileSync(filePath);
|
|
const sha512 = crypto.createHash('sha512').update(fileBuffer).digest('base64');
|
|
const size = fileBuffer.length;
|
|
console.log(ymlFile + ': Updated ' + fileEntry.url + ' sha512=' + sha512.substring(0, 20) + '... size=' + size);
|
|
fileEntry.sha512 = sha512;
|
|
fileEntry.size = size;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update top-level sha512 to match primary file
|
|
if (ymlData.path) {
|
|
const primary = ymlData.files && ymlData.files.find(f => f.url === ymlData.path);
|
|
if (primary) {
|
|
ymlData.sha512 = primary.sha512;
|
|
}
|
|
}
|
|
|
|
fs.writeFileSync(ymlPath, yaml.dump(ymlData, { lineWidth: -1 }));
|
|
console.log(ymlFile + ' updated successfully');
|
|
}
|
|
"
|
|
|
|
# Overwrite unsigned release assets with signed versions
|
|
- name: Publish signed Windows binaries to GitHub Release
|
|
if: steps.signpath.outcome == 'success'
|
|
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
|
|
with:
|
|
token: ${{ secrets.GITHUB_TOKEN }}
|
|
files: |
|
|
.tmp/app-builds/*.exe
|
|
.tmp/app-builds/*.blockmap
|
|
.tmp/app-builds/latest*.yml
|
|
draft: true
|
|
prerelease: ${{ contains(github.ref, 'RC') || contains(github.ref, 'beta') || contains(github.ref, 'alpha') }}
|