diff --git a/README.md b/README.md index ee64173..b62fd88 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,32 @@ hotdata tables list --connection-id [--format tab hotdata query "" --workspace-id [--connection ] [--format table|json|csv] ``` +## Releasing + +Releases use a two-phase workflow wrapping [`cargo-release`](https://github.com/crate-ci/cargo-release). + +**Phase 1 — prepare** + +```sh +scripts/release.sh prepare +# e.g. scripts/release.sh prepare 0.2.0 +``` + +This will: +1. Create a `release/` branch +2. Bump the version in `Cargo.toml`, update `CHANGELOG.md`, and push the branch +3. Open a GitHub pull request and launch it in the browser + +Squash and merge the PR into `main` when ready. + +**Phase 2 — finish** + +```sh +scripts/release.sh finish +``` + +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. + ## Configuration Config is stored at `~/.hotdata/config.yml` keyed by profile (default: `default`). diff --git a/scripts/release.sh b/scripts/release.sh new file mode 100755 index 0000000..5137e9c --- /dev/null +++ b/scripts/release.sh @@ -0,0 +1,90 @@ +#!/usr/bin/env bash +# release.sh — two-phase release wrapper around cargo-release +# +# Usage: +# scripts/release.sh prepare # steps 0-2: branch, bump, push PR +# scripts/release.sh finish # step 4: tag, publish, trigger dist + +set -euo pipefail + +COMMAND="${1:-}" +VERSION="${2:-}" + +usage() { + echo "Usage:" + echo " scripts/release.sh prepare # create release branch and open PR" + echo " scripts/release.sh finish # tag and publish from main" + exit 1 +} + +require_clean_tree() { + if ! git diff --quiet || ! git diff --cached --quiet; then + echo "error: working tree is not clean. Commit or stash your changes first." + exit 1 + fi +} + +case "$COMMAND" in + prepare) + if [ -z "$VERSION" ]; then + echo "error: version required (e.g. scripts/release.sh prepare 0.2.0)" + usage + fi + + BRANCH="release/$VERSION" + + require_clean_tree + + # step 0: create release branch + echo "→ Creating branch $BRANCH" + git checkout -b "$BRANCH" + + # step 2: bump versions, commit, push branch + echo "" + echo "→ Running cargo release (no publish, no tag)..." + cargo release --no-publish --no-tag --allow-branch="$BRANCH" "$VERSION" + + echo "" + echo "→ Opening pull request..." + PR_URL=$(gh pr create \ + --title "chore: Release hotdata-cli version $VERSION" \ + --base main \ + --head "$BRANCH") + + echo "" + echo "✓ PR created: $PR_URL" + if command -v xdg-open &>/dev/null; then + xdg-open "$PR_URL" || true + elif command -v open &>/dev/null; then + open "$PR_URL" || true + fi + echo "" + echo "Next steps:" + echo " 1. Review and merge the PR (use 'Squash and merge')" + echo " 2. Run: scripts/release.sh finish" + ;; + + finish) + require_clean_tree + + CURRENT_BRANCH="$(git rev-parse --abbrev-ref HEAD)" + if [ "$CURRENT_BRANCH" != "main" ]; then + echo "→ Switching to main..." + git checkout main + fi + + echo "→ Pulling latest main..." + git pull + + echo "" + echo "→ Running cargo release (tagging release)..." + cargo release + + echo "" + echo "✓ Release complete. Tag pushed and dist workflow triggered." + ;; + + *) + usage + ;; +esac diff --git a/src/datasets.rs b/src/datasets.rs index 970de1c..af6fdb0 100644 --- a/src/datasets.rs +++ b/src/datasets.rs @@ -118,14 +118,21 @@ fn do_upload( content_type: &str, reader: R, pb: ProgressBar, + content_length: Option, ) -> String { let url = format!("{api_url}/files"); - let resp = match client + let mut req = client .post(&url) .header("Authorization", format!("Bearer {api_key}")) .header("X-Workspace-Id", workspace_id) - .header("Content-Type", content_type) + .header("Content-Type", content_type); + + if let Some(len) = content_length { + req = req.header("Content-Length", len); + } + + let resp = match req .body(reqwest::blocking::Body::new(reader)) .send() { @@ -190,7 +197,7 @@ fn upload_from_file( let pb = make_progress_bar(file_size); let reader = pb.wrap_read(f); - let id = do_upload(client, api_key, workspace_id, api_url, ft.content_type, reader, pb); + let id = do_upload(client, api_key, workspace_id, api_url, ft.content_type, reader, pb, Some(file_size)); (id, ft.format) } @@ -216,7 +223,7 @@ fn upload_from_stdin( pb.enable_steady_tick(std::time::Duration::from_millis(80)); let reader = pb.wrap_read(reader); - let id = do_upload(client, api_key, workspace_id, api_url, ft.content_type, reader, pb); + let id = do_upload(client, api_key, workspace_id, api_url, ft.content_type, reader, pb, None); (id, ft.format) }