Skip to content
Merged
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
4 changes: 3 additions & 1 deletion .github/workflows/ci-cache-warmup-platform-jobs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ jobs:
strategy:
fail-fast: false
matrix:
runner: [ubuntu-24.04-arm, blacksmith-4vcpu-ubuntu-2404]
runner:
- ubuntu-24.04-arm
- ${{ github.repository == 'stablyai/orca' && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04' }}
runs-on: ${{ matrix.runner }}
timeout-minutes: 10
env:
Expand Down
48 changes: 47 additions & 1 deletion .github/workflows/fork-integration-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,52 @@ jobs:
compression-level: 0
retention-days: 14

linux:
needs: identity
runs-on: ubuntu-22.04
timeout-minutes: 90
env:
ORCA_LOCAL_BUILD_VERSION: ${{ needs.identity.outputs.version }}
ORCA_INTEGRATION_TAG: ${{ needs.identity.outputs.tag }}
ORCA_BUILD_COMMIT: ${{ needs.identity.outputs.sha }}
NODE_OPTIONS: --max-old-space-size=4096
steps:
- uses: actions/checkout@v6
with:
ref: ${{ needs.identity.outputs.sha }}
persist-credentials: false
- uses: pnpm/setup@v2
with:
install: false
- uses: actions/setup-node@v6
with:
node-version-file: package.json
cache: pnpm
- run: pnpm install --frozen-lockfile
- uses: ./.github/actions/install-mobile-dependencies
- uses: ./.github/actions/prepare-wayland-clipboard
- name: Test integration Linux build and update contracts
run: pnpm exec vitest run --config config/vitest.config.ts config/scripts/integration-builds/integration-builds.test.mjs src/shared/integration-builds/release-catalog.test.ts src/main/integration-builds/integration-update-feed.test.ts
- name: Install Linux accessibility verification dependencies
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends python3-gi gir1.2-gtk-3.0 gir1.2-atspi-2.0
- name: Build desktop and native helpers
run: pnpm run build:release
- name: Package Linux x64 AppImage
run: |
pnpm run ensure:electron-runtime
pnpm exec electron-builder --config config/scripts/integration-builds/electron-builder.cjs --linux AppImage --x64 --publish never
- name: Verify packaged Linux CLI launch
run: node config/scripts/run-linux-cli-launch-contract-docker.mjs --appimage dist/orca-linux.AppImage
- uses: actions/upload-artifact@v7
with:
name: integration-linux-x64-${{ needs.identity.outputs.sha }}
path: dist/orca-linux.AppImage
if-no-files-found: error
compression-level: 0
retention-days: 14

android:
needs: identity
env:
Expand Down Expand Up @@ -318,7 +364,7 @@ jobs:
retention-days: 14

publish:
needs: [identity, macos, android, release-summary]
needs: [identity, macos, linux, android, release-summary]
if: github.repository == 'Fartown/orca' && github.ref == 'refs/heads/fork/integration' && github.event_name != 'pull_request'
runs-on: ubuntu-latest
timeout-minutes: 20
Expand Down
6 changes: 6 additions & 0 deletions config/architecture-policies.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@
".github/workflows/ci-closed-pr-caches.yml",
"config/scripts/ci-dependency-download-cache.test.mjs",
".github/workflows/fork-integration-build.yml",
".github/workflows/pr.yml",
"config/scripts/pr-preflight-gates.test.mjs",
"config/scripts/ci-dependency-download-cache.test.mjs",
".github/workflows/ci-cache-warmup-platform-jobs.yml",
"config/scripts/mobile-web-bundle-packaging-workflow-contract.test.mjs",
"config/scripts/integration-builds/**",
"src/shared/integration-builds/**",
Expand Down Expand Up @@ -582,6 +586,8 @@
"mobile/src/mobile-web-shell/bridge-host-init.test.ts",
".github/workflows/fork-integration-build.yml",
"config/scripts/mobile-web-bundle-packaging-workflow-contract.test.mjs",
"config/scripts/ci-dependency-download-cache.test.mjs",
".github/workflows/ci-cache-warmup-platform-jobs.yml",
"config/scripts/integration-builds/**",
"src/shared/integration-builds/**",
"src/main/integration-builds/**",
Expand Down
9 changes: 8 additions & 1 deletion config/fork-features.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@
"features": [
{
"id": "integration-builds",
"title": "集成分支自动出包:同一提交的 macOS 与 Android 内测包",
"title": "集成分支自动出包:同一提交的 macOS、Linux 与 Android 内测包",
"goal": "同源发布 fork 内测包,并提供桌面更新清单和 Android 应用内更新",
"journal": "docs/issue/集成分支自动出包/journal.md",
"dependsOn": [
"config/electron-builder.config.cjs",
"config/scripts/build-mac-local.mjs",
"config/scripts/build-wayland-clipboard.mjs",
"config/scripts/run-linux-cli-launch-contract-docker.mjs",
"mobile/scripts/prepare-android-release.mjs",
"mobile/src/theme/mobile-theme.ts",
"src/main/startup/configure-process.ts",
Expand Down Expand Up @@ -63,6 +65,11 @@
"config/scripts/integration-builds/ai-release-summary.mjs"
],
"seams": [
{
"file": ".github/workflows/ci-cache-warmup-platform-jobs.yml",
"mustContain": "github.repository == 'stablyai/orca'",
"why": "Warm the Linux dependency cache on the same available runners used by fork PR checks."
},
{
"file": "config/scripts/ci-draft-pr-admission.test.mjs",
"mustContain": "repository = 'stablyai/orca'",
Expand Down
14 changes: 8 additions & 6 deletions config/scripts/ci-dependency-download-cache.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -382,14 +382,16 @@ describe('CI dependency download caches', () => {
const warmInstall = warm.steps.find((step) => step.uses === installer)
const unitInstall = units.steps.find((step) => step.uses === installer)
expect(warm['runs-on']).toBe('${{ matrix.runner }}')
expect(warm.strategy.matrix.runner).toEqual([
'ubuntu-24.04-arm',
runInNewContext(pr.jobs.test.with.runner.slice(3, -2), {
github: { repository: 'stablyai/orca' }
})
])
for (const repository of ['stablyai/orca', 'Fartown/orca']) {
const context = { github: { repository } }
expect(
warm.strategy.matrix.runner.map((runner) =>
runner.startsWith('${{') ? runInNewContext(runner.slice(3, -2), context) : runner
)
).toEqual([
'ubuntu-24.04-arm',
runInNewContext(pr.jobs.test.with.runner.slice(3, -2), context)
])
expect(runInNewContext(pr.jobs.preflight['runs-on'].slice(3, -2), context)).toBe(
repository === 'stablyai/orca' ? 'blacksmith-8vcpu-ubuntu-2404' : 'ubuntu-24.04'
)
Expand Down
4 changes: 4 additions & 0 deletions config/scripts/integration-builds/electron-builder.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,9 @@ module.exports = {
dmg: {
...upstream.dmg,
artifactName: 'orca-integration-macos-${arch}.${ext}'
},
linux: {
...upstream.linux,
target: ['AppImage']
}
}
32 changes: 26 additions & 6 deletions config/scripts/integration-builds/integration-builds.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,9 @@ describe('integration package identity and signing', () => {
expect(config.mac.target).toEqual(['dmg', 'zip'])
expect(config.extraMetadata.orcaUpdateChannel).toBe('integration')
expect(config.extraMetadata.version).toBe(env.ORCA_LOCAL_BUILD_VERSION)
expect(config.linux.extraResources).toBe(upstream.linux.extraResources)
expect(config.linux.target).toEqual(['AppImage'])
expect(config.appImage).toBe(upstream.appImage)
})

it('refuses mixing formal release credentials and integration packaging', () => {
Expand Down Expand Up @@ -238,9 +241,20 @@ describe('complete, immutable fork prereleases', () => {
const manifest = JSON.parse(readFileSync(join(directory, 'build-info.json'), 'utf8'))
expect(manifest.sha).toBe(sha)
expect(manifest.assets.map((asset) => asset.name)).toEqual([
...PACKAGE_NAMES,
...PACKAGE_NAMES.filter((name) => name !== 'orca-linux.AppImage'),
LEGACY_INTEL_PLACEHOLDER
])
expect(manifest.linuxAssets.map((asset) => asset.name)).toEqual(['orca-linux.AppImage'])
const linuxUpdate = parse(readFileSync(join(directory, 'latest-linux.yml'), 'utf8'))
expect(linuxUpdate.version).toBe(env.ORCA_LOCAL_BUILD_VERSION)
expect(linuxUpdate.files).toEqual(
manifest.linuxAssets.map((asset) => ({
url: asset.name,
sha512: asset.sha512,
size: asset.bytes
}))
)
expect(gh.mock.calls[3][0]).toContain(join(directory, 'latest-linux.yml'))
expect(manifest.androidVersionCode).toBe(androidConfig.androidVersionCode(timestamp))
expect(manifest.desktopVersion).toBe('1.2.3-preview.2')
expect(manifest.macCertificateSha256).toBe(signingCertificate().sha256)
Expand Down Expand Up @@ -284,15 +298,15 @@ describe('complete, immutable fork prereleases', () => {
)
})

it('does not create a release when one platform is missing or empty', async () => {
it.each(PACKAGE_NAMES)('does not publish when %s is missing or empty', async (name) => {
const directory = packages()
const gh = github()
writeFileSync(join(directory, PACKAGE_NAMES[2]), '')
writeFileSync(join(directory, name), '')
await expect(publishRelease({ env, directory, mobile, gh, git: repository() })).rejects.toThrow(
/empty/
)
expect(gh).not.toHaveBeenCalled()
rmSync(join(directory, PACKAGE_NAMES[2]))
rmSync(join(directory, name))
await expect(hashPackages(directory)).rejects.toThrow()
})

Expand Down Expand Up @@ -488,7 +502,7 @@ describe('workflow wiring', () => {
"${{ github.event_name == 'pull_request' }}"
)
expect(workflow.jobs.identity.if).toContain("github.repository == 'Fartown/orca'")
for (const name of ['macos', 'android']) {
for (const name of ['macos', 'linux', 'android']) {
const job = workflow.jobs[name]
expect(job.needs).toBe('identity')
expect(job.steps[0].with.ref).toBe('${{ needs.identity.outputs.sha }}')
Expand All @@ -504,7 +518,13 @@ describe('workflow wiring', () => {
(step) => step.name === 'Package internal-test DMG and update ZIP'
).run
).toContain('--publish never')
expect(workflow.jobs.publish.needs).toEqual(['identity', 'macos', 'android', 'release-summary'])
expect(workflow.jobs.publish.needs).toEqual([
'identity',
'macos',
'linux',
'android',
'release-summary'
])
expect(workflow.jobs.macos.env.CSC_FOR_PULL_REQUEST).toBe('true')
expect(workflow.jobs.publish.if).toContain("github.ref == 'refs/heads/fork/integration'")
expect(workflow.jobs.publish.if).not.toContain('always()')
Expand Down
24 changes: 19 additions & 5 deletions config/scripts/integration-builds/publish-release.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ import {
export const PACKAGE_NAMES = [
'orca-integration-macos-arm64.dmg',
'orca-integration-macos-arm64.zip',
'orca-integration-android.apk'
'orca-integration-android.apk',
'orca-linux.AppImage'
]
const MAC_UPDATE_ZIP = 'orca-integration-macos-arm64.zip'

Expand Down Expand Up @@ -69,6 +70,7 @@ const INSTALL_NOTES = [
'提供 Apple Silicon ZIP 与 latest-mac.yml,支持同一固定签名身份之间的原生自动更新。现有 ad-hoc 旧版需先手动安装一次 DMG,之后可应用内下载并确认重启更新。',
`Intel 版已停止提供。${LEGACY_INTEL_PLACEHOLDER} 不是安装包,只是一份说明,让 2026-09-17 之前的集成包仍把本次发布识别为完整并继续自动更新。`,
'Android 集成包自动检查 fork 更新,可在应用内下载 APK 后通过系统确认安装。旧版需先手动安装一次;不同签名安装不能覆盖。APK 使用 Expo debug 内测签名,不用于商店发布。',
'Linux x64 提供 AppImage 与 latest-linux.yml,复用上游原生依赖和 glibc 2.31 门槛。已解包运行的安装需要手动替换;安装前保留原应用和配置。',
'build-info.json 和 SHA256SUMS.txt 记录来源及下载校验和。'
]

Expand Down Expand Up @@ -103,7 +105,7 @@ function buildNotes(
foldedSection(
'构建信息',
bulletList([
`版本:macOS ${version};Android ${android.version}(versionCode ${android.versionCode})`,
`版本:macOS / Linux ${version};Android ${android.version}(versionCode ${android.versionCode})`,
`提交:[\`${sha.slice(0, 12)}\`](https://github.com/${repo}/commit/${sha})`,
`构建:${runUrl}`,
since
Expand Down Expand Up @@ -232,11 +234,14 @@ export async function publishRelease({
androidCertificateSha256: ANDROID_CERT_SHA256,
macSigning: 'fixed self-signed publisher, not notarized',
macCertificateSha256,
assets: published,
// Older clients reject unknown names in assets, but ignore optional manifest fields.
assets: published.filter((asset) => asset.name !== 'orca-linux.AppImage'),
linuxAssets: published.filter((asset) => asset.name === 'orca-linux.AppImage'),
changes: manifestChanges(context.buildChanges, syncSummaries)
}
writeFileSync(join(directory, 'build-info.json'), `${JSON.stringify(manifest, null, 2)}\n`)
writeFileSync(join(directory, 'latest-mac.yml'), macUpdateManifest(version, assets))
writeFileSync(join(directory, 'latest-linux.yml'), linuxUpdateManifest(version, assets))
writeFileSync(
join(directory, 'SHA256SUMS.txt'),
published.map((a) => `${a.sha256} ${a.name}\n`).join('')
Expand Down Expand Up @@ -304,6 +309,7 @@ export async function publishRelease({
...PACKAGE_NAMES,
LEGACY_INTEL_PLACEHOLDER,
'latest-mac.yml',
'latest-linux.yml',
'build-info.json',
'SHA256SUMS.txt'
].map((name) => join(directory, name))
Expand Down Expand Up @@ -337,9 +343,17 @@ export async function publishRelease({
}

export function macUpdateManifest(version, assets) {
const files = assets.filter((asset) => asset.name === MAC_UPDATE_ZIP)
return updateManifest(version, assets, MAC_UPDATE_ZIP)
}

export function linuxUpdateManifest(version, assets) {
return updateManifest(version, assets, 'orca-linux.AppImage')
}

function updateManifest(version, assets, name) {
const files = assets.filter((asset) => asset.name === name)
if (files.length !== 1) {
throw new Error('The Apple Silicon update ZIP is required.')
throw new Error(`The update package is required: ${name}`)
}
return [
`version: ${JSON.stringify(version)}`,
Expand Down
Loading
Loading