diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b1f73c2..6b26dd1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,10 +10,13 @@ permissions: packages: write jobs: - build-and-release: - name: Build Jar, Publish Release & Docker Image + build: + name: Test, build jar, release & Docker runs-on: ubuntu-latest - + outputs: + version: ${{ steps.version.outputs.version }} + env: + DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} steps: - name: Checkout code uses: actions/checkout@v4 @@ -26,6 +29,7 @@ jobs: cache: 'maven' - name: Verify tag matches the version in pom.xml, cli and mcp + id: version run: | TAG_VERSION=${GITHUB_REF#refs/tags/v} POM_VERSION=$(mvn -B -q help:evaluate -Dexpression=project.version -DforceStdout) @@ -35,22 +39,30 @@ jobs: fi grep -q "const VERSION = '${TAG_VERSION}'" cli/kview.mjs || { echo "::error::cli/kview.mjs VERSION does not match ${TAG_VERSION}"; exit 1; } grep -q "const VERSION = '${TAG_VERSION}'" mcp/kview-mcp.js || { echo "::error::mcp/kview-mcp.js VERSION does not match ${TAG_VERSION}"; exit 1; } + echo "version=${TAG_VERSION}" >> "$GITHUB_OUTPUT" - name: Build JAR with Maven (tests must be green) run: mvn -B clean package - - name: Compute Checksum + - name: Stage release files run: | - cd target - sha256sum kview-*.jar > checksums.txt - cat checksums.txt + mkdir stage + cp target/kview-*.jar stage/kview.jar + sha256sum stage/kview.jar > stage/kview.jar.sha256 + sha256sum target/kview-*.jar >> stage/kview.jar.sha256 + + - name: Upload jar artifact + uses: actions/upload-artifact@v4 + with: + name: jar + path: stage/* - - name: Create GitHub Release + - name: Create GitHub Release (jar + checksums) uses: softprops/action-gh-release@v2 with: files: | target/kview-*.jar - target/checksums.txt + stage/kview.jar.sha256 generate_release_notes: true - name: Set up QEMU @@ -83,3 +95,209 @@ jobs: push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} + + - name: Mirror the multi-arch image to Docker Hub + if: env.DOCKERHUB_USERNAME != '' + env: + DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} + DOCKERHUB_REPOSITORY: ${{ secrets.DOCKERHUB_REPOSITORY }} + VERSION: ${{ steps.version.outputs.version }} + run: | + echo "$DOCKERHUB_TOKEN" | docker login docker.io --username "$DOCKERHUB_USERNAME" --password-stdin + SOURCE="ghcr.io/${{ github.repository }}" + TARGET="docker.io/${DOCKERHUB_REPOSITORY}" + docker buildx imagetools create \ + -t "${TARGET}:${VERSION}" \ + -t "${TARGET}:latest" \ + "${SOURCE}:${VERSION}" + docker logout docker.io + + bundles: + name: Native bundle (${{ matrix.os }}) + needs: build + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + platform: linux-x64 + pack: tar + - os: macos-latest + platform: macos-aarch64 + pack: tar + - os: windows-latest + platform: windows-x64 + pack: zip + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up JDK 21 (with jpackage) + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '21' + + - name: Download jar + uses: actions/download-artifact@v4 + with: + name: jar + path: stage + + - name: Create app image with jpackage + shell: bash + run: | + VERSION="${{ needs.build.outputs.version }}" + mkdir -p appdir + cp stage/kview.jar appdir/kview.jar + jpackage \ + --type app-image \ + --name kview \ + --app-version "$VERSION" \ + --vendor "Kview" \ + --description "Kview — Apache Kafka UI, CLI and MCP server" \ + --input appdir \ + --main-jar kview.jar \ + --dest dist + + - name: Add the direct-CLI launcher (linux) + if: matrix.os == 'ubuntu-latest' + shell: bash + run: | + cp kview dist/kview/bin/kview-cli + printf '#!/usr/bin/env sh\nexec "$(cd "$(dirname "$0")" && pwd)/bin/kview" --cli "$@"\n' > dist/kview/kview + chmod +x dist/kview/kview dist/kview/bin/kview-cli + + - name: Add the direct-CLI launcher (macos) + if: matrix.os == 'macos-latest' + shell: bash + run: | + printf '#!/usr/bin/env bash\nexec "$(cd "$(dirname "$0")" && pwd)/kview.app/Contents/MacOS/kview" --cli "$@"\n' > dist/kview/kview + chmod +x dist/kview/kview + + - name: Add the direct-CLI launcher (windows) + if: matrix.os == 'windows-latest' + shell: pwsh + run: | + @' + @echo off + set "DIR=%~dp0" + "%DIR%kview.exe" --cli %* + '@ | Set-Content -Path dist/kview/kview.cmd -Encoding ascii + + - name: Build the deb package (linux) + if: matrix.os == 'ubuntu-latest' + shell: bash + run: | + VERSION="${{ needs.build.outputs.version }}" + jpackage \ + --type deb \ + --name kview \ + --app-version "$VERSION" \ + --vendor "Kview" \ + --description "Kview — Apache Kafka UI, CLI and MCP server" \ + --input appdir \ + --main-jar kview.jar \ + --dest dist-pkg + + - name: Pack the bundle (tar) + if: matrix.pack == 'tar' + shell: bash + run: | + VERSION="${{ needs.build.outputs.version }}" + tar -czf "kview-${VERSION}-${{ matrix.platform }}.tar.gz" -C dist kview + + - name: Pack the bundle (zip) + if: matrix.pack == 'zip' + shell: pwsh + run: | + $version = "${{ needs.build.outputs.version }}" + Compress-Archive -Path dist/kview -DestinationPath "kview-$version-${{ matrix.platform }}.zip" + + - name: Checksum the bundle (linux) + if: matrix.pack == 'tar' && matrix.os == 'ubuntu-latest' + shell: bash + run: sha256sum "kview-${{ needs.build.outputs.version }}-${{ matrix.platform }}.tar.gz" > "kview-${{ needs.build.outputs.version }}-${{ matrix.platform }}.tar.gz.sha256" + + - name: Checksum the bundle (macos) + if: matrix.pack == 'tar' && matrix.os == 'macos-latest' + shell: bash + run: shasum -a 256 "kview-${{ needs.build.outputs.version }}-${{ matrix.platform }}.tar.gz" > "kview-${{ needs.build.outputs.version }}-${{ matrix.platform }}.tar.gz.sha256" + + - name: Checksum the bundle (windows) + if: matrix.pack == 'zip' + shell: pwsh + run: | + $version = "${{ needs.build.outputs.version }}" + $file = "kview-$version-${{ matrix.platform }}.zip" + $hash = (Get-FileHash $file -Algorithm SHA256).Hash.ToLower() + "$hash $file" | Set-Content "$file.sha256" + + - name: Upload bundle artifact + uses: actions/upload-artifact@v4 + with: + name: bundle-${{ matrix.platform }} + path: | + kview-${{ needs.build.outputs.version }}-${{ matrix.platform }}.* + dist-pkg/*.deb + + publish: + name: Attach bundles, publish npm & Homebrew + needs: [build, bundles] + runs-on: ubuntu-latest + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + HOMEBREW_TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Node.js 20 + uses: actions/setup-node@v4 + with: + node-version: '20' + registry-url: 'https://registry.npmjs.org' + + - name: Download all bundle artifacts + uses: actions/download-artifact@v4 + with: + path: bundles + pattern: bundle-* + merge-multiple: true + + - name: Attach bundles to the release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: gh release upload "${{ github.ref_name }}" bundles/* --clobber + + - name: Publish the npm package + if: env.NPM_TOKEN != '' + run: | + cp -r cli mcp npm/ + cd npm + npm version "${{ needs.build.outputs.version }}" --no-git-tag-version --allow-same-version + npm publish --access public + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Update the Homebrew tap + if: env.HOMEBREW_TAP_TOKEN != '' + env: + VERSION: ${{ needs.build.outputs.version }} + run: | + MAC_SHA256=$(sha256sum "bundles/kview-${VERSION}-macos-aarch64.tar.gz" | cut -d' ' -f1) + LINUX_SHA256=$(sha256sum "bundles/kview-${VERSION}-linux-x64.tar.gz" | cut -d' ' -f1) + sed -e "s/{{VERSION}}/${VERSION}/g" \ + -e "s/{{MAC_SHA256}}/${MAC_SHA256}/g" \ + -e "s/{{LINUX_SHA256}}/${LINUX_SHA256}/g" \ + packaging/homebrew/kview.rb.tmpl > kview.rb + git clone "https://x-access-token:${HOMEBREW_TAP_TOKEN}@github.com/${HOMEBREW_TAP_REPO:-ritikbansod/homebrew-kview}.git" tap + mkdir -p tap/Formula + cp kview.rb tap/Formula/kview.rb + cd tap + git config user.name "kview-release-bot" + git config user.email "noreply@github.com" + git add Formula/kview.rb + git commit -m "kview ${VERSION}" + git push diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e46319..8ac26fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added +- **Distribution channels** (jar, native bundles, npm, Docker, Homebrew, SDKMAN): each release now ships jpackage native bundles with a bundled JRE for Linux (tar.gz + .deb), macOS (tar.gz) and Windows (zip) — server launcher plus a `kview` direct-CLI wrapper, no Java install needed; an npm `kview` package (thin client + `kview-mcp`, direct mode when a jar/`KVIEW_JAR` is present); Docker CLI passthrough; a Homebrew formula template pushed to the tap on release; and `docs/RELEASING.md` with the full per-channel checklist (including the manual SDKMAN submission). Release steps for npm/Homebrew activate when their repo secrets (`NPM_TOKEN`, `HOMEBREW_TAP_TOKEN`) are configured. - **Direct-mode CLI** (`java -jar kview.jar --cli ` or the shipped `kview`/`kview.cmd` wrappers): talk to a broker straight from the jar without a server — overview, topics, topic detail, browse (schema-registry decoded), groups, lag, produce, search and replay, with `--json` output and the full security flag set (SASL/mTLS/OAuth). Boots a headless (no web server) application context and reuses the same services as the UI; an optional `--schema-registry URL` attaches a volatile registry for decoding. The thin REST client (`cli/kview.mjs`) remains for CI/remote use. - **Protobuf decode and encode for Schema Registry topics** (F4): PROTOBUF subjects now decode in the browser, live tail and `/decode` endpoint into proto3 JSON (original field names), and JSON payloads encode into Confluent wire bytes (message-index prefix included) on the produce path. `.proto` text from the registry is compiled in-process via `kafka-protobuf-provider` (Confluent Community License, resolved from `packages.confluent.io`); compiled schemas are TTL-cached. Multi-message `.proto` files select the message via the wire prefix or an optional `messageName` on `/encode`. Nested-message indexes are not supported yet. - **Prometheus metrics** (F6): `/actuator/prometheus` now serves `kview_*` series — per-cluster topic/partition counts, under-replicated and offline partitions, reachability (`kview_cluster_*` with a `cluster` tag), per-group consumer lag (`kview_consumer_group_lag` with `cluster`/`group` tags) and a produced-messages counter (`kview_messages_produced_total`). A 15-second in-process sampler (`KVIEW_METRICS_INTERVAL_MS`) keeps scrapes off the broker; series appear and disappear with the clusters and groups they describe. Endpoint follows the actuator auth rules (public in the default no-auth mode). diff --git a/README.md b/README.md index 2bfc815..fc0f30a 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,16 @@ Confluent Platform, Redpanda, Amazon MSK, Azure Event Hubs, Aiven and others. Se support covers PLAINTEXT, TLS, mTLS and SASL (PLAIN / SCRAM-SHA-256 / SCRAM-SHA-512 / OAUTHBEARER). [COMPATIBILITY.md](COMPATIBILITY.md) has the exact settings per distribution. +## Install + +| Channel | Get it | Needs | +|---|---|---| +| **JAR** | [Releases](https://github.com/ritikbansod/kview/releases) → `kview.jar` — starts the server UI, or the direct CLI: `java -jar kview.jar --cli topics --bootstrap-server localhost:9092` | Java 21+ | +| **Native bundles** | Releases → `kview--linux-x64.tar.gz` / `kview--macos-aarch64.tar.gz` / `kview--windows-x64.zip` (also a Linux `.deb`) — bundled JRE, server launcher **and** `kview` CLI | nothing | +| **npm** | `npm install -g kview` → `kview` (thin client; direct mode when a jar/`KVIEW_JAR` is present) and `npx kview-mcp` | Node 18+ | +| **Docker** | `docker run -d -p 8090:8090 ghcr.io/ritikbansod/kview:latest` — CLI passthrough: `docker run --rm ghcr.io/ritikbansod/kview --cli topics --bootstrap-server host:9092` — also mirrored to Docker Hub on release (see [docs/RELEASING.md](docs/RELEASING.md)) | Docker | +| **Homebrew** | tap `ritikbansod/homebrew-kview`, then `brew install kview` | macOS/Linux | + Browsing and tailing are read-only: Kview never commits offsets and never joins a real consumer group, so it is safe to point at production topics. @@ -283,6 +293,8 @@ agent's writes are distinguishable from a human's in the audit log. Bug reports, suggestions, and pull requests are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for development guidelines. If you want to check compatibility against a distribution, `node scripts/verify-compatibility.mjs` re-runs the verification matrix against a local broker. +Maintainers: cutting a release is a version bump + one tag — the full per-channel checklist +lives in [docs/RELEASING.md](docs/RELEASING.md). ## License diff --git a/docs/RELEASE-NOTES-v1.1.0-draft.md b/docs/RELEASE-NOTES-v1.1.0-draft.md new file mode 100644 index 0000000..a10aef0 --- /dev/null +++ b/docs/RELEASE-NOTES-v1.1.0-draft.md @@ -0,0 +1,134 @@ +# Kview v1.1.0 — Release notes (final draft) + +> **Status:** draft. Everything below is implemented across ten stacked pull requests +> (#1–#10) that originated in the v1.0.0 deep analysis. Merge them in order (#1 → #10), +> follow [RELEASING.md](RELEASING.md), then tag `v1.1.0`. + +--- + +## Headline + +Kview v1.0.0 shipped as a working tool with known sharp edges. v1.1.0 turns it into a +defensible open-source project: the wide-open API is now secured, the AI/MCP surface got a +purpose-built safety pack, two flagship features landed (whole-topic search and DLQ replay), +Protobuf works end to end, the CLI runs standalone without a server, and Kview is +installable through every standard channel — native bundles, npm, Docker Hub, Homebrew. +Underneath, the runtime was brought current (Spring Boot 3.5.16 / kafka-clients 3.9.2) and +all **18 independently verified issues** from the analysis were resolved. The test suite +grew from **30 to 66 tests** across **18 classes**, all green on the Linux + Windows CI +matrix. Six of the analysis's seven top-ranked feature candidates shipped (the seventh, +the schema registry browser, is the remaining roadmap item). + +## 1. Security & safety + +- **API authentication with roles** (was: completely unauthenticated with permissive CORS — + the analysis's only high-severity code finding). `KVIEW_AUTH_MODE=token` (static bearer + tokens) or `oidc` (JWTs from any issuer), each with an **admin** and a **read-only** + role: reads for everyone, mutations admin-only. CORS closes when auth is on; the default + remains zero-config and open on loopback, regression-tested. +- **AI/MCP safety pack**: global read-only mode (`KVIEW_READONLY=true` — every mutation + refused, UI banner), an append-only mutation **audit trail** (`KVIEW_AUDIT=true` → + `data/audit.log`, with every client identifying itself via `X-Kview-Client`, so an + agent's writes are provably distinguishable from a human's), and a per-tool allowlist for + the MCP server (`KVIEW_ALLOWED_TOOLS`). +- MCP hardening: the server no longer crashes on startup for Node ≤ 22.6 (ESM fix), and + destructive MCP calls keep their `confirm=true` guard. + +## 2. New features + +- **Whole-topic message search** — the biggest explorer gap. A bounded background job scans + every partition (regex / contains / key filters, timestamp window; max 1000 results, + 2M scanned, 3 concurrent jobs) with progress and cancellation. Available in the UI + (**Search** tab), CLI, and MCP (`kview_search_messages` + `kview_search_status`). +- **DLQ triage & bulk replay** — copy filtered messages to another topic, on the same or a + different connected cluster, preserving keys, headers and original timestamps; `dryRun` + previews the selection. UI (**Replay** tab), CLI, MCP (with `confirm=true`). +- **Direct-mode CLI** — `java -jar kview.jar --cli topics --bootstrap-server localhost:9092` + talks to the broker with **no server**, reusing the same services: browse comes out + schema-registry decoded, plus overview/topics/groups/lag/produce/search/replay, `--json` + output and the full SASL/mTLS/OAuth flag set. Shipped `kview` / `kview.cmd` wrappers. +- **Protobuf codecs** — PROTOBUF subjects decode to proto3 JSON in the browser, live tail + and `/decode`, and JSON payloads encode into Confluent wire bytes (message-index prefix + included) on produce. Nested-message indexes are reported as unsupported rather than + mis-decoded. +- **Prometheus metrics** — `/actuator/prometheus` serves `kview_*` series: per-cluster + topics/partitions, under-replicated and offline partitions, reachability, per-group + consumer lag, and a produced-messages counter. A 15-second in-process sampler keeps + scrapes off the broker. + +## 3. Distribution & install + +| Channel | What you get | +|---|---| +| **JAR** (GitHub Releases) | tested `kview.jar` — starts the server UI *or* the direct CLI | +| **Native bundles** | `kview--linux-x64.tar.gz` (+ `.deb`), `kview--macos-aarch64.tar.gz`, `kview--windows-x64.zip` — bundled JRE, server launcher **and** `kview` CLI; no Java install needed | +| **npm** | `npm install -g kview` → thin client + `npx kview-mcp`; direct mode when a jar/`KVIEW_JAR` is present | +| **Docker** | `ghcr.io/ritikbansod/kview:{v,latest}` (amd64+arm64), mirrored to **Docker Hub** on release; CLI passthrough: `docker run --rm ghcr.io/ritikbansod/kview --cli topics --bootstrap-server host:9092` | +| **Homebrew / SDKMAN** | formula template pushed to the tap on release; SDKMAN submission documented | + +The release pipeline is fully automated in three jobs (build → per-OS jpackage bundles → +publish), with npm/Homebrew/Docker-Hub steps activating only when their repo secrets are +configured. The full per-channel checklist lives in [RELEASING.md](RELEASING.md). + +## 4. Fixed (verified issues from the deep analysis — all 18 resolved) + +- Leaked consumer containers from a subscribe race; per-group buffers freed on unsubscribe; + containers stopped on shutdown. +- Data race on the schema-registry settings map (hot decode path) — now a `ConcurrentHashMap`. +- A slow or unreachable schema registry stalled every decode cluster-wide — cache loads are + per-key now. +- `KafkaClusterManager` get/evict could hand out a handle being closed — mutually excluded. +- Integration tests overrode a wrong property name and rewrote the developer's real `./data` + on every run — both suites now use `target/`. +- `kview lag` read a non-existent API field (silent zero lag rows); `kview clusters` tabled a + field the API never returned; the README's `reset-offsets` example failed as written. +- Doc drift: README/COMPATIBILITY no longer claim the shipped Schema Registry support is + "on the roadmap"; the divergent legacy `kview-cli.js` was removed; internal runbooks moved + out of the repo root. +- Release trust: releases build **with the test suite green**, the release tag must match the + versions in `pom.xml`/`cli`/`mcp`, CI runs the MCP server and CLI smokes, and the Maven + wrapper ships so a clone builds with just a JDK. + +## 5. Platform + +- Spring Boot **3.3.4 → 3.5.16** (the 3.3 line is past OSS support end): Spring Framework + 6.2.x, Spring Security 6.5.x, spring-kafka 3.3.x, micrometer 1.15.x, **kafka-clients + 3.9.2**. Full suite green with zero test changes. + +## 6. Upgrading from v1.0.0 + +- **Nothing breaks by default**: authentication and the safety switches are off, the config + surface is additive, and the default-mode behavior is regression-tested. +- New optional env vars: `KVIEW_AUTH_MODE`, `KVIEW_AUTH_TOKENS`, `KVIEW_AUTH_ALLOWED_ORIGINS`, + `KVIEW_READONLY`, `KVIEW_AUDIT`, `KVIEW_METRICS_INTERVAL_MS`. +- The legacy `kview-cli.js` is gone — `cli/kview.mjs` (thin client) and the jar's direct mode + are the two supported CLIs. +- Docker users: image available from GHCR (as before) and Docker Hub (once release secrets + are configured). + +## 7. Quality statement + +- Every one of the 18 issues was **independently reproduced** before being fixed, and every + feature was **feasibility-checked against the code** by a dedicated reviewer. +- **66/66 tests green** across 18 classes on the ubuntu + windows CI matrix, including + embedded-Kafka integration suites for browse, search, replay, protobuf, auth, audit, + read-only mode, metrics and the direct CLI. +- The auth role matrix was additionally verified against the packaged jar over live HTTP, + and the jpackage bundled-runtime launcher was smoke-tested with no system Java present. +- Not yet verified: a live Confluent Cloud/Strimzi registry round-trip for protobuf, and the + packaging pipeline's first real tagged run (recommend `v1.0.1-rc.1`). + +## 8. Remaining roadmap + +- **Schema registry browser** (version diff, compatibility mode, upload dry-run) — the one + feature candidate not yet built. +- Nested protobuf message indexes (rare subjects). +- `docs/RELEASING.md` secrets (`NPM_TOKEN`, `HOMEBREW_TAP_TOKEN`) to activate npm/Homebrew, + and the SDKMAN submission after the first release. +- COMPATIBILITY matrix re-verification against live distributions on kafka-clients 3.9.2. + +## 9. For maintainers + +- Merge order: #1 trust fixes → #2 auth → #3 safety pack → #4 search → #5 replay → + #6 metrics → #7 Boot 3.5 → #8 protobuf → #9 direct CLI → #10 packaging. +- Then: [RELEASING.md](RELEASING.md) — bump the three versions, roll the CHANGELOG, tag. diff --git a/docs/RELEASE-PLAN.md b/docs/RELEASE-PLAN.md index a7b94f7..8c3e213 100644 --- a/docs/RELEASE-PLAN.md +++ b/docs/RELEASE-PLAN.md @@ -1,3 +1,10 @@ +> [!ARCHIVE] +> **This is the v1.0.0 release runbook (2026-09-12), kept for history.** Its items are all +> closed — license, artifacts, Docker, CI, QA. For cutting a release today, follow +> [RELEASING.md](RELEASING.md) instead. + +--- + # Kview — First Public Release Analysis & Runbook **Date:** 2026-09-12 · **Scope:** what it takes to cut v1 so users can actually *start using* Kview. diff --git a/docs/RELEASING.md b/docs/RELEASING.md new file mode 100644 index 0000000..cadcac0 --- /dev/null +++ b/docs/RELEASING.md @@ -0,0 +1,40 @@ +# Releasing Kview + +Cutting a release is a version bump + one tag; the release workflow does the rest. + +## One-time setup (per channel) + +| Channel | Setup | Notes | +|---|---|---| +| Docker (GHCR) | nothing | pushes with `GITHUB_TOKEN` on every tag | +| Docker Hub | repo secrets `DOCKERHUB_USERNAME`, `DOCKERHUB_TOKEN` (a Docker Hub access token), `DOCKERHUB_REPOSITORY` (e.g. `youruser/kview`) | the multi-arch image is **mirrored from GHCR** with `buildx imagetools` — same digests, no rebuild; the step is skipped while the secrets are absent | +| GitHub bundles | nothing | jar + Linux/macOS/Windows jpackage bundles + Linux `.deb` attach to the release | +| npm | add a repo secret `NPM_TOKEN` (an npm automation token for the `kview` package) | the `Publish the npm package` step is skipped while the secret is absent | +| Homebrew | create a tap repo (default `ritikbansod/homebrew-kview`) and add repo secrets `HOMEBREW_TAP_TOKEN` (a fine-grained PAT with write access to the tap) and optionally `HOMEBREW_TAP_REPO` | the formula is rendered from `packaging/homebrew/kview.rb.tmpl` and pushed to `tap/Formula/kview.rb` | +| SDKMAN | **deferred** — nothing to configure; submit later at https://vendors.sdkman.io (candidate `kview`, version, `https://github.com/ritikbansod/kview/releases/download/vX.Y.Z/kview-X.Y.Z-linux-x64.tar.gz`) if/when there is user demand | manual, one form per release; the release pipeline has no dependency on it | + +## Release checklist + +1. Bump the version in **three** places — the workflow guard enforces all of them: + - `pom.xml` `` + - `cli/kview.mjs` `const VERSION` + - `mcp/kview-mcp.js` `const VERSION` +2. Update `CHANGELOG.md` (move `[Unreleased]` to the version + date). +3. Commit, then `git tag vX.Y.Z && git push origin vX.Y.Z`. + +## What the workflow produces + +- **jar**: tested build (`mvn clean package`, tests green) attached to the release with checksums; tag/version guard fails the release on mismatch. +- **Docker**: `ghcr.io/ritikbansod/kview:{version,latest}` for `linux/amd64` + `arm64`. The image also serves the direct CLI: `docker run --rm ghcr.io/ritikbansod/kview --cli topics --bootstrap-server host:9092`. +- **Native bundles** (jpackage app-images with a bundled JRE — no Java install needed): + - `kview-X.Y.Z-linux-x64.tar.gz` (+ `kview_X.Y.Z_amd64.deb`) + - `kview-X.Y.Z-macos-aarch64.tar.gz` (unsigned — `xattr -cr kview.app` on first run if Gatekeeper complains) + - `kview-X.Y.Z-windows-x64.zip` + Each bundle contains the server launcher (`bin/kview` / `kview.exe`) and a `kview`/`kview-cli` direct-CLI wrapper. +- **npm**: the `kview` package (thin client + `kview-mcp`) is published with the release version; the dispatcher auto-uses a local jar for direct mode when present. + +## After the release + +1. Verify the release assets and the Docker tags. +2. Submit the SDKMAN candidate (see the table above). +3. Announce per `docs/RELEASE-PLAN.md`. diff --git a/npm/README.md b/npm/README.md new file mode 100644 index 0000000..c0165fe --- /dev/null +++ b/npm/README.md @@ -0,0 +1,16 @@ +# kview (npm package) + +Installs two commands: + +- **`kview`** — the Kview CLI. + - **Thin client (default):** talks to a running Kview server — `KVIEW_URL` (default + `http://localhost:8090`). Zero dependencies, Node 18+. + - **Direct mode (no server):** when a kview jar is reachable — `KVIEW_JAR=/path/to/kview.jar` + or a `kview-*.jar` in the current directory — commands run straight against the broker + (needs Java 21): `kview topics --bootstrap-server localhost:9092`. +- **`kview-mcp`** — the Model Context Protocol server for AI assistants + (Claude Desktop, Cursor, ZCode): `npx kview-mcp` with `KVIEW_URL`/`KVIEW_TOKEN`/`KVIEW_CLUSTER` + env vars. + +The npm package ships the thin client and MCP server only; the full server, the direct-mode +jar and native bundles come from the [GitHub releases](https://github.com/ritikbansod/kview/releases). diff --git a/npm/bin/kview-mcp.mjs b/npm/bin/kview-mcp.mjs new file mode 100644 index 0000000..e6efea7 --- /dev/null +++ b/npm/bin/kview-mcp.mjs @@ -0,0 +1,9 @@ +#!/usr/bin/env node +// kview-mcp npm bin — runs the bundled MCP server (npx kview-mcp). +import { spawnSync } from 'node:child_process'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const server = join(dirname(fileURLToPath(import.meta.url)), '..', 'mcp', 'kview-mcp.js'); +const result = spawnSync(process.execPath, [server, ...process.argv.slice(2)], { stdio: 'inherit' }); +process.exit(result.status ?? 1); diff --git a/npm/bin/kview.mjs b/npm/bin/kview.mjs new file mode 100644 index 0000000..c2c5ca6 --- /dev/null +++ b/npm/bin/kview.mjs @@ -0,0 +1,44 @@ +#!/usr/bin/env node +// kview npm dispatcher. +// +// Two modes: +// - Direct mode (no server): when a kview jar is available — KVIEW_JAR env or a +// kview-*.jar in the current directory — commands run against the broker with +// the bundled-Java launcher (needs Java 21). +// - Thin client (default): talks to a running Kview server (KVIEW_URL, default +// http://localhost:8090). Zero dependencies. +import { readdirSync, existsSync } from 'node:fs'; +import { spawnSync } from 'node:child_process'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const here = dirname(fileURLToPath(import.meta.url)); +const thinClient = join(here, '..', 'cli', 'kview.mjs'); +const args = process.argv.slice(2); + +function findJar() { + if (process.env.KVIEW_JAR) return process.env.KVIEW_JAR; + if (!existsSync(process.cwd())) return null; + const jars = readdirSync(process.cwd()).filter((f) => /^kview-.+\.jar$/.test(f)).sort(); + return jars.length ? join(process.cwd(), jars[jars.length - 1]) : null; +} + +const jar = findJar(); +if (jar) { + const result = spawnSync('java', ['-jar', jar, '--cli', ...args], { stdio: 'inherit' }); + if (result.error) { + console.error(`error: could not run java for direct mode (${result.error.message}) — falling back to the thin client`); + } else { + process.exit(result.status ?? 1); + } +} + +if (args.includes('--bootstrap-server')) { + console.error('error: --bootstrap-server needs direct mode — download the kview jar from'); + console.error(' https://github.com/ritikbansod/kview/releases and set KVIEW_JAR (Java 21 required),'); + console.error(' or run the Kview server and use it without --bootstrap-server.'); + process.exit(1); +} + +const result = spawnSync(process.execPath, [thinClient, ...args], { stdio: 'inherit' }); +process.exit(result.status ?? 1); diff --git a/npm/package.json b/npm/package.json new file mode 100644 index 0000000..75ac334 --- /dev/null +++ b/npm/package.json @@ -0,0 +1,31 @@ +{ + "name": "kview", + "version": "1.0.0", + "description": "Kview CLI and MCP server for Apache Kafka — thin client for a Kview server, plus the MCP server for AI assistants. Direct-to-broker mode works with the kview jar (KVIEW_JAR).", + "license": "Apache-2.0", + "homepage": "https://github.com/ritikbansod/kview", + "repository": { + "type": "git", + "url": "https://github.com/ritikbansod/kview" + }, + "engines": { + "node": ">=18" + }, + "bin": { + "kview": "bin/kview.mjs", + "kview-mcp": "bin/kview-mcp.mjs" + }, + "files": [ + "bin/", + "cli/", + "mcp/", + "README.md" + ], + "keywords": [ + "kafka", + "cli", + "mcp", + "model-context-protocol", + "apache-kafka" + ] +} diff --git a/packaging/homebrew/kview.rb.tmpl b/packaging/homebrew/kview.rb.tmpl new file mode 100644 index 0000000..db0b120 --- /dev/null +++ b/packaging/homebrew/kview.rb.tmpl @@ -0,0 +1,43 @@ +class Kview < Formula + desc "Apache Kafka UI, CLI and MCP server — direct-mode CLI with a bundled JRE" + homepage "https://github.com/ritikbansod/kview" + version "{{VERSION}}" + license "Apache-2.0" + + on_macos do + url "https://github.com/ritikbansod/kview/releases/download/v{{VERSION}}/kview-{{VERSION}}-macos-aarch64.tar.gz" + sha256 "{{MAC_SHA256}}" + end + + on_linux do + url "https://github.com/ritikbansod/kview/releases/download/v{{VERSION}}/kview-{{VERSION}}-linux-x64.tar.gz" + sha256 "{{LINUX_SHA256}}" + end + + def install + libexec.install Dir["*"] + launcher = OS.mac? ? libexec/"kview.app/Contents/MacOS/kview" : libexec/"bin/kview" + + (bin/"kview").write <<~EOS + #!/bin/bash + exec "#{launcher}" --cli "$@" + EOS + + (bin/"kview-server").write <<~EOS + #!/bin/bash + exec "#{launcher}" "$@" + EOS + end + + def caveats + <<~EOS + `kview` runs the direct-mode CLI against your brokers (bundled JRE — no Java install needed). + `kview-server` starts the Kview web UI on port 8090. + A .deb package is also attached to each GitHub release for dpkg-based systems. + EOS + end + + test do + assert_match "kview", shell_output("#{bin}/kview --version") + end +end diff --git a/src/main/java/com/ritikbansod/kafkawrapper/cli/CliMain.java b/src/main/java/com/ritikbansod/kafkawrapper/cli/CliMain.java index 3eeeaa2..1c052dc 100644 --- a/src/main/java/com/ritikbansod/kafkawrapper/cli/CliMain.java +++ b/src/main/java/com/ritikbansod/kafkawrapper/cli/CliMain.java @@ -51,6 +51,10 @@ public static int run(String[] args) { } public static int run(String[] args, PrintStream out, PrintStream err) { + if (args.length > 0 && (args[0].equals("--version") || args[0].equals("-v"))) { + out.println("kview " + version()); + return 0; + } if (args.length == 0 || args[0].equals("help") || args[0].equals("--help")) { printHelp(out); return args.length == 0 ? 2 : 0; @@ -338,6 +342,12 @@ private static String rootMessage(Throwable e) { return t.getMessage() == null ? t.getClass().getSimpleName() : t.getMessage(); } + private static String version() { + Package pkg = CliMain.class.getPackage(); + return pkg != null && pkg.getImplementationVersion() != null + ? pkg.getImplementationVersion() : "dev"; + } + private static void printHelp(PrintStream out) { out.println(""" kview CLI (direct mode) — talks to a Kafka broker without a server.