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]

Reply via email to