Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 128 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,89 @@ jobs:
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
npm run dist
npm run build
npx electron-builder --publish never

- name: Verify latest-mac.yml exists
run: |
if [ ! -f "release/latest-mac.yml" ]; then
echo "⚠️ latest-mac.yml not found, generating from build artifacts..."
# Get version from package.json
VERSION=$(node -p "require('./package.json').version")

# Find the DMG and ZIP files
DMG_ARM64=$(ls release/*-arm64.dmg 2>/dev/null | head -1 || true)
DMG_X64=$(ls release/*-x64.dmg 2>/dev/null | head -1 || true)
ZIP_ARM64=$(ls release/*-arm64-mac.zip 2>/dev/null | head -1 || true)
ZIP_X64=$(ls release/*-x64-mac.zip 2>/dev/null | head -1 || true)

# Generate latest-mac.yml
cat > release/latest-mac.yml << EOF
version: ${VERSION}
files:
EOF

# Add arm64 DMG if exists
if [ -n "$DMG_ARM64" ] && [ -f "$DMG_ARM64" ]; then
DMG_ARM64_NAME=$(basename "$DMG_ARM64")
DMG_ARM64_SIZE=$(stat -f%z "$DMG_ARM64" 2>/dev/null || stat -c%s "$DMG_ARM64")
DMG_ARM64_SHA512=$(shasum -a 512 "$DMG_ARM64" | cut -d' ' -f1 | xxd -r -p | base64)
cat >> release/latest-mac.yml << EOF
- url: ${DMG_ARM64_NAME}
sha512: ${DMG_ARM64_SHA512}
size: ${DMG_ARM64_SIZE}
EOF
fi

# Add x64 DMG if exists
if [ -n "$DMG_X64" ] && [ -f "$DMG_X64" ]; then
DMG_X64_NAME=$(basename "$DMG_X64")
DMG_X64_SIZE=$(stat -f%z "$DMG_X64" 2>/dev/null || stat -c%s "$DMG_X64")
DMG_X64_SHA512=$(shasum -a 512 "$DMG_X64" | cut -d' ' -f1 | xxd -r -p | base64)
cat >> release/latest-mac.yml << EOF
- url: ${DMG_X64_NAME}
sha512: ${DMG_X64_SHA512}
size: ${DMG_X64_SIZE}
EOF
fi

# Add arm64 ZIP if exists
if [ -n "$ZIP_ARM64" ] && [ -f "$ZIP_ARM64" ]; then
ZIP_ARM64_NAME=$(basename "$ZIP_ARM64")
ZIP_ARM64_SIZE=$(stat -f%z "$ZIP_ARM64" 2>/dev/null || stat -c%s "$ZIP_ARM64")
ZIP_ARM64_SHA512=$(shasum -a 512 "$ZIP_ARM64" | cut -d' ' -f1 | xxd -r -p | base64)
cat >> release/latest-mac.yml << EOF
- url: ${ZIP_ARM64_NAME}
sha512: ${ZIP_ARM64_SHA512}
size: ${ZIP_ARM64_SIZE}
EOF
fi

# Add x64 ZIP if exists
if [ -n "$ZIP_X64" ] && [ -f "$ZIP_X64" ]; then
ZIP_X64_NAME=$(basename "$ZIP_X64")
ZIP_X64_SIZE=$(stat -f%z "$ZIP_X64" 2>/dev/null || stat -c%s "$ZIP_X64")
ZIP_X64_SHA512=$(shasum -a 512 "$ZIP_X64" | cut -d' ' -f1 | xxd -r -p | base64)
cat >> release/latest-mac.yml << EOF
- url: ${ZIP_X64_NAME}
sha512: ${ZIP_X64_SHA512}
size: ${ZIP_X64_SIZE}
EOF
fi

# Add path and releaseDate
cat >> release/latest-mac.yml << EOF
path: ${DMG_ARM64_NAME:-${DMG_X64_NAME:-Bottleneck.dmg}}
releaseDate: '$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")'
EOF

echo "✅ Generated latest-mac.yml:"
cat release/latest-mac.yml
else
echo "✅ latest-mac.yml exists:"
cat release/latest-mac.yml
fi

- name: Upload macOS artifacts
uses: actions/upload-artifact@v4
with:
Expand Down Expand Up @@ -82,7 +163,52 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
npm run dist
npm run build
npx electron-builder --publish never

- name: Verify latest.yml exists
shell: pwsh
run: |
if (!(Test-Path "release/latest.yml")) {
Write-Host "⚠️ latest.yml not found, generating from build artifacts..."
$version = (Get-Content package.json | ConvertFrom-Json).version
$exe = Get-ChildItem -Path release -Filter "*.exe" | Select-Object -First 1

if ($exe) {
$exeName = $exe.Name
$exeSize = $exe.Length
$exeHash = (Get-FileHash -Path $exe.FullName -Algorithm SHA512).Hash
$exeHashBase64 = [Convert]::ToBase64String([byte[]]::new($exeHash.Length / 2))

# Convert hex to bytes then to base64
$bytes = [byte[]]::new($exeHash.Length / 2)
for ($i = 0; $i -lt $exeHash.Length; $i += 2) {
$bytes[$i / 2] = [convert]::ToByte($exeHash.Substring($i, 2), 16)
}
$exeHashBase64 = [Convert]::ToBase64String($bytes)

$releaseDate = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.000Z")

$content = @"
version: $version
files:
- url: $exeName
sha512: $exeHashBase64
size: $exeSize
path: $exeName
releaseDate: '$releaseDate'
"@
$content | Out-File -FilePath "release/latest.yml" -Encoding utf8
Write-Host "✅ Generated latest.yml:"
Get-Content "release/latest.yml"
} else {
Write-Host "❌ No exe file found in release directory"
exit 1
}
} else {
Write-Host "✅ latest.yml exists:"
Get-Content "release/latest.yml"
}

- name: Upload Windows artifacts
uses: actions/upload-artifact@v4
Expand Down
44 changes: 40 additions & 4 deletions src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -262,9 +262,28 @@ app.whenReady().then(async () => {

autoUpdater.on('error', (err) => {
console.error('Update error:', err);
mainWindow?.webContents.send('updater:error', {
message: err.message
});

// Check if this is a missing update manifest error (404)
const errorMessage = err.message || '';
const is404Error = errorMessage.includes('404') ||
errorMessage.includes('HttpError') ||
errorMessage.includes('Cannot find latest');

if (is404Error) {
// This is likely a missing latest-mac.yml or latest.yml file
// Don't show an error to the user - just log it and treat as "no update available"
console.log('[Updater] Update manifest not found (404) - treating as no update available');
console.log('[Updater] This can happen if the release is missing the update metadata files');
mainWindow?.webContents.send('updater:update-not-available', {
version: app.getVersion(),
reason: 'manifest-not-found'
});
} else {
// For other errors, send the error to the renderer
mainWindow?.webContents.send('updater:error', {
message: err.message
});
}
});
}

Expand Down Expand Up @@ -502,7 +521,24 @@ ipcMain.handle("updater:check-for-updates", async () => {
} : null
};
} catch (error) {
return { success: false, error: (error as Error).message };
const errorMessage = (error as Error).message || '';

// Check if this is a 404 error (missing update manifest)
const is404Error = errorMessage.includes('404') ||
errorMessage.includes('HttpError') ||
errorMessage.includes('Cannot find latest');

if (is404Error) {
// Treat missing update manifest as "no update available" rather than an error
console.log('[Updater] Update manifest not found - treating as no update available');
return {
success: true,
updateInfo: null,
noManifest: true
};
}

return { success: false, error: errorMessage };
} finally {
isUpdateCheckInProgress = false;
}
Expand Down