Skip to content

Port to Bun, upgrade dependencies and move CI to GitHub Actions - #16

Merged
sleipner01 merged 33 commits into
mainfrom
bun-migration
Sep 26, 2026
Merged

sleipner01 merged 33 commits into
mainfrom
bun-migration

Conversation

@sleipner01

@sleipner01 sleipner01 commented Sep 26, 2026 •

Copy link
Copy Markdown
Owner

Moves the project from npm to Bun, upgrades every dependency, replaces ESLint with Oxlint, and ports CI from GitLab to GitHub Actions.

Bun

  • client and server are Bun workspaces, so one bun install sets up everything. A single bun.lock replaces the three package-lock.json files.
  • The server runs on Bun: bun --watch replaces nodemon, and Bun's built-in .env loading replaces dotenv.
  • Server tests run with bun test instead of Vitest, in about 1.5 s.

Upgrades

Area From → To
React 18 → 19
MUI 5 → 9
Apollo Client 3 → 4
React Router react-router-dom 6 → react-router 8
Vite / Vitest 4 / 0.34 → 8 / 5
TypeScript 5 → 7
Stylelint 15 → 17
Apollo Server / Mongoose 4 / 7 → 5 / 9
Playwright, jsdom, Testing Library, faker, uuid, Vercel Analytics, Sass latest

graphql stays on 16, which both Apollo Server 5 and Apollo Client 4 require.

Tooling

  • Oxlint replaces ESLint. Its type-aware rules run on tsgolint, which is built on TypeScript 7, so no TypeScript 6 alias is needed. Import sorting keeps eslint-plugin-simple-import-sort as an Oxlint JS plugin. ESLint, typescript-eslint and ten plugins, configs and resolvers are gone. Linting takes about 1.3 s.
  • Stylelint uses stylelint-config-standard-scss. Before, it parsed SCSS with the LESS parser and had a CSS-in-JS parser for files with no CSS.
  • Prettier runs on its own over the whole repo, with a config that only lists non-default options. Dropping endOfLine: auto exposed some CRLF files, which are reformatted.
  • TypeScript follows the current Vite layout: tsconfig.app.json and tsconfig.node.json, checked with tsc -b. This also type-checks the e2e tests and config files, which weren't checked before.
  • MUI components and icons are imported by path, which halves the unit tests' import time.
  • bun run check runs every check CI runs, except e2e.
  • In total, installs dropped from 769 packages to about 600.

Fixes

  • Playwright: tests no longer use the old /project2 base path.
    • They run against start:e2e, a server backed by an in-memory MongoDB with a small seeded dataset, so no database access or secrets are needed.
    • The 14 tests that expect the shared test database's movies are tagged @test-db and skipped, unless E2E_TEST_DB_URI is set.
    • The e2e servers use their own ports (5174 and 4100), so a run never reuses a dev server connected to a real database.
  • Server URI: the server reads the database URI from its first argument or URI. This drops the CI and NODE_ENV=manual modes the old GitLab job needed.
  • Lint warning: UserContext and useUser move to their own module, so fast refresh works.
  • Tests that could never fail:
    • Many Playwright assertions weren't awaited, so they never ran.
    • Server tests used try/catch around expect, so they passed when nothing threw.
    • WatchlistButton tests checked hashed CSS module class names, which never match.
    • All of these now assert real behavior.
  • Bugs exposed by Apollo 4's typed queries: the movies page never showed "No movies found", and pressing Enter in an empty search threw.

CI on GitHub Actions

ci.yml runs four parallel jobs on pull requests and pushes to main: lint and typecheck, client unit tests, server tests, and e2e. There's no build job, since Vercel and Render build on deploy. With warm caches each job takes about 20 s.

What keeps it fast:

  • A composite action caches Bun's package store, and each job installs only the workspaces it needs.
  • The MongoDB binary and Playwright's Chromium are cached.
  • Superseded runs are cancelled.

.gitlab-ci.yml stays as a legacy file.

Deployment

Neither platform's settings are changed here. docs/deployment.md lists the expected settings:

  • Vercel: bun run build, output client/dist.
  • Render: bun install --frozen-lockfile --production --filter bingewatcher-server, then bun run start. This skips mongodb-memory-server, which moved to devDependencies.

Testing

  • bun run check passes: lint, style lint, format, typecheck, 67 client tests and 21 server tests.
  • All four CI jobs pass. The e2e job runs 10 tests in Chromium.
  • E2E: the 10 tests that don't need the test database pass locally and in CI. The 14 @test-db tests weren't run, since the test database isn't available.
  • Manual smoke test: movies, search, login menu, movie details, rating and watchlist all work on the upgraded stack.

🤖 Generated with Claude Code

sleipner01 and others added 14 commits September 26, 2026 11:50
Replace npm with Bun as package manager, script runner and server
runtime. Client and server become workspaces under the root, so a
single bun install sets up everything.

- Swap nodemon for bun --watch
- Drop the unused node-sass (sass is already used) and concurrently
- Declare dotenv and @graphql-tools/merge as server dependencies,
  since isolated installs no longer hoist them
- Import defineConfig from vitest/config in the server, which does
  not depend on vite
- Run CI on oven/bun and re-enable the server test job

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Apollo Server 5, Mongoose 9, MongoDB driver 7, mongodb-memory-server 11
and Vitest 5. graphql stays on 16, which Apollo Server 5 requires.

- Drop dotenv in favour of Bun's built-in .env loading
- Remove the connect options the MongoDB 7 driver no longer accepts
- Download the MongoDB binary in a global setup, so parallel test
  files don't race for the same lockfile on a cold cache

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Vite 8, Vitest 5, @vitejs/plugin-react 6, jsdom 30, Sass, Playwright
and dotenv. Vitest now resolves its config from --root, so point it
at the tests with --dir instead to keep vite.config.ts in use.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Also bumps Testing Library to 16, which needs @testing-library/dom as
an explicit peer. React 19 treats a value returned from a ref callback
as its cleanup, so the search result ref callback no longer returns
the assignment.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
react-router-dom was folded into react-router in v7. RouterProvider
comes from react-router/dom so it can use react-dom's flushSync.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Move the search adornment from the removed TextField InputProps to
  slotProps.input
- Wrap the user menu items in a MenuList, which MenuItem now requires

Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Import React hooks, ApolloProvider and MockedProvider from their new
  /react entry points, and add the rxjs peer dependency
- Drop MockedProvider's removed addTypename prop
- Type every query document with TypedDocumentNode, since untyped
  results are now unknown instead of any

The typed results surfaced a few latent bugs: the movies page checked
the length of the result object instead of the movie list, so it never
showed "No movies found", and pressing Enter in an empty search threw.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
faker is used at runtime by UserContext, so it moves to dependencies.
uuid ships its own types, so @types/uuid is dropped.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
ESLint 10 is out, but eslint-plugin-react and eslint-plugin-import
only support up to 9.

- Replace .eslintrc.cjs with eslint.config.mjs and the typescript-eslint
  meta package
- Resolve imports with eslint-import-resolver-typescript, since the node
  resolver doesn't understand exports-only packages like react-router 8
- Derive the movie count on the movies page during render, which the
  new react-hooks set-state-in-effect rule requires
- Remove a stale disable comment in the server

Co-Authored-By: Claude Opus 5.5 <[email protected]>
TypeScript 7 ships without a JS API, which typescript-eslint needs.
Following the side-by-side setup from the 7.0 release notes, typescript
is aliased to @typescript/typescript6 for tooling, and TypeScript 7 is
installed as @typescript/native so tsc runs the 7.0 compiler.

TypeScript no longer includes every @types package by default, so the
client declares the Vitest globals it uses explicitly.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Replace the deprecated grid-gap with gap, which the new
property-no-deprecated rule flags.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Snapshot changes are markup only: MUI class names and icon paths,
React 19 useId formats, and CSS module hashes.

The StarRating snapshot test now waits for the fetched rating, since
Apollo 4's MockedProvider delays responses and the snapshot was
otherwise taken before the rating arrived.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@vercel

vercel Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
bingewatcher_client Ready Ready Preview Sep 26, 2026 10:51am UTC

sleipner01 and others added 11 commits September 26, 2026 12:16
Oxlint's type-aware rules run on tsgolint, which is built on the
TypeScript 7 compiler. That removes the need for typescript-eslint and
the TypeScript 6 alias, so typescript is a plain ^7 dependency again.

- The React, hooks and fast refresh rules are native Oxlint rules
- Import sorting keeps eslint-plugin-simple-import-sort, loaded as an
  Oxlint JS plugin
- Prettier runs on its own instead of through ESLint
- Drops ESLint and ten plugins, configs and resolvers
- VS Code recommends the Oxc extension and fixes on save with it

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Fast refresh only works for files that export components alone, so
UserContext.tsx now only exports the provider.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Await Playwright's web-first assertions, which silently passed when
  left floating, and wait for URLs directly instead of wrapping
  waitForURL in a truthy check
- Assert rejections with rejects.toThrow in the server tests, which
  passed when nothing was thrown
- Sort ratings numerically in the rating order test
- Mark fire-and-forget navigate and Apollo calls with void, and stop
  awaiting setTimeout in the search component
- Await the waitFor in the UserContext test and assert on the rating in
  the StarRating test
- Go to /movie/852436 in e2e tests, which no longer live under /project2

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The config parsed SCSS with the LESS parser and CSS-in-JS with
postcss-lit, though the project only has SCSS modules. Switching to the
SCSS standard config also makes most of the disabled rules unnecessary.
Drops postcss-less, postcss-lit, stylelint-config-standard and
stylelint-scss, which the SCSS config already includes.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Keep only the options that differ from Prettier 3's defaults, and run
it on the whole repository instead of src folders only. Prettier
already skips gitignored files.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Mostly CRLF line endings, which endOfLine: auto used to hide, and
markdown and GraphQL files outside src.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Follows the current Vite template: tsconfig.app.json covers src with
vite/client and Vitest globals types, and tsconfig.node.json covers the
config files and e2e tests, which were not type-checked before. The
root tsconfig only references the two, and tsc -b checks both.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Serve tests from http://localhost:5173 instead of the old /project2
  base path, which Vite no longer uses
- Point the dev server at the local API during e2e runs and start the
  server with cwd instead of cd
- Report to GitHub annotations in CI
- Drop dotenv, which the config no longer needs, and the unused
  @vitest/ui
- Make test:unit a single run, add test:unit:watch and typecheck

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Bun's test runner replaces Vitest and Vite on the server, and runs the
suite in about 1.5 seconds. The MongoDB binary download moves to a test
preload.

- Move mongodb-memory-server to devDependencies, so production installs
  on Render don't download a MongoDB binary
- Drop the direct mongodb dependency, which only Mongoose uses, and the
  unused config/db.js
- Compare genre ids instead of matching Mongoose documents with
  objectContaining, which bun:test can't read

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Rename the package to bingewatcher, drop the unused directories field,
and replace the pipeline script with check, which runs the same steps
as CI. Build scripts only cover the client, since the server has no
build step.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Lint, client tests, server tests and e2e tests run as parallel jobs.
There is no build job, since Vercel builds the client and Render runs
the server from source.

To keep the jobs fast:
- A composite action caches Bun's package store, and each job installs
  only the workspaces it needs
- The MongoDB binary and Playwright's Chromium are cached, and only the
  headless shell is installed
- Superseded runs on the same branch are cancelled

The e2e job needs a DB_URI secret pointing at the test database, and
fails early when it's missing. The GitLab config stays as a legacy file.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
sleipner01 and others added 2 commits September 26, 2026 12:27
- Rewrite the root README around the new requirements and setup, with
  env files, scripts, e2e tests, CI and deployment
- Update the client and server READMEs to the new scripts, bun test and
  the server's environment variables
- Describe the GitHub Actions jobs and the DB_URI secret in ci.md
- Replace the NTNU VM guide with deployment docs for Vercel and Render
- Fix stale paths and tools in the Playwright, testing, database and
  file structure docs

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Replaces an any and its stale ESLint disable comment.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
sleipner01 and others added 2 commits September 26, 2026 12:30
Barrel imports from @mui/icons-material load every icon module in tests
and in the dev server, since neither tree-shakes. Path imports, which
some files already used, cut the unit tests' import time roughly in
half.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Threads start faster than the default forked processes, which makes the
suite about 15% faster.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The click tests checked hashed CSS module class names, which never
match, so their assertions always passed. They also finished before the
mutation resolved, leaving a state update to land after jsdom was torn
down, which failed CI once tests ran in threads. Both tests now wait
for the button's accessible name to flip, and the small button test
checks that no text label renders.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@sleipner01 sleipner01 changed the title Port to Bun and upgrade all dependencies Port to Bun, upgrade dependencies and move CI to GitHub Actions Sep 26, 2026
sleipner01 and others added 2 commits September 26, 2026 12:50
start:e2e starts the server against mongodb-memory-server, seeded with
a small, predictable set of genres and movies. The server now reads the
database URI from its first argument or the URI variable, which drops
the CI and NODE_ENV=manual special cases the old GitLab job needed.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Tests that expect the test database's movies are tagged @test-db and
skipped, unless E2E_TEST_DB_URI points at that database. The rest run
against the seeded e2e server, so CI no longer needs a DB_URI secret.

The e2e servers use their own ports, so a run never reuses a dev server
connected to another database.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@sleipner01
sleipner01 merged commit 763efdd into main Sep 26, 2026
6 checks passed

This branch was successfully deployed

1 active deployment
Preview — c5d85c29 Deployed Sep 26, 2026 by vercel[bot]
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