diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 705f91452..3e347f92b 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -12,8 +12,29 @@ permissions: jobs: release-please: runs-on: ubuntu-latest + outputs: + release_created: ${{ steps.release.outputs.release_created }} + tag_name: ${{ steps.release.outputs.tag_name }} steps: - uses: googleapis/release-please-action@v4 + id: release with: release-type: rust package-name: rtk + + update-latest-tag: + name: Update 'latest' tag + needs: release-please + if: ${{ needs.release-please.outputs.release_created == 'true' }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Update latest tag + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git tag -fa latest -m "Latest stable release (${{ needs.release-please.outputs.tag_name }})" + git push origin latest --force diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b36b9ca1e..1717c2bd4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -180,32 +180,6 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - update-latest-tag: - name: Update 'latest' tag - needs: release - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Get version - id: version - run: | - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - echo "version=${{ github.event.inputs.tag }}" >> $GITHUB_OUTPUT - elif [[ "${{ github.ref }}" == refs/tags/* ]]; then - echo "version=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT - fi - - - name: Update latest tag - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git tag -fa latest -m "Latest stable release (${{ steps.version.outputs.version }})" - git push origin latest --force - # TODO: Enable when HOMEBREW_TAP_TOKEN is configured # homebrew: # name: Update Homebrew Tap diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 000000000..f1c1e5884 --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "0.5.0" +} diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 000000000..d404af7d3 --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,10 @@ +{ + "packages": { + ".": { + "release-type": "rust", + "package-name": "rtk", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": true + } + } +} diff --git a/src/ccusage.rs b/src/ccusage.rs index 71a44082b..822cca15c 100644 --- a/src/ccusage.rs +++ b/src/ccusage.rs @@ -82,8 +82,8 @@ struct MonthlyEntry { // ── Public API ── -/// Check if ccusage CLI is available in PATH -pub fn is_available() -> bool { +/// Check if ccusage binary exists in PATH +fn binary_exists() -> bool { Command::new("which") .arg("ccusage") .output() @@ -91,16 +91,47 @@ pub fn is_available() -> bool { .unwrap_or(false) } +/// Build the ccusage command, falling back to npx if binary not in PATH +fn build_command() -> Option { + if binary_exists() { + return Some(Command::new("ccusage")); + } + + // Fallback: try npx + let npx_check = Command::new("npx") + .arg("ccusage") + .arg("--help") + .stdout(std::process::Stdio::null()) + .stderr(std::process::Stdio::null()) + .status(); + + if npx_check.map(|s| s.success()).unwrap_or(false) { + let mut cmd = Command::new("npx"); + cmd.arg("ccusage"); + return Some(cmd); + } + + None +} + +/// Check if ccusage CLI is available (binary or via npx) +pub fn is_available() -> bool { + build_command().is_some() +} + /// Fetch usage data from ccusage for the last 90 days /// /// Returns `Ok(None)` if ccusage is unavailable (graceful degradation) /// Returns `Ok(Some(vec))` with parsed data on success /// Returns `Err` only on unexpected failures (JSON parse, etc.) pub fn fetch(granularity: Granularity) -> Result>> { - if !is_available() { - eprintln!("⚠️ ccusage not found. Install: npm i -g ccusage"); - return Ok(None); - } + let mut cmd = match build_command() { + Some(cmd) => cmd, + None => { + eprintln!("⚠️ ccusage not found. Install: npm i -g ccusage (or use npx ccusage)"); + return Ok(None); + } + }; let subcommand = match granularity { Granularity::Daily => "daily", @@ -108,7 +139,7 @@ pub fn fetch(granularity: Granularity) -> Result>> { Granularity::Monthly => "monthly", }; - let output = Command::new("ccusage") + let output = cmd .arg(subcommand) .arg("--json") .arg("--since")