8000 Migrate to sbt 2.0.8, Scala 3.9.0; webpack → vite by rleibman · Pull Request #10 · rleibman/chuti · GitHub
[go: up one dir, main page]

Skip to content

Migrate to sbt 2.0.8, Scala 3.9.0; webpack → vite - #10

Merged
rleibman merged 9 commits into
mainfrom
sbt2-scala39-migration
Sep 9, 2026
Merged

rleibman merged 9 commits into
mainfrom
sbt2-scala39-migration

Conversation

@rleibman
@rleibman rleibman commented Sep 8, 2026
Copy link
Copy Markdown
Owner

Migrates the build to sbt 2.0.8 and Scala 3.9.0, and replaces sbt-scalajs-bundler (webpack) with vite.

Plugins with no sbt 2 build

  • sbt-scalajs-bundler — replaced by vite (below).
  • sbt-explicit-dependencies — dropped.
  • sbt-revolver — dropped. reStart is gone, use run.

sbt-header moved org (de.heikoseeberger → com.github.sbt). Bumped: sbt-scalajs 1.22.0, caliban-codegen-sbt 3.1.5, sbt-scalajs-crossproject 1.4.0, sbt-scalafmt 2.6.2, sbt-scalafix 0.14.8, sbt-mima 1.1.6, sbt-stryker4s 1.1.1.

sbt 2 breaking changes hit

  • %%% is gone. sbt-platform-deps, which defined it, has no sbt2 build; %% is platform-aware under sbt 2.
  • Universal / mappings is keyed by HashedVirtualFileRef, not File — converted via fileConverter.
  • dist/debugDist renamed to webDist/webDebugDist. sbt-native-packager also defines dist, and under sbt 2 it is a TaskKey[HashedVirtualFileRef] against our TaskKey[File]; two types under one name is a hard load error. Both are Def.uncached, since File is not a cacheable task output.
  • Test / testGrouping is Def.uncached — Seq[Tests.Group] has no JsonFormat, and there is nothing worth caching there anyway.
  • excludeLintKeys for the keys lintUnused reports — all plugin-defined (sbt-git's version and gitDescribedVersion/useGitDescribe, sbt-native-packager's Rpm/Debian/Universal scopes), not ours.
  • chuti-stlib hand-suffixed _sjs1_3 — for ivy-local layout coursier cross-versions the module directory but derives the jar name without the platform suffix, which does not exist.
  • scalajs-react declared VersionScheme.Always across all its modules — stlib was generated against 3.0.0 while zio-auth 3.1.7 and web pull 4.0.0. Declared explicitly rather than lowering evictionErrorLevel, so a NEW conflict still fails the build.

Scala 3.9 source fix

model/shared/src/main/scala/chuti/events.scala — Scala 3.9 no longer allows a type ascription on a pattern that is not a plain variable or number literal, so the two tuple destructurings lose theirs. The right-hand sides are fully typed already, so nothing infers differently.

webpack → vite

Vite is driven from sbt, not the reverse: the linker output path goes over in the environment rather than vite spawning sbt print fastLinkJSOutput and re-entering the build.

custom.webpack.config.js is deleted, along with css-loader, style-loader, file-loader, url-loader and webpack-merge — vite handles CSS and asset imports natively, so that whole loader stack was only there to do what vite does out of the box. npmDependencies move out of build.sbt into a real web/package.json.

Under sbt 2 the linker output lands in target/out/sjs1/..., far from web/node_modules, so a small vite plugin re-resolves its bare imports as if they came from web/. Rollup warnings for "use client" directives and broken sourcemaps are filtered for node_modules only, so the same warnings from our own code still surface.

Dependencies

caliban 3.0.0 → 3.1.5 — the library had drifted behind its own codegen plugin, which was already on 3.1.5. Also zio-auth 3.1.7, zio-json 1.0.0, zio 2.1.26, zio-http 3.11.4, zio-config 4.0.8, flyway 13.5.0, sttp 4.0.26, logback 1.6.3, mariadb 3.5.10, jsoniter 2.40.1, jwt 11.0.4, and others.

Verification

sbt clean Test/compile and sbt web/webDebugDist both exit 0 with zero warnings.

The test suite still fails on missing local DB config (Missing data at chuti.db.dataSource.password) — environmental and pre-existing, not from this change.

🤖 Generated with Claude Code

https://claude.ai/code/session_01FUquyESPidPbweqpxTG2ge

rleibman and others added 9 commits September 7, 2026 22:05
sbt 2 build migration:
- sbt-header moved org (de.heikoseeberger -> com.github.sbt); only the new org publishes an sbt2 build.
  sbt-scalajs 1.22.0, caliban-codegen-sbt 3.1.5, sbt-scalajs-crossproject 1.4.0, sbt-scalafmt 2.6.2,
  sbt-scalafix 0.14.8, sbt-mima 1.1.6, sbt-stryker4s 1.1.1.
- sbt-explicit-dependencies and sbt-revolver have no sbt2 build; both dropped. `reStart` is gone, use `run`.
- `%%%` is gone: sbt-platform-deps, which defined it, has no sbt2 build, and `%%` is platform-aware under
  sbt 2. Infix `withSources ()` becomes `.withSources()` on the whole ModuleID.
- Universal / mappings is keyed by HashedVirtualFileRef rather than File; converted via fileConverter.
- dist/debugDist renamed to webDist/webDebugDist. sbt-native-packager also defines `dist`, and under sbt 2 it
  is a TaskKey[HashedVirtualFileRef] against our TaskKey[File]; two types under one name is a hard load error.
  Both are Def.uncached because File is not a cacheable task output.
- Test / testGrouping is Def.uncached: Seq[Tests.Group] has no JsonFormat, and there is nothing worth caching.
- excludeLintKeys for the keys sbt 2's lintUnused reports; all are plugin-defined (sbt-git's version and
  gitDescribedVersion/useGitDescribe, sbt-native-packager's Rpm/Debian/Universal scopes), not ours.
- chuti-stlib is hand-suffixed _sjs1_3: for ivy-local layout coursier cross-versions the module directory but
  derives the jar name without the platform suffix, which does not exist.
- scalajs-react declared VersionScheme.Always across all its modules: stlib was generated against 3.0.0 while
  zio-auth 3.1.7 and web pull 4.0.0. Declared rather than lowering evictionErrorLevel, so a NEW conflict still
  fails the build.

Scala 3.9 source fix (model/shared/.../chuti/events.scala):
Type ascriptions on patterns other than a plain variable or number literal are no longer supported, so the two
tuple destructurings lose theirs. The right-hand sides are fully typed already, so nothing infers differently.

webpack -> vite:
sbt-scalajs-bundler has no sbt2 build. Vite replaces it, driven FROM sbt: the linker output path goes over in
the environment rather than vite spawning `sbt print fastLinkJSOutput` and re-entering the build.
- custom.webpack.config.js is deleted along with css-loader, style-loader, file-loader, url-loader and
  webpack-merge: vite handles CSS and asset imports natively.
- Scala.js now emits ESModule + SmallModulesFor, which is what vite consumes.
- Node resolution: under sbt 2 the linker output lands in target/out/sjs1/..., far from web/node_modules, so a
  small vite plugin re-resolves its bare imports as if they came from web/.
- npmDependencies move out of build.sbt into a real web/package.json.
- Rollup warnings for "use client" directives and broken sourcemaps are filtered for node_modules only, so the
  same warnings from our own code still surface.

Dependency upgrades: caliban 3.0.0 -> 3.1.5 (the library had drifted behind its own codegen plugin), zio-auth
3.1.7, zio-json 1.0.0, zio 2.1.26, zio-http 3.11.4, zio-config 4.0.8, flyway 13.5.0, sttp 4.0.26, logback
1.6.3, mariadb 3.5.10, jsoniter 2.40.1, jwt 11.0.4, and others.

`sbt clean Test/compile` and `sbt web/webDebugDist` both build with zero warnings. The test suite still fails
on missing local DB config (chuti.db.dataSource.password), which is environmental and pre-existing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FUquyESPidPbweqpxTG2ge
The Scala CI workflow has been failing on main since at least February, dying with exit code 127 after ~10
seconds. `actions/setup-java` with `cache: 'sbt'` only caches sbt's directories -- it does not install sbt, and
the runner images no longer ship it, so `sbt` was simply not found. Adds sbt/setup-sbt@v1.

Also switches `debugDist` to `web/webDebugDist`: sbt-native-packager owns the `dist` key name under sbt 2, so
ours were renamed in the migration. And caches web/node_modules for the vite build, keyed on package-lock.json.

The `run:` block becomes a proper block scalar; as a folded string the `cd` steps were being joined onto one
line, which only worked by accident of the trailing semicolons.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FUquyESPidPbweqpxTG2ge
sbt 2 removed `config:key` in favour of `Config/key`, so every `debian:packageBin` and
`universal:packageZipTarball` invocation in CI, deploy scripts and docs failed with
"Not a valid key: debian". The dist/debugDist tasks were also renamed to webDist/webDebugDist
in the migration (sbt-native-packager owns the `dist` name under sbt 2), and the scripts still
called the old names.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FUquyESPidPbweqpxTG2ge
The Scala CI build got past the missing-sbt failure and then died in stLib with
"Couldn't parse: Parse error at 94.27" on @types/node -- sbt-converter 1.0.0-beta44 cannot parse the
TypeScript that ships in current @types/node, which arrives transitively.

beta45 parses it. This is not a new pin: dmscreen, jorlan and meal-o-rama are all already on beta45 and
build their stLibs fine; chuti was simply the one left behind. No stIgnore entry is needed -- none of those
three ignores "node" either.

Regenerating the facades surfaced one new warning: scalajs-react's StBuildingComponent is `inline`, so its
call to the deprecated scala.scalajs.runtime.linkingInfo is reported at each of our call sites. Nothing here
can fix that; suppressed with the same narrowly-matched -Wconf jorlan uses, so other deprecations still warn.

Verified locally: stLib/publishLocal, then `sbt clean compile web/webDebugDist` -- exit 0, zero warnings.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FUquyESPidPbweqpxTG2ge
.gitignore anchored bloop's output as /.bloop/ and /project/.bloop/, both relative to the repo root, so
stLib/project/.bloop/ was not matched and a generated stlib-build.json got committed in the previous change.
Unanchored to .bloop/ so it matches at any depth, and removed the stale /project/.bloop/ line it subsumes.
(The other four repos were checked; none has leaked one.)

Also scalafmtAll/scalafmtSbt over the build files touched by the migration.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FUquyESPidPbweqpxTG2ge
Unquoted, `sbt compile web/webDebugDist` was parsed as a single command and failed with
"Expected whitespace character / Expected '/'". Quoting each task matches what was verified locally.

The stLib step now succeeds after the beta45 bump -- the job reached this line 11 minutes in.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FUquyESPidPbweqpxTG2ge
zio-auth is published to GitHub Packages, not Maven Central, and this build had no resolver for it. It only
ever resolved where a `publishLocal` had left a copy in ~/.ivy2/local; a clean CI checkout failed with
"Error downloading net.leibman:zio-auth_3:3.1.7 ... not found" against every configured repository.

Adds the resolver and the matching credentials block (GitHub Packages requires auth even for public
packages), mirroring what jorlan already had -- which is why jorlan's CI resolved it and these did not.

Verified by hiding ~/.ivy2/local/net.leibman/zio-auth* and confirming server/update and web/update still
resolve.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FUquyESPidPbweqpxTG2ge
CI runs sbt through the thin client (sbtn -- the log says "entering thin client - BEEP WHIRR"), which joins
multiple argv entries into a single command string. `sbt compile web/webDebugDist` therefore parsed as ONE
command and failed with "Expected whitespace character / Expected '/'". The plain sbt script does not do
this, which is why it only ever failed on CI and never locally.

Quoting each task separately, as the previous attempt did, changes nothing -- the shell strips those quotes
and sbt still receives two arguments. The fix is a single argument with a semicolon.

Reproduced locally with sbtn: two args exit 1 with that exact error, semicolon form exits 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FUquyESPidPbweqpxTG2ge
runViteBuild asked node for an 8 GB heap, tuned on a 16 GB dev machine. A GitHub runner has 7 GB total, so
that gets the job SIGTERMed by the OOM killer rather than failing cleanly. Lowered to 6 GB, and it now leaves
NODE_OPTIONS alone entirely when a heap size is already set, so it can be tuned per environment.

Verified web/webDebugDist still builds at the 6 GB ceiling.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FUquyESPidPbweqpxTG2ge
@rleibman
rleibman merged commit 4342bf2 into main Sep 9, 2026
1 check passed
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

0