Skip to content

build: switch to the OneLiteFeather minestom-extensions fork - #207

Merged
TheMeinerLP merged 2 commits into
mainfrom
build/minestom-extensions
Aug 28, 2026
Merged

build: switch to the OneLiteFeather minestom-extensions fork#207
TheMeinerLP merged 2 commits into
mainfrom
build/minestom-extensions

Conversation

@TheMeinerLP

@TheMeinerLP TheMeinerLP commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Rebased onto main and retargeted: #206 is merged, so this PR no longer stacks on build/buildsrc-conventions. #208 was merged into this branch and therefore ships with it — the two commits below are the whole diff.

Commit
build: switch to the OneLiteFeather minestom-extensions fork the change described here
feat: log through SLF4J and report errors to Sentry (#208) already reviewed in #208

Why

dev.hollowcube:minestom-ce-extensions is archived and resolves extension dependencies through a Kotlin class (net.minestom.dependencies.maven.MavenRepository). That forced two workarounds, both of which were carrying an explanatory comment in the build:

  • bundle kotlin-stdlib-jdk8 into the fat jar, otherwise NoClassDefFoundError: kotlin/jvm/internal/Intrinsics
  • route com.github.Minestom:DependencyGetter through the reposilite JitPack proxy

On top of that, TitanApplication needed a reflection hack because ExtensionBootstrap.init() accepted no Auth.

What

Switch to net.onelitefeather:minestom-extensions 2.2.0, the OneLiteFeather fork. Same packages (net.hollowcube.minestom.extensions, net.minestom.server.extensions), but resolution via Maven Resolver.

Both workarounds are gone:

  • kotlin-stdlib-jdk8 dropped from :app and from the version catalog
  • the JitPack proxy replaced by the credential-free OneLiteFeatherReleases repository that hosts the fork

The reflection hack is gone. The fork ships ExtensionBootstrap.init(Auth) (minestom-extensions#7, release 2.2.0), added for exactly this case:

-        MinecraftServer server = MinecraftServer.init(new Auth.Velocity(secret));
-        try {
-            var constructor = ExtensionBootstrap.class.getDeclaredConstructor(MinecraftServer.class);
-            constructor.setAccessible(true);
-            return constructor.newInstance(server);
-        } catch (ReflectiveOperationException exception) {
-            throw new IllegalStateException("Failed to initialise the extension bootstrap", exception);
-        }
+        return ExtensionBootstrap.init(new Auth.Velocity(secret));

That closes open item 1 of the OLF Minestom Project Standard.

:bridge now generates extension.json from @ExtensionInfo instead of keeping it by hand (OLF-L5-01). The handwritten file and the ReplaceTokens filter for @version@ are removed.

Verification

Generated vs. handwritten extension.json — identical content; only key order and whitespace differ:

Field before after
name TitanCloudNetPermissions TitanCloudNetPermissions
entrypoint net.onelitefeather.titan.bridge.TitanBridgePermissionExtension same
version 1.14.0 1.14.0
authors ["OneLiteFeather"] ["OneLiteFeather"]
dependencies ["CloudNet_Bridge"] ["CloudNet_Bridge"]

The bridge jar carries exactly the same entries as before — @ExtensionInfo has source retention, so the annotation never reaches the jar.

Cold Gradle cache (fresh --gradle-user-home): all 65 modules resolve without the JitPack proxy, including com.github.CloudNetService.cloud-command-framework, which comes from repository.derklaro.dev.

Velocity path against the real fat jar — the branch that previously needed reflection:

java -Dminestom.velocity.secret=… -jar app-titan.jar

Gets past ExtensionBootstrap and LuckPerms and stops at NoSuchFileException: worlds — the same point where the pre-change jar stops in an identical run (control run performed). No NoClassDefFoundError.

Tests: 63, 0 failures. (The count includes the 7 tests that came with #208.)

Side effect: the fat jar loses the Kotlin and Shrinkwrap trees

Measured against main, both jars built from a clean shadowJar:

main this PR
size 43.7 MB 43.0 MB
entries 25,650 25,280
kotlin/** 1,045 0
shrinkwrap resolver 579 0
org/eclipse/aether 448 651

The old DependencyGetter pulled in shrinkwrap-resolver, arquillian-spacelift and jsoup; Maven Resolver replaces that with aether + httpclient.

The extension switch on its own removed ~2.8 MB. The net figure is smaller because the Sentry and Logback dependencies from #208 add roughly 2 MB back — that is the cost of the observability change, not of this one.

Notes

  • The commit type is build:, so this produces no changelog entry. If the loader switch should be visible to ops in CHANGELOG.md, say so and it becomes a feat:.
  • The SLF4J(W): No SLF4J providers were found warning noted during the original smoke test is fixed by feat: log through SLF4J and report errors to Sentry #208, which is part of this branch.
  • MapProvider still compiles with two removal warnings (GsonFileHandler, AnvilLoader(Path)), both pre-existing and unrelated to this change.

@TheMeinerLP
TheMeinerLP requested a review from a team as a code owner August 27, 2026 19:55
@TheMeinerLP
TheMeinerLP force-pushed the build/buildsrc-conventions branch from c9a0860 to 00dfaed Compare August 27, 2026 20:03
@TheMeinerLP
TheMeinerLP force-pushed the build/minestom-extensions branch from 33bf01e to e22c1ca Compare August 27, 2026 20:04
TheMeinerLP and others added 2 commits August 28, 2026 08:55
dev.hollowcube:minestom-ce-extensions is archived and resolves extension
dependencies through a Kotlin class (net.minestom.dependencies.maven.
MavenRepository). That forced two workarounds Titan carried in comments:
bundling kotlin-stdlib-jdk8 into the fat jar, and routing resolution
through the reposilite JitPack proxy for com.github.Minestom:
DependencyGetter.

net.onelitefeather:minestom-extensions 2.2.0 keeps the same packages
(net.hollowcube.minestom.extensions, net.minestom.server.extensions) but
resolves through Maven Resolver, so both workarounds are gone:

- kotlin-stdlib-jdk8 dropped from :app and from the version catalog
- the JitPack proxy repository replaced by the credential-free
  OneLiteFeatherReleases repository the fork is published to. Verified
  with a cold Gradle user home: all 65 modules resolve without it,
  com.github.CloudNetService.cloud-command-framework included (it comes
  from repository.derklaro.dev).

The fork also offers ExtensionBootstrap.init(Auth), added upstream for
exactly this case. Titan's Velocity path no longer has to reach for
getDeclaredConstructor(MinecraftServer.class) + setAccessible(true) - the
reflection hack listed as open point 1 in the OLF Minestom project
standard is gone.

:bridge now generates extension.json from @ExtensionInfo instead of
keeping a hand-written file plus a ReplaceTokens filter for @Version@.
The generated descriptor carries the same name, entrypoint, version,
authors and dependencies; only key order and whitespace differ. The
annotation has source retention, so the bridge jar holds exactly the same
entries as before.

Verified: 56 tests, 0 failures (unchanged). The Velocity branch was smoke
tested against the real fat jar with -Dminestom.velocity.secret set; it
gets past ExtensionBootstrap and LuckPerms and stops at the same missing
worlds/ directory as the pre-change jar does in the same run.

Fat jar shrinks from 43.7 MB to 40.8 MB (25650 to 23719 entries): the old
DependencyGetter dragged in shrinkwrap-resolver, arquillian-spacelift,
jsoup and a large commons/plexus tree; Maven Resolver replaces them with
aether plus httpclient.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QtbnMbAj6m2q3vrDYFizDr
Titan shipped without an SLF4J binding. slf4j-api arrived transitively
through Minestom, no provider was ever declared, and the fat jars answered
every log call with "No SLF4J providers were found" and dropped it. On top
of that, Minestom's default ExceptionManager handler is
Throwable::printStackTrace, so a listener that threw left a bare stack
trace on stdout and nothing else. A production lobby was effectively
running blind.

Logging
- logback-classic as runtimeOnly in :app and :setup, slf4j-api declared
  explicitly where code compiles against it
- one logback.xml in :common, so both fat jars get the same configuration
  without copying it
- TitanObservability.installExceptionHandler() replaces Minestom's
  printStackTrace default, so exceptions reach the log like everything else

Sentry
- optional, exactly as Sturnus does it: no TITAN_SENTRY_DSN means
  Sentry.init is never called and the process behaves as it does today.
  One jar serves operators with and without a Sentry instance.
- release comes from the jar's Implementation-Version manifest attribute
  (added in titan.java-conventions), so an issue points at a deploy
- environment from TITAN_SENTRY_ENVIRONMENT
- reporting has exactly one entry point, the logback appender at ERROR.
  No second path that could double-report or drift out of sync.

Player attribution
EventNodeImpl catches what a listener throws and hands it to the exception
manager one frame up, on the same thread. TitanObservability.guard() sits
inside that frame and records the failing event's player, which the handler
puts into the MDC as player.uuid / player.name. Recording happens in a
catch block only - a listener that returns normally pays for an entered try
and nothing else, which matters because the guarded set includes
PlayerMoveEvent and PlayerPacketEvent.

sendDefaultPii stays false: the SDK's PII defaults are request headers and
IP addresses, which say nothing about a Minestom crash. The player identity
that does is attached deliberately.

Verified against the real fat jar. Without a DSN:
  INFO  n.o.t.c.o.TitanObservability - Sentry reporting disabled - ...
  INFO  luckperms - Loading configuration...
  ERROR n.o.t.c.o.TitanObservability - Unhandled exception
        java.nio.file.NoSuchFileException: worlds
With TITAN_SENTRY_DSN and TITAN_SENTRY_ENVIRONMENT set:
  INFO  n.o.t.c.o.TitanObservability - Sentry reporting enabled -
        release 1.14.0, environment smoketest
Before this change the same run printed "No SLF4J providers were found"
and a bare stack trace.

The appender is declared with an empty DSN on purpose. It is constructed
when the first logger is created, necessarily before bootstrap() can call
Sentry.init, and an appender that finds no DSN then logs "DSN is required"
at WARN - which makes Logback dump its entire configuration status on every
start.

7 new tests in :common (63 total, 0 failures).


Claude-Session: https://claude.ai/code/session_01QtbnMbAj6m2q3vrDYFizDr

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
@TheMeinerLP
TheMeinerLP force-pushed the build/minestom-extensions branch from 6b4c4e6 to 5512f2f Compare August 28, 2026 06:57
@TheMeinerLP
TheMeinerLP changed the base branch from build/buildsrc-conventions to main August 28, 2026 06:57
@TheMeinerLP TheMeinerLP reopened this Aug 28, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Test results

 66 files   66 suites   53s ⏱️
 62 tests  60 ✅ 2 💤 0 ❌
189 runs  183 ✅ 6 💤 0 ❌

Results for commit 5512f2f.

@TheMeinerLP
TheMeinerLP merged commit 301f89c into main Aug 28, 2026
7 checks 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