Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,25 @@ doriscli tablet db.orders --detail

---

## Install

The recommended way to install doris-cli is from **npm** — it ships prebuilt
binaries, so there is no Rust toolchain and no compile step:

```bash
npm install -g @apache-doris/doriscli
doriscli --version
```

On install, npm pulls **only** the binary that matches your OS + CPU (via
`optionalDependencies` + `os`/`cpu` constraints). Supported platforms: macOS
(arm64), Linux (x64, arm64). The published package is scoped
(`@apache-doris/doriscli`), but the installed command is just `doriscli`.

For any other platform — or to hack on doris-cli itself — build from source below.

---

## Build

Requires a recent stable Rust toolchain (verified on rustc 1.87; the crate uses the
Expand Down
159 changes: 159 additions & 0 deletions RELEASING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
# Releasing Apache Doris CLI

This is the release manager's guide to publishing a new version of the Doris CLI
to npm. The CLI is distributed as **prebuilt npm packages** under the
`@apache-doris` scope; end users install it with
`npm install -g @apache-doris/doriscli`.

The whole build-and-publish pipeline is automated in
[`.github/workflows/release-npm.yml`](.github/workflows/release-npm.yml). A
release manager's job is to bump the version, validate with a dry run, and push
a tag — the workflow does the rest.

## What gets published

A release publishes four packages, all at the same version:

| Package | Contents |
|---|---|
| `@apache-doris/doriscli` | The launcher + `optionalDependencies` on the three platform packages. **This is what users install.** |
| `@apache-doris/doriscli-darwin-arm64` | Prebuilt binary for macOS arm64 |
| `@apache-doris/doriscli-linux-x64` | Prebuilt binary for Linux x64 |
| `@apache-doris/doriscli-linux-arm64` | Prebuilt binary for Linux arm64 |

On install, npm resolves **only** the platform package matching the user's
OS + CPU (via `optionalDependencies` plus each platform package's `os`/`cpu`
fields); the launcher then execs that binary. The set of platforms lives in
`npm/doriscli/platforms.js` — the single source of truth shared by the launcher
and the packaging script.

## Versioning

**The published version is read from `Cargo.toml` (`[package].version`)** by
`npm/build-packages.cjs`, so npm always matches the crate. The git tag does not
set the version — it only has to match it.

npm versions are **immutable**: you cannot re-publish a version. Every release is
a new version number, and a broken release is fixed by shipping the next patch
(see [Fixing a broken release](#fixing-a-broken-release)).

## One-time prerequisites

- An npm account in the **`apache-doris`** npm organization with publish rights to
the `@apache-doris` scope.
- Repository secret **`NPM_TOKEN`** set to a **Classic _Automation_ token**
(npmjs.com → Access Tokens → Generate New Token → Classic Token → **Automation**).
The org enforces two-factor auth for writes, and an Automation token is the type
that bypasses 2FA in CI. A granular or read-only token will fail — see
[Troubleshooting](#troubleshooting).
- Permission to push tags to `apache/doris-cli`.

## Release steps

### 1. Bump the version

Edit `Cargo.toml`:

```toml
[package]
version = "X.Y.Z"
```

Refresh the lockfile so the CI gate (which runs `--locked`) stays green, then
commit both files and merge to `main`:

```bash
cargo check # rewrites Cargo.lock to match the new version
git add Cargo.toml Cargo.lock && git commit -m "release X.Y.Z"
```

### 2. Dry-run the pipeline (recommended)

From the GitHub UI: **Actions → release-npm → Run workflow**, leaving
**`dry_run` ☑ checked** (the default). This builds every platform, assembles all
packages, and runs `npm publish --dry-run` — **no token is used and nothing is
published.** Confirm the run is green.

CLI equivalent:

```bash
gh workflow run release-npm.yml -f dry_run=true
```

### 3. Tag and push (the real publish)

```bash
git tag -a vX.Y.Z -m "doriscli X.Y.Z"
git push <apache-remote> vX.Y.Z # the remote pointing at apache/doris-cli
```

The `v*.*.*` tag triggers `release-npm`, which builds all three platforms and
publishes. Platform packages publish first; the main package publishes last, so
its `optionalDependencies` already resolve on the registry.

### 4. Verify

After the run is green:

```bash
npm view @apache-doris/doriscli dist-tags # `latest` should be X.Y.Z

# install into a throwaway dir and actually run the binary
tmp=$(mktemp -d) && ( cd "$tmp" \
&& npm install @apache-doris/doriscli \
&& ./node_modules/.bin/doriscli --version )
rm -rf "$tmp"
```

npm moves the `latest` dist-tag to the highest version automatically, so a plain
`npm install -g @apache-doris/doriscli` picks up the new release.

## How the pipeline works

`release-npm.yml` has two jobs:

- **build** — a matrix over the three platforms. Each runner does
`cargo build --release`, then `node npm/build-packages.cjs platform <key> <binary>`
assembles that platform package and uploads it as an artifact.
- **publish** — downloads the artifacts, **re-applies the executable bit**,
assembles the main package, and `npm publish`es each one.

> ⚠️ **Do not remove the `chmod 0755` step in the publish job.**
> `actions/upload-artifact` strips the Unix executable bit, so the prebuilt
> binary arrives in the publish job as `0644`. The publish job re-runs
> `chmod 0755` on each binary *before* `npm publish`; without it, npm ships a
> non-executable binary and the CLI fails at runtime with `EACCES`. (This is
> exactly how `0.1.0` shipped broken.)

### Actions allowlist

`apache/doris-cli` is under the Apache org's GitHub Actions allowlist: only
GitHub-owned actions (`actions/*`) and **local** actions (`./.github/actions/...`)
are permitted. That's why the Rust toolchain setup is a local composite action
(`.github/actions/setup-rust-toolchain`) instead of a third-party one. **Do not
add third-party actions** to the workflow — they will be rejected.

## Troubleshooting

| Symptom | Cause / fix |
|---|---|
| `403 Forbidden ... You may not perform that action with these credentials` | The token can't publish that name. Ensure package names are scoped to `@apache-doris` and `NPM_TOKEN` has publish rights to the scope. |
| `403 ... Two-factor authentication ... required` | `NPM_TOKEN` doesn't bypass 2FA. Use a **Classic Automation** token, not a granular or read-only one. |
| CLI fails with `EACCES` after install | The published binary isn't executable — the `chmod 0755` step in the publish job is missing or didn't match the binary path. |
| CI fails with a lockfile / `--locked` error | `Cargo.lock` is out of sync after the version bump. Run `cargo check` (rewrites the lock) and commit `Cargo.lock`. |
| Want to add a platform | Add it to `npm/doriscli/platforms.js` **and** the build matrix in `release-npm.yml`. |

## Fixing a broken release

A published version is immutable, so fix it by publishing the next patch version.
Then steer users off the bad one:

```bash
for p in doriscli doriscli-darwin-arm64 doriscli-linux-x64 doriscli-linux-arm64; do
npm deprecate @apache-doris/$p@<bad-version> "broken; use <good-version>+"
done
```

If the bad version is only minutes old and almost certainly uninstalled, you can
instead `npm unpublish @apache-doris/<pkg>@<bad-version>` for each package
(npm allows single-version unpublish within 72 hours).
Loading