import { readFileSync } from 'node:fs' import { dirname, join, resolve } from 'node:path' import { fileURLToPath } from 'node:url' import { describe, expect, it } from 'vitest' import { parse } from 'yaml' const projectDir = resolve(dirname(fileURLToPath(import.meta.url)), '../..') const packageJson = JSON.parse(readFileSync(join(projectDir, 'package.json'), 'utf8')) describe('Electron runtime package contract', () => { it('keeps root postinstall as the single Electron binary install owner', () => { expect(packageJson.scripts.postinstall).toBe('node config/scripts/rebuild-native-deps.mjs') expect(packageJson.pnpm.onlyBuiltDependencies).not.toContain('electron') }) it('guards package scripts that launch Electron tooling', () => { const scripts = packageJson.scripts const guardedScripts = [ 'start', 'dev', 'dev-stable-name', 'build:unpack', 'build:win', 'build:mac', 'build:mac:release', 'build:linux', 'test:e2e', 'test:e2e:terminal-rendering-golden', 'test:e2e:terminal-rendering-release-evidence', 'test:e2e:headful' ] for (const scriptName of guardedScripts) { expect(scripts[scriptName], scriptName).toContain('pnpm run ensure:electron-runtime &&') } }) it('keeps Windows and Linux package builds off the macOS native helper build', () => { const scripts = packageJson.scripts expect(scripts['build:desktop']).not.toContain('build:computer-macos') expect(scripts['build:win']).toContain('pnpm run build:desktop') expect(scripts['build:win']).not.toContain('pnpm run build ') expect(scripts['build:win']).not.toContain('build:computer-macos') expect(scripts['build:linux']).toContain('pnpm run build:desktop') expect(scripts['build:linux']).not.toContain('pnpm run build ') expect(scripts['build:linux']).not.toContain('build:computer-macos') expect(scripts['build:mac']).toContain('pnpm run build:computer-macos') expect(scripts['build:release']).toContain('pnpm run build:native') expect(scripts['build:release']).not.toContain('build:computer-macos') }) it('guards release publishing before electron-builder runs', () => { const releaseWorkflow = readFileSync( join(projectDir, '.github/workflows/release-cut.yml'), 'utf8' ) const parsedWorkflow = parse(releaseWorkflow) const releaseCommands = new Map( parsedWorkflow.jobs.build.strategy.matrix.include.map(({ platform, release_command }) => [ platform, release_command ]) ) expect([...releaseCommands.keys()].sort()).toEqual(['linux', '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') expect(command.indexOf('ensure-native-runtime')).toBeLessThan( command.indexOf('electron-builder') ) } expect(releaseCommands.get('mac')).toContain(' && ORCA_MAC_RELEASE=1 ') expect(releaseCommands.get('linux')).toContain(' && pnpm exec electron-builder ') expect(releaseCommands.get('win')).toContain( '; if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }; pnpm exec electron-builder ' ) }) it('lets release-cut tag a version that is already present on main', () => { const releaseWorkflow = readFileSync( join(projectDir, '.github/workflows/release-cut.yml'), 'utf8' ) const parsedWorkflow = parse(releaseWorkflow) const bumpStep = parsedWorkflow.jobs.cut.steps.find( (step) => step.name === 'Bump package.json and tag' ) expect(bumpStep.run).toContain('git diff --cached --quiet') expect(bumpStep.run).toContain('git commit --allow-empty -m "$commit_message"') }) it('keeps release-cut RC retries monotonic across stale attempts', () => { const releaseWorkflow = readFileSync( join(projectDir, '.github/workflows/release-cut.yml'), 'utf8' ) const parsedWorkflow = parse(releaseWorkflow) const versionStep = parsedWorkflow.jobs.cut.steps.find( (step) => step.name === 'Compute next version' ) expect(versionStep.run).toContain('node config/scripts/release-rc-history.mjs "$1"') expect(versionStep.run).toContain('tag_matches_current_ref') expect(versionStep.run).toContain('cutting the next version instead of reusing stale artifacts') expect(versionStep.run).toContain('git rev-parse "$existing_rc_tag"') }) it('bumps separate Homebrew casks for stable and RC desktop tags', () => { const releaseWorkflow = parse( readFileSync(join(projectDir, '.github/workflows/release-cut.yml'), 'utf8') ) const homebrewWorkflow = parse( readFileSync(join(projectDir, '.github/workflows/homebrew-bump.yml'), 'utf8') ) expect(releaseWorkflow.jobs['homebrew-bump'].if).toContain( "startsWith(needs.cut.outputs.tag, 'v')" ) expect(releaseWorkflow.jobs['homebrew-bump'].if).not.toContain('-rc.') expect(releaseWorkflow.jobs['homebrew-bump-published-rc-draft'].with.tag).toBe( '${{ needs.cut.outputs.latest_published_rc_tag }}' ) const resolveCaskStep = homebrewWorkflow.jobs['bump-cask'].steps.find( (step) => step.name === 'Resolve cask target' ) const renderStep = homebrewWorkflow.jobs['bump-cask'].steps.find( (step) => step.name === 'Render updated cask file' ) const copyStep = homebrewWorkflow.jobs['bump-cask'].steps.find( (step) => step.name === 'Copy cask into tap and open PR' ) expect(resolveCaskStep.run).toContain('token="orca@rc"') expect(resolveCaskStep.run).toContain('token="orca"') expect(renderStep.env.CASK_PATH).toBe('${{ steps.cask.outputs.path }}') expect(copyStep.run).toContain('cp "$CASK_PATH" "tap/$CASK_PATH"') expect(copyStep.run).toContain('git add "$CASK_PATH"') }) it('installs the Electron package binary in PR checks without changing native module ABI', () => { const prWorkflow = readFileSync(join(projectDir, '.github/workflows/pr.yml'), 'utf8') const parsedWorkflow = parse(prWorkflow) const installStep = parsedWorkflow.jobs.verify.steps.find( (step) => step.name === 'Install Electron package binary for tests' ) expect(installStep.run).toBe('node config/scripts/install-electron-package-binary.mjs') }) it('smokes the packaged CLI from outside the checkout in PR checks', () => { const prWorkflow = readFileSync(join(projectDir, '.github/workflows/pr.yml'), 'utf8') const parsedWorkflow = parse(prWorkflow) const smokeStep = parsedWorkflow.jobs.verify.steps.find( (step) => step.name === 'Smoke packaged CLI' ) expect(smokeStep.run).toBe( 'node config/scripts/smoke-packaged-cli.mjs --app-dir=dist/linux-unpacked' ) }) it('keeps terminal scale perf wired to the report budget gate', () => { const packageScripts = packageJson.scripts const terminalPerfWorkflow = parse( readFileSync(join(projectDir, '.github/workflows/terminal-perf.yml'), 'utf8') ) const steps = terminalPerfWorkflow.jobs['terminal-perf'].steps const runStep = steps.find((step) => step.name === 'Run terminal scale perf report gate') const uploadStep = steps.find((step) => step.name === 'Upload terminal perf report') expect(packageScripts['test:e2e:terminal-perf:scale:report']).toContain( 'run-terminal-scale-perf-report-gate.mjs' ) expect(runStep.run).toContain('pnpm run test:e2e:terminal-perf:scale:report') expect(runStep.run).toContain('xvfb-run --auto-servernum') const manualProfileKnobs = [ ['ORCA_TERMINAL_PERF_FRAME_COUNT', 'frame_count', 'ORCA_E2E_OPENCODE_FRAME_COUNT'], [ 'ORCA_TERMINAL_PERF_FRAME_INTERVAL_MS', 'frame_interval_ms', 'ORCA_E2E_OPENCODE_FRAME_INTERVAL_MS' ], [ 'ORCA_TERMINAL_PERF_PRESSURE_OUTPUT_CHARS', 'pressure_output_chars', 'ORCA_E2E_OPENCODE_PRESSURE_OUTPUT_CHARS' ], ['ORCA_TERMINAL_PERF_SCALE_PANES', 'scale_panes', 'ORCA_E2E_OPENCODE_SCALE_PANES'], [ 'ORCA_TERMINAL_PERF_SCALE_CROSS_WORKSPACE_PANES', 'scale_cross_workspace_panes', 'ORCA_E2E_OPENCODE_SCALE_CROSS_WORKSPACE_PANES' ], [ 'ORCA_TERMINAL_PERF_SCALE_PRESSURE_PANES', 'scale_pressure_panes', 'ORCA_E2E_OPENCODE_SCALE_PRESSURE_PANES' ], [ 'ORCA_TERMINAL_PERF_SCALE_HIDDEN_PRESSURE_PANES', 'scale_hidden_pressure_panes', 'ORCA_E2E_OPENCODE_SCALE_HIDDEN_PRESSURE_PANES' ] ] for (const [workflowEnv, inputName, runnerEnv] of manualProfileKnobs) { expect(runStep.env[workflowEnv]).toBe(`\${{ inputs.${inputName} }}`) expect(runStep.run).toContain(runnerEnv) } expect(uploadStep.uses).toBe('actions/upload-artifact@v7') expect(uploadStep.with.path).toBe('${{ env.ORCA_E2E_TERMINAL_PERF_REPORT_PATH }}') }) it('keeps terminal rendering regressions in the fast golden E2E gate', () => { const packageScripts = packageJson.scripts const goldenWorkflow = parse( readFileSync(join(projectDir, '.github/workflows/golden-e2e-experiment.yml'), 'utf8') ) const releaseWorkflow = parse( readFileSync(join(projectDir, '.github/workflows/release-cut.yml'), 'utf8') ) const steps = goldenWorkflow.jobs['golden-e2e'].steps const linuxRunStep = steps.find((step) => step.name === 'Run golden E2E tests on Linux') const macRunStep = steps.find((step) => step.name === 'Run golden E2E tests on macOS') const windowsRunStep = steps.find((step) => step.name === 'Run golden E2E tests on Windows') const pullRequestPaths = goldenWorkflow.on.pull_request.paths const releaseGoldenJob = releaseWorkflow.jobs['terminal-rendering-golden'] const releaseEvidenceJob = releaseWorkflow.jobs['terminal-rendering-release-evidence'] const releaseBuildNeeds = releaseWorkflow.jobs.build.needs const publishReleaseNeeds = releaseWorkflow.jobs['publish-release'].needs expect(packageScripts['test:e2e:terminal-rendering-golden']).toContain( '@terminal-rendering-golden' ) expect(packageScripts['test:e2e:terminal-rendering-golden']).toContain( 'terminal-raw-emoji-table-scroll-restore.spec.ts' ) expect(packageScripts['test:e2e:terminal-rendering-golden']).not.toContain( 'terminal-long-table-scroll-restore.spec.ts' ) expect(packageScripts['test:e2e:terminal-rendering-release-evidence']).toContain( 'terminal-opencode-emoji-table-rendering.spec.ts' ) expect(packageScripts['test:e2e:terminal-rendering-release-evidence']).toContain( 'terminal-long-table-scroll-restore.spec.ts' ) for (const runStep of [linuxRunStep, macRunStep, windowsRunStep]) { expect(runStep.run).toContain('pnpm run test:e2e:terminal-rendering-golden') } expect(pullRequestPaths).toContain('tests/e2e/terminal-raw-emoji-table-scroll-restore.spec.ts') expect(pullRequestPaths).toContain('tests/e2e/fixtures/terminal-emoji-table.md') expect(pullRequestPaths).toContain('src/renderer/src/lib/pane-manager/**') expect(releaseBuildNeeds).not.toContain('terminal-rendering-golden') expect(releaseBuildNeeds).not.toContain('terminal-rendering-release-evidence') expect(publishReleaseNeeds).toContain('terminal-rendering-golden') expect(publishReleaseNeeds).toContain('build') expect(publishReleaseNeeds).not.toContain('terminal-rendering-release-evidence') expect(releaseGoldenJob['continue-on-error']).toBeUndefined() expect(releaseGoldenJob.strategy.matrix.include.map(({ platform }) => platform).sort()).toEqual( ['linux', 'mac', 'windows'] ) expect(releaseGoldenJob.steps.map((step) => step.run ?? '')).toContain( 'xvfb-run --auto-servernum env SKIP_BUILD=1 ORCA_E2E_FORWARD_APP_LOGS=1 pnpm run test:e2e:terminal-rendering-golden' ) expect(releaseEvidenceJob['continue-on-error']).toBe(true) expect( releaseEvidenceJob.strategy.matrix.include.map(({ platform }) => platform).sort() ).toEqual(['linux', 'mac', 'windows']) expect(releaseEvidenceJob.steps.map((step) => step.run ?? '')).toContain( 'xvfb-run --auto-servernum env SKIP_BUILD=1 ORCA_E2E_FORWARD_APP_LOGS=1 pnpm run test:e2e:terminal-rendering-release-evidence' ) }) })