Jmix CLI is distributed as self-contained application images through GitHub Releases. GitHub Packages is not used because it targets package-manager registries such as Maven, npm, NuGet, and containers; the CLI needs directly downloadable platform archives for an unauthenticated bootstrap command.
Each application image contains the CLI, its libraries, and a reduced JDK 25
runtime produced with jlink and jpackage. Users do not need Java to launch
the wizard. JDK detection inside the wizard concerns the generated Jmix project,
not the CLI runtime.
Every release contains an archive and SHA-256 checksum for each supported platform:
| Platform | Archive |
|---|---|
| Linux x64 | jmix-cli-linux-x64.tar.gz |
| Linux ARM64 | jmix-cli-linux-arm64.tar.gz |
| macOS x64 | jmix-cli-macos-x64.tar.gz |
| macOS ARM64 | jmix-cli-macos-arm64.tar.gz |
| Windows x64 | jmix-cli-windows-x64.zip |
The release also includes install.sh and install.ps1. README downloads the
installer from the same release as the application archives, keeping the
bootstrap script and archive layout in sync.
In a terminal, the bootstrap installer highlights download, verification,
extraction, and wizard startup messages. On macOS/Linux, curl shows its native
download progress bar (or activity indicator when the size is unknown).
PowerShell shows native progress for installation phases and web downloads,
and closes its progress display before starting the wizard and on failure.
Redirected output and CI use plain status lines; NO_COLOR disables colored
status messages.
On macOS and Linux, the installer adds ~/.local/bin to the detected Bash,
Zsh, or POSIX shell profile when the directory is not already in PATH. Since
the bootstrap script runs in a child shell, it also prints the export command
needed to use jmix immediately without restarting the current shell. Set
JMIX_CLI_SKIP_PATH_UPDATE=1 to keep shell profiles unchanged.
The stable asset names allow installers to use GitHub's
releases/latest/download URL while the release tag records the exact version.
Installations are stored by archive checksum, so installing the same release is
a no-op and an update does not overwrite the previous version.
Installed builds update themselves (io.jmix.cli.update). Startup compares the
running version's checksum directory with the published .sha256 before the
command line is parsed, installs a differing release beside the current one,
repoints the managed command, and re-runs the typed command on the new version,
exiting with its code, so an update applies to the command that triggered it
rather than the next one. Within the interval below a release can still be up
to ten minutes old before a run picks it up. A check that cannot complete is
reported and the command continues. jmix update performs the same install on
demand and ignores the interval.
update.lock in the install root serves both as the updater lock, so parallel
runs never download the same release at once, and as the record of the last
check time, which throttles checks to one per SelfUpdater.CHECK_INTERVAL
(ten minutes). The time is written before the check runs, so an offline machine
reports the failure once per interval rather than on every command. A stamp
dated in the future is treated as stale, so a wrong clock cannot suppress
updates permanently. The restarted command receives --no-update as its first
argument, which stops an update loop without an environment variable that the
IDE, file manager and Gradle processes would inherit.
The release check is skipped for --help, for jmix update, and for the
--no-update flag, which is read from the raw arguments in main() because
the update happens before parsing. Local bookkeeping — cache pruning, the
in-use marker, and version cleanup — still runs, so a version used only with
--no-update is never pruned as unused.
Each complete version directory holds a .jmix-installed marker, written by
the installers and by self-update. Every run touches the marker of the version
it runs, so cleanup can remove versions that are neither running nor linked and
have gone unused for a week, while a second command sharing the install root
keeps its own version. A directory without a marker is an interrupted or
partially deleted install: it is reinstalled rather than reused, and pruned
after an hour. Archive timestamps are fixed by the reproducible build, so the
marker — not the directory mtime — is the only reliable install time.
The installers also record their --bin-dir choice in <install-root>/bin-dir
because the CLI cannot otherwise find a custom command location. Keep that
file, the .jmix-installed marker, and the wrapper marker in install.ps1 in
sync with SelfUpdater. install.sh accepts the resolved symlink self-update
writes, so re-running the installer after an update is still a no-op.
Trust model: the archive and its checksum come from the same release endpoint, so verification proves transfer integrity, not authorship — the same trust model as the bootstrap installers, now applied automatically. Anyone able to publish releases therefore reaches installed CLIs on their next run; keep release credentials protected accordingly.
Build and test the current platform's bundle:
./gradlew clean build releaseBundle
tests/test-install.shOn Windows:
./gradlew clean build releaseBundle
tests/test-install.ps1Releases are fully automatic. On every push to main, auto-release.yml
derives the next semantic version from the Conventional Commit types since the
last v* tag:
| Commits since the last tag | Result |
|---|---|
type!: subject or BREAKING CHANGE: |
major (minor while pre-1.0) |
feat: |
minor |
fix: or perf: |
patch |
only docs:, chore:, ci:, test:, … |
no release |
When a bump applies, the workflow pushes the vX.Y.Z tag and dispatches the
release workflow on it (tags pushed with the workflow token do not fire the
tag-push event). The release workflow builds and tests every platform image,
verifies all checksums, and creates the GitHub release only after the complete
matrix succeeds — a broken build therefore fails before anything is published.
Commit types decide releases: keep them accurate, and use docs:/chore:/ci:
for changes that must not publish a release.
Pushing a vX.Y.Z tag manually still triggers the same release workflow when a
hand-picked release point is needed.
Post-release checklist:
- Confirm the repository is public so the bootstrap commands need no token.
- Verify the release is marked immutable in GitHub.
- Run both README bootstrap commands on clean machines.
Do not upload release files manually.