Skip to content

[No QA] Migrate GitHub Actions and scripts tests from Jest to bun:test - #98402

Draft
roryabraham wants to merge 16 commits into
mainfrom
rory/bun-test-migration-gh-scripts
Draft

[No QA] Migrate GitHub Actions and scripts tests from Jest to bun:test#98402
roryabraham wants to merge 16 commits into
mainfrom
rory/bun-test-migration-gh-scripts

Conversation

@roryabraham

@roryabraham roryabraham commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Explanation of Change

This is PR 2 of 4 from the bottom-up ncc → esbuild migration plan. PR 1 (Migrate scripts from ts-node to Bun) is already merged; PR 1 and PR 2 are independent, and PR 3 (upgrading @actions/* and @octokit/* to their ESM-only majors) needs both.

It migrates the tests covering our build and deploy tooling — .github/actions/, .github/libs/, .github/scripts/ and scripts/ — from Jest to bun:test. That is every test whose import graph reaches @actions/* or @octokit/* bar one (see below), which is the bar that matters: those are exactly the tests that would otherwise need hand-built CJS shims in PR 3.

Why

@actions/core and @actions/github are ESM-only from the versions PR 3 upgrades to. Jest resolves them through Babel's CommonJS interop, so keeping these tests on Jest would mean building and maintaining hand-written CJS shims for every @actions/* and @octokit/* package they touch (which is what the original all-in-one PR ended up doing, then had to undo). Bun imports them natively, so the shims are never needed.

What changed:

  • Moved 22 test files into a new tests/tooling/ directory and renamed them to the *.test.ts suffix Bun's runner discovers. This is the main structural decision in the PR: every config that has to treat these files differently — jest.config.js, tsconfig.json, ESLint, knip, the npm script, the CI path filters — now uses a single glob instead of a 19-entry list that has to be updated by hand each time a test is added. The first commit is a pure move so the content diff is readable on its own.
  • Added npm run test:tooling (TZ=utc bun test --isolate --preload ./tests/tooling/setup.ts ./tests/tooling). --isolate gives each file a fresh module registry and a fresh process.env, so module-level state and mock.module() calls can't leak between files. That removed the need for the cross-file cleanup hooks the earlier attempt at this needed.
  • Ported the 22 files. Mechanically: import the test globals from bun:test, drop the @jest-environment node docblocks, swap Jest's two-parameter mock generics for Bun's single function-type form. The differences that needed real work:
    • Bun resolves @actions/* as real ESM, whose namespace exports are read-only live bindings, so core.getInput = mock no longer works — these use jest.spyOn instead. That also drops these tests' dependency on @src/types/utils/asMutable.
    • Bun has no jest.mock(path) automock, so fs, fs/promises and child_process are replaced with explicit mock.module() calls, and auto-mocked @actions/core becomes per-function spies.
    • Bun has no advanceTimersByTimeAsync, so DeployChecklistUtils' retry tests get a small local helper that alternates between yielding to the microtask queue and advancing the clock by the exact backoff schedule the code under test declares.
  • Dropped jest-when and @types/jest-when. Its only consumer was postOrReplaceComment, and it depends on Jest's internal expect-matcher state. Its per-argument stubbing is replaced with a plain inputs record per test that throws on an undeclared input — stricter than what it replaced, which silently returned undefined.
  • Fixed a latent bug this surfaced: markPullRequestsAsDeployed.ts was the only action still ending in module.exports = run while using ESM import at the top. Babel rewrote the whole file to CommonJS for Jest so the mix went unnoticed; Bun loads it as real ESM and refuses it outright. Changed to export default run like every other action. The rebuilt bundle changes only the three corresponding lines and its require.main === require.cache[...] entry guard — the only thing that actually invokes the action — is unaffected.
  • Restored deploy gating. Moving these files out of tests/unit/ took them out of test.yml, which preDeploy.yml calls on every merge to main — so the tests covering preDeploy's own machinery (isDeployChecklistLocked, the deploy checklist, markPullRequestsAsDeployed) stopped gating the deploy they guard. bunTests.yml now has a workflow_call trigger and runs in preDeploy alongside typecheck/lint/test. That also closes the same pre-existing gap for the server/ Bun tests, which have never gated main. It's split into two jobs so a server-test failure doesn't hide the tooling results.
  • Added tests/tooling/README.md (how to run the suite and a single file, what belongs in the directory, the bun:test/Jest API gaps), and updated README.md, tests/README.md, CLAUDE.md and bunfig.toml, all of which described Jest as the only unit-test runner.

The one test that couldn't move, and why

tests/unit/generateTranslationsTest.ts stays on Jest. It runs fine under bun:test — I migrated it and got 40/40 — but it cannot be type-checked there, and the reason is a constraint worth knowing about before anyone else tries:

tests/tooling/ type-checks with @types/bun, whose ambient JSX declarations conflict with the app's React and react-native types. scripts/generateTranslations.ts imports src/languages/en, so the test's type graph pulls in ~3,000 app files, and checking those with bun types instead of jest types produces thousands of errors the root project doesn't report. I confirmed it both ways: with main's tsconfig.json there are zero errors in src/, and swapping only the types array reproduces them.

The test side can't narrow that graph, because the import comes from the script. Moving it needs the pure logic extracted from the script, or a separate type-check strategy — either way its own PR. It still has to move before @actions/* goes ESM-only, so PR 3 is not fully unblocked until it does. The constraint and the reasoning are recorded in tests/tooling/README.md so this isn't rediscovered from scratch.

ChatGPTTranslator hit the same wall for a reason that was fixable: it imported Locale from @src/types/onyx/Locale, which re-exports the whole @src/CONST barrel. It now uses TranslationTargetLocale from @src/CONST/LOCALES, which is self-contained and is the type the API it exercises actually takes.

Also deliberately left alone: test:bun (the server/ suite) is named on a different axis from test:tooling and is really test:server, but renaming it is unrelated churn.

Test counts and timing

Identical before and after: 22 files, 276 tests in tests/tooling, all passing. Verified per group against the Jest baseline — the 19 originally-planned files ran 253 tests under Jest and run 253 here; the three added later ran 23 and run 23.

End-to-end the two runners are a wash, but that's because the suite is dominated by tests that wait on things neither runner controls. Splitting those out (macOS, M-series, 5 runs each, Jest with a warm .jest-cache, same test counts on both sides):

Group Files Tests Jest bun:test Speedup
Everything except the two below 17 242 3.65s 0.63s 5.8×
awaitStagingDeploys — polls on a real timer 1 2 6.54s 5.11s 1.3×
CIGitLogic — drives real git/npm subprocesses 1 9 56.3s 51.9s 1.1×
Whole suite 19 253 58.9s 58.1s 1.0×
One file (GithubUtils), the inner dev loop 1 42 1.35s 0.06s 23×

So: ~6× on the part that is actually running test code, and no meaningful change on the ~52s of real subprocess work that dominates the total.

The CPU numbers are the more striking part. For that 17-file group Jest burns 37.7s of CPU across ~10 workers to produce 3.65s of wall clock; Bun uses 0.61s of CPU in a single process — roughly 60× less machine time for the same 242 tests. That's what actually costs money on a shared CI runner, and it's why the single-file case collapses from 1.35s to 60ms: there's no worker pool to spin up and no Babel transform to cache.

Two caveats worth stating: Jest parallelises across files and bun test --isolate is sequential in one process, so Jest is being measured at its best here; and Jest's cold-cache numbers are worse (4.44s vs 1.35s for the single file), while Bun has no cache to warm.

Fixed Issues

$
PROPOSAL:

Tests

  1. Run npm install in a clean checkout.
  2. Run npm run test:tooling and confirm it reports 253 pass, 0 fail across 19 files.
  3. Run npm test -- tests/unit tests/actions and confirm the Jest suite still passes and no longer collects the 22 migrated files (npx jest --listTests | grep -c tests/tooling prints 0).
  4. Run npm run typecheck and confirm the new tests/tooling/tsconfig.json project passes along with the other three.
  5. Run npm run lint and npm run knip and confirm both are clean — in particular that jest-when is no longer reported as an unused dependency and tests/tooling/setup.ts is not reported as an unused file.
  6. Run ./.github/scripts/verifyActions.sh and confirm it reports "Github Actions are up to date!" — i.e. the rebuilt markPullRequestsAsDeployed bundle matches its source.
  7. Run node .github/actions/javascript/markPullRequestsAsDeployed/index.js with no inputs set and confirm it fails with Error: Input required and not supplied: PR_LIST (the entry guard still fires) rather than doing nothing.
  8. Run a single file the way tests/tooling/README.md documents — TZ=utc bun test --isolate --preload ./tests/tooling/setup.ts ./tests/tooling/GithubUtils.test.ts — and confirm it passes on its own.
  9. Confirm the failure path: temporarily change one of the expectedBackoffsMs values in tests/tooling/DeployChecklistUtils.test.ts from 2000 to 1999, re-run, and confirm the test fails with "did its retry schedule change?" rather than passing or hanging.

This PR also exercises several of the bundled actions on itself before merge, at least isAuthorizedContributor and verifySignedCommits, and Validate GitHub Actions runs the bundle build in CI.

  • Verify that no errors appear in the JS console

Offline tests

N/A - this PR only changes test tooling and CI configuration, not app code.

QA Steps

N/A - this PR only changes internal test/CI tooling; there is no end-user-facing or staging/production behavior to QA.

  • Verify that no errors appear in the JS console

PR Author Checklist

  • I linked the correct issue in the ### Fixed Issues section above
  • I wrote clear testing steps that cover the changes made in this PR
    • I added steps for local testing in the Tests section
    • I added steps for the expected offline behavior in the Offline steps section
    • I added steps for Staging and/or Production testing in the QA steps section
    • I added steps to cover failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
    • I tested this PR with a High Traffic account against the staging or production API to ensure there are no regressions (e.g. long loading states that impact usability).
  • I included screenshots or videos for tests on all platforms
  • I ran the tests on all platforms & verified they passed on:
    • Android: Native
    • Android: mWeb Chrome
    • iOS: Native
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
  • I verified there are no console errors (if there's a console error not related to the PR, report it or open an issue for it to be fixed)
  • I followed proper code patterns (see Reviewing the code)
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is either coming verbatim from figma or has been approved by marketing (in order to get marketing approval, ask the Bug Zero team member to add the Waiting for copy label to the issue)
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I followed the guidelines as stated in the Review Guidelines
  • I tested other components that can be impacted by my changes (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar are working as expected)
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))
  • If new assets were added or existing ones were modified, I verified that:
    • The assets are optimized and compressed (for SVG files, run npm run compress-svg)
    • The assets load correctly across all supported platforms.
  • If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account.
  • If the PR modifies the UI (e.g. new buttons, new UI components, changing the padding/spacing/sizing, moving components, etc) or modifies the form input styles:
    • I verified that all the inputs inside a form are aligned with each other.
    • I added Design label and/or tagged @Expensify/design so the design team can review the changes.
  • I added unit tests for any new feature or bug fix in this PR to help automatically prevent regressions in this user flow.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.

Screenshots/Videos

Android: Native

N/A - test tooling change only, no app UI affected.

Android: mWeb Chrome

N/A - test tooling change only, no app UI affected.

iOS: Native

N/A - test tooling change only, no app UI affected.

iOS: mWeb Safari

N/A - test tooling change only, no app UI affected.

MacOS: Chrome / Safari

N/A - test tooling change only, no app UI affected.

These 19 files test .github/actions, .github/libs, .github/scripts and
scripts/ — build and CI tooling rather than app code. Grouping them in
one directory lets every config that needs to treat them differently
(Jest, tsconfig, ESLint, the Bun test runner, CI path filters) use a
single glob instead of enumerating each file.

Renamed to the *.test.ts suffix Bun's test runner discovers, so the
whole directory can be passed to `bun test` without listing files.

Pure move: no file contents changed.
Adds the plumbing that lets tests/tooling/ run under `bun test` instead
of Jest:

- tests/tooling/tsconfig.json type-checks the directory with @types/bun
  (bun:test's ambient types conflict with the @types/jest the root
  config uses), and the root tsconfig excludes it so nothing is checked
  twice with the wrong types.
- tests/tooling/setup.ts is preloaded to default GITHUB_REPOSITORY,
  mirroring jest/setup.ts for the files Jest still owns.
- `test:tooling` runs the whole directory with --isolate, which gives
  each file a fresh module registry, so module-level state and
  mock.module() calls can't leak between files the way they would in
  Bun's default single-registry mode.
- Jest ignores the directory, ESLint parses it with the new tsconfig,
  knip treats the files as entry points (nothing imports them), and the
  Bun CI job runs the new script.
These 14 files exercise .github/actions and .github/libs, whose
@actions/@octokit dependencies go ESM-only in their next majors. Running
them under bun:test means those imports resolve natively, so no CJS
shims are needed when the upgrade lands.

Mechanical changes throughout: import the test globals from bun:test,
drop the `@jest-environment node` docblocks (Bun has no jsdom to opt out
of), and swap Jest's two-parameter mock generics for Bun's single
function-type form.

Two differences needed real changes:

- Bun resolves @actions/* as real ESM, whose namespace exports are
  read-only live bindings, so `core.getInput = mock` no longer works;
  these now use jest.spyOn. That also removes the tests' dependency on
  @src/types/utils/asMutable.
- Bun has no `jest.mock(path)` automock and no
  advanceTimersByTimeAsync. Auto-mocked modules are replaced with
  explicit per-function spies, and DeployChecklistUtils' retry tests get
  a small local helper that yields to the microtask queue before firing
  the timer.

postOrReplaceComment also drops jest-when, which depends on Jest's
internal expect-matcher state. Its per-argument stubbing is replaced
with a plain inputs record per test that throws on an undeclared input,
preserving jest-when's strictness about unexpected calls.
This is the only action still ending in `module.exports = run` while
using ESM `import` statements at the top. Babel rewrote the whole file
to CommonJS for Jest, so the mix went unnoticed; Bun loads it as real
ESM and refuses it outright ("Expected CommonJS module to have a
function wrapper"), which blocks migrating its test to bun:test.

Every other action already uses `export default run`. The rebuilt bundle
changes only the three corresponding lines, and its
`require.main === require.cache[...]` entry guard - the only thing that
actually invokes the action - is unaffected.
Completes tests/tooling/ with the five files the earlier commit left on
Jest, so the whole directory now runs under one runner:

- bumpVersion and Git relied on `jest.mock(path)`'s automock, which Bun
  has no equivalent of. They now replace `fs`/`fs/promises` and
  `child_process` with mock.module() before importing the module under
  test. --isolate keeps those replacements from reaching other files.
- markPullRequestsAsDeployed drops its ActionUtils mock and exercises the
  real getJSONInput, since the mock only reimplemented it. That means
  core.getInput's stub has to return strings, as the real one does, and
  MOBILE_EXPENSIFY_PR_LIST has to be a declared input rather than one
  that throws and gets swallowed.
- DeployChecklistUtils' retry tests replace advanceTimersByTimeAsync,
  which Bun lacks, with a helper that alternates between yielding to the
  microtask queue and advancing the clock until the call settles. It
  throws rather than hanging if the call is waiting on something else.
- versionUpdater, failureNotifier and detectReactComponent needed only
  their bun:test imports.
- @types/bun types the assertion helpers more tightly than @types/jest
  did, so a few mocks needed adjusting: DeployChecklistUtils' redundant
  listForRepo mock is folded into the spy that wrapped it,
  expect.stringContaining (typed `any` by bun-types) becomes toContain,
  and the two octokit stubs that can't carry the endpoint statics get an
  explicit disable with the reason.
- getPullRequestIncrementalChanges stubs unset inputs as '' rather than
  null, matching what core.getInput actually returns.
- oxfmt sorts bun:test alongside the other test-runner imports, which
  also reorders the imports in the existing server/ bun tests.
- jest-when and @types/jest-when are dropped now that
  postOrReplaceComment, their only consumer, no longer uses them.
Jest's CI job passes --silent, so this output was never visible before;
`bun test` has no equivalent flag, and the code under test logs enough
to bury the results (2200 lines of output for a 19-file run). Stub the
console in the preload instead, behind TEST_VERBOSE.
- runWithFakeTimers now advances by the exact backoff schedule the code
  under test declares, restoring what the Jest version pinned; advancing
  by an arbitrary amount had made the tests pass for any delay. It also
  installs the fake timers inside the try, so a synchronous throw can't
  leave the clock frozen for later tests.
- GithubUtils re-installs its core.getInput spy per test:
  getCommitHistoryBetweenTags' afterEach calls jest.restoreAllMocks(),
  which under Jest could not reach the plain assignment this replaced.
- createOrUpdateDeployChecklist pins the clock. Its assertions re-derive
  today's date and compare it against the title the action stamped, and
  Jest's global fake timers used to make that deterministic. test:tooling
  also pins TZ=utc, as npm test does.
- isDeployChecklistLocked drops its module restore, dead under --isolate,
  along with the comment claiming files share a module registry.
- bunTests.yml watches tests/utils/**, which five of these files import.
- Git's two `not.toContain(expect.stringContaining(...))` assertions were
  vacuous: toContain compares with === and ignores asymmetric matchers.
- Comment fixes: the @actions/github context justification described an
  environment that doesn't hold in CI, and CIGitLogic's preamble still
  described Jest, which no longer runs it. It now also records that a
  failure cascades, since Bun's --bail can't be scoped to one file.
- Tightened the four seatbelt counts that dropped.
Moving these files out of tests/unit/ took them out of test.yml, which
preDeploy.yml calls on every merge to main - so the tests covering
preDeploy's own machinery (isDeployChecklistLocked, the deploy checklist,
markPullRequestsAsDeployed) stopped gating the deploy they guard. Give
bunTests.yml a workflow_call trigger and add it to preDeploy alongside
typecheck/lint/test. That also closes the same pre-existing gap for the
server tests.

Split bunTests.yml into two jobs so a server-test failure no longer hides
the tooling results, and so the git-heavy tooling suite runs alongside
the victory-chart-renderer build rather than after it.

The rest is documentation, because nothing told a contributor that this
directory exists or how to run it:

- tests/tooling/README.md covers how to run the suite and a single file
  (the leading ./ and the flags are both load-bearing), the rule for what
  belongs here rather than in tests/unit/, and the bun:test/Jest API gaps
  that shaped these files.
- README.md, tests/README.md and CLAUDE.md all described Jest as the only
  unit-test runner.
- bunfig.toml's comment claimed Jest owns all of tests/.

Also: typecheck.yml's paths filter listed tsconfig.json literally, so
editing tests/tooling/tsconfig.json (or server/tsconfig.json) skipped
typecheck entirely.
Adding the workflow_call trigger meant this group is now evaluated for
pushes to main, where github.ref is the same for every merge - so two
merges in quick succession would cancel each other's run. That is the
bug #41936 fixed for lint and test, and it would be worse
here: confirmPassingBuild only treats 'failure' as failing, so a
cancelled bunTests would pass the gate silently. Use the same
SHA-qualified group the other preDeploy workflows use.

Also drop GithubUtils' core.getInput stub, which had no implementation
and no assertions behind it - the tests that need core stub it
themselves - and move useFakeTimers inside the try that restores them.
These four transitively import @actions/core at runtime, so they would
have broken under Jest the moment the ESM-only upgrade landed. Moving
them now means no Jest test can reach @actions/* or @octokit/* any more,
which is the whole point of this PR: nothing in tests/unit/ needs a CJS
shim built for it.

Per-file notes:

- artifactsResolver replaces child_process, githubCLI and GithubUtils
  with mock.module() in place of three jest.mock() automocks. Typing the
  paginate mock properly also retires two no-unsafe-type-assertion
  disables.
- generateTranslations swaps `en` per test via a fresh mock.module()
  rather than a getter over a mutable variable: the script imports `en`
  as a default binding, which Bun resolves once at link time, so a getter
  only fires for the first test. Its Git stubs are now spies on the three
  static methods the script actually calls, rather than an automock of
  the whole class, and the diff fixtures gained the `diffType` the real
  Git.diff always sets - inert at runtime, but it means the fixtures now
  type-check against FileDiff.
- ChatGPTTranslator drops its OpenAIUtils automock. Only
  `promptResponses` was ever stubbed, and the constructor it was hiding
  just stores a key.
- createRetestRequestForCP needed only its bun:test imports.

test:tooling now also preloads scripts/stubReactNative.js:
generateTranslations reads src/languages/en, which pulls in react-native,
whose Flow syntax Bun can't parse. Jest got away with it because Babel
transformed react-native. This is the same stub
`bun scripts/generateTranslations.ts` already runs with - bunfig.toml's
top-level preload doesn't apply to `bun test`.

Counts are unchanged: the four ran 63 tests under Jest and run 63 here,
so tests/tooling is 23 files and 316 tests.
…orts

Migrating generateTranslationsTest ran fine under bun:test (40/40), but it
can't be type-checked there. tests/tooling type-checks with @types/bun,
whose ambient JSX declarations conflict with the app's React and
react-native types, and the script under test imports src/languages/en -
which drags ~3,000 app files into the project and reports thousands of
errors that the root project, with `jest` types instead of `bun`, does not.
Confirmed both directions: main's tsconfig finds no errors in src/, and
swapping only the `types` array reproduces them.

The test side can't narrow that graph, because the import comes from the
script. Moving it needs the pure logic extracted or a separate type-check
strategy, so it stays on Jest here and still has to move before
@actions/* goes ESM-only. Recorded in tests/tooling/README.md alongside
the constraint itself, so the next person doesn't rediscover it.

ChatGPTTranslator hit the same wall for a reason that *was* fixable: it
imported Locale from @src/types/onyx/Locale, which re-exports the whole
@src/CONST barrel. It now uses TranslationTargetLocale from
@src/CONST/LOCALES - self-contained, and the type the API it exercises
actually takes.

The stubReactNative preload goes away with generateTranslations, since
nothing else here reaches react-native.
@codecov

codecov Bot commented Aug 12, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
see 26 files with indirect coverage changes

Three unrelated failures on this PR:

- `verify` rebuilt isDeployChecklistLocked's bundle and found a diff.
  Main recently made the html-entities patch idempotent by folding the
  "worklet" directive onto the "use strict" line, but the committed
  bundle still embeds the old two-line form, so every PR branched off
  current main fails this check. Rebuilt against the current patch. Not
  introduced here - it reproduces on plain main - but it blocks this PR,
  so it's fixed here.
- `Compare knip issues against main` reported three new `unlisted`
  findings, which were the same three pre-existing ones at their new
  tests/tooling paths. @octokit/request-error is imported directly by six
  files (three of these tests plus .github/libs/GithubUtils,
  .github/libs/isTeamMember and getPullRequestIncrementalChanges) but
  only ever resolved transitively through @actions/github, so declaring
  it resolves the finding rather than relocating it.
- `spellcheck` flagged "Backoffs" and "bunfig". The first is gone:
  runWithFakeTimers' parameter is now expectedDelaysMs, matching the
  LIST_RETRY_DELAYS_MS constant it is pinned to. The second is a real
  filename, so it goes in the dictionary.

The Android HybridApp job also failed, at "Load files from 1Password".
That is infrastructure - the iOS job on the same runner passed, and
nothing here touches the build - so there is nothing to fix for it.
Three conflicts, all where main independently reworked files this branch
had migrated:

- createOrUpdateDeployChecklist: main replaced the hand-built fake octokit
  with a real one from initOctokitWithToken plus spies, added createMock
  in place of `as unknown as` casts, and added the
  mockDeployChecklistIssuesByLabel helper. Resolved by taking main's file
  wholesale and re-applying this branch's bun migration onto it, rather
  than stitching nine hunks: the memfs mock.module, the dynamic import,
  explicit @actions/core spies, the pinned clock, and Bun's Mock<T> in
  place of jest.SpiedFunction/MockedFunction/mocked. Octokit's
  defaults/endpoint statics are now copied onto the mocks with
  Object.assign, since a Bun mock doesn't carry them and paginate reads
  them off the method.
- isDeployChecklistLocked: main's changes were jest.mocked and typed
  requireActual, both of which the bun version already supersedes, so the
  conflicting hunks take this branch's side. Main's createMock
  improvements merged cleanly and are kept.
- eslint.seatbelt.tsv: kept this branch's renamed paths, minus the three
  entries main deleted outright - its createMock rework removed the last
  no-unsafe-type-assertion violations in failureNotifier,
  createOrUpdateDeployChecklist and isDeployChecklistLocked. Confirmed by
  running ESLint, which neither errors nor re-tightens the baseline.

Verified after the merge: 276 tooling tests pass, all four tsconfig
projects are clean, ESLint and knip are clean, all 24 action bundles
still reproduce, generateTranslationsTest still passes under Jest, and no
Jest test reaches @actions/* or @octokit/*.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant