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>
This commit is contained in:
Carlos Alexandro Becker 2026-04-23 00:48:06 -03:00
parent 15fa2a96d4
commit 54268d5e7e
No known key found for this signature in database
5 changed files with 51 additions and 11 deletions

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 vX.Y.Z-<sha>-nightly GoReleaser release', 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 vX.Y.Z-<sha>-nightly GoReleaser Pro release', 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

@ -104,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-abc1234-nightly')).toEqual(
'https://github.com/goreleaser/goreleaser-pro-internal/.github/workflows/nightly-pro.yml@refs/heads/main'
);
});

2
dist/index.js generated vendored

File diff suppressed because one or more lines are too long

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`;