13
0
Fork 0
mirror of https://github.com/goreleaser/goreleaser-action.git synced 2026-07-02 02:59:33 +00:00
goreleaser-action/src/github.ts
Copilot 5daf1e915a
Some checks failed
test / test (push) Has been cancelled
validate / lint (push) Has been cancelled
ci / ci (goreleaser, macos-latest, latest) (push) Has been cancelled
ci / ci (goreleaser, macos-latest, ~> 2.13) (push) Has been cancelled
ci / ci (goreleaser, ubuntu-latest, latest) (push) Has been cancelled
ci / ci (goreleaser, ubuntu-latest, ~> 2.13) (push) Has been cancelled
ci / ci (goreleaser, windows-latest, latest) (push) Has been cancelled
ci / ci (goreleaser, windows-latest, ~> 2.13) (push) Has been cancelled
ci / ci (goreleaser-pro, macos-latest, latest) (push) Has been cancelled
ci / ci (goreleaser-pro, macos-latest, ~> 2.13) (push) Has been cancelled
ci / ci (goreleaser-pro, ubuntu-latest, latest) (push) Has been cancelled
ci / ci (goreleaser-pro, ubuntu-latest, ~> 2.13) (push) Has been cancelled
ci / ci (goreleaser-pro, windows-latest, latest) (push) Has been cancelled
ci / ci (goreleaser-pro, windows-latest, ~> 2.13) (push) Has been cancelled
ci / install-only (false, goreleaser, latest) (push) Has been cancelled
ci / install-only (false, goreleaser, ~> 2.13) (push) Has been cancelled
ci / install-only (false, goreleaser-pro, latest) (push) Has been cancelled
ci / install-only (false, goreleaser-pro, ~> 2.13) (push) Has been cancelled
ci / install-only (true, goreleaser, latest) (push) Has been cancelled
ci / install-only (true, goreleaser, ~> 2.13) (push) Has been cancelled
ci / install-only (true, goreleaser-pro, latest) (push) Has been cancelled
ci / install-only (true, goreleaser-pro, ~> 2.13) (push) Has been cancelled
ci / signing (macos-latest) (push) Has been cancelled
ci / signing (ubuntu-latest) (push) Has been cancelled
ci / signing (windows-latest) (push) Has been cancelled
ci / upload-artifact (push) Has been cancelled
ci / dist (push) Has been cancelled
ci / nightly (goreleaser, macos-latest) (push) Has been cancelled
ci / nightly (goreleaser, ubuntu-latest) (push) Has been cancelled
ci / nightly (goreleaser, windows-latest) (push) Has been cancelled
ci / nightly (goreleaser-pro, macos-latest) (push) Has been cancelled
ci / nightly (goreleaser-pro, ubuntu-latest) (push) Has been cancelled
ci / nightly (goreleaser-pro, windows-latest) (push) Has been cancelled
validate / build (push) Has been cancelled
validate / vendor (push) Has been cancelled
fix: nightly resolution to select newest published release (#562)
* fix(nightly): pick latest nightly by published_at

GitHub's /releases endpoint is not reliably ordered by published_at,
so resolveNightly could pick an older nightly than the most recent
one. Filter, sort by published_at desc, and take the first.

* test(nightly): add regression coverage for release ordering

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-05-18 21:38:28 -03:00

174 lines
6 KiB
TypeScript

import * as goreleaser from './goreleaser';
import * as semver from 'semver';
import * as core from '@actions/core';
import * as httpm from '@actions/http-client';
const maxRetries = 10;
const timeoutMs = 1000;
const withRetry = async <T>(operation: () => Promise<T>): Promise<T> => {
let lastError: Error;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
lastError = error as Error;
if (attempt === maxRetries) {
break;
}
core.debug(`Attempt ${attempt + 1} failed, retrying in ${timeoutMs}: ${lastError.message}`);
await new Promise(resolve => setTimeout(resolve, timeoutMs));
}
}
throw lastError;
};
export interface GitHubRelease {
tag_name: string;
published_at?: 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 latestNightlyRelease = (releases: Array<GitHubRelease>): GitHubRelease | undefined => {
return releases
.filter(r => nightlyTagRegex.test(r.tag_name))
.sort((a, b) => (b.published_at || '').localeCompare(a.published_at || ''))
.shift();
};
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'.");
return getReleaseTag(distribution, '~> v2');
}
return getReleaseTag(distribution, version);
};
export const getReleaseTag = async (distribution: string, version: string): Promise<GitHubRelease> => {
if (version === 'nightly') {
return resolveNightly(distribution);
}
// If version is a specific version (not a range), skip the JSON check
const cleanVersion: string = cleanTag(version);
if (semver.valid(cleanVersion)) {
let tag = version.startsWith('v') ? version : `v${version}`;
// Handle GoReleaser Pro suffix for versions < 2.7.0, but only if not already present
// TODO: remove all this `-pro` thing at some point.
if (goreleaser.isPro(distribution) && semver.lt(cleanVersion, '2.7.0') && !tag.endsWith('-pro')) {
tag = tag + goreleaser.distribSuffix(distribution);
}
return {tag_name: tag};
}
const tag: string = (await resolveVersion(distribution, version)) || version;
const suffix: string = goreleaser.distribSuffix(distribution);
const url = `https://goreleaser.com/releases${suffix}.json`;
const releases = await withRetry(async () => {
const http: httpm.HttpClient = new httpm.HttpClient('goreleaser-action');
const resp: httpm.HttpClientResponse = await http.get(url);
const body = await resp.readBody();
const statusCode = resp.message.statusCode || 500;
if (statusCode >= 400) {
throw new Error(
`Failed to get GoReleaser release ${version} from ${url} with status code ${statusCode}: ${body}`
);
}
return <Array<GitHubRelease>>JSON.parse(body);
});
const res = releases.filter(r => r.tag_name === tag).shift();
if (res) {
return res;
}
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 = latestNightlyRelease(releases);
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) {
throw new Error(`Cannot download ${distribution} tags`);
}
core.debug(`Found ${allTags.length} tags in total`);
const cleanTags: Array<string> = allTags.map(tag => cleanTag(tag));
const cleanVersion: string = cleanTag(version);
if (!semver.valid(cleanVersion) && !semver.validRange(cleanVersion)) {
// if the given version is invalid, return whatever we got.
return version;
}
const v = semver.maxSatisfying(cleanTags, cleanVersion);
if (semver.lt(v, '2.7.0')) {
// if its a version older than 2.7.0, append the suffix.
return v + goreleaser.distribSuffix(distribution);
}
return v;
};
interface GitHubTag {
tag_name: string;
}
const getAllTags = async (distribution: string): Promise<Array<string>> => {
const suffix: string = goreleaser.distribSuffix(distribution);
const url = `https://goreleaser.com/releases${suffix}.json`;
core.debug(`Downloading ${url}`);
return withRetry(async () => {
const http: httpm.HttpClient = new httpm.HttpClient('goreleaser-action');
const response = await http.getJson<Array<GitHubTag>>(url);
if (response.result == null) {
return [];
}
return response.result.map(obj => obj.tag_name);
});
};
const cleanTag = (tag: string): string => {
return tag.replace(/-pro$/, '');
};