Bootstrap unification, standalone bundles + messaging, local release redesign, bare-common coordinates#38
Merged
Conversation
Bump the library version to 1.24.0 and the reusable build-logic plugins to 0.1.6. 1.24.0 adds mountSubCommands for flat sub-command grafting and the standalone bungee/velocity bundles; 0.1.6 carries the publish fixes (skip_shadow_publish ordering, java-component binding) and per-target fabric java that landed after the 0.1.5 publish. Published to Reposilite across all targets (+1.20.1, +1.21.10, +1.21.11, +26.1.1, +26.2).
…anup Switch the published Maven coordinate from +<minecraft> to +java<N>. MagicUtils' compiled bytecode depends only on the Java level, not the Minecraft version: the whole library is byte-identical between targets that share a Java level (measured: +1.21.10 vs +1.21.11 differed in 0 classes) and differs wholesale only across Java levels (bytecode major 65 vs 69). The old per-Minecraft scheme published five near-duplicate copies where three real variants exist. Now one representative target per Java level (mc1201/17, mc12110/21, mc2611/25) publishes +java17, +java21, +java25; the other targets remain for the build/smoke matrix. Consumers keep declaring the bare version and resolve +java<N> from their target's Java level, so downstreams need no change. build-logic cleanup (0.1.6 -> 0.1.7): - Merge bukkit/bungee/velocity bundle plugins into one parameterised MagicUtilsJvmBundlePlugin (JvmBundlePlatform table). - Extract the duplicated publication pom-strip into stripPomDependencies and the Bukkit/Velocity consumer embed logic into configureJvmConsumerEmbed. - processor is build-only (annotationProcessor, baked into consumers, never a runtime dependency) -> category NONE, no longer published. Published to Reposilite across java17/java21/java25; processor excluded.
Add the messaging module: a typed MessageBus over a MessageTransport SPI with envelope codec and Target-based addressing (broadcast / all-backends / proxy / server / player). Two interchangeable transports behind one SPI: the default plugin-messaging transport (no extra dependency) and an optional Redis pub/sub transport (Jedis). Platform wiring mirrors the diagnostics pattern: BukkitMessagingSupport / VelocityMessagingSupport / BungeeMessagingSupport plus enableMessaging() / messagingRedis() / configureMessaging() on each Bootstrap.Builder. platform-bukkit/bungee/velocity depend on :messaging. Jedis is compileOnly in :messaging (the Redis transport is class-loaded lazily) and shaded+relocated into the standalone bundles via the shared magicUtilsBundleRedis helper, so operators get the Redis transport out of the box once they enable it in messaging.yml. Verified: bundle carries 647 relocated jedis classes, 0 unrelocated, 29 messaging classes. Merged from the MagicUtils-messaging branch onto the 1.25.0 +java publish scheme. refreshReflectionAllowlist run (messaging imports shifted the getMethod marker line in BukkitMagicUtilsConsumerRegistry).
…eBootstrap The five platform bootstrap builders (Bukkit, Bungee, Velocity, Fabric, NeoForge) each duplicated the same eight language/messages flags, their setters, and the apply/close logic. Extract that byte-identical wiring into core/bootstrap/LanguageBootstrap (single source of truth) and delegate from BukkitBootstrap. Also adds withRecommendedDefaults()/minimal() presets so the default flag set is explicit instead of a wall of booleans. Remaining platforms are migrated in follow-up commits.
…ageBootstrap Delegate the language/messages flags, setters, apply and close-hook logic to core LanguageBootstrap in the four remaining platform bootstraps, matching the Bukkit migration. Each builder now exposes withRecommendedDefaults()/minimal() presets. Proxy builders pass their LoggerCore directly; MC builders pass logger.getCore(). Removes the duplicated boolean-flag blocks entirely.
Verifies withRecommendedDefaults()/minimal() flag state and that apply() plus installMessagesCloseHooks() register/clear the scoped and global Messages manager and logger binding, driven against real LanguageManager/Messages.
Velocity gains forPlugin(proxy, plugin) and Bungee gains forPlugin(plugin), deriving the plugin name (and Velocity's data directory as plugins/<id>) from the plugin's @Plugin/description metadata. The verbose overloads that take an explicit name/dataDirectory are kept and marked @deprecated for source compatibility. This makes the entry points consistent across platforms: Bukkit/Bungee forPlugin(plugin), Velocity forPlugin(proxy, plugin), Fabric/NeoForge forMod(id, supplier).
… duplication Each platform LogBuilder re-declared ~10 covariant overrides of the shared fluent methods (to/target/toAll/toConsole/noPrefix/prefixMode/args/ placeholders/withResolvers/toAudiences) purely to keep the chain typed as the platform builder rather than LogBuilderCore. Make LogBuilderCore<SELF> return SELF from those methods so subclasses inherit them already correctly typed. Platform builders now hold only their player/command-source overloads (Bukkit -66 lines, Fabric/NeoForge -~70 each).
Velocity previously exposed only the raw LoggerCore. Add a Velocity Logger (plus PrefixedLogger and LogBuilder) mirroring Bukkit/Fabric/NeoForge: generated info/warn/error/success/debug/trace methods, Player-typed overloads, prefixed sub-loggers, and a fluent LogBuilder. VelocityBootstrap now builds and returns this Logger (RuntimeResult/Result.logger type LoggerCore -> Logger; safe, no external consumer uses it). VelocityAudience is made public so the facade can wrap players.
Mirror the Velocity change on BungeeCord: add a Bungee Logger (plus PrefixedLogger and LogBuilder) with generated log methods, ProxiedPlayer-typed overloads, prefixed sub-loggers, and a fluent LogBuilder. BungeeBootstrap now builds and returns this Logger (RuntimeResult/Result.logger type LoggerCore -> Logger; safe, no external consumer uses it). BungeeAudience is made public so the facade can wrap players. All five platforms now expose the same typed Logger facade.
VelocityBootstrap/BungeeBootstrap now return the typed Logger facade instead of LoggerCore. The standalone velocity-bundle and bungee-bundle plugins read bootstrap.logger() as a LoggerCore, so call .getCore() to unwrap it.
Show the consistent metadata-derived entry points (forPlugin(this) on Bukkit/ Bungee, forPlugin(proxy, this) on Velocity, forMod on Fabric/NeoForge), document the withRecommendedDefaults()/minimal() presets, note NeoForge has a bootstrap (not manual wiring), and state that bootstrap.logger() returns a typed Logger facade on every platform.
setEscapePlaceholders existed on LoggerCore but was the one LoggerCore method missing from LoggerAdapter, forcing callers to drop to getCore(). Delegate it from the adapter so it is available on the typed Logger facade on every platform.
Give every platform Logger facade a class-level JavaDoc that frames it as the single output surface (console log line + player chat via the same level methods), with a short usage example and a note that the fluent log() builder is only for composite cases. Remove the @SuppressWarnings("doclint:missing") suppressions on the logger facades, LoggerCore, LogBuilder, PrefixedLogger, and FabricExternalPlaceholderEngine, documenting the few members that needed it so javadoc stays green without hiding gaps.
… colour Players saw the internal log level spelled out in chat as [Plugin SUCCESS] / [Plugin WARN]. Two changes to LogMessageFormatter: - Drop the level suffix from the chat prefix. The prefix is rendered into the chat message only (the console line already carries the level via the platform logger), so chat now shows just the plugin brand [Plugin]. - Decouple the level colour from gradients. The success/warn/error signal is now conveyed by colour and applied to every chat message; useGradientChat only chooses between a two-stop gradient and a single solid colour, so the signal survives even with gradients disabled. Adds tests: chat keeps the brand but not the level word, and SUCCESS vs ERROR carry distinct colours with gradients off.
Fabric mods can now enable cross-server messaging, matching the Bukkit/Bungee/ Velocity backends. Adds FabricMessagingSupport.install (Redis transport via the shared messaging module; hostsPlayer via Platform.playerById) and the messaging surface on FabricBootstrap (enableMessaging/messagingRedis/configureMessaging + buildRuntime wiring). No default plugin-messaging transport: riding the proxy's vanilla channel from a mod needs the platform networking API on modern MC, so without Redis the bus falls back to the in-process loopback transport (logged). Compiles on mc12110 and mc1201.
Mirrors the Fabric change on NeoForge: NeoForgeMessagingSupport.install (Redis transport via the shared messaging module) plus the messaging surface on NeoForgeBootstrap (enableMessaging/messagingRedis/configureMessaging + buildRuntime wiring). hostsPlayer resolves via the MinecraftServer supplier (server.getPlayerList().getPlayer(id)) since the NeoForge platform provider does not override Platform.playerById. No default plugin-messaging transport; without Redis the bus falls back to loopback (logged). Compiles on mc12110 and mc262 (NeoForge is disabled on the 1.20.x targets).
…lone jar The NeoForge bundle adds project deps non-transitively, so the newly-added :commands-neoforge messaging code pulled neither the :messaging module nor Jackson into the jar — MessageCodec would NoClassDefFoundError at runtime. Add :messaging to bundleContents and the jackson-databind/core/annotations artifacts explicitly by coordinate (mirroring the Adventure handling). This also closes a pre-existing gap where Jackson was absent entirely, so the config module now has its runtime dependency on the NeoForge bundle too. Verified the jar now contains the messaging classes, RedisMessageTransport, and Jackson.
…e X.Y.Z The library ships only per-Java coordinates (X.Y.Z+java<N>); a bare X.Y.Z POM is never published, so smokeTest polled a URL that 404s forever. Build the URL from the default target's Java level (java21 on mc12110) and percent-encode the '+'. Factor the '+java<N>' format into one pure javaSuffixedCoordinate() reused by publishedVersion, the Modrinth bundle file name, and the smoke URL.
Cross-checks -Pversion across gradle.properties, the git tag, Reposilite Maven (HEAD on the java-suffixed POM, same coordinate as smokeTest) and Modrinth. Strict fail (non-zero) when the required three disagree; Modrinth is advisory (published manually, may lag) and is skipped without a token. -Preport prints the report without failing. Comparison logic (evaluateReleaseConsistency) and the Modrinth version-list parse (parseModrinthVersionIds, factored out of the publish task) live in the model and are unit-tested; the task is a thin git/HTTP wrapper.
Ships the smokeTest java-suffix fix and the new verifyReleaseConsistency task to consumers. Published to Reposilite by publish-maven.yml alongside the library.
The 'one Gradle invocation per Minecraft target' mechanism (wrapper name, isolated child Gradle home, -Ptarget) was inline in registerAllTargetsTask. Extract it into registerMagicUtilsFanout so the upcoming local Maven and Modrinth release fan-outs reuse it instead of copying the machinery. Adds a dryRun mode that prints the command per target instead of spawning a child build. buildAllTargets --dry-run is unchanged (same buildTarget<Name> tasks).
MagicUtilsReleaseSpec makes every release step (validate/bump/tag/push/
maven/modrinth/javadoc/verify) individually optional, so a consumer builds
exactly the pipeline they want. releasePlan() gives the fixed run order;
applyReleaseOverrides() merges -Prelease.<step>=true|false over the DSL.
Wired as magicMatrix { release { ... } } into magicutilsReleaseSpec. No task
consumes the spec yet.
releaseMavenAll publishes the full module matrix to the Maven repo locally, one Gradle invocation per publish unit (one representative target per Java level, from the shared publishUnits), plus publishBuildLogic for the plugins. Reuses registerMagicUtilsFanout; the dry-run command set matches printPublishMatrix unit-for-unit. Factors the gradlew wrapper name into magicUtilsGradleWrapperName so it is spelled once. Actions-free equivalent of publish-maven.yml.
releaseTag creates the vX.Y.Z tag locally and only pushes to origin when push is enabled (-Prelease.push=true), matching the release spec's push=false default. Idempotent: an existing local tag is left in place. Replaces the tagging that lived in release.yml so the tag step runs without actions.
releaseModrinth builds the platform bundle jars for every Java level (one representative target per level, reusing the shared publishUnits + fan-out) and then runs the existing matrix-driven publishToModrinth. The +java<N> bundle jars accumulate side by side per platform, so one publishToModrinth finds all of them. Factors the unit resolution into resolvePublishUnits so the Maven and Modrinth fan-outs share it. Actions-free Modrinth publish.
releaseJavadoc runs aggregatedJavadocZip and PUTs the zip to the publish repo at the stable latest path plus a versioned copy, replacing the curl upload in publish-javadoc.yml. URL builders (javadocLatestUrl/javadocVersionUrl) are pure and unit-tested. Credentials reuse the shared findMagicUtilsPublishSecret (now internal). The versioned URL uses the raw -Pversion, not the +java<N> project.version. -Prelease.dryRun prints the target URLs.
release now runs exactly the steps enabled in release { } (validate/bump/
tag/maven/modrinth/javadoc/verify), in the fixed plan order with a
mustRunAfter chain, and prints the plan. -Prelease.<step>=true|false
overrides the DSL per invocation. Adds releaseValidateBuild (non-Fabric
build gate). dispatchRelease is dropped from the default release (release is
now fully local) but kept as a standalone task for the CI-trigger path.
The orchestrator is registered after every step task so the chain resolves.
The Modrinth token was env-only (MODRINTH_TOKEN) while the Maven secrets accepted a property or env. Make it symmetric: findMagicUtilsModrinthToken reads modrinth_token property or MODRINTH_TOKEN env, so a consumer configures every secret one way (e.g. all in ~/.gradle/gradle.properties). The token is carried as an @internal task input (never in the up-to-date hash), resolved in the configuration phase, in both publishToModrinth and verifyReleaseConsistency.
The Maven release fan-out runs 'clean' in each child invocation to republish from scratch. With the child Gradle home under build/ (release-maven-gradle-home), that clean deleted build/ mid-run — including the child's own daemon working directory — failing with 'could not setcwd (errno 2)'. Move the child home to .gradle/<subdir>, which survives clean. Applies to every fan-out (buildAllTargets too), so none can self-destruct.
A release can fail mid-way (Maven publish dies on one target) and must be re-runnable. But preflight rejected the requested version once its tag existed or gradle.properties was already bumped to it, making a resume impossible. Treat requested == current as a resume: skip the tag/monotonicity checks (a prior run already cut them). A fresh release (version not yet bumped to) is still strictly validated.
…release) A release can fail mid-publish; re-running then 409'd against the immutable releases repo on every coordinate a prior run already uploaded (e.g. the build-logic plugins). -Pskip_existing HEADs each POM and skips the PublishToMavenRepository task when it is already present, so a resumed release publishes only what is missing. releaseMavenAll and publishBuildLogic pass it by default, making the whole Maven release resumable (Modrinth already is, by version_number).
The Maven release fan-out runs each target in a child Gradle with an isolated --gradle-user-home, so the child cannot read ~/.gradle/gradle.properties and its publish 401'd. Forward the parent-resolved publish_user/publish_password to the child as PUBLISH_USER/PUBLISH_TOKEN env (never as -P, so they stay out of ps). MagicUtilsFanoutInvocation gains an env map for this.
releaseModrinth built each representative with -Pscenario=workspace, but the java17 representative (mc1201, a 1.20.x target) has no neoforge, so the child failed with 'target does not support platforms: neoforge'. Use the target's availablePlatformsFor set as -PincludePlatforms instead, so each Java level builds exactly the bundles it supports (neoforge only on java21/java25).
The javadoc zip uploads to a stable latest/ path plus a versioned path. The immutable releases repo returns 409 when latest/ already holds a prior release's zip, which failed the whole release on a step that is effectively done. Treat 409 as a skip (warn, continue) on every javadoc target, so a resumed release does not die on javadoc and the versioned path still uploads.
The messaging package was absent from the 26.x (java25) fabric bundle: it was never listed in the bundle plugin and reached the <26 bundle only transitively via commands-fabric's api(:messaging) through Loom's namedElements fat jar. The 26.x bundle is built from bundleShadow alone (the jiJRemap path drops transitive api project deps), so messaging silently vanished from the shipped java25 jar. Add :messaging to bundleLibProjects so it is inlined on every branch, matching how the neoforge/jvm bundles list it explicitly.
…dles Plain library modules are byte-identical across Java levels (the per-level diffs are javac codegen artifacts, not behaviour), so publishing three near-duplicate +java17/21/25 copies was redundant. Only bundles genuinely differ per level (their shaded dependency set tracks the Minecraft branch). Introduce magicUtilsModuleIsBundle (artifact id ends in -bundle) and magicUtilsPublishedModuleVersion: bundles get <base>+java<N>, plain modules get the bare <base>. The root build stamps project.version per module via this rule, and every consumer plugin resolves the same coordinate shape, so publish and consume agree by construction. A resumed release with -Pskip_existing skips the already-uploaded bare module on the second and third Java-level representatives. The release smoke/verify poll the smoke artifact's real coordinate (magicutils-core, now bare) instead of a java-suffixed one that would 404.
Publishing off a feature branch pins the git tag and the immutable Maven
coordinate to a commit that may never land on the release branch as written,
so the release history diverges from it. releasePreflight now checks the
current branch against a configurable releaseBranch (default main; null
disables the gate) and fails fast before any tag or upload.
Configurable via release { releaseBranch = ... }, overridable per invocation
with -Prelease.branch=<name>, and bypassable once for a genuine off-branch
hotfix with -Prelease.allowAnyBranch=true. releaseBranchViolation is a pure
function so the gate is unit-tested without a git checkout.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Brings
mainup to the current state of the project.mainhad drifted well behind the working branch; this PR is the accumulated work since, in a few themes.Bootstrap unification
Shared language-wiring extracted into a core
LanguageBootstrap; Fabric / NeoForge / Velocity / Bungee migrated onto it. TypedLoggerfacades added to Velocity and Bungee for parity with the other platforms. Metadata-derivedforPluginoverloads for proxies.Standalone bundles + messaging
Standalone Fabric / NeoForge / JVM bundles as drop-in mods/plugins. Cross-server
messagingmodule (plugin-messaging transport by default, optional Redis/Jedis shaded into JVM bundles).Release mechanic redesign (build-logic as a reusable tool)
The release is now fully local and configurable via
release { }DSL +-Prelease.<step>overrides, no longer requiring GitHub Actions:-Pskip_existingHEADs each POM), fan-out credential forwarding, release-consistency verification.Coordinate scheme: common modules bare,
+java<N>only for bundlesPlain library modules are byte-identical across Java levels, so they now publish once under the bare
<base>coordinate instead of three near-duplicate+java17/21/25copies. Bundles keep+java<N>(their shaded dependency set really differs per level). Publish side and every consumer plugin agree by construction via one discriminator.fabric-bundle messaging fix
The 26.x (java25) fabric bundle was shipping without the
messagingpackage (it only reached the <26 bundle transitively).:messagingis now inlined on every branch.Release branch gate
releasePreflightnow refuses to release off the configured release branch (defaultmain); overridable with-Prelease.branch, bypassable with-Prelease.allowAnyBranch=true../gradlew -p build-logic testis green; full builds pass on obfuscated (mc1201) and deobfuscated (mc262) targets; the coordinate scheme was verified end-to-end against afile://sandbox repo across two Java-level representatives.