Commit Graph
7 Commits
Author SHA1 Message Date
frostebite 1ab7c56964 security: avoid interpolating values into a bash -c string in downloadCli
CodeQL flagged the install.sh invocation (js/actions/uncontrolled-
command-line) - correctly this time, unlike the pre-existing false
positive on src/index.ts:45. resolvedVersion/destDir were passed safely
as quoted positional params ($0/$1/$2) rather than concatenated into
the command text, so it wasn't exploitable, but building a `bash -c
'... "$0" ...'` string at all is exactly the shape that query looks
for, and there was a strictly better option available: fetch
install.sh's content directly, write it to a file, and run that file
with a plain args array - the same shape this file's own callers
already use for the CLI binary itself, with no shell-text construction
step for the query to flag in the first place.

Also fixes a real, environment-dependent test bug found while touching
this: the "restores from cache" test's real fs.chmod call throws ENOENT
on Linux for a path that doesn't exist on disk, gets swallowed by
restoreFromCache's own try/catch, and silently falls through to the
real install path - passing locally only because the chmod call is
skipped entirely on `win32` (a Windows dev machine), never because the
cache-restore logic under test actually worked. fs/promises is now
mocked like @actions/cache and @actions/exec already were.
2026-08-26 23:28:32 +01:00
frostebite 5aebed59ab fix: avoid Array#toReversed() - unsupported on this project's target Node 2026-08-26 18:32:00 +01:00
frostebite 8b0cc6cbd9 refactor: delegate CLI install to game-ci/cli's shared install.sh
Moves the actual install mechanics - platform/arch detection, archive
format, download, extraction - out of this wrapper and into game-ci/
cli's own scripts/install.sh (game-ci/cli#187), fetched and run at the
resolved version's tag. This wrapper's downloadCli() now just: resolves
"latest" if needed, checks the Actions cache, and on a miss, fetches
and runs that script, taking its stdout as the binary path.

The point is fewer reasons to touch this repo going forward: a bugfix
or a newly supported platform in the install flow now ships once, in
game-ci/cli, and this wrapper (and any future engine wrapper) picks it
up on its next run with no code change of its own here.

@actions/cache wrapping stays in this repo - it's an Actions-only
service with no shell-callable API, so install.sh has no way to drive
it itself. Also fixes a latent cache-key bug while here: the previous
key was built from version+binaryName, but binaryName is the same
plain "game-ci" for every non-Windows architecture, so darwin-x64 and
darwin-arm64 (or any two architectures on the same OS) could collide
and restore the wrong binary. The new key includes process.arch.
2026-08-26 18:29:53 +01:00
frostebite b5caacf1c2 fix: authenticate resolveLatestTag's GitHub API call to avoid rate limiting
Confirmed hitting this for real on #844: "Failed to resolve the latest
game-ci CLI release: GitHub API returned 403" on both the MacOS and
Ubuntu re-triggered runs. Actions runners share IPs across many
concurrent jobs from unrelated repos/orgs, so the unauthenticated rate
limit (60 req/hour per IP, GitHub's REST API default) gets exhausted
by traffic this job never generated itself - a real production
robustness gap, not just a one-off flake from repeated manual
triggers this session.

Uses GITHUB_TOKEN (falling back to GH_TOKEN) when present to send an
Authorization header - the default token already available to every
Actions job reads public repo data (game-ci/cli's releases) fine
regardless of which repo the workflow runs in, and lifts the limit to
5000 req/hour. No token still works exactly as before (no header).

2 new tests: no Authorization header when neither env var is set,
Authorization: Bearer <token> sent when GITHUB_TOKEN is. 13/13 pass in
download-cli.test.ts, 40/40 across the full suite.

Rebuilds dist/index.js - action.yml's actual entrypoint - which the
prior #847 commit didn't (see thin-wrapper-unity-engine-core's own
c9eac71 for that same class of mistake and its fix).
2026-08-25 20:05:47 +01:00
frostebite 43d4978f4d feat: cache the game-ci CLI download even when cliVersion=latest
cliVersion defaults to 'latest', and caching was previously skipped
entirely for it - only pinned versions (cliVersion: v0.1.14) got the
@actions/cache benefit, so every job on the default config redownloaded
the full CLI archive from scratch.

Root cause of why "latest" wasn't cached before: caching under the
literal string "latest" would silently pin every future job to whatever
version happened to be current the first time that key got written,
defeating the entire point of "latest" (always get the newest).

Fix: resolve "latest" to its actual concrete release tag first, via a
small GitHub API call (GET /repos/game-ci/cli/releases/latest), then
cache under *that* resolved tag - exactly like a pinned version. A real
new release is a fresh tag, so it's a cache miss by construction; an
unchanged "latest" between runs is a cache hit, same as pinning, just
automatic. Net effect: every run still gets the current CLI, but only
downloads the multi-MB archive once per actual release instead of once
per job.

Verification:
- yarn typecheck: clean.
- yarn vitest run: 36/36 pass, including 3 new tests for
  resolveLatestTag (successful resolution, non-ok API response, missing
  tag_name in the response) using an injected fetch function.
- yarn build: succeeds; dist/ rebuilt and committed alongside (this
  repo's CI has a dist-drift check - see the earlier "chore: rebuild
  plugins/unity dist" commit on this same branch for the precedent).
- oxfmt --check: clean.
2026-08-24 20:27:53 +01:00
frostebite 8ca64ed6e1 fix: download and extract the release archive, not a bare binary
The compiled game-ci binary was never actually self-contained - see
game-ci/cli#73. It now ships as an archive (.tar.gz / .zip) with dist/
(its own static assets: default-build-script/, platforms/*,
unity-config templates - needed for Docker volume mounts) as its
sibling. download-cli.ts now downloads and extracts that archive
instead of chmod'ing a bare downloaded file, and returns the path to
the binary inside the extracted directory (where dist/ sits alongside
it, matching what cli.ts now expects on disk).
2026-08-14 05:03:50 +01:00
frostebite 9a06a71753 feat: rework thin wrapper to invoke game-ci/cli as a subprocess
Supersedes the previous approach on this branch, which imported
@game-ci/unity-engine-core as an in-process library. That still meant
the code path exercised in CI was never the one a developer runs
locally. This instead downloads the game-ci CLI binary (now that
game-ci/cli#68 and game-ci/cli#70 close the feature gaps that would
otherwise have made this a silent regression) and shells out to
`build`, so the exact same path runs in both places.

- build-args.ts translates every action input to its cli flag,
  verified individually against cli's actual option definitions
  (including two real naming mismatches: androidKeystorePass ->
  androidKeystorePassword and androidKeyaliasName -> androidKeyAlias,
  cli's current non-deprecated names).
- download-cli.ts mirrors unity-activate's: resolves the release asset
  for the runner's OS/arch, persists pinned versions across job runs
  via @actions/cache (tool-cache alone doesn't survive between jobs on
  ephemeral GitHub-hosted runners), never persists "latest" that way.
- Credentials (UNITY_EMAIL etc.) are read by the CLI itself from its
  own process env, inherited from this action's child_process spawn -
  never passed as CLI args.
- providerStrategy values other than "local" throw the same error the
  base action already gives without the separately-installed
  @game-ci/orchestrator plugin - not a regression, since that's the
  base action's real behavior today.
- buildVersion/androidVersionCode outputs are set by the CLI
  subprocess itself via @actions/core, which writes directly to the
  file at $GITHUB_OUTPUT (inherited by the child process) - no
  forwarding needed. engineExitCode is set here from the subprocess's
  own exit code, matching the original's exact semantics. `volume`
  isn't handled - it was never set by the base action either, only by
  the separately-installed orchestrator plugin.
- action.yml gains a `cliVersion` input (default "latest"). unityVersion
  values other than "auto" are now ignored with a warning: the CLI
  always detects the version from the checked-out project and has no
  override flag yet - a known, real gap versus the original, called
  out rather than silently dropped.
- Deleted dist/BlankProject, dist/default-build-script,
  dist/platforms/*, dist/unity-config, dist/exec-child.js: all dead
  under the new structure. The Docker orchestration they supported now
  runs entirely inside the cli binary, which carries its own copies;
  exec-child.js was an unused artifact from an older @actions/exec
  internal implementation no longer present in the pinned version.
2026-08-14 03:28:14 +01:00