diff --git a/README.md b/README.md index ecee210..cd669a9 100644 --- a/README.md +++ b/README.md @@ -94,10 +94,13 @@ the [check-latest input](docs/advanced-usage.md#check-latest-version). The `go-version` input supports the following syntax: - Specific versions: `1.25`, `1.24.11`, `1.24.0-rc.1`, `1.23.0-beta.1` +- Native Go versions: `go1.21`, `go1.21.5`, `go1.18beta2`, `go1.21rc2`. `go1.21` resolves to exactly `1.21.0`, unlike `1.21`, which matches patch releases. - SemVer version range syntax: `^1.25.1`, `~1.24.1`, `>=1.25.0-rc.1`, `<1.25.0`, `>=1.22.0 <1.24.0` - Aliases: `stable`, `oldstable` - Wildcards: `1.25.x`, `1.x` +> **Note:** Native Go version validation and normalization apply only when no custom download base URL is configured. When `go-download-base-url` or `GO_DOWNLOAD_BASE_URL` is set, the `go-version` input is passed through unchanged. + For details on Semantic Versioning, see [the semver package documentation](https://github.com/npm/node-semver). > **Note**: Due to the peculiarities of YAML parsing, it is recommended to wrap the version in single quotation marks: diff --git a/__tests__/setup-go.test.ts b/__tests__/setup-go.test.ts index c1946bd..5bfc1a7 100644 --- a/__tests__/setup-go.test.ts +++ b/__tests__/setup-go.test.ts @@ -874,6 +874,121 @@ describe('setup-go', () => { expect(im.makeSemver('1.13.1')).toBe('1.13.1'); }); + it('isGoNativeVersion accepts supported Go-native version formats', () => { + expect(im.isGoNativeVersion('go1.21')).toBe(true); + expect(im.isGoNativeVersion('go1.21.5')).toBe(true); + expect(im.isGoNativeVersion('go1.18beta2')).toBe(true); + expect(im.isGoNativeVersion('go1.21rc2')).toBe(true); + + expect(im.isGoNativeVersion('go1.21.x')).toBe(false); + expect(im.isGoNativeVersion('go^1.21')).toBe(false); + expect(im.isGoNativeVersion('1.21')).toBe(false); + expect(im.isGoNativeVersion('go1')).toBe(false); + expect(im.isGoNativeVersion('go1.21.5beta1')).toBe(false); + expect(im.isGoNativeVersion('go1.21.0rc1')).toBe(false); + }); + + it('accepts a Go-native go-version input and normalizes it', async () => { + os.platform = 'linux'; + os.arch = 'x64'; + inputs['go-version'] = 'go1.21'; + findSpy.mockImplementation(() => path.normalize('/cache/go/1.21.0/x64')); + + await main.run(); + + expect(logSpy).toHaveBeenCalledWith('Setup go version spec 1.21.0'); + expect(findSpy).toHaveBeenCalledWith('go', '1.21.0', 'x64'); + expect(logSpy).toHaveBeenCalledWith( + 'Successfully set up Go version 1.21.0' + ); + }); + + it('rejects a malformed Go-native go-version input', async () => { + os.platform = 'linux'; + os.arch = 'x64'; + inputs['go-version'] = 'go1.21.x'; + + await main.run(); + + expect(cnSpy).toHaveBeenCalledWith( + expect.stringContaining('Invalid Go version "go1.21.x"') + ); + }); + + it('accepts a Go-native go-version input with beta suffix and normalizes it', async () => { + os.platform = 'linux'; + os.arch = 'x64'; + inputs['go-version'] = 'go1.18beta2'; + findSpy.mockImplementation(() => + path.normalize('/cache/go/1.18.0-beta.2/x64') + ); + + await main.run(); + + expect(logSpy).toHaveBeenCalledWith('Setup go version spec 1.18.0-beta.2'); + expect(findSpy).toHaveBeenCalledWith('go', '1.18.0-beta.2', 'x64'); + expect(logSpy).toHaveBeenCalledWith( + 'Successfully set up Go version 1.18.0-beta.2' + ); + }); + + it('accepts a Go-native go-version input with rc suffix and normalizes it', async () => { + os.platform = 'linux'; + os.arch = 'x64'; + inputs['go-version'] = 'go1.21rc2'; + findSpy.mockImplementation(() => + path.normalize('/cache/go/1.21.0-rc.2/x64') + ); + + await main.run(); + + expect(logSpy).toHaveBeenCalledWith('Setup go version spec 1.21.0-rc.2'); + expect(findSpy).toHaveBeenCalledWith('go', '1.21.0-rc.2', 'x64'); + expect(logSpy).toHaveBeenCalledWith( + 'Successfully set up Go version 1.21.0-rc.2' + ); + }); + + it('downloads a normalized Go-native version on cache miss', async () => { + os.platform = 'linux'; + os.arch = 'x64'; + inputs['go-version'] = 'go1.13'; + + findSpy.mockImplementation(() => ''); + dlSpy.mockImplementation(async () => '/some/temp/path'); + extractTarSpy.mockImplementation(async () => '/some/other/temp/path'); + cacheSpy.mockImplementation(async () => + path.normalize('/cache/go/1.13.0/x64') + ); + + await main.run(); + + expect(logSpy).toHaveBeenCalledWith('Attempting to download 1.13.0...'); + expect(dlSpy).toHaveBeenCalled(); + expect(logSpy).toHaveBeenCalledWith( + 'Successfully set up Go version 1.13.0' + ); + }); + + it('keeps go-prefixed version unchanged when a custom download URL is set', async () => { + os.platform = 'linux'; + os.arch = 'x64'; + + const customBaseUrl = 'https://example.com/golang'; + inputs['go-version'] = 'go1.25.1'; + inputs['go-download-base-url'] = customBaseUrl; + + const toolName = im.customToolCacheName(customBaseUrl); + findSpy.mockImplementation(() => + path.normalize(`/cache/${toolName}/1.25.1/x64`) + ); + + await main.run(); + + expect(logSpy).toHaveBeenCalledWith('Setup go version spec go1.25.1'); + expect(findSpy).toHaveBeenCalledWith(toolName, 'go1.25.1', 'x64'); + }); + describe('check-latest flag', () => { it("use local version and don't check manifest if check-latest is not specified", async () => { os.platform = 'linux'; diff --git a/action.yml b/action.yml index 79299e5..e66304c 100644 --- a/action.yml +++ b/action.yml @@ -3,7 +3,7 @@ description: 'Setup a Go environment and add it to the PATH' author: 'GitHub' inputs: go-version: - description: 'The Go version to download (if necessary) and use. Supports semver spec and ranges. Be sure to enclose this option in single quotation marks.' + description: 'The Go version to download (if necessary) and use. Supports native Go versions (e.g. go1.21.5), semver specs and ranges. Be sure to enclose this option in single quotation marks.' go-version-file: description: 'Path to the go.mod, go.work, .go-version, or .tool-versions file.' check-latest: diff --git a/dist/setup/index.js b/dist/setup/index.js index e8bba74..4ef95a4 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -42335,6 +42335,10 @@ async function findMatch(versionSpec, arch = external_os_default().arch(), dlUrl } return result; } +function isGoNativeVersion(version) { + const expression = /^go[1-9]\d*\.(0|[1-9]\d*)(?:\.(0|[1-9]\d*)|(?:beta|rc)[1-9]\d*)?$/; + return expression.test(version); +} // // Convert the go version syntax into semver for semver matching // 1.13.1 => 1.13.1 @@ -97835,6 +97839,15 @@ function resolveVersionInput() { warning('Both go-version and go-version-file inputs are specified, only go-version will be used'); } if (version) { + // With a custom download URL leave the version unchanged. + const customBaseUrl = getInput('go-download-base-url') || + process.env['GO_DOWNLOAD_BASE_URL']; + if (version.startsWith('go') && !customBaseUrl) { + if (!isGoNativeVersion(version)) { + throw new Error(`Invalid Go version "${version}". Go-native versions must look like go1.21, go1.21.5, go1.18beta2, or go1.21rc2. For version ranges, use semver syntax (e.g. ~1.21, ^1.21).`); + } + version = makeSemver(version); + } return version; } if (versionFilePath) { diff --git a/docs/advanced-usage.md b/docs/advanced-usage.md index 0e3efa5..dbffded 100644 --- a/docs/advanced-usage.md +++ b/docs/advanced-usage.md @@ -53,6 +53,8 @@ steps: To try a **pre-release**: Download and use beta or RC Go versions as needed. +When no custom download base URL is configured, the `go-version` input also accepts native Go version strings: `go1.21.5`, `go1.18beta2`, and `go1.21rc2` normalize to `1.21.5`, `1.18.0-beta.2`, and `1.21.0-rc.2`, respectively. `go1.21` resolves to exactly `1.21.0`; use `1.21` for matching patch releases. For ranges and wildcards, use SemVer syntax such as `~1.21` or `1.21.x`, not `go1.21.x`. + ```yaml # RC version steps: diff --git a/src/installer.ts b/src/installer.ts index 1354d7b..be05fe8 100644 --- a/src/installer.ts +++ b/src/installer.ts @@ -610,6 +610,12 @@ export async function findMatch( return result; } +export function isGoNativeVersion(version: string): boolean { + const expression = + /^go[1-9]\d*\.(0|[1-9]\d*)(?:\.(0|[1-9]\d*)|(?:beta|rc)[1-9]\d*)?$/; + return expression.test(version); +} + // // Convert the go version syntax into semver for semver matching // 1.13.1 => 1.13.1 diff --git a/src/main.ts b/src/main.ts index b1c9a49..ac3d8ae 100644 --- a/src/main.ts +++ b/src/main.ts @@ -215,6 +215,18 @@ function resolveVersionInput(): string { } if (version) { + // With a custom download URL leave the version unchanged. + const customBaseUrl = + core.getInput('go-download-base-url') || + process.env['GO_DOWNLOAD_BASE_URL']; + if (version.startsWith('go') && !customBaseUrl) { + if (!installer.isGoNativeVersion(version)) { + throw new Error( + `Invalid Go version "${version}". Go-native versions must look like go1.21, go1.21.5, go1.18beta2, or go1.21rc2. For version ranges, use semver syntax (e.g. ~1.21, ^1.21).` + ); + } + version = installer.makeSemver(version); + } return version; }