Documentation

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 -sources jar 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-publish is 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 as compileOnly, 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.
  • version reads from the VERSION env var. JitPack sets VERSION to the git tag it's building, so a tag of 1.2.0 publishes version 1.2.0. Locally it falls back to a snapshot.
  • group is mostly cosmetic here. JitPack serves your artifact at com.github.<owner>:<repo>:<tag> regardless of what you set, so consumers use that coordinate, not your group.

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. A 200 (a download) means the sources jar published; a 404 means it didn't, and you've got the module-metadata or withSourcesJar() 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.