name: Validate Orchestrator Compatibility # ============================================================================== # Essential plugin health checks — runs on every PR and push. # Fast (~5 min): compilation, unit tests, plugin interface, type declarations. # # For exhaustive integration tests (k8s, AWS, local-docker, rclone) see # validate-orchestrator-integration.yml which runs on a daily cron. # ============================================================================== on: workflow_dispatch: push: branches: [main, 'release/**', 'feature/**', 'refactor/**'] paths: - 'src/model/orchestrator-plugin.ts' - 'src/model/build-parameters.ts' - 'src/model/input.ts' - 'src/model/github.ts' - 'src/model/cli/cli.ts' - 'src/model/input-readers/**' - 'src/index.ts' - 'src/types/game-ci-orchestrator.d.ts' - 'action.yml' - 'package.json' - 'yarn.lock' - '.github/workflows/validate-orchestrator.yml' pull_request: branches: [main, 'release/**'] paths: - 'src/model/orchestrator-plugin.ts' - 'src/model/build-parameters.ts' - 'src/model/input.ts' - 'src/model/github.ts' - 'src/model/cli/cli.ts' - 'src/model/input-readers/**' - 'src/index.ts' - 'src/types/game-ci-orchestrator.d.ts' - 'action.yml' - 'package.json' - 'yarn.lock' - '.github/workflows/validate-orchestrator.yml' permissions: contents: read packages: read concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true jobs: # ============================================================================ # PLUGIN ARCHITECTURE HEALTH CHECK # ============================================================================ # Validates that: # 1. unity-builder compiles and its unit tests pass # 2. Plugin loader degrades gracefully without orchestrator # 3. Orchestrator compiles and its unit tests pass # 4. Plugin loader loads all services when orchestrator is installed # 5. Type declarations match actual exports # ============================================================================ plugin-health: name: Plugin Architecture Health runs-on: ubuntu-latest steps: - name: Checkout unity-builder uses: actions/checkout@v4 - name: Checkout orchestrator uses: actions/checkout@v4 with: repository: game-ci/orchestrator ref: ${{ github.head_ref || github.ref_name }} path: orchestrator-standalone continue-on-error: true id: orchestrator-branch - name: Fallback to orchestrator main branch if: steps.orchestrator-branch.outcome == 'failure' uses: actions/checkout@v4 with: repository: game-ci/orchestrator path: orchestrator-standalone - name: Install package manager (from package.json) run: | corepack enable corepack install - uses: actions/setup-node@v4 with: node-version: 20 - name: Resolve yarn cache folder id: yarn-config run: echo "cacheFolder=$(yarn config get cacheFolder)" >> "$GITHUB_OUTPUT" - name: Restore yarn install cache (node_modules + cacheFolder + install-state) uses: actions/cache@v4 with: path: | ${{ steps.yarn-config.outputs.cacheFolder }} .yarn/install-state.gz key: yarn-v2-${{ runner.os }}-node-20-${{ hashFiles('yarn.lock') }} restore-keys: | yarn-v2-${{ runner.os }}-node-20- # --- unity-builder compilation and tests --- - name: Install unity-builder dependencies env: YARN_ENABLE_HARDENED_MODE: 'false' run: | case "$(yarn --version)" in 1.*) echo 'expected up-to-date yarn version'; exit 1 ;; esac yarn install --immutable - name: Build unity-builder run: | echo "Building unity-builder TypeScript..." npx tsc echo "✓ unity-builder compiles successfully" - name: Run orchestrator-plugin unit tests run: | echo "Running orchestrator-plugin unit tests..." npx jest orchestrator-plugin --verbose --detectOpenHandles --forceExit # --- Plugin loader without orchestrator --- - name: Verify plugin loader returns undefined without orchestrator run: | echo "Checking plugin loader handles missing @game-ci/orchestrator..." node -e " const { loadOrchestratorPlugin } = require('./lib/model/orchestrator-plugin'); (async () => { const plugin = await loadOrchestratorPlugin(); if (plugin !== undefined) { console.error('ERROR: loadOrchestratorPlugin should return undefined when package not installed'); process.exit(1); } console.log('✓ loadOrchestratorPlugin() returns undefined when package not installed'); })(); " - name: Verify orchestrator type declarations exist run: | if [ -f "src/types/game-ci-orchestrator.d.ts" ]; then echo "✓ Type declarations for @game-ci/orchestrator exist" else echo "::error::Missing type declarations: src/types/game-ci-orchestrator.d.ts" exit 1 fi # --- Orchestrator compilation and tests --- - name: Build and pack orchestrator working-directory: orchestrator-standalone run: | yarn install --immutable echo "Building orchestrator..." npx tsc echo "✓ orchestrator compiles successfully" echo "Packing orchestrator as tarball..." npm pack - name: Run orchestrator unit tests working-directory: orchestrator-standalone run: | echo "Running orchestrator unit tests..." npx jest --no-cache 2>&1 | tail -20 # --- Plugin loader with orchestrator installed --- - name: Install orchestrator into unity-builder run: | echo "Installing orchestrator into unity-builder workspace..." npm install ./orchestrator-standalone/game-ci-orchestrator-*.tgz --no-save --legacy-peer-deps - name: Verify plugin loader returns exports with orchestrator installed run: | echo "Checking plugin loader returns defined exports..." node -e " const { loadOrchestratorPlugin } = require('./lib/model/orchestrator-plugin'); (async () => { const plugin = await loadOrchestratorPlugin(); if (plugin === undefined) { console.error('ERROR: loadOrchestratorPlugin should return defined plugin when package is installed'); process.exit(1); } const lifecycleMethods = [ 'initialize', 'canHandleBuild', 'handleBuild', 'beforeLocalBuild', 'afterLocalBuild', 'handlePostBuild', ]; for (const method of lifecycleMethods) { if (typeof plugin[method] !== 'function') { console.error('ERROR: plugin.' + method + ' should be a function, got ' + typeof plugin[method]); process.exit(1); } } console.log('✓ loadOrchestratorPlugin() returns plugin with all ' + lifecycleMethods.length + ' lifecycle methods'); })(); " - name: Verify type declarations match orchestrator exports run: | echo "Checking type declarations align with orchestrator exports..." node -e " const orch = require('@game-ci/orchestrator'); const expectedExports = [ 'Orchestrator', 'BuildReliabilityService', 'TestWorkflowService', 'HotRunnerService', 'OutputService', 'OutputTypeRegistry', 'ArtifactUploadHandler', 'IncrementalSyncService', 'ChildWorkspaceService', 'LocalCacheService', 'SubmoduleProfileService', 'LfsAgentService', 'GitHooksService', ]; const missing = expectedExports.filter(e => orch[e] === undefined); if (missing.length > 0) { console.error('ERROR: Missing exports from @game-ci/orchestrator:', missing.join(', ')); process.exit(1); } console.log('✓ All ' + expectedExports.length + ' declared exports present in orchestrator package'); " - name: Smoke test orchestrator build wiring run: | echo "Verifying orchestrator build wiring end-to-end..." node -e " const { loadOrchestratorPlugin } = require('./lib/model/orchestrator-plugin'); (async () => { // Verify plugin loads successfully with orchestrator installed const plugin = await loadOrchestratorPlugin(); if (plugin === undefined) { console.error('ERROR: plugin should be defined when orchestrator is installed'); process.exit(1); } // Verify all lifecycle methods are callable const lifecycleMethods = [ 'initialize', 'canHandleBuild', 'handleBuild', 'beforeLocalBuild', 'afterLocalBuild', 'handlePostBuild', ]; for (const m of lifecycleMethods) { if (typeof plugin[m] !== 'function') { console.error('ERROR: plugin.' + m + ' should be a function, got ' + typeof plugin[m]); process.exit(1); } } console.log('✓ Plugin has all ' + lifecycleMethods.length + ' lifecycle methods'); // Verify canHandleBuild returns a boolean const canHandle = plugin.canHandleBuild(); if (typeof canHandle !== 'boolean') { console.error('ERROR: canHandleBuild() should return a boolean, got ' + typeof canHandle); process.exit(1); } console.log('✓ canHandleBuild() returns boolean'); console.log('✓ Plugin architecture wiring verified'); })(); "