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 dbc Logo + +[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://github.com/columnar-tech/dbc/blob/main/LICENSE) +[![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/columnar-tech/dbc)](https://github.com/columnar-tech/dbc/releases) +[![npm](https://img.shields.io/npm/v/@columnar-tech/dbc)](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); +}