Publishing a Library to JitPack
The tribot-dev-plugin lets scripters pull in
open-source libraries with bundled(...) instead of copy-pasting code or waiting for Tribot to adopt
something into an official API. This guide is the other side of that: how to author and publish a
library so other people can consume it that way.
We'll use NullableLib as the reference example. It's a small Kotlin library that sits on top of the automation SDK and adds ergonomic helpers. Everything here generalizes to any library you want to share.
The single most important thing to get right is publishing sources. If you skip it, your library still works as a compiled dependency, but two things quietly break:
- Consumers get no inline documentation. Hovering your types in IntelliJ shows nothing, and they can't jump into your source.
- The dev plugin can't include your library in a repo-compatible source upload. When a scripter
bundles your library and builds a source zip to submit to the Tribot repo, the plugin pulls each
bundled library's
-sourcesjar to assemble that zip. No sources jar means your code is missing from the upload, and the plugin warns about it.
So most of this guide is ordinary Gradle, with one section dedicated to doing sources correctly.
A library is not a script
A script is a class that implements TribotScript and gets registered in a tribot { scripts { ... } }
block. A library is just code other projects depend on. It has no script entry, registers nothing, and
isn't deployed to the client on its own. It ships inside a consumer's script jar when they bundle it.
That means a library project skips the tribot { scripts { ... } } registration entirely and instead
adds the maven-publish plugin so it can be published. Otherwise the project setup is the same as any
other Tribot project, so if you haven't set one up before, read
IntelliJ Project Setup first — this guide assumes you're
comfortable with the plugin and Gradle basics.
settings.gradle.kts
Identical to any Tribot project. JitPack doesn't publish the Gradle plugin marker for
org.tribot.dev, so we map the plugin ID to its JitPack module by hand:
pluginManagement {
repositories {
gradlePluginPortal()
maven("https://jitpack.io")
}
resolutionStrategy {
eachPlugin {
if (requested.id.id == "org.tribot.dev") {
useModule("com.github.TribotRS.tribot-dev-plugin:plugin:${requested.version}")
}
}
}
}
rootProject.name = "nullable-lib"
build.gradle.kts
Here's the full build script for a publishable library, annotated below:
plugins {
kotlin("jvm") version "2.1.21"
id("org.tribot.dev") version "latest.release"
`maven-publish`
}
group = "org.tribot"
// JitPack sets the VERSION env var to the git tag when building a release.
version = System.getenv("VERSION") ?: "1.0.0-SNAPSHOT"
repositories {
mavenCentral()
maven("https://repo.runelite.net")
maven("https://jitpack.io")
}
dependencies {
// The automation SDK is provided by the dev plugin as compileOnly — don't redeclare it.
testImplementation(kotlin("test"))
}
kotlin {
jvmToolchain(21)
}
java {
withSourcesJar()
}
// JitPack mangles Gradle Module Metadata: it rewrites the `sourcesElements` variant to
// point at the base jar instead of the `-sources` jar, so IDEs attach the compiled jar
// as "sources." Disabling module metadata lets consumers resolve sources via the standard
// `-sources` classifier convention, which JitPack serves correctly.
tasks.withType<GenerateModuleMetadata>().configureEach {
enabled = false
}
tasks.test {
useJUnitPlatform()
}
publishing {
publications {
create<MavenPublication>("maven") {
from(components["java"])
}
}
}
What each piece is doing:
maven-publishis the standard Gradle plugin for producing publishable artifacts. JitPack runs your build and serves whatever it publishes, so you need this applied.id("org.tribot.dev")gives you the automation SDK, the script SDK, RuneLite, and the rest of Tribot's runtime libraries ascompileOnly, exactly like in a script project. You write your library against the SDK without declaring it as a dependency. Because the consumer's project also applies the plugin, those same SDK types resolve on their end too, so your library never has to bundle or transitively depend on the SDK.versionreads from theVERSIONenv var. JitPack setsVERSIONto the git tag it's building, so a tag of1.2.0publishes version1.2.0. Locally it falls back to a snapshot.groupis mostly cosmetic here. JitPack serves your artifact atcom.github.<owner>:<repo>:<tag>regardless of what you set, so consumers use that coordinate, not yourgroup.
Don't declare the automation SDK yourself. It's tempting to add
compileOnly("com.github.TribotRS:automation-sdk:..."), but the dev plugin already wires it in. Pin
it yourself and you risk compiling against a different SDK version than the client actually ships.
Publishing sources correctly
This is the part people get wrong, and it's two lines plus a deletion.
1. Generate a sources jar. java { withSourcesJar() } adds a -sources jar to the java
component, so from(components["java"]) in the publication picks it up automatically alongside the
main jar. That's what carries your actual source and KDoc.
2. Disable Gradle Module Metadata. This is the non-obvious one:
tasks.withType<GenerateModuleMetadata>().configureEach {
enabled = false
}
Modern Gradle publishes a .module metadata file describing your artifact's "variants," including
which one holds sources. JitPack rewrites that metadata as it republishes, and in doing so points the
sources variant at your compiled jar instead of the -sources jar. The result is that an IDE thinks
it's downloading your source but attaches the bytecode jar, and the consumer sees decompiled output
with no comments.
Disabling module metadata removes the .module file entirely. Consumers then fall back to the
older, classifier-based convention: "the sources for foo-1.2.0.jar live at foo-1.2.0-sources.jar."
JitPack serves that -sources.jar URL correctly, so IDE docs and the dev plugin's source resolution
both work.
Why the source zip needs this too. When a consumer runs the dev plugin's source-zip task to
produce a repo upload, the plugin resolves the -sources artifact for every library in their
bundled configuration and unpacks it into the zip. A library that never published a sources jar is
skipped with a warning, and its code is simply absent from the source upload. Publishing sources is
what makes your library a first-class citizen in repo submissions, not just at compile time.
jitpack.yml
JitPack defaults to an older JDK. Tribot targets JDK 21, so pin it with a jitpack.yml at the repo
root:
jdk:
- openjdk21
This matches the jvmToolchain(21) in your build. Without it, JitPack may build with a JDK that
can't compile your code or produces class files Tribot can't load.
Cutting a release
JitPack builds git tags on demand. There's nothing to push to a registry, no credentials to manage. A release is just a tag:
git tag 1.2.0
git push origin 1.2.0
The first time someone requests that version, JitPack checks out the tag, runs your build, and caches
the result. Use a plain semver tag like 1.2.0; the version your artifact publishes as is whatever
the tag name is (via the VERSION env var above).
Optional: verify the build in CI
JitPack building lazily has a downside — you don't find out a tag is broken until someone tries to use it. NullableLib uses a small GitHub Actions workflow that builds the tag, creates a GitHub Release, then asks JitPack to build it and fails if the artifact doesn't come out:
name: Release
on:
push:
tags:
- '[0-9]+.*'
- 'v[0-9]+.*'
permissions:
contents: write
jobs:
release:
runs-on: ubuntu-latest
env:
OWNER: Nullable-TB
REPO: nullable-lib
TAG: ${{ github.ref_name }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '21'
- uses: gradle/actions/setup-gradle@v4
- name: Build and test
env:
VERSION: ${{ github.ref_name }}
run: ./gradlew build
- name: Create GitHub Release
env:
GH_TOKEN: ${{ github.token }}
run: gh release create "$TAG" --title "$TAG" --generate-notes
- name: Trigger and verify JitPack build
run: |
set -euo pipefail
curl -s --max-time 600 \
"https://jitpack.io/com/github/$OWNER/$REPO/$TAG/build.log" | tee jitpack-build.log || true
code=$(curl -s -o /dev/null -w '%{http_code}' \
"https://jitpack.io/com/github/$OWNER/$REPO/$TAG/$REPO-$TAG.pom")
if [ "$code" != "200" ]; then
echo "::error::JitPack build failed or artifact missing (HTTP $code). See log above."
exit 1
fi
It's optional, but it means a bad tag fails loudly in your face instead of silently in a consumer's build.
Consuming your library
Once a tag is built, anyone with a Tribot project bundles it with the JitPack coordinate
com.github.<owner>:<repo>:<tag>:
dependencies {
bundled("com.github.Nullable-TB:nullable-lib:latest.release")
}
latest.release always resolves the newest tag; pin an exact version like
com.github.Nullable-TB:nullable-lib:1.2.0 for reproducible builds. bundled (covered in
IntelliJ Project Setup) both compiles against the library and
packs it into the consumer's script jar so it's present at runtime inside Tribot Echo.
Verifying sources actually published
After a release builds, confirm sources are really there before relying on them. The fastest checks:
- Open
https://jitpack.io/#<owner>/<repo>and look at the build log for your tag. A green build with a published artifact is the baseline. - Hit the sources URL directly in a browser:
https://jitpack.io/com/github/<owner>/<repo>/<tag>/<repo>-<tag>-sources.jar. A200(a download) means the sources jar published; a404means it didn't, and you've got the module-metadata orwithSourcesJar()step wrong. - In a consumer project, bundle the library, refresh Gradle, and hover one of your types in IntelliJ. If your KDoc shows up, everything is wired correctly end to end.
Wrapping up
A publishable Tribot library is a normal Kotlin Gradle project plus three things: the
org.tribot.dev plugin (for the SDK), maven-publish, and the source-publishing setup —
withSourcesJar() with Gradle Module Metadata disabled. Get those right, tag a release, and your
library is consumable by anyone with bundled(...), complete with inline docs and repo-ready source.
NullableLib is open source and small enough to read end to end if you want a working reference: github.com/Nullable-TB/nullable-lib. For more on the broader push toward community-built libraries, see the Developer Overview.