Releasing Onion¶
Onion uses git tags to drive releases. The version is derived automatically
from the latest tag by sbt-dynver, so there
is no manual version := ... line to update in build.sbt.
Release checklist¶
-
Make sure
Both parts matter. Diagnostics are bilingual, and release CI runs in English while local development is usuallymainis green — in both locales.ja_JP, so a test asserting on message text can pass in one locale and fail in the other. Under sbt 2,testdelegates totestQuickand reportsNo tests to runon an unchanged tree, and-Dis only picked up by a freshly started server — so withoutshutdownandtestFullthis step can look green having run nothing. -
Decide the next version. Onion follows Semantic Versioning with milestone and RC pre-releases when needed:
- Patch release:
v0.2.1 - Minor release:
v0.3.0 - Milestone:
v0.3.0-M1 -
Release candidate:
v0.3.0-RC1 -
Update
CHANGELOG.md. Add a new section for the release with the date and a summary of user-facing changes, bug fixes, and internal improvements. -
Start the release.
Preferred — trigger the release workflow's workflow_dispatch event and let
it create the tag itself. Pushing a v* tag from a client is rejected by the
repository's tag protection (HTTP 403), which is what left releases stuck for
months (issue #334), so the workflow creates the tag itself with the Actions
token instead of relying on a client-side push:
gh workflow run release.yml -f version=v0.2.0 --ref main
gh run watch "$(gh run list --workflow=release.yml --limit 1 --json databaseId --jq '.[0].databaseId')"
Without the gh CLI (e.g. a session with only the GitHub API/MCP tools), the
same workflow_dispatch event is POST
/repos/{owner}/{repo}/actions/workflows/release.yml/dispatches with
{"ref": "main", "inputs": {"version": "v0.2.0"}} — a GitHub MCP server
typically exposes this as an "run workflow" action taking workflow_id:
"release.yml", ref: "main", inputs: {version: "v0.2.0"}.
The tag-push path still works for anyone whose credentials are allowed to
create v* refs:
Do not commit a ## [X.Y.Z] CHANGELOG heading before the tag exists: if
the release then fails, the heading has to be reverted, which is exactly the
release-and-revert loop #334 describes. Confirm the release first, then
finalize the heading.
- Let CI do the rest. The release workflow will:
- run the test suite,
- build the fat jar (
sbt assembly) and the distribution zip (sbt dist), - verify the tag matches the sbt-derived version,
- smoke-test the fat jar,
- generate SHA-256 checksums,
- create a GitHub Release with the artifacts and auto-generated notes.
-
then, in a separate job, publish
org.onion-lang:onionandorg.onion-lang:onion-llmto Maven Central (see Publishing to Maven Central; until its secrets exist this job only leaves a notice). -
Verify the release.
- Check the GitHub Release page.
- Download
onion-<version>.jarand confirm:
Local artifact inspection¶
To build the same artifacts locally without creating a release:
Outputs (sbt 2's default layout nests build products under target/out/<platform>/<scalaVersion>/<project>/):
- target/out/jvm/scala-3.3.7/onion/onion-<version>.jar (fat jar)
- target/out/jvm/scala-3.3.7/onion/onion-dist-<version>.zip (distribution archive)
Publishing to Maven Central¶
Every release also publishes two artifacts to Maven Central:
| Artifact | What it is |
|---|---|
org.onion-lang:onion |
The compiler, runtime library and tools as a normal library jar (not the fat jar), with a POM listing its dependencies: scala3-library_3, ASM, JLine, LSP4J, tomlj, coursier interface, ... |
org.onion-lang:onion-llm |
The LLM battery. Depends on com.anthropic:anthropic-java, and on org.onion-lang:onion as provided |
Each comes with a -sources.jar, a -javadoc.jar, .asc signatures and .md5/.sha1
checksums. Only the Maven groupId is org.onion-lang; the packages stay onion.*.
How it works¶
- The
publish-centraljob of the release workflow runs after thereleasejob, on the same tag. It imports the signing key and runssbt centralStage sonaUpload:centralStagechecks the version and then runspublishSignedfor both modules intotarget/sona-staging, andsonaUpload(built into sbt 2) zips that directory and uploads it to the Central Portal as one deployment. Signatures come from sbt-pgp 2.3.2, the sbt 2 build, which drives thegpgbinary. - Manual release by default.
sonaUploadleaves the deployment validated but not published. Open https://central.sonatype.com/publishing/deployments and press Publish (or Drop). A release on Central is permanent: it can never be deleted or replaced, so the first releases get a human look. When that has gone well a few times, set the repository variableCENTRAL_AUTO_RELEASEtotrue(Settings → Secrets and variables → Actions → Variables) and the job runssonaReleaseinstead, which publishes without waiting. - Without the secrets the job only prints the notice "Maven Central publishing skipped" and succeeds; the GitHub Release is not affected. If only some of the four secrets are set it fails, so a half-finished setup does not go unnoticed.
- Only release versions.
centralStage,publish,publishSigned,sonaUploadandsonaReleaserefuse any version that is not a plain release version (0.137.0,0.138.0-RC1): sbt-dynver's+<distance>-<sha>versions of an untagged commit, the date suffix of a dirty tree and-SNAPSHOTall fail before anything is built.publishM2andpublishLocalare not affected. - The job builds from the tag's sources with no build cache, and keeps the staged
directory as a workflow artifact (
maven-central-vX.Y.Z) for inspection.
One-time setup¶
- Sign in to the Central Portal. Open https://central.sonatype.com and sign in
with GitHub, as
kmizu. No new account is needed. (A legacy OSSRH/Sonatype account from earlier Scala releases, if you have one, can be used instead.) - Add the namespace. Publishing → Namespaces → Add Namespace:
org.onion-lang. - Verify it with DNS. The Portal shows a verification key for the namespace. Add a
TXT record on
onion-lang.org(the apex,@) whose value is that key, wait untildig +short TXT onion-lang.orgshows it, and press Verify Namespace. The record can be removed once the namespace is verified. - Generate a user token. Account menu → View Account → Generate User Token. The
username and password it shows once are
CENTRAL_USERNAMEandCENTRAL_PASSWORD(not your login). - Prepare a GPG key. Your personal key is fine; a dedicated project key is optional.
To create one:
gpg --full-generate-key(ed25519 or RSA 4096), then find its id withgpg --list-secret-keys --keyid-format long. Publish the public key, which Central uses to check the signatures: Export the secret key, base64 on one line, for CI: - Add the four repository secrets (Settings → Secrets and variables → Actions), or
with
gh, which prompts for each value: - Run the first release the usual way (step 4 above): push the tag
vX.Y.Z, or, where a client tag push is rejected with HTTP 403, start the workflow withgh workflow run release.yml -f version=vX.Y.Z --ref main. Whenpublish-centralis green, open https://central.sonatype.com/publishing/deployments, check that the deployment holdsonionandonion-llm(jar, sources, javadoc, pom, each signed) and press Publish. The files reachrepo1.maven.orgwithin about half an hour; search.maven.org takes longer.
Local dry run¶
The same staging runs locally and uploads nothing. On a release tag (or after
set ThisBuild / version := "0.0.0-dryrun"), with a throwaway key in a temporary
GNUPGHOME and its passphrase in PGP_PASSPHRASE:
target/sona-staging/org/onion-lang/ then holds both modules and
target/sona-bundle/bundle.zip is exactly what sonaUpload would send. On Windows,
point sbt-pgp at Git's gpg: set Global / PgpKeys.gpgCommand := "C:/Program Files/Git/usr/bin/gpg.exe".
Depending on the artifacts¶
org.onion-lang:onion has no _3 suffix: use %, not %%. A program that uses
onion-llm from Java or Scala also declares onion, because the battery has it as
provided.
// sbt
libraryDependencies ++= Seq(
"org.onion-lang" % "onion" % "<version>",
"org.onion-lang" % "onion-llm" % "<version>"
)
// Gradle (Kotlin DSL)
dependencies {
implementation("org.onion-lang:onion:<version>")
implementation("org.onion-lang:onion-llm:<version>")
}
<!-- Maven -->
<dependency>
<groupId>org.onion-lang</groupId>
<artifactId>onion</artifactId>
<version>X.Y.Z</version>
</dependency>
<dependency>
<groupId>org.onion-lang</groupId>
<artifactId>onion-llm</artifactId>
<version>X.Y.Z</version>
</dependency>
An Onion script or project already runs on Onion, so it names only the battery:
Hotfix releases¶
For a hotfix against an already-released version, branch from the release tag,
apply the fix, and push a new patch tag (e.g. v0.2.1).