Skip to content

Commit 4f96abf

Browse files
caarlos0acouvreurCopilot
authored
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>
1 parent 15fa2a9 commit 4f96abf

7 files changed

Lines changed: 204 additions & 4 deletions

File tree

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,11 +222,28 @@ Following inputs can be used as `step.with` keys
222222
|------------------|---------|--------------|------------------------------------------------------------------|
223223
| `distribution` | String | `goreleaser` | GoReleaser distribution, either `goreleaser` or `goreleaser-pro` |
224224
| `version`**¹** | String | `~> v2` | GoReleaser version |
225+
| `version-file`**²** | String | | Read the GoReleaser version from a file (see below) |
225226
| `args` | String | | Arguments to pass to GoReleaser |
226227
| `workdir` | String | `.` | Working directory (below repository root) |
227228
| `install-only` | Bool | `false` | Just install GoReleaser |
228229

229230
> **¹** 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`.
231+
>
232+
> **²** Path to a file containing the GoReleaser version. Resolved relative
233+
> to `workdir`. Currently only [`.tool-versions`](https://asdf-vm.com/manage/configuration.html#tool-versions)
234+
> (asdf/mise) format is supported. When set, this takes precedence over `version`.
235+
>
236+
> ```yaml
237+
> # .tool-versions
238+
> goreleaser 2.13.0
239+
> ```
240+
>
241+
> ```yaml
242+
> - uses: goreleaser/goreleaser-action@v7
243+
> with:
244+
> version-file: .tool-versions
245+
> args: release --clean
246+
> ```
230247

231248
### outputs
232249

__tests__/version.test.ts

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import {describe, expect, it, beforeEach, afterEach} from '@jest/globals';
2+
import * as fs from 'fs';
3+
import * as os from 'os';
4+
import * as path from 'path';
5+
import {getRequestedVersion} from '../src/version';
6+
import {Inputs} from '../src/context';
7+
8+
const baseInputs = (overrides: Partial<Inputs>): Inputs => ({
9+
distribution: 'goreleaser',
10+
version: '~> v2',
11+
versionFile: '',
12+
args: '',
13+
workdir: '.',
14+
installOnly: false,
15+
...overrides
16+
});
17+
18+
describe('getRequestedVersion', () => {
19+
let tmpDir: string;
20+
21+
beforeEach(() => {
22+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'goreleaser-version-'));
23+
});
24+
25+
afterEach(() => {
26+
fs.rmSync(tmpDir, {recursive: true, force: true});
27+
});
28+
29+
const writeToolVersions = (content: string, name = '.tool-versions'): void => {
30+
fs.writeFileSync(path.join(tmpDir, name), content);
31+
};
32+
33+
describe('without version-file', () => {
34+
it('returns the version input as-is', () => {
35+
expect(getRequestedVersion(baseInputs({version: 'v1.2.3'}))).toBe('v1.2.3');
36+
});
37+
38+
it('returns the default version when none is provided', () => {
39+
expect(getRequestedVersion(baseInputs({version: '~> v2'}))).toBe('~> v2');
40+
});
41+
});
42+
43+
describe('with .tool-versions', () => {
44+
it('parses an unprefixed version and adds the v prefix', () => {
45+
writeToolVersions('goreleaser 1.2.3\n');
46+
expect(getRequestedVersion(baseInputs({versionFile: '.tool-versions', workdir: tmpDir}))).toBe('v1.2.3');
47+
});
48+
49+
it('keeps an existing v prefix without doubling it', () => {
50+
writeToolVersions('goreleaser v1.2.3\n');
51+
expect(getRequestedVersion(baseInputs({versionFile: '.tool-versions', workdir: tmpDir}))).toBe('v1.2.3');
52+
});
53+
54+
it('takes precedence over the version input', () => {
55+
writeToolVersions('goreleaser 1.2.3\n');
56+
expect(getRequestedVersion(baseInputs({version: 'v9.9.9', versionFile: '.tool-versions', workdir: tmpDir}))).toBe(
57+
'v1.2.3'
58+
);
59+
});
60+
61+
it('ignores other tools and picks goreleaser', () => {
62+
writeToolVersions(['nodejs 20.10.0', 'goreleaser 2.13.0', 'python 3.12.1', ''].join('\n'));
63+
expect(getRequestedVersion(baseInputs({versionFile: '.tool-versions', workdir: tmpDir}))).toBe('v2.13.0');
64+
});
65+
66+
it('skips full-line and inline comments', () => {
67+
writeToolVersions(['# pinned for CI', 'goreleaser 2.13.0 # minimum cosign-verifiable', ''].join('\n'));
68+
expect(getRequestedVersion(baseInputs({versionFile: '.tool-versions', workdir: tmpDir}))).toBe('v2.13.0');
69+
});
70+
71+
it('preserves "latest"', () => {
72+
writeToolVersions('goreleaser latest\n');
73+
expect(getRequestedVersion(baseInputs({versionFile: '.tool-versions', workdir: tmpDir}))).toBe('latest');
74+
});
75+
76+
it('uses only the first version when multiple fallbacks are listed', () => {
77+
// asdf supports listing fallback versions; we install the first match.
78+
writeToolVersions('goreleaser 2.13.0 2.12.4\n');
79+
expect(getRequestedVersion(baseInputs({versionFile: '.tool-versions', workdir: tmpDir}))).toBe('v2.13.0');
80+
});
81+
82+
it('accepts an absolute path and ignores workdir', () => {
83+
const abs = path.join(tmpDir, '.tool-versions');
84+
fs.writeFileSync(abs, 'goreleaser 2.13.0\n');
85+
expect(getRequestedVersion(baseInputs({versionFile: abs, workdir: '/nonexistent'}))).toBe('v2.13.0');
86+
});
87+
88+
it('throws when the file does not exist', () => {
89+
expect(() => getRequestedVersion(baseInputs({versionFile: '.tool-versions', workdir: tmpDir}))).toThrow(
90+
/version-file not found/
91+
);
92+
});
93+
94+
it('throws when the file has no goreleaser entry', () => {
95+
writeToolVersions(['nodejs 20.10.0', 'python 3.12.1', ''].join('\n'));
96+
expect(() => getRequestedVersion(baseInputs({versionFile: '.tool-versions', workdir: tmpDir}))).toThrow(
97+
/No goreleaser entry/
98+
);
99+
});
100+
101+
it('throws when the goreleaser entry has no version', () => {
102+
writeToolVersions('goreleaser\n');
103+
expect(() => getRequestedVersion(baseInputs({versionFile: '.tool-versions', workdir: tmpDir}))).toThrow(
104+
/No version specified for goreleaser/
105+
);
106+
});
107+
});
108+
109+
describe('with an unsupported file', () => {
110+
it('throws a clear error', () => {
111+
fs.writeFileSync(path.join(tmpDir, '.go-version'), '1.2.3\n');
112+
expect(() => getRequestedVersion(baseInputs({versionFile: '.go-version', workdir: tmpDir}))).toThrow(
113+
/Unsupported version-file/
114+
);
115+
});
116+
});
117+
});

action.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ inputs:
1515
description: 'GoReleaser version'
1616
default: '~> v2'
1717
required: false
18+
version-file:
19+
description: |
20+
Read the GoReleaser version from a file. Path is resolved relative to
21+
`workdir`. Currently only `.tool-versions` (asdf/mise) is supported.
22+
When set, takes precedence over `version`.
23+
required: false
1824
args:
1925
description: 'Arguments to pass to GoReleaser'
2026
required: false

dist/index.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/context.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export const osArch: string = os.arch();
77
export interface Inputs {
88
distribution: string;
99
version: string;
10+
versionFile: string;
1011
args: string;
1112
workdir: string;
1213
installOnly: boolean;
@@ -16,6 +17,7 @@ export async function getInputs(): Promise<Inputs> {
1617
return {
1718
distribution: core.getInput('distribution') || 'goreleaser',
1819
version: core.getInput('version') || '~> v2',
20+
versionFile: core.getInput('version-file'),
1921
args: core.getInput('args'),
2022
workdir: core.getInput('workdir') || '.',
2123
installOnly: core.getBooleanInput('install-only')

src/main.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,16 @@ import yargs from 'yargs';
44
import type {Arguments} from 'yargs';
55
import * as context from './context';
66
import * as goreleaser from './goreleaser';
7+
import {getRequestedVersion} from './version';
78
import * as core from '@actions/core';
89
import * as exec from '@actions/exec';
910

1011
async function run(): Promise<void> {
1112
try {
1213
const inputs: context.Inputs = await context.getInputs();
13-
const bin = await goreleaser.install(inputs.distribution, inputs.version);
14-
core.info(`GoReleaser ${inputs.version} installed successfully`);
14+
const version = getRequestedVersion(inputs);
15+
const bin = await goreleaser.install(inputs.distribution, version);
16+
core.info(`GoReleaser ${version} installed successfully`);
1517

1618
if (inputs.installOnly) {
1719
const goreleaserDir = path.dirname(bin);

src/version.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import * as fs from 'fs';
2+
import * as path from 'path';
3+
import {Inputs} from './context';
4+
5+
// Resolves the GoReleaser version to install.
6+
//
7+
// When `version-file` is set, it is read from disk and parsed; the resolved
8+
// value takes precedence over the `version` input. Otherwise, `version` is
9+
// returned as-is (it always has a default — see context.getInputs).
10+
export function getRequestedVersion(inputs: Inputs): string {
11+
if (!inputs.versionFile) {
12+
return inputs.version;
13+
}
14+
15+
const filePath = path.isAbsolute(inputs.versionFile)
16+
? inputs.versionFile
17+
: path.join(inputs.workdir || '.', inputs.versionFile);
18+
19+
if (!fs.existsSync(filePath)) {
20+
throw new Error(`version-file not found: ${filePath}`);
21+
}
22+
23+
const basename = path.basename(filePath);
24+
const content = fs.readFileSync(filePath, 'utf-8');
25+
26+
switch (basename) {
27+
case '.tool-versions':
28+
return parseToolVersions(content, filePath);
29+
default:
30+
throw new Error(`Unsupported version-file: ${filePath} (only .tool-versions is supported)`);
31+
}
32+
}
33+
34+
// Parses a single `goreleaser <version>` entry out of a `.tool-versions` file
35+
// (asdf/mise format). Full-line `#` comments and inline `# ...` suffixes are
36+
// stripped. When a tool lists multiple fallback versions only the first is
37+
// used. Bare semvers are returned with a leading `v`; constraint expressions
38+
// (`~> v2`, `latest`, ...) are returned as-is.
39+
function parseToolVersions(content: string, filePath: string): string {
40+
for (const rawLine of content.split('\n')) {
41+
const line = rawLine.replace(/#.*$/, '').trim();
42+
if (!line) {
43+
continue;
44+
}
45+
const tokens = line.split(/\s+/);
46+
if (tokens[0] !== 'goreleaser') {
47+
continue;
48+
}
49+
const version = tokens[1];
50+
if (!version) {
51+
throw new Error(`No version specified for goreleaser in ${filePath}`);
52+
}
53+
return /^\d/.test(version) ? `v${version}` : version;
54+
}
55+
throw new Error(`No goreleaser entry found in ${filePath}`);
56+
}

0 commit comments

Comments
 (0)