Compare commits

..

7 commits

Author SHA1 Message Date
Carlos Alexandro Becker
5cc7ebb73d
ci: update actions
Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
2026-05-02 13:23:47 -03:00
dependabot[bot]
702f5f91c9
ci(deps): bump the actions group with 3 updates (#560)
Bumps the actions group with 3 updates: [sigstore/cosign-installer](https://github.com/sigstore/cosign-installer), [actions/upload-artifact](https://github.com/actions/upload-artifact) and [actions/setup-node](https://github.com/actions/setup-node).


Updates `sigstore/cosign-installer` from 3.9.2 to 4.1.1
- [Release notes](https://github.com/sigstore/cosign-installer/releases)
- [Commits](d58896d6a1...cad07c2e89)

Updates `actions/upload-artifact` from 7.0.0 to 7.0.1
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](bbbca2ddaa...043fb46d1a)

Updates `actions/setup-node` from 5.0.0 to 6.4.0
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](a0853c2454...48b55a011b)

---
updated-dependencies:
- dependency-name: sigstore/cosign-installer
  dependency-version: 4.1.1
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: actions/upload-artifact
  dependency-version: 7.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions
- dependency-name: actions/setup-node
  dependency-version: 6.4.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-02 12:25:51 -03:00
Carlos Alexandro Becker
1a80836c5c
ci(nightly): pass GITHUB_TOKEN to nightly integration job
Releases API is rate-limited for unauthenticated requests.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-26 18:04:06 -03:00
Carlos Alexandro Becker
a71152e827
refactor: drop legacy 'nightly' tag fallback
Both goreleaser and goreleaser-pro now publish nightly releases as
vX.Y.Z-<sha>-nightly, so the action no longer needs to special-case
or fall back to the moving 'nightly' tag.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-26 18:02:55 -03:00
Carlos Alexandro Becker
4c6ab561ad
feat: resolve nightly to latest vX.Y.Z-<sha>-nightly release (#558)
* feat: resolve nightly to latest vX.Y.Z-<sha>-nightly release

Query GitHub releases API to resolve the 'nightly' version input to the
latest immutable nightly tag, replacing the moving 'nightly' tag that is
being removed for supply-chain hardening.

Refs goreleaser/goreleaser#6550

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat: keep legacy 'nightly' tag working during transition

Fall back to the moving 'nightly' tag when no immutable
vX.Y.Z-<sha>-nightly release is found, so the action keeps working
between this release and the goreleaser nightly switchover.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* test: assert isNightlyTag accepts legacy fallback

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: accept nightly tags without 'v' prefix

goreleaser-pro publishes nightly releases as e.g. 2.16.0-eaeb08c50-nightly
(no 'v' prefix). Make the nightly tag regex tolerate either form, and
split the integration tests so OSS asserts the legacy fallback while
Pro asserts the new <version>-<sha>-nightly format.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Revert "fix: accept nightly tags without 'v' prefix"

The missing 'v' prefix on the goreleaser-pro nightly was a release
mistake; new nightlies will keep the 'v' prefix.

This reverts commit 7673f7f.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* ci: pass GITHUB_TOKEN to tests

The new nightly resolution hits api.github.com/repos/.../releases,
which is rate-limited for unauthenticated requests.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs: note GITHUB_TOKEN need for nightly resolution

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-26 16:39:25 -03:00
Carlos Alexandro Becker
4f96abf297
feat: add version-file input (#556)
Resolves the GoReleaser version from a file. Currently supports the
asdf/mise `.tool-versions` format; resolved value takes precedence
over the `version` input.

  # .tool-versions
  goreleaser 2.13.0

  - uses: goreleaser/goreleaser-action@v7
    with:
      version-file: .tool-versions
      args: release --clean

Path is resolved relative to `workdir` unless absolute. Bare semvers
are auto-prefixed with `v`; constraint expressions and `latest` are
returned as-is. Multiple fallback versions per asdf convention are
accepted but only the first is used.

Refs #541
Closes #542

Co-authored-by: Anthony Couvreur <22034450+acouvreur@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-23 23:05:24 -03:00
Carlos Alexandro Becker
15fa2a96d4
test: cover install across release eras (#555)
Add install tests pinned to versions that exercise every release era so
we don't regress the graceful-skip path for releases that pre-date the
cosign v3 sigstore bundle:

- v0.182.0  pre-checksums-signing
- v1.26.2   cosign v2 detached .sig only
- v2.12.4   last release before sigstore bundles
- v2.13.0   first release with sigstore bundle (minimum verifiable)
- v2.15.3   recent release with sigstore bundle

Plus an explicit verifyChecksum integration test that installs v2.12.4
with cosign in PATH to confirm the cosign step is skipped (not failed)
when the sigstore bundle is absent.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-18 15:55:31 -03:00
15 changed files with 358 additions and 127 deletions

View file

@ -37,25 +37,21 @@ jobs:
- goreleaser
- goreleaser-pro
steps:
-
name: Checkout
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
-
name: Set up Go
- name: Set up Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version: stable
-
name: Check
- name: Check
uses: ./
with:
version: ${{ matrix.version }}
args: check --verbose
workdir: ./test
-
name: GoReleaser
- name: GoReleaser
if: ${{ !(github.event_name == 'pull_request' && matrix.distribution == 'goreleaser-pro') }}
uses: ./
env:
@ -81,30 +77,25 @@ jobs:
- true
- false
steps:
-
name: Checkout
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
-
name: Set up Go
- name: Set up Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version: 1.18
-
name: Install cosign
- name: Install cosign
if: matrix.cosign
uses: sigstore/cosign-installer@d58896d6a1865668819e1d91763c7751a165e159 # v3.9.2
-
name: GoReleaser
uses: sigstore/cosign-installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 # v4.1.1
- name: GoReleaser
if: ${{ !(github.event_name == 'pull_request' && matrix.distribution == 'goreleaser-pro') }}
uses: ./
with:
distribution: ${{ matrix.distribution }}
version: ${{ matrix.version }}
install-only: true
-
name: Check
- name: Check
if: ${{ !(github.event_name == 'pull_request' && matrix.distribution == 'goreleaser-pro') }}
run: |
goreleaser check --verbose
@ -120,25 +111,21 @@ jobs:
- macos-latest
- windows-latest
steps:
-
name: Checkout
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
-
name: Set up Go
- name: Set up Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version: 1.18
-
name: Import GPG key
- name: Import GPG key
id: import_gpg
uses: crazy-max/ghaction-import-gpg@2dc316deee8e90f13e1a351ab510b4d5bc0c82cd # v7.0.0
with:
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY_TEST }}
passphrase: ${{ secrets.PASSPHRASE_TEST }}
-
name: Check
- name: Check
uses: ./
with:
version: latest
@ -146,8 +133,7 @@ jobs:
workdir: ./test
env:
GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }}
-
name: GoReleaser
- name: GoReleaser
uses: ./
with:
version: latest
@ -159,31 +145,26 @@ jobs:
upload-artifact:
runs-on: ubuntu-latest
steps:
-
name: Checkout
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
-
name: Set up Go
- name: Set up Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version: 1.18
-
name: Check
- name: Check
uses: ./
with:
args: check --verbose
workdir: ./test
-
name: GoReleaser
- name: GoReleaser
uses: ./
with:
args: release --skip=publish --clean --snapshot
workdir: ./test
-
name: Upload assets
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
- name: Upload assets
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: myapp
path: ./test/dist/*
@ -191,24 +172,20 @@ jobs:
dist:
runs-on: ubuntu-latest
steps:
-
name: Checkout
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
-
name: Set up Go
- name: Set up Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version: 1.18
-
name: GoReleaser
- name: GoReleaser
uses: ./
with:
args: release --config .goreleaser-dist.yml --skip=publish --clean --snapshot
workdir: ./test
-
name: Check dist
- name: Check dist
run: |
tree -nh ./test/_output
@ -225,27 +202,24 @@ jobs:
- goreleaser-pro
- goreleaser
steps:
-
name: Checkout
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
-
name: Set up Go
- name: Set up Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version: 1.18
-
name: GoReleaser
- name: GoReleaser
uses: ./
with:
install-only: true
distribution: ${{ matrix.distribution }}
version: nightly
-
name: Check
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Check
run: |
goreleaser check -f ./test/.goreleaser.yml
goreleaser --version
goreleaser --version | grep nightly

View file

@ -28,19 +28,15 @@ jobs:
tag:
runs-on: ubuntu-latest
steps:
-
name: Checkout
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
-
name: Git config
- name: Git config
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
-
name: Move ${{ github.event.inputs.major_version }} to ${{ github.event.inputs.target }}
- name: Move ${{ github.event.inputs.major_version }} to ${{ github.event.inputs.target }}
run: git tag -f ${{ github.event.inputs.major_version }} ${{ github.event.inputs.target }}
-
name: Push
- name: Push
run: git push origin ${{ github.event.inputs.major_version }} --force

View file

@ -19,28 +19,24 @@ jobs:
test:
runs-on: ubuntu-latest
steps:
-
name: Checkout
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
-
name: Setup Node.js
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v6.0.0
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: '.node-version'
cache: npm
-
name: Install cosign
uses: sigstore/cosign-installer@d58896d6a1865668819e1d91763c7751a165e159 # v3.9.2
-
name: Install dependencies
- name: Install cosign
uses: sigstore/cosign-installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 # v4.1.1
- name: Install dependencies
run: npm ci
-
name: Test
- name: Test
run: npm test
-
name: Upload coverage
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload coverage
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
with:
files: ./coverage/clover.xml

View file

@ -19,45 +19,35 @@ jobs:
lint:
runs-on: ubuntu-latest
steps:
-
name: Checkout
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
-
name: Setup Node.js
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v6.0.0
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: '.node-version'
cache: npm
-
name: Install dependencies
- name: Install dependencies
run: npm ci
-
name: Format check
- name: Format check
run: npm run format-check
-
name: Lint
- name: Lint
run: npm run lint
build:
runs-on: ubuntu-latest
steps:
-
name: Checkout
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
-
name: Setup Node.js
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v6.0.0
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.0.0
with:
node-version-file: '.node-version'
cache: npm
-
name: Install dependencies
- name: Install dependencies
run: npm ci --ignore-scripts
-
name: Rebuild dist
- name: Rebuild dist
run: npm run build
-
name: Compare dist
- name: Compare dist
id: diff
run: |
if [ "$(git diff --ignore-space-at-eol dist | wc -l)" -gt "0" ]; then
@ -65,10 +55,9 @@ jobs:
git diff dist
exit 1
fi
-
name: Upload built dist on failure
- name: Upload built dist on failure
if: ${{ failure() && steps.diff.conclusion == 'failure' }}
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: dist
path: dist
@ -76,20 +65,16 @@ jobs:
vendor:
runs-on: ubuntu-latest
steps:
-
name: Checkout
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
-
name: Setup Node.js
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v6.0.0
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.0.0
with:
node-version-file: '.node-version'
cache: npm
-
name: Refresh package-lock.json
- name: Refresh package-lock.json
run: npm install --package-lock-only
-
name: Compare package-lock.json
- name: Compare package-lock.json
run: |
if [ -n "$(git status --porcelain -- package-lock.json)" ]; then
echo "package-lock.json is out of sync with package.json. Run 'npm install' and commit." >&2

View file

@ -96,6 +96,11 @@ checksums file against the GoReleaser release workflow's OIDC identity. If
> versions the cosign step is silently skipped — only the `checksums.txt`
> SHA-256 verification runs.
> **Note**: when `version: nightly` is used, the action resolves the
> latest immutable `vX.Y.Z-<sha>-nightly` release from the GitHub
> Releases API. Pass `GITHUB_TOKEN` to the action step (as in the example
> above) to avoid unauthenticated API rate limits.
To enable signature verification, install cosign before running the action:
```yaml
@ -222,11 +227,28 @@ Following inputs can be used as `step.with` keys
|------------------|---------|--------------|------------------------------------------------------------------|
| `distribution` | String | `goreleaser` | GoReleaser distribution, either `goreleaser` or `goreleaser-pro` |
| `version`**Âą** | String | `~> v2` | GoReleaser version |
| `version-file`**²** | String | | Read the GoReleaser version from a file (see below) |
| `args` | String | | Arguments to pass to GoReleaser |
| `workdir` | String | `.` | Working directory (below repository root) |
| `install-only` | Bool | `false` | Just install GoReleaser |
> **Âą** Can be a fixed version like `v0.117.0` or a max satisfying semver one like `~> 0.132`. In this case this will return `v0.132.1`.
>
> **²** Path to a file containing the GoReleaser version. Resolved relative
> to `workdir`. Currently only [`.tool-versions`](https://asdf-vm.com/manage/configuration.html#tool-versions)
> (asdf/mise) format is supported. When set, this takes precedence over `version`.
>
> ```yaml
> # .tool-versions
> goreleaser 2.13.0
> ```
>
> ```yaml
> - uses: goreleaser/goreleaser-action@v7
> with:
> version-file: .tool-versions
> args: release --clean
> ```
### outputs

View file

@ -56,16 +56,16 @@ describe('getRelease', () => {
expect(release?.tag_name).not.toEqual('');
});
it('returns nightly GoReleaser GitHub release', async () => {
it('resolves nightly to a <version>-<sha>-nightly release for OSS GoReleaser', async () => {
const release = await github.getRelease('goreleaser', 'nightly');
expect(release).not.toBeNull();
expect(release?.tag_name).not.toEqual('');
expect(release.tag_name).toMatch(github.nightlyTagRegex);
});
it('returns nightly GoReleaser Pro GitHub release', async () => {
it('resolves nightly to a <version>-<sha>-nightly release for GoReleaser Pro', async () => {
const release = await github.getRelease('goreleaser-pro', 'nightly');
expect(release).not.toBeNull();
expect(release?.tag_name).not.toEqual('');
expect(release.tag_name).toMatch(github.nightlyTagRegex);
});
it('returns v0.182.0 GoReleaser Pro GitHub release', async () => {

View file

@ -16,11 +16,38 @@ describe('install', () => {
expect(fs.existsSync(bin)).toBe(true);
}, 100000);
// The following pinned versions exercise install across release eras to
// guard against regressions in checksum handling and the cosign skip path:
// - v0.182.0 : pre-checksums-signing era
// - v1.26.2 : cosign v2 detached `.sig` only
// - v2.12.4 : last release before sigstore bundles (cosign skipped)
// - v2.13.0 : first release with cosign v3 sigstore bundle
// - v2.15.3 : recent release with sigstore bundle
it('acquires v0.182.0 (pre-signing) version of GoReleaser', async () => {
const bin = await goreleaser.install('goreleaser', 'v0.182.0');
expect(fs.existsSync(bin)).toBe(true);
}, 100000);
it('acquires v1.26.2 (cosign v2 .sig) version of GoReleaser', async () => {
const bin = await goreleaser.install('goreleaser', 'v1.26.2');
expect(fs.existsSync(bin)).toBe(true);
}, 100000);
it('acquires v2.12.4 (last pre-sigstore-bundle) version of GoReleaser', async () => {
const bin = await goreleaser.install('goreleaser', 'v2.12.4');
expect(fs.existsSync(bin)).toBe(true);
}, 100000);
it('acquires v2.13.0 (minimum cosign-verifiable) version of GoReleaser', async () => {
const bin = await goreleaser.install('goreleaser', 'v2.13.0');
expect(fs.existsSync(bin)).toBe(true);
}, 100000);
it('acquires v2.15.3 (recent sigstore-bundle) version of GoReleaser', async () => {
const bin = await goreleaser.install('goreleaser', 'v2.15.3');
expect(fs.existsSync(bin)).toBe(true);
}, 100000);
it('acquires latest v2 version of GoReleaser Pro', async () => {
const bin = await goreleaser.install('goreleaser-pro', '~> v2');
expect(fs.existsSync(bin)).toBe(true);
@ -77,14 +104,14 @@ describe('getCertificateIdentity', () => {
);
});
it('uses nightly-oss.yml@refs/heads/main for OSS nightly', () => {
expect(goreleaser.getCertificateIdentity('goreleaser', 'nightly')).toEqual(
it('uses nightly-oss.yml@refs/heads/main for OSS nightly tag', () => {
expect(goreleaser.getCertificateIdentity('goreleaser', 'v2.16.0-abc1234-nightly')).toEqual(
'https://github.com/goreleaser/goreleaser/.github/workflows/nightly-oss.yml@refs/heads/main'
);
});
it('uses nightly-pro.yml@refs/heads/main for Pro nightly', () => {
expect(goreleaser.getCertificateIdentity('goreleaser-pro', 'nightly')).toEqual(
it('uses nightly-pro.yml@refs/heads/main for Pro nightly tag', () => {
expect(goreleaser.getCertificateIdentity('goreleaser-pro', 'v2.16.0-eaeb08c50-nightly')).toEqual(
'https://github.com/goreleaser/goreleaser-pro-internal/.github/workflows/nightly-pro.yml@refs/heads/main'
);
});
@ -112,6 +139,14 @@ describe('verifyChecksum', () => {
expect(fs.existsSync(bin)).toBe(true);
}, 120000);
it('installs a pre-v2.13 release (no sigstore bundle) without failing when cosign is present', async () => {
// v2.12.x is the last release that did NOT publish checksums.txt.sigstore.json.
// The action must still install it cleanly: checksum verified, cosign step skipped.
await requireCosign();
const bin = await goreleaser.install('goreleaser', 'v2.12.4');
expect(fs.existsSync(bin)).toBe(true);
}, 120000);
it('throws on checksum mismatch', async () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gha-'));
const archive = path.join(dir, 'fake.tar.gz');

117
__tests__/version.test.ts Normal file
View file

@ -0,0 +1,117 @@
import {describe, expect, it, beforeEach, afterEach} from '@jest/globals';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import {getRequestedVersion} from '../src/version';
import {Inputs} from '../src/context';
const baseInputs = (overrides: Partial<Inputs>): Inputs => ({
distribution: 'goreleaser',
version: '~> v2',
versionFile: '',
args: '',
workdir: '.',
installOnly: false,
...overrides
});
describe('getRequestedVersion', () => {
let tmpDir: string;
beforeEach(() => {
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'goreleaser-version-'));
});
afterEach(() => {
fs.rmSync(tmpDir, {recursive: true, force: true});
});
const writeToolVersions = (content: string, name = '.tool-versions'): void => {
fs.writeFileSync(path.join(tmpDir, name), content);
};
describe('without version-file', () => {
it('returns the version input as-is', () => {
expect(getRequestedVersion(baseInputs({version: 'v1.2.3'}))).toBe('v1.2.3');
});
it('returns the default version when none is provided', () => {
expect(getRequestedVersion(baseInputs({version: '~> v2'}))).toBe('~> v2');
});
});
describe('with .tool-versions', () => {
it('parses an unprefixed version and adds the v prefix', () => {
writeToolVersions('goreleaser 1.2.3\n');
expect(getRequestedVersion(baseInputs({versionFile: '.tool-versions', workdir: tmpDir}))).toBe('v1.2.3');
});
it('keeps an existing v prefix without doubling it', () => {
writeToolVersions('goreleaser v1.2.3\n');
expect(getRequestedVersion(baseInputs({versionFile: '.tool-versions', workdir: tmpDir}))).toBe('v1.2.3');
});
it('takes precedence over the version input', () => {
writeToolVersions('goreleaser 1.2.3\n');
expect(getRequestedVersion(baseInputs({version: 'v9.9.9', versionFile: '.tool-versions', workdir: tmpDir}))).toBe(
'v1.2.3'
);
});
it('ignores other tools and picks goreleaser', () => {
writeToolVersions(['nodejs 20.10.0', 'goreleaser 2.13.0', 'python 3.12.1', ''].join('\n'));
expect(getRequestedVersion(baseInputs({versionFile: '.tool-versions', workdir: tmpDir}))).toBe('v2.13.0');
});
it('skips full-line and inline comments', () => {
writeToolVersions(['# pinned for CI', 'goreleaser 2.13.0 # minimum cosign-verifiable', ''].join('\n'));
expect(getRequestedVersion(baseInputs({versionFile: '.tool-versions', workdir: tmpDir}))).toBe('v2.13.0');
});
it('preserves "latest"', () => {
writeToolVersions('goreleaser latest\n');
expect(getRequestedVersion(baseInputs({versionFile: '.tool-versions', workdir: tmpDir}))).toBe('latest');
});
it('uses only the first version when multiple fallbacks are listed', () => {
// asdf supports listing fallback versions; we install the first match.
writeToolVersions('goreleaser 2.13.0 2.12.4\n');
expect(getRequestedVersion(baseInputs({versionFile: '.tool-versions', workdir: tmpDir}))).toBe('v2.13.0');
});
it('accepts an absolute path and ignores workdir', () => {
const abs = path.join(tmpDir, '.tool-versions');
fs.writeFileSync(abs, 'goreleaser 2.13.0\n');
expect(getRequestedVersion(baseInputs({versionFile: abs, workdir: '/nonexistent'}))).toBe('v2.13.0');
});
it('throws when the file does not exist', () => {
expect(() => getRequestedVersion(baseInputs({versionFile: '.tool-versions', workdir: tmpDir}))).toThrow(
/version-file not found/
);
});
it('throws when the file has no goreleaser entry', () => {
writeToolVersions(['nodejs 20.10.0', 'python 3.12.1', ''].join('\n'));
expect(() => getRequestedVersion(baseInputs({versionFile: '.tool-versions', workdir: tmpDir}))).toThrow(
/No goreleaser entry/
);
});
it('throws when the goreleaser entry has no version', () => {
writeToolVersions('goreleaser\n');
expect(() => getRequestedVersion(baseInputs({versionFile: '.tool-versions', workdir: tmpDir}))).toThrow(
/No version specified for goreleaser/
);
});
});
describe('with an unsupported file', () => {
it('throws a clear error', () => {
fs.writeFileSync(path.join(tmpDir, '.go-version'), '1.2.3\n');
expect(() => getRequestedVersion(baseInputs({versionFile: '.go-version', workdir: tmpDir}))).toThrow(
/Unsupported version-file/
);
});
});
});

View file

@ -15,6 +15,12 @@ inputs:
description: 'GoReleaser version'
default: '~> v2'
required: false
version-file:
description: |
Read the GoReleaser version from a file. Path is resolved relative to
`workdir`. Currently only `.tool-versions` (asdf/mise) is supported.
When set, takes precedence over `version`.
required: false
args:
description: 'Arguments to pass to GoReleaser'
required: false

4
dist/index.js generated vendored

File diff suppressed because one or more lines are too long

View file

@ -7,6 +7,7 @@ export const osArch: string = os.arch();
export interface Inputs {
distribution: string;
version: string;
versionFile: string;
args: string;
workdir: string;
installOnly: boolean;
@ -16,6 +17,7 @@ export async function getInputs(): Promise<Inputs> {
return {
distribution: core.getInput('distribution') || 'goreleaser',
version: core.getInput('version') || '~> v2',
versionFile: core.getInput('version-file'),
args: core.getInput('args'),
workdir: core.getInput('workdir') || '.',
installOnly: core.getBooleanInput('install-only')

View file

@ -30,6 +30,13 @@ export interface GitHubRelease {
tag_name: string;
}
// Matches the new-style nightly release tag pattern: vX.Y.Z-<sha>-nightly
export const nightlyTagRegex = /^v\d+\.\d+\.\d+-[0-9a-f]+-nightly$/i;
export const isNightlyTag = (tag: string): boolean => {
return nightlyTagRegex.test(tag);
};
export const getRelease = async (distribution: string, version: string): Promise<GitHubRelease> => {
if (version === 'latest') {
core.warning("You are using 'latest' as default version. Will lock to '~> v2'.");
@ -40,7 +47,7 @@ export const getRelease = async (distribution: string, version: string): Promise
export const getReleaseTag = async (distribution: string, version: string): Promise<GitHubRelease> => {
if (version === 'nightly') {
return {tag_name: version};
return resolveNightly(distribution);
}
// If version is a specific version (not a range), skip the JSON check
@ -81,6 +88,39 @@ export const getReleaseTag = async (distribution: string, version: string): Prom
throw new Error(`Cannot find GoReleaser release ${version} in ${url}`);
};
// resolveNightly looks up the latest immutable nightly release of the form
// `vX.Y.Z-<sha>-nightly` on the GitHub releases of the given distribution.
const resolveNightly = async (distribution: string): Promise<GitHubRelease> => {
const url = `https://api.github.com/repos/goreleaser/${distribution}/releases?per_page=100`;
core.debug(`Resolving latest nightly release from ${url}`);
const releases = await withRetry(async () => {
const http: httpm.HttpClient = new httpm.HttpClient('goreleaser-action');
const headers: {[name: string]: string} = {
Accept: 'application/vnd.github+json',
'X-GitHub-Api-Version': '2022-11-28'
};
const token = process.env.GITHUB_TOKEN;
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
const resp: httpm.HttpClientResponse = await http.get(url, headers);
const body = await resp.readBody();
const statusCode = resp.message.statusCode || 500;
if (statusCode >= 400) {
throw new Error(`Failed to list releases from ${url} with status code ${statusCode}: ${body}`);
}
return <Array<GitHubRelease>>JSON.parse(body);
});
const match = releases.find(r => nightlyTagRegex.test(r.tag_name));
if (!match) {
throw new Error(`No '<version>-<sha>-nightly' release found in ${url}`);
}
core.info(`Resolved nightly to ${match.tag_name}`);
return match;
};
const resolveVersion = async (distribution: string, version: string): Promise<string | null> => {
const allTags: Array<string> | null = await getAllTags(distribution);
if (!allTags) {

View file

@ -120,7 +120,7 @@ async function verifyCosignSignature(
export const getCertificateIdentity = (distribution: string, tag: string): string => {
const pro = isPro(distribution);
if (tag === 'nightly') {
if (github.isNightlyTag(tag)) {
const workflow = pro ? 'nightly-pro.yml' : 'nightly-oss.yml';
const repo = pro ? 'goreleaser-pro-internal' : 'goreleaser';
return `https://github.com/goreleaser/${repo}/.github/workflows/${workflow}@refs/heads/main`;

View file

@ -4,14 +4,16 @@ import yargs from 'yargs';
import type {Arguments} from 'yargs';
import * as context from './context';
import * as goreleaser from './goreleaser';
import {getRequestedVersion} from './version';
import * as core from '@actions/core';
import * as exec from '@actions/exec';
async function run(): Promise<void> {
try {
const inputs: context.Inputs = await context.getInputs();
const bin = await goreleaser.install(inputs.distribution, inputs.version);
core.info(`GoReleaser ${inputs.version} installed successfully`);
const version = getRequestedVersion(inputs);
const bin = await goreleaser.install(inputs.distribution, version);
core.info(`GoReleaser ${version} installed successfully`);
if (inputs.installOnly) {
const goreleaserDir = path.dirname(bin);

56
src/version.ts Normal file
View file

@ -0,0 +1,56 @@
import * as fs from 'fs';
import * as path from 'path';
import {Inputs} from './context';
// Resolves the GoReleaser version to install.
//
// When `version-file` is set, it is read from disk and parsed; the resolved
// value takes precedence over the `version` input. Otherwise, `version` is
// returned as-is (it always has a default — see context.getInputs).
export function getRequestedVersion(inputs: Inputs): string {
if (!inputs.versionFile) {
return inputs.version;
}
const filePath = path.isAbsolute(inputs.versionFile)
? inputs.versionFile
: path.join(inputs.workdir || '.', inputs.versionFile);
if (!fs.existsSync(filePath)) {
throw new Error(`version-file not found: ${filePath}`);
}
const basename = path.basename(filePath);
const content = fs.readFileSync(filePath, 'utf-8');
switch (basename) {
case '.tool-versions':
return parseToolVersions(content, filePath);
default:
throw new Error(`Unsupported version-file: ${filePath} (only .tool-versions is supported)`);
}
}
// Parses a single `goreleaser <version>` entry out of a `.tool-versions` file
// (asdf/mise format). Full-line `#` comments and inline `# ...` suffixes are
// stripped. When a tool lists multiple fallback versions only the first is
// used. Bare semvers are returned with a leading `v`; constraint expressions
// (`~> v2`, `latest`, ...) are returned as-is.
function parseToolVersions(content: string, filePath: string): string {
for (const rawLine of content.split('\n')) {
const line = rawLine.replace(/#.*$/, '').trim();
if (!line) {
continue;
}
const tokens = line.split(/\s+/);
if (tokens[0] !== 'goreleaser') {
continue;
}
const version = tokens[1];
if (!version) {
throw new Error(`No version specified for goreleaser in ${filePath}`);
}
return /^\d/.test(version) ? `v${version}` : version;
}
throw new Error(`No goreleaser entry found in ${filePath}`);
}