This is an automated email from the ASF dual-hosted git repository.
morningman pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/doris-cli.git
The following commit(s) were added to refs/heads/main by this push:
new a9c7bef build(npm): publish doriscli to npm via prebuilt per-platform
packages (#2)
a9c7bef is described below
commit a9c7befa0cc3cfdc02e95e35d39813a099256bff
Author: Mingyu Chen (Rayner) <[email protected]>
AuthorDate: Sat May 30 11:27:08 2026 +0800
build(npm): publish doriscli to npm via prebuilt per-platform packages (#2)
Distribute the Rust binary through npm using the esbuild model: a thin
`doriscli` launcher package resolves and execs the prebuilt binary shipped
in a matching optional platform sub-package (doriscli-<os>-<cpu>), which npm
selects via os/cpu constraints — no Rust toolchain or build step on install.
- npm/doriscli: launcher + shared platform table + package template + readme
- npm/build-packages.cjs: assembles npm/dist/* (version single-sourced from
Cargo.toml)
- .github/workflows/release-npm.yml: cross-compile 5 targets, publish on a
v* tag;
manual runs default to a --dry-run validation (no token, no version
consumed)
Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
---
.github/workflows/release-npm.yml | 108 ++++++++++++++++++++++++++++++++++++++
.gitignore | 3 ++
npm/build-packages.cjs | 99 ++++++++++++++++++++++++++++++++++
npm/doriscli/README.md | 38 ++++++++++++++
npm/doriscli/bin/doriscli | 46 ++++++++++++++++
npm/doriscli/package.json | 14 +++++
npm/doriscli/platforms.js | 16 ++++++
7 files changed, 324 insertions(+)
diff --git a/.github/workflows/release-npm.yml
b/.github/workflows/release-npm.yml
new file mode 100644
index 0000000..acb5167
--- /dev/null
+++ b/.github/workflows/release-npm.yml
@@ -0,0 +1,108 @@
+name: release-npm
+
+# Cross-compiles doriscli for every supported platform, assembles the npm
+# packages, and publishes them. Triggered by pushing a v* tag (whose number
must
+# match the version in Cargo.toml) or run manually.
+#
+# Prerequisite: add an npm automation token as the repo secret `NPM_TOKEN`
+# (npmjs.com → Access Tokens → Generate → "Automation").
+
+on:
+ workflow_dispatch:
+ inputs:
+ dry_run:
+ description: "Validate build + packaging without publishing (npm
publish --dry-run)"
+ type: boolean
+ default: true
+ push:
+ tags: ["v*.*.*"]
+
+permissions:
+ contents: read
+
+jobs:
+ build:
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - { runner: macos-14, key: darwin-arm64, target:
aarch64-apple-darwin }
+ - { runner: macos-13, key: darwin-x64, target: x86_64-apple-darwin }
+ - { runner: ubuntu-latest, key: linux-x64, target:
x86_64-unknown-linux-gnu }
+ # If arm64 Linux runners aren't available to this repo, swap to the
+ # `cross` tool or houseabsolute/actions-rust-cross for this entry.
+ - { runner: ubuntu-24.04-arm, key: linux-arm64, target:
aarch64-unknown-linux-gnu }
+ - { runner: windows-latest, key: win32-x64, target:
x86_64-pc-windows-msvc }
+ runs-on: ${{ matrix.runner }}
+ steps:
+ - uses: actions/checkout@v4
+ - uses: dtolnay/rust-toolchain@stable
+ with:
+ targets: ${{ matrix.target }}
+ # The crate builds OpenSSL from source (openssl `vendored`); on Windows
that
+ # needs NASM + Perl (Perl ships on the windows runner).
+ - if: runner.os == 'Windows'
+ uses: ilammy/setup-nasm@v1
+ - uses: actions/setup-node@v4
+ with:
+ node-version: 20
+ - run: cargo build --release --target ${{ matrix.target }}
+ - name: Assemble platform package
+ shell: bash
+ run: |
+ BIN="target/${{ matrix.target }}/release/doriscli"
+ [ "${{ runner.os }}" = "Windows" ] && BIN="${BIN}.exe"
+ node npm/build-packages.cjs platform ${{ matrix.key }} "$BIN"
+ - uses: actions/upload-artifact@v4
+ with:
+ name: pkg-${{ matrix.key }}
+ path: npm/dist/doriscli-${{ matrix.key }}
+ if-no-files-found: error
+
+ publish:
+ needs: build
+ runs-on: ubuntu-latest
+ env:
+ # Manual runs default to a dry run (validate without publishing); a v*
tag
+ # push does a real publish. --dry-run needs no token, so you can validate
+ # the whole pipeline before NPM_TOKEN is even set.
+ DRYRUN: ${{ (github.event_name == 'workflow_dispatch' && inputs.dry_run)
&& '--dry-run' || '' }}
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version: 20
+ registry-url: https://registry.npmjs.org
+ - uses: actions/download-artifact@v4
+ with:
+ path: npm/dist
+ pattern: pkg-*
+ - name: Arrange dist dirs
+ shell: bash
+ run: |
+ set -e
+ cd npm/dist
+ # download-artifact nests each artifact under its name (pkg-<key>);
the
+ # generator + main package expect doriscli-<key>.
+ for d in pkg-*/; do
+ dest="doriscli-${d#pkg-}"
+ rm -rf "${dest%/}"
+ mv "$d" "${dest%/}"
+ done
+ ls -la
+ - name: Assemble main package
+ run: node npm/build-packages.cjs main
+ - name: Publish platform packages
+ env:
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
+ run: |
+ set -e
+ echo "DRYRUN='${DRYRUN}' (empty = real publish)"
+ for d in npm/dist/doriscli-*/; do
+ npm publish "$d" --access public $DRYRUN
+ done
+ - name: Publish main package
+ env:
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
+ # Published last so the optionalDependencies already exist on the
registry.
+ run: npm publish npm/dist/doriscli --access public $DRYRUN
diff --git a/.gitignore b/.gitignore
index f967642..168f6f0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,6 @@
# e2e test harness: local connection config + run logs
/tests/e2e/cluster.env
/tests/e2e/results/
+
+# npm packaging: generated, publishable packages (built by
npm/build-packages.cjs)
+/npm/dist/
diff --git a/npm/build-packages.cjs b/npm/build-packages.cjs
new file mode 100644
index 0000000..8e906bc
--- /dev/null
+++ b/npm/build-packages.cjs
@@ -0,0 +1,99 @@
+#!/usr/bin/env node
+"use strict";
+/*
+ * Assemble the publishable npm packages into npm/dist/.
+ *
+ * node npm/build-packages.cjs platform <key> <path-to-binary> # one
platform sub-package
+ * node npm/build-packages.cjs main # the main
"doriscli" package
+ *
+ * The version is single-sourced from Cargo.toml so npm always matches the
crate.
+ * npm/doriscli/ holds the authored sources; ALWAYS publish from npm/dist/*.
+ */
+const fs = require("fs");
+const path = require("path");
+const { PLATFORMS } = require("./doriscli/platforms.js");
+
+const NPM_DIR = __dirname; // npm/
+const ROOT = path.join(NPM_DIR, ".."); // repo root
+const SRC_MAIN = path.join(NPM_DIR, "doriscli"); // authored main package
+const DIST = path.join(NPM_DIR, "dist");
+
+function crateVersion() {
+ const toml = fs.readFileSync(path.join(ROOT, "Cargo.toml"), "utf8");
+ // First line that starts with `version = "..."` is the [package] version
+ // (dependency lines start with the dep name, never with `version`).
+ const m = toml.match(/^\s*version\s*=\s*"([^"]+)"/m);
+ if (!m) throw new Error("could not find a [package] version in Cargo.toml");
+ return m[1];
+}
+
+function freshDir(p) {
+ fs.rmSync(p, { recursive: true, force: true });
+ fs.mkdirSync(p, { recursive: true });
+}
+
+function buildPlatform(key, binPath) {
+ const entry = PLATFORMS[key];
+ if (!entry) {
+ throw new Error(`unknown platform "${key}". Known:
${Object.keys(PLATFORMS).join(", ")}`);
+ }
+ if (!binPath || !fs.existsSync(binPath)) {
+ throw new Error(`binary not found: ${binPath}`);
+ }
+ const version = crateVersion();
+ const [os, cpu] = key.split("-");
+ const outDir = path.join(DIST, `doriscli-${key}`);
+ freshDir(path.join(outDir, "bin"));
+
+ const destBin = path.join(outDir, "bin", entry.binName);
+ fs.copyFileSync(binPath, destBin);
+ fs.chmodSync(destBin, 0o755);
+
+ const pkg = {
+ name: `doriscli-${key}`,
+ version,
+ description: `Prebuilt doriscli binary for ${key}.`,
+ license: "Apache-2.0",
+ repository: { type: "git", url:
"git+https://github.com/morningman/doris-cli.git" },
+ os: [os],
+ cpu: [cpu],
+ files: ["bin/"],
+ // Tell Yarn PnP to keep this on disk — it's a native executable, not JS.
+ preferUnplugged: true,
+ };
+ fs.writeFileSync(path.join(outDir, "package.json"), JSON.stringify(pkg,
null, 2) + "\n");
+ fs.copyFileSync(path.join(ROOT, "LICENSE.txt"), path.join(outDir,
"LICENSE.txt"));
+ console.log(`built ${path.relative(ROOT, outDir)} (${version},
${os}/${cpu})`);
+}
+
+function buildMain() {
+ const version = crateVersion();
+ const outDir = path.join(DIST, "doriscli");
+ freshDir(path.join(outDir, "bin"));
+
+ fs.copyFileSync(path.join(SRC_MAIN, "bin", "doriscli"), path.join(outDir,
"bin", "doriscli"));
+ fs.chmodSync(path.join(outDir, "bin", "doriscli"), 0o755);
+ fs.copyFileSync(path.join(SRC_MAIN, "platforms.js"), path.join(outDir,
"platforms.js"));
+ fs.copyFileSync(path.join(SRC_MAIN, "README.md"), path.join(outDir,
"README.md"));
+ fs.copyFileSync(path.join(ROOT, "LICENSE.txt"), path.join(outDir,
"LICENSE.txt"));
+
+ const pkg = JSON.parse(fs.readFileSync(path.join(SRC_MAIN, "package.json"),
"utf8"));
+ delete pkg["//"];
+ pkg.version = version;
+ pkg.optionalDependencies = {};
+ for (const key of Object.keys(PLATFORMS)) {
+ pkg.optionalDependencies[`doriscli-${key}`] = version;
+ }
+ fs.writeFileSync(path.join(outDir, "package.json"), JSON.stringify(pkg,
null, 2) + "\n");
+ console.log(`built ${path.relative(ROOT, outDir)} (${version},
${Object.keys(PLATFORMS).length} optional deps)`);
+}
+
+const [cmd, ...rest] = process.argv.slice(2);
+if (cmd === "platform") {
+ buildPlatform(rest[0], rest[1]);
+} else if (cmd === "main") {
+ buildMain();
+} else {
+ process.stderr.write("usage: build-packages.cjs platform <key> <binary> |
main\n");
+ process.exit(2);
+}
diff --git a/npm/doriscli/README.md b/npm/doriscli/README.md
new file mode 100644
index 0000000..92693c5
--- /dev/null
+++ b/npm/doriscli/README.md
@@ -0,0 +1,38 @@
+# doriscli
+
+A fast, scriptable CLI for the **Apache Doris kernel**. It connects over the
MySQL
+protocol (+ the FE HTTP API), executes, and returns **structured JSON**.
+
+## Install
+
+```bash
+npm install -g doriscli
+doriscli --version
+```
+
+This package ships a prebuilt native binary. On install, npm automatically
pulls
+**only** the platform package that matches your OS + CPU (via
`optionalDependencies`
++ `os`/`cpu` constraints), so there is no compile step and no Rust toolchain
needed.
+
+Supported platforms: macOS (arm64, x64), Linux (x64, arm64), Windows (x64). On
any
+other platform, [build from
source](https://github.com/morningman/doris-cli#build).
+
+## Quick start
+
+```bash
+# Save a connection ("prod" is a name you choose)
+doriscli auth add prod --host 127.0.0.1 --port 9030 --http-port 8030 --user
root --password 'secret'
+
+# Verify it (version, backends, workload groups)
+doriscli --env prod auth status
+
+# Query
+doriscli --env prod sql "SELECT COUNT(*) FROM db.orders"
+```
+
+See the [full documentation](https://github.com/morningman/doris-cli#readme)
for
+`sql`, `profile`, `tablet`, `auth`, stateless mode, and SOCKS5 tunneling.
+
+## License
+
+[Apache-2.0](./LICENSE.txt)
diff --git a/npm/doriscli/bin/doriscli b/npm/doriscli/bin/doriscli
new file mode 100644
index 0000000..4ece3c5
--- /dev/null
+++ b/npm/doriscli/bin/doriscli
@@ -0,0 +1,46 @@
+#!/usr/bin/env node
+"use strict";
+// Thin launcher: locate the prebuilt binary that ships in the matching
optional
+// platform sub-package (e.g. doriscli-darwin-arm64) and exec it, forwarding
the
+// args, stdio and exit code. No build step, no postinstall — works even with
+// `npm install --ignore-scripts` and in read-only install dirs.
+const { spawnSync } = require("child_process");
+const path = require("path");
+const { PLATFORMS } = require("../platforms.js");
+
+function fail(msg) {
+ process.stderr.write(`doriscli: ${msg}\n`);
+ process.exit(1);
+}
+
+const key = `${process.platform}-${process.arch}`;
+const entry = PLATFORMS[key];
+if (!entry) {
+ fail(
+ `unsupported platform "${key}". Supported:
${Object.keys(PLATFORMS).join(", ")}.\n` +
+ `Build from source instead:
https://github.com/morningman/doris-cli#build`
+ );
+}
+
+const pkg = `doriscli-${key}`;
+let binPath;
+try {
+ // Resolve via the sub-package's package.json so we don't depend on the
+ // node_modules hoisting layout, then join the known binary path.
+ const pkgJson = require.resolve(`${pkg}/package.json`);
+ binPath = path.join(path.dirname(pkgJson), "bin", entry.binName);
+} catch (_) {
+ fail(
+ `the platform package "${pkg}" is not installed.\n` +
+ `This usually means npm skipped optional dependencies. Reinstall
with:\n` +
+ ` npm install -g doriscli (do not pass --no-optional /
--omit=optional)`
+ );
+}
+
+const result = spawnSync(binPath, process.argv.slice(2), { stdio: "inherit" });
+if (result.error) {
+ if (result.error.code === "ENOENT") fail(`binary not found at ${binPath}`);
+ throw result.error;
+}
+// spawnSync sets status=null when the child was killed by a signal.
+process.exit(result.status === null ? 1 : result.status);
diff --git a/npm/doriscli/package.json b/npm/doriscli/package.json
new file mode 100644
index 0000000..f27f7ce
--- /dev/null
+++ b/npm/doriscli/package.json
@@ -0,0 +1,14 @@
+{
+ "//": "version + optionalDependencies are injected by npm/build-packages.cjs
from Cargo.toml at release time. Publish ONLY from npm/dist/, never this
directory.",
+ "name": "doriscli",
+ "version": "0.0.0-dev",
+ "description": "Doris CLI — SQL, profiles, and tablet analysis against an
Apache Doris kernel",
+ "keywords": ["doris", "apache-doris", "cli", "sql", "mysql", "olap",
"tablet", "profile"],
+ "homepage": "https://github.com/morningman/doris-cli#readme",
+ "repository": { "type": "git", "url":
"git+https://github.com/morningman/doris-cli.git" },
+ "license": "Apache-2.0",
+ "bin": { "doriscli": "bin/doriscli" },
+ "files": ["bin/", "platforms.js", "README.md", "LICENSE.txt"],
+ "engines": { "node": ">=16" },
+ "optionalDependencies": {}
+}
diff --git a/npm/doriscli/platforms.js b/npm/doriscli/platforms.js
new file mode 100644
index 0000000..81c6934
--- /dev/null
+++ b/npm/doriscli/platforms.js
@@ -0,0 +1,16 @@
+"use strict";
+// Canonical table of supported platforms — the single source of truth shared
by
+// the bin launcher (bin/doriscli) and the build script
(npm/build-packages.cjs).
+//
+// The key is `${process.platform}-${process.arch}`, which also doubles as:
+// - the npm "<os>-<cpu>" pair (e.g. darwin-arm64), and
+// - the platform sub-package name suffix: `doriscli-<key>`.
+const PLATFORMS = {
+ "darwin-arm64": { rustTarget: "aarch64-apple-darwin", binName: "doriscli" },
+ "darwin-x64": { rustTarget: "x86_64-apple-darwin", binName: "doriscli" },
+ "linux-x64": { rustTarget: "x86_64-unknown-linux-gnu", binName: "doriscli" },
+ "linux-arm64": { rustTarget: "aarch64-unknown-linux-gnu", binName:
"doriscli" },
+ "win32-x64": { rustTarget: "x86_64-pc-windows-msvc", binName: "doriscli.exe"
},
+};
+
+module.exports = { PLATFORMS };
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]