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 path: orchestrator-standalone - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: 20 cache: yarn # --- unity-builder compilation and tests --- - name: Install unity-builder dependencies run: yarn install --frozen-lockfile - 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 { loadOrchestrator, loadEnterpriseServices } = require('./lib/model/orchestrator-plugin'); (async () => { const orch = await loadOrchestrator(); if (orch !== undefined) { console.error('ERROR: loadOrchestrator should return undefined when package not installed'); process.exit(1); } console.log('✓ loadOrchestrator() returns undefined when package not installed'); const services = await loadEnterpriseServices(); if (services !== undefined) { console.error('ERROR: loadEnterpriseServices should return undefined when package not installed'); process.exit(1); } console.log('✓ loadEnterpriseServices() 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 --frozen-lockfile 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 { loadOrchestrator, loadEnterpriseServices } = require('./lib/model/orchestrator-plugin'); (async () => { const orch = await loadOrchestrator(); if (orch === undefined) { console.error('ERROR: loadOrchestrator should return defined exports when package is installed'); process.exit(1); } if (typeof orch.run !== 'function') { console.error('ERROR: loadOrchestrator().run should be a function'); process.exit(1); } console.log('✓ loadOrchestrator() returns defined exports with orchestrator installed'); const services = await loadEnterpriseServices(); if (services === undefined) { console.error('ERROR: loadEnterpriseServices should return defined exports when package is installed'); process.exit(1); } const expectedServices = [ 'BuildReliabilityService', 'TestWorkflowService', 'HotRunnerService', 'OutputService', 'OutputTypeRegistry', 'ArtifactUploadHandler', 'IncrementalSyncService', ]; for (const svc of expectedServices) { if (services[svc] === undefined) { console.error('ERROR: ' + svc + ' should be defined'); process.exit(1); } } console.log('✓ loadEnterpriseServices() returns all ' + expectedServices.length + ' services'); const lazyLoaders = [ 'loadChildWorkspaceService', 'loadLocalCacheService', 'loadSubmoduleProfileService', 'loadLfsAgentService', 'loadGitHooksService', ]; for (const loader of lazyLoaders) { if (typeof services[loader] !== 'function') { console.error('ERROR: ' + loader + ' should be a function'); process.exit(1); } const loaded = await services[loader](); if (loaded === undefined) { console.error('ERROR: ' + loader + '() should return defined service'); process.exit(1); } } console.log('✓ All ' + lazyLoaders.length + ' lazy loaders return defined services'); })(); " - 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'); "