actions-up 1.2.1 ā 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +3 -3
- package/dist/core/api/check-updates.js +11 -7
- package/dist/core/api/create-github-client.d.ts +8 -0
- package/dist/core/api/create-github-client.js +55 -0
- package/dist/core/api/get-all-releases.d.ts +20 -0
- package/dist/core/api/get-all-releases.js +35 -0
- package/dist/core/api/get-all-tags.d.ts +17 -0
- package/dist/core/api/get-all-tags.js +13 -0
- package/dist/core/api/get-latest-release.d.ts +15 -0
- package/dist/core/api/get-latest-release.js +28 -0
- package/dist/core/api/get-reference-type.d.ts +16 -0
- package/dist/core/api/get-reference-type.js +21 -0
- package/dist/core/api/get-tag-info.d.ts +19 -0
- package/dist/core/api/get-tag-info.js +96 -0
- package/dist/core/api/get-tag-sha.d.ts +19 -0
- package/dist/core/api/get-tag-sha.js +30 -0
- package/dist/core/api/internal-rate-limit-error.d.ts +3 -0
- package/dist/core/api/internal-rate-limit-error.js +8 -0
- package/dist/core/api/make-request.d.ts +13 -0
- package/dist/core/api/make-request.js +31 -0
- package/dist/core/api/resolve-github-token-sync.d.ts +6 -0
- package/dist/core/api/resolve-github-token-sync.js +48 -0
- package/dist/core/api/update-rate-limit-info.d.ts +8 -0
- package/dist/core/api/update-rate-limit-info.js +10 -0
- package/dist/core/interactive/format-version.d.ts +3 -2
- package/dist/core/interactive/format-version.js +33 -2
- package/dist/core/interactive/prompt-update-selection.js +42 -8
- package/dist/core/scan-github-actions.js +1 -1
- package/dist/package.js +1 -1
- package/dist/types/github-client-context.d.ts +32 -0
- package/dist/types/github-client.d.ts +42 -0
- package/dist/types/release-info.d.ts +23 -0
- package/dist/types/tag-info.d.ts +14 -0
- package/package.json +1 -1
- package/dist/core/api/client.d.ts +0 -98
- package/dist/core/api/client.js +0 -292
package/dist/cli/index.js
CHANGED
|
@@ -32,10 +32,10 @@ function run() {
|
|
|
32
32
|
console.info(pc.green("\n⨠Everything is already at the latest version!\n"));
|
|
33
33
|
return;
|
|
34
34
|
}
|
|
35
|
-
spinner.success(`Found ${pc.yellow(outdated.length)} updates available${breaking.length > 0 ? ` (${pc.
|
|
35
|
+
spinner.success(`Found ${pc.yellow(outdated.length)} updates available${breaking.length > 0 ? ` (${pc.redBright(breaking.length)} breaking)` : ""}`);
|
|
36
36
|
if (options.dryRun) {
|
|
37
37
|
console.info(pc.yellow("\nš Dry Run - No changes will be made\n"));
|
|
38
|
-
for (let update of outdated) console.info(`${pc.cyan(update.action.file ?? "unknown")}:\n${update.action.name}: ${pc.
|
|
38
|
+
for (let update of outdated) console.info(`${pc.cyan(update.action.file ?? "unknown")}:\n${update.action.name}: ${pc.redBright(update.currentVersion)} ā ${pc.green(update.latestVersion)} ${update.latestSha ? pc.gray(`(${update.latestSha.slice(0, 7)})`) : ""}\n`);
|
|
39
39
|
console.info(pc.gray(`\n${outdated.length} actions would be updated\n`));
|
|
40
40
|
return;
|
|
41
41
|
}
|
|
@@ -64,7 +64,7 @@ function run() {
|
|
|
64
64
|
console.error(pc.yellow("\nā ļø Rate Limit Exceeded\n"));
|
|
65
65
|
console.error(error.message);
|
|
66
66
|
console.error(pc.gray("\nExample: GITHUB_TOKEN=ghp_xxxx actions-up\n"));
|
|
67
|
-
} else console.error(pc.
|
|
67
|
+
} else console.error(pc.redBright("\nError:"), error instanceof Error ? error.message : String(error));
|
|
68
68
|
process.exit(1);
|
|
69
69
|
}
|
|
70
70
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createGitHubClient } from "./create-github-client.js";
|
|
2
2
|
import semver from "semver";
|
|
3
3
|
async function checkUpdates(actions, token) {
|
|
4
|
-
let client =
|
|
4
|
+
let client = createGitHubClient(token);
|
|
5
5
|
let externalActions = actions.filter((action) => action.type === "external");
|
|
6
6
|
if (externalActions.length === 0) return [];
|
|
7
7
|
let uniqueActions = /* @__PURE__ */ new Map();
|
|
@@ -35,7 +35,7 @@ async function checkUpdates(actions, token) {
|
|
|
35
35
|
try {
|
|
36
36
|
let currentVersions = uniqueActions.get(actionName);
|
|
37
37
|
let firstVersion = currentVersions[0]?.version;
|
|
38
|
-
if (firstVersion) {
|
|
38
|
+
if (firstVersion && !isSha(firstVersion) && !isSemverLike(firstVersion)) {
|
|
39
39
|
let referenceType = await client.getRefType(owner, repo, firstVersion);
|
|
40
40
|
if (referenceType === "branch") return [...results, {
|
|
41
41
|
version: null,
|
|
@@ -45,7 +45,7 @@ async function checkUpdates(actions, token) {
|
|
|
45
45
|
}
|
|
46
46
|
let release = await client.getLatestRelease(owner, repo);
|
|
47
47
|
if (!release) {
|
|
48
|
-
let allReleases = await client.getAllReleases(owner, repo,
|
|
48
|
+
let allReleases = await client.getAllReleases(owner, repo, 1);
|
|
49
49
|
let stableRelease = allReleases.find((currentRelease) => !currentRelease.isPrerelease);
|
|
50
50
|
release = stableRelease ?? allReleases[0] ?? null;
|
|
51
51
|
}
|
|
@@ -63,9 +63,8 @@ async function checkUpdates(actions, token) {
|
|
|
63
63
|
}
|
|
64
64
|
if (release) {
|
|
65
65
|
let { version, sha } = release;
|
|
66
|
-
if (!sha) try {
|
|
67
|
-
|
|
68
|
-
sha = tagInfo?.sha ?? null;
|
|
66
|
+
if (!sha && version) try {
|
|
67
|
+
sha = await client.getTagSha(owner, repo, version);
|
|
69
68
|
} catch {}
|
|
70
69
|
return [...results, {
|
|
71
70
|
actionName,
|
|
@@ -168,4 +167,9 @@ function isSha(value) {
|
|
|
168
167
|
let normalized = value.replace(/^v/u, "");
|
|
169
168
|
return /^[0-9a-f]{7,40}$/iu.test(normalized);
|
|
170
169
|
}
|
|
170
|
+
function isSemverLike(value) {
|
|
171
|
+
if (!value) return false;
|
|
172
|
+
let normalized = value.trim();
|
|
173
|
+
return /^v?\d+(?:\.\d+){0,2}$/u.test(normalized);
|
|
174
|
+
}
|
|
171
175
|
export { checkUpdates };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { GitHubClient } from '../../types/github-client';
|
|
2
|
+
/**
|
|
3
|
+
* Create a functional GitHub API client with internal caches and rate-limit.
|
|
4
|
+
*
|
|
5
|
+
* @param token - Optional GitHub token override.
|
|
6
|
+
* @returns Client with bound methods.
|
|
7
|
+
*/
|
|
8
|
+
export declare function createGitHubClient(token?: string): GitHubClient;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { resolveGitHubTokenSync } from "./resolve-github-token-sync.js";
|
|
2
|
+
import { getReferenceType } from "./get-reference-type.js";
|
|
3
|
+
import { getLatestRelease } from "./get-latest-release.js";
|
|
4
|
+
import { getAllReleases } from "./get-all-releases.js";
|
|
5
|
+
import { getTagInfo } from "./get-tag-info.js";
|
|
6
|
+
import { getAllTags } from "./get-all-tags.js";
|
|
7
|
+
import { getTagSha } from "./get-tag-sha.js";
|
|
8
|
+
function createGitHubClient(token) {
|
|
9
|
+
let resolved = token ?? process.env["GITHUB_TOKEN"] ?? resolveGitHubTokenSync();
|
|
10
|
+
let context = {
|
|
11
|
+
caches: {
|
|
12
|
+
refType: /* @__PURE__ */ new Map(),
|
|
13
|
+
tagInfo: /* @__PURE__ */ new Map(),
|
|
14
|
+
tagSha: /* @__PURE__ */ new Map()
|
|
15
|
+
},
|
|
16
|
+
rateLimitRemaining: resolved ? 5e3 : 60,
|
|
17
|
+
baseUrl: "https://api.github.com",
|
|
18
|
+
rateLimitReset: /* @__PURE__ */ new Date(),
|
|
19
|
+
token: resolved
|
|
20
|
+
};
|
|
21
|
+
return {
|
|
22
|
+
getRateLimitStatus: () => ({
|
|
23
|
+
remaining: context.rateLimitRemaining,
|
|
24
|
+
resetAt: context.rateLimitReset
|
|
25
|
+
}),
|
|
26
|
+
getRefType: (owner, repo, reference) => getReferenceType(context, {
|
|
27
|
+
reference,
|
|
28
|
+
owner,
|
|
29
|
+
repo
|
|
30
|
+
}),
|
|
31
|
+
shouldWaitForRateLimit: (threshold = 100) => context.rateLimitRemaining < threshold,
|
|
32
|
+
getAllReleases: (owner, repo, limit) => getAllReleases(context, {
|
|
33
|
+
owner,
|
|
34
|
+
limit,
|
|
35
|
+
repo
|
|
36
|
+
}),
|
|
37
|
+
getAllTags: (owner, repo, limit) => getAllTags(context, {
|
|
38
|
+
owner,
|
|
39
|
+
limit,
|
|
40
|
+
repo
|
|
41
|
+
}),
|
|
42
|
+
getTagInfo: (owner, repo, tag) => getTagInfo(context, {
|
|
43
|
+
owner,
|
|
44
|
+
repo,
|
|
45
|
+
tag
|
|
46
|
+
}),
|
|
47
|
+
getLatestRelease: (owner, repo) => getLatestRelease(context, owner, repo),
|
|
48
|
+
getTagSha: (owner, repo, tag) => getTagSha(context, {
|
|
49
|
+
owner,
|
|
50
|
+
repo,
|
|
51
|
+
tag
|
|
52
|
+
})
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
export { createGitHubClient };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { GitHubClientContext } from '../../types/github-client-context';
|
|
2
|
+
import { ReleaseInfo } from '../../types/release-info';
|
|
3
|
+
/**
|
|
4
|
+
* Fetch releases for a repository.
|
|
5
|
+
*
|
|
6
|
+
* Resolves SHA only for the first returned release via target_commitish when it
|
|
7
|
+
* looks like a SHA; further enrichment happens at higher levels when needed.
|
|
8
|
+
*
|
|
9
|
+
* @param context - Client context.
|
|
10
|
+
* @param parameters - Request parameters.
|
|
11
|
+
* @param parameters.owner - Repository owner.
|
|
12
|
+
* @param parameters.repo - Repository name.
|
|
13
|
+
* @param parameters.limit - Maximum number of releases to fetch (default 10).
|
|
14
|
+
* @returns Array of normalized release information.
|
|
15
|
+
*/
|
|
16
|
+
export declare function getAllReleases(context: GitHubClientContext, parameters: {
|
|
17
|
+
limit?: number;
|
|
18
|
+
owner: string;
|
|
19
|
+
repo: string;
|
|
20
|
+
}): Promise<ReleaseInfo[]>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { makeRequest } from "./make-request.js";
|
|
2
|
+
import { GitHubRateLimitError } from "./internal-rate-limit-error.js";
|
|
3
|
+
async function getAllReleases(context, parameters) {
|
|
4
|
+
try {
|
|
5
|
+
let { limit = 10, owner, repo } = parameters;
|
|
6
|
+
let releasesResp = await makeRequest(context, `/repos/${owner}/${repo}/releases?per_page=${limit}`);
|
|
7
|
+
let releases = releasesResp.data;
|
|
8
|
+
let releaseInfos = [];
|
|
9
|
+
let i = 0;
|
|
10
|
+
for (let release of releases) {
|
|
11
|
+
let sha = null;
|
|
12
|
+
if (i === 0 && release.tag_name) sha = isLikelySha(release.target_commitish) ? release.target_commitish : null;
|
|
13
|
+
releaseInfos.push({
|
|
14
|
+
publishedAt: new Date(release.published_at),
|
|
15
|
+
name: release.name ?? release.tag_name,
|
|
16
|
+
description: release.body ?? null,
|
|
17
|
+
isPrerelease: release.prerelease,
|
|
18
|
+
version: release.tag_name,
|
|
19
|
+
url: release.html_url,
|
|
20
|
+
sha
|
|
21
|
+
});
|
|
22
|
+
i++;
|
|
23
|
+
}
|
|
24
|
+
return releaseInfos;
|
|
25
|
+
} catch (error) {
|
|
26
|
+
if (error instanceof Error && error.message.includes("rate limit")) throw new GitHubRateLimitError(context.rateLimitReset);
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function isLikelySha(value) {
|
|
31
|
+
if (typeof value !== "string" || value.trim() === "") return false;
|
|
32
|
+
let normalized = value.replace(/^v/u, "");
|
|
33
|
+
return /^[0-9a-f]{7,40}$/iu.test(normalized);
|
|
34
|
+
}
|
|
35
|
+
export { getAllReleases };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { GitHubClientContext } from '../../types/github-client-context';
|
|
2
|
+
import { TagInfo } from '../../types/tag-info';
|
|
3
|
+
/**
|
|
4
|
+
* Fetch tags list.
|
|
5
|
+
*
|
|
6
|
+
* @param context - Client context.
|
|
7
|
+
* @param parameters - Request parameters.
|
|
8
|
+
* @param parameters.owner - Repository owner.
|
|
9
|
+
* @param parameters.repo - Repository name.
|
|
10
|
+
* @param parameters.limit - Max items.
|
|
11
|
+
* @returns TagInfo array.
|
|
12
|
+
*/
|
|
13
|
+
export declare function getAllTags(context: GitHubClientContext, parameters: {
|
|
14
|
+
limit?: number;
|
|
15
|
+
owner: string;
|
|
16
|
+
repo: string;
|
|
17
|
+
}): Promise<TagInfo[]>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { makeRequest } from "./make-request.js";
|
|
2
|
+
async function getAllTags(context, parameters) {
|
|
3
|
+
let { limit = 30, owner, repo } = parameters;
|
|
4
|
+
let resp = await makeRequest(context, `/repos/${owner}/${repo}/tags?per_page=${limit}`);
|
|
5
|
+
let tags = resp.data;
|
|
6
|
+
return tags.map((tag) => ({
|
|
7
|
+
sha: tag.commit.sha,
|
|
8
|
+
tag: tag.name,
|
|
9
|
+
message: null,
|
|
10
|
+
date: null
|
|
11
|
+
}));
|
|
12
|
+
}
|
|
13
|
+
export { getAllTags };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { GitHubClientContext } from '../../types/github-client-context';
|
|
2
|
+
import { ReleaseInfo } from '../../types/release-info';
|
|
3
|
+
/**
|
|
4
|
+
* Fetch the latest release for a repository.
|
|
5
|
+
*
|
|
6
|
+
* If the latest release does not exist (404), returns null. The commit SHA is
|
|
7
|
+
* taken from target_commitish only when it looks like a SHA; otherwise SHA is
|
|
8
|
+
* left null and may be resolved later via tag lookups.
|
|
9
|
+
*
|
|
10
|
+
* @param context - Client context.
|
|
11
|
+
* @param owner - Repository owner.
|
|
12
|
+
* @param repo - Repository name.
|
|
13
|
+
* @returns Last release info or null when no latest release exists.
|
|
14
|
+
*/
|
|
15
|
+
export declare function getLatestRelease(context: GitHubClientContext, owner: string, repo: string): Promise<ReleaseInfo | null>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { makeRequest } from "./make-request.js";
|
|
2
|
+
import { GitHubRateLimitError } from "./internal-rate-limit-error.js";
|
|
3
|
+
async function getLatestRelease(context, owner, repo) {
|
|
4
|
+
try {
|
|
5
|
+
let releaseResp = await makeRequest(context, `/repos/${owner}/${repo}/releases/latest`);
|
|
6
|
+
let release = releaseResp.data;
|
|
7
|
+
let sha = isLikelySha(release.target_commitish) ? release.target_commitish : null;
|
|
8
|
+
return {
|
|
9
|
+
publishedAt: new Date(release.published_at),
|
|
10
|
+
name: release.name ?? release.tag_name,
|
|
11
|
+
description: release.body ?? null,
|
|
12
|
+
isPrerelease: release.prerelease,
|
|
13
|
+
version: release.tag_name,
|
|
14
|
+
url: release.html_url,
|
|
15
|
+
sha
|
|
16
|
+
};
|
|
17
|
+
} catch (error) {
|
|
18
|
+
if (error && typeof error === "object" && "status" in error && error.status === 404) return null;
|
|
19
|
+
if (error instanceof Error && error.message.includes("rate limit")) throw new GitHubRateLimitError(context.rateLimitReset);
|
|
20
|
+
throw error;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function isLikelySha(value) {
|
|
24
|
+
if (typeof value !== "string" || value.trim() === "") return false;
|
|
25
|
+
let normalized = value.replace(/^v/u, "");
|
|
26
|
+
return /^[0-9a-f]{7,40}$/iu.test(normalized);
|
|
27
|
+
}
|
|
28
|
+
export { getLatestRelease };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { GitHubClientContext } from '../../types/github-client-context';
|
|
2
|
+
/**
|
|
3
|
+
* Detect whether a reference is a tag or a branch.
|
|
4
|
+
*
|
|
5
|
+
* @param context - Client context.
|
|
6
|
+
* @param parameters - Request parameters.
|
|
7
|
+
* @param parameters.owner - Repository owner.
|
|
8
|
+
* @param parameters.repo - Repository name.
|
|
9
|
+
* @param parameters.reference - Reference name.
|
|
10
|
+
* @returns 'tag' | 'branch' | null.
|
|
11
|
+
*/
|
|
12
|
+
export declare function getReferenceType(context: GitHubClientContext, parameters: {
|
|
13
|
+
reference: string;
|
|
14
|
+
owner: string;
|
|
15
|
+
repo: string;
|
|
16
|
+
}): Promise<'branch' | 'tag' | null>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { makeRequest } from "./make-request.js";
|
|
2
|
+
async function getReferenceType(context, parameters) {
|
|
3
|
+
let { reference, owner, repo } = parameters;
|
|
4
|
+
let cacheKey = `${owner}/${repo}#${reference}`;
|
|
5
|
+
if (context.caches.refType.has(cacheKey)) return context.caches.refType.get(cacheKey) ?? null;
|
|
6
|
+
try {
|
|
7
|
+
await makeRequest(context, `/repos/${owner}/${repo}/git/refs/tags/${reference}`);
|
|
8
|
+
context.caches.refType.set(cacheKey, "tag");
|
|
9
|
+
return "tag";
|
|
10
|
+
} catch {
|
|
11
|
+
try {
|
|
12
|
+
await makeRequest(context, `/repos/${owner}/${repo}/git/refs/heads/${reference}`);
|
|
13
|
+
context.caches.refType.set(cacheKey, "branch");
|
|
14
|
+
return "branch";
|
|
15
|
+
} catch {
|
|
16
|
+
context.caches.refType.set(cacheKey, null);
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export { getReferenceType };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { GitHubClientContext } from '../../types/github-client-context';
|
|
2
|
+
import { TagInfo } from '../../types/tag-info';
|
|
3
|
+
/**
|
|
4
|
+
* Fetch tag information by tag name. Tries release-by-tag first to obtain
|
|
5
|
+
* metadata (date/message), then resolves the commit SHA via refs. Falls back to
|
|
6
|
+
* refs-only lookup when release-by-tag is not found.
|
|
7
|
+
*
|
|
8
|
+
* @param context - Client context.
|
|
9
|
+
* @param parameters - Request parameters.
|
|
10
|
+
* @param parameters.owner - Repository owner.
|
|
11
|
+
* @param parameters.repo - Repository name.
|
|
12
|
+
* @param parameters.tag - Tag name (may include 'refs/tags/' prefix).
|
|
13
|
+
* @returns TagInfo object or null when tag cannot be found.
|
|
14
|
+
*/
|
|
15
|
+
export declare function getTagInfo(context: GitHubClientContext, parameters: {
|
|
16
|
+
owner: string;
|
|
17
|
+
repo: string;
|
|
18
|
+
tag: string;
|
|
19
|
+
}): Promise<TagInfo | null>;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { makeRequest } from "./make-request.js";
|
|
2
|
+
import { GitHubRateLimitError } from "./internal-rate-limit-error.js";
|
|
3
|
+
async function getTagInfo(context, parameters) {
|
|
4
|
+
try {
|
|
5
|
+
let { owner, repo, tag } = parameters;
|
|
6
|
+
let displayTag = tag.replace(/^refs\/tags\//u, "");
|
|
7
|
+
let cacheKey = `${owner}/${repo}#${displayTag}`;
|
|
8
|
+
if (context.caches.tagInfo.has(cacheKey)) return context.caches.tagInfo.get(cacheKey) ?? null;
|
|
9
|
+
try {
|
|
10
|
+
let releaseResp = await makeRequest(context, `/repos/${owner}/${repo}/releases/tags/${displayTag}`);
|
|
11
|
+
let releaseData = releaseResp.data;
|
|
12
|
+
let date = releaseData.published_at ? new Date(releaseData.published_at) : null;
|
|
13
|
+
let message = releaseData.body ?? null;
|
|
14
|
+
let sha = null;
|
|
15
|
+
try {
|
|
16
|
+
let referenceResp = await makeRequest(context, `/repos/${owner}/${repo}/git/refs/tags/${displayTag}`);
|
|
17
|
+
let referenceData = referenceResp.data;
|
|
18
|
+
let { type: objectType, sha: objectSha } = referenceData.object;
|
|
19
|
+
if (objectSha && objectType === "tag") try {
|
|
20
|
+
let tagResp = await makeRequest(context, `/repos/${owner}/${repo}/git/tags/${objectSha}`);
|
|
21
|
+
let tagData = tagResp.data;
|
|
22
|
+
sha = tagData.object.sha ?? objectSha;
|
|
23
|
+
let taggerDate = tagData.tagger?.date;
|
|
24
|
+
if (!date && taggerDate) date = new Date(taggerDate);
|
|
25
|
+
if (!message && typeof tagData.message === "string") ({message} = tagData);
|
|
26
|
+
} catch {
|
|
27
|
+
sha = objectSha;
|
|
28
|
+
}
|
|
29
|
+
else if (objectSha && objectType === "commit") {
|
|
30
|
+
sha = objectSha;
|
|
31
|
+
if (!date || !message) try {
|
|
32
|
+
let commitResp = await makeRequest(context, `/repos/${owner}/${repo}/git/commits/${objectSha}`);
|
|
33
|
+
let { message: commitMessage, author } = commitResp.data;
|
|
34
|
+
if (!message && typeof commitMessage === "string") message = commitMessage;
|
|
35
|
+
let authorDate = author?.date;
|
|
36
|
+
if (!date && authorDate) date = new Date(authorDate);
|
|
37
|
+
} catch (error) {}
|
|
38
|
+
}
|
|
39
|
+
} catch {
|
|
40
|
+
if (isLikelySha(releaseData.target_commitish)) sha = releaseData.target_commitish;
|
|
41
|
+
}
|
|
42
|
+
let result = {
|
|
43
|
+
tag: displayTag,
|
|
44
|
+
message,
|
|
45
|
+
date,
|
|
46
|
+
sha
|
|
47
|
+
};
|
|
48
|
+
context.caches.tagInfo.set(cacheKey, result);
|
|
49
|
+
return result;
|
|
50
|
+
} catch {
|
|
51
|
+
try {
|
|
52
|
+
let referenceResp = await makeRequest(context, `/repos/${owner}/${repo}/git/refs/tags/${displayTag}`);
|
|
53
|
+
let referenceData = referenceResp.data;
|
|
54
|
+
let { sha } = referenceData.object;
|
|
55
|
+
let message = null;
|
|
56
|
+
let date = null;
|
|
57
|
+
if (referenceData.object.type === "tag") try {
|
|
58
|
+
let tagResp = await makeRequest(context, `/repos/${owner}/${repo}/git/tags/${sha}`);
|
|
59
|
+
let tagData = tagResp.data;
|
|
60
|
+
sha = tagData.object.sha ?? sha;
|
|
61
|
+
message = tagData.message ?? null;
|
|
62
|
+
date = tagData.tagger.date ? new Date(tagData.tagger.date) : null;
|
|
63
|
+
} catch (error) {}
|
|
64
|
+
else try {
|
|
65
|
+
let commitResp = await makeRequest(context, `/repos/${owner}/${repo}/git/commits/${sha}`);
|
|
66
|
+
let commitData = commitResp.data;
|
|
67
|
+
message = commitData.message ?? null;
|
|
68
|
+
date = commitData.author.date ? new Date(commitData.author.date) : null;
|
|
69
|
+
} catch (error) {}
|
|
70
|
+
let result = {
|
|
71
|
+
tag: displayTag,
|
|
72
|
+
message,
|
|
73
|
+
date,
|
|
74
|
+
sha
|
|
75
|
+
};
|
|
76
|
+
context.caches.tagInfo.set(cacheKey, result);
|
|
77
|
+
return result;
|
|
78
|
+
} catch (tagError) {
|
|
79
|
+
if (tagError && typeof tagError === "object" && "status" in tagError) {
|
|
80
|
+
context.caches.tagInfo.set(cacheKey, null);
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
throw tagError;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
} catch (error) {
|
|
87
|
+
if (error instanceof Error && error.message.includes("rate limit")) throw new GitHubRateLimitError(context.rateLimitReset);
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function isLikelySha(value) {
|
|
92
|
+
if (typeof value !== "string" || value.trim() === "") return false;
|
|
93
|
+
let normalized = value.replace(/^v/u, "");
|
|
94
|
+
return /^[0-9a-f]{7,40}$/iu.test(normalized);
|
|
95
|
+
}
|
|
96
|
+
export { getTagInfo };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { GitHubClientContext } from '../../types/github-client-context';
|
|
2
|
+
/**
|
|
3
|
+
* Resolve commit SHA for a tag without fetching commit metadata.
|
|
4
|
+
*
|
|
5
|
+
* Prefers annotated tag target when present; otherwise falls back to the
|
|
6
|
+
* lightweight tag (commit) SHA.
|
|
7
|
+
*
|
|
8
|
+
* @param context - Client context.
|
|
9
|
+
* @param parameters - Request parameters.
|
|
10
|
+
* @param parameters.owner - Repository owner.
|
|
11
|
+
* @param parameters.repo - Repository name.
|
|
12
|
+
* @param parameters.tag - Tag name (may include 'refs/tags/' prefix).
|
|
13
|
+
* @returns Commit SHA or null if it cannot be determined.
|
|
14
|
+
*/
|
|
15
|
+
export declare function getTagSha(context: GitHubClientContext, parameters: {
|
|
16
|
+
owner: string;
|
|
17
|
+
repo: string;
|
|
18
|
+
tag: string;
|
|
19
|
+
}): Promise<string | null>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { makeRequest } from "./make-request.js";
|
|
2
|
+
import { GitHubRateLimitError } from "./internal-rate-limit-error.js";
|
|
3
|
+
async function getTagSha(context, parameters) {
|
|
4
|
+
let { owner, repo, tag } = parameters;
|
|
5
|
+
let displayTag = tag.replace(/^refs\/tags\//u, "");
|
|
6
|
+
let cacheKey = `${owner}/${repo}#${displayTag}`;
|
|
7
|
+
if (context.caches.tagSha.has(cacheKey)) return context.caches.tagSha.get(cacheKey) ?? null;
|
|
8
|
+
try {
|
|
9
|
+
let referenceResp = await makeRequest(context, `/repos/${owner}/${repo}/git/refs/tags/${displayTag}`);
|
|
10
|
+
let referenceData = referenceResp.data;
|
|
11
|
+
let objectSha = referenceData.object.sha;
|
|
12
|
+
let objectType = referenceData.object.type;
|
|
13
|
+
let sha = null;
|
|
14
|
+
if (objectSha && objectType === "tag") try {
|
|
15
|
+
let tagResp = await makeRequest(context, `/repos/${owner}/${repo}/git/tags/${objectSha}`);
|
|
16
|
+
let tagData = tagResp.data;
|
|
17
|
+
sha = tagData.object.sha ?? null;
|
|
18
|
+
} catch {
|
|
19
|
+
sha = objectSha;
|
|
20
|
+
}
|
|
21
|
+
else if (objectSha && objectType === "commit") sha = objectSha;
|
|
22
|
+
context.caches.tagSha.set(cacheKey, sha);
|
|
23
|
+
return sha;
|
|
24
|
+
} catch (error) {
|
|
25
|
+
if (error instanceof Error && error.message.includes("rate limit")) throw new GitHubRateLimitError(context.rateLimitReset);
|
|
26
|
+
context.caches.tagSha.set(cacheKey, null);
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export { getTagSha };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
var GitHubRateLimitError = class extends Error {
|
|
2
|
+
constructor(resetAt) {
|
|
3
|
+
let resetTime = resetAt.toLocaleTimeString();
|
|
4
|
+
super(`GitHub API rate limit exceeded. Resets at ${resetTime}`);
|
|
5
|
+
this.name = "GitHubRateLimitError";
|
|
6
|
+
}
|
|
7
|
+
};
|
|
8
|
+
export { GitHubRateLimitError };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { GitHubClientContext } from '../../types/github-client-context';
|
|
2
|
+
/**
|
|
3
|
+
* Perform an HTTP request against GitHub API with auth and rate-limit updates.
|
|
4
|
+
*
|
|
5
|
+
* @param context - Client context with token and rate-limit state.
|
|
6
|
+
* @param path - API path beginning with '/'.
|
|
7
|
+
* @param options - Request init options.
|
|
8
|
+
* @returns Response headers and parsed data.
|
|
9
|
+
*/
|
|
10
|
+
export declare function makeRequest(context: GitHubClientContext, path: string, options?: RequestInit): Promise<{
|
|
11
|
+
headers: Record<string, string>;
|
|
12
|
+
data: unknown;
|
|
13
|
+
}>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { updateRateLimitInfo } from "./update-rate-limit-info.js";
|
|
2
|
+
async function makeRequest(context, path, options = {}) {
|
|
3
|
+
let headers = {
|
|
4
|
+
Accept: "application/vnd.github.v3+json",
|
|
5
|
+
"User-Agent": "actions-up",
|
|
6
|
+
...options.headers
|
|
7
|
+
};
|
|
8
|
+
if (context.token) headers["Authorization"] = `Bearer ${context.token}`;
|
|
9
|
+
let response = await fetch(`${context.baseUrl}${path}`, {
|
|
10
|
+
...options,
|
|
11
|
+
headers
|
|
12
|
+
});
|
|
13
|
+
let responseHeaders = {};
|
|
14
|
+
for (let [key, value] of response.headers.entries()) responseHeaders[key] = value;
|
|
15
|
+
updateRateLimitInfo(context, responseHeaders);
|
|
16
|
+
if (!response.ok) {
|
|
17
|
+
let error = /* @__PURE__ */ new Error(`GitHub API error: ${response.status} ${response.statusText}`);
|
|
18
|
+
error.status = response.status;
|
|
19
|
+
if (response.status === 403) {
|
|
20
|
+
let text = await response.text();
|
|
21
|
+
if (text.includes("rate limit") || text.includes("API rate limit")) error.message = "API rate limit exceeded";
|
|
22
|
+
}
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
let data = await response.json();
|
|
26
|
+
return {
|
|
27
|
+
headers: responseHeaders,
|
|
28
|
+
data
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export { makeRequest };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { execFileSync } from "node:child_process";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
function resolveGitHubTokenSync() {
|
|
5
|
+
let fromGithubToken = process.env["GITHUB_TOKEN"];
|
|
6
|
+
if (fromGithubToken && fromGithubToken.trim() !== "") return fromGithubToken.trim();
|
|
7
|
+
let fromGhToken = process.env["GH_TOKEN"];
|
|
8
|
+
if (fromGhToken && fromGhToken.trim() !== "") return fromGhToken.trim();
|
|
9
|
+
try {
|
|
10
|
+
let output = execFileSync("gh", ["auth", "token"], {
|
|
11
|
+
stdio: [
|
|
12
|
+
"ignore",
|
|
13
|
+
"pipe",
|
|
14
|
+
"ignore"
|
|
15
|
+
],
|
|
16
|
+
encoding: "utf8",
|
|
17
|
+
timeout: 500
|
|
18
|
+
});
|
|
19
|
+
let token = output.trim();
|
|
20
|
+
if (token) return token;
|
|
21
|
+
} catch {}
|
|
22
|
+
try {
|
|
23
|
+
let gitConfigPath = join(process.cwd(), ".git", "config");
|
|
24
|
+
let content = readFileSync(gitConfigPath, "utf8");
|
|
25
|
+
let directMatch = content.match(/^\s*(?:github\.(?:oauth-token|token)|hub\.oauthtoken)\s*=\s*(?<token>\S[^\n\r]*)$/mu);
|
|
26
|
+
let directToken = directMatch?.groups?.["token"]?.trim();
|
|
27
|
+
if (directToken) return directToken;
|
|
28
|
+
let currentSection = null;
|
|
29
|
+
for (let rawLine of content.split(/\r?\n/u)) {
|
|
30
|
+
let line = rawLine.trim();
|
|
31
|
+
let sectionMatch = line.match(/^\[(?<name>[^\]]+)\]$/u);
|
|
32
|
+
if (sectionMatch?.groups) {
|
|
33
|
+
currentSection = sectionMatch.groups["name"].toLowerCase();
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (currentSection === "github") {
|
|
37
|
+
let tokenMatch = line.match(/^(?:oauth-token|token)\s*=\s*(?<val>\S[^\n\r]*)$/u);
|
|
38
|
+
if (tokenMatch?.groups?.["val"]) return tokenMatch.groups["val"].trim();
|
|
39
|
+
}
|
|
40
|
+
if (currentSection === "hub") {
|
|
41
|
+
let oauthMatch = line.match(/^oauthtoken\s*=\s*(?<val>\S[^\n\r]*)$/u);
|
|
42
|
+
if (oauthMatch?.groups?.["val"]) return oauthMatch.groups["val"].trim();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
} catch {}
|
|
46
|
+
return void 0;
|
|
47
|
+
}
|
|
48
|
+
export { resolveGitHubTokenSync };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { GitHubClientContext } from '../../types/github-client-context';
|
|
2
|
+
/**
|
|
3
|
+
* Update rate limit information from response headers.
|
|
4
|
+
*
|
|
5
|
+
* @param context - Client context with mutable rate limit fields.
|
|
6
|
+
* @param headers - Response headers map.
|
|
7
|
+
*/
|
|
8
|
+
export declare function updateRateLimitInfo(context: GitHubClientContext, headers: Record<string, undefined | string | number>): void;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
function updateRateLimitInfo(context, headers) {
|
|
2
|
+
let remaining = headers["x-ratelimit-remaining"];
|
|
3
|
+
if (remaining !== void 0) context.rateLimitRemaining = typeof remaining === "string" ? Number.parseInt(remaining, 10) : remaining;
|
|
4
|
+
let reset = headers["x-ratelimit-reset"];
|
|
5
|
+
if (reset !== void 0) {
|
|
6
|
+
let resetTime = typeof reset === "string" ? Number.parseInt(reset, 10) : reset;
|
|
7
|
+
context.rateLimitReset = /* @__PURE__ */ new Date(resetTime * 1e3);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export { updateRateLimitInfo };
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Formats a version string for display, handling null/undefined values.
|
|
3
3
|
*
|
|
4
|
-
* @param
|
|
4
|
+
* @param latestVersion - Latest version string or null/undefined.
|
|
5
|
+
* @param currentVersion - Current version string or null/undefined.
|
|
5
6
|
* @returns Formatted version string or 'unknown' placeholder.
|
|
6
7
|
*/
|
|
7
|
-
export declare function formatVersion(
|
|
8
|
+
export declare function formatVersion(latestVersion: undefined | string | null, currentVersion: undefined | string | null): string;
|