Skip to content

chore: quality-tightening (yarn 1 -> 4, oxfmt + oxlint + tsc + vitest + husky + first-ever test) - #105

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

frostebite merged 2 commits into
mainfrom
quality-tightening

Conversation

@webbertakken

@webbertakken webbertakken commented May 5, 2026

Copy link
Copy Markdown
Member

Yarn 1 \u2192 4 migration plus the standard quality-tightening rollout. Repo had 0 tests before.

Migrations

  • Yarn 1 \u2192 4.14.1
  • prettier \u2192 oxfmt
  • eslint (with @typescript-eslint, github, jest, prettier, unicorn) \u2192 oxlint with eslint-plugin-unicorn
  • jest 26 + jest-circus + ts-jest + @types/jest \u2192 vitest 4 + vite 7 + @vitest/coverage-istanbul
  • husky 7 \u2192 husky 9 with scripts/ensure-husky.mjs self-heal + lint-staged
  • TypeScript 4.x \u2192 5; tsconfig target ES2022 + lib ES2022 + DOM
  • mise.toml: node 20.18.0, yarn 4.14.1, actionlint, shellcheck, gitleaks

Refactor + first test

  • The action did one thing: core.setFailed(deprecation message). Extracted DEPRECATION_MESSAGE + main() so the test can drive them directly (the entry point still auto-runs main() outside NODE_ENV='test').
  • New src/index.test.ts asserts:
    1. The deprecation message references the updated docs URL (https://game.ci/docs/github/activation).
    2. Calling main() forwards that exact message to core.setFailed.

Verified locally

  • yarn lint \u2014 0 errors, 0 warnings
  • yarn format:check \u2014 clean
  • yarn typecheck \u2014 clean
  • yarn test \u2014 2 / 2
  • yarn build \u2014 succeeds (ncc bundle 950kB)
  • actionlint \u2014 clean

Summary by CodeRabbit

  • Chores
    • The unity-request-activation-file action is now deprecated and will fail with a deprecation notice directing users to updated documentation
    • Modernized development tooling configuration and dependencies
    • Updated infrastructure for code quality and testing

… + husky + first-ever test)

Yarn 1 -> 4 migration plus the standard quality-tightening rollout.
Repo had 0 tests before.

- Drops yarn 1 lockfile + .yarnrc; adds .yarnrc.yml + mise.toml
  (node 20.18.0, yarn 4.14.1, actionlint, shellcheck, gitleaks).
- prettier -> oxfmt
- eslint (with @typescript-eslint, github, jest, prettier, unicorn)
  -> oxlint with eslint-plugin-unicorn
- jest 26 + jest-circus + ts-jest + @types/jest -> vitest 4 + vite 7
  + @vitest/coverage-istanbul (jest.config.js removed)
- new: tsgo --noEmit (alongside tsc fallback)
- husky 7 -> husky 9 with scripts/ensure-husky.mjs self-heal +
  lint-staged
- new: gitleaks, actionlint, shellcheck as mise-managed binaries
- TypeScript bumped 4.x -> 5; tsconfig target ES2022 + lib
  ES2022 + DOM, skipLibCheck on, types: [node]
- Added standard yarn 4 .gitignore entries

Refactor + first test:
- The action did one thing: call core.setFailed(deprecation message).
  Extracted DEPRECATION_MESSAGE + main() so the test can drive them
  directly (the entry point still auto-runs main() outside NODE_ENV
  ='test').
- New src/index.test.ts asserts:
  1. The deprecation message references the updated docs URL
     (https://game.ci/docs/github/activation).
  2. Calling main() forwards that exact message to core.setFailed.

Verified locally: lint 0/0, format clean, typecheck clean,
test 2/2, build succeeds, actionlint clean.
@coderabbitai

coderabbitai Bot commented May 5, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@webbertakken has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 48 minutes and 27 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 37034956-5581-4a4e-8df2-10d78218e9a9

📥 Commits

Reviewing files that changed from the base of the PR and between 6a6cd5a and a97b021.

📒 Files selected for processing (1)
  • .github/workflows/main.yml
📝 Walkthrough

Walkthrough

This PR performs a comprehensive tooling migration: replacing ESLint with Oxlint, Prettier with Oxfmt, and Jest with Vitest. It also marks the main action as deprecated, updates Husky to v9, and modernizes build configuration with new dependency management.

Changes

Linting and Formatting Tooling Migration

Layer / File(s) Summary
Remove Legacy Linting Config
.eslintrc.json, .eslintignore
ESLint configuration and ignore file deleted entirely, removing plugins, extends, parser, and rule customizations.
Add Oxlint Config
.oxlintrc.json
New Oxlint configuration defines plugins (TypeScript, Vitest, Unicorn, OXC), severity categories, comprehensive rule set with test file overrides, environment settings for Node/ES2024 with Vitest globals, and ignore patterns.
Remove Legacy Formatting Config
.prettierrc.json, .prettierignore
Prettier configuration file deleted and node_modules and dist ignore patterns removed.
Add Oxfmt Config
.oxfmtrc.json
New Oxfmt configuration specifies formatting preferences (semi, single quotes, trailing commas, print width 100) and ignore patterns for build/test directories.
Update Linting Scripts
package.json
Added lint and format scripts using Oxlint and Oxfmt; added lint-staged rules for code formatting on commit.

Testing Framework Migration

Layer / File(s) Summary
Remove Jest Config
jest.config.js
Jest configuration file deleted entirely.
Add Vitest Config
vitest.config.mts
New Vitest configuration specifies Node environment, globals enabled, test file patterns, and Istanbul coverage with text/HTML/LCOV reporters.
Update Test Scripts & TypeScript
package.json, tsconfig.json
Added test, test:watch, and coverage npm scripts using Vitest; expanded TypeScript target to es2022, added DOM lib, moduleResolution, skipLibCheck, and file inclusion/exclusion patterns.
Add Vitest Test File
src/index.test.ts
New test validates deprecation message content and behavior of the exported main function.

Source Code Deprecation

Layer / File(s) Summary
Core Deprecation Logic
src/index.ts
Added DEPRECATION_MESSAGE constant and exported main() function that emits deprecation via core.setFailed; conditional execution skips when NODE_ENV is "test".
Deprecation Tests
src/index.test.ts
Validates that DEPRECATION_MESSAGE contains required deprecation text and updated docs URL; verifies main function triggers setFailed with the message.

Build Infrastructure and Development Dependencies

Layer / File(s) Summary
Husky Git Hooks Setup
.husky/pre-commit, .husky/.gitignore, scripts/ensure-husky.mjs
Rewrote pre-commit hook to use minimal POSIX shell and simplified commands (lint-staged, typecheck, optional gitleaks); added self-healing installer script that validates and reinstalls hooks on demand.
Yarn and Tool Versioning
.yarnrc, .yarnrc.yml, mise.toml, .gitignore
Removed old Yarn audit settings; added .yarnrc.yml with Yarn 4 (Berry) configuration (approvedGitRepositories, nodeLinker, cache/hardening settings); added .gitignore ignore block for Yarn 4; added mise.toml with pinned versions for Node 20.18.0, Yarn 4.14.1, and latest linting/security tools.
Package Manifest & Dependencies
package.json
Updated scripts for new tooling (lint, format, typecheck, setup:hooks, prepare for Husky v9); migrated devDependencies from Jest/ESLint/Prettier to Vitest/Oxlint/Oxfmt; added lint-staged configuration; set packageManager to Yarn 4.14.1.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 A rabbit hops through tooling migration anew,
Oxlint and Oxfmt paint the code bright true,
Vitest races faster where Jest once stood,
Husky guards the pre-commit—as good hooks should,
A deprecation message bids the old adieu! 🎭

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The PR title accurately summarizes the main change: a comprehensive quality-tightening migration including Yarn 1→4, Prettier→oxfmt, ESLint→oxlint, Jest→Vitest, Husky 7→9, and the addition of the first-ever test.
Description check ✅ Passed The PR description comprehensively covers all changes with clear sections (Migrations, Refactor + first test, Verified locally) and includes detailed verification steps, but the checklist template is not followed.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch quality-tightening

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.

@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: 4

🧹 Nitpick comments (5)
vitest.config.mts (1)

12-12: ⚡ Quick win

Excluding src/index.ts from coverage hides the very exports you just added tests for.

Per the PR description, DEPRECATION_MESSAGE and main() were extracted from src/index.ts specifically so src/index.test.ts can exercise them. Excluding the entire file from coverage makes those tests invisible in coverage reports and removes the signal for future regressions.

If the goal was to skip the auto-run guard line, prefer an inline ignore (e.g. /* v8 ignore next */ — oxfmt-friendly equivalent for istanbul: /* istanbul ignore next */) over a file-level exclude.

♻️ Suggested change
       include: ['src/**/*.ts'],
-      exclude: ['src/**/*.test.ts', 'src/index.ts'],
+      exclude: ['src/**/*.test.ts'],

Then in src/index.ts, around the auto-run block:

/* istanbul ignore next -- entry-point auto-run, not exercised by unit tests */
if (process.env.NODE_ENV !== 'test') {
  main();
}
🤖 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 `@vitest.config.mts` at line 12, Remove src/index.ts from the vitest coverage
exclude list so tests can cover DEPRECATION_MESSAGE and main(); then update
src/index.ts to keep the auto-run guard but mark that specific block to be
ignored by coverage (use an istanbul/coverage comment such as "/* istanbul
ignore next */" immediately before the if that calls main()) so the rest of the
file remains visible to tests while the runtime-only auto-run line is excluded
from coverage.
mise.toml (1)

4-6: ⚡ Quick win

Pin actionlint, shellcheck, and gitleaks to explicit versions.

The whole point of mise.toml is reproducible toolchains — using "latest" for these three reintroduces version drift between contributors and CI, which can cause sudden lint/secret-scan failures unrelated to a given PR. Consider pinning them like node and yarn.

♻️ Suggested change
 [tools]
 node = "20.18.0"
 yarn = "4.14.1"
-actionlint = "latest"
-shellcheck = "latest"
-gitleaks = "latest"
+actionlint = "1.7.7"
+shellcheck = "0.10.0"
+gitleaks = "8.21.2"

(Use whatever current versions you've validated locally.)

🤖 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 currently pins actionlint,
shellcheck, and gitleaks to the string "latest", which allows version drift;
update the three entries (actionlint, shellcheck, gitleaks) to explicit, tested
version strings (e.g., "vX.Y.Z" or "X.Y.Z") used by your team/CI so the
toolchain is reproducible, verify those versions locally/CI, and commit the
updated mise.toml.
tsconfig.json (1)

4-4: 💤 Low value

Is DOM lib intentional for a Node action?

This action runs in Node (per mise.toml and vitest env: 'node'), so including DOM in lib lets references to document, window, fetch (DOM Fetch typings), etc. typecheck successfully even though they would fail or behave differently at runtime in Node 20. If nothing in src/ actually needs DOM types, dropping it tightens the type safety net.

♻️ Suggested change
-    "lib": ["es2022", "DOM"],
+    "lib": ["es2022"],
🤖 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 `@tsconfig.json` at line 4, The tsconfig "lib" entry currently includes "DOM",
which enables browser globals (document/window/fetch) that are inappropriate for
a Node-only action; open the tsconfig.json and remove "DOM" from the "lib" array
(leave "es2022" or other Node-target libs) or replace it with Node-specific libs
if needed, ensuring the "lib" property no longer types browser globals so
Node-only code (check src/) typechecks correctly; only re-add DOM if a specific
file truly requires browser typings.
package.json (2)

12-12: ⚡ Quick win

test script reinstalls Husky hooks — surprising side effect in CI.

yarn test shouldn't have a side effect of writing/repairing .husky/. In CI the git hooks aren't useful (no commits happen), and on read-only environments or sandboxed runners ensure-husky.mjs may fail or leave artifacts. The prepare script (line 21) and setup:hooks (line 20) already cover developer-machine bootstrap.

♻️ Suggested change
-    "test": "node scripts/ensure-husky.mjs && vitest run",
+    "test": "vitest run",

Keep setup:hooks for explicit local setup, and rely on prepare (run by Yarn after install) for normal dev flow.

🤖 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` at line 12, The "test" npm script currently runs "node
scripts/ensure-husky.mjs && vitest run", which causes Husky hooks to be
installed as a side effect; remove the call to scripts/ensure-husky.mjs so the
"test" script only runs the test runner (e.g., change "test" to run just "vitest
run"), and keep Husky setup to the existing "prepare" and "setup:hooks" scripts
for developer machines; update any CI documentation or pipelines that invoke
"yarn test" to expect no repository modifications.

36-36: 💤 Low value

Pin @typescript/native-preview exactly or document the tracking decision.

^7.0.0-dev.20260505.1 is a prerelease range matching frequent date-stamped dev versions (the package publishes many times per day). While caret semver on prerelease versions acts roughly like an exact pin in practice, resolution semantics around -dev.YYYYMMDD.N tags are easy to misunderstand, and the package explicitly states feature parity is incomplete (declaration emit, API, watch mode still in progress) with behavioral changes expected before the TypeScript 7 release candidate. Since typecheck:tsgo is a secondary check and the primary typecheck uses stable tsc, this fallback strategy is sensible—but either remove the ^ to pin exactly or add a brief comment noting this intentionally tracks a preview build.

🤖 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` at line 36, The package.json entry for
`@typescript/native-preview` currently uses a caret prerelease range
("@typescript/native-preview": "^7.0.0-dev.20260505.1") which can be misleading
given frequent dev publishes; either remove the leading caret to pin the
dependency exactly (change the value to "7.0.0-dev.20260505.1") or add an inline
comment near the "@typescript/native-preview" declaration explaining that this
intentionally tracks a preview/dev build and is only used as a secondary
fallback for the typecheck:tsgo script while primary typechecking uses stable
tsc.
🤖 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 @.husky/pre-commit:
- Around line 1-7: Add "set -e" immediately after the shebang so the hook aborts
on any failure; specifically modify the pre-commit script containing the
commands yarn lint-staged, yarn typecheck and the gitleaks protect --staged
block so that if yarn lint-staged (or any subsequent command) fails the script
exits immediately instead of continuing to later commands.

In @.yarnrc.yml:
- Around line 1-2: Remove or restrict the overly permissive
approvedGitRepositories entry in .yarnrc.yml: either delete the
approvedGitRepositories: ['**'] key to revert to Yarn's secure default
deny-all-for-git behavior, or replace it with a specific allowlist such as
['https://github.com/yourorg/**'] to only permit trusted repos; also confirm
(and optionally document via a comment) the intentionality of
enableHardenedMode: false—leave it as-is only if you have a justified reason,
otherwise remove or set it to true so Yarn's hardened checks are enforced.

In `@package.json`:
- Around line 39-40: Remove the dead devDependencies "eslint" and
"eslint-plugin-unicorn" from package.json's devDependencies, ensuring any
references to those package names are deleted; after editing package.json run
the package manager install (npm install or yarn install) to update the lockfile
and CI caches so the removed packages are no longer installed or audited.

In `@scripts/ensure-husky.mjs`:
- Around line 25-55: The script should short-circuit when not inside a Git
repository to avoid attempting Husky installation; add an explicit git presence
check (e.g., run execSync('git rev-parse --is-inside-work-tree') or equivalent
and treat failures as "not a git repo") before the existing call that sets
configuredHooksPath, and if that check fails exit 0. Update the logic around
configuredHooksPath, existsSync(sentinelHook) and huskyBin so the new non-git
guard returns early (process.exit(0)) before trying execSync(`node ${huskyBin}`)
or any husky actions; keep existing error handling for real install failures
unchanged.

---

Nitpick comments:
In `@mise.toml`:
- Around line 4-6: The mise.toml currently pins actionlint, shellcheck, and
gitleaks to the string "latest", which allows version drift; update the three
entries (actionlint, shellcheck, gitleaks) to explicit, tested version strings
(e.g., "vX.Y.Z" or "X.Y.Z") used by your team/CI so the toolchain is
reproducible, verify those versions locally/CI, and commit the updated
mise.toml.

In `@package.json`:
- Line 12: The "test" npm script currently runs "node scripts/ensure-husky.mjs
&& vitest run", which causes Husky hooks to be installed as a side effect;
remove the call to scripts/ensure-husky.mjs so the "test" script only runs the
test runner (e.g., change "test" to run just "vitest run"), and keep Husky setup
to the existing "prepare" and "setup:hooks" scripts for developer machines;
update any CI documentation or pipelines that invoke "yarn test" to expect no
repository modifications.
- Line 36: The package.json entry for `@typescript/native-preview` currently uses
a caret prerelease range ("@typescript/native-preview": "^7.0.0-dev.20260505.1")
which can be misleading given frequent dev publishes; either remove the leading
caret to pin the dependency exactly (change the value to "7.0.0-dev.20260505.1")
or add an inline comment near the "@typescript/native-preview" declaration
explaining that this intentionally tracks a preview/dev build and is only used
as a secondary fallback for the typecheck:tsgo script while primary typechecking
uses stable tsc.

In `@tsconfig.json`:
- Line 4: The tsconfig "lib" entry currently includes "DOM", which enables
browser globals (document/window/fetch) that are inappropriate for a Node-only
action; open the tsconfig.json and remove "DOM" from the "lib" array (leave
"es2022" or other Node-target libs) or replace it with Node-specific libs if
needed, ensuring the "lib" property no longer types browser globals so Node-only
code (check src/) typechecks correctly; only re-add DOM if a specific file truly
requires browser typings.

In `@vitest.config.mts`:
- Line 12: Remove src/index.ts from the vitest coverage exclude list so tests
can cover DEPRECATION_MESSAGE and main(); then update src/index.ts to keep the
auto-run guard but mark that specific block to be ignored by coverage (use an
istanbul/coverage comment such as "/* istanbul ignore next */" immediately
before the if that calls main()) so the rest of the file remains visible to
tests while the runtime-only auto-run line is excluded from coverage.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 576e3bf5-0ccb-4a82-980e-a018d30c5ace

📥 Commits

Reviewing files that changed from the base of the PR and between ada8622 and 6a6cd5a.

⛔ Files ignored due to path filters (4)
  • dist/index.js is excluded by !**/dist/**
  • dist/index.js.map is excluded by !**/dist/**, !**/*.map
  • dist/licenses.txt is excluded by !**/dist/**
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (19)
  • .eslintignore
  • .eslintrc.json
  • .gitignore
  • .husky/.gitignore
  • .husky/pre-commit
  • .oxfmtrc.json
  • .oxlintrc.json
  • .prettierignore
  • .prettierrc.json
  • .yarnrc
  • .yarnrc.yml
  • jest.config.js
  • mise.toml
  • package.json
  • scripts/ensure-husky.mjs
  • src/index.test.ts
  • src/index.ts
  • tsconfig.json
  • vitest.config.mts
💤 Files with no reviewable changes (7)
  • .eslintignore
  • .prettierignore
  • .husky/.gitignore
  • jest.config.js
  • .prettierrc.json
  • .yarnrc
  • .eslintrc.json

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

yarn build
git add dist
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

Pre-commit hook does not fail on lint-staged errors.

Without set -e, the script continues past failures and the hook's exit status becomes that of the last command executed. Concretely:

  • If yarn lint-staged fails (oxlint/oxfmt errors) but yarn typecheck passes, the commit goes through with lint errors.
  • If yarn typecheck fails, gitleaks is never reached, and only typecheck's exit code blocks the commit (acceptable, but inconsistent).

Add set -e so any failing step aborts the hook.

🐛 Proposed 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
🤖 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, Add "set -e" immediately after the
shebang so the hook aborts on any failure; specifically modify the pre-commit
script containing the commands yarn lint-staged, yarn typecheck and the gitleaks
protect --staged block so that if yarn lint-staged (or any subsequent command)
fails the script exits immediately instead of continuing to later commands.

Comment thread .yarnrc.yml
Comment on lines +1 to +2
approvedGitRepositories:
- '**'

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

🧩 Analysis chain

🌐 Web query:

Yarn 4 enableHardenedMode default behavior and approvedGitRepositories security implications

💡 Result:

In Yarn 4 (Berry), enableHardenedMode is false by default, but automatically enabled (true) when Yarn detects it is running in a public GitHub pull request.[1][2][3] This mode activates --check-resolutions and --refresh-lockfile during installs to validate lockfile contents against registry metadata, protecting against lockfile poisoning attacks at the cost of slower installs.[1][4][3] approvedGitRepositories defaults to an empty array [].[5][6] When empty, Yarn blocks all git dependencies (via git: protocol), refusing to fetch any repository URLs as they won't match any patterns; users must explicitly configure glob patterns (e.g., https://github.com/yarnpkg/*) to allow specific repos.[5][7][8] This prevents arbitrary code execution from untrusted git sources, a security feature added in early 2026.[5] Security implications: Hardened mode mitigates supply-chain attacks in untrusted PRs.[1][4] approvedGitRepositories enforces a strict allowlist for git deps, blocking malicious or unexpected clones by default—critical since git repos are packed and executed.[5][8] Without configuration, projects using git dependencies will fail install; CI like GitHub Actions may need updates.[7] Explicitly set enableHardenedMode: false to disable in trusted envs, but avoid in CI.[1][2]

Citations:


🏁 Script executed:

cat -n .yarnrc.yml

Repository: game-ci/unity-request-activation-file

Length of output: 289


Reconsider the permissive approvedGitRepositories setting.

approvedGitRepositories: ['**'] (lines 1–2) overrides Yarn's secure default (an empty allowlist that blocks all git dependencies) to permit any git-sourced dependency without vetting. For a public GitHub Action, this increases supply-chain risk if a transitive dependency introduces a malicious git source. Either remove this key entirely to enforce the default allowlist, or restrict it to specific trusted repositories (e.g., ['https://github.com/yourorg/**']).

enableHardenedMode: false (line 8) is a lower-priority concern for this action: Yarn 4 automatically enables hardened mode when running in public GitHub PR contexts, so the lockfile/registry tampering checks are active where they matter most. That said, confirm these settings were intentional choices rather than defaults; a comment in the file documenting the rationale would help future maintainers.

🤖 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 @.yarnrc.yml around lines 1 - 2, Remove or restrict the overly permissive
approvedGitRepositories entry in .yarnrc.yml: either delete the
approvedGitRepositories: ['**'] key to revert to Yarn's secure default
deny-all-for-git behavior, or replace it with a specific allowlist such as
['https://github.com/yourorg/**'] to only permit trusted repos; also confirm
(and optionally document via a comment) the intentionality of
enableHardenedMode: false—leave it as-is only if you have a justified reason,
otherwise remove or set it to true so Yarn's hardened checks are enforced.

Comment thread package.json
Comment on lines +39 to +40
"eslint": "^10.3.0",
"eslint-plugin-unicorn": "^64.0.0",

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm there are no remaining ESLint configs or invocations.
fd -H -t f -e json -e js -e cjs -e mjs -e yaml -e yml '\.eslintrc' || true
rg -nP '\b(eslint|eslint-plugin-unicorn)\b' --hidden -g '!yarn.lock' -g '!**/node_modules/**'

Repository: game-ci/unity-request-activation-file

Length of output: 50395


Remove dead eslint and eslint-plugin-unicorn devDeps from package.json.

The PR explicitly migrates from ESLint to oxlint. No .eslintrc configs or ESLint invocations remain in the codebase. Keeping these dependencies inflates install size, lockfile churn, and security audit surface.

Suggested removal
     "@vitest/coverage-istanbul": "^4.1.5",
-    "eslint": "^10.3.0",
-    "eslint-plugin-unicorn": "^64.0.0",
     "husky": "9",
🤖 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 39 - 40, Remove the dead devDependencies "eslint"
and "eslint-plugin-unicorn" from package.json's devDependencies, ensuring any
references to those package names are deleted; after editing package.json run
the package manager install (npm install or yarn install) to update the lockfile
and CI caches so the removed packages are no longer installed or audited.

Comment thread scripts/ensure-husky.mjs
Comment on lines +25 to +55
try {
configuredHooksPath = execSync('git config --get core.hooksPath', {
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'ignore'],
}).trim();
} catch {
// not a git repo or config unset — fall through and try to install
}

if (configuredHooksPath === expectedHooksPath && existsSync(sentinelHook)) {
process.exit(0);
}

if (!huskyBin) {
// husky not installed yet (yarn install hasn't run) — silent no-op
process.exit(0);
}

console.log('· installing git hooks (husky self-heal)…');
try {
execSync(`node ${huskyBin}`, { stdio: 'inherit' });
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
console.error(
`\n❌ husky install failed: ${message}\n\n` +
` git pre-commit hooks are NOT installed; commits will skip lint/format/tests.\n` +
` Fix the underlying error above, then run \`yarn setup:hooks\` to retry.\n` +
` To bypass this guard temporarily (NOT recommended): HUSKY=0 yarn <cmd>.\n`,
);
process.exit(1);
}

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify the current script lacks an explicit git worktree guard and can proceed to husky install paths.
set -euo pipefail

echo "Check for existing early worktree guard:"
rg -n "rev-parse --is-inside-work-tree|is-inside-work-tree" scripts/ensure-husky.mjs || true

echo
echo "Show control-flow region around git config + install attempt:"
nl -ba scripts/ensure-husky.mjs | sed -n '17,60p'

Repository: game-ci/unity-request-activation-file

Length of output: 247


🏁 Script executed:

# Check if file exists and determine its size
wc -l scripts/ensure-husky.mjs

echo "---"

# Show lines 17-60 using available tools
sed -n '17,60p' scripts/ensure-husky.mjs | cat -n

Repository: game-ci/unity-request-activation-file

Length of output: 1734


Add an explicit non-git early-exit guard before attempting Husky install.

The code currently catches failures from git config --get core.hooksPath but cannot distinguish between "config unset" and "not a git repository". In non-git contexts, the code proceeds to attempt a Husky install which will fail hard with process.exit(1), unnecessarily blocking workflows.

Suggested fix
 import { execSync } from 'node:child_process';
 import { existsSync } from 'node:fs';

 if (process.env.CI || process.env.HUSKY === '0') process.exit(0);
 
+try {
+  execSync('git rev-parse --is-inside-work-tree', {
+    stdio: ['ignore', 'ignore', 'ignore'],
+  });
+} catch {
+  // Not a git repository; hooks are not applicable.
+  process.exit(0);
+}
+
 const expectedHooksPath = '.husky/_';
 const sentinelHook = '.husky/_/pre-commit';
🤖 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 `@scripts/ensure-husky.mjs` around lines 25 - 55, The script should
short-circuit when not inside a Git repository to avoid attempting Husky
installation; add an explicit git presence check (e.g., run execSync('git
rev-parse --is-inside-work-tree') or equivalent and treat failures as "not a git
repo") before the existing call that sets configuredHooksPath, and if that check
fails exit 0. Update the logic around configuredHooksPath,
existsSync(sentinelHook) and huskyBin so the new non-git guard returns early
(process.exit(0)) before trying execSync(`node ${huskyBin}`) or any husky
actions; keep existing error handling for real install failures unchanged.

The previous workflow used the runner's global yarn 1.22 and ran
'yarn lint' (the old combined prettier+eslint script). It failed in
CI because the package.json has 'packageManager: yarn@4.14.1' and
yarn 1 refuses to run when corepack is required.

Aligned with the standard ci.yml shape used across the rest of the
rollout:
- Read node version from mise.toml.
- corepack enable + corepack install before any yarn invocation.
- Cache node_modules + yarn cache folder + install-state.
- Format + Lint + Typecheck + Test + Build steps.
@frostebite
frostebite merged commit d781242 into main May 6, 2026
6 checks passed
@frostebite
frostebite deleted the quality-tightening branch May 6, 2026 18:34
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