build: publish linux arm64 release artifacts (#4755)
This commit is contained in:
parent
24000561b4
commit
531bcc9a0e
|
|
@ -716,8 +716,14 @@ jobs:
|
|||
~\AppData\Local\electron\Cache
|
||||
~\AppData\Local\electron-builder\Cache
|
||||
- os: ubuntu-latest
|
||||
platform: linux
|
||||
release_command: node config/scripts/ensure-native-runtime.mjs --runtime=electron && pnpm exec electron-builder --config config/electron-builder.config.cjs --linux --publish always
|
||||
platform: linux-x64
|
||||
release_command: node config/scripts/ensure-native-runtime.mjs --runtime=electron && pnpm exec electron-builder --config config/electron-builder.config.cjs --linux AppImage deb rpm --x64 --publish always
|
||||
eb_cache_path: |
|
||||
~/.cache/electron
|
||||
~/.cache/electron-builder
|
||||
- os: ubuntu-24.04-arm
|
||||
platform: linux-arm64
|
||||
release_command: node config/scripts/ensure-native-runtime.mjs --runtime=electron && ORCA_LINUX_ARM64_RELEASE=1 pnpm exec electron-builder --config config/electron-builder.config.cjs --linux AppImage deb rpm --arm64 --publish always
|
||||
eb_cache_path: |
|
||||
~/.cache/electron
|
||||
~/.cache/electron-builder
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ const {
|
|||
} = require('./packaged-runtime-node-modules.cjs')
|
||||
|
||||
const isMacRelease = process.env.ORCA_MAC_RELEASE === '1'
|
||||
const isLinuxArm64Release = process.env.ORCA_LINUX_ARM64_RELEASE === '1'
|
||||
const featureWallResources = {
|
||||
from: 'resources/onboarding/feature-wall',
|
||||
to: 'onboarding/feature-wall'
|
||||
|
|
@ -273,12 +274,12 @@ module.exports = {
|
|||
},
|
||||
featureWallResources
|
||||
],
|
||||
target: ['AppImage', 'deb', 'rpm'],
|
||||
target: ['AppImage', 'deb'],
|
||||
maintainer: 'stablyai',
|
||||
category: 'Utility'
|
||||
},
|
||||
appImage: {
|
||||
artifactName: 'orca-linux.${ext}'
|
||||
artifactName: isLinuxArm64Release ? 'orca-linux-arm64.${ext}' : 'orca-linux.${ext}'
|
||||
},
|
||||
deb: {
|
||||
packageName: 'orca-ide',
|
||||
|
|
|
|||
|
|
@ -71,8 +71,8 @@ describe('electron-builder config', () => {
|
|||
expect(electronBuilderConfig.linux.desktop.entry.StartupWMClass).toBe('orca')
|
||||
})
|
||||
|
||||
it('builds RPMs without changing existing Linux artifact names', () => {
|
||||
expect(electronBuilderConfig.linux.target).toEqual(['AppImage', 'deb', 'rpm'])
|
||||
it('uses AppImage and deb as local Linux targets without changing existing artifact names', () => {
|
||||
expect(electronBuilderConfig.linux.target).toEqual(['AppImage', 'deb'])
|
||||
expect(electronBuilderConfig.appImage.artifactName).toBe('orca-linux.${ext}')
|
||||
expect(electronBuilderConfig.deb.artifactName).toBe('orca-ide_${version}_${arch}.${ext}')
|
||||
expect(electronBuilderConfig.rpm).toMatchObject({
|
||||
|
|
@ -81,6 +81,26 @@ describe('electron-builder config', () => {
|
|||
})
|
||||
})
|
||||
|
||||
it('uses a distinct AppImage name for Linux arm64 release uploads', () => {
|
||||
const configPath = require.resolve('../electron-builder.config.cjs')
|
||||
const original = process.env.ORCA_LINUX_ARM64_RELEASE
|
||||
try {
|
||||
delete require.cache[configPath]
|
||||
process.env.ORCA_LINUX_ARM64_RELEASE = '1'
|
||||
expect(require('../electron-builder.config.cjs').appImage.artifactName).toBe(
|
||||
'orca-linux-arm64.${ext}'
|
||||
)
|
||||
} finally {
|
||||
if (original === undefined) {
|
||||
delete process.env.ORCA_LINUX_ARM64_RELEASE
|
||||
} else {
|
||||
process.env.ORCA_LINUX_ARM64_RELEASE = original
|
||||
}
|
||||
delete require.cache[configPath]
|
||||
require('../electron-builder.config.cjs')
|
||||
}
|
||||
})
|
||||
|
||||
it('uses Orca native rebuild hook instead of electron-builder default rebuild', () => {
|
||||
expect(electronBuilderConfig.beforeBuild).toBe(electronBuilderNativeRebuild)
|
||||
expect(electronBuilderConfig.npmRebuild).toBe(true)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
import os from 'node:os'
|
||||
|
||||
const maxOldSpaceSizeMb = 4096
|
||||
const minOldSpaceSizeMb = 2048
|
||||
const reservedSystemMemoryMb = 1024
|
||||
|
||||
export function getBuildOldSpaceSizeMb(totalMemoryBytes = os.totalmem()) {
|
||||
const totalMemoryMb = Math.floor(totalMemoryBytes / 1024 / 1024)
|
||||
const hostSizedLimitMb = Math.max(minOldSpaceSizeMb, totalMemoryMb - reservedSystemMemoryMb)
|
||||
|
||||
return Math.min(maxOldSpaceSizeMb, hostSizedLimitMb)
|
||||
}
|
||||
|
||||
export function appendBuildOldSpaceOption(existingNodeOptions, totalMemoryBytes = os.totalmem()) {
|
||||
const requestedNodeOptions = `--max-old-space-size=${getBuildOldSpaceSizeMb(totalMemoryBytes)}`
|
||||
const trimmedNodeOptions = existingNodeOptions?.trim()
|
||||
|
||||
return trimmedNodeOptions ? `${trimmedNodeOptions} ${requestedNodeOptions}` : requestedNodeOptions
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import { describe, expect, it } from 'vitest'
|
||||
import { appendBuildOldSpaceOption, getBuildOldSpaceSizeMb } from './node-old-space-limit.mjs'
|
||||
|
||||
const gib = 1024 * 1024 * 1024
|
||||
|
||||
describe('node old-space build limit', () => {
|
||||
it('caps larger hosts at the release-runner build heap', () => {
|
||||
expect(getBuildOldSpaceSizeMb(8 * gib)).toBe(4096)
|
||||
})
|
||||
|
||||
it('reserves memory for smaller arm64 hosts', () => {
|
||||
expect(getBuildOldSpaceSizeMb(4 * gib)).toBe(3072)
|
||||
})
|
||||
|
||||
it('keeps existing NODE_OPTIONS while appending the build heap limit last', () => {
|
||||
expect(appendBuildOldSpaceOption('--trace-warnings --max-old-space-size=8192', 4 * gib)).toBe(
|
||||
'--trace-warnings --max-old-space-size=8192 --max-old-space-size=3072'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
@ -50,6 +50,11 @@ describe('Electron runtime package contract', () => {
|
|||
expect(scripts['build:release']).not.toContain('build:computer-macos')
|
||||
})
|
||||
|
||||
it('runs the web build through the heap-sized Vite wrapper', () => {
|
||||
expect(packageJson.scripts['build:web']).toContain('node config/scripts/run-vite-web-build.mjs')
|
||||
expect(packageJson.scripts['build:web']).toContain('node config/scripts/verify-web-build.mjs')
|
||||
})
|
||||
|
||||
it('guards release publishing before electron-builder runs', () => {
|
||||
const releaseWorkflow = readFileSync(
|
||||
join(projectDir, '.github/workflows/release-cut.yml'),
|
||||
|
|
@ -63,7 +68,7 @@ describe('Electron runtime package contract', () => {
|
|||
])
|
||||
)
|
||||
|
||||
expect([...releaseCommands.keys()].sort()).toEqual(['linux', 'mac', 'win'])
|
||||
expect([...releaseCommands.keys()].sort()).toEqual(['linux-arm64', 'linux-x64', 'mac', 'win'])
|
||||
for (const command of releaseCommands.values()) {
|
||||
expect(command).toContain('node config/scripts/ensure-native-runtime.mjs --runtime=electron')
|
||||
expect(command).toContain('electron-builder')
|
||||
|
|
@ -72,7 +77,10 @@ describe('Electron runtime package contract', () => {
|
|||
)
|
||||
}
|
||||
expect(releaseCommands.get('mac')).toContain(' && ORCA_MAC_RELEASE=1 ')
|
||||
expect(releaseCommands.get('linux')).toContain(' && pnpm exec electron-builder ')
|
||||
expect(releaseCommands.get('linux-x64')).toContain(' && pnpm exec electron-builder ')
|
||||
expect(releaseCommands.get('linux-x64')).toContain('--linux AppImage deb rpm --x64')
|
||||
expect(releaseCommands.get('linux-arm64')).toContain('ORCA_LINUX_ARM64_RELEASE=1')
|
||||
expect(releaseCommands.get('linux-arm64')).toContain('--linux AppImage deb rpm --arm64')
|
||||
expect(releaseCommands.get('win')).toContain(
|
||||
'; if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }; pnpm exec electron-builder '
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,19 +1,15 @@
|
|||
import { spawn } from 'node:child_process'
|
||||
import { createRequire } from 'node:module'
|
||||
import path from 'node:path'
|
||||
import { appendBuildOldSpaceOption } from './node-old-space-limit.mjs'
|
||||
|
||||
const require = createRequire(import.meta.url)
|
||||
const electronVitePackageJson = require.resolve('electron-vite/package.json')
|
||||
const electronViteCli = path.join(path.dirname(electronVitePackageJson), 'bin', 'electron-vite.js')
|
||||
const requestedNodeOptions = '--max-old-space-size=4096'
|
||||
const existingNodeOptions = process.env.NODE_OPTIONS?.trim()
|
||||
|
||||
// Release builds have started OOMing on GitHub's macOS runners during the
|
||||
// renderer bundle, so we force a larger heap here in one shared entrypoint
|
||||
// instead of relying on shell-specific env syntax in individual scripts.
|
||||
const nodeOptions = existingNodeOptions
|
||||
? `${existingNodeOptions} ${requestedNodeOptions}`
|
||||
: requestedNodeOptions
|
||||
// renderer bundle. Reserve memory on smaller hosts so the OS does not kill Vite.
|
||||
const nodeOptions = appendBuildOldSpaceOption(process.env.NODE_OPTIONS)
|
||||
|
||||
const child = spawn(process.execPath, [electronViteCli, 'build', ...process.argv.slice(2)], {
|
||||
stdio: 'inherit',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
import { spawn } from 'node:child_process'
|
||||
import { createRequire } from 'node:module'
|
||||
import path from 'node:path'
|
||||
import { appendBuildOldSpaceOption } from './node-old-space-limit.mjs'
|
||||
|
||||
const require = createRequire(import.meta.url)
|
||||
const vitePackageJson = require.resolve('vite/package.json')
|
||||
const viteCli = path.join(path.dirname(vitePackageJson), 'bin', 'vite.js')
|
||||
|
||||
// Why: Raspberry Pi and release runners can hit Node's default old-space
|
||||
// ceiling, but smaller hosts still need memory left for the OS and bundler.
|
||||
const nodeOptions = appendBuildOldSpaceOption(process.env.NODE_OPTIONS)
|
||||
|
||||
const child = spawn(
|
||||
process.execPath,
|
||||
[viteCli, 'build', '--config', 'vite.web.config.ts', ...process.argv.slice(2)],
|
||||
{
|
||||
stdio: 'inherit',
|
||||
env: {
|
||||
...process.env,
|
||||
NODE_OPTIONS: nodeOptions
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
child.on('exit', (code, signal) => {
|
||||
if (signal) {
|
||||
process.kill(process.pid, signal)
|
||||
return
|
||||
}
|
||||
|
||||
process.exit(code ?? 1)
|
||||
})
|
||||
|
|
@ -8,11 +8,15 @@ export function getRequiredReleaseAssetNames(tag) {
|
|||
const version = tag.replace(/^v/i, '')
|
||||
return [
|
||||
'latest-linux.yml',
|
||||
'latest-linux-arm64.yml',
|
||||
'latest-mac.yml',
|
||||
'latest.yml',
|
||||
'orca-linux.AppImage',
|
||||
'orca-linux-arm64.AppImage',
|
||||
`orca-ide_${version}_amd64.deb`,
|
||||
`orca-ide_${version}_arm64.deb`,
|
||||
`orca-ide-${version}.x86_64.rpm`,
|
||||
`orca-ide-${version}.aarch64.rpm`,
|
||||
'orca-windows-setup.exe',
|
||||
'orca-windows-setup.exe.blockmap',
|
||||
`Orca-${version}-mac.zip`,
|
||||
|
|
@ -86,7 +90,12 @@ export async function verifyRequiredReleaseAssets({ repo, tag, token }) {
|
|||
const assetsByName = new Map(release.assets.map((asset) => [asset.name, asset]))
|
||||
|
||||
const requiredNames = new Set(getRequiredReleaseAssetNames(tag))
|
||||
const manifestNames = ['latest-linux.yml', 'latest-mac.yml', 'latest.yml']
|
||||
const manifestNames = [
|
||||
'latest-linux.yml',
|
||||
'latest-linux-arm64.yml',
|
||||
'latest-mac.yml',
|
||||
'latest.yml'
|
||||
]
|
||||
|
||||
for (const manifestName of manifestNames) {
|
||||
const manifestAsset = assetsByName.get(manifestName)
|
||||
|
|
|
|||
|
|
@ -45,12 +45,16 @@ describe('getRequiredReleaseAssetNames', () => {
|
|||
)
|
||||
})
|
||||
|
||||
it('includes the Linux RPM alongside the existing AppImage and deb names', () => {
|
||||
it('includes x64 and arm64 Linux assets', () => {
|
||||
expect(getRequiredReleaseAssetNames('v1.4.27')).toEqual(
|
||||
expect.arrayContaining([
|
||||
'latest-linux-arm64.yml',
|
||||
'orca-linux.AppImage',
|
||||
'orca-linux-arm64.AppImage',
|
||||
'orca-ide_1.4.27_amd64.deb',
|
||||
'orca-ide-1.4.27.x86_64.rpm'
|
||||
'orca-ide_1.4.27_arm64.deb',
|
||||
'orca-ide-1.4.27.x86_64.rpm',
|
||||
'orca-ide-1.4.27.aarch64.rpm'
|
||||
])
|
||||
)
|
||||
})
|
||||
|
|
@ -100,4 +104,32 @@ describe('verifyRequiredReleaseAssets', () => {
|
|||
).rejects.toThrow('Missing: Orca-1.4.27-arm64-mac.zip')
|
||||
expect(latestMacAsset).toBeTruthy()
|
||||
})
|
||||
|
||||
it('checks assets referenced by the Linux arm64 updater manifest', async () => {
|
||||
const tag = 'v1.4.27'
|
||||
const required = getRequiredReleaseAssetNames(tag)
|
||||
const release = releaseWithAssets(tag, required)
|
||||
const arm64Manifest = release.assets.find((asset) => asset.name === 'latest-linux-arm64.yml')
|
||||
const fetchMock = vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce(jsonResponse([release]))
|
||||
.mockResolvedValueOnce(jsonResponse('version: 1.4.27\n'))
|
||||
.mockResolvedValueOnce(
|
||||
jsonResponse(
|
||||
[
|
||||
'version: 1.4.27',
|
||||
'files:',
|
||||
' - url: orca-linux-arm64.AppImage.blockmap',
|
||||
'path: orca-linux-arm64.AppImage'
|
||||
].join('\n')
|
||||
)
|
||||
)
|
||||
.mockResolvedValue(jsonResponse('version: 1.4.27\n'))
|
||||
vi.stubGlobal('fetch', fetchMock)
|
||||
|
||||
await expect(
|
||||
verifyRequiredReleaseAssets({ repo: 'stablyai/orca', tag, token: 'token' })
|
||||
).rejects.toThrow('Missing: orca-linux-arm64.AppImage.blockmap')
|
||||
expect(arm64Manifest).toBeTruthy()
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@
|
|||
"audit:localization": "node config/scripts/audit-localization-coverage.mjs",
|
||||
"build:cli": "tsc -p config/tsconfig.cli.json --outDir out --composite false --incremental false && node config/scripts/verify-cli-bin.mjs --fix-executable && node config/scripts/install-dev-cli.mjs",
|
||||
"build:electron-vite": "node config/scripts/run-electron-vite-build.mjs",
|
||||
"build:web": "vite build --config vite.web.config.ts && node config/scripts/verify-web-build.mjs",
|
||||
"build:web": "node config/scripts/run-vite-web-build.mjs && node config/scripts/verify-web-build.mjs",
|
||||
"build:desktop": "pnpm run typecheck && pnpm run build:relay && pnpm run build:cli && pnpm run build:electron-vite && pnpm run build:web",
|
||||
"build": "pnpm run build:desktop && pnpm run build:native",
|
||||
"build:release": "pnpm run build:relay && pnpm run build:native && pnpm run verify:computer-native && pnpm run build:cli && pnpm run build:electron-vite && pnpm run build:web",
|
||||
|
|
@ -68,7 +68,7 @@
|
|||
"build:icons": "bash resources/icon-source/generate.sh",
|
||||
"build:mac": "pnpm run build:desktop && pnpm run build:computer-macos && pnpm run ensure:electron-runtime && electron-builder --config config/electron-builder.config.cjs --mac",
|
||||
"build:mac:release": "node config/scripts/verify-macos-release-env.mjs && ORCA_MAC_RELEASE=1 pnpm run build:desktop && ORCA_MAC_RELEASE=1 pnpm run build:computer-macos && pnpm run ensure:electron-runtime && ORCA_MAC_RELEASE=1 electron-builder --config config/electron-builder.config.cjs --mac",
|
||||
"build:linux": "pnpm run build:desktop && pnpm run ensure:electron-runtime && electron-builder --config config/electron-builder.config.cjs --linux",
|
||||
"build:linux": "pnpm run build:desktop && pnpm run ensure:electron-runtime && electron-builder --config config/electron-builder.config.cjs --linux AppImage deb",
|
||||
"test:e2e": "pnpm run ensure:electron-runtime && npx playwright test --config tests/playwright.config.ts --project electron-headless",
|
||||
"test:e2e:terminal-rendering-golden": "pnpm run ensure:electron-runtime && npx playwright test tests/e2e/terminal-raw-emoji-table-scroll-restore.spec.ts --grep @terminal-rendering-golden --config tests/playwright.config.ts --project electron-headless --workers=1",
|
||||
"test:e2e:terminal-rendering-release-evidence": "pnpm run ensure:electron-runtime && npx playwright test tests/e2e/terminal-opencode-emoji-table-rendering.spec.ts tests/e2e/terminal-long-table-scroll-restore.spec.ts --config tests/playwright.config.ts --project electron-headless --workers=2",
|
||||
|
|
|
|||
Loading…
Reference in New Issue