Skip to content

chore: quality-tightening (oxfmt + oxlint + tsc + vitest + husky + actionlint) - #833

Merged
frostebite merged 3 commits into
mainfrom
quality-tightening
May 6, 2026
Merged

chore: quality-tightening (oxfmt + oxlint + tsc + vitest + husky + actionlint)#833
frostebite merged 3 commits into
mainfrom
quality-tightening

Conversation

@webbertakken

@webbertakken webbertakken commented May 5, 2026

Copy link
Copy Markdown
Member

Standard rollout for unity-builder. Most of the work was porting 24 test files from jest 27 to vitest 4.

Drops

  • prettier, jest 27, jest-circus, ts-jest, @types/jest, @jest/globals
  • airbnb / github / typescript-eslint / prettier / unicorn / jest eslint configs and plugins
  • lefthook + lefthook.yml

Adds

  • oxfmt, oxlint with eslint-plugin-unicorn
  • vitest 4 + vite 7 + @vitest/coverage-istanbul
  • tsgo --noEmit (alongside tsc fallback)
  • husky 9 with scripts/ensure-husky.mjs self-heal + lint-staged
  • mise.toml: actionlint, shellcheck, gitleaks
  • TypeScript: target ES2020 \u2192 ES2022 + lib ES2022 + DOM (for Error.cause and modern globals)

Test migration (24 files)

  • Bulk-converted jest.* \u2192 vi.*; jest.Mocked \u2192 Mocked from vitest; jest.MockedFunction \u2192 MockedFunction.
  • Added vitest imports to all *.test.ts files (and __mocks__/*.ts) that didn't have them.
  • src/index.ts: extracted runMain() as a named export and gated the module-level invocation behind NODE_ENV !== 'test'. The index-plugin-features test now calls runMain() directly instead of relying on jest's removed vi.isolateModules.
  • index-plugin-features.test.ts: moved hoisted refs (mockPlugin, mockLoadOrchestratorPlugin) into vi.hoisted() so vi.mock factories can reference them. Replaced arrow constructor mock for ImageTag with regular function() {...} (vitest 4 disallows arrows as ctors). Replaced require('./model') / require('@actions/core') inside test bodies with top-level imports.
  • model/orchestrator-plugin.test.ts: dropped jest's { virtual: true } flag (vitest doesn't support it); replaced the 'mock factory throws' pattern with 'createPlugin throws' so vitest doesn't wrap the error message at the assertion site.
  • model/versioning.test.ts: stray jest.spyOn \u2192 vi.spyOn; replaced mockImplementation() with no args (jest pattern) by mockResolvedValue('') / mockImplementation(() => undefined) where the source expects a string return.

Workflow shell-quoting cleanup (actionlint)

  • All bare $GITHUB_STEP_SUMMARY / $GITHUB_OUTPUT / $GITHUB_ENV redirects quoted across 2 workflows (SC2086).
  • s3://$VAR \u2192 s3://"$VAR".
  • for i in {1..N} loops where i isn't referenced renamed to for _ in (SC2034).
  • grep ... | wc -l \u2192 grep -c ... (SC2126).
  • Multiple consecutive >> $file redirects in validate-community-plugins.yml summary block collapsed into a single block redirect (SC2129).
  • cat $file | python3 -c '...' \u2192 python3 -c '...' < $file (SC2002).
  • http://${VAR}:port \u2192 http://"${VAR}":port (SC2086).

tsgo

Kept tsc --noEmit as default typecheck because unity-builder publishes CommonJS for the GitHub Action consumer, which conflicts with tsgo's bundler/node16 moduleResolution requirement (per playbook trap #9). yarn typecheck:tsgo is wired up for when consumers move to ESM.

Caveats

28 pre-existing oxlint warnings remain (mostly typescript/no-explicit-any across the build-parameter shapes and vitest/no-disabled-tests on 2 explicitly skipped scenarios). Per playbook trap #22 the lint script drops --deny-warnings.

Verified locally

  • yarn format:check \u2014 clean (110 files)
  • yarn lint \u2014 0 errors, 28 warnings
  • yarn typecheck \u2014 clean
  • yarn test \u2014 340 / 342 (2 pre-existing skipped)
  • actionlint \u2014 clean across all 12 workflows

Summary by CodeRabbit

Release Notes

  • New Features

    • Integrated Oxlint and Oxfmt for improved code quality and formatting standards.
    • Added Husky git hooks for automatic linting, type checking, and secret detection on commits.
    • Extended Plugin interface with new lifecycle hooks: handleBuild, beforeLocalBuild, and afterLocalBuild.
  • Tests

    • Migrated test suite from Jest to Vitest for improved performance and developer experience.
  • Chores

    • Updated TypeScript configuration for modern ES2022 support.
    • Removed legacy ESLint and Prettier configurations in favor of Oxlint/Oxfmt.
    • Removed legacy Lefthook configuration, replaced with Husky.
    • Updated GitHub workflows and package dependencies.

…tionlint)

Standard rollout for unity-builder. Most of the work was porting 24
test files from jest 27 to vitest 4.

- prettier -> oxfmt
- eslint (with @typescript-eslint, github, jest, prettier, unicorn) ->
  oxlint with eslint-plugin-unicorn
- jest 27 + jest-circus + ts-jest + @types/jest + @jest/globals ->
  vitest 4 + vite 7 + @vitest/coverage-istanbul (jest config files
  removed)
- new: tsgo --noEmit (alongside tsc fallback)
- lefthook (and lefthook.yml) -> husky 9 with the standard
  scripts/ensure-husky.mjs self-heal pattern + lint-staged
- new: gitleaks, actionlint, shellcheck as mise-managed binaries
- TypeScript bumped target ES2020 -> ES2022 + lib ES2022 + DOM (for
  Error.cause and modern globals)

Test migration (24 files):
- Bulk-converted jest.* -> vi.*; jest.Mocked -> Mocked from vitest;
  jest.MockedFunction -> MockedFunction.
- Added vitest imports to all *.test.ts files (and __mocks__/*.ts)
  that didn't have them.
- src/index.ts: extracted runMain() as a named export and gated the
  module-level invocation behind NODE_ENV !== 'test'. The
  index-plugin-features test now calls runMain() directly instead of
  relying on jest's removed vi.isolateModules.
- index-plugin-features.test.ts: moved hoisted refs (mockPlugin,
  mockLoadOrchestratorPlugin) into vi.hoisted() so vi.mock factories
  can reference them. Replaced arrow constructor mock for ImageTag
  with regular function() {...} (vitest 4 disallows arrows as ctors).
  Replaced require('./model') / require('@actions/core') inside test
  bodies with top-level imports.
- model/orchestrator-plugin.test.ts: dropped jest's '{ virtual: true }'
  flag (vitest doesn't support it); replaced the
  'mock factory throws' pattern with 'createPlugin throws' so vitest
  doesn't wrap the error message at the assertion site.
- model/versioning.test.ts: stray jest.spyOn -> vi.spyOn; replaced
  mockImplementation() with no args (jest pattern) by
  mockResolvedValue('') / mockImplementation(() => undefined) where
  the source expects a string return.

Workflow shell-quoting cleanup (actionlint):
- All bare $GITHUB_STEP_SUMMARY / $GITHUB_OUTPUT / $GITHUB_ENV
  redirects quoted across 2 workflows (SC2086).
- s3://$AWS_STACK_NAME / s3://$BUCKET_NAME -> s3://"$AWS_STACK_NAME"
  / s3://"$BUCKET_NAME".
- 'for i in {1..N}; do ... done' loops where i isn't referenced in
  the body renamed to 'for _ in' (SC2034).
- 'grep ... | wc -l' -> 'grep -c ...' (SC2126).
- Multiple consecutive '>> $file' redirects in
  validate-community-plugins.yml summary block collapsed into a
  single block redirect (SC2129).
- 'cat $file | python3 -c "..."' -> 'python3 -c "..." < $file'
  (SC2002).
- http://${VAR}:port -> http://"${VAR}":port (SC2086).

tsgo: kept tsc --noEmit as the default 'typecheck' because
unity-builder publishes CommonJS for the GitHub Action consumer,
which conflicts with tsgo's bundler/node16 moduleResolution
requirement (per playbook trap #9). 'yarn typecheck:tsgo' is wired
up for when consumers move to ESM.

Caveats: 28 pre-existing oxlint warnings remain (mostly
typescript/no-explicit-any across the build-parameter shapes and
vitest/no-disabled-tests on 2 explicitly skipped scenarios). Per
playbook trap #22 the lint script drops --deny-warnings.

Verified locally: format clean, lint 0/28, typecheck clean,
test 340/342 (2 pre-existing skipped), actionlint clean across all
12 workflows.
@coderabbitai

coderabbitai Bot commented May 5, 2026

Copy link
Copy Markdown

Caution

Review failed

Failed to post review comments

📝 Walkthrough

Walkthrough

This PR modernizes the repository's development toolchain by migrating from Jest to Vitest for testing, replacing ESLint/Prettier with Oxlint/Oxfmt for linting and formatting, switching from Lefthook to Husky for git hooks, and enhancing the plugin lifecycle with new hooks for build handling. TypeScript and dependency configurations are updated accordingly.

Changes

Development Toolchain Modernization

Layer / File(s) Summary
Configuration Cleanup
.eslintignore, .eslintrc.json, .prettierrc.json, .prettierignore, lefthook.yml
Legacy ESLint, Prettier, and Lefthook configurations are removed.
New Linting/Formatting Tools
.oxlintrc.json, .oxfmtrc.json
Oxlint and Oxfmt configurations introduced with comprehensive rule sets and ignore patterns.
Git Hooks Migration
.husky/pre-commit, scripts/ensure-husky.mjs
Husky replaces Lefthook with a pre-commit hook that runs lint-staged, typecheck, and gitleaks protection.
Package.json & Dependencies
package.json
Removed Jest, ts-jest, and Lefthook; added Husky, Vitest, Vite, Oxlint, Oxfmt, and related ecosystem packages. Scripts updated to use Vitest and oxlint/oxfmt; lint-staged configuration added.
TypeScript Configuration
tsconfig.json
Target updated to ES2022, moduleResolution and outDir/rootDir added, lib expanded to include DOM, esModuleInterop and skipLibCheck enabled.
Test Framework Configuration
vitest.config.mts, src/test/setup.ts
New Vitest config file added with node environment, globals, and Istanbul coverage configuration. Test setup file enforces console spy requirements via beforeEach/afterEach hooks.
Jest Setup Removal
src/jest.setup.ts
Jest-specific console failure configuration removed.
Test File Migrations
src/**/*.test.ts, src/model/**/*.test.ts, .github/workflows/validate-orchestrator.yml
All test files migrate from Jest to Vitest: Jest imports replaced with Vitest, jest.fn/jest.spyOn/jest.mock converted to vi equivalents, afterEach hooks updated to vi.clearAllMocks/vi.restoreAllMocks. GitHub workflow steps updated to run yarn vitest instead of npx jest.
Supporting Infrastructure
.github/workflows/build-tests-ubuntu.yml, .github/workflows/validate-community-plugins.yml, mise.toml, .github/ISSUE_TEMPLATE/bug_report.md, action.yml, types/shell-quote.d.ts
CI workflows reformatted and optimized (Unity license env var, job names, artifact step names); mise.toml gains actionlint, shellcheck, gitleaks; issue template and action.yml receive minor formatting updates.

Plugin System Enhancement

Layer / File(s) Summary
Plugin Interface
src/model/plugin.ts
Plugin interface extended with three new lifecycle hooks: handleBuild(baseImage) returning exit code and optional fallback flag, beforeLocalBuild(workspace) for pre-build setup, and afterLocalBuild(workspace, exitCode) for post-build cleanup.
Plugin Implementation & Tests
src/model/plugin.test.ts
Plugin test suite migrated to Vitest with new test coverage for the expanded lifecycle methods and error handling for non-function createPlugin exports.
Index Entry Point
src/index.ts
runMain() function exported for direct test invocation; module-level invocation guarded by NODE_ENV check to prevent side effects during testing.
Integration Tests
src/index-plugin-features.test.ts
Test suite migrated to Vitest with comprehensive coverage of new lifecycle hooks (initialize, beforeLocalBuild, afterLocalBuild, handlePostBuild) including hook ordering assertions and explicit beforeLocalBuild mock implementations.

Code Formatting & Refactoring (across multiple files)

Layer / File(s) Summary
Method Signature Reformatting
src/model/build-parameters.ts, src/model/cli/cli.ts, src/model/image-environment-factory.ts, src/model/platform-setup/setup-android.ts, src/model/platform-setup/setup-mac.ts
Function calls, method signatures, and parameter lists expanded to multi-line format for readability without changing semantics.
Expression Reformatting
src/model/input.ts, src/model/versioning.ts, src/model/android-versioning.ts, src/model/input-readers/git-repo.ts, src/model/input-readers/test-license-reader.ts
Conditional expressions, string operations, and chained calls reformatted to multi-line blocks; logic preserved.
Export Block Formatting
src/model/index.ts
Export statement converted from single-line to multi-line per-symbol list with trailing commas.
Test Scaffolding Additions
src/**/*.test.ts (multiple files)
Additional Vitest utilities (vi, beforeEach, afterEach, beforeAll, afterAll, test) imported into test files where needed for consistency.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

This PR encompasses substantial structural changes across multiple layers: comprehensive test framework migration affecting 40+ test files with mock rewiring, complete development toolchain replacement (build tools, linting, formatting, hooks), new plugin lifecycle hooks with corresponding test coverage, TypeScript configuration updates, and numerous code formatting adjustments. The heterogeneity of changes (test framework migration, configuration removal/addition, interface enhancements, formatting) demands separate reasoning for each layer despite some repetitive patterns in test file updates.

Possibly related PRs

  • game-ci/unity-builder#819: Adds plugin loader infrastructure and orchestrator integration affecting the same Plugin interface and runtime behavior.
  • game-ci/unity-builder#830: Updates repository toolchain configuration (mise.toml, GitHub workflows, ESLint config) with overlapping changes.
  • game-ci/unity-builder#775: Modifies src/index.ts entry point and Orchestrator integration, touching the same main entry file.

Suggested labels

tooling, testing, refactoring

Suggested reviewers

  • GabLeRoux
  • cloudymax
  • davidmfinol

Poem

🐰 Whiskers twitching with delight,
From Jest to Vitest, shiny and bright!
Oxlint dances, Oxfmt sings,
Husky guards with safety rings. 🎉
Old configs hop away with care,
New hooks bloom in the fresh spring air!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: a comprehensive quality-tightening overhaul replacing Prettier/Jest/ESLint configs with oxfmt, oxlint, vitest, and related tooling.
Description check ✅ Passed The PR description is comprehensive and well-structured, covering all required template sections: changes, related items, and checklist. It provides detailed context on drops, additions, test migrations, workflow cleanup, tsgo rationale, and local verification results.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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 quality-tightening

@github-actions

github-actions Bot commented May 5, 2026

Copy link
Copy Markdown

Cat Gif

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/model/input.test.ts (1)

218-228: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Test asserts the wrong property — androidSymbolType is never verified.

Line 225 checks Input.androidExportType inside the androidSymbolType describe block. This is a copy-paste from the androidExportType test above. The spy is mocking getInput with symbol-type values ('none', 'public', 'debugging'), so the assertion should target Input.androidSymbolType.

🐛 Proposed fix
-      expect(Input.androidExportType).toStrictEqual(expected);
+      expect(Input.androidSymbolType).toStrictEqual(expected);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/model/input.test.ts` around lines 218 - 228, The test in the
androidSymbolType block is asserting the wrong property; change the expectation
to verify Input.androidSymbolType instead of Input.androidExportType, keeping
the mock spy on core.getInput and the test.each table as-is so the symbol-type
values ('none','public','debugging') are validated; update the assertion line to
expect(Input.androidSymbolType).toStrictEqual(expected) and keep the spy call
count assertion.
🧹 Nitpick comments (2)
.github/workflows/build-tests-ubuntu.yml (1)

38-38: 💤 Low value

Minor: whitespace inconsistencies in the interpolated expression.

Inside the name: string the spacing around ${{ ... }} is inconsistent: ${{ matrix.unityVersion}} is missing the trailing space and ${{startsWith(...) is missing the leading space, while ${{ matrix.targetPlatform }} follows the canonical style. Functionally identical, but worth aligning so future formatter passes don't churn the line.

♻️ Proposed alignment
-    name: "${{ matrix.targetPlatform }} on ${{ matrix.unityVersion}}${{startsWith(matrix.buildProfile, 'Assets') && ' (via Build Profile)' || '' }}"
+    name: "${{ matrix.targetPlatform }} on ${{ matrix.unityVersion }}${{ startsWith(matrix.buildProfile, 'Assets') && ' (via Build Profile)' || '' }}"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/build-tests-ubuntu.yml at line 38, The interpolated GitHub
Actions job name contains inconsistent spacing around the expressions; update
the string so all expressions use consistent spacing like `${{
matrix.targetPlatform }}`, `${{ matrix.unityVersion }}`, and `${{
startsWith(matrix.buildProfile, 'Assets') && ' (via Build Profile)' || '' }}`
(ensure leading/trailing spaces inside each `${{ ... }}`) to match canonical
style and avoid formatter churn.
src/index-plugin-features.test.ts (1)

118-123: 💤 Low value

Minor comment inaccuracy — code still uses a dynamic import.

The inline comment says the new approach avoids "round-tripping through dynamic imports," but runIndex still does await import('./index') on every call. The intent of the comment is probably to contrast against the old pattern of jest.isolateModules + require, not against dynamic imports generally. Worth a small wording fix to avoid confusion.

📝 Suggested wording
-  // index.ts exports `runMain` for testability (the file used to rely on
-  // top-level execution + jest's `vi.isolateModules`, but vitest 4 dropped
-  // that API). Calling the exported function directly is cleaner than
-  // round-tripping through dynamic imports.
+  // index.ts exports `runMain` for testability. The old Jest approach relied on
+  // jest.isolateModules + require to re-execute the module per test. Calling
+  // the exported function directly avoids that isolation complexity.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/index-plugin-features.test.ts` around lines 118 - 123, Update the
inaccurate inline comment around the test helper that imports './index' (the
block referencing runMain and the helper runIndex) to clarify that we are
avoiding the old jest.isolateModules + require pattern, not dynamic imports in
general; reword to state we now call the exported runMain directly for clarity
while tests still use a dynamic import (await import('./index')) rather than
claiming we avoid "round-tripping through dynamic imports." Ensure the comment
references runMain and the test helper (runIndex) so future readers understand
the distinction.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/validate-orchestrator-integration.yml:
- Around line 365-369: The PVC count logic produces two lines when no matches
because `grep -c ... || echo "0"` appends an extra 0; fix the loop that sets
PVC_COUNT (the command substitution used to assign PVC_COUNT) by removing the
`|| echo "0"` or replacing it with a no-op on failure (e.g., `|| true`), or
switch to a reliable count method (e.g., `kubectl get pvc -n default | grep -c
"unity-builder-pvc-"` or `kubectl get pvc -n default --no-headers | grep -c
...`) so PVC_COUNT is always a single integer and the `[ "$PVC_COUNT" -eq 0 ]`
check in the for-loop can succeed and break as intended.

In @.husky/pre-commit:
- Around line 1-7: The pre-commit hook currently can return success even when
earlier steps fail because it lacks "set -e" and does not chain commands; update
the .husky/pre-commit script so failures in "yarn lint-staged" or "yarn
typecheck" cause the hook to exit non-zero — either add "set -e" (or "set -euo
pipefail") at the top of the script, or chain the commands with "&&" (e.g. "yarn
lint-staged && yarn typecheck && { if command -v gitleaks ...; fi }") and keep
the existing gitleaks check inside the final conditional block so the hook’s
exit status reflects earlier failures.

In `@mise.toml`:
- Around line 4-6: The mise.toml entries use "latest" for actionlint,
shellcheck, and gitleaks which causes non-reproducible CI/hooks; update the
three keys actionlint, shellcheck, and gitleaks to explicit version strings
(e.g., semantic version tags or commit SHAs) instead of "latest", commit the
pinned versions, and add a comment or changelog note to document the chosen
versions so future bumps are intentional and auditable.

In `@src/model/input-readers/git-repo.ts`:
- Around line 26-28: The code builds a shell string using Input.projectPath and
passes it to GitRepoReader.runCommand, exposing a command-injection risk and
breaking on paths with spaces; change the call to invoke git directly with
arguments and set the working directory instead of interpolating the path—i.e.,
call GitRepoReader.runCommand with the command/args for "git remote -v" and a
cwd option of Input.projectPath (or use a child_process variant that accepts {
cwd: Input.projectPath }), preserve the .replace() behavior on the output, and
ensure errors are propagated/handled as before.

In `@src/model/versioning.test.ts`:
- Around line 255-259: The test currently checks the Promise object instead of
its rejection; update the assertion to await the Promise rejection: replace
expect(Versioning.parseSemanticVersion()).toMatchObject({}) with await
expect(Versioning.parseSemanticVersion()).rejects.toThrow() (or use
.rejects.toMatchObject({...}) if you want to assert a specific error shape).
Keep the existing spy on Versioning.getVersionDescription() returning
'no-match-can-be-made' and ensure the test name "throws when no match could be
made" still reflects the rejection assertion.

---

Outside diff comments:
In `@src/model/input.test.ts`:
- Around line 218-228: The test in the androidSymbolType block is asserting the
wrong property; change the expectation to verify Input.androidSymbolType instead
of Input.androidExportType, keeping the mock spy on core.getInput and the
test.each table as-is so the symbol-type values ('none','public','debugging')
are validated; update the assertion line to
expect(Input.androidSymbolType).toStrictEqual(expected) and keep the spy call
count assertion.

---

Nitpick comments:
In @.github/workflows/build-tests-ubuntu.yml:
- Line 38: The interpolated GitHub Actions job name contains inconsistent
spacing around the expressions; update the string so all expressions use
consistent spacing like `${{ matrix.targetPlatform }}`, `${{ matrix.unityVersion
}}`, and `${{ startsWith(matrix.buildProfile, 'Assets') && ' (via Build
Profile)' || '' }}` (ensure leading/trailing spaces inside each `${{ ... }}`) to
match canonical style and avoid formatter churn.

In `@src/index-plugin-features.test.ts`:
- Around line 118-123: Update the inaccurate inline comment around the test
helper that imports './index' (the block referencing runMain and the helper
runIndex) to clarify that we are avoiding the old jest.isolateModules + require
pattern, not dynamic imports in general; reword to state we now call the
exported runMain directly for clarity while tests still use a dynamic import
(await import('./index')) rather than claiming we avoid "round-tripping through
dynamic imports." Ensure the comment references runMain and the test helper
(runIndex) so future readers understand the distinction.
🪄 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: 4dd14922-c3da-42bd-a989-cc3332b989e2

📥 Commits

Reviewing files that changed from the base of the PR and between 365bdb5 and 3fa95d0.

⛔ Files ignored due to path filters (2)
  • src/model/__snapshots__/versioning.test.ts.snap is excluded by !**/*.snap
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (63)
  • .eslintignore
  • .eslintrc.json
  • .github/ISSUE_TEMPLATE/bug_report.md
  • .github/workflows/build-tests-ubuntu.yml
  • .github/workflows/validate-community-plugins.yml
  • .github/workflows/validate-orchestrator-integration.yml
  • .husky/pre-commit
  • .oxfmtrc.json
  • .oxlintrc.json
  • .prettierignore
  • .prettierrc.json
  • action.yml
  • jest.ci.config.js
  • jest.config.js
  • lefthook.yml
  • mise.toml
  • package.json
  • scripts/ensure-husky.mjs
  • src/index-plugin-features.test.ts
  • src/index.ts
  • src/integrity.test.ts
  • src/model/__mocks__/input.ts
  • src/model/__mocks__/versioning.ts
  • src/model/action.test.ts
  • src/model/android-versioning.test.ts
  • src/model/android-versioning.ts
  • src/model/build-parameters.test.ts
  • src/model/build-parameters.ts
  • src/model/cache.test.ts
  • src/model/cli/cli.ts
  • src/model/docker.test.ts
  • src/model/docker.ts
  • src/model/error/command-execution-error.test.ts
  • src/model/error/not-implemented-exception.test.ts
  • src/model/error/validation-error.test.ts
  • src/model/image-environment-factory.ts
  • src/model/image-tag.test.ts
  • src/model/index.test.ts
  • src/model/index.ts
  • src/model/input-readers/git-repo.test.ts
  • src/model/input-readers/git-repo.ts
  • src/model/input-readers/github-cli.test.ts
  • src/model/input-readers/test-license-reader.ts
  • src/model/input.test.ts
  • src/model/input.ts
  • src/model/orchestrator-plugin.test.ts
  • src/model/orchestrator-plugin.ts
  • src/model/output.test.ts
  • src/model/platform-setup/setup-android.ts
  • src/model/platform-setup/setup-mac.ts
  • src/model/platform-validation/validate-windows.ts
  • src/model/platform.test.ts
  • src/model/project.test.ts
  • src/model/system-integration.test.ts
  • src/model/system.test.ts
  • src/model/unity-versioning.test.ts
  • src/model/unity-versioning.ts
  • src/model/unity.test.ts
  • src/model/versioning.test.ts
  • src/model/versioning.ts
  • tsconfig.json
  • types/shell-quote.d.ts
  • vitest.config.mts
💤 Files with no reviewable changes (8)
  • .eslintrc.json
  • jest.ci.config.js
  • lefthook.yml
  • jest.config.js
  • .prettierignore
  • .eslintignore
  • .prettierrc.json
  • types/shell-quote.d.ts

Comment on lines +365 to 369
for _ in {1..30}; do
PVC_COUNT=$(kubectl get pvc -n default 2>/dev/null | grep -c "unity-builder-pvc-" || echo "0")
if [ "$PVC_COUNT" -eq 0 ]; then echo "All PVCs deleted"; break; fi
sleep 1
done

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 | ⚡ Quick win

Minor: grep -c … || echo "0" produces a multi-line PVC_COUNT on the no-match path.

grep -c already prints 0 for no matches and exits with status 1, so the || echo "0" runs and appends a second 0. The command substitution captures both lines, so PVC_COUNT becomes "0\n0" whenever no unity-builder-pvc- PVCs are present. [ "$PVC_COUNT" -eq 0 ] then errors with integer expression expected, the break is never taken, and this cleanup wait runs the full 30 iterations even after PVCs are gone.

🛠 Suggested fix
-          for _ in {1..30}; do
-            PVC_COUNT=$(kubectl get pvc -n default 2>/dev/null | grep -c "unity-builder-pvc-" || echo "0")
-            if [ "$PVC_COUNT" -eq 0 ]; then echo "All PVCs deleted"; break; fi
-            sleep 1
-          done
+          for _ in {1..30}; do
+            PVC_COUNT=$(kubectl get pvc -n default 2>/dev/null | grep -c "unity-builder-pvc-" || true)
+            if [ "${PVC_COUNT:-0}" -eq 0 ]; then echo "All PVCs deleted"; break; fi
+            sleep 1
+          done
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/validate-orchestrator-integration.yml around lines 365 -
369, The PVC count logic produces two lines when no matches because `grep -c ...
|| echo "0"` appends an extra 0; fix the loop that sets PVC_COUNT (the command
substitution used to assign PVC_COUNT) by removing the `|| echo "0"` or
replacing it with a no-op on failure (e.g., `|| true`), or switch to a reliable
count method (e.g., `kubectl get pvc -n default | grep -c "unity-builder-pvc-"`
or `kubectl get pvc -n default --no-headers | grep -c ...`) so PVC_COUNT is
always a single integer and the `[ "$PVC_COUNT" -eq 0 ]` check in the for-loop
can succeed and break as intended.

Comment thread .husky/pre-commit
Comment on lines +1 to +7
#!/usr/bin/env sh
yarn lint-staged
yarn typecheck

if command -v gitleaks >/dev/null 2>&1; then
gitleaks protect --staged --no-banner --redact
fi

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 | ⚡ Quick win

Critical: failures in lint-staged/typecheck are silently swallowed by this hook.

The script has no set -e and the steps are not chained with &&. Husky 9 executes the hook script as-is and uses its final exit status. The last statement is the if … fi, which evaluates to 0 whenever gitleaks is not installed (because the then branch is skipped). On any developer machine without gitleaks, the hook will therefore exit 0 even if yarn lint-staged or yarn typecheck failed — commits with lint/format/type errors will pass through the pre-commit gate, defeating the intent of this PR.

🛡️ Suggested fix
 #!/usr/bin/env sh
+set -e
+
 yarn lint-staged
 yarn typecheck

 if command -v gitleaks >/dev/null 2>&1; then
   gitleaks protect --staged --no-banner --redact
 fi

Equivalent alternative if you prefer not to use set -e:

#!/usr/bin/env sh
yarn lint-staged && yarn typecheck && {
  if command -v gitleaks >/dev/null 2>&1; then
    gitleaks protect --staged --no-banner --redact
  fi
}
📝 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
#!/usr/bin/env sh
yarn lint-staged
yarn typecheck
if command -v gitleaks >/dev/null 2>&1; then
gitleaks protect --staged --no-banner --redact
fi
#!/usr/bin/env sh
set -e
yarn lint-staged
yarn typecheck
if command -v gitleaks >/dev/null 2>&1; then
gitleaks protect --staged --no-banner --redact
fi
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.husky/pre-commit around lines 1 - 7, The pre-commit hook currently can
return success even when earlier steps fail because it lacks "set -e" and does
not chain commands; update the .husky/pre-commit script so failures in "yarn
lint-staged" or "yarn typecheck" cause the hook to exit non-zero — either add
"set -e" (or "set -euo pipefail") at the top of the script, or chain the
commands with "&&" (e.g. "yarn lint-staged && yarn typecheck && { if command -v
gitleaks ...; fi }") and keep the existing gitleaks check inside the final
conditional block so the hook’s exit status reflects earlier failures.

Comment thread mise.toml
Comment on lines +4 to +6
actionlint = "latest"
shellcheck = "latest"
gitleaks = "latest"

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 | ⚡ Quick win

Pin tool versions instead of latest for reproducible CI/hooks.

Using latest on Line 4, Line 5, and Line 6 makes local and CI behavior drift over time and can break pipelines unexpectedly. Pin explicit versions (and bump intentionally).

Suggested change
-actionlint = "latest"
-shellcheck = "latest"
-gitleaks = "latest"
+actionlint = "1.7.7"
+shellcheck = "0.10.0"
+gitleaks = "8.24.2"
📝 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
actionlint = "latest"
shellcheck = "latest"
gitleaks = "latest"
actionlint = "1.7.7"
shellcheck = "0.10.0"
gitleaks = "8.24.2"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mise.toml` around lines 4 - 6, The mise.toml entries use "latest" for
actionlint, shellcheck, and gitleaks which causes non-reproducible CI/hooks;
update the three keys actionlint, shellcheck, and gitleaks to explicit version
strings (e.g., semantic version tags or commit SHAs) instead of "latest", commit
the pinned versions, and add a comment or changelog note to document the chosen
versions so future bumps are intentional and auditable.

Comment on lines +26 to +28
const value = (
await GitRepoReader.runCommand(`cd ${Input.projectPath} && git remote -v`)
).replace(/ /g, ``);

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 | 🏗️ Heavy lift

Avoid shell interpolation with Input.projectPath (command-injection risk).

On Line 27, building a shell command with cd ${Input.projectPath} allows shell metacharacter injection and breaks on some valid paths. Use argument-based process APIs with cwd instead of interpolating into a command string.

Suggested refactor
-import { exec } from 'node:child_process';
+import { execFile } from 'node:child_process';

-  private static async runCommand(command: string): Promise<string> {
+  private static async runCommand(
+    command: string,
+    args: string[],
+    cwd?: string,
+  ): Promise<string> {
     return new Promise<string>((resolve, reject) => {
-      exec(command, { maxBuffer: 1024 * 10000 }, (error, stdout) => {
+      execFile(command, args, { cwd, maxBuffer: 1024 * 10000 }, (error, stdout) => {
         if (error) {
           reject(error);
           return;
         }
         resolve(stdout.toString());
       });
     });
   }

-    const value = (
-      await GitRepoReader.runCommand(`cd ${Input.projectPath} && git remote -v`)
-    ).replace(/ /g, ``);
+    const value = (await GitRepoReader.runCommand('git', ['remote', '-v'], Input.projectPath)).replace(
+      / /g,
+      ``,
+    );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/model/input-readers/git-repo.ts` around lines 26 - 28, The code builds a
shell string using Input.projectPath and passes it to GitRepoReader.runCommand,
exposing a command-injection risk and breaking on paths with spaces; change the
call to invoke git directly with arguments and set the working directory instead
of interpolating the path—i.e., call GitRepoReader.runCommand with the
command/args for "git remote -v" and a cwd option of Input.projectPath (or use a
child_process variant that accepts { cwd: Input.projectPath }), preserve the
.replace() behavior on the output, and ensure errors are propagated/handled as
before.

Comment on lines 255 to 259
it('throws when no match could be made', async () => {
jest.spyOn(Versioning, 'getVersionDescription').mockResolvedValue('no-match-can-be-made');
vi.spyOn(Versioning, 'getVersionDescription').mockResolvedValue('no-match-can-be-made');

await expect(Versioning.parseSemanticVersion()).toMatchObject({});
});

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 | ⚡ Quick win

Test "throws when no match could be made" never actually asserts a throw — it always passes.

expect(Versioning.parseSemanticVersion()).toMatchObject({}) without .rejects matches the Promise object itself against an empty schema, which is always satisfied. The intended behavior (asserting the function rejects) is never verified.

🐛 Proposed fix
-      await expect(Versioning.parseSemanticVersion()).toMatchObject({});
+      await expect(Versioning.parseSemanticVersion()).rejects.toThrow();
📝 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
it('throws when no match could be made', async () => {
jest.spyOn(Versioning, 'getVersionDescription').mockResolvedValue('no-match-can-be-made');
vi.spyOn(Versioning, 'getVersionDescription').mockResolvedValue('no-match-can-be-made');
await expect(Versioning.parseSemanticVersion()).toMatchObject({});
});
it('throws when no match could be made', async () => {
vi.spyOn(Versioning, 'getVersionDescription').mockResolvedValue('no-match-can-be-made');
await expect(Versioning.parseSemanticVersion()).rejects.toThrow();
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/model/versioning.test.ts` around lines 255 - 259, The test currently
checks the Promise object instead of its rejection; update the assertion to
await the Promise rejection: replace
expect(Versioning.parseSemanticVersion()).toMatchObject({}) with await
expect(Versioning.parseSemanticVersion()).rejects.toThrow() (or use
.rejects.toMatchObject({...}) if you want to assert a specific error shape).
Keep the existing spy on Versioning.getVersionDescription() returning
'no-match-can-be-made' and ensure the test name "throws when no match could be
made" still reflects the rejection assertion.

…tightening

Three issues surfaced in CI after the jest -> vitest port:

1. **Obsolete snapshot blocks Tests job.**
   src/model/__snapshots__/versioning.test.ts.snap had two entries
   for the same 'throws for invalid strategy' assertion: one in the
   vitest format ('Versioning > determineBuildVersion > ...') and one
   in the legacy jest format without the '>'. vitest correctly
   regenerates the new one and flags the old one as obsolete; CI
   runs without --update so 'Test Files 1 failed' even though all
   343 tests passed. Removed the obsolete entry.

2. **'Plugin Architecture Health' workflow still calls jest.**
   .github/workflows/validate-orchestrator.yml had two 'npx jest'
   steps (orchestrator-plugin unit tests + orchestrator-standalone
   tests). The unity-builder + orchestrator codebases are both on
   vitest now. Replaced both with 'yarn vitest run'.

3. **jest-fail-on-console + src/jest.setup.ts left over.**
   The earlier vitest port missed the jest-fail-on-console
   integration. yarn install in CI surfaced
   YN0002: doesn't provide @jest/globals (requested by
   jest-fail-on-console). Removed jest-fail-on-console + jest.setup.ts;
   added src/test/setup.ts with the equivalent vitest beforeEach
   spies (same as unity-test-runner).
@codecov

codecov Bot commented May 5, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 33.33333% with 36 lines in your changes missing coverage. Please review.
✅ Project coverage is 42.70%. Comparing base (821ba97) to head (704c0b6).

Files with missing lines Patch % Lines
src/model/__mocks__/versioning.ts 0.00% 16 Missing ⚠️
src/model/platform-setup/setup-android.ts 0.00% 4 Missing ⚠️
src/model/platform-setup/setup-mac.ts 0.00% 4 Missing ⚠️
src/model/image-environment-factory.ts 0.00% 3 Missing ⚠️
src/model/build-parameters.ts 71.42% 0 Missing and 2 partials ⚠️
src/model/cli/cli.ts 0.00% 2 Missing ⚠️
src/model/input-readers/test-license-reader.ts 0.00% 2 Missing ⚠️
src/model/docker.ts 0.00% 1 Missing ⚠️
src/model/platform-validation/validate-windows.ts 0.00% 1 Missing ⚠️
src/model/versioning.ts 80.00% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##             main     #833       +/-   ##
===========================================
- Coverage   70.95%   42.70%   -28.26%     
===========================================
  Files          29       37        +8     
  Lines         878      733      -145     
  Branches      239      201       -38     
===========================================
- Hits          623      313      -310     
- Misses        255      381      +126     
- Partials        0       39       +39     
Files with missing lines Coverage Δ
src/model/__mocks__/input.ts 100.00% <100.00%> (ø)
src/model/android-versioning.ts 100.00% <100.00%> (ø)
src/model/input-readers/git-repo.ts 35.29% <100.00%> (-26.25%) ⬇️
src/model/input.ts 73.33% <100.00%> (-15.49%) ⬇️
src/model/plugin.ts 93.75% <100.00%> (-6.25%) ⬇️
src/model/unity-versioning.ts 100.00% <100.00%> (ø)
src/model/docker.ts 0.00% <0.00%> (-9.81%) ⬇️
src/model/platform-validation/validate-windows.ts 0.00% <0.00%> (ø)
src/model/versioning.ts 83.11% <80.00%> (-8.48%) ⬇️
src/model/build-parameters.ts 76.19% <71.42%> (-16.24%) ⬇️
... and 6 more

... and 23 files with indirect coverage changes

🚀 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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.github/workflows/validate-orchestrator.yml (1)

161-165: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Change yarn vitest run to yarn test or npx jest — the orchestrator repo uses Jest, not Vitest.

The orchestrator-standalone working directory is a checkout of game-ci/orchestrator, which currently has Jest (^27.5.1) in its devDependencies and runs tests via node --experimental-vm-modules ./node_modules/jest/bin/jest.js. Vitest is not installed in that repo. Running yarn vitest run will fail with a module-not-found error and break the workflow.

Either run yarn test (which delegates to the repo's test script) or npx jest to match the existing test setup.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/validate-orchestrator.yml around lines 161 - 165, Update
the "Run orchestrator unit tests" workflow step in the orchestrator-standalone
job: replace the failing command `yarn vitest run 2>&1 | tail -30` with a
command that invokes the repo's Jest tests such as `yarn test 2>&1 | tail -30`
or `npx jest 2>&1 | tail -30` so the step uses the existing test runner; keep
the same working-directory (orchestrator-standalone) and output piping to tail.
🧹 Nitpick comments (1)
package.json (1)

71-75: ⚡ Quick win

Remove stale dependenciesMeta.lefthook entry.

lefthook was removed as a dependency in this PR, but the dependenciesMeta block still references it. Yarn silently ignores dependenciesMeta entries for absent packages, so there's no functional impact — but leaving it is misleading dead configuration in a quality-tightening PR.

🧹 Proposed cleanup
-  "dependenciesMeta": {
-    "lefthook": {
-      "built": true
-    }
-  }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@package.json` around lines 71 - 75, Remove the stale dependenciesMeta entry
for "lefthook": open package.json, locate the "dependenciesMeta" object and
delete the nested "lefthook" block (the "lefthook": { "built": true } entry) so
the file no longer references a package that was removed; ensure JSON remains
valid (commas adjusted) after removal.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/test/setup.ts`:
- Around line 3-7: The comment in src/test/setup.ts misleads by saying tests can
opt-out with vi.spyOn(console, 'error') even though beforeEach replaces
console.error with fail; update the comment to explain that vi.spyOn alone will
call through to the replaced implementation and still throw, and that tests must
call vi.spyOn(console, 'error').mockImplementation(() => {}) (or similar
mockImplementation) to suppress the throw; reference the beforeEach replacement
of console.error/console.warn and the use of vi.spyOn(console,
'error')/vi.spyOn(console, 'warn') so readers know exactly how to opt out.

---

Outside diff comments:
In @.github/workflows/validate-orchestrator.yml:
- Around line 161-165: Update the "Run orchestrator unit tests" workflow step in
the orchestrator-standalone job: replace the failing command `yarn vitest run
2>&1 | tail -30` with a command that invokes the repo's Jest tests such as `yarn
test 2>&1 | tail -30` or `npx jest 2>&1 | tail -30` so the step uses the
existing test runner; keep the same working-directory (orchestrator-standalone)
and output piping to tail.

---

Nitpick comments:
In `@package.json`:
- Around line 71-75: Remove the stale dependenciesMeta entry for "lefthook":
open package.json, locate the "dependenciesMeta" object and delete the nested
"lefthook" block (the "lefthook": { "built": true } entry) so the file no longer
references a package that was removed; ensure JSON remains valid (commas
adjusted) after removal.
🪄 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: fb4026b4-9e50-4ad1-9319-722663a533b2

📥 Commits

Reviewing files that changed from the base of the PR and between 3fa95d0 and 9925f2b.

⛔ Files ignored due to path filters (2)
  • src/model/__snapshots__/versioning.test.ts.snap is excluded by !**/*.snap
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (5)
  • .github/workflows/validate-orchestrator.yml
  • package.json
  • src/jest.setup.ts
  • src/test/setup.ts
  • vitest.config.mts
💤 Files with no reviewable changes (1)
  • src/jest.setup.ts

Comment thread src/test/setup.ts
Comment on lines +3 to +7
// Fail tests when console.error / console.warn etc are called from
// production code under test. Mirrors the jest-fail-on-console behaviour
// the previous jest setup enforced. Tests can opt-out by replacing the
// method with vi.spyOn(console, 'error') for the duration of that test.
const original = {

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 | ⚡ Quick win

The opt-out comment is misleading — vi.spyOn alone still throws.

vi.spyOn wraps an existing method, and the original implementation still works by default. Because beforeEach replaces console.error with fail before the test runs, a bare vi.spyOn(console, 'error') will install a spy that calls through to fail, which still throws. The comment as written will cause confusing failures for any contributor who follows it literally.

.mockImplementation(() => {}) is required to suppress the throw:

📝 Proposed comment fix
-// Tests can opt-out by replacing the method with vi.spyOn(console, 'error') for the duration of that test.
+// Tests can opt-out via vi.spyOn(console, 'error').mockImplementation(() => {}) for the duration of that test.
📝 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
// Fail tests when console.error / console.warn etc are called from
// production code under test. Mirrors the jest-fail-on-console behaviour
// the previous jest setup enforced. Tests can opt-out by replacing the
// method with vi.spyOn(console, 'error') for the duration of that test.
const original = {
// Fail tests when console.error / console.warn etc are called from
// production code under test. Mirrors the jest-fail-on-console behaviour
// the previous jest setup enforced. Tests can opt-out via vi.spyOn(console, 'error').mockImplementation(() => {}) for the duration of that test.
const original = {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/test/setup.ts` around lines 3 - 7, The comment in src/test/setup.ts
misleads by saying tests can opt-out with vi.spyOn(console, 'error') even though
beforeEach replaces console.error with fail; update the comment to explain that
vi.spyOn alone will call through to the replaced implementation and still throw,
and that tests must call vi.spyOn(console, 'error').mockImplementation(() => {})
(or similar mockImplementation) to suppress the throw; reference the beforeEach
replacement of console.error/console.warn and the use of vi.spyOn(console,
'error')/vi.spyOn(console, 'warn') so readers know exactly how to opt out.

@frostebite
frostebite merged commit 16c5c20 into main May 6, 2026
64 of 65 checks passed
@frostebite
frostebite deleted the quality-tightening branch May 6, 2026 18:07
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.

2 participants