mirror of
https://github.com/stablyai/orca.git
synced 2026-11-02 10:08:13 +00:00
ci: share the mobile bundle across SSH Windows host cells (#31883)
This commit is contained in:
@@ -87,9 +87,29 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
mobile_web_bundle:
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.draft != true
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: ./.github/actions/install-node-dependencies
|
||||
- uses: ./.github/actions/build-mobile-web-bundle
|
||||
- uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ format('ssh-windows-mobile-web-{0}', github.sha) }}
|
||||
path: out/mobile-web/
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
overwrite: true
|
||||
|
||||
hosts:
|
||||
# A draft carries no verdict; readiness re-triggers this workflow.
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft != true }}
|
||||
needs: mobile_web_bundle
|
||||
if: >-
|
||||
${{ !cancelled() && needs.mobile_web_bundle.result == 'success' &&
|
||||
(github.event_name != 'pull_request' || github.event.pull_request.draft != true) }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -166,7 +186,18 @@ jobs:
|
||||
- uses: ./.github/actions/prepare-windows-cli-launcher
|
||||
with:
|
||||
arches: ${{ matrix.arch }}
|
||||
- uses: actions/download-artifact@v8
|
||||
id: mobile-bundle
|
||||
continue-on-error: true
|
||||
with:
|
||||
name: ${{ format('ssh-windows-mobile-web-{0}', github.sha) }}
|
||||
path: out/mobile-web/
|
||||
# A failed-only retry may outlive the producer artifact's retention.
|
||||
- uses: ./.github/actions/build-mobile-web-bundle
|
||||
if: steps.mobile-bundle.outcome != 'success'
|
||||
- name: Verify the mobile bundle before staging it
|
||||
shell: bash
|
||||
run: node -e "require('./config/scripts/verify-packaged-mobile-web-bundle.cjs').assertMobileWebBundleBuilt()"
|
||||
- name: Build the win32 template
|
||||
shell: bash
|
||||
env:
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
import { copyFileSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { runProcessSync } from '@orca/process-host'
|
||||
import { expect, it } from 'vitest'
|
||||
import { parse } from 'yaml'
|
||||
import { compileAggregateExpression } from './pr-aggregate-gate-context.mjs'
|
||||
import { writeMobileWebBundleFixtureTree } from './mobile-web-bundle-fixture-tree.mjs'
|
||||
|
||||
const workflow = parse(readFileSync('.github/workflows/ssh-windows-hosts.yml', 'utf8'))
|
||||
const producer = workflow.jobs.mobile_web_bundle
|
||||
const consumer = workflow.jobs.hosts
|
||||
const upload = producer.steps.find((step) => step.uses?.startsWith('actions/upload-artifact'))
|
||||
const download = consumer.steps.find((step) => step.id === 'mobile-bundle')
|
||||
const fallback = consumer.steps.find(
|
||||
(step) => step.uses === './.github/actions/build-mobile-web-bundle'
|
||||
)
|
||||
const verify = consumer.steps.find(
|
||||
(step) => step.name === 'Verify the mobile bundle before staging it'
|
||||
)
|
||||
|
||||
it('binds the bundle to the same run and source across architectures and retry attempts', () => {
|
||||
expect(producer['runs-on']).toBe('ubuntu-latest')
|
||||
for (const job of [producer, consumer]) {
|
||||
expect(
|
||||
job.steps.find((step) => step.uses?.startsWith('actions/checkout')).with.ref
|
||||
).toBeUndefined()
|
||||
}
|
||||
expect(download.with['run-id']).toBeUndefined()
|
||||
expect(download.with.repository).toBeUndefined()
|
||||
const name = compileAggregateExpression(upload.with.name, ['github'])
|
||||
const requested = compileAggregateExpression(download.with.name, ['github'])
|
||||
for (const sha of ['a'.repeat(40), 'b'.repeat(40)]) {
|
||||
const first = { github: { sha, run_attempt: 1 } }
|
||||
const retry = { github: { sha, run_attempt: 2 } }
|
||||
expect(name(first).coerceString()).toBe(requested(retry).coerceString())
|
||||
expect(name(first).coerceString()).toContain(sha)
|
||||
}
|
||||
expect(upload.with.overwrite).toBe(true)
|
||||
expect(upload.with.path).toBe(download.with.path)
|
||||
})
|
||||
|
||||
it.each(['success', 'failure', 'cancelled', 'skipped', ''])(
|
||||
'admits Windows hosts only after a successful producer: %s',
|
||||
(result) => {
|
||||
const eligible = compileAggregateExpression(consumer.if, ['github', 'needs'])
|
||||
const value = {
|
||||
github: { event_name: 'pull_request', event: { pull_request: { draft: false } } },
|
||||
needs: { mobile_web_bundle: { result } }
|
||||
}
|
||||
expect(consumer.needs).toBe('mobile_web_bundle')
|
||||
expect(eligible(value).coerceString()).toBe(String(result === 'success'))
|
||||
expect(eligible(value, true).coerceString()).toBe('false')
|
||||
}
|
||||
)
|
||||
|
||||
it.each(['success', 'failure', 'skipped', ''])(
|
||||
'rebuilds locally only when the artifact is unavailable: %s',
|
||||
(outcome) => {
|
||||
const eligible = compileAggregateExpression(fallback.if, ['steps'])
|
||||
expect(eligible({ steps: { 'mobile-bundle': { outcome } } }).coerceString()).toBe(
|
||||
String(outcome !== 'success')
|
||||
)
|
||||
expect(download['continue-on-error']).toBe(true)
|
||||
expect(fallback['continue-on-error']).toBeUndefined()
|
||||
expect(verify['continue-on-error']).toBeUndefined()
|
||||
const template = consumer.steps.findIndex((step) =>
|
||||
step.run?.includes('build-orcad-template.mjs')
|
||||
)
|
||||
expect(consumer.steps.indexOf(download)).toBeLessThan(consumer.steps.indexOf(fallback))
|
||||
expect(consumer.steps.indexOf(fallback)).toBeLessThan(consumer.steps.indexOf(verify))
|
||||
expect(consumer.steps.indexOf(verify)).toBeLessThan(template)
|
||||
}
|
||||
)
|
||||
|
||||
it.skipIf(process.platform === 'win32')(
|
||||
'executes the consumer verifier without mobile dependencies and rejects corrupt assets',
|
||||
async () => {
|
||||
const directory = mkdtempSync(join(tmpdir(), 'ssh-mobile-bundle-'))
|
||||
try {
|
||||
mkdirSync(join(directory, 'config', 'scripts'), { recursive: true })
|
||||
for (const file of [
|
||||
'config/dark-build-switches.cjs',
|
||||
'config/scripts/verify-packaged-mobile-web-bundle.cjs'
|
||||
]) {
|
||||
copyFileSync(file, join(directory, file))
|
||||
}
|
||||
const bundleDir = join(directory, 'out', 'mobile-web')
|
||||
await writeMobileWebBundleFixtureTree({ outDir: bundleDir })
|
||||
const execute = () =>
|
||||
runProcessSync({
|
||||
program: 'bash',
|
||||
args: ['-e', '-o', 'pipefail', '-c', verify.run],
|
||||
cwd: directory
|
||||
})
|
||||
expect(execute().code).toBe(0)
|
||||
writeFileSync(join(bundleDir, 'index.html'), 'corrupt')
|
||||
expect(execute().code).not.toBe(0)
|
||||
} finally {
|
||||
rmSync(directory, { recursive: true, force: true })
|
||||
}
|
||||
}
|
||||
)
|
||||
Reference in New Issue
Block a user