Skip to content

Commit 0fa05b2

Browse files
authored
Add release script wrapping cargo release
1 parent 8628b1b commit 0fa05b2

File tree

3 files changed

+127
-4
lines changed

3 files changed

+127
-4
lines changed

README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,32 @@ hotdata tables list <workspace_id> --connection-id <connection_id> [--format tab
5454
hotdata query "<sql>" --workspace-id <workspace_id> [--connection <connection_id>] [--format table|json|csv]
5555
```
5656

57+
## Releasing
58+
59+
Releases use a two-phase workflow wrapping [`cargo-release`](https://github.com/crate-ci/cargo-release).
60+
61+
**Phase 1 — prepare**
62+
63+
```sh
64+
scripts/release.sh prepare <version>
65+
# e.g. scripts/release.sh prepare 0.2.0
66+
```
67+
68+
This will:
69+
1. Create a `release/<version>` branch
70+
2. Bump the version in `Cargo.toml`, update `CHANGELOG.md`, and push the branch
71+
3. Open a GitHub pull request and launch it in the browser
72+
73+
Squash and merge the PR into `main` when ready.
74+
75+
**Phase 2 — finish**
76+
77+
```sh
78+
scripts/release.sh finish
79+
```
80+
81+
Run this from any branch after the PR is merged. It will switch to `main`, pull the latest, tag the release, and trigger the dist workflow.
82+
5783
## Configuration
5884

5985
Config is stored at `~/.hotdata/config.yml` keyed by profile (default: `default`).

scripts/release.sh

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
#!/usr/bin/env bash
2+
# release.sh — two-phase release wrapper around cargo-release
3+
#
4+
# Usage:
5+
# scripts/release.sh prepare <version> # steps 0-2: branch, bump, push PR
6+
# scripts/release.sh finish # step 4: tag, publish, trigger dist
7+
8+
set -euo pipefail
9+
10+
COMMAND="${1:-}"
11+
VERSION="${2:-}"
12+
13+
usage() {
14+
echo "Usage:"
15+
echo " scripts/release.sh prepare <version> # create release branch and open PR"
16+
echo " scripts/release.sh finish # tag and publish from main"
17+
exit 1
18+
}
19+
20+
require_clean_tree() {
21+
if ! git diff --quiet || ! git diff --cached --quiet; then
22+
echo "error: working tree is not clean. Commit or stash your changes first."
23+
exit 1
24+
fi
25+
}
26+
27+
case "$COMMAND" in
28+
prepare)
29+
if [ -z "$VERSION" ]; then
30+
echo "error: version required (e.g. scripts/release.sh prepare 0.2.0)"
31+
usage
32+
fi
33+
34+
BRANCH="release/$VERSION"
35+
36+
require_clean_tree
37+
38+
# step 0: create release branch
39+
echo "→ Creating branch $BRANCH"
40+
git checkout -b "$BRANCH"
41+
42+
# step 2: bump versions, commit, push branch
43+
echo ""
44+
echo "→ Running cargo release (no publish, no tag)..."
45+
cargo release --no-publish --no-tag --allow-branch="$BRANCH" "$VERSION"
46+
47+
echo ""
48+
echo "→ Opening pull request..."
49+
PR_URL=$(gh pr create \
50+
--title "chore: Release hotdata-cli version $VERSION" \
51+
--base main \
52+
--head "$BRANCH")
53+
54+
echo ""
55+
echo "✓ PR created: $PR_URL"
56+
if command -v xdg-open &>/dev/null; then
57+
xdg-open "$PR_URL" || true
58+
elif command -v open &>/dev/null; then
59+
open "$PR_URL" || true
60+
fi
61+
echo ""
62+
echo "Next steps:"
63+
echo " 1. Review and merge the PR (use 'Squash and merge')"
64+
echo " 2. Run: scripts/release.sh finish"
65+
;;
66+
67+
finish)
68+
require_clean_tree
69+
70+
CURRENT_BRANCH="$(git rev-parse --abbrev-ref HEAD)"
71+
if [ "$CURRENT_BRANCH" != "main" ]; then
72+
echo "→ Switching to main..."
73+
git checkout main
74+
fi
75+
76+
echo "→ Pulling latest main..."
77+
git pull
78+
79+
echo ""
80+
echo "→ Running cargo release (tagging release)..."
81+
cargo release
82+
83+
echo ""
84+
echo "✓ Release complete. Tag pushed and dist workflow triggered."
85+
;;
86+
87+
*)
88+
usage
89+
;;
90+
esac

src/datasets.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,14 +118,21 @@ fn do_upload<R: std::io::Read + Send + 'static>(
118118
content_type: &str,
119119
reader: R,
120120
pb: ProgressBar,
121+
content_length: Option<u64>,
121122
) -> String {
122123
let url = format!("{api_url}/files");
123124

124-
let resp = match client
125+
let mut req = client
125126
.post(&url)
126127
.header("Authorization", format!("Bearer {api_key}"))
127128
.header("X-Workspace-Id", workspace_id)
128-
.header("Content-Type", content_type)
129+
.header("Content-Type", content_type);
130+
131+
if let Some(len) = content_length {
132+
req = req.header("Content-Length", len);
133+
}
134+
135+
let resp = match req
129136
.body(reqwest::blocking::Body::new(reader))
130137
.send()
131138
{
@@ -190,7 +197,7 @@ fn upload_from_file(
190197
let pb = make_progress_bar(file_size);
191198
let reader = pb.wrap_read(f);
192199

193-
let id = do_upload(client, api_key, workspace_id, api_url, ft.content_type, reader, pb);
200+
let id = do_upload(client, api_key, workspace_id, api_url, ft.content_type, reader, pb, Some(file_size));
194201
(id, ft.format)
195202
}
196203

@@ -216,7 +223,7 @@ fn upload_from_stdin(
216223
pb.enable_steady_tick(std::time::Duration::from_millis(80));
217224
let reader = pb.wrap_read(reader);
218225

219-
let id = do_upload(client, api_key, workspace_id, api_url, ft.content_type, reader, pb);
226+
let id = do_upload(client, api_key, workspace_id, api_url, ft.content_type, reader, pb, None);
220227
(id, ft.format)
221228
}
222229

0 commit comments

Comments
 (0)