Skip to content

Insider Pre-Release #352

Insider Pre-Release

Insider Pre-Release #352

name: Insider Pre-Release
on:
schedule:
- cron: "0 0 * * *" # daily midnight UTC
workflow_dispatch:
permissions:
contents: write
jobs:
check-commits:
runs-on: ubuntu-latest
outputs:
should_build: ${{ steps.check.outputs.should_build }}
commit_count: ${{ steps.check.outputs.commit_count }}
steps:
- name: Checkout dev branch
uses: actions/checkout@v4
with:
ref: dev
fetch-depth: 0
- name: Check for commits in the last 24 hours
id: check
run: |
COMMIT_COUNT=$(git log --since="24 hours ago" --oneline | wc -l)
echo "commit_count=$COMMIT_COUNT" >> $GITHUB_OUTPUT
if [ "$COMMIT_COUNT" -gt 0 ]; then
echo "should_build=true" >> $GITHUB_OUTPUT
else
echo "should_build=false" >> $GITHUB_OUTPUT
fi
build:
needs: check-commits
if: needs.check-commits.outputs.should_build == 'true'
runs-on: ${{ matrix.os }}
strategy:
matrix:
include:
- os: ubuntu-latest
config: buildScripts/electron-builder-linux-insider.json
artifact_name: linux-build
- os: windows-latest
config: buildScripts/electron-builder-win-insider.json
artifact_name: windows-x64-build
win_arch: x64
win_unpacked_dir: win-unpacked
- os: windows-latest
config: buildScripts/electron-builder-win-arm64-insider.json
artifact_name: windows-arm64-build
win_arch: arm64
win_unpacked_dir: win-arm64-unpacked
- os: macos-latest
config: buildScripts/electron-builder-mac-insider.json
artifact_name: macos-build
steps:
- name: Checkout dev branch
uses: actions/checkout@v4
with:
ref: dev
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
- name: Install pnpm
run: npm install -g pnpm@10.18.3
- name: Install dependencies
run: pnpm install
- name: Update version for insider build
id: version
run: |
CURRENT_VERSION=$(node -p "require('./package.json').version")
DATE_TAG=$(date +%Y%m%d)
NEW_VERSION="$CURRENT_VERSION-dev.$DATE_TAG"
echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
echo "📦 Insider version: $NEW_VERSION"
# Update package.json with new version
node -e "const fs=require('fs'); const pkg=require('./package.json'); pkg.version='$NEW_VERSION'; fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2));"
shell: bash
- name: Build application
run: pnpm run build
env:
PPTB_CHANNEL: insider
SUPABASE_URL: ${{ secrets.SUPABASE_URL }}
SUPABASE_ANON_KEY: ${{ secrets.SUPABASE_ANON_KEY }}
APPINSIGHTS_CONNECTION_STRING: ${{ secrets.APPINSIGHTS_CONNECTION_STRING }}
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
- name: Package application (Ubuntu)
if: matrix.os == 'ubuntu-latest'
run: node ./buildScripts/package.js --config=${{ matrix.config }}
env:
PPTB_CHANNEL: insider
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SUPABASE_URL: ${{ secrets.SUPABASE_URL }}
SUPABASE_ANON_KEY: ${{ secrets.SUPABASE_ANON_KEY }}
APPINSIGHTS_CONNECTION_STRING: ${{ secrets.APPINSIGHTS_CONNECTION_STRING }}
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
- name: Build application directory (Windows)
if: matrix.os == 'windows-latest'
run: pnpm exec electron-builder --config ${{ matrix.config }} --dir --${{ matrix.win_arch }}
env:
PPTB_CHANNEL: insider
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SUPABASE_URL: ${{ secrets.SUPABASE_URL }}
SUPABASE_ANON_KEY: ${{ secrets.SUPABASE_ANON_KEY }}
APPINSIGHTS_CONNECTION_STRING: ${{ secrets.APPINSIGHTS_CONNECTION_STRING }}
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
- name: Sign application binaries with Azure Trusted Signing (Windows)
if: matrix.os == 'windows-latest'
uses: azure/artifact-signing-action@v1
with:
azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }}
azure-client-id: ${{ secrets.AZURE_CLIENT_ID }}
azure-client-secret: ${{ secrets.AZURE_CLIENT_SECRET }}
endpoint: ${{ secrets.TRUSTED_SIGNING_ENDPOINT }}
signing-account-name: ${{ secrets.TRUSTED_SIGNING_ACCOUNT_NAME }}
certificate-profile-name: ${{ secrets.TRUSTED_SIGNING_CERTIFICATE_PROFILE }}
files-folder: ${{ github.workspace }}/build/${{ matrix.win_unpacked_dir }}
files-folder-filter: exe,dll
files-folder-recurse: true
timestamp-rfc3161: http://timestamp.acs.microsoft.com
timestamp-digest: SHA256
description: Power Platform ToolBox (Insider)
description-url: https://github.com/PowerPlatformToolBox/desktop-app
- name: Generate app-update.yml metadata (Windows)
if: matrix.os == 'windows-latest'
shell: powershell
run: |
$projectRoot = "${{ github.workspace }}"
$unpackedDir = Join-Path $projectRoot "build/${{ matrix.win_unpacked_dir }}"
$resourcesDir = Join-Path $unpackedDir "resources"
$appUpdateYml = Join-Path $resourcesDir "app-update.yml"
if (Test-Path $appUpdateYml) {
Write-Host "app-update.yml already exists at $appUpdateYml"
exit 0
}
$configPath = Join-Path $projectRoot "${{ matrix.config }}"
$config = Get-Content $configPath -Raw | ConvertFrom-Json
$parentConfig = $null
if ($config.extends) {
$parentPath = Join-Path $projectRoot $config.extends
if (Test-Path $parentPath) {
$parentConfig = Get-Content $parentPath -Raw | ConvertFrom-Json
}
}
$publish = $null
if ($config.publish) {
$publish = $config.publish
} elseif ($parentConfig -and $parentConfig.publish) {
$publish = $parentConfig.publish
} else {
$publish = [pscustomobject]@{
provider = "github"
owner = "PowerPlatformToolBox"
repo = "desktop-app"
}
}
$packageJsonPath = Join-Path $projectRoot "package.json"
$packageJson = Get-Content $packageJsonPath -Raw | ConvertFrom-Json
$sanitizedName = ($packageJson.name -replace "[^a-zA-Z0-9._-]", "")
$updaterCacheDirName = "{0}-updater" -f $sanitizedName.ToLowerInvariant()
if (-not (Test-Path $resourcesDir)) {
New-Item -ItemType Directory -Path $resourcesDir -Force | Out-Null
}
$lines = @()
$lines += "provider: $($publish.provider)"
if ($publish.owner) { $lines += "owner: $($publish.owner)" }
if ($publish.repo) { $lines += "repo: $($publish.repo)" }
if ($publish.releaseType) { $lines += "releaseType: $($publish.releaseType)" }
if ($publish.channel) { $lines += "channel: $($publish.channel)" }
$lines += "updaterCacheDirName: $updaterCacheDirName"
Set-Content -Path $appUpdateYml -Value ($lines -join "`n") -NoNewline
Write-Host "Generated app-update.yml at $appUpdateYml"
- name: Validate app-update.yml exists (Windows)
if: matrix.os == 'windows-latest'
shell: powershell
run: |
$unpackedDir = Join-Path "${{ github.workspace }}/build" "${{ matrix.win_unpacked_dir }}"
$resourcesDir = Join-Path $unpackedDir "resources"
$appUpdateYml = Join-Path $resourcesDir "app-update.yml"
if (-not (Test-Path $appUpdateYml)) {
Write-Host "❌ Missing updater config: $appUpdateYml"
Write-Host "Windows in-app auto-update requires NSIS metadata in resources/app-update.yml."
if (Test-Path $resourcesDir) {
Write-Host "Contents of resources directory:"
Get-ChildItem $resourcesDir | Select-Object Name, Length
}
exit 1
}
Write-Host "✅ Found updater config: $appUpdateYml"
- name: Package installers from signed directory (Windows)
if: matrix.os == 'windows-latest'
run: pnpm exec electron-builder --config ${{ matrix.config }} --prepackaged build/${{ matrix.win_unpacked_dir }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SUPABASE_URL: ${{ secrets.SUPABASE_URL }}
SUPABASE_ANON_KEY: ${{ secrets.SUPABASE_ANON_KEY }}
APPINSIGHTS_CONNECTION_STRING: ${{ secrets.APPINSIGHTS_CONNECTION_STRING }}
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
- name: Sign Windows installers with Azure Trusted Signing
if: matrix.os == 'windows-latest'
uses: azure/artifact-signing-action@v1
with:
azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }}
azure-client-id: ${{ secrets.AZURE_CLIENT_ID }}
azure-client-secret: ${{ secrets.AZURE_CLIENT_SECRET }}
endpoint: ${{ secrets.TRUSTED_SIGNING_ENDPOINT }}
signing-account-name: ${{ secrets.TRUSTED_SIGNING_ACCOUNT_NAME }}
certificate-profile-name: ${{ secrets.TRUSTED_SIGNING_CERTIFICATE_PROFILE }}
files-folder: ${{ github.workspace }}/build
files-folder-filter: exe,msi
files-folder-recurse: false
timestamp-rfc3161: http://timestamp.acs.microsoft.com
timestamp-digest: SHA256
description: Power Platform ToolBox (Insider)
description-url: https://github.com/PowerPlatformToolBox/desktop-app
- name: Regenerate latest.yml with correct SHA256 hashes (Windows)
if: matrix.os == 'windows-latest'
shell: powershell
run: |
$buildDir = "${{ github.workspace }}/build"
$latestYml = Join-Path $buildDir "latest.yml"
if (Test-Path $latestYml) {
Write-Host "Regenerating latest.yml with correct hashes..."
# Read the existing YAML to preserve version and other metadata
$ymlContent = Get-Content $latestYml -Raw
# Extract version from existing YAML
$versionMatch = [regex]::Match($ymlContent, 'version:\s+([^\s]+)')
$version = if ($versionMatch.Success) { $versionMatch.Groups[1].Value } else { "unknown" }
# Find the main EXE file (look for the installer)
$exeFiles = @(Get-ChildItem "$buildDir/*.exe" -ErrorAction SilentlyContinue)
if ($exeFiles.Count -gt 0) {
# Sort to get the latest/main installer (NSIS or main app exe)
$mainExe = $exeFiles | Where-Object { $_.Name -match "(NSIS|Setup|Installer)" } | Select-Object -First 1
if (-not $mainExe) {
$mainExe = $exeFiles[0] # Fallback to first exe
}
Write-Host "Using EXE: $($mainExe.Name)"
# Calculate SHA256 and SHA512 hashes
$sha256 = (Get-FileHash -Path $mainExe.FullName -Algorithm SHA256).Hash.ToLower()
$sha512 = (Get-FileHash -Path $mainExe.FullName -Algorithm SHA512).Hash.ToLower()
$size = $mainExe.Length
Write-Host "SHA256: $sha256"
Write-Host "SHA512: $sha512"
Write-Host "Size: $size"
# Create new YAML content with correct hashes
$releaseDate = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.000Z')
$yml = @{
version = $version
files = @(
@{
url = $mainExe.Name
sha512 = $sha512
sha256 = $sha256
size = $size
blockMapSize = $null
}
)
releaseDate = $releaseDate
}
$newYmlContent = "version: $version`n"
$newYmlContent += "files:`n"
$newYmlContent += " - url: $($mainExe.Name)`n"
$newYmlContent += " sha512: $sha512`n"
$newYmlContent += " sha256: $sha256`n"
$newYmlContent += " size: $size`n"
$newYmlContent += " blockMapSize: null`n"
$newYmlContent += "releaseDate: $releaseDate"
# Write updated YAML
Set-Content -Path $latestYml -Value $newYmlContent
Write-Host "✅ latest.yml regenerated with correct hashes"
} else {
Write-Host "⚠️ No EXE files found, skipping YAML regeneration"
}
} else {
Write-Host "⚠️ latest.yml not found at $latestYml"
}
- name: Prepare macOS signing certificate
if: matrix.os == 'macos-latest'
shell: bash
env:
MACOS_CERT_P12: ${{ secrets.MACOS_CERT_P12 }}
MACOS_CERT_PASSWORD: ${{ secrets.MACOS_CERT_PASSWORD }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: |
if [[ -z "$MACOS_CERT_P12" || -z "$MACOS_CERT_PASSWORD" || -z "$APPLE_ID" || -z "$APPLE_APP_SPECIFIC_PASSWORD" || -z "$APPLE_TEAM_ID" ]]; then
echo "Mac signing secrets are not configured."
exit 1
fi
CERT_PATH="$RUNNER_TEMP/macos-signing-cert.p12"
printf '%s' "$MACOS_CERT_P12" | base64 --decode > "$CERT_PATH"
chmod 600 "$CERT_PATH"
{
echo "CSC_LINK=$CERT_PATH"
echo "CSC_KEY_PASSWORD=$MACOS_CERT_PASSWORD"
echo "APPLE_ID=$APPLE_ID"
echo "APPLE_APP_SPECIFIC_PASSWORD=$APPLE_APP_SPECIFIC_PASSWORD"
echo "APPLE_TEAM_ID=$APPLE_TEAM_ID"
echo "MACOS_CERT_PATH=$CERT_PATH"
} >> "$GITHUB_ENV"
- name: Verify certificate details
if: matrix.os == 'macos-latest'
shell: bash
run: |
echo "=== Checking available signing identities ==="
security find-identity -v -p codesigning
echo ""
echo "Note: Should show 'Developer ID Application' not 'Mac App Distribution'"
- name: Package application (macOS)
if: matrix.os == 'macos-latest'
shell: bash
env:
PPTB_CHANNEL: insider
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SUPABASE_URL: ${{ secrets.SUPABASE_URL }}
SUPABASE_ANON_KEY: ${{ secrets.SUPABASE_ANON_KEY }}
APPINSIGHTS_CONNECTION_STRING: ${{ secrets.APPINSIGHTS_CONNECTION_STRING }}
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
DEBUG: electron-builder
CSC_FOR_PULL_REQUEST: true
CSC_IDENTITY_AUTO_DISCOVERY: true
run: |
node ./buildScripts/package.js --config=${{ matrix.config }}
- name: Verify macOS code signing (pre-notarization)
if: matrix.os == 'macos-latest'
shell: bash
run: |
APP_PATH="build/mac/Power Platform ToolBox Insider.app"
echo "=== Verifying main app signature ==="
codesign --verify --deep --strict --verbose=4 "$APP_PATH" 2>&1
echo ""
echo "=== Checking for timestamps on main app ==="
codesign -dvvv "$APP_PATH" 2>&1 | grep -i timestamp || echo "❌ WARNING: NO TIMESTAMP FOUND ON MAIN APP!"
echo ""
echo "=== Verifying critical nested binaries ==="
# Check helper apps
for helper in "$APP_PATH/Contents/Frameworks/"*.app; do
if [ -d "$helper" ]; then
echo "Checking helper: $(basename "$helper")"
codesign --verify --strict "$helper" 2>&1 || echo "❌ Failed: $helper"
codesign -dvvv "$helper" 2>&1 | grep -i timestamp || echo "❌ No timestamp: $helper"
fi
done
# Check frameworks
echo ""
echo "=== Checking frameworks ==="
find "$APP_PATH/Contents/Frameworks" -name "*.framework" -type d -maxdepth 1 | while read framework; do
echo "Checking: $(basename "$framework")"
codesign --verify --strict "$framework" 2>&1 || echo "❌ Failed: $framework"
done
# Check dylibs
echo ""
echo "=== Checking dynamic libraries ==="
find "$APP_PATH" -name "*.dylib" -type f | head -5 | while read dylib; do
echo "Checking: $(basename "$dylib")"
codesign --verify --strict "$dylib" 2>&1 || echo "❌ Failed: $dylib"
done
echo ""
echo "=== Note ==="
echo "Skipping 'spctl --assess' here because it typically reports 'source=Unnotarized Developer ID' until the notarization+stapling job completes."
echo ""
echo "✅ macOS code signing verification complete"
- name: Submit macOS notarization request
if: matrix.os == 'macos-latest'
shell: bash
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: |
node ./buildScripts/notarize.js submit --assets="build/*.dmg,build/*.zip,build/*.pkg" --app="build/mac/Power Platform ToolBox Insider.app" --output="build/notarization-info.json"
- name: Cleanup macOS signing certificate
if: ${{ always() && matrix.os == 'macos-latest' }}
shell: bash
run: |
if [[ -n "${MACOS_CERT_PATH:-}" && -f "$MACOS_CERT_PATH" ]]; then
rm -f "$MACOS_CERT_PATH"
echo "Removed temporary macOS signing certificate."
fi
- name: Remove quarantine attributes (macOS)
if: matrix.os == 'macos-latest'
run: |
echo "Removing quarantine attributes from DMG files..."
find build -name "*.dmg" -exec xattr -cr {} \; || true
echo "✅ Quarantine attributes removed"
shell: bash
- name: Regenerate latest-linux.yml with correct SHA256 hashes (Linux)
if: matrix.os == 'ubuntu-latest'
shell: bash
run: |
echo "🔄 Regenerating latest-linux.yml with correct artifact hashes..."
BUILD_DIR="${{ github.workspace }}/build"
# Find all YAML files
YML_FILES=$(find "$BUILD_DIR" -name "latest*.yml" -o -name "*-linux.yml")
if [[ -z "$YML_FILES" ]]; then
echo "⚠️ No YAML files found, skipping regeneration"
exit 0
fi
for YML_FILE in $YML_FILES; do
echo "Processing: $YML_FILE"
# Extract version from existing YAML
VERSION=$(grep -oP 'version:\s+\K[^\s]+' "$YML_FILE" || echo "unknown")
# Find the main AppImage file
APP_IMAGE=$(find "$BUILD_DIR" -maxdepth 1 -type f -name "*.AppImage" | head -n 1)
if [[ -n "$APP_IMAGE" && -f "$APP_IMAGE" ]]; then
echo " Found AppImage: $(basename "$APP_IMAGE")"
# Calculate SHA256 and SHA512 hashes
HASH256=$(sha256sum "$APP_IMAGE" | awk '{print $1}')
HASH512=$(sha512sum "$APP_IMAGE" | awk '{print $1}')
SIZE=$(stat -c %s "$APP_IMAGE" 2>/dev/null || stat -f %z "$APP_IMAGE" 2>/dev/null)
echo " SHA256: $HASH256"
echo " SHA512: $HASH512"
echo " Size: $SIZE"
# Create new YAML with correct hashes using printf to avoid YAML parsing issues
printf "version: %s\nfiles:\n - url: %s\n sha512: %s\n sha256: %s\n size: %s\n blockMapSize: null\nreleaseDate: %s\n" \
"$VERSION" \
"$(basename "$APP_IMAGE")" \
"$HASH512" \
"$HASH256" \
"$SIZE" \
"$(date -u +'%Y-%m-%dT%H:%M:%S.000Z')" > "$YML_FILE"
echo " ✅ Updated $YML_FILE"
else
echo " ⚠️ No AppImage found in $BUILD_DIR"
fi
done
echo "✅ Regeneration complete"
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.artifact_name }}
path: |
build/*.dmg
build/*.pkg
build/*.exe
build/*.msi
build/*.zip
build/*.AppImage
build/*.yml
build/notarization-info.json
retention-days: 30
prepare-release-metadata:
needs: [check-commits, build]
if: needs.check-commits.outputs.should_build == 'true'
runs-on: ubuntu-latest
outputs:
new_version: ${{ steps.version.outputs.new_version }}
tag_name: ${{ steps.version.outputs.tag_name }}
release_name: ${{ steps.version.outputs.release_name }}
whatsnew: ${{ steps.whatsnew.outputs.commits }}
steps:
- name: Checkout dev branch
uses: actions/checkout@v4
with:
ref: dev
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
- name: Calculate version
id: version
run: |
CURRENT_VERSION=$(node -p "require('./package.json').version")
DATE_TAG=$(date +%Y%m%d)
NEW_VERSION="$CURRENT_VERSION-dev.$DATE_TAG"
echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
echo "tag_name=v$NEW_VERSION" >> $GITHUB_OUTPUT
echo "release_name=Insider Dev Build - $NEW_VERSION" >> $GITHUB_OUTPUT
shell: bash
- name: Generate what's new
id: whatsnew
run: |
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
if [ -n "$LAST_TAG" ]; then
COMMITS=$(git log --oneline --no-merges -10 "$LAST_TAG"..HEAD)
else
COMMITS=$(git log --oneline --no-merges -10)
fi
WHATSNEW=$(echo "$COMMITS" | sed 's/^/- /')
echo "commits<<EOF" >> $GITHUB_OUTPUT
echo "$WHATSNEW" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
shell: bash
mac-notarization:
needs: [check-commits, build]
if: needs.check-commits.outputs.should_build == 'true'
runs-on: macos-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: dev
fetch-depth: 0
- name: Download macOS artifacts
uses: actions/download-artifact@v4
with:
name: macos-build
path: notarize
merge-multiple: true
- name: Locate notarization info
id: notarize-info
shell: bash
run: |
INFO_FILE=$(find notarize -name "notarization-info.json" | head -n 1)
if [[ -z "$INFO_FILE" ]]; then
echo "Notarization info file not found within downloaded artifacts." >&2
find notarize -maxdepth 3 -type f || true
exit 1
fi
echo "info_path=$INFO_FILE" >> $GITHUB_OUTPUT
- name: Wait for notarization acceptance
shell: bash
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: |
node ./buildScripts/notarize.js wait --info="${{ steps.notarize-info.outputs.info_path }}" --timeout-hours=12 --interval-minutes=5
- name: Staple macOS artifacts
shell: bash
run: |
found=0
while IFS= read -r -d '' file; do
found=1
echo "Stapling $(basename "$file")"
xcrun stapler staple "$file"
done < <(find notarize -type f \( -name "*.dmg" -o -name "*.pkg" \) -print0)
if [[ "$found" -eq 0 ]]; then
echo "No macOS artifacts found to staple under 'notarize'." >&2
find notarize -maxdepth 4 -type f || true
exit 1
fi
- name: Regenerate latest-mac.yml with correct SHA256 hashes
shell: bash
run: |
echo "🔄 Merging macOS x64 and ARM64 latest-mac.yml files..."
# Find all ZIP files (required for mac auto-update)
X64_ZIP=$(find notarize -name "*-x64-mac.zip" -type f | head -n 1)
ARM64_ZIP=$(find notarize -name "*-arm64-mac.zip" -type f | head -n 1)
if [[ -z "$X64_ZIP" || -z "$ARM64_ZIP" ]]; then
echo "⚠️ One or both macOS ZIP files not found. Skipping merge."
echo "X64_ZIP: $X64_ZIP"
echo "ARM64_ZIP: $ARM64_ZIP"
# Fallback to single architecture (original behavior)
YML_FILES=$(find notarize -name "latest*.yml" -o -name "*-mac.yml")
if [[ -n "$YML_FILES" ]]; then
for YML_FILE in $YML_FILES; do
VERSION=$(grep -oP 'version:\s+\K[^\s]+' "$YML_FILE" || echo "unknown")
ZIP_FILE=$(find "$(dirname "$YML_FILE")" -maxdepth 1 -name "*.zip" | head -n 1)
if [[ -n "$ZIP_FILE" && -f "$ZIP_FILE" ]]; then
HASH256=$(shasum -a 256 "$ZIP_FILE" | awk '{print $1}')
HASH512=$(shasum -a 512 "$ZIP_FILE" | awk '{print $1}')
SIZE=$(stat -f '%z' "$ZIP_FILE" 2>/dev/null || stat -c '%s' "$ZIP_FILE" 2>/dev/null)
printf "version: %s\nfiles:\n - url: %s\n sha512: %s\n sha256: %s\n size: %s\n blockMapSize: null\nreleaseDate: %s\n" \
"$VERSION" "$(basename "$ZIP_FILE")" "$HASH512" "$HASH256" "$SIZE" "$(date -u +'%Y-%m-%dT%H:%M:%S.000Z')" > "$YML_FILE"
fi
done
fi
exit 0
fi
echo "Found X64 ZIP: $(basename "$X64_ZIP")"
echo "Found ARM64 ZIP: $(basename "$ARM64_ZIP")"
# Find any existing YAML to extract version
EXISTING_YML=$(find notarize -name "latest*.yml" -o -name "*-mac.yml" | head -n 1)
VERSION=$(awk '/^version:/{print $2; exit}' "$EXISTING_YML" 2>/dev/null || echo "unknown")
echo "Version: $VERSION"
# Calculate hashes for x64 ZIP
echo "🔐 Calculating hashes for x64 ZIP..."
X64_SHA256=$(shasum -a 256 "$X64_ZIP" | awk '{print $1}')
X64_SHA512=$(shasum -a 512 "$X64_ZIP" | awk '{print $1}')
X64_SIZE=$(stat -f '%z' "$X64_ZIP" 2>/dev/null || stat -c '%s' "$X64_ZIP" 2>/dev/null)
echo " SHA256: $X64_SHA256"
echo " SHA512: $X64_SHA512"
echo " Size: $X64_SIZE"
# Calculate hashes for ARM64 ZIP
echo "🔐 Calculating hashes for ARM64 ZIP..."
ARM64_SHA256=$(shasum -a 256 "$ARM64_ZIP" | awk '{print $1}')
ARM64_SHA512=$(shasum -a 512 "$ARM64_ZIP" | awk '{print $1}')
ARM64_SIZE=$(stat -f '%z' "$ARM64_ZIP" 2>/dev/null || stat -c '%s' "$ARM64_ZIP" 2>/dev/null)
echo " SHA256: $ARM64_SHA256"
echo " SHA512: $ARM64_SHA512"
echo " Size: $ARM64_SIZE"
# Create merged YAML with both architectures
MERGED_YML="notarize/latest-mac.yml"
cat > "$MERGED_YML" << EOF
version: $VERSION
files:
- url: $(basename "$X64_ZIP")
sha512: $X64_SHA512
sha256: $X64_SHA256
size: $X64_SIZE
blockMapSize: null
- url: $(basename "$ARM64_ZIP")
sha512: $ARM64_SHA512
sha256: $ARM64_SHA256
size: $ARM64_SIZE
blockMapSize: null
releaseDate: $(date -u +'%Y-%m-%dT%H:%M:%S.000Z')
EOF
echo ""
echo "✅ Merged latest-mac.yml created:"
cat "$MERGED_YML"
# Remove any other YAML files in subdirectories to prevent conflicts
find notarize -name "latest*.yml" -o -name "*-mac.yml" | while read yml; do
if [[ "$yml" != "$MERGED_YML" ]]; then
rm -f "$yml"
echo "Removed: $yml"
fi
done
echo "✅ Regeneration complete"
- name: Upload stapled macOS artifacts
uses: actions/upload-artifact@v4
with:
name: macos-build
path: |
notarize/**/*.dmg
notarize/**/*.pkg
notarize/**/*.zip
notarize/**/*.yml
notarize/**/notarization-info.json
retention-days: 30
overwrite: true
publish-release:
needs: [check-commits, build, prepare-release-metadata, mac-notarization]
if: needs.check-commits.outputs.should_build == 'true'
runs-on: ubuntu-latest
steps:
- name: Checkout dev branch
uses: actions/checkout@v4
with:
ref: dev
fetch-depth: 0
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
- name: Display structure of downloaded files
run: ls -R artifacts
- name: Delete old pre-releases
run: |
echo "🗑️ Deleting old pre-releases..."
gh release list --limit 1000 --json tagName,isPrerelease --jq '.[] | select(.isPrerelease == true) | .tagName' | awk 'NR>2' | while IFS= read -r TAG; do
if [ -n "$TAG" ]; then
echo "Deleting pre-release: $TAG"
gh release delete "$TAG" --yes --cleanup-tag || echo "⚠️ Failed to delete $TAG (may not exist or already deleted)"
fi
done
echo "✅ Pre-release cleanup completed"
echo "⏳ Waiting for deletion to propagate..."
sleep 5
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Merge Windows latest.yml files
run: |
echo "🔄 Merging Windows x64 and ARM64 latest.yml files..."
# Find the Windows YAML files
X64_YML=$(find artifacts/windows-x64-build -name "latest.yml" 2>/dev/null || echo "")
ARM64_YML=$(find artifacts/windows-arm64-build -name "latest.yml" 2>/dev/null || echo "")
if [[ -z "$X64_YML" || -z "$ARM64_YML" ]]; then
echo "⚠️ One or both Windows YAML files not found. Skipping merge."
echo "X64_YML: $X64_YML"
echo "ARM64_YML: $ARM64_YML"
exit 0
fi
echo "Found X64 YAML: $X64_YML"
echo "Found ARM64 YAML: $ARM64_YML"
# Extract version and release date from x64 YAML (should be the same for both)
VERSION=$(grep -oP 'version:\s+\K[^\s]+' "$X64_YML" || echo "unknown")
RELEASE_DATE=$(grep -oP 'releaseDate:\s+\K.+' "$X64_YML" || date -u +'%Y-%m-%dT%H:%M:%S.000Z')
echo "Version: $VERSION"
echo "Release Date: $RELEASE_DATE"
# Find the EXE files in each artifact directory
X64_EXE=$(find artifacts/windows-x64-build -name "*-x64-win.exe" -type f | head -n 1)
ARM64_EXE=$(find artifacts/windows-arm64-build -name "*-arm64-win.exe" -type f | head -n 1)
if [[ -z "$X64_EXE" || -z "$ARM64_EXE" ]]; then
echo "❌ Could not find both x64 and ARM64 EXE files"
echo "X64_EXE: $X64_EXE"
echo "ARM64_EXE: $ARM64_EXE"
exit 1
fi
echo "Found X64 EXE: $(basename "$X64_EXE")"
echo "Found ARM64 EXE: $(basename "$ARM64_EXE")"
# Calculate hashes for x64
X64_SHA256=$(sha256sum "$X64_EXE" | awk '{print $1}')
X64_SHA512=$(sha512sum "$X64_EXE" | awk '{print $1}')
X64_SIZE=$(stat -c %s "$X64_EXE" 2>/dev/null || stat -f %z "$X64_EXE" 2>/dev/null)
echo "X64 SHA256: $X64_SHA256"
echo "X64 SHA512: $X64_SHA512"
echo "X64 Size: $X64_SIZE"
# Calculate hashes for ARM64
ARM64_SHA256=$(sha256sum "$ARM64_EXE" | awk '{print $1}')
ARM64_SHA512=$(sha512sum "$ARM64_EXE" | awk '{print $1}')
ARM64_SIZE=$(stat -c %s "$ARM64_EXE" 2>/dev/null || stat -f %z "$ARM64_EXE" 2>/dev/null)
echo "ARM64 SHA256: $ARM64_SHA256"
echo "ARM64 SHA512: $ARM64_SHA512"
echo "ARM64 Size: $ARM64_SIZE"
# Create merged YAML with both architectures
MERGED_YML="artifacts/latest.yml"
cat > "$MERGED_YML" << EOF
version: $VERSION
files:
- url: $(basename "$X64_EXE")
sha512: $X64_SHA512
sha256: $X64_SHA256
size: $X64_SIZE
blockMapSize: null
- url: $(basename "$ARM64_EXE")
sha512: $ARM64_SHA512
sha256: $ARM64_SHA256
size: $ARM64_SIZE
blockMapSize: null
releaseDate: $RELEASE_DATE
EOF
echo ""
echo "✅ Merged latest.yml created:"
cat "$MERGED_YML"
# Remove the individual YAML files so they don't get uploaded
rm -f "$X64_YML" "$ARM64_YML"
echo ""
echo "✅ Individual YAML files removed"
shell: bash
- name: Prepare release files
run: |
echo "📦 Preparing release files..."
mkdir -p release-files
# Copy all release artifacts (excluding individual Windows YAML files which were removed)
find artifacts -type f \( -name "*.AppImage" -o -name "*.dmg" -o -name "*.zip" -o -name "*.exe" -o -name "*.msi" -o -name "*.pkg" -o -name "*.snap" -o -name "*.deb" -o -name "*.rpm" -o -name "latest*.yml" \) -exec cp {} release-files/ \;
echo "✅ Release files prepared"
ls -lh release-files/
shell: bash
- name: Publish release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ needs.prepare-release-metadata.outputs.tag_name }}
name: ${{ needs.prepare-release-metadata.outputs.release_name }}
body: |
# 👀 Insider Pre-Release
## 📋 What's New
${{ needs.prepare-release-metadata.outputs.whatsnew }}
## TESTING PURPOSES ONLY - NOT FOR PRODUCTION USE
**Version:** ${{ needs.prepare-release-metadata.outputs.new_version }}
**Branch:** dev
**Commits:** ${{ needs.check-commits.outputs.commit_count }} in the last 24 hours
**Build Date:** ${{ github.run_id }}
## ⚠️ Pre-release Warning
This is an automated insider build from the development branch. It may contain:
- 🐛 Bugs and instability
- 🚧 Incomplete features
- 💥 Breaking changes
**Not recommended for production use!**
## 📦 Installation
Choose the appropriate installer for your platform:
- **Windows**: Download the `.exe` installer or the portable `.zip`
- **macOS**: Download the `.dmg` (signed + notarized) or `.zip`
- **Linux**: Download the `.AppImage` file
## 🔗 Links
- [View commits](${{ github.server_url }}/${{ github.repository }}/commits/dev)
- [Report an issue](${{ github.server_url }}/${{ github.repository }}/issues/new)
---
*This release was automatically generated by the nightly insider packaging workflow.*
files: |
release-files/*
prerelease: true
draft: false
fail_on_unmatched_files: false
make_latest: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# publish-types-dev:
# needs: [check-commits, publish-release, prepare-release-metadata]
# if: needs.check-commits.outputs.should_build == 'true'
# permissions:
# contents: read
# id-token: write
# uses: ./.github/workflows/publish-npm-types.yml
# with:
# branch: dev
# tag: dev
# version: ${{ needs.prepare-release-metadata.outputs.new_version }}
# secrets: inherit