actions-up 1.2.0 → 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.
Files changed (37) hide show
  1. package/dist/cli/index.js +3 -3
  2. package/dist/core/api/check-updates.js +11 -7
  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/readme.md +4 -130
  36. package/dist/core/api/client.d.ts +0 -98
  37. package/dist/core/api/client.js +0 -261
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,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, 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
  }
@@ -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
- let tagInfo = await client.getTagInfo(owner, repo, version);
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,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;
@@ -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 version - Version string or null/undefined.
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(version: undefined | string | null): string;
8
+ export declare function formatVersion(latestVersion: undefined | string | null, currentVersion: undefined | string | null): string;