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
82 changes: 76 additions & 6 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,60 @@ jobs:
include:
- runner: macos-latest
target: aarch64-apple-darwin
node_target: darwin-arm64
- runner: macos-15-intel
target: x86_64-apple-darwin
node_target: darwin-x64
- runner: ubuntu-latest
target: x86_64-unknown-linux-gnu
node_target: linux-x64
- runner: ubuntu-24.04-arm
target: aarch64-unknown-linux-gnu
node_target: linux-arm64
- runner: windows-2022
target: x86_64-pc-windows-gnu
node_target: win32-x64
runs-on: ${{ matrix.runner }}
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
# Windows cgo build needs a GCC toolchain (pg_query_go uses -std=gnu99)
- name: Set up MinGW (Windows)
if: runner.os == 'Windows'
uses: msys2/setup-msys2@v2
with:
msystem: MINGW64
install: mingw-w64-x86_64-gcc
path-type: inherit
- name: Build
run: |
ext=""
[ "${{ runner.os }}" = "Windows" ] && ext=".exe"
CGO_ENABLED=1 go build \
-ldflags "-X main.version=${GITHUB_REF_NAME}" \
-o dryrun \
-o "dryrun${ext}" \
./cmd/dryrun
tar -cJf dry_run_cli-${{ matrix.target }}.tar.xz dryrun
- name: Archive (tar.xz)
if: runner.os != 'Windows'
run: tar -cJf dry_run_cli-${{ matrix.target }}.tar.xz dryrun
- name: Archive (zip)
if: runner.os == 'Windows'
run: 7z a dry_run_cli-${{ matrix.target }}.zip dryrun.exe
- uses: actions/upload-artifact@v4
with:
name: tar-${{ matrix.target }}
path: dry_run_cli-${{ matrix.target }}.*
retention-days: 1
# raw binary for the npm platform packages
- uses: actions/upload-artifact@v4
with:
name: ${{ matrix.target }}
path: dry_run_cli-${{ matrix.target }}.tar.xz
name: bin-${{ matrix.node_target }}
path: dryrun${{ runner.os == 'Windows' && '.exe' || '' }}
retention-days: 1

release:
Expand All @@ -44,15 +77,52 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
pattern: tar-*
merge-multiple: true
path: dist/
- name: Checksums
run: shasum -a 256 dist/*.tar.xz > dist/checksums.txt
run: cd dist && shasum -a 256 *.tar.xz *.zip > checksums.txt
- name: Publish release
run: |
gh release create "$GITHUB_REF_NAME" \
--title "$GITHUB_REF_NAME" \
--generate-notes \
dist/*.tar.xz dist/checksums.txt
dist/*.tar.xz dist/*.zip dist/checksums.txt
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

npm-publish:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
registry-url: https://registry.npmjs.org
- uses: actions/download-artifact@v4
with:
pattern: bin-*
path: bins/
- name: Stage binaries
run: |
for t in darwin-arm64 darwin-x64 linux-x64 linux-arm64; do
mkdir -p npm/packages/$t/bin
cp bins/bin-$t/dryrun npm/packages/$t/bin/dryrun
chmod +x npm/packages/$t/bin/dryrun
done
mkdir -p npm/packages/win32-x64/bin
cp bins/bin-win32-x64/dryrun.exe npm/packages/win32-x64/bin/dryrun.exe
- name: Set versions
run: node npm/scripts/set-version.mjs "${GITHUB_REF_NAME#v}"
- name: Publish platform packages
run: |
for t in darwin-arm64 darwin-x64 linux-x64 linux-arm64 win32-x64; do
npm publish --access public npm/packages/$t
done
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Publish launcher package
run: npm publish --access public npm/dryrun
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
85 changes: 62 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,55 @@ No database connection needed. The assistant never sees credentials.

**The server should do analysis, not pass-through.** Returning raw `\d+` output is marginally better than pasting it into the chat yourself. The value is in *interpreting* that data: checking whether a migration is safe for your PostgreSQL version, flagging missing FK indexes, and validating column references against the actual schema.

## Install

**Homebrew:**

```sh
brew install boringsql/boringsql/dryrun
```

**npm / npx:**

If you already have Node, you can run `dryrun` without installing anything:

```sh
npx @boringsql/dryrun --version
```

That fetches the prebuilt binary for your platform (darwin-arm64, linux-x64, linux-arm64), caches it, and prints the version. To put `dryrun` permanently on your PATH:

```sh
npm install -g @boringsql/dryrun
dryrun --version
```

The npm package wraps the same Go binary; every CLI command works identically. Commands like `lint` need a schema snapshot first — see [Quickstart](#quickstart). Prebuilt binaries cover macOS (Apple Silicon + Intel), Linux (x64 + arm64), and Windows x64. On other platforms (Alpine/musl, Windows arm64), use Homebrew or build from source.

**From source:**

Requires Go 1.26+. If you don't have it, install via [go.dev/dl](https://go.dev/dl/).

```sh
git clone https://github.com/boringsql/dryrun.git
cd dryrun
go build -o bin/dryrun ./cmd/dryrun
```

The binary is at `bin/dryrun`.

## 30-second demo

Point **`dryrun`** at any schema JSON file (see [examples/demo](examples/demo/) for a ready-made one):
With `dryrun` installed, lint a ready-made schema snapshot from a clone of this repo, no database and no setup:

```sh
cd examples/demo
git clone https://github.com/boringsql/dryrun.git
cd dryrun/examples/demo
dryrun lint
```

(Installed via npm or Homebrew but didn't clone the repo? You won't have `examples/demo` — jump to [Quickstart](#quickstart) to point `dryrun` at your own schema. The sample output below is what `lint` produces.)

```
[ERROR] public.audit_log: table has no primary key
fix: add a primary key (bigint GENERATED ALWAYS AS IDENTITY recommended)
Expand All @@ -74,27 +114,7 @@ dryrun lint
22 violation(s): 6 error, 16 warning, 0 info (13 tables checked)
```

No database needed. Works entirely from the JSON file.

## Install

**Homebrew:**

```sh
brew install boringsql/boringsql/dryrun
```

**From source:**

Requires Go 1.26+. If you don't have it, install via [go.dev/dl](https://go.dev/dl/).

```sh
git clone https://github.com/boringsql/dryrun.git
cd dryrun
go build -o bin/dryrun ./cmd/dryrun
```

The binary is at `bin/dryrun`.
No database needed. Works entirely from the offline snapshot.

## Quickstart

Expand Down Expand Up @@ -236,6 +256,25 @@ If you built from source, use the full path to the binary:
claude mcp add dryrun -- /path/to/dryrun mcp-serve
```

Or, with no install at all, point the client at `npx`:

```sh
claude mcp add dryrun -- npx -y @boringsql/dryrun mcp-serve
```

The raw client config for this form is:

```json
{
"mcpServers": {
"dryrun": {
"command": "npx",
"args": ["-y", "@boringsql/dryrun", "mcp-serve"]
}
}
}
```

That's it. The server auto-discovers `.dryrun/schema.json` in the current project. No database credentials needed, your AI assistant gets full schema intelligence from the offline snapshot.

For projects with multiple databases, run one `dryrun mcp-serve` per database and add an entry per server in your client config. Native multi-database serving inside one MCP process is tracked in [#4](https://github.com/boringSQL/dryrun/issues/4).
Expand Down
1 change: 1 addition & 0 deletions npm/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
packages/*/bin/
36 changes: 36 additions & 0 deletions npm/dryrun/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# dryrun

PostgreSQL schema/query advisor with a built-in MCP server. This npm package is
a thin launcher — the real binary is written in Go and ships as a
platform-specific dependency that npm selects automatically.

## Use as an MCP server

No install needed. Add to your MCP client config:

```json
{
"mcpServers": {
"dryrun": {
"command": "npx",
"args": ["-y", "@boringsql/dryrun", "mcp-serve"]
}
}
}
```

`npx` fetches the package on first run and caches it; subsequent launches are
local. Pin a version for reproducibility: `"@boringsql/dryrun@0.8.2"`.

## CLI

```sh
npx @boringsql/dryrun --help
```

Or install globally: `npm i -g @boringsql/dryrun` then `dryrun --help`.

## Supported platforms

`darwin-arm64`, `linux-x64`, `linux-arm64`. On other platforms, grab a prebuilt
binary from the [GitHub Releases](https://github.com/boringSQL/dryrun/releases).
55 changes: 55 additions & 0 deletions npm/dryrun/bin/dryrun.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#!/usr/bin/env node
"use strict";

// Launcher for the dryrun Go binary. The actual binary ships inside a
// platform-specific optionalDependency (@boringsql/dryrun-<os>-<cpu>); npm
// installs only the one matching the host. We resolve it and exec, forwarding
// argv + stdio verbatim so MCP stdio transport passes straight through.

const { spawnSync } = require("node:child_process");

// node arch/platform -> our published platform packages
const SUPPORTED = {
"darwin arm64": "@boringsql/dryrun-darwin-arm64",
"darwin x64": "@boringsql/dryrun-darwin-x64",
"linux x64": "@boringsql/dryrun-linux-x64",
"linux arm64": "@boringsql/dryrun-linux-arm64",
"win32 x64": "@boringsql/dryrun-win32-x64",
};

function resolveBinary() {
const key = `${process.platform} ${process.arch}`;
const pkg = SUPPORTED[key];
if (!pkg) {
fail(
`unsupported platform ${key}.\n` +
`Supported: ${Object.keys(SUPPORTED).join(", ")}.\n` +
`Install a prebuilt binary from https://github.com/boringSQL/dryrun/releases instead.`
);
}
try {
const binName = process.platform === "win32" ? "dryrun.exe" : "dryrun";
return require.resolve(`${pkg}/bin/${binName}`);
} catch (_e) {
fail(
`the platform package ${pkg} is not installed.\n` +
`This usually means npm skipped optional dependencies. Reinstall without\n` +
`--no-optional (e.g. \`npm i @boringsql/dryrun\`), or grab a binary from\n` +
`https://github.com/boringSQL/dryrun/releases.`
);
}
}

function fail(msg) {
process.stderr.write(`dryrun: ${msg}\n`);
process.exit(1);
}

const result = spawnSync(resolveBinary(), process.argv.slice(2), {
stdio: "inherit",
});

if (result.error) fail(result.error.message);
// propagate signal-kills as the conventional 128+signal exit code
if (result.signal) process.exit(1);
process.exit(result.status === null ? 1 : result.status);
39 changes: 39 additions & 0 deletions npm/dryrun/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"name": "@boringsql/dryrun",
"version": "0.0.0",
"description": "dryrun — PostgreSQL schema/query advisor and MCP server. npx launcher.",
"bin": {
"dryrun": "bin/dryrun.js"
},
"files": [
"bin/dryrun.js"
],
"optionalDependencies": {
"@boringsql/dryrun-darwin-arm64": "0.0.0",
"@boringsql/dryrun-darwin-x64": "0.0.0",
"@boringsql/dryrun-linux-x64": "0.0.0",
"@boringsql/dryrun-linux-arm64": "0.0.0",
"@boringsql/dryrun-win32-x64": "0.0.0"
},
"engines": {
"node": ">=16"
},
"license": "BSD-2-Clause",
"repository": {
"type": "git",
"url": "git+https://github.com/boringSQL/dryrun.git"
},
"homepage": "https://github.com/boringSQL/dryrun#readme",
"bugs": {
"url": "https://github.com/boringSQL/dryrun/issues"
},
"keywords": [
"postgres",
"postgresql",
"mcp",
"model-context-protocol",
"sql",
"schema",
"dryrun"
]
}
19 changes: 19 additions & 0 deletions npm/packages/darwin-arm64/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "@boringsql/dryrun-darwin-arm64",
"version": "0.0.0",
"description": "darwin-arm64 binary for dryrun",
"os": [
"darwin"
],
"cpu": [
"arm64"
],
"files": [
"bin/dryrun"
],
"license": "BSD-2-Clause",
"repository": {
"type": "git",
"url": "git+https://github.com/boringSQL/dryrun.git"
}
}
Loading