diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index bc60be94..37ca5b19 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -94,6 +94,30 @@ jobs:
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }}
+ - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
+ with:
+ node-version: '24.x'
+
+ - name: Create and verify npm packages
+ run: |
+ VERSION="${{ fromJSON(steps.gorelease-snapshot.outputs.metadata).version }}"
+ for platform in darwin-amd64 darwin-arm64 linux-amd64 linux-arm64; do
+ node packages/npm/scripts/create_packages.js \
+ --version "$VERSION" \
+ --platform "$platform" \
+ --archive "dist/dbc-${platform}-${VERSION}.tar.gz"
+ done
+ node packages/npm/scripts/create_packages.js \
+ --version "$VERSION" \
+ --platform windows-amd64 \
+ --archive "dist/dbc-windows-amd64-${VERSION}.zip"
+
+ # Verify we can install
+ cd packages/npm/wrapper
+ npm install ../packages/dbc-linux-x64
+ npm install
+ node bin/dbc.js --version
+
- name: Upload Snapshot Artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
@@ -107,6 +131,8 @@ jobs:
dist/*.rpm
dist/msi/**/*.msi
dist/python/*
+ packages/npm/packages/*/
+ packages/npm/wrapper/
production_deploy:
runs-on: ubuntu-latest
@@ -249,3 +275,47 @@ jobs:
--value="${RELEASE_VERSION}"
env:
RELEASE_VERSION: ${{ fromJSON(steps.gorelease.outputs.metadata).version }}
+
+ publish_npm:
+ runs-on: ubuntu-latest
+ environment: production
+ needs: production_deploy
+ if: github.event_name == 'push'
+ permissions:
+ contents: read
+ id-token: write # For npm provenance
+ steps:
+ - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
+ with:
+ persist-credentials: false
+
+ - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
+ with:
+ node-version: '24.x'
+ registry-url: 'https://registry.npmjs.org'
+
+ - name: Publish to npm
+ run: |
+ VERSION="${{ github.ref_name }}"
+ VERSION="${VERSION#v}" # strip leading 'v'
+
+ # Create packages for all platforms from the release we just published
+ node packages/npm/scripts/create_packages.js --version "$VERSION"
+
+ # Handle pre-releases: Prereleases (e.g. 0.3.0-rc1) publish under
+ # 'next' so they don't become the default for 'npm install
+ # @columnar-tech/dbc'
+ if echo "$VERSION" | grep -q '-'; then
+ NPM_TAG="next"
+ else
+ NPM_TAG="latest"
+ fi
+
+ # Make sure to upload the platform-specific packages before the
+ # wrapper since they're deps
+ for pkg in packages/npm/packages/*/; do
+ npm publish "$pkg" --provenance --access public --tag "$NPM_TAG"
+ done
+ npm publish packages/npm/wrapper --provenance --access public --tag "$NPM_TAG"
+ env:
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
diff --git a/packages/npm/.gitignore b/packages/npm/.gitignore
new file mode 100644
index 00000000..3a500e69
--- /dev/null
+++ b/packages/npm/.gitignore
@@ -0,0 +1,9 @@
+node_modules/
+
+# Files generated by scripts/create_packages.js — not committed
+packages/*/bin/dbc*
+packages/*/package.json
+packages/*/README.md
+packages/*/LICENSE
+wrapper/package.json
+wrapper/LICENSE
diff --git a/packages/npm/packages/README.template.md b/packages/npm/packages/README.template.md
new file mode 100644
index 00000000..de2a802a
--- /dev/null
+++ b/packages/npm/packages/README.template.md
@@ -0,0 +1,27 @@
+
+
+# @columnar-tech/dbc-PLATFORM_SUFFIX
+
+This package contains the `PLATFORM_SUFFIX` binary for [`@columnar-tech/dbc`](https://www.npmjs.com/package/@columnar-tech/dbc).
+
+**Do not install this package directly.** Install the wrapper instead:
+
+```sh
+npm install -g @columnar-tech/dbc
+```
+
+This package is automatically selected by npm based on your operating system and CPU architecture.
diff --git a/packages/npm/platforms.js b/packages/npm/platforms.js
new file mode 100644
index 00000000..16bbe031
--- /dev/null
+++ b/packages/npm/platforms.js
@@ -0,0 +1,62 @@
+// Copyright 2026 Columnar Technologies Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+"use strict";
+
+// Add a new entry here to support a new platform across both the
+// build script (create_packages.js) and the wrapper shim (bin/dbc.js).
+const PLATFORMS = [
+ {
+ goosArch: "darwin-arm64",
+ npmPkg: "@columnar-tech/dbc-darwin-arm64",
+ description: "macOS arm64 binary for dbc, the CLI for installing ADBC drivers",
+ binary: "dbc",
+ os: "darwin",
+ cpu: "arm64",
+ },
+ {
+ goosArch: "darwin-amd64",
+ npmPkg: "@columnar-tech/dbc-darwin-x64",
+ description: "macOS x64 binary for dbc, the CLI for installing ADBC drivers",
+ binary: "dbc",
+ os: "darwin",
+ cpu: "x64",
+ },
+ {
+ goosArch: "linux-arm64",
+ npmPkg: "@columnar-tech/dbc-linux-arm64",
+ description: "Linux arm64 binary for dbc, the CLI for installing ADBC drivers",
+ binary: "dbc",
+ os: "linux",
+ cpu: "arm64",
+ },
+ {
+ goosArch: "linux-amd64",
+ npmPkg: "@columnar-tech/dbc-linux-x64",
+ description: "Linux x64 binary for dbc, the CLI for installing ADBC drivers",
+ binary: "dbc",
+ os: "linux",
+ cpu: "x64",
+ },
+ {
+ goosArch: "windows-amd64",
+ npmPkg: "@columnar-tech/dbc-win32-x64",
+ description: "Windows x64 binary for dbc, the CLI for installing ADBC drivers",
+ binary: "dbc.exe",
+ os: "win32",
+ cpu: "x64",
+ },
+];
+
+module.exports = { PLATFORMS };
diff --git a/packages/npm/scripts/create_packages.js b/packages/npm/scripts/create_packages.js
new file mode 100755
index 00000000..dad802d7
--- /dev/null
+++ b/packages/npm/scripts/create_packages.js
@@ -0,0 +1,323 @@
+#!/usr/bin/env node
+// Copyright 2026 Columnar Technologies Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// create_packages.js
+//
+// Populates the per-platform npm packages with the dbc binary extracted from
+// a GoReleaser archive. Mirrors scripts/create_wheels.py in design.
+//
+// Requires: gh, tar, unzip (for .zip archives)
+//
+// Usage:
+//
+// # Create npm packages for all platforms in one go:
+// node create_packages.js --version 0.3.0
+//
+// # Create npm packages for a specific platform, including the wrapper:
+// node create_packages.js --version 0.3.0 --platform darwin-arm64
+
+"use strict";
+
+const fs = require("fs");
+const os = require("os");
+const path = require("path");
+const { execFileSync } = require("child_process");
+
+const GITHUB_REPO = "columnar-tech/dbc";
+
+const { PLATFORMS } = require("../platforms.js");
+
+function findPlatform(goosArch) {
+ return PLATFORMS.find((p) => p.goosArch === goosArch);
+}
+
+// The platform packages live in packages/
where is the unscoped
+// package name (e.g. "dbc-darwin-arm64", not "@columnar-tech/dbc-darwin-arm64")
+function pkgDirFor(info) {
+ return path.join(PACKAGES_DIR, info.npmPkg.replace(/^@[^/]+\//, ""));
+}
+
+const PACKAGES_DIR = path.resolve(__dirname, "..", "packages");
+const WRAPPER_DIR = path.resolve(__dirname, "..", "wrapper");
+const REPO_ROOT = path.resolve(__dirname, "..", "..", "..");
+const PLATFORM_README_TEMPLATE = path.join(PACKAGES_DIR, "README.template.md");
+
+function ghDownload(tag, pattern, destDir) {
+ execFileSync(
+ "gh",
+ [
+ "release",
+ "download",
+ tag,
+ "--repo",
+ GITHUB_REPO,
+ "--pattern",
+ pattern,
+ "--dir",
+ destDir,
+ "--clobber",
+ ],
+ { stdio: "inherit" },
+ );
+}
+
+// Return the asset names for a release tag.
+function ghReleaseAssets(tag) {
+ const out = execFileSync("gh", [
+ "release",
+ "view",
+ tag,
+ "--repo",
+ GITHUB_REPO,
+ "--json",
+ "assets",
+ ]);
+ return JSON.parse(out.toString()).assets.map((a) => a.name);
+}
+
+function extractBinary(archivePath, binaryName, destDir) {
+ if (archivePath.endsWith(".zip")) {
+ // -j: junk paths (strip directory components)
+ execFileSync(
+ "unzip",
+ ["-j", "-o", archivePath, `*${binaryName}`, "-d", destDir],
+ {
+ stdio: ["ignore", "pipe", "pipe"],
+ },
+ );
+ } else {
+ // GoReleaser archives are flat (no top-level directory wrapper)
+ execFileSync("tar", ["-xzf", archivePath, "-C", destDir], {
+ stdio: ["ignore", "pipe", "pipe"],
+ });
+ }
+
+ const extracted = path.join(destDir, binaryName);
+ if (!fs.existsSync(extracted)) {
+ throw new Error(
+ `Binary '${binaryName}' not found after extracting ${archivePath}`,
+ );
+ }
+ return extracted;
+}
+
+const COMMON_FIELDS = {
+ keywords: ["adbc", "arrow", "database", "drivers", "dbc"],
+ homepage: "https://columnar.tech/dbc",
+ bugs: "https://github.com/columnar-tech/dbc/issues",
+ license: "Apache-2.0",
+};
+
+function writePlatformPackageJson(info, pkgDir, version) {
+ const unscoped = info.npmPkg.replace(/^@[^/]+\//, ""); // e.g. "dbc-darwin-arm64"
+ const pkgJson = {
+ name: info.npmPkg,
+ version,
+ description: info.description,
+ ...COMMON_FIELDS,
+ repository: {
+ type: "git",
+ url: "https://github.com/columnar-tech/dbc.git",
+ directory: `packages/npm/packages/${unscoped}`,
+ },
+ os: [info.os],
+ cpu: [info.cpu],
+ files: ["bin/", "README.md", "LICENSE"],
+ };
+ fs.writeFileSync(
+ path.join(pkgDir, "package.json"),
+ JSON.stringify(pkgJson, null, 2) + "\n",
+ );
+}
+
+function writeWrapperPackageJson(version) {
+ const optionalDependencies = Object.fromEntries(
+ PLATFORMS.map((p) => [p.npmPkg, version]),
+ );
+ const pkgJson = {
+ name: "@columnar-tech/dbc",
+ version,
+ description: "The CLI for installing and managing ADBC drivers",
+ ...COMMON_FIELDS,
+ repository: {
+ type: "git",
+ url: "https://github.com/columnar-tech/dbc.git",
+ directory: "packages/npm/wrapper",
+ },
+ bin: { dbc: "bin/dbc.js" },
+ files: ["bin/", "README.md", "LICENSE"],
+ optionalDependencies,
+ };
+ fs.writeFileSync(
+ path.join(WRAPPER_DIR, "package.json"),
+ JSON.stringify(pkgJson, null, 2) + "\n",
+ );
+ fs.copyFileSync(
+ path.join(REPO_ROOT, "LICENSE"),
+ path.join(WRAPPER_DIR, "LICENSE"),
+ );
+ console.log(`Wrote wrapper package.json at version ${version}`);
+}
+
+function writePlatformDocs(info, pkgDir) {
+ // README: substitute PLATFORM placeholder with the unscoped package name
+ const unscoped = info.npmPkg.replace(/^@[^/]+\/dbc-/, ""); // e.g. "darwin-arm64"
+ const readme = fs
+ .readFileSync(PLATFORM_README_TEMPLATE, "utf8")
+ .replaceAll("PLATFORM_SUFFIX", unscoped);
+ fs.writeFileSync(path.join(pkgDir, "README.md"), readme);
+
+ // LICENSE: copy from the repo root
+ fs.copyFileSync(
+ path.join(REPO_ROOT, "LICENSE"),
+ path.join(pkgDir, "LICENSE"),
+ );
+}
+
+function populatePlatform(goosArch, archivePath, version) {
+ const info = findPlatform(goosArch);
+ if (!info) throw new Error(`Unknown platform: ${goosArch}`);
+
+ const pkgDir = pkgDirFor(info);
+ const binDir = path.join(pkgDir, "bin");
+ fs.mkdirSync(binDir, { recursive: true });
+ const destPath = path.join(binDir, info.binary);
+
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "dbc-npm-"));
+ try {
+ console.log(
+ `Extracting ${info.binary} from ${path.basename(archivePath)}...`,
+ );
+ const extracted = extractBinary(archivePath, info.binary, tmpDir);
+ fs.copyFileSync(extracted, destPath);
+ fs.chmodSync(destPath, 0o755);
+ console.log(` → ${destPath} (${fs.statSync(destPath).size} bytes)`);
+ } finally {
+ fs.rmSync(tmpDir, { recursive: true, force: true });
+ }
+
+ writePlatformDocs(info, pkgDir);
+ writePlatformPackageJson(info, pkgDir, version);
+ console.log(` → wrote ${info.npmPkg} package.json at version ${version}`);
+}
+
+function parseArgs() {
+ const args = process.argv.slice(2);
+ const opts = {
+ version: null,
+ platform: null,
+ archive: null,
+ };
+
+ for (let i = 0; i < args.length; i++) {
+ switch (args[i]) {
+ case "--version":
+ opts.version = args[++i];
+ break;
+ case "--platform":
+ opts.platform = args[++i];
+ break;
+ case "--archive":
+ opts.archive = args[++i];
+ break;
+ default:
+ console.error(`Unknown argument: ${args[i]}`);
+ process.exit(1);
+ }
+ }
+
+ if (!opts.version) {
+ console.error("--version is required");
+ process.exit(1);
+ }
+
+ return opts;
+}
+
+function main() {
+ const opts = parseArgs();
+ const version = opts.version.replace(/^v/, "");
+
+ const created = [];
+
+ if (opts.archive) {
+ if (!opts.platform) {
+ console.error("--platform is required when --archive is provided");
+ process.exit(1);
+ }
+ populatePlatform(opts.platform, path.resolve(opts.archive), version);
+ writeWrapperPackageJson(version);
+ created.push(pkgDirFor(findPlatform(opts.platform)));
+ created.push(WRAPPER_DIR);
+ } else {
+ // Download from GitHub releases
+ const tag = `v${version}`;
+ const platforms = opts.platform
+ ? [opts.platform]
+ : PLATFORMS.map((p) => p.goosArch);
+
+ // Find the actual archive filename for each platform from the release
+ const assetNames = ghReleaseAssets(tag);
+
+ for (const goosArch of platforms) {
+ const info = findPlatform(goosArch);
+ if (!info) {
+ console.error(`Unknown platform: ${goosArch}`);
+ process.exit(1);
+ }
+
+ const [goos, goarch] = goosArch.split("-");
+ const assetName = assetNames.find(
+ (n) =>
+ n.startsWith(`dbc-${goos}-${goarch}-`) &&
+ (n.endsWith(".tar.gz") || n.endsWith(".zip")),
+ );
+
+ if (!assetName) {
+ console.error(
+ `No archive asset found for ${goosArch} in release ${tag}`,
+ );
+ process.exit(1);
+ }
+
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "dbc-npm-dl-"));
+ try {
+ // gh verifies checksums automatically against the release's checksums file
+ console.log(`Downloading ${assetName}...`);
+ ghDownload(tag, assetName, tmpDir);
+ populatePlatform(goosArch, path.join(tmpDir, assetName), version);
+ } finally {
+ fs.rmSync(tmpDir, { recursive: true, force: true });
+ }
+
+ created.push(pkgDirFor(info));
+ }
+
+ writeWrapperPackageJson(version);
+ created.push(WRAPPER_DIR);
+ }
+
+ console.log(`\nCreated ${created.length} package(s):`);
+ for (const p of created) {
+ console.log(` ${p}`);
+ }
+}
+
+try {
+ main();
+} catch (err) {
+ console.error(err.message);
+ process.exit(1);
+}
diff --git a/packages/npm/wrapper/README.md b/packages/npm/wrapper/README.md
new file mode 100644
index 00000000..cf9a9c0d
--- /dev/null
+++ b/packages/npm/wrapper/README.md
@@ -0,0 +1,94 @@
+
+
+# dbc
+
+[](https://github.com/columnar-tech/dbc/blob/main/LICENSE)
+[](https://github.com/columnar-tech/dbc/releases)
+[](https://www.npmjs.com/package/@columnar-tech/dbc)
+
+**dbc is the command-line tool for installing and managing [ADBC](https://arrow.apache.org/adbc) drivers.**
+
+## Install
+
+```sh
+npm install -g @columnar-tech/dbc
+```
+
+Or run without installing:
+
+```sh
+npx @columnar-tech/dbc --help
+```
+
+## Usage
+
+Search for available drivers:
+
+```sh
+dbc search
+```
+
+Install a driver:
+
+```sh
+dbc install snowflake
+```
+
+Then use it with the [Node.js ADBC driver manager](https://arrow.apache.org/adbc/current/javascript/driver_manager.html):
+
+```sh
+npm install @apache-arrow/adbc-driver-manager apache-arrow
+```
+
+```typescript
+import { AdbcDatabase } from '@apache-arrow/adbc-driver-manager'
+
+const db = new AdbcDatabase({
+ driver: 'snowflake',
+ databaseOptions: {
+ username: 'USER',
+ password: 'PASS',
+ 'adbc.snowflake.sql.auth_type': 'auth_snowflake',
+ 'adbc.snowflake.sql.account': 'ACCOUNT-IDENT',
+ },
+})
+
+let conn
+try {
+ conn = await db.connect()
+ const table = await conn.query('SELECT * FROM CUSTOMER LIMIT 5')
+ console.log(table.toString())
+} finally {
+ await conn?.close()
+ await db.close()
+}
+```
+
+## Other installation methods
+
+dbc is also available via Homebrew, shell script, pip/uv, WinGet, and MSI installer.
+See the [installation docs](https://docs.columnar.tech/dbc/getting_started/installation) for all options.
+
+## Documentation
+
+Full documentation at [docs.columnar.tech/dbc](https://docs.columnar.tech/dbc).
+
+## Links
+
+- [GitHub Repo](https://github.com/columnar-tech/dbc)
+- [Issues](https://github.com/columnar-tech/dbc/issues)
+- [Discussions](https://github.com/columnar-tech/dbc/discussions)
diff --git a/packages/npm/wrapper/bin/dbc.js b/packages/npm/wrapper/bin/dbc.js
new file mode 100755
index 00000000..474197b4
--- /dev/null
+++ b/packages/npm/wrapper/bin/dbc.js
@@ -0,0 +1,46 @@
+#!/usr/bin/env node
+// Copyright 2026 Columnar Technologies Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+"use strict";
+
+const { execFileSync } = require("child_process");
+const { platform, arch } = process;
+const { PLATFORMS } = require("../../platforms.js");
+
+const entry = PLATFORMS.find((p) => p.os === platform && p.cpu === arch);
+
+if (!entry) {
+ console.error(
+ `dbc: unsupported platform: ${platform}/${arch}. Please file an issue at https://github.com/columnar-tech/dbc/issues`,
+ );
+ process.exit(1);
+}
+
+let binPath;
+try {
+ binPath = require.resolve(`${entry.npmPkg}/bin/${entry.binary}`);
+} catch {
+ console.error(
+ `dbc: could not find the platform package ${entry.npmPkg}.\n` +
+ ` Try reinstalling: npm install -g @columnar-tech/dbc`,
+ );
+ process.exit(1);
+}
+
+try {
+ execFileSync(binPath, process.argv.slice(2), { stdio: "inherit" });
+} catch (err) {
+ process.exit(err.status ?? 1);
+}