mirror of
https://github.com/stablyai/orca
synced 2026-04-21 14:17:16 +00:00
speed up release workflow (#584)
This commit is contained in:
parent
d3748e1c82
commit
b2c31b6536
2 changed files with 6 additions and 361 deletions
96
.github/workflows/release.yml
vendored
96
.github/workflows/release.yml
vendored
|
|
@ -27,28 +27,13 @@ jobs:
|
|||
include:
|
||||
- os: macos-15
|
||||
platform: mac
|
||||
arch: arm64
|
||||
artifact_name: release-mac-arm64
|
||||
build_command: pnpm build:release
|
||||
package_command: ORCA_MAC_RELEASE=1 pnpm exec electron-builder --config config/electron-builder.config.cjs --mac --arm64 --publish never
|
||||
- os: macos-15-intel
|
||||
platform: mac
|
||||
arch: x64
|
||||
artifact_name: release-mac-x64
|
||||
build_command: pnpm build:release
|
||||
package_command: ORCA_MAC_RELEASE=1 pnpm exec electron-builder --config config/electron-builder.config.cjs --mac --x64 --publish never
|
||||
release_command: ORCA_MAC_RELEASE=1 pnpm exec electron-builder --config config/electron-builder.config.cjs --mac --publish always
|
||||
- os: windows-latest
|
||||
platform: win
|
||||
arch: x64
|
||||
artifact_name: release-win-x64
|
||||
build_command: pnpm build:release
|
||||
package_command: pnpm exec electron-builder --config config/electron-builder.config.cjs --win --publish never
|
||||
release_command: pnpm exec electron-builder --config config/electron-builder.config.cjs --win --publish always
|
||||
- os: ubuntu-latest
|
||||
platform: linux
|
||||
arch: x64
|
||||
artifact_name: release-linux-x64
|
||||
build_command: pnpm build:release
|
||||
package_command: pnpm exec electron-builder --config config/electron-builder.config.cjs --linux --publish never
|
||||
release_command: pnpm exec electron-builder --config config/electron-builder.config.cjs --linux --publish always
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
|
|
@ -83,10 +68,10 @@ jobs:
|
|||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
|
||||
- name: Build app
|
||||
run: ${{ matrix.build_command }}
|
||||
run: pnpm build:release
|
||||
|
||||
- name: Package release artifacts
|
||||
run: ${{ matrix.package_command }}
|
||||
- name: Publish release artifacts
|
||||
run: ${{ matrix.release_command }}
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CSC_LINK: ${{ secrets.MAC_CERTS }}
|
||||
|
|
@ -95,81 +80,12 @@ jobs:
|
|||
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
|
||||
- name: Collect release assets
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p release-publish
|
||||
|
||||
shopt -s nullglob
|
||||
for pattern in \
|
||||
"dist/*.dmg" \
|
||||
"dist/*.zip" \
|
||||
"dist/*.AppImage" \
|
||||
"dist/*.deb" \
|
||||
"dist/*.exe" \
|
||||
"dist/*.blockmap" \
|
||||
"dist/latest*.yml"; do
|
||||
for file in $pattern; do
|
||||
cp "$file" release-publish/
|
||||
done
|
||||
done
|
||||
|
||||
# Why: each split macOS build emits a same-named latest-mac.yml into
|
||||
# dist/. We keep the arm64 manifest as canonical and rename the Intel
|
||||
# one so the publish job can merge both manifests before GitHub sees
|
||||
# the assets.
|
||||
if [[ "${{ matrix.platform }}" == "mac" && "${{ matrix.arch }}" == "x64" ]]; then
|
||||
mv release-publish/latest-mac.yml release-publish/latest-mac-x64.yml
|
||||
fi
|
||||
|
||||
- name: Upload release assets
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.artifact_name }}
|
||||
path: release-publish/*
|
||||
if-no-files-found: error
|
||||
|
||||
publish-release:
|
||||
needs: release
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24
|
||||
|
||||
- name: Download release assets
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: release-*
|
||||
path: release-assets
|
||||
|
||||
- name: Merge macOS updater manifests
|
||||
run: |
|
||||
node config/scripts/merge-mac-update-manifests.mjs \
|
||||
release-assets/release-mac-arm64/latest-mac.yml \
|
||||
release-assets/release-mac-x64/latest-mac-x64.yml
|
||||
rm release-assets/release-mac-x64/latest-mac-x64.yml
|
||||
|
||||
- name: Upload release assets
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
files=()
|
||||
while IFS= read -r -d '' file; do
|
||||
files+=("$file")
|
||||
done < <(find release-assets -type f -print0)
|
||||
|
||||
gh release upload "${{ github.ref_name }}" "${files[@]}" --clobber --repo "${{ github.repository }}"
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Publish release
|
||||
run: gh release edit ${{ github.ref_name }} --draft=false --repo ${{ github.repository }}
|
||||
env:
|
||||
|
|
|
|||
|
|
@ -1,271 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
import { readFileSync, writeFileSync } from 'node:fs'
|
||||
|
||||
function stripSingleQuotes(value) {
|
||||
if (value.startsWith("'") && value.endsWith("'")) {
|
||||
return value.slice(1, -1).replace(/''/g, "'")
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
function parseScalarValue(rawValue) {
|
||||
const trimmed = rawValue.trim()
|
||||
const isQuoted = trimmed.startsWith("'") && trimmed.endsWith("'") && trimmed.length >= 2
|
||||
const value = isQuoted ? trimmed.slice(1, -1).replace(/''/g, "'") : trimmed
|
||||
|
||||
if (isQuoted) {
|
||||
return value
|
||||
}
|
||||
if (value === 'true') {
|
||||
return true
|
||||
}
|
||||
if (value === 'false') {
|
||||
return false
|
||||
}
|
||||
if (/^-?\d+(?:\.\d+)?$/.test(value)) {
|
||||
return Number(value)
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
function finalizeFileRecord(currentFile, sourcePath, lineNumber) {
|
||||
if (currentFile == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (
|
||||
typeof currentFile.url !== 'string' ||
|
||||
typeof currentFile.sha512 !== 'string' ||
|
||||
typeof currentFile.size !== 'number'
|
||||
) {
|
||||
throw new Error(
|
||||
`Invalid macOS update manifest at ${sourcePath}:${lineNumber}: incomplete file entry.`
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
url: currentFile.url,
|
||||
sha512: currentFile.sha512,
|
||||
size: currentFile.size
|
||||
}
|
||||
}
|
||||
|
||||
function parseMacUpdateManifest(raw, sourcePath) {
|
||||
const lines = raw.split(/\r?\n/)
|
||||
const files = []
|
||||
const extras = {}
|
||||
let version = null
|
||||
let releaseDate = null
|
||||
let inFiles = false
|
||||
let currentFile = null
|
||||
|
||||
for (const [index, rawLine] of lines.entries()) {
|
||||
const lineNumber = index + 1
|
||||
const line = rawLine.trimEnd()
|
||||
if (line.length === 0) {
|
||||
continue
|
||||
}
|
||||
|
||||
const fileUrlMatch = line.match(/^ - url:\s*(.+)$/)
|
||||
if (fileUrlMatch?.[1]) {
|
||||
const finalized = finalizeFileRecord(currentFile, sourcePath, lineNumber)
|
||||
if (finalized) {
|
||||
files.push(finalized)
|
||||
}
|
||||
currentFile = { url: stripSingleQuotes(fileUrlMatch[1].trim()) }
|
||||
inFiles = true
|
||||
continue
|
||||
}
|
||||
|
||||
const fileShaMatch = line.match(/^ sha512:\s*(.+)$/)
|
||||
if (fileShaMatch?.[1]) {
|
||||
if (currentFile == null) {
|
||||
throw new Error(
|
||||
`Invalid macOS update manifest at ${sourcePath}:${lineNumber}: sha512 without a file entry.`
|
||||
)
|
||||
}
|
||||
currentFile.sha512 = stripSingleQuotes(fileShaMatch[1].trim())
|
||||
continue
|
||||
}
|
||||
|
||||
const fileSizeMatch = line.match(/^ size:\s*(\d+)$/)
|
||||
if (fileSizeMatch?.[1]) {
|
||||
if (currentFile == null) {
|
||||
throw new Error(
|
||||
`Invalid macOS update manifest at ${sourcePath}:${lineNumber}: size without a file entry.`
|
||||
)
|
||||
}
|
||||
currentFile.size = Number(fileSizeMatch[1])
|
||||
continue
|
||||
}
|
||||
|
||||
if (line === 'files:') {
|
||||
inFiles = true
|
||||
continue
|
||||
}
|
||||
|
||||
if (inFiles && currentFile != null) {
|
||||
const finalized = finalizeFileRecord(currentFile, sourcePath, lineNumber)
|
||||
if (finalized) {
|
||||
files.push(finalized)
|
||||
}
|
||||
currentFile = null
|
||||
}
|
||||
inFiles = false
|
||||
|
||||
const topLevelMatch = line.match(/^([A-Za-z][A-Za-z0-9]*):\s*(.+)$/)
|
||||
if (!topLevelMatch?.[1] || topLevelMatch[2] === undefined) {
|
||||
throw new Error(
|
||||
`Invalid macOS update manifest at ${sourcePath}:${lineNumber}: unsupported line '${line}'.`
|
||||
)
|
||||
}
|
||||
|
||||
const [, key, rawValue] = topLevelMatch
|
||||
const value = parseScalarValue(rawValue)
|
||||
|
||||
if (key === 'version') {
|
||||
if (typeof value !== 'string') {
|
||||
throw new Error(
|
||||
`Invalid macOS update manifest at ${sourcePath}:${lineNumber}: version must be a string.`
|
||||
)
|
||||
}
|
||||
version = value
|
||||
continue
|
||||
}
|
||||
|
||||
if (key === 'releaseDate') {
|
||||
if (typeof value !== 'string') {
|
||||
throw new Error(
|
||||
`Invalid macOS update manifest at ${sourcePath}:${lineNumber}: releaseDate must be a string.`
|
||||
)
|
||||
}
|
||||
releaseDate = value
|
||||
continue
|
||||
}
|
||||
|
||||
if (key === 'path' || key === 'sha512') {
|
||||
// Why: electron-updater only needs the expanded files[] entries to decide
|
||||
// which mac ZIP to fetch for the current architecture. We intentionally
|
||||
// drop the single-file legacy fields because keeping one arch-specific
|
||||
// path here would make the merged manifest lie about the other arch.
|
||||
continue
|
||||
}
|
||||
|
||||
extras[key] = value
|
||||
}
|
||||
|
||||
const finalized = finalizeFileRecord(currentFile, sourcePath, lines.length)
|
||||
if (finalized) {
|
||||
files.push(finalized)
|
||||
}
|
||||
|
||||
if (!version) {
|
||||
throw new Error(`Invalid macOS update manifest at ${sourcePath}: missing version.`)
|
||||
}
|
||||
if (!releaseDate) {
|
||||
throw new Error(`Invalid macOS update manifest at ${sourcePath}: missing releaseDate.`)
|
||||
}
|
||||
if (files.length === 0) {
|
||||
throw new Error(`Invalid macOS update manifest at ${sourcePath}: missing files.`)
|
||||
}
|
||||
|
||||
return {
|
||||
version,
|
||||
releaseDate,
|
||||
files,
|
||||
extras
|
||||
}
|
||||
}
|
||||
|
||||
function mergeExtras(primary, secondary) {
|
||||
const merged = { ...primary }
|
||||
|
||||
for (const [key, value] of Object.entries(secondary)) {
|
||||
const existing = merged[key]
|
||||
if (existing !== undefined && existing !== value) {
|
||||
throw new Error(
|
||||
`Cannot merge macOS update manifests: conflicting '${key}' values ('${existing}' vs '${value}').`
|
||||
)
|
||||
}
|
||||
merged[key] = value
|
||||
}
|
||||
|
||||
return merged
|
||||
}
|
||||
|
||||
function mergeMacUpdateManifests(primary, secondary) {
|
||||
if (primary.version !== secondary.version) {
|
||||
throw new Error(
|
||||
`Cannot merge macOS update manifests with different versions (${primary.version} vs ${secondary.version}).`
|
||||
)
|
||||
}
|
||||
|
||||
const filesByUrl = new Map()
|
||||
for (const file of [...primary.files, ...secondary.files]) {
|
||||
const existing = filesByUrl.get(file.url)
|
||||
if (existing && (existing.sha512 !== file.sha512 || existing.size !== file.size)) {
|
||||
throw new Error(
|
||||
`Cannot merge macOS update manifests: conflicting file entry for ${file.url}.`
|
||||
)
|
||||
}
|
||||
filesByUrl.set(file.url, file)
|
||||
}
|
||||
|
||||
return {
|
||||
version: primary.version,
|
||||
releaseDate:
|
||||
primary.releaseDate >= secondary.releaseDate ? primary.releaseDate : secondary.releaseDate,
|
||||
files: [...filesByUrl.values()],
|
||||
extras: mergeExtras(primary.extras, secondary.extras)
|
||||
}
|
||||
}
|
||||
|
||||
function quoteYamlString(value) {
|
||||
return `'${value.replace(/'/g, "''")}'`
|
||||
}
|
||||
|
||||
function serializeScalarValue(value) {
|
||||
if (typeof value === 'string') {
|
||||
return quoteYamlString(value)
|
||||
}
|
||||
return String(value)
|
||||
}
|
||||
|
||||
function serializeMacUpdateManifest(manifest) {
|
||||
const lines = [`version: ${manifest.version}`, 'files:']
|
||||
|
||||
for (const file of manifest.files) {
|
||||
lines.push(` - url: ${file.url}`)
|
||||
lines.push(` sha512: ${file.sha512}`)
|
||||
lines.push(` size: ${file.size}`)
|
||||
}
|
||||
|
||||
for (const [key, value] of Object.entries(manifest.extras)) {
|
||||
lines.push(`${key}: ${serializeScalarValue(value)}`)
|
||||
}
|
||||
|
||||
lines.push(`releaseDate: ${quoteYamlString(manifest.releaseDate)}`)
|
||||
lines.push('')
|
||||
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
function main() {
|
||||
const [primaryPath, secondaryPath, outputPath = primaryPath] = process.argv.slice(2)
|
||||
|
||||
if (!primaryPath || !secondaryPath) {
|
||||
console.error(
|
||||
'Usage: node config/scripts/merge-mac-update-manifests.mjs <latest-mac.yml> <latest-mac-x64.yml> [output-path]'
|
||||
)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const primary = parseMacUpdateManifest(readFileSync(primaryPath, 'utf8'), primaryPath)
|
||||
const secondary = parseMacUpdateManifest(readFileSync(secondaryPath, 'utf8'), secondaryPath)
|
||||
const merged = mergeMacUpdateManifests(primary, secondary)
|
||||
|
||||
writeFileSync(outputPath, serializeMacUpdateManifest(merged))
|
||||
}
|
||||
|
||||
main()
|
||||
Loading…
Reference in a new issue