From b6a349036c1193b3b625e74c2a155ff51f15cda8 Mon Sep 17 00:00:00 2001 From: Jacob Page Date: Tue, 29 Sep 2026 12:43:10 -0700 Subject: [PATCH 1/3] feat(action): add composite GitHub Action to install gddy CLI Adds action/action.yml wrapping install.sh/install.ps1 so workflows can install gddy via `uses: godaddy/cli/action@action-v1`. Versioned as its own release-please package (action-vX.Y.Z tags, floating action-v1 major tag) so action-only changes never bump or interleave with the CLI's own gddy release train. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/action-test.yml | 69 ++++++++++++++++++++++++++++++ .github/workflows/release.yml | 22 ++++++++++ .release-please-manifest.json | 3 +- README.md | 11 +++++ action/action.yml | 71 +++++++++++++++++++++++++++++++ release-please-config.json | 6 +++ 6 files changed, 181 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/action-test.yml create mode 100644 action/action.yml diff --git a/.github/workflows/action-test.yml b/.github/workflows/action-test.yml new file mode 100644 index 00000000..3e014b01 --- /dev/null +++ b/.github/workflows/action-test.yml @@ -0,0 +1,69 @@ +name: Test setup-gddy action + +# Exercises action/action.yml itself against real GitHub Releases, on all +# three OSes the CLI ships for. Path-filtered so it doesn't run on unrelated +# CLI-only PRs — see the "action" release-please package in +# release-please-config.json for why this is versioned separately from gddy. + +on: + pull_request: + paths: + - "action/**" + - "install.sh" + - "install.ps1" + - ".github/workflows/action-test.yml" + push: + branches: [main] + paths: + - "action/**" + - "install.sh" + - "install.ps1" + +jobs: + latest: + name: Install latest (${{ matrix.os }}) + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{ matrix.os }} + steps: + - name: Checkout code + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + + - name: Run action + id: gddy + uses: ./action + + - name: Verify gddy is installed and on PATH + shell: bash + run: | + gddy --version + test -n "${{ steps.gddy.outputs.version }}" + test -n "${{ steps.gddy.outputs.path }}" + + pinned-version: + name: Install pinned version (${{ matrix.os }}) + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{ matrix.os }} + steps: + - name: Checkout code + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + + - name: Run action + id: gddy + uses: ./action + with: + version: v0.2.23 + + - name: Verify pinned version was installed + shell: bash + run: | + gddy --version + case "${{ steps.gddy.outputs.version }}" in + *0.2.23*) ;; + *) echo "expected v0.2.23, got '${{ steps.gddy.outputs.version }}'" >&2; exit 1 ;; + esac diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e0cea134..d69b7000 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -31,6 +31,8 @@ jobs: outputs: releases_created: ${{ steps.release.outputs.releases_created }} tag_name: ${{ steps.release.outputs['rust--tag_name'] }} + action_release_created: ${{ steps.release.outputs['action--release_created'] }} + action_tag_name: ${{ steps.release.outputs['action--tag_name'] }} steps: - uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 #v5.0.0 id: release @@ -40,6 +42,26 @@ jobs: manifest-file: .release-please-manifest.json target-branch: main + action-major-tag: + name: Move floating action major tag + needs: release-please + if: needs.release-please.outputs.action_release_created == 'true' + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout code + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + fetch-depth: 0 + + - name: Move floating major tag + run: | + TAG="${{ needs.release-please.outputs.action_tag_name }}" + MAJOR="action-v$(echo "$TAG" | sed -E 's/^action-v([0-9]+)\..*/\1/')" + git tag -f "$MAJOR" "$TAG" + git push origin "$MAJOR" --force + build: name: Build ${{ matrix.target }} needs: release-please diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 13554660..d8654f9b 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,4 @@ { - "rust": "0.2.23" + "rust": "0.2.23", + "action": "1.0.0" } diff --git a/README.md b/README.md index 244cc189..39f980f6 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,17 @@ Both installers download, checksum-verify, and install the binary for your platf Once installed, `gddy update check` / `gddy update apply` handles upgrades in place. +### GitHub Actions + +Use [`godaddy/cli/action`](action/action.yml) to install `gddy` inside a workflow: + +```yaml +- uses: godaddy/cli/action@action-v1 +- run: gddy --version +``` + +Pin an exact `gddy` release with `with: { version: v0.2.23 }`; omit it for latest. The action is versioned independently of the CLI itself — `action-v1` always points at the latest `action-v1.x.y` release. + ### Agentic Skills To install Claude Code skills for the `gddy` CLI, run the following: diff --git a/action/action.yml b/action/action.yml new file mode 100644 index 00000000..8bf0838d --- /dev/null +++ b/action/action.yml @@ -0,0 +1,71 @@ +name: Setup gddy CLI +description: > + Install the GoDaddy CLI (gddy) from GitHub Releases and add it to PATH +author: GoDaddy +branding: + icon: download-cloud + color: green + +inputs: + version: + description: > + gddy release to install, e.g. "v0.2.23". Defaults to the latest release. + required: false + default: latest + +outputs: + version: + description: > + The gddy version that was installed (as reported by `gddy --version`) + value: > + ${{ steps.install-unix.outputs.version || steps.install-windows.outputs.version }} + path: + description: Directory the gddy binary was installed to + value: > + ${{ steps.install-unix.outputs.path || steps.install-windows.outputs.path }} + +runs: + using: composite + steps: + - name: Install gddy (Linux/macOS) + id: install-unix + if: runner.os != 'Windows' + shell: bash + run: | + set -euo pipefail + prefix="${RUNNER_TEMP}/gddy-bin" + mkdir -p "$prefix" + + args=(--prefix "$prefix") + if [ "${{ inputs.version }}" != "latest" ]; then + args+=(--version "${{ inputs.version }}") + fi + + bash "${{ github.action_path }}/../install.sh" "${args[@]}" + + echo "$prefix" >> "$GITHUB_PATH" + { + echo "path=$prefix" + echo "version=$("$prefix/gddy" --version)" + } >> "$GITHUB_OUTPUT" + + - name: Install gddy (Windows) + id: install-windows + if: runner.os == 'Windows' + shell: pwsh + run: | + $prefix = Join-Path $env:RUNNER_TEMP 'gddy-bin' + New-Item -ItemType Directory -Path $prefix -Force | Out-Null + + $scriptArgs = @{ Prefix = $prefix } + if ('${{ inputs.version }}' -ne 'latest') { + $scriptArgs['Version'] = '${{ inputs.version }}' + } + + & "${{ github.action_path }}\..\install.ps1" @scriptArgs + + $installedVersion = & "$prefix\gddy.exe" --version + + Add-Content -Path $env:GITHUB_PATH -Value $prefix + Add-Content -Path $env:GITHUB_OUTPUT -Value "path=$prefix" + Add-Content -Path $env:GITHUB_OUTPUT -Value "version=$installedVersion" diff --git a/release-please-config.json b/release-please-config.json index 823c0d03..8f742c47 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -22,6 +22,12 @@ "include-component-in-tag": false, "draft": true, "force-tag-creation": true + }, + "action": { + "release-type": "simple", + "component": "action", + "include-component-in-tag": true, + "draft": false } } } From 6c7838b88de82aaa1d62701a7950f5c2ffe07dfd Mon Sep 17 00:00:00 2001 From: Jacob Page Date: Tue, 29 Sep 2026 15:00:15 -0700 Subject: [PATCH 2/3] fix(action): address Copilot review findings on gddy setup action - Pass the version input through env vars instead of template interpolation in both install steps, closing a command-injection vector via a crafted `version` value. - Never let an action-only release become the repo's "latest" release (it has no binaries attached, and gddy's own install one-liner depends on `releases/latest/download/*`). - Gate the CLI build/attach pipeline on the rust package's own release-please output instead of the cross-package aggregate, so an action-only release can no longer trigger a spurious CLI build. - Enable separate-pull-requests so simultaneous rust/action version bumps never get bundled into one release PR. - Move install.sh/install.ps1 under action/ so the action's own release-please package actually covers the scripts it runs; their release-asset download URLs are unaffected since GitHub Release assets are keyed by filename, not source path. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/action-test.yml | 4 ---- .github/workflows/release.yml | 20 ++++++++++++++------ .github/workflows/rust-port-alpha.yaml | 6 +++--- action/action.yml | 16 ++++++++++------ install.ps1 => action/install.ps1 | 0 install.sh => action/install.sh | 0 release-please-config.json | 1 + 7 files changed, 28 insertions(+), 19 deletions(-) rename install.ps1 => action/install.ps1 (100%) rename install.sh => action/install.sh (100%) diff --git a/.github/workflows/action-test.yml b/.github/workflows/action-test.yml index 3e014b01..754618d7 100644 --- a/.github/workflows/action-test.yml +++ b/.github/workflows/action-test.yml @@ -9,15 +9,11 @@ on: pull_request: paths: - "action/**" - - "install.sh" - - "install.ps1" - ".github/workflows/action-test.yml" push: branches: [main] paths: - "action/**" - - "install.sh" - - "install.ps1" jobs: latest: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d69b7000..e0d058ec 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,7 +29,7 @@ jobs: name: Release Please runs-on: ubuntu-latest outputs: - releases_created: ${{ steps.release.outputs.releases_created }} + rust_release_created: ${{ steps.release.outputs['rust--release_created'] }} tag_name: ${{ steps.release.outputs['rust--tag_name'] }} action_release_created: ${{ steps.release.outputs['action--release_created'] }} action_tag_name: ${{ steps.release.outputs['action--tag_name'] }} @@ -42,8 +42,8 @@ jobs: manifest-file: .release-please-manifest.json target-branch: main - action-major-tag: - name: Move floating action major tag + action-release: + name: Finalize action release needs: release-please if: needs.release-please.outputs.action_release_created == 'true' runs-on: ubuntu-latest @@ -55,6 +55,14 @@ jobs: with: fetch-depth: 0 + # This release has no binaries attached — it must never become the + # repo's "latest" release, or the CLI's own + # `releases/latest/download/install.sh` one-liner 404s. + - name: Keep gddy as the repo's "latest" release + env: + GH_TOKEN: ${{ github.token }} + run: gh release edit "${{ needs.release-please.outputs.action_tag_name }}" --latest=false + - name: Move floating major tag run: | TAG="${{ needs.release-please.outputs.action_tag_name }}" @@ -65,7 +73,7 @@ jobs: build: name: Build ${{ matrix.target }} needs: release-please - if: needs.release-please.outputs.releases_created == 'true' || (github.event_name == 'workflow_dispatch' && inputs.publish_only == true) + if: needs.release-please.outputs.rust_release_created == 'true' || (github.event_name == 'workflow_dispatch' && inputs.publish_only == true) runs-on: ${{ matrix.os }} strategy: fail-fast: false @@ -290,8 +298,8 @@ jobs: dist/gddy-*.tar.gz \ dist/gddy-*.zip \ dist/gddy-checksums-sha256.txt \ - install.sh \ - install.ps1 \ + action/install.sh \ + action/install.ps1 \ --clobber # Publish only now assets are complete; stays a draft if build fails. gh release edit "$TAG" --draft=false --latest diff --git a/.github/workflows/rust-port-alpha.yaml b/.github/workflows/rust-port-alpha.yaml index 1ebcb221..1ed2e3bb 100644 --- a/.github/workflows/rust-port-alpha.yaml +++ b/.github/workflows/rust-port-alpha.yaml @@ -6,7 +6,7 @@ name: main alpha # prerelease on the fixed `alpha` tag, overwritten on each push. # # This repo is PUBLIC, so the install flow uses plain `curl` against the public -# release assets — no `gh` or auth required (see install.sh). +# release assets — no `gh` or auth required (see action/install.sh). on: push: @@ -198,5 +198,5 @@ jobs: dist/gddy-aarch64-apple-darwin.tar.gz \ dist/gddy-x86_64-pc-windows-msvc.zip \ dist/gddy-checksums-sha256.txt \ - install.sh \ - install.ps1 + action/install.sh \ + action/install.ps1 diff --git a/action/action.yml b/action/action.yml index 8bf0838d..c8c64207 100644 --- a/action/action.yml +++ b/action/action.yml @@ -31,17 +31,19 @@ runs: id: install-unix if: runner.os != 'Windows' shell: bash + env: + GDDY_VERSION: ${{ inputs.version }} run: | set -euo pipefail prefix="${RUNNER_TEMP}/gddy-bin" mkdir -p "$prefix" args=(--prefix "$prefix") - if [ "${{ inputs.version }}" != "latest" ]; then - args+=(--version "${{ inputs.version }}") + if [ "$GDDY_VERSION" != "latest" ]; then + args+=(--version "$GDDY_VERSION") fi - bash "${{ github.action_path }}/../install.sh" "${args[@]}" + bash "${{ github.action_path }}/install.sh" "${args[@]}" echo "$prefix" >> "$GITHUB_PATH" { @@ -53,16 +55,18 @@ runs: id: install-windows if: runner.os == 'Windows' shell: pwsh + env: + GDDY_VERSION: ${{ inputs.version }} run: | $prefix = Join-Path $env:RUNNER_TEMP 'gddy-bin' New-Item -ItemType Directory -Path $prefix -Force | Out-Null $scriptArgs = @{ Prefix = $prefix } - if ('${{ inputs.version }}' -ne 'latest') { - $scriptArgs['Version'] = '${{ inputs.version }}' + if ($env:GDDY_VERSION -ne 'latest') { + $scriptArgs['Version'] = $env:GDDY_VERSION } - & "${{ github.action_path }}\..\install.ps1" @scriptArgs + & "${{ github.action_path }}\install.ps1" @scriptArgs $installedVersion = & "$prefix\gddy.exe" --version diff --git a/install.ps1 b/action/install.ps1 similarity index 100% rename from install.ps1 rename to action/install.ps1 diff --git a/install.sh b/action/install.sh similarity index 100% rename from install.sh rename to action/install.sh diff --git a/release-please-config.json b/release-please-config.json index 8f742c47..dd1481d3 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -2,6 +2,7 @@ "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", "bump-minor-pre-major": true, "bump-patch-for-minor-pre-major": true, + "separate-pull-requests": true, "changelog-sections": [ { "type": "feat", "section": "Features" }, { "type": "fix", "section": "Bug Fixes" }, From 1cee4aa8efff91631a33b7b76db69ed1fce54e55 Mon Sep 17 00:00:00 2001 From: Jacob Page Date: Tue, 29 Sep 2026 15:08:04 -0700 Subject: [PATCH 3/3] fix(action): don't leave a persistent PATH mutation on the runner install.ps1's normal PATH update writes to the Windows user's registry PATH, which is the right behavior for a real end-user install but an unwanted, persistent side effect on a (especially self-hosted) CI runner. Add -SkipPathUpdate so the action can rely solely on $GITHUB_PATH for job-scoped PATH, same as the Linux/macOS step already does. Co-Authored-By: Claude Sonnet 5 --- action/action.yml | 2 +- action/install.ps1 | 32 +++++++++++++++++++++----------- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/action/action.yml b/action/action.yml index c8c64207..bd8b7b02 100644 --- a/action/action.yml +++ b/action/action.yml @@ -61,7 +61,7 @@ runs: $prefix = Join-Path $env:RUNNER_TEMP 'gddy-bin' New-Item -ItemType Directory -Path $prefix -Force | Out-Null - $scriptArgs = @{ Prefix = $prefix } + $scriptArgs = @{ Prefix = $prefix; SkipPathUpdate = $true } if ($env:GDDY_VERSION -ne 'latest') { $scriptArgs['Version'] = $env:GDDY_VERSION } diff --git a/action/install.ps1 b/action/install.ps1 index ec190bb8..c45caefa 100644 --- a/action/install.ps1 +++ b/action/install.ps1 @@ -23,7 +23,12 @@ [CmdletBinding()] param( [string]$Prefix, - [string]$Version + [string]$Version, + # Skips the persistent (registry) user PATH update below, only setting + # PATH for the current session. Intended for callers (e.g. the + # accompanying GitHub Action) that manage PATH themselves and don't want + # this install to leave a stale entry behind on long-lived machines. + [switch]$SkipPathUpdate ) Set-StrictMode -Version Latest @@ -132,16 +137,21 @@ try { # ── 4. PATH ────────────────────────────────────────────────────────────── - $userPath = [Environment]::GetEnvironmentVariable('Path', 'User') - # Wrap in @(...): Windows PowerShell 5.1 (unlike pwsh 7+) doesn't add an - # auto Count property to a bare $null/single-string pipeline result, so - # this throws under Set-StrictMode when there are 0 or 1 matches. - $onPath = @($userPath -split ';' | Where-Object { $_.TrimEnd('\') -ieq $Prefix.TrimEnd('\') }).Count -gt 0 - if (-not $onPath) { - $newPath = if ([string]::IsNullOrEmpty($userPath)) { $Prefix } else { "$userPath;$Prefix" } - [Environment]::SetEnvironmentVariable('Path', $newPath, 'User') - $env:Path = "$env:Path;$Prefix" # current session - Write-Info "Added $Prefix to your user PATH (restart your shell for new shells to see it)." + if ($SkipPathUpdate) { + $env:Path = "$env:Path;$Prefix" # current session only + Write-Info "Skipping persistent user PATH update for $Prefix (caller manages PATH)." + } else { + $userPath = [Environment]::GetEnvironmentVariable('Path', 'User') + # Wrap in @(...): Windows PowerShell 5.1 (unlike pwsh 7+) doesn't add an + # auto Count property to a bare $null/single-string pipeline result, so + # this throws under Set-StrictMode when there are 0 or 1 matches. + $onPath = @($userPath -split ';' | Where-Object { $_.TrimEnd('\') -ieq $Prefix.TrimEnd('\') }).Count -gt 0 + if (-not $onPath) { + $newPath = if ([string]::IsNullOrEmpty($userPath)) { $Prefix } else { "$userPath;$Prefix" } + [Environment]::SetEnvironmentVariable('Path', $newPath, 'User') + $env:Path = "$env:Path;$Prefix" # current session + Write-Info "Added $Prefix to your user PATH (restart your shell for new shells to see it)." + } } } finally { Remove-Item -Recurse -Force -LiteralPath $work -ErrorAction SilentlyContinue