actions-up 1.2.1 → 1.3.1

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.
Files changed (36) hide show
  1. package/dist/cli/index.js +3 -3
  2. package/dist/core/api/check-updates.js +79 -19
  3. package/dist/core/api/create-github-client.d.ts +8 -0
  4. package/dist/core/api/create-github-client.js +55 -0
  5. package/dist/core/api/get-all-releases.d.ts +20 -0
  6. package/dist/core/api/get-all-releases.js +35 -0
  7. package/dist/core/api/get-all-tags.d.ts +17 -0
  8. package/dist/core/api/get-all-tags.js +13 -0
  9. package/dist/core/api/get-latest-release.d.ts +15 -0
  10. package/dist/core/api/get-latest-release.js +28 -0
  11. package/dist/core/api/get-reference-type.d.ts +16 -0
  12. package/dist/core/api/get-reference-type.js +21 -0
  13. package/dist/core/api/get-tag-info.d.ts +19 -0
  14. package/dist/core/api/get-tag-info.js +96 -0
  15. package/dist/core/api/get-tag-sha.d.ts +19 -0
  16. package/dist/core/api/get-tag-sha.js +30 -0
  17. package/dist/core/api/internal-rate-limit-error.d.ts +3 -0
  18. package/dist/core/api/internal-rate-limit-error.js +8 -0
  19. package/dist/core/api/make-request.d.ts +13 -0
  20. package/dist/core/api/make-request.js +31 -0
  21. package/dist/core/api/resolve-github-token-sync.d.ts +6 -0
  22. package/dist/core/api/resolve-github-token-sync.js +48 -0
  23. package/dist/core/api/update-rate-limit-info.d.ts +8 -0
  24. package/dist/core/api/update-rate-limit-info.js +10 -0
  25. package/dist/core/interactive/format-version.d.ts +3 -2
  26. package/dist/core/interactive/format-version.js +33 -2
  27. package/dist/core/interactive/prompt-update-selection.js +42 -8
  28. package/dist/core/scan-github-actions.js +1 -1
  29. package/dist/package.js +1 -1
  30. package/dist/types/github-client-context.d.ts +32 -0
  31. package/dist/types/github-client.d.ts +42 -0
  32. package/dist/types/release-info.d.ts +23 -0
  33. package/dist/types/tag-info.d.ts +14 -0
  34. package/package.json +1 -1
  35. package/dist/core/api/client.d.ts +0 -98
  36. 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.red(breaking.length)} breaking)` : ""}`);
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.red(update.currentVersion)} → ${pc.green(update.latestVersion)} ${update.latestSha ? pc.gray(`(${update.latestSha.slice(0, 7)})`) : ""}\n`);
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.red("\nError:"), error instanceof Error ? error.message : String(error));
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 { Client } from "./client.js";
1
+ import { createGitHubClient } from "./create-github-client.js";
2
2
  import semver from "semver";
3
3
  async function checkUpdates(actions, token) {
4
- let client = new Client(token);
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,27 +45,82 @@ 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, 10);
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
  }
52
- if (!release) {
53
- let tags = await client.getAllTags(owner, repo, 30);
54
- if (tags.length > 0) {
55
- let semverTag = tags.find((tag) => /^v?\d+(?:\.\d+){0,2}/u.test(tag.tag));
56
- let latestTag = semverTag ?? tags[0];
57
- if (latestTag) return [...results, {
58
- version: latestTag.tag,
59
- sha: latestTag.sha,
60
- actionName
61
- }];
62
- }
63
- }
64
52
  if (release) {
65
53
  let { version, sha } = release;
66
- if (!sha) try {
67
- let tagInfo = await client.getTagInfo(owner, repo, version);
68
- sha = tagInfo?.sha ?? null;
54
+ let considerTags = false;
55
+ {
56
+ let normalized = normalizeVersion(version);
57
+ let hasVersion = Boolean(version && version.trim() !== "");
58
+ let majorOnly = hasVersion && /^v?\d+$/u.test(version.trim());
59
+ let valid = semver.valid(normalized);
60
+ considerTags = !hasVersion || majorOnly || !valid;
61
+ }
62
+ if (considerTags) {
63
+ let tags$1 = await client.getAllTags(owner, repo, 30);
64
+ if (tags$1.length > 0) {
65
+ let semverCandidates = tags$1.filter((tag) => isSemverLike(tag.tag)).map((tag) => ({
66
+ v: semver.valid(normalizeVersion(tag.tag)),
67
+ raw: tag
68
+ }));
69
+ if (semverCandidates.length > 0) {
70
+ semverCandidates.sort((a, b) => {
71
+ let cmp = semver.rcompare(a.v, b.v);
72
+ if (cmp !== 0) return cmp;
73
+ let aSpecific = /\d+\.\d+/u.test(a.raw.tag) ? 1 : 0;
74
+ let bSpecific = /\d+\.\d+/u.test(b.raw.tag) ? 1 : 0;
75
+ return bSpecific - aSpecific;
76
+ });
77
+ let best = semverCandidates[0].raw;
78
+ let releaseSem = semver.valid(normalizeVersion(version) ?? void 0);
79
+ if (!releaseSem || semver.gt(semverCandidates[0].v, releaseSem) || semver.eq(semverCandidates[0].v, releaseSem) && /\d+\.\d+/u.test(best.tag)) {
80
+ let tagVersion = best.tag;
81
+ let tagSha = best.sha?.length ? best.sha : null;
82
+ if (!tagSha && tagVersion) try {
83
+ tagSha = await client.getTagSha(owner, repo, tagVersion);
84
+ } catch {}
85
+ return [...results, {
86
+ version: tagVersion,
87
+ sha: tagSha,
88
+ actionName
89
+ }];
90
+ }
91
+ }
92
+ }
93
+ }
94
+ if (!sha && version) try {
95
+ sha = await client.getTagSha(owner, repo, version);
96
+ } catch {}
97
+ return [...results, {
98
+ actionName,
99
+ version,
100
+ sha
101
+ }];
102
+ }
103
+ let tags = await client.getAllTags(owner, repo, 30);
104
+ if (tags.length > 0) {
105
+ let semverCandidates = tags.filter((tag) => isSemverLike(tag.tag)).map((tag) => ({
106
+ v: semver.valid(normalizeVersion(tag.tag)),
107
+ raw: tag
108
+ }));
109
+ let best;
110
+ if (semverCandidates.length > 0) {
111
+ semverCandidates.sort((a, b) => {
112
+ let cmp = semver.rcompare(a.v, b.v);
113
+ if (cmp !== 0) return cmp;
114
+ let aSpecific = /\d+\.\d+/u.test(a.raw.tag) ? 1 : 0;
115
+ let bSpecific = /\d+\.\d+/u.test(b.raw.tag) ? 1 : 0;
116
+ return bSpecific - aSpecific;
117
+ });
118
+ best = semverCandidates[0].raw;
119
+ } else best = tags[0];
120
+ let version = best.tag;
121
+ let sha = best.sha?.length ? best.sha : null;
122
+ if (!sha && version) try {
123
+ sha = await client.getTagSha(owner, repo, version);
69
124
  } catch {}
70
125
  return [...results, {
71
126
  actionName,
@@ -168,4 +223,9 @@ function isSha(value) {
168
223
  let normalized = value.replace(/^v/u, "");
169
224
  return /^[0-9a-f]{7,40}$/iu.test(normalized);
170
225
  }
226
+ function isSemverLike(value) {
227
+ if (!value) return false;
228
+ let normalized = value.trim();
229
+ return /^v?\d+(?:\.\d+){0,2}$/u.test(normalized);
230
+ }
171
231
  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,3 @@
1
+ export declare class GitHubRateLimitError extends Error {
2
+ constructor(resetAt: Date);
3
+ }
@@ -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,6 @@
1
+ /**
2
+ * Resolve GitHub token from environment, gh CLI, or git config.
3
+ *
4
+ * @returns Token string or undefined when not found.
5
+ */
6
+ export declare function resolveGitHubTokenSync(): undefined | string;