mirror of
https://github.com/NanmiCoder/claude-code-haha.git
synced 2026-10-10 03:43:11 +08:00
fix(ci): run macOS Swift checks on a required macOS lane
This commit is contained in:
@@ -245,6 +245,21 @@ jobs:
|
||||
- name: Run desktop native checks
|
||||
run: bun run check:native
|
||||
|
||||
macos-swift-checks:
|
||||
name: macos-swift-checks
|
||||
needs: scope-plan
|
||||
if: needs.scope-plan.outputs.desktop_native_checks == 'true'
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version-file: package.json
|
||||
- name: Run macOS Swift checks
|
||||
run: bun run check:swift
|
||||
|
||||
persistence-checks:
|
||||
name: persistence-checks
|
||||
needs: scope-plan
|
||||
@@ -336,6 +351,7 @@ jobs:
|
||||
- agent-flow-checks
|
||||
- adapter-checks
|
||||
- desktop-native-checks
|
||||
- macos-swift-checks
|
||||
- persistence-checks
|
||||
- docs-checks
|
||||
- coverage-checks
|
||||
@@ -380,6 +396,7 @@ jobs:
|
||||
require_selected "agent-flow-checks" "${{ needs.scope-plan.outputs.agent_flow_checks }}" "${{ needs.agent-flow-checks.result }}"
|
||||
require_selected "adapter-checks" "${{ needs.scope-plan.outputs.adapter_checks }}" "${{ needs.adapter-checks.result }}"
|
||||
require_selected "desktop-native-checks" "${{ needs.scope-plan.outputs.desktop_native_checks }}" "${{ needs.desktop-native-checks.result }}"
|
||||
require_selected "macos-swift-checks" "${{ needs.scope-plan.outputs.desktop_native_checks }}" "${{ needs.macos-swift-checks.result }}"
|
||||
require_selected "persistence-checks" "${{ needs.scope-plan.outputs.persistence_checks }}" "${{ needs.persistence-checks.result }}"
|
||||
require_selected "docs-checks" "${{ needs.scope-plan.outputs.docs_checks }}" "${{ needs.docs-checks.result }}"
|
||||
require_selected "coverage-checks" "${{ needs.scope-plan.outputs.coverage_checks }}" "${{ needs.coverage-checks.result }}"
|
||||
|
||||
+2
-2
@@ -14,7 +14,7 @@
|
||||
"perf:local-index:10k": "bun run scripts/perf/local-index-benchmark.ts --sessions 10000 --runs 20",
|
||||
"check:pr": "bun run scripts/pr/check-pr.ts",
|
||||
"check:impact": "bun run scripts/pr/impact-report.ts",
|
||||
"check:policy": "bun test ./scripts/pr/bun-test-filter.test.ts ./scripts/pr/change-policy.test.ts ./scripts/pr/changed-files.test.ts ./scripts/pr/dead-imports.test.ts ./scripts/pr/module-graph.test.ts ./scripts/pr/pr-triage-workflow.test.ts ./scripts/pr/pr-quality-workflow.test.ts ./scripts/pr/release-workflow.test.ts ./scripts/pr/quality-contract.test.ts ./scripts/pr/test-environment.test.ts ./scripts/release-update-metadata.test.ts ./scripts/git-hooks/install.test.ts ./scripts/quality-gate/quarantine.test.ts ./scripts/quality-gate/coverage.test.ts ./scripts/quality-gate/provider-smoke/execute.test.ts ./scripts/quality-gate/desktop-smoke/execute.test.ts ./scripts/quality-gate/providerTargets.test.ts ./scripts/quality-gate/runner.test.ts ./scripts/quality-gate/sandbox.test.ts ./scripts/quality-gate/agent-flow/scenarios.test.ts ./scripts/quality-gate/agent-flow/live.test.ts ./scripts/quality-gate/desktop-smoke/deterministic.test.ts ./scripts/quality-gate/computer-use-live-smoke.test.ts",
|
||||
"check:policy": "bun test ./scripts/pr/bun-test-filter.test.ts ./scripts/pr/change-policy.test.ts ./scripts/pr/changed-files.test.ts ./scripts/pr/dead-imports.test.ts ./scripts/pr/module-graph.test.ts ./scripts/pr/pr-triage-workflow.test.ts ./scripts/pr/pr-quality-workflow.test.ts ./scripts/pr/release-workflow.test.ts ./scripts/pr/quality-contract.test.ts ./scripts/pr/test-environment.test.ts ./scripts/pr/run-swift-checks.test.ts ./scripts/release-update-metadata.test.ts ./scripts/git-hooks/install.test.ts ./scripts/quality-gate/quarantine.test.ts ./scripts/quality-gate/coverage.test.ts ./scripts/quality-gate/provider-smoke/execute.test.ts ./scripts/quality-gate/desktop-smoke/execute.test.ts ./scripts/quality-gate/providerTargets.test.ts ./scripts/quality-gate/runner.test.ts ./scripts/quality-gate/sandbox.test.ts ./scripts/quality-gate/agent-flow/scenarios.test.ts ./scripts/quality-gate/agent-flow/live.test.ts ./scripts/quality-gate/desktop-smoke/deterministic.test.ts ./scripts/quality-gate/computer-use-live-smoke.test.ts",
|
||||
"check:server": "bun run scripts/pr/run-server-tests.ts",
|
||||
"check:provider-contract": "bun run scripts/pr/run-provider-contract-tests.ts",
|
||||
"check:chat-contract": "bun run scripts/pr/run-chat-contract-tests.ts",
|
||||
@@ -44,7 +44,7 @@
|
||||
"docs:build": "npm --prefix site run build",
|
||||
"docs:preview": "npm --prefix site run preview",
|
||||
"check:agent-flow:live": "bun run scripts/quality-gate/agent-flow/live-cli.ts",
|
||||
"check:swift": "swift test --package-path native/cu-helper --enable-xctest",
|
||||
"check:swift": "bun run scripts/pr/run-swift-checks.ts",
|
||||
"check:computer-use-live-smoke": "bun run scripts/quality-gate/computer-use-live-smoke.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -3,6 +3,9 @@ import { readFileSync } from 'node:fs'
|
||||
import { parse } from 'yaml'
|
||||
|
||||
type WorkflowJob = {
|
||||
if?: string
|
||||
'runs-on'?: string
|
||||
'continue-on-error'?: boolean
|
||||
needs?: string | string[]
|
||||
steps?: Array<{ name?: string; run?: string; 'working-directory'?: string }>
|
||||
}
|
||||
@@ -62,6 +65,7 @@ describe('PR quality workflow', () => {
|
||||
'agent-flow-checks',
|
||||
'adapter-checks',
|
||||
'desktop-native-checks',
|
||||
'macos-swift-checks',
|
||||
'persistence-checks',
|
||||
'docs-checks',
|
||||
'coverage-checks',
|
||||
@@ -92,6 +96,55 @@ describe('PR quality workflow', () => {
|
||||
}
|
||||
})
|
||||
|
||||
test('requires macOS Swift checks alongside the selected Linux native packaging lane', () => {
|
||||
const jobs = workflowJobs(readFileSync('.github/workflows/pr-quality.yml', 'utf8'))
|
||||
const packageJson = JSON.parse(readFileSync('package.json', 'utf8')) as { scripts: Record<string, string> }
|
||||
const macos = jobs['macos-swift-checks']!
|
||||
const linux = jobs['desktop-native-checks']!
|
||||
const gate = jobs['pr-quality-gate']!
|
||||
expect(macos['runs-on']).toBe('macos-latest')
|
||||
expect(linux['runs-on']).toBe('ubuntu-22.04')
|
||||
for (const job of [macos, linux]) {
|
||||
expect(job.needs).toBe('scope-plan')
|
||||
expect(job.if).toBe("needs.scope-plan.outputs.desktop_native_checks == 'true'")
|
||||
expect(job['continue-on-error']).not.toBe(true)
|
||||
}
|
||||
expect(macos.steps?.some(step => step.run === 'bun run check:swift')).toBe(true)
|
||||
expect(linux.steps?.some(step => step.run === 'bun run check:native')).toBe(true)
|
||||
expect(gate.needs).toContain('macos-swift-checks')
|
||||
expect(gate.needs).toContain('desktop-native-checks')
|
||||
expect(packageJson.scripts['check:swift']).toBe('bun run scripts/pr/run-swift-checks.ts')
|
||||
expect(packageJson.scripts['check:policy']).toContain('scripts/pr/run-swift-checks.test.ts')
|
||||
for (const command of ['check:swift', 'build:sidecars', 'test:compiled-sidecar-smoke', 'check:electron', 'electron:package:dir', 'test:package-smoke:current']) {
|
||||
expect(packageJson.scripts['check:native']).toContain(`bun run ${command}`)
|
||||
}
|
||||
})
|
||||
|
||||
test.each([
|
||||
{ selected: true, macos: 'success', linux: 'success', exit: 0 },
|
||||
{ selected: true, macos: 'failure', linux: 'success', exit: 1 },
|
||||
{ selected: true, macos: 'skipped', linux: 'success', exit: 1 },
|
||||
{ selected: true, macos: 'success', linux: 'failure', exit: 1 },
|
||||
{ selected: false, macos: 'skipped', linux: 'skipped', exit: 0 },
|
||||
{ selected: false, macos: 'success', linux: 'skipped', exit: 1 },
|
||||
])('enforces both native results in the stable gate: %j', scenario => {
|
||||
const jobs = workflowJobs(readFileSync('.github/workflows/pr-quality.yml', 'utf8'))
|
||||
const gateScript = jobs['pr-quality-gate']!.steps!.find(step => step.run?.includes('require_selected'))!.run!
|
||||
const script = gateScript.replace(/\$\{\{\s*([^}]+?)\s*\}\}/g, (_match, expression: string) => {
|
||||
if (expression === 'needs.scope-plan.outputs.desktop_native_checks') return String(scenario.selected)
|
||||
if (expression.startsWith('needs.scope-plan.outputs.')) return 'false'
|
||||
if (expression === 'needs.macos-swift-checks.result') return scenario.macos
|
||||
if (expression === 'needs.desktop-native-checks.result') return scenario.linux
|
||||
if (expression === 'needs.scope-plan.result' || expression === 'needs.policy-enforcement.result') return 'success'
|
||||
return 'skipped'
|
||||
})
|
||||
const result = Bun.spawnSync(['bash', '-c', script], {
|
||||
env: { PATH: process.env.PATH ?? '' },
|
||||
stdout: 'pipe', stderr: 'pipe',
|
||||
})
|
||||
expect(result.exitCode, new TextDecoder().decode(result.stdout)).toBe(scenario.exit)
|
||||
})
|
||||
|
||||
test('keeps coverage artifacts observable in CI', () => {
|
||||
const workflow = readFileSync('.github/workflows/pr-quality.yml', 'utf8')
|
||||
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import { describe, expect, mock, test } from 'bun:test'
|
||||
import { existsSync, writeFileSync } from 'node:fs'
|
||||
import { isAbsolute, join } from 'node:path'
|
||||
import { runSwiftChecks } from './run-swift-checks'
|
||||
|
||||
describe('Swift platform checks', () => {
|
||||
test.each(['linux', 'win32'] as const)('does not invoke the macOS-only package on %s', async platform => {
|
||||
const run = mock(async () => { throw new Error('Swift cannot run on this fixture platform') })
|
||||
expect(await runSwiftChecks({ platform, run })).toBe(0)
|
||||
expect(run).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test.each([0, 7])('runs the full macOS package in isolation and propagates exit %s', async exitCode => {
|
||||
let home = ''
|
||||
const run = mock(async (command: string[], options: { cwd: string; env: Record<string, string> }) => {
|
||||
home = options.env.HOME!
|
||||
expect(command.slice(0, 2)).toEqual(['swift', 'test'])
|
||||
expect(command).toContain('--enable-xctest')
|
||||
expect(command[command.indexOf('--package-path') + 1]).toBe(join(options.cwd, 'native/cu-helper'))
|
||||
expect(command[command.indexOf('--scratch-path') + 1]).toBe(join(home, 'build'))
|
||||
expect(isAbsolute(options.cwd)).toBe(true)
|
||||
expect(home).not.toBe(process.env.HOME)
|
||||
expect(options.env.CLAUDE_CONFIG_DIR).toBe(join(home, '.claude'))
|
||||
expect(options.env.ANTHROPIC_API_KEY).toBeUndefined()
|
||||
expect(options.env.ANTHROPIC_AUTH_TOKEN).toBeUndefined()
|
||||
expect(options.env.GH_TOKEN).toBeUndefined()
|
||||
writeFileSync(join(home, 'cleanup-fixture'), 'disposable Swift test output')
|
||||
return exitCode
|
||||
})
|
||||
expect(await runSwiftChecks({ platform: 'darwin', run })).toBe(exitCode)
|
||||
expect(run).toHaveBeenCalledTimes(1)
|
||||
expect(existsSync(home)).toBe(false)
|
||||
})
|
||||
|
||||
test('cleans the sandbox and reports a Swift launch failure', async () => {
|
||||
let home = ''
|
||||
await expect(runSwiftChecks({
|
||||
platform: 'darwin',
|
||||
run: async (_command, options) => {
|
||||
home = options.env.HOME!
|
||||
throw new Error('swift executable unavailable')
|
||||
},
|
||||
})).rejects.toThrow('swift executable unavailable')
|
||||
expect(existsSync(home)).toBe(false)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env bun
|
||||
|
||||
import { mkdtempSync, rmSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join, resolve } from 'node:path'
|
||||
import { createSandboxedTestEnvironment } from './test-environment'
|
||||
|
||||
type SwiftCheckRunner = (
|
||||
command: string[],
|
||||
options: { cwd: string; env: Record<string, string> },
|
||||
) => Promise<number>
|
||||
|
||||
export async function runSwiftChecks(options: {
|
||||
platform?: NodeJS.Platform
|
||||
run?: SwiftCheckRunner
|
||||
} = {}): Promise<number> {
|
||||
const platform = options.platform ?? process.platform
|
||||
if (platform !== 'darwin') {
|
||||
console.log(`[swift-checks] not applicable on ${platform}: cu-helper targets macOS; PR CI requires the macOS Swift job separately`)
|
||||
return 0
|
||||
}
|
||||
|
||||
const root = resolve(import.meta.dir, '../..')
|
||||
const sandboxHome = mkdtempSync(join(tmpdir(), 'cc-haha-swift-checks-'))
|
||||
const run = options.run ?? (async (command, spawnOptions) => {
|
||||
const child = Bun.spawn(command, { ...spawnOptions, stdout: 'inherit', stderr: 'inherit' })
|
||||
return await child.exited
|
||||
})
|
||||
try {
|
||||
return await run([
|
||||
'swift', 'test',
|
||||
'--package-path', join(root, 'native/cu-helper'),
|
||||
'--scratch-path', join(sandboxHome, 'build'),
|
||||
'--enable-xctest',
|
||||
], { cwd: root, env: createSandboxedTestEnvironment(sandboxHome) })
|
||||
} finally {
|
||||
rmSync(sandboxHome, { recursive: true, force: true })
|
||||
}
|
||||
}
|
||||
|
||||
if (import.meta.main) process.exit(await runSwiftChecks())
|
||||
Reference in New Issue
Block a user