Skip to content

Commit 2416bea

Browse files
sync from internal repository
0 parents  commit 2416bea

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+3348
-0
lines changed

.github/workflows/check.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
name: Check
2+
3+
on:
4+
workflow_call:
5+
6+
jobs:
7+
check:
8+
runs-on: ubuntu-latest
9+
steps:
10+
- uses: actions/checkout@v4
11+
- uses: actions/setup-python@v5
12+
with:
13+
python-version: "3.14"
14+
- uses: actions/cache@v4
15+
with:
16+
path: |
17+
~/.cargo/registry
18+
~/.cargo/git
19+
~/.cache/uv
20+
dkdc-md-cli/target
21+
key: check-${{ runner.os }}-${{ hashFiles('**/Cargo.lock', 'uv.lock') }}
22+
restore-keys: check-${{ runner.os }}-
23+
- run: bin/setup
24+
- run: bin/check

.github/workflows/ci.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
check:
11+
uses: ./.github/workflows/check.yml

.github/workflows/close-prs.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
name: Close PRs
2+
3+
on:
4+
pull_request_target:
5+
types: [opened, reopened]
6+
7+
jobs:
8+
close:
9+
runs-on: ubuntu-latest
10+
permissions:
11+
pull-requests: write
12+
steps:
13+
- uses: actions/github-script@v7
14+
with:
15+
script: |
16+
await github.rest.issues.createComment({
17+
owner: context.repo.owner,
18+
repo: context.repo.repo,
19+
issue_number: context.payload.pull_request.number,
20+
body: 'we don\'t do that here'
21+
});
22+
await github.rest.pulls.update({
23+
owner: context.repo.owner,
24+
repo: context.repo.repo,
25+
pull_number: context.payload.pull_request.number,
26+
state: 'closed'
27+
});
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
name: Release Python
2+
3+
on:
4+
push:
5+
tags: ["v*.*.*"]
6+
workflow_dispatch:
7+
inputs:
8+
tag:
9+
description: "Tag to release (e.g. v0.1.0)"
10+
required: true
11+
12+
permissions:
13+
contents: read
14+
15+
jobs:
16+
check:
17+
uses: ./.github/workflows/check.yml
18+
19+
build:
20+
name: Build (${{ matrix.target }})
21+
needs: check
22+
runs-on: ${{ matrix.os }}
23+
strategy:
24+
fail-fast: false
25+
matrix:
26+
include:
27+
- target: x86_64-unknown-linux-gnu
28+
os: ubuntu-latest
29+
- target: aarch64-unknown-linux-gnu
30+
os: ubuntu-24.04-arm
31+
- target: x86_64-apple-darwin
32+
os: macos-latest
33+
- target: aarch64-apple-darwin
34+
os: macos-latest
35+
steps:
36+
- uses: actions/checkout@v4
37+
- uses: actions/setup-python@v5
38+
with:
39+
python-version: "3.14"
40+
- uses: actions/cache@v4
41+
with:
42+
path: |
43+
~/.cargo/registry
44+
~/.cargo/git
45+
~/.cache/uv
46+
~/Library/Caches/uv
47+
dkdc-md-cli/target
48+
key: wheel-${{ matrix.target }}-${{ hashFiles('**/Cargo.lock', 'uv.lock') }}
49+
restore-keys: wheel-${{ matrix.target }}-
50+
- run: bin/setup
51+
- run: rustup target add ${{ matrix.target }}
52+
- run: bin/build-wheels --target ${{ matrix.target }} --out dist
53+
- uses: actions/upload-artifact@v4
54+
with:
55+
name: wheel-${{ matrix.target }}
56+
path: dist/*.whl
57+
if-no-files-found: error
58+
59+
sdist:
60+
name: Build sdist
61+
needs: check
62+
runs-on: ubuntu-latest
63+
steps:
64+
- uses: actions/checkout@v4
65+
- uses: actions/setup-python@v5
66+
with:
67+
python-version: "3.14"
68+
- uses: actions/cache@v4
69+
with:
70+
path: ~/.cache/uv
71+
key: sdist-${{ runner.os }}-${{ hashFiles('uv.lock') }}
72+
restore-keys: sdist-${{ runner.os }}-
73+
- run: bin/setup
74+
- run: bin/build-sdist
75+
- uses: actions/upload-artifact@v4
76+
with:
77+
name: sdist
78+
path: dist/*.tar.gz
79+
if-no-files-found: error
80+
81+
publish:
82+
name: Publish to PyPI
83+
needs: [build, sdist]
84+
runs-on: ubuntu-latest
85+
environment: pypi
86+
permissions:
87+
id-token: write
88+
steps:
89+
- uses: actions/download-artifact@v4
90+
with:
91+
path: dist
92+
merge-multiple: true
93+
- uses: pypa/gh-action-pypi-publish@release/v1
94+
with:
95+
packages-dir: dist/

.github/workflows/release.yml

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
tags: ["v*.*.*"]
6+
workflow_dispatch:
7+
inputs:
8+
tag:
9+
description: "Tag to release (e.g. v0.1.0)"
10+
required: true
11+
12+
permissions:
13+
contents: read
14+
15+
jobs:
16+
check:
17+
uses: ./.github/workflows/check.yml
18+
19+
build:
20+
name: Build (${{ matrix.target }})
21+
needs: check
22+
runs-on: ${{ matrix.os }}
23+
strategy:
24+
fail-fast: false
25+
matrix:
26+
include:
27+
- target: x86_64-unknown-linux-gnu
28+
os: ubuntu-latest
29+
- target: aarch64-unknown-linux-gnu
30+
os: ubuntu-24.04-arm
31+
- target: x86_64-apple-darwin
32+
os: macos-latest
33+
- target: aarch64-apple-darwin
34+
os: macos-latest
35+
steps:
36+
- uses: actions/checkout@v4
37+
- uses: actions/setup-python@v5
38+
with:
39+
python-version: "3.14"
40+
- uses: actions/cache@v4
41+
with:
42+
path: |
43+
~/.cargo/registry
44+
~/.cargo/git
45+
~/.cache/uv
46+
~/Library/Caches/uv
47+
dkdc-md-cli/target
48+
key: build-${{ matrix.target }}-${{ hashFiles('**/Cargo.lock', 'uv.lock') }}
49+
restore-keys: build-${{ matrix.target }}-
50+
- run: bin/setup
51+
- run: rustup target add ${{ matrix.target }}
52+
- run: bin/build-rs --release --target ${{ matrix.target }}
53+
- name: Determine version
54+
id: tag
55+
run: |
56+
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
57+
echo "version=${{ github.event.inputs.tag }}" >> "$GITHUB_OUTPUT"
58+
else
59+
echo "version=${{ github.ref_name }}" >> "$GITHUB_OUTPUT"
60+
fi
61+
- name: Package
62+
run: |
63+
staging=$(mktemp -d)
64+
cp dkdc-md-cli/target/${{ matrix.target }}/release/md "$staging/"
65+
cp LICENSE "$staging/"
66+
tar -czf dkdc-md-cli-${{ steps.tag.outputs.version }}-${{ matrix.target }}.tar.gz -C "$staging" .
67+
- uses: actions/upload-artifact@v4
68+
with:
69+
name: dkdc-md-cli-${{ matrix.target }}
70+
path: dkdc-md-cli-*.tar.gz
71+
if-no-files-found: error
72+
73+
release:
74+
name: Create Release
75+
needs: build
76+
runs-on: ubuntu-latest
77+
permissions:
78+
contents: write
79+
steps:
80+
- name: Determine version
81+
id: tag
82+
run: |
83+
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
84+
echo "version=${{ github.event.inputs.tag }}" >> "$GITHUB_OUTPUT"
85+
else
86+
echo "version=${{ github.ref_name }}" >> "$GITHUB_OUTPUT"
87+
fi
88+
- uses: actions/download-artifact@v4
89+
with:
90+
path: artifacts
91+
merge-multiple: true
92+
- name: Create GitHub Release
93+
env:
94+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
95+
run: gh release create "${{ steps.tag.outputs.version }}" --repo "${{ github.repository }}" --title "${{ steps.tag.outputs.version }}" --generate-notes artifacts/*

.gitignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
target/
2+
dist/
3+
*.pyc
4+
__pycache__/
5+
.venv/
6+
*.egg-info/
7+
*.so
8+
*.dylib
9+
.DS_Store
10+
.sync-repo/

CLAUDE.md

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
# dkdc-md-cli
2+
3+
CLI for the MotherDuck REST API. Rust core with PyO3 Python bindings.
4+
5+
**Standalone project.** This lives in the dkdc monorepo for convenience but is entirely disconnected from the rest of the monorepo. It has no dependencies on any dkdc crate, is not part of the dkdc Cargo workspace, and is not a sub-CLI of `dkdc`. It is synced to its own public repo and published independently.
6+
7+
## architecture
8+
9+
```
10+
dkdc-md-cli/ # pure Rust core (lib + binary)
11+
src/
12+
lib.rs # module exports, pub fn run()
13+
main.rs # binary entry point
14+
cli.rs # clap CLI definition + command dispatch
15+
client.rs # ureq HTTP client for MotherDuck API
16+
auth.rs # token resolution (env vars)
17+
dkdc-md-cli-py/ # PyO3 cdylib bindings (own workspace, built by maturin)
18+
src/lib.rs # single run() function exposed as dkdc_md.core
19+
src/
20+
dkdc_md/
21+
__init__.py # thin Python wrapper, entry point for `uv tool install .`
22+
```
23+
24+
The `-py` crate is **not** in the Cargo workspace (cdylib can't link Python symbols via `cargo build`). It's built exclusively by maturin.
25+
26+
Crates.io: `dkdc-md-cli`. PyPI: `dkdc-md-cli`. Installed binary: `md`.
27+
28+
## development
29+
30+
```bash
31+
bin/setup # install rustup + uv if needed
32+
bin/build # build Rust + Python (bin/build-rs, bin/build-py)
33+
bin/check # lint + test (bin/check-rs, bin/check-py)
34+
bin/format # auto-format (bin/format-rs, bin/format-py)
35+
bin/test # run tests (bin/test-rs, bin/test-py)
36+
bin/install # install locally (bin/install-rs, bin/install-py)
37+
```
38+
39+
Rust checks: `cargo fmt --check`, `cargo clippy -- -D warnings`, `cargo test`
40+
Python checks: `ruff check .`, `ruff format --check .`
41+
42+
## testing
43+
44+
Integration test against the live MotherDuck API (requires `MOTHERDUCK_TOKEN`):
45+
46+
```bash
47+
tests/integration-test
48+
```
49+
50+
This creates a temporary service account, exercises duckling config (pulse -> standard -> pulse), creates/lists/deletes tokens, then cleans up. Uses cleanup trap for safety.
51+
52+
## authentication
53+
54+
Token resolution order (first non-empty wins):
55+
1. `motherduck_token` env var
56+
2. `MOTHERDUCK_TOKEN` env var
57+
3. `motherduck_api_key` env var
58+
4. `MOTHERDUCK_API_KEY` env var
59+
60+
## CLI reference
61+
62+
```
63+
md [-o text|json] [-V]
64+
65+
service-account create <username>
66+
service-account delete <username>
67+
68+
token list <username>
69+
token create <username> --name NAME [--ttl SECS] [--token-type read-write|read-scaling]
70+
token delete <username> <token_id>
71+
72+
duckling get <username>
73+
duckling set <username> --rw-size SIZE --rs-size SIZE --flock-size N
74+
75+
account list-active
76+
```
77+
78+
Instance sizes are validated client-side via clap ValueEnum: `pulse`, `standard`, `jumbo`, `mega`, `giga`.
79+
80+
## MotherDuck API reference
81+
82+
OpenAPI spec: https://api.motherduck.com/docs/specs
83+
84+
## CI/CD
85+
86+
Public repo: `lostmygithubaccount/dkdc-md-cli`
87+
88+
- `.github/workflows/ci.yml` — runs checks on push/PR to main
89+
- `.github/workflows/check.yml` — reusable workflow (fmt, clippy, test, ruff)
90+
- `.github/workflows/release.yml` — multi-platform Rust binaries on version tags
91+
- `.github/workflows/release-python.yml` — PyPI wheels + sdist on version tags
92+
93+
Release: tag `v*.*.*` triggers both workflows. PyPI uses OIDC trusted publishing.
94+
95+
Sync to public repo: `bin/sync-public-repo` (optional `--release` flag to tag).
96+
97+
## conventions
98+
99+
- Rust stable toolchain (edition 2024, requires 1.93+)
100+
- All API methods return `serde_json::Value` (thin wrapper, not typed responses)
101+
- `handle_response()` reads body as text first, then tries JSON parse (robust against non-JSON errors)
102+
- `service-account create` uses API defaults (standard, flock_size=4). Use `duckling set` to override config after creation.

0 commit comments

Comments
 (0)