name: Windows CI on: push: pull_request: workflow_dispatch: inputs: live_gate: description: Optional live gate to run after verification required: true default: none type: choice options: - none - twitch - updater-postpublish source_version: description: Published source version for the updater gate required: false type: string source_sha256: description: SHA-256 of the published source installer required: false type: string update_version: description: Newly published target version for the updater gate required: false type: string update_sha512: description: SHA-512 from the newly published latest.yml required: false type: string permissions: contents: read jobs: verify: runs-on: windows-latest timeout-minutes: 120 env: CI: 'true' steps: - uses: actions/checkout@v4 timeout-minutes: 10 - uses: actions/setup-node@v4 timeout-minutes: 10 with: node-version: '24.11.1' cache: npm - name: Clean install run: npm ci timeout-minutes: 10 - name: Provision Electron binary run: | npx install-electron --no if ($LASTEXITCODE -ne 0) { npx install-electron --no if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } } timeout-minutes: 10 - name: Lint run: npm run lint timeout-minutes: 10 - name: Lint configuration contract run: npm run test:lint-config timeout-minutes: 10 - name: Security contracts run: npm run security:check timeout-minutes: 10 - name: Security regression tests run: npm run test:security timeout-minutes: 10 - name: CI contract run: npm run test:ci-contract timeout-minutes: 10 - name: Installer contract run: npm run test:installer-contract timeout-minutes: 10 - name: Managed tools contract run: npm run test:managed-tools-contract timeout-minutes: 10 - name: Cutter matrix provisioning contract run: npm run test:cutter-matrix-contract timeout-minutes: 10 - name: Build run: npm run build timeout-minutes: 10 - name: Live integration contract run: npm run test:live-integration-contract timeout-minutes: 10 - name: Unit tests run: npm run test:unit timeout-minutes: 10 - name: Focused Electron smoke run: | npm run test:e2e:focused if ($LASTEXITCODE -ne 0) { npm run test:e2e:focused if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } } timeout-minutes: 20 - name: Cutter media matrix run: | node scripts/smoke-test-cutter-media-matrix.js if ($LASTEXITCODE -ne 0) { node scripts/smoke-test-cutter-media-matrix.js if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } } timeout-minutes: 20 - name: Clean managed tools provision and repair run: npm run test:managed-tools-live timeout-minutes: 10 - name: Package directory run: | npm run pack if ($LASTEXITCODE -ne 0) { npm run pack if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } } timeout-minutes: 10 - name: Packaged launch smoke run: npm run test:packaged-launch timeout-minutes: 10 - name: Build installer run: | npm run dist:ci if ($LASTEXITCODE -ne 0) { npm run dist:ci if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } } timeout-minutes: 10 - name: Installer smoke run: npm run test:installer timeout-minutes: 10 twitch-live: if: github.event_name == 'workflow_dispatch' && github.event.inputs.live_gate == 'twitch' needs: verify runs-on: windows-latest timeout-minutes: 60 env: CI: 'true' steps: - uses: actions/checkout@v4 timeout-minutes: 10 - uses: actions/setup-node@v4 timeout-minutes: 10 with: node-version: '24.11.1' cache: npm - name: Clean install run: npm ci timeout-minutes: 10 - name: Build run: npm run build timeout-minutes: 10 - name: Verify live integration contract run: npm run test:live-integration-contract timeout-minutes: 10 - name: Provision pinned media tools run: | $env:TWITCH_VOD_MANAGER_LIVE_TOOL_ROOT = Join-Path $env:RUNNER_TEMP "tvm-live-tools-$env:GITHUB_RUN_ID-$env:GITHUB_RUN_ATTEMPT" @' const fs = require('node:fs'); const path = require('node:path'); const tools = require('./dist/tools.js'); (async () => { const root = process.env.TWITCH_VOD_MANAGER_LIVE_TOOL_ROOT; const streamlinkDirectory = path.join(root, 'streamlink'); const ffmpegDirectory = path.join(root, 'ffmpeg'); const temporaryDirectory = path.join(root, 'temporary'); fs.mkdirSync(temporaryDirectory, { recursive: true }); tools.initToolDirs(streamlinkDirectory, ffmpegDirectory, () => temporaryDirectory); const result = await tools.repairManagedTools(); if (!result.success) throw new Error(`Pinned media tool provisioning failed: ${JSON.stringify(result.statuses)}`); if (!process.env.GITHUB_ENV) throw new Error('Actions environment export file is unavailable'); fs.appendFileSync(process.env.GITHUB_ENV, [ `TWITCH_VOD_MANAGER_LIVE_STREAMLINK_PATH=${tools.getStreamlinkPath()}`, `TWITCH_VOD_MANAGER_LIVE_FFPROBE_PATH=${tools.getFFprobePath()}`, '' ].join('\n')); })().catch((error) => { console.error(error instanceof Error ? error.message : String(error)); process.exitCode = 1; }); '@ | node if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } timeout-minutes: 10 - name: Twitch provider OAuth, Helix and bounded VOD gate env: TWITCH_VOD_MANAGER_LIVE_INTEGRATION: '1' TWITCH_VOD_MANAGER_LIVE_TWITCH_CLIENT_ID: ${{ secrets.TWITCH_VOD_MANAGER_LIVE_TWITCH_CLIENT_ID }} TWITCH_VOD_MANAGER_LIVE_TWITCH_CLIENT_SECRET: ${{ secrets.TWITCH_VOD_MANAGER_LIVE_TWITCH_CLIENT_SECRET }} TWITCH_VOD_MANAGER_LIVE_TWITCH_LOGIN: ${{ secrets.TWITCH_VOD_MANAGER_LIVE_TWITCH_LOGIN }} TWITCH_VOD_MANAGER_LIVE_TWITCH_VOD_ID: ${{ secrets.TWITCH_VOD_MANAGER_LIVE_TWITCH_VOD_ID }} run: npm run test:live:twitch timeout-minutes: 10 updater-live-postpublish: if: github.event_name == 'workflow_dispatch' && github.event.inputs.live_gate == 'updater-postpublish' needs: verify runs-on: windows-latest timeout-minutes: 60 env: CI: 'true' TWITCH_VOD_MANAGER_LIVE_INTEGRATION: '1' TWITCH_VOD_MANAGER_LIVE_SOURCE_VERSION: ${{ github.event.inputs.source_version }} TWITCH_VOD_MANAGER_LIVE_SOURCE_SHA256: ${{ github.event.inputs.source_sha256 }} TWITCH_VOD_MANAGER_LIVE_UPDATE_VERSION: ${{ github.event.inputs.update_version }} TWITCH_VOD_MANAGER_LIVE_UPDATE_SHA512: ${{ github.event.inputs.update_sha512 }} TWITCH_VOD_MANAGER_LIVE_UPDATE_COMMIT_SHA: ${{ github.sha }} steps: - uses: actions/checkout@v4 timeout-minutes: 10 with: ref: ${{ github.sha }} - uses: actions/setup-node@v4 timeout-minutes: 10 with: node-version: '24.11.1' cache: npm - name: Clean install run: npm ci timeout-minutes: 10 - name: Require explicit post-publish updater inputs run: | $required = @( 'TWITCH_VOD_MANAGER_LIVE_SOURCE_VERSION', 'TWITCH_VOD_MANAGER_LIVE_SOURCE_SHA256', 'TWITCH_VOD_MANAGER_LIVE_UPDATE_VERSION', 'TWITCH_VOD_MANAGER_LIVE_UPDATE_SHA512', 'TWITCH_VOD_MANAGER_LIVE_UPDATE_COMMIT_SHA' ) foreach ($name in $required) { if ([string]::IsNullOrWhiteSpace([Environment]::GetEnvironmentVariable($name))) { throw "Missing required post-publish updater input: $name" } } if ($env:TWITCH_VOD_MANAGER_LIVE_UPDATE_COMMIT_SHA -notmatch '^[0-9a-fA-F]{40}$') { throw 'Pinned updater commit provenance must be a 40-character hexadecimal SHA' } if (-not [string]::Equals($env:TWITCH_VOD_MANAGER_LIVE_UPDATE_COMMIT_SHA, $env:GITHUB_SHA, [StringComparison]::OrdinalIgnoreCase)) { throw 'Pinned updater commit provenance must match GITHUB_SHA' } $sourceVersionText = $env:TWITCH_VOD_MANAGER_LIVE_SOURCE_VERSION.Trim() $updateVersionText = $env:TWITCH_VOD_MANAGER_LIVE_UPDATE_VERSION.Trim() $versionPattern = '^(0|[1-9][0-9]{0,8})\.(0|[1-9][0-9]{0,8})\.(0|[1-9][0-9]{0,8})$' if ($sourceVersionText -notmatch $versionPattern) { throw 'Pinned source version must be an exact three-segment numeric release version' } if ($updateVersionText -notmatch $versionPattern) { throw 'Pinned update version must be an exact three-segment numeric release version' } $sourceVersion = [version]$sourceVersionText $updateVersion = [version]$updateVersionText if ($sourceVersion -ge $updateVersion) { throw 'Pinned source version must be older than the update version' } $packageVersion = (Get-Content -Raw -LiteralPath package.json | ConvertFrom-Json).version if ($updateVersionText -ne $packageVersion) { throw "Pinned update version must match package.json version $packageVersion" } $expectedRef = "refs/tags/v$updateVersionText" if ($env:GITHUB_REF -ne $expectedRef) { throw "Post-publish updater gate must run from release tag $expectedRef" } timeout-minutes: 10 - name: Build run: npm run build timeout-minutes: 10 - name: Verify live integration contract run: npm run test:live-integration-contract timeout-minutes: 10 - name: Verify published updater path run: npm run test:live:updater-postpublish timeout-minutes: 25