Skip to content

feat: CLI plugin adapter for @game-ci/cli#824

Closed
frostebite wants to merge 1 commit intomainfrom
feat/cli-plugin-adapter
Closed

feat: CLI plugin adapter for @game-ci/cli#824
frostebite wants to merge 1 commit intomainfrom
feat/cli-plugin-adapter

Conversation

@frostebite
Copy link
Copy Markdown
Member

@frostebite frostebite commented Mar 28, 2026

Summary

  • Adds a GameCIPlugin-compatible export at src/cli-plugin/ so the CLI repo can consume the orchestrator as a plugin via PluginRegistry.register(orchestratorPlugin)
  • build-parameters-adapter: Maps flat yargs CLI options → BuildParameters object that all providers expect
  • orchestrator-options-plugin: Registers all 40+ orchestrator-specific options (AWS, K8s, hooks, storage, caching, etc.) with the CLI's yargs instance
  • provider-adapter: Wraps existing ProviderInterface constructors so the CLI can instantiate them with yargs options instead of BuildParameters directly
  • index.ts: Exports the plugin with all 6 built-in providers (aws, k8s, local-docker, local-system, local, test)
  • Adds exports field to package.json for ./cli-plugin subpath import
  • 7 tests covering plugin shape, options registration, and parameter mapping — all passing

Related

Test plan

  • All 7 new tests pass (npx jest src/cli-plugin/cli-plugin.test.ts)
  • All 367 existing tests still pass (no regressions)
  • ESLint, Prettier, and build hooks all pass
  • Integration test: install orchestrator as dependency in CLI repo, register plugin, verify provider instantiation

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced CLI plugin support enabling orchestrator configuration from the command line.
    • Added configurable options for container resources, provider settings, AWS and Kubernetes parameters, caching, and Git operations.
    • Extended package exports to support subpath imports for CLI functionality.
  • Tests

    • Added comprehensive test coverage for CLI plugin functionality.

Adds a GameCIPlugin-compatible export so the CLI repo can consume the
orchestrator as a plugin. This includes:

- build-parameters-adapter: maps flat yargs options to BuildParameters
- orchestrator-options-plugin: registers all orchestrator options via yargs
- provider-adapter: wraps ProviderInterface constructors for CLI PluginRegistry
- index.ts: exports the orchestratorPlugin with all providers (aws, k8s, docker, local, test)
- cli-plugin.test.ts: 7 tests covering plugin shape, options registration, and parameter mapping

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 28, 2026

📝 Walkthrough

Walkthrough

This PR introduces a GameCI CLI plugin system by exposing a new ./cli-plugin subpath in package.json and implementing a complete orchestrator plugin adapter. The system includes option registration, CLI input mapping to internal parameters, provider wrapping, and comprehensive tests validating the integration.

Changes

Cohort / File(s) Summary
Package Configuration
package.json
Added exports field mapping the default entry (".") to ./dist/index.js and a new subpath ("./cli-plugin") to ./src/cli-plugin/index.ts.
CLI Plugin Core
src/cli-plugin/index.ts, src/cli-plugin/orchestrator-options-plugin.ts, src/cli-plugin/provider-adapter.ts, src/cli-plugin/build-parameters-adapter.ts
Implemented complete CLI plugin adapter: main entry point exports orchestratorPlugin with metadata and provider mappings; option configuration registers 30+ orchestrator-specific CLI flags; build parameter adapter maps CLI options to internal BuildParameters with precedence rules and defaults; provider adapter wraps provider constructors for CLI compatibility.
Plugin Tests
src/cli-plugin/cli-plugin.test.ts
Added test suite validating plugin metadata, option registration, provider availability, and CLI-to-BuildParameters conversion with default values.

Sequence Diagram

sequenceDiagram
    participant CLI as CLI System
    participant Plugin as orchestratorPlugin
    participant OptConfig as configureOrchestratorOptions
    participant BuildAdapter as createBuildParametersFromCliOptions
    participant ProviderAdapter as createProviderAdapter
    participant Provider as Actual Provider

    CLI->>Plugin: Load plugin
    activate Plugin
    Note over Plugin: name: 'orchestrator'<br/>version: '3.0.0'
    Plugin-->>CLI: Return orchestratorPlugin metadata
    deactivate Plugin
    
    CLI->>OptConfig: Register CLI options
    activate OptConfig
    Note over OptConfig: Register 30+ flags:<br/>region, containerCpu,<br/>kubeConfig, etc.
    OptConfig-->>CLI: Options registered
    deactivate OptConfig
    
    CLI->>CLI: User provides CLI arguments
    
    CLI->>BuildAdapter: Convert options
    activate BuildAdapter
    Note over BuildAdapter: Map CLI options to<br/>BuildParameters with<br/>env var fallback & defaults
    BuildAdapter-->>CLI: Return BuildParameters
    deactivate BuildAdapter
    
    CLI->>ProviderAdapter: Create provider instance
    activate ProviderAdapter
    ProviderAdapter->>Provider: new ProviderClass(buildParameters)
    activate Provider
    Provider-->>ProviderAdapter: Provider ready
    deactivate Provider
    ProviderAdapter-->>CLI: Provider adapter ready
    deactivate ProviderAdapter
    
    CLI->>ProviderAdapter: Call workflow methods
    activate ProviderAdapter
    ProviderAdapter->>Provider: Forward method call
    activate Provider
    Provider-->>ProviderAdapter: Return result
    deactivate Provider
    ProviderAdapter-->>CLI: Return result
    deactivate ProviderAdapter
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A plugin so grand, with options galore,
CLI adapters we've added to the lore,
Parameters mapped from commands so fine,
Providers now wrapped in adapter design,
The orchestrator's CLI begins to shine! 🎭✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding a CLI plugin adapter for @game-ci/cli, which is the primary objective of the changeset.
Description check ✅ Passed The description covers all required template sections with substantive content: detailed changes, related issues/PRs, test results, and a completed checklist.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/cli-plugin-adapter

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown

Cat Gif

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (1)
src/cli-plugin/cli-plugin.test.ts (1)

60-93: Add regression tests for currently uncovered adapter edge cases.

Please add assertions for region, resourceTracking, useSharedBuilder, useCleanupCron, and garbageMaxAge = 0 so silent mapping regressions are caught.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cli-plugin/cli-plugin.test.ts` around lines 60 - 93, The tests for
createBuildParametersFromCliOptions are missing assertions for several adapter
edge-case fields; update the two test cases to assert mapping and defaults for
region, resourceTracking, useSharedBuilder, useCleanupCron, and garbageMaxAge
(including explicit check that garbageMaxAge defaults to 0). In the 'maps yargs
options to BuildParameters' test add assertions that the returned
BuildParameters contains the provided region value and boolean flags
resourceTracking, useSharedBuilder, useCleanupCron and garbageMaxAge set from
the input; in the 'applies defaults for missing options' test assert the
expected default values for region, resourceTracking, useSharedBuilder,
useCleanupCron and that garbageMaxAge === 0 so regressions in the mapping are
caught.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@package.json`:
- Around line 6-9: The package exports point "./cli-plugin" at the raw
TypeScript source ("./src/cli-plugin/index.ts"), which will be published and
break Node consumers; change this by adding a "files" field to package.json to
explicitly include the compiled output (e.g., "dist/") and exclude raw "src",
update the "./cli-plugin" export to point to the compiled entry (e.g.,
"dist/cli-plugin/index.js"), and adjust the build pipeline (the ncc build or
your build script) to emit a dist/cli-plugin/index.js artifact (or alternatively
document that TypeScript runtime tooling is required); reference the "exports"
key and the "./cli-plugin" subpath and ensure the build task that produces
"dist/" is updated to generate the cli-plugin subpath.

In `@src/cli-plugin/build-parameters-adapter.ts`:
- Line 103: The default buildPlatform assignment in build-parameters-adapter.ts
incorrectly treats only providerStrategy === 'local' as host-local; update the
logic around bp.buildPlatform (the assignment that uses options.buildPlatform ||
(bp.providerStrategy !== 'local' ? 'linux' : process.platform)) so that
providerStrategy values 'local' and 'local-system' are both considered
host-local (i.e., if bp.providerStrategy is neither 'local' nor 'local-system'
default to 'linux', otherwise use process.platform); change the conditional to
check for both identifiers (e.g., via explicit comparisons or an inclusion
check) to ensure local-system uses the host platform default.
- Line 115: The assignment bp.garbageMaxAge = Number(options.garbageMaxAge) ||
24 erroneously treats 0 as falsy and resets it to 24; instead parse the value
and only fallback when the parsed value is NaN/invalid—e.g., compute const
parsed = Number(options.garbageMaxAge) and set bp.garbageMaxAge =
Number.isFinite(parsed) ? parsed : 24 (or use a nullish/NaN check) so that 0 is
preserved; update the code around bp.garbageMaxAge and options.garbageMaxAge
accordingly.
- Around line 53-124: The BuildParameters mapping in build-parameters-adapter.ts
is dropping several registered CLI flags (region, containerHookFiles,
commandHookFiles, resourceTracking, useSharedBuilder, useCleanupCron); update
the bp assignments (the BuildParameters object referenced as bp) to copy these
options from the incoming options object: set bp.region = options.region || ''
(or appropriate default), map bp.containerHookFiles and bp.commandHookFiles to
arrays (e.g. options.containerHookFiles ? options.containerHookFiles.split(',')
: []), set bp.resourceTracking = options.resourceTracking === true ||
options.resourceTracking === 'true', and set bp.useSharedBuilder and
bp.useCleanupCron similarly to boolean conversions (=== true || === 'true'); add
these lines alongside the other mappings in the same adapter so user-provided
values are preserved.

In `@src/cli-plugin/index.ts`:
- Line 57: The Test provider is violating the provider interface because
TestOrchestrator implements runTaskInWorkflow(commands, buildGuid, image, ...)
while the expected signature is runTaskInWorkflow(buildGuid, image, commands,
...); update TestOrchestrator.runTaskInWorkflow to take parameters in the
correct order (buildGuid, image, commands, ...) and adjust the internal use of
those parameters accordingly so that createProviderAdapter(TestOrchestrator)
forwards arguments correctly to the implementation.

---

Nitpick comments:
In `@src/cli-plugin/cli-plugin.test.ts`:
- Around line 60-93: The tests for createBuildParametersFromCliOptions are
missing assertions for several adapter edge-case fields; update the two test
cases to assert mapping and defaults for region, resourceTracking,
useSharedBuilder, useCleanupCron, and garbageMaxAge (including explicit check
that garbageMaxAge defaults to 0). In the 'maps yargs options to
BuildParameters' test add assertions that the returned BuildParameters contains
the provided region value and boolean flags resourceTracking, useSharedBuilder,
useCleanupCron and garbageMaxAge set from the input; in the 'applies defaults
for missing options' test assert the expected default values for region,
resourceTracking, useSharedBuilder, useCleanupCron and that garbageMaxAge === 0
so regressions in the mapping are caught.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 067f94e5-a5e7-4b64-8cd3-aebe063e0820

📥 Commits

Reviewing files that changed from the base of the PR and between ce7ce7a and 12a3a02.

📒 Files selected for processing (6)
  • package.json
  • src/cli-plugin/build-parameters-adapter.ts
  • src/cli-plugin/cli-plugin.test.ts
  • src/cli-plugin/index.ts
  • src/cli-plugin/orchestrator-options-plugin.ts
  • src/cli-plugin/provider-adapter.ts

Comment on lines +6 to +9
"exports": {
".": "./dist/index.js",
"./cli-plugin": "./src/cli-plugin/index.ts"
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "package exports:"
jq '.exports' package.json

echo "tsconfig output dirs:"
jq '.compilerOptions | {rootDir, outDir}' tsconfig.json

echo "cli-plugin source entry:"
fd -p '^index\.ts$' src/cli-plugin

echo "compiled cli-plugin JS tracked in repo (if present):"
fd -p '^index\.js$' lib/cli-plugin dist/cli-plugin || true

Repository: game-ci/unity-builder

Length of output: 254


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== tsconfig.json content ==="
cat tsconfig.json | head -30

echo "=== Checking for compiled cli-plugin output ==="
ls -la lib/cli-plugin 2>/dev/null || echo "lib/cli-plugin does not exist"
ls -la dist/cli-plugin 2>/dev/null || echo "dist/cli-plugin does not exist"

echo "=== Build scripts in package.json ==="
jq '.scripts | keys[] as $k | "\($k): \(.[$k])"' package.json 2>/dev/null | head -20 || grep -A 20 '"scripts"' package.json

echo "=== cli-plugin source files ==="
find src/cli-plugin -type f -name "*.ts" -o -name "*.js" 2>/dev/null | head -10

Repository: game-ci/unity-builder

Length of output: 2985


🏁 Script executed:

#!/bin/bash

echo "=== Directory structure of dist/ ==="
ls -la dist/ 2>/dev/null | head -20 || echo "dist does not exist or is empty"

echo "=== Directory structure of lib/ ==="
ls -la lib/ 2>/dev/null | head -20 || echo "lib does not exist or is empty"

echo "=== Checking .gitignore for exclusions ==="
cat .gitignore | grep -E "(^lib|^dist|/lib|/dist)" || echo "No explicit lib/dist gitignore rules found"

echo "=== Content of dist/index.js (first 50 lines) ==="
head -50 dist/index.js 2>/dev/null || echo "dist/index.js not found"

Repository: game-ci/unity-builder

Length of output: 3115


🏁 Script executed:

#!/bin/bash

echo "=== package.json files and exports fields ==="
jq '{files, exports, main, bin}' package.json 2>/dev/null || cat package.json | grep -A 10 '"files"\|"exports"\|"main"\|"bin"'

echo "=== Checking for ncc or webpack config ==="
ls -la | grep -E "(ncc|webpack|build)" || echo "No ncc/webpack config files found"

echo "=== How cli-plugin is imported in tests/usage ==="
grep -r "cli-plugin" src/ --include="*.ts" | grep -E "(import|require)" | head -5 || echo "No local imports of cli-plugin found"

echo "=== Check if cli-plugin is meant to be external or internal ==="
grep -r "from.*cli-plugin\|require.*cli-plugin" . --include="*.ts" --include="*.js" 2>/dev/null | grep -v node_modules | head -10

Repository: game-ci/unity-builder

Length of output: 459


🏁 Script executed:

#!/bin/bash

echo "=== Check if cli-plugin is referenced in README or docs ==="
find . -name "README*" -o -name "*.md" 2>/dev/null | xargs grep -l "cli-plugin" 2>/dev/null || echo "No docs mention cli-plugin"

echo "=== Check GitHub workflows/actions for how this is published ==="
cat .github/workflows/*.yml 2>/dev/null | grep -A 5 -B 5 "publish\|npm\|registry" || echo "No publish workflows found"

echo "=== Check if ts-node or similar is listed as dependency ==="
jq '{devDependencies, dependencies}' package.json | grep -E "ts-node|tsx|register" || echo "Checking dependencies..."

grep -E "ts-node|tsx" package.json || echo "No ts-node detected"

echo "=== What does the cli-plugin export contain? ==="
head -30 src/cli-plugin/index.ts

Repository: game-ci/unity-builder

Length of output: 2325


🏁 Script executed:

#!/bin/bash

echo "=== Check what npm would publish ==="
npm ls --depth=0 2>/dev/null || echo "Checking published files..."

echo "=== Simulate npm publish: list files that would be included ==="
git ls-files 2>/dev/null | head -30

echo "=== Is src/ tracked in git? ==="
git ls-files | grep "^src/" | wc -l

echo "=== Is lib/ tracked? ==="
git ls-files | grep "^lib/" | wc -l

echo "=== Full exports and file info ==="
cat package.json | jq '{name, version, main, exports, files, publishConfig}' 

echo "=== What actually happens when you require the subpath? ==="
echo "Checking if there are any test files that import cli-plugin..."
find . -name "*.test.ts" -o -name "*.spec.ts" | xargs grep -l "cli-plugin" 2>/dev/null | head -3

Repository: game-ci/unity-builder

Length of output: 3397


Subpath export points to raw TypeScript source that requires TypeScript runtime support.

"./cli-plugin": "./src/cli-plugin/index.ts" will be published as-is since src/ is tracked in git and no "files" field limits what npm includes. This breaks import 'unity-builder/cli-plugin' for standard Node.js consumers without TypeScript loaders (ts-node, tsx, etc.).

The proposed fix of "./cli-plugin": "./lib/cli-plugin/index.js" is incorrect—lib/ is gitignored and will not be included in the published package.

Recommended solutions:

  1. Add "files" field to explicitly control published outputs and ensure dist/ is included:

    {
      "files": ["dist", "src"]
    }

    Then update the export to point to a pre-compiled dist/cli-plugin/index.js (requires build changes to output subpaths).

  2. Or update the build to generate dist/cli-plugin/index.js during ncc build instead of bundling everything into dist/index.js.

  3. Or document that this package requires TypeScript tooling and update consumers to use ts-node or similar.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@package.json` around lines 6 - 9, The package exports point "./cli-plugin" at
the raw TypeScript source ("./src/cli-plugin/index.ts"), which will be published
and break Node consumers; change this by adding a "files" field to package.json
to explicitly include the compiled output (e.g., "dist/") and exclude raw "src",
update the "./cli-plugin" export to point to the compiled entry (e.g.,
"dist/cli-plugin/index.js"), and adjust the build pipeline (the ncc build or
your build script) to emit a dist/cli-plugin/index.js artifact (or alternatively
document that TypeScript runtime tooling is required); reference the "exports"
key and the "./cli-plugin" subpath and ensure the build task that produces
"dist/" is updated to generate the cli-plugin subpath.

Comment on lines +53 to +124
// Provider / orchestrator settings
bp.providerStrategy = options.providerStrategy || 'local-docker';
bp.customParameters = options.customParameters || '';
bp.sshAgent = options.sshAgent || '';
bp.sshPublicKeysDirectoryPath = options.sshPublicKeysDirectoryPath || '';
bp.gitPrivateToken = options.gitPrivateToken || '';
bp.runAsHostUser = options.runAsHostUser || 'false';
bp.chownFilesTo = options.chownFilesTo || '';

// AWS
bp.awsStackName = options.awsStackName || 'game-ci';
bp.awsEndpoint = options.awsEndpoint;
bp.awsCloudFormationEndpoint = options.awsCloudFormationEndpoint || options.awsEndpoint;
bp.awsEcsEndpoint = options.awsEcsEndpoint || options.awsEndpoint;
bp.awsKinesisEndpoint = options.awsKinesisEndpoint || options.awsEndpoint;
bp.awsCloudWatchLogsEndpoint = options.awsCloudWatchLogsEndpoint || options.awsEndpoint;
bp.awsS3Endpoint = options.awsS3Endpoint || options.awsEndpoint;

// Storage
bp.storageProvider = options.storageProvider || 's3';
bp.rcloneRemote = options.rcloneRemote || '';

// Kubernetes
bp.kubeConfig = options.kubeConfig || '';
bp.kubeVolume = options.kubeVolume || '';
bp.kubeVolumeSize = options.kubeVolumeSize || '25Gi';
bp.kubeStorageClass = options.kubeStorageClass || '';

// Container resources
bp.containerMemory = options.containerMemory || '3072';
bp.containerCpu = options.containerCpu || '1024';
bp.containerNamespace = options.containerNamespace || 'default';

// Hooks
bp.commandHooks = options.commandHooks || '';
bp.postBuildContainerHooks = options.postBuildContainerHooks || '';
bp.preBuildContainerHooks = options.preBuildContainerHooks || '';
bp.customJob = options.customJob || '';

// Git / CI
bp.runNumber = options.runNumber || process.env.GITHUB_RUN_NUMBER || '0';
bp.branch = options.branch || process.env.GITHUB_REF?.replace('refs/', '').replace('heads/', '') || '';
bp.githubRepo = options.githubRepo || process.env.GITHUB_REPOSITORY || '';
bp.orchestratorRepoName = options.orchestratorRepoName || 'game-ci/unity-builder';
bp.cloneDepth = Number.parseInt(options.cloneDepth || '50', 10);
bp.gitSha = options.gitSha || process.env.GITHUB_SHA || '';
bp.orchestratorBranch = options.orchestratorBranch || 'main';
bp.orchestratorDebug = options.orchestratorDebug === true || options.orchestratorDebug === 'true';

// Build platform
bp.buildPlatform = options.buildPlatform || (bp.providerStrategy !== 'local' ? 'linux' : process.platform);
bp.isCliMode = true;

// Caching
bp.cacheKey = options.cacheKey || bp.branch;
bp.pullInputList = options.pullInputList ? options.pullInputList.split(',') : [];
bp.inputPullCommand = options.inputPullCommand || '';

// Advanced
bp.maxRetainedWorkspaces = Number.parseInt(options.maxRetainedWorkspaces || '0', 10);
bp.useLargePackages = options.useLargePackages === true || options.useLargePackages === 'true';
bp.useCompressionStrategy = options.useCompressionStrategy === true || options.useCompressionStrategy === 'true';
bp.garbageMaxAge = Number(options.garbageMaxAge) || 24;
bp.githubChecks = options.githubChecks === true || options.githubChecks === 'true';
bp.asyncWorkflow = options.asyncOrchestrator === true || options.asyncOrchestrator === 'true';
bp.githubCheckId = options.githubCheckId || '';
bp.finalHooks = options.finalHooks ? options.finalHooks.split(',') : [];
bp.skipLfs = options.skipLfs === true || options.skipLfs === 'true';
bp.skipCache = options.skipCache === true || options.skipCache === 'true';
bp.cacheUnityInstallationOnMac =
options.cacheUnityInstallationOnMac === true || options.cacheUnityInstallationOnMac === 'true';
bp.unityHubVersionOnMac = options.unityHubVersionOnMac || '';
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Several declared CLI flags are silently discarded during mapping.

region, containerHookFiles, commandHookFiles, resourceTracking, useSharedBuilder, and useCleanupCron are registered options but never copied into BuildParameters, so user-provided values are lost.

🔧 Proposed mapping additions
   // Provider / orchestrator settings
+  bp.region = options.region || 'eu-west-2';
   bp.providerStrategy = options.providerStrategy || 'local-docker';
   bp.customParameters = options.customParameters || '';
@@
   // Hooks
+  bp.containerHookFiles = options.containerHookFiles || '';
+  bp.commandHookFiles = options.commandHookFiles || '';
   bp.commandHooks = options.commandHooks || '';
@@
   // Advanced
+  bp.resourceTracking = options.resourceTracking === true || options.resourceTracking === 'true';
+  bp.useSharedBuilder = options.useSharedBuilder === true || options.useSharedBuilder === 'true';
+  bp.useCleanupCron = options.useCleanupCron === true || options.useCleanupCron === 'true';
   bp.maxRetainedWorkspaces = Number.parseInt(options.maxRetainedWorkspaces || '0', 10);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Provider / orchestrator settings
bp.providerStrategy = options.providerStrategy || 'local-docker';
bp.customParameters = options.customParameters || '';
bp.sshAgent = options.sshAgent || '';
bp.sshPublicKeysDirectoryPath = options.sshPublicKeysDirectoryPath || '';
bp.gitPrivateToken = options.gitPrivateToken || '';
bp.runAsHostUser = options.runAsHostUser || 'false';
bp.chownFilesTo = options.chownFilesTo || '';
// AWS
bp.awsStackName = options.awsStackName || 'game-ci';
bp.awsEndpoint = options.awsEndpoint;
bp.awsCloudFormationEndpoint = options.awsCloudFormationEndpoint || options.awsEndpoint;
bp.awsEcsEndpoint = options.awsEcsEndpoint || options.awsEndpoint;
bp.awsKinesisEndpoint = options.awsKinesisEndpoint || options.awsEndpoint;
bp.awsCloudWatchLogsEndpoint = options.awsCloudWatchLogsEndpoint || options.awsEndpoint;
bp.awsS3Endpoint = options.awsS3Endpoint || options.awsEndpoint;
// Storage
bp.storageProvider = options.storageProvider || 's3';
bp.rcloneRemote = options.rcloneRemote || '';
// Kubernetes
bp.kubeConfig = options.kubeConfig || '';
bp.kubeVolume = options.kubeVolume || '';
bp.kubeVolumeSize = options.kubeVolumeSize || '25Gi';
bp.kubeStorageClass = options.kubeStorageClass || '';
// Container resources
bp.containerMemory = options.containerMemory || '3072';
bp.containerCpu = options.containerCpu || '1024';
bp.containerNamespace = options.containerNamespace || 'default';
// Hooks
bp.commandHooks = options.commandHooks || '';
bp.postBuildContainerHooks = options.postBuildContainerHooks || '';
bp.preBuildContainerHooks = options.preBuildContainerHooks || '';
bp.customJob = options.customJob || '';
// Git / CI
bp.runNumber = options.runNumber || process.env.GITHUB_RUN_NUMBER || '0';
bp.branch = options.branch || process.env.GITHUB_REF?.replace('refs/', '').replace('heads/', '') || '';
bp.githubRepo = options.githubRepo || process.env.GITHUB_REPOSITORY || '';
bp.orchestratorRepoName = options.orchestratorRepoName || 'game-ci/unity-builder';
bp.cloneDepth = Number.parseInt(options.cloneDepth || '50', 10);
bp.gitSha = options.gitSha || process.env.GITHUB_SHA || '';
bp.orchestratorBranch = options.orchestratorBranch || 'main';
bp.orchestratorDebug = options.orchestratorDebug === true || options.orchestratorDebug === 'true';
// Build platform
bp.buildPlatform = options.buildPlatform || (bp.providerStrategy !== 'local' ? 'linux' : process.platform);
bp.isCliMode = true;
// Caching
bp.cacheKey = options.cacheKey || bp.branch;
bp.pullInputList = options.pullInputList ? options.pullInputList.split(',') : [];
bp.inputPullCommand = options.inputPullCommand || '';
// Advanced
bp.maxRetainedWorkspaces = Number.parseInt(options.maxRetainedWorkspaces || '0', 10);
bp.useLargePackages = options.useLargePackages === true || options.useLargePackages === 'true';
bp.useCompressionStrategy = options.useCompressionStrategy === true || options.useCompressionStrategy === 'true';
bp.garbageMaxAge = Number(options.garbageMaxAge) || 24;
bp.githubChecks = options.githubChecks === true || options.githubChecks === 'true';
bp.asyncWorkflow = options.asyncOrchestrator === true || options.asyncOrchestrator === 'true';
bp.githubCheckId = options.githubCheckId || '';
bp.finalHooks = options.finalHooks ? options.finalHooks.split(',') : [];
bp.skipLfs = options.skipLfs === true || options.skipLfs === 'true';
bp.skipCache = options.skipCache === true || options.skipCache === 'true';
bp.cacheUnityInstallationOnMac =
options.cacheUnityInstallationOnMac === true || options.cacheUnityInstallationOnMac === 'true';
bp.unityHubVersionOnMac = options.unityHubVersionOnMac || '';
// Provider / orchestrator settings
bp.region = options.region || 'eu-west-2';
bp.providerStrategy = options.providerStrategy || 'local-docker';
bp.customParameters = options.customParameters || '';
bp.sshAgent = options.sshAgent || '';
bp.sshPublicKeysDirectoryPath = options.sshPublicKeysDirectoryPath || '';
bp.gitPrivateToken = options.gitPrivateToken || '';
bp.runAsHostUser = options.runAsHostUser || 'false';
bp.chownFilesTo = options.chownFilesTo || '';
// AWS
bp.awsStackName = options.awsStackName || 'game-ci';
bp.awsEndpoint = options.awsEndpoint;
bp.awsCloudFormationEndpoint = options.awsCloudFormationEndpoint || options.awsEndpoint;
bp.awsEcsEndpoint = options.awsEcsEndpoint || options.awsEndpoint;
bp.awsKinesisEndpoint = options.awsKinesisEndpoint || options.awsEndpoint;
bp.awsCloudWatchLogsEndpoint = options.awsCloudWatchLogsEndpoint || options.awsEndpoint;
bp.awsS3Endpoint = options.awsS3Endpoint || options.awsEndpoint;
// Storage
bp.storageProvider = options.storageProvider || 's3';
bp.rcloneRemote = options.rcloneRemote || '';
// Kubernetes
bp.kubeConfig = options.kubeConfig || '';
bp.kubeVolume = options.kubeVolume || '';
bp.kubeVolumeSize = options.kubeVolumeSize || '25Gi';
bp.kubeStorageClass = options.kubeStorageClass || '';
// Container resources
bp.containerMemory = options.containerMemory || '3072';
bp.containerCpu = options.containerCpu || '1024';
bp.containerNamespace = options.containerNamespace || 'default';
// Hooks
bp.containerHookFiles = options.containerHookFiles || '';
bp.commandHookFiles = options.commandHookFiles || '';
bp.commandHooks = options.commandHooks || '';
bp.postBuildContainerHooks = options.postBuildContainerHooks || '';
bp.preBuildContainerHooks = options.preBuildContainerHooks || '';
bp.customJob = options.customJob || '';
// Git / CI
bp.runNumber = options.runNumber || process.env.GITHUB_RUN_NUMBER || '0';
bp.branch = options.branch || process.env.GITHUB_REF?.replace('refs/', '').replace('heads/', '') || '';
bp.githubRepo = options.githubRepo || process.env.GITHUB_REPOSITORY || '';
bp.orchestratorRepoName = options.orchestratorRepoName || 'game-ci/unity-builder';
bp.cloneDepth = Number.parseInt(options.cloneDepth || '50', 10);
bp.gitSha = options.gitSha || process.env.GITHUB_SHA || '';
bp.orchestratorBranch = options.orchestratorBranch || 'main';
bp.orchestratorDebug = options.orchestratorDebug === true || options.orchestratorDebug === 'true';
// Build platform
bp.buildPlatform = options.buildPlatform || (bp.providerStrategy !== 'local' ? 'linux' : process.platform);
bp.isCliMode = true;
// Caching
bp.cacheKey = options.cacheKey || bp.branch;
bp.pullInputList = options.pullInputList ? options.pullInputList.split(',') : [];
bp.inputPullCommand = options.inputPullCommand || '';
// Advanced
bp.resourceTracking = options.resourceTracking === true || options.resourceTracking === 'true';
bp.useSharedBuilder = options.useSharedBuilder === true || options.useSharedBuilder === 'true';
bp.useCleanupCron = options.useCleanupCron === true || options.useCleanupCron === 'true';
bp.maxRetainedWorkspaces = Number.parseInt(options.maxRetainedWorkspaces || '0', 10);
bp.useLargePackages = options.useLargePackages === true || options.useLargePackages === 'true';
bp.useCompressionStrategy = options.useCompressionStrategy === true || options.useCompressionStrategy === 'true';
bp.garbageMaxAge = Number(options.garbageMaxAge) || 24;
bp.githubChecks = options.githubChecks === true || options.githubChecks === 'true';
bp.asyncWorkflow = options.asyncOrchestrator === true || options.asyncOrchestrator === 'true';
bp.githubCheckId = options.githubCheckId || '';
bp.finalHooks = options.finalHooks ? options.finalHooks.split(',') : [];
bp.skipLfs = options.skipLfs === true || options.skipLfs === 'true';
bp.skipCache = options.skipCache === true || options.skipCache === 'true';
bp.cacheUnityInstallationOnMac =
options.cacheUnityInstallationOnMac === true || options.cacheUnityInstallationOnMac === 'true';
bp.unityHubVersionOnMac = options.unityHubVersionOnMac || '';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cli-plugin/build-parameters-adapter.ts` around lines 53 - 124, The
BuildParameters mapping in build-parameters-adapter.ts is dropping several
registered CLI flags (region, containerHookFiles, commandHookFiles,
resourceTracking, useSharedBuilder, useCleanupCron); update the bp assignments
(the BuildParameters object referenced as bp) to copy these options from the
incoming options object: set bp.region = options.region || '' (or appropriate
default), map bp.containerHookFiles and bp.commandHookFiles to arrays (e.g.
options.containerHookFiles ? options.containerHookFiles.split(',') : []), set
bp.resourceTracking = options.resourceTracking === true ||
options.resourceTracking === 'true', and set bp.useSharedBuilder and
bp.useCleanupCron similarly to boolean conversions (=== true || === 'true'); add
these lines alongside the other mappings in the same adapter so user-provided
values are preserved.

bp.orchestratorDebug = options.orchestratorDebug === true || options.orchestratorDebug === 'true';

// Build platform
bp.buildPlatform = options.buildPlatform || (bp.providerStrategy !== 'local' ? 'linux' : process.platform);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

local-system gets the wrong default buildPlatform.

Current default treats only providerStrategy === 'local' as host-local. local-system is also host-local and should not default to 'linux' on macOS/Windows.

🔧 Proposed fix
-  bp.buildPlatform = options.buildPlatform || (bp.providerStrategy !== 'local' ? 'linux' : process.platform);
+  const isHostLocalProvider =
+    bp.providerStrategy === 'local' || bp.providerStrategy === 'local-system';
+  bp.buildPlatform = options.buildPlatform || (isHostLocalProvider ? process.platform : 'linux');
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
bp.buildPlatform = options.buildPlatform || (bp.providerStrategy !== 'local' ? 'linux' : process.platform);
const isHostLocalProvider =
bp.providerStrategy === 'local' || bp.providerStrategy === 'local-system';
bp.buildPlatform = options.buildPlatform || (isHostLocalProvider ? process.platform : 'linux');
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cli-plugin/build-parameters-adapter.ts` at line 103, The default
buildPlatform assignment in build-parameters-adapter.ts incorrectly treats only
providerStrategy === 'local' as host-local; update the logic around
bp.buildPlatform (the assignment that uses options.buildPlatform ||
(bp.providerStrategy !== 'local' ? 'linux' : process.platform)) so that
providerStrategy values 'local' and 'local-system' are both considered
host-local (i.e., if bp.providerStrategy is neither 'local' nor 'local-system'
default to 'linux', otherwise use process.platform); change the conditional to
check for both identifiers (e.g., via explicit comparisons or an inclusion
check) to ensure local-system uses the host platform default.

bp.maxRetainedWorkspaces = Number.parseInt(options.maxRetainedWorkspaces || '0', 10);
bp.useLargePackages = options.useLargePackages === true || options.useLargePackages === 'true';
bp.useCompressionStrategy = options.useCompressionStrategy === true || options.useCompressionStrategy === 'true';
bp.garbageMaxAge = Number(options.garbageMaxAge) || 24;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

garbageMaxAge cannot be set to 0.

Number(options.garbageMaxAge) || 24 forces 0 back to 24. This breaks a valid boundary value.

🔧 Proposed fix
-  bp.garbageMaxAge = Number(options.garbageMaxAge) || 24;
+  const parsedGarbageMaxAge = Number(options.garbageMaxAge);
+  bp.garbageMaxAge = Number.isNaN(parsedGarbageMaxAge) ? 24 : parsedGarbageMaxAge;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
bp.garbageMaxAge = Number(options.garbageMaxAge) || 24;
const parsedGarbageMaxAge = Number(options.garbageMaxAge);
bp.garbageMaxAge = Number.isNaN(parsedGarbageMaxAge) ? 24 : parsedGarbageMaxAge;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cli-plugin/build-parameters-adapter.ts` at line 115, The assignment
bp.garbageMaxAge = Number(options.garbageMaxAge) || 24 erroneously treats 0 as
falsy and resets it to 24; instead parse the value and only fallback when the
parsed value is NaN/invalid—e.g., compute const parsed =
Number(options.garbageMaxAge) and set bp.garbageMaxAge = Number.isFinite(parsed)
? parsed : 24 (or use a nullish/NaN check) so that 0 is preserved; update the
code around bp.garbageMaxAge and options.garbageMaxAge accordingly.

'local-docker': createProviderAdapter(LocalDockerOrchestrator),
'local-system': createProviderAdapter(LocalOrchestrator),
local: createProviderAdapter(LocalOrchestrator),
test: createProviderAdapter(TestOrchestrator),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n --type=ts -C2 'runTaskInWorkflow\s*\(' \
  src/model/orchestrator/providers/provider-interface.ts \
  src/model/orchestrator/providers/test/index.ts \
  src/cli-plugin/provider-adapter.ts

Repository: game-ci/unity-builder

Length of output: 1646


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Get full runTaskInWorkflow signature from test provider
echo "=== Test Provider Implementation ===" 
sed -n '45,60p' src/model/orchestrator/providers/test/index.ts

echo -e "\n=== Provider Interface Definition ===" 
sed -n '26,42p' src/model/orchestrator/providers/provider-interface.ts

echo -e "\n=== Adapter Implementation ===" 
sed -n '33,43p' src/cli-plugin/provider-adapter.ts

echo -e "\n=== Line 57 context in index.ts ===" 
sed -n '50,65p' src/cli-plugin/index.ts

Repository: game-ci/unity-builder

Length of output: 2340


test provider exposes a critical interface violation: TestOrchestrator.runTaskInWorkflow parameters are in the wrong order.

The interface defines runTaskInWorkflow(buildGuid, image, commands, ...), but TestOrchestrator implements runTaskInWorkflow(commands, buildGuid, image, ...). The adapter forwards arguments in interface-compliant order, causing TestOrchestrator to receive them misaligned: buildGuid receives the buildGuid value interpreted as commands, image receives buildGuid as image, and commands receives image as commands. This violates the provider contract and produces incorrect runtime behavior when test is selected in production.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cli-plugin/index.ts` at line 57, The Test provider is violating the
provider interface because TestOrchestrator implements
runTaskInWorkflow(commands, buildGuid, image, ...) while the expected signature
is runTaskInWorkflow(buildGuid, image, commands, ...); update
TestOrchestrator.runTaskInWorkflow to take parameters in the correct order
(buildGuid, image, commands, ...) and adjust the internal use of those
parameters accordingly so that createProviderAdapter(TestOrchestrator) forwards
arguments correctly to the implementation.

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 28, 2026

Codecov Report

❌ Patch coverage is 90.05848% with 17 lines in your changes missing coverage. Please review.
✅ Project coverage is 33.36%. Comparing base (ce7ce7a) to head (12a3a02).

Files with missing lines Patch % Lines
src/cli-plugin/provider-adapter.ts 19.04% 17 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #824      +/-   ##
==========================================
+ Coverage   31.23%   33.36%   +2.12%     
==========================================
  Files          84       88       +4     
  Lines        4565     4736     +171     
  Branches     1103     1137      +34     
==========================================
+ Hits         1426     1580     +154     
- Misses       3139     3156      +17     
Files with missing lines Coverage Δ
src/cli-plugin/build-parameters-adapter.ts 100.00% <100.00%> (ø)
src/cli-plugin/index.ts 100.00% <100.00%> (ø)
src/cli-plugin/orchestrator-options-plugin.ts 100.00% <100.00%> (ø)
src/cli-plugin/provider-adapter.ts 19.04% <19.04%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@frostebite frostebite closed this Mar 28, 2026
@frostebite frostebite deleted the feat/cli-plugin-adapter branch March 28, 2026 20:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant