Skip to content

chore!: migrate to ESM, adopt node24 runtime, roll up open Dependabot PRs - #121

Merged
BrandonLWhite merged 2 commits into
mainfrom
chore/esm-node24-dependabot-rollup
Sep 1, 2026
Merged

chore!: migrate to ESM, adopt node24 runtime, roll up open Dependabot PRs#121
BrandonLWhite merged 2 commits into
mainfrom
chore/esm-node24-dependabot-rollup

Conversation

@BrandonLWhite

Copy link
Copy Markdown
Member

Summary

Closes out all three open Dependabot PRs in one change, fixes the CI failures that were blocking them, and moves the published action to the node24 runtime.

Dependabot PR Bump Status here
#108 @actions/core 2.0.2 → 3.0.1 ✅ applied (^3.0.1)
#114 @vercel/ncc 0.38.4 → 0.44.0 ✅ applied (^0.44.0, resolves 0.44.1)
#120 npm_and_yarn security group ✅ applied — brace-expansion 1.1.12→1.1.18 & 2.1.0→2.1.4, flatted 3.3.3→3.4.4, undici 5.29.0→6.28.0

Why those PRs were red

All three failed check-dist. Dependabot only edits package.json / package-lock.json; it never runs npm run prepare, so the checked-in dist/ bundle drifts from the sources. That check is working as designed — it just can't be satisfied by a bot.

#108 also failed units, and that one is not cosmetic. @actions/core 3.0.0 is ESM-only and its exports map declares only an import condition:

"exports": { ".": { "types": "./lib/core.d.ts", "import": "./lib/core.js" } }

With no require condition, require("@actions/core") fails on every Node version — Node 22/24's require(esm) support doesn't help, because resolution fails before module-format detection:

Error [ERR_PACKAGE_PATH_NOT_EXPORTED]: No "exports" main defined in .../@actions/core/package.json

So taking #108 requires converting this action from CommonJS to ESM. That pairs naturally with the requested node24 move, so both are here.

Two latent CI defects found while verifying this

Both were pre-existing on main and are fixed in this PR.

1. node-version-file: package.json never resolved. The manifest had no engines.node, so setup-node silently fell back to the runner's preinstalled Node. From the check-dist log on #120:

##[warning]Could not determine node version from .../package.json. Falling back
Resolved package.json as
node: v22.23.2

CI was building dist/ on Node 22 while action.yml declared node20 — the declared runtime was never exercised. Fixed by adding engines.node: "^24.0.0" (setup-node reads volta.nodedevEngines.runtimeengines.node, and accepts semver ranges).

2. check-dist could not see files the build newly adds. The comparison used git diff, which ignores untracked files. ncc 0.44 emits a new dist/package.json and renames sourcemap-register.jssourcemap-register.cjs. Without an untracked check, this very PR could have shipped a dist/index.js whose first line is import './sourcemap-register.cjs'; while that file was untracked — green CI, broken action. Now the step also fails on untracked files under dist/.

Changes

ESM migration

  • index.js / find-python-projects.js converted to import + named exports; "type": "module" added.
  • Test suite rewritten to mock with jest.unstable_mockModule. ESM module namespaces are frozen, so the previous jest.spyOn(core, "info") pattern now throws TypeError: Cannot assign to read only property 'info' of object '[object Module]'. Jest runs under --experimental-vm-modules.
  • All 12 tests and all 4 snapshots pass unchanged — the snapshots are the evidence that this refactor is behavior-neutral.

node24

  • action.yml: runs.using: node20node24.
  • engines.node: ^24.0.0, which also makes node-version-file: package.json work.
  • units job gains a setup-node step so unit tests run on the same pinned Node as check-dist (previously it used whatever the runner shipped).

npm run lint repaired — broken on main since the eslint 10 bump (#113), and invisible because no CI job runs lint:

Error: Cannot find module 'globals'
Require stack: - eslint.config.cjs

The flat config imported globals, @eslint/js and @eslint/eslintrc, none of which were ever declared as devDependencies; they used to be hoisted out of eslint's own tree, and eslint 10 no longer provides them. Now globals and @eslint/js are declared explicitly, and the legacy FlatCompat/eslintrc shim is replaced with js.configs.recommended. Per repo convention this fixes the linter rather than suppressing it.

Verification

Run locally on Node 24.19.0:

  • npm run all → lint clean, ncc build clean, 12/12 tests + 4/4 snapshots pass.
  • check-dist logic replayed locally against the committed tree → dist/ is byte-reproducible and in sync.
  • End-to-end: copied only the git-tracked files (no node_modules) and ran node dist/index.js with INPUT_ROOT-DIR=./test-fixtures/multi-project and a GITHUB_OUTPUT file. It emitted all three outputs and produced exactly the four matrix entries the Test: * jobs expect — sub-project-1, sub-project-2, project-5, sub-project-6 — with unchanged install/test commands.
  • node24 confirmed a valid runs.using value, and engines.node confirmed as a setup-node node-version-file source, against upstream docs/source rather than assumed.

Notes for the reviewer

  • Deliberately left out of scope: npm audit --omit=dev still reports one high-severity advisory in shipped code — picomatch <=2.3.1, reached via globby@11 → fast-glob → micromatch. There is no in-range fix; it needs a globby major bump (12+ is ESM-only, so this PR unblocks that) and that changes glob behavior, so it belongs in its own PR with its own testing. Remaining npm audit findings are dev-toolchain-only.
  • @vercel/ncc is pinned at ^0.44.0 to match Bump @vercel/ncc from 0.38.4 to 0.44.0 #114 exactly, even though 0.45.0 has since shipped. Dependabot will offer 0.45.0 separately.
  • The two workflow hardening changes (untracked-file check, units setup-node) are the only edits here that go beyond the literal dependency bumps. Both are load-bearing for this PR's correctness, but say the word and I'll split them out.
  • #120, #114 and #108 can be closed once this merges.

🤖 Generated with Claude Code

@BrandonLWhite BrandonLWhite added dependencies Pull requests that update a dependency file javascript Pull requests that update Javascript code github_actions Pull requests that update GitHub Actions code labels Sep 1, 2026
@BrandonLWhite

Copy link
Copy Markdown
Member Author

Follow-up commit 91a4a89: declared @jest/globals explicitly (the ESM tests import from it, but it was only reachable transitively through jest — the same undeclared-import problem that had broken npm run lint on main), and switched the test script to NODE_OPTIONS=--experimental-vm-modules NODE_NO_WARNINGS=1 jest to match pipx-install-action / python-test-matrix-generator-action and drop the ExperimentalWarning noise.

dist/ is unchanged by this commit. All 9 checks green.

… PRs

Summary:
=======
Three Dependabot PRs (#108, #114, #120) were all stuck on a red `check-dist`
because Dependabot only edits `package.json` / `package-lock.json` and never
rebuilds the checked-in `dist/` bundle. #108 was additionally red on `units`:
`@actions/core` 3.0.0 is ESM-only and its `exports` map declares no `require`
condition, so `require("@actions/core")` fails outright with
`ERR_PACKAGE_PATH_NOT_EXPORTED` on any Node version. Taking that bump therefore
required converting the action from CommonJS to ESM, which pairs naturally with
moving the published runtime to node24.

Two latent CI defects surfaced while verifying the above and are fixed here:

- `node-version-file: package.json` never resolved, because the manifest had no
  `engines.node`. setup-node logged "Could not determine node version ...
  Falling back" and silently built `dist/` on the runner's Node 22, so CI was
  not exercising the runtime the action declares.
- The `check-dist` comparison used `git diff`, which cannot see files the build
  newly adds. ncc 0.44 emits `dist/package.json` and renames
  `sourcemap-register.js` to `.cjs`; without the untracked-file check, shipping
  the action with a missing `sourcemap-register.cjs` would have passed CI and
  failed at runtime.

Actions:
=======
- Convert `index.js` and `find-python-projects.js` to ESM named exports and set
  `"type": "module"`.
- Rewrite the test suite to mock via `jest.unstable_mockModule`, since frozen
  ESM namespaces cannot be patched by `jest.spyOn`; run jest under
  `--experimental-vm-modules`. All 12 tests and 4 snapshots pass unchanged,
  confirming the migration is behavior-neutral.
- Bump `@actions/core` to ^3.0.1 (#108) and `@vercel/ncc` to ^0.44.0 (#114).
- Apply the npm_and_yarn security group (#120): brace-expansion 1.1.12 -> 1.1.18
  and 2.1.0 -> 2.1.4, flatted 3.3.3 -> 3.4.4, undici 5.29.0 -> 6.28.0.
- Set `runs.using` to node24 in `action.yml` and add `engines.node: ^24.0.0` so
  `node-version-file: package.json` resolves.
- Add setup-node to the `units` job so unit tests run on the same pinned Node.
- Harden the `check-dist` comparison to also fail on untracked files in `dist/`.
- Repair `npm run lint`, broken since the eslint 10 bump: the flat config
  imported `globals`, `@eslint/js` and `@eslint/eslintrc`, none of which were
  declared and which eslint 10 no longer provides transitively. Declare
  `globals` and `@eslint/js`, and drop the `FlatCompat`/eslintrc shim in favor
  of `js.configs.recommended`.
- Rebuild `dist/`; add `dist/package.json` and `dist/sourcemap-register.cjs`,
  remove the now-stale `dist/sourcemap-register.js`.

BREAKING CHANGE: the action now runs on the node24 runtime and its sources are
ESM. Consumers referencing the action by tag are unaffected, but GitHub Actions
runners must support `runs.using: node24`.

ai-generated: true
Summary:
=======
The ESM test suite imports `jest`, `describe`, `it`, `expect` and `beforeEach`
from `@jest/globals`, but that package was only reachable transitively through
`jest`. That is the same undeclared-import bug class that had already broken
`npm run lint` on main, so declare it explicitly before it bites.

Also aligns the test script with the sibling action repos
(pipx-install-action, python-test-matrix-generator-action), which additionally
silences the VM-modules ExperimentalWarning.

Actions:
=======
- Add `@jest/globals` ^30.4.2 to devDependencies.
- Change the `test` script to
  `NODE_OPTIONS=--experimental-vm-modules NODE_NO_WARNINGS=1 jest`.

ai-generated: true
@BrandonLWhite
BrandonLWhite force-pushed the chore/esm-node24-dependabot-rollup branch from 91a4a89 to 142713a Compare September 1, 2026 21:07
@BrandonLWhite

Copy link
Copy Markdown
Member Author

Rebased onto main (6d07cb7) to clear the conflicts introduced by #114 and #120 landing directly.

How the conflicts were resolved

  • package.json — only real conflict, and a trivial one: both sides had already set @vercel/ncc to ^0.44.0. Kept this branch's version, which is a superset.
  • dist/index.js, dist/index.js.map, package-lock.json — derived; regenerated from the merged manifest rather than hand-merged. npm install reported up to date and npm run prepare produced a byte-identical dist/, so the resolved tree is exactly what the build emits.
  • dist/sourcemap-register.js (modify/delete) — kept deleted. ncc emits .cjs for ESM output; git scored it a 100% rename, so the content main rebuilt is identical to the .cjs here.

Verified nothing main brought was dropped: diffed both lockfiles package-by-package — zero packages missing, zero downgrades. The #120 security targets are all still at their bumped versions (brace-expansion 1.1.18 / 2.1.4, flatted 3.4.4, undici 6.28.0).

Re-ran locally on Node 24.19.0: lint clean, 12/12 tests + 4/4 snapshots, dist/ in sync, and the end-to-end run from tracked-files-only still yields the same four matrix entries. All 9 checks green.

Since #114 and #120 are now merged, this PR's remaining dependency delta is just #108 (@actions/core 3.0.1) plus the ESM/node24 migration it requires.

@BrandonLWhite
BrandonLWhite merged commit c3de5f7 into main Sep 1, 2026
9 checks passed
@BrandonLWhite
BrandonLWhite deleted the chore/esm-node24-dependabot-rollup branch September 1, 2026 21:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file github_actions Pull requests that update GitHub Actions code javascript Pull requests that update Javascript code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant