actions-up 0.0.1 → 1.0.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 (77) hide show
  1. package/bin/actions-up.js +5 -0
  2. package/dist/cli/index.d.ts +2 -0
  3. package/dist/cli/index.js +67 -0
  4. package/dist/core/api/check-updates.d.ts +10 -0
  5. package/dist/core/api/check-updates.js +139 -0
  6. package/dist/core/api/client.d.ts +79 -0
  7. package/dist/core/api/client.js +187 -0
  8. package/dist/core/ast/guards/has-range.d.ts +10 -0
  9. package/dist/core/ast/guards/has-range.js +4 -0
  10. package/dist/core/ast/guards/is-node.d.ts +8 -0
  11. package/dist/core/ast/guards/is-node.js +4 -0
  12. package/dist/core/ast/guards/is-pair.d.ts +8 -0
  13. package/dist/core/ast/guards/is-pair.js +4 -0
  14. package/dist/core/ast/guards/is-scalar.d.ts +8 -0
  15. package/dist/core/ast/guards/is-scalar.js +4 -0
  16. package/dist/core/ast/guards/is-yaml-map.d.ts +8 -0
  17. package/dist/core/ast/guards/is-yaml-map.js +4 -0
  18. package/dist/core/ast/guards/is-yaml-sequence.d.ts +8 -0
  19. package/dist/core/ast/guards/is-yaml-sequence.js +4 -0
  20. package/dist/core/ast/scanners/scan-composite-action-ast.d.ts +14 -0
  21. package/dist/core/ast/scanners/scan-composite-action-ast.js +18 -0
  22. package/dist/core/ast/scanners/scan-workflow-ast.d.ts +14 -0
  23. package/dist/core/ast/scanners/scan-workflow-ast.js +23 -0
  24. package/dist/core/ast/update/apply-updates.d.ts +7 -0
  25. package/dist/core/ast/update/apply-updates.js +40 -0
  26. package/dist/core/ast/utils/extract-uses-from-steps.d.ts +13 -0
  27. package/dist/core/ast/utils/extract-uses-from-steps.js +24 -0
  28. package/dist/core/ast/utils/find-map-pair.d.ts +12 -0
  29. package/dist/core/ast/utils/find-map-pair.js +10 -0
  30. package/dist/core/ast/utils/get-line-number.d.ts +10 -0
  31. package/dist/core/ast/utils/get-line-number.js +9 -0
  32. package/dist/core/constants.d.ts +4 -0
  33. package/dist/core/constants.js +4 -0
  34. package/dist/core/fs/is-yaml-file.d.ts +7 -0
  35. package/dist/core/fs/is-yaml-file.js +4 -0
  36. package/dist/core/fs/read-yaml-document.d.ts +11 -0
  37. package/dist/core/fs/read-yaml-document.js +11 -0
  38. package/dist/core/index.d.ts +3 -0
  39. package/dist/core/index.js +4 -0
  40. package/dist/core/interactive/format-version.d.ts +7 -0
  41. package/dist/core/interactive/format-version.js +5 -0
  42. package/dist/core/interactive/pad-string.d.ts +8 -0
  43. package/dist/core/interactive/pad-string.js +9 -0
  44. package/dist/core/interactive/prompt-update-selection.d.ts +2 -0
  45. package/dist/core/interactive/prompt-update-selection.js +203 -0
  46. package/dist/core/interactive/strip-ansi.d.ts +7 -0
  47. package/dist/core/interactive/strip-ansi.js +21 -0
  48. package/dist/core/parsing/parse-action-reference.d.ts +30 -0
  49. package/dist/core/parsing/parse-action-reference.js +34 -0
  50. package/dist/core/scan-action-file.d.ts +10 -0
  51. package/dist/core/scan-action-file.js +7 -0
  52. package/dist/core/scan-github-actions.d.ts +17 -0
  53. package/dist/core/scan-github-actions.js +116 -0
  54. package/dist/core/scan-workflow-file.d.ts +9 -0
  55. package/dist/core/scan-workflow-file.js +7 -0
  56. package/dist/core/schema/composite/is-composite-action-runs.d.ts +8 -0
  57. package/dist/core/schema/composite/is-composite-action-runs.js +6 -0
  58. package/dist/core/schema/composite/is-composite-action-step.d.ts +8 -0
  59. package/dist/core/schema/composite/is-composite-action-structure.d.ts +9 -0
  60. package/dist/core/schema/composite/is-composite-action-structure.js +6 -0
  61. package/dist/core/schema/workflow/is-workflow-job.d.ts +8 -0
  62. package/dist/core/schema/workflow/is-workflow-step.d.ts +8 -0
  63. package/dist/core/schema/workflow/is-workflow-structure.d.ts +8 -0
  64. package/dist/core/schema/workflow/is-workflow-structure.js +6 -0
  65. package/dist/package.js +2 -0
  66. package/dist/types/action-update.d.ts +21 -0
  67. package/dist/types/composite-action-runs.d.ts +12 -0
  68. package/dist/types/composite-action-step.d.ts +23 -0
  69. package/dist/types/composite-action-structure.d.ts +21 -0
  70. package/dist/types/github-action.d.ts +23 -0
  71. package/dist/types/scan-result.d.ts +12 -0
  72. package/dist/types/workflow-job.d.ts +18 -0
  73. package/dist/types/workflow-step.d.ts +20 -0
  74. package/dist/types/workflow-structure.d.ts +15 -0
  75. package/license.md +20 -0
  76. package/package.json +52 -1
  77. package/readme.md +175 -0
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { run } from '../dist/cli/index.js'
4
+
5
+ run()
@@ -0,0 +1,2 @@
1
+ /** Run the CLI. */
2
+ export declare function run(): void;
@@ -0,0 +1,67 @@
1
+ import { promptUpdateSelection } from "../core/interactive/prompt-update-selection.js";
2
+ import { applyUpdates } from "../core/ast/update/apply-updates.js";
3
+ import { checkUpdates } from "../core/api/check-updates.js";
4
+ import { scanGitHubActions } from "../core/scan-github-actions.js";
5
+ import "../core/index.js";
6
+ import { version } from "../package.js";
7
+ import { createSpinner } from "nanospinner";
8
+ import "node:worker_threads";
9
+ import pc from "picocolors";
10
+ import cac from "cac";
11
+ function run() {
12
+ let cli = cac("actions-up");
13
+ cli.help().version(version).option("--yes, -y", "Skip all confirmations").command("", "Update GitHub Actions").action(async (options) => {
14
+ console.info(pc.cyan("\n🚀 Actions Up!\n"));
15
+ let spinner = createSpinner("Scanning GitHub Actions...").start();
16
+ try {
17
+ let scanResult = await scanGitHubActions(process.cwd());
18
+ let totalActions = scanResult.actions.length;
19
+ let totalWorkflows = scanResult.workflows.size;
20
+ let totalCompositeActions = scanResult.compositeActions.size;
21
+ spinner.success(`Found ${pc.yellow(totalActions)} actions in ${pc.yellow(totalWorkflows)} workflows and ${pc.yellow(totalCompositeActions)} composite actions`);
22
+ if (totalActions === 0) {
23
+ console.info(pc.green("\n✨ No GitHub Actions found in this repository"));
24
+ return;
25
+ }
26
+ spinner = createSpinner("Checking for updates...").start();
27
+ let updates = await checkUpdates(scanResult.actions, process.env["GITHUB_TOKEN"]);
28
+ let outdated = updates.filter((update) => update.hasUpdate);
29
+ let breaking = outdated.filter((update) => update.isBreaking);
30
+ if (outdated.length === 0) {
31
+ spinner.success("All actions are up to date!");
32
+ console.info(pc.green("\n✨ Everything is already at the latest version!\n"));
33
+ return;
34
+ }
35
+ spinner.success(`Found ${pc.yellow(outdated.length)} updates available${breaking.length > 0 ? ` (${pc.red(breaking.length)} breaking)` : ""}`);
36
+ if (options.yes) {
37
+ let toUpdate = outdated.filter((update) => update.latestSha);
38
+ if (toUpdate.length === 0) {
39
+ console.info(pc.yellow("\n⚠️ No actions with SHA available for update\n"));
40
+ return;
41
+ }
42
+ console.info(pc.yellow(`\n🔄 Updating ${toUpdate.length} actions...\n`));
43
+ await applyUpdates(toUpdate);
44
+ console.info(pc.green("\n✓ Updates applied successfully!"));
45
+ } else {
46
+ let selected = await promptUpdateSelection(updates);
47
+ if (!selected || selected.length === 0) {
48
+ console.info(pc.gray("\nNo updates applied"));
49
+ return;
50
+ }
51
+ console.info(pc.yellow(`\n🔄 Updating ${selected.length} selected actions...\n`));
52
+ await applyUpdates(selected);
53
+ console.info(pc.green("\n✓ Updates applied successfully!"));
54
+ }
55
+ } catch (error) {
56
+ spinner.error("Failed");
57
+ if (error instanceof Error && error.name === "GitHubRateLimitError") {
58
+ console.error(pc.yellow("\n⚠️ Rate Limit Exceeded\n"));
59
+ console.error(error.message);
60
+ console.error(pc.gray("\nExample: GITHUB_TOKEN=ghp_xxxx actions-up\n"));
61
+ } else console.error(pc.red("\nError:"), error instanceof Error ? error.message : String(error));
62
+ process.exit(1);
63
+ }
64
+ });
65
+ cli.parse();
66
+ }
67
+ export { run };
@@ -0,0 +1,10 @@
1
+ import { GitHubAction } from '../../types/github-action';
2
+ import { ActionUpdate } from '../../types/action-update';
3
+ /**
4
+ * Check for updates for GitHub Actions.
5
+ *
6
+ * @param actions - Array of GitHub Actions to check.
7
+ * @param token - Optional GitHub token for authentication.
8
+ * @returns Array of update information.
9
+ */
10
+ export declare function checkUpdates(actions: GitHubAction[], token?: string): Promise<ActionUpdate[]>;
@@ -0,0 +1,139 @@
1
+ import { Client } from "./client.js";
2
+ import semver from "semver";
3
+ async function checkUpdates(actions, token) {
4
+ let client = new Client(token);
5
+ let externalActions = actions.filter((action) => action.type === "external");
6
+ if (externalActions.length === 0) return [];
7
+ let uniqueActions = /* @__PURE__ */ new Map();
8
+ for (let action of externalActions) {
9
+ let group = uniqueActions.get(action.name) ?? [];
10
+ group.push(action);
11
+ uniqueActions.set(action.name, group);
12
+ }
13
+ let sharedState = {
14
+ rateLimitError: null,
15
+ rateLimitHit: false
16
+ };
17
+ let releaseResults = await [...uniqueActions.keys()].reduce((promise, actionName) => promise.then(async (results) => {
18
+ if (sharedState.rateLimitHit) return [...results, {
19
+ version: null,
20
+ actionName,
21
+ sha: null
22
+ }];
23
+ let [owner, repo] = actionName.split("/");
24
+ if (!owner || !repo) return [...results, {
25
+ version: null,
26
+ actionName,
27
+ sha: null
28
+ }];
29
+ try {
30
+ let release = await client.getLatestRelease(owner, repo);
31
+ if (!release) {
32
+ let allReleases = await client.getAllReleases(owner, repo, 10);
33
+ let stableRelease = allReleases.find((currentRelease) => !currentRelease.isPrerelease);
34
+ release = stableRelease ?? allReleases[0] ?? null;
35
+ }
36
+ if (release) {
37
+ let { version, sha } = release;
38
+ if (!sha) try {
39
+ let tagInfo = await client.getTagInfo(owner, repo, version);
40
+ sha = tagInfo?.sha ?? null;
41
+ } catch {}
42
+ return [...results, {
43
+ actionName,
44
+ version,
45
+ sha
46
+ }];
47
+ }
48
+ return [...results, {
49
+ version: null,
50
+ actionName,
51
+ sha: null
52
+ }];
53
+ } catch (error) {
54
+ if (error instanceof Error && error.name === "GitHubRateLimitError") {
55
+ sharedState.rateLimitHit = true;
56
+ sharedState.rateLimitError = error;
57
+ return [...results, {
58
+ version: null,
59
+ actionName,
60
+ sha: null
61
+ }];
62
+ }
63
+ console.warn(`Failed to check ${actionName}:`, error);
64
+ return [...results, {
65
+ version: null,
66
+ actionName,
67
+ sha: null
68
+ }];
69
+ }
70
+ }), Promise.resolve([]));
71
+ if (sharedState.rateLimitError) {
72
+ let error = /* @__PURE__ */ new Error("GitHub API rate limit exceeded. Please set GITHUB_TOKEN environment variable to increase the limit.\nSee: https://github.com/azat-io/actions-up?tab=readme-ov-file#with-github-token");
73
+ error.name = "GitHubRateLimitError";
74
+ throw error;
75
+ }
76
+ let cache = /* @__PURE__ */ new Map();
77
+ for (let result of releaseResults) cache.set(result.actionName, {
78
+ version: result.version,
79
+ sha: result.sha
80
+ });
81
+ let updates = [];
82
+ for (let action of externalActions) {
83
+ let cached = cache.get(action.name);
84
+ if (cached) updates.push(createUpdate(action, cached.version, cached.sha));
85
+ else updates.push(createUpdate(action, null, null));
86
+ }
87
+ return updates;
88
+ }
89
+ function createUpdate(action, latestVersion, latestSha) {
90
+ let currentVersion = normalizeVersion(action.version ?? "");
91
+ let normalized = latestVersion ? normalizeVersion(latestVersion) : null;
92
+ let hasUpdate = false;
93
+ let isBreaking = false;
94
+ if (currentVersion && isSha(currentVersion)) {
95
+ if (latestSha) hasUpdate = !compareSha(currentVersion, latestSha);
96
+ else if (normalized) hasUpdate = true;
97
+ } else if (currentVersion && normalized) {
98
+ let current = semver.valid(currentVersion);
99
+ let latest = semver.valid(normalized);
100
+ if (current && latest) {
101
+ hasUpdate = semver.lt(current, latest);
102
+ if (hasUpdate) {
103
+ let currentMajor = semver.major(current);
104
+ let latestMajor = semver.major(latest);
105
+ isBreaking = latestMajor > currentMajor;
106
+ }
107
+ } else if (currentVersion !== normalized) hasUpdate = true;
108
+ }
109
+ return {
110
+ currentVersion: action.version ?? "unknown",
111
+ latestVersion,
112
+ isBreaking,
113
+ latestSha,
114
+ hasUpdate,
115
+ action
116
+ };
117
+ }
118
+ function compareSha(sha1, sha2) {
119
+ if (!sha1 || !sha2) return false;
120
+ let normalized1 = sha1.replace(/^v/u, "");
121
+ let normalized2 = sha2.replace(/^v/u, "");
122
+ let minLength = Math.min(normalized1.length, normalized2.length);
123
+ if (minLength < 7) return false;
124
+ return normalized1.slice(0, Math.max(0, minLength)).toLowerCase() === normalized2.slice(0, Math.max(0, minLength)).toLowerCase();
125
+ }
126
+ function normalizeVersion(version) {
127
+ if (!version) return null;
128
+ let normalized = version.replace(/^v/u, "");
129
+ if (/^[0-9a-f]{7,40}$/iu.test(normalized)) return version;
130
+ let coerced = semver.coerce(normalized);
131
+ if (coerced) return coerced.version;
132
+ return version;
133
+ }
134
+ function isSha(value) {
135
+ if (!value) return false;
136
+ let normalized = value.replace(/^v/u, "");
137
+ return /^[0-9a-f]{7,40}$/iu.test(normalized);
138
+ }
139
+ export { checkUpdates };
@@ -0,0 +1,79 @@
1
+ /** Processed release information with normalized types. */
2
+ interface ReleaseInfo {
3
+ /** Release description or null if not provided. */
4
+ description: string | null;
5
+ /** Whether this release is marked as a pre-release. */
6
+ isPrerelease: boolean;
7
+ /** Git commit SHA for this release. */
8
+ sha: string | null;
9
+ /** Date when the release was published. */
10
+ publishedAt: Date;
11
+ /** Version tag name (e.g., 'v1.2.3'). */
12
+ version: string;
13
+ /** Release name or tag name if name not provided. */
14
+ name: string;
15
+ /** GitHub URL for this release. */
16
+ url: string;
17
+ }
18
+ /** Processed tag information with normalized types. */
19
+ interface TagInfo {
20
+ /** Tag or commit message, null if not provided. */
21
+ message: string | null;
22
+ /** Date when the tag was created or committed. */
23
+ date: Date | null;
24
+ /** Tag name (e.g., 'v1.2.3'). */
25
+ tag: string;
26
+ /** Git commit SHA that this tag points to. */
27
+ sha: string;
28
+ }
29
+ /** GitHub GraphQL client with optional authentication. */
30
+ export declare class Client {
31
+ private readonly graphqlWithAuth;
32
+ private rateLimitRemaining;
33
+ private rateLimitReset;
34
+ /**
35
+ * Creates a new GitHub API client.
36
+ *
37
+ * @param token - Optional GitHub token for authentication.
38
+ */
39
+ constructor(token?: string);
40
+ private static isRateLimitError;
41
+ /**
42
+ * Get specific tag/version information.
43
+ *
44
+ * @param owner - The repository owner.
45
+ * @param repo - The repository name.
46
+ * @param tag - The tag name to fetch.
47
+ * @returns Tag information or null if not found.
48
+ */
49
+ getTagInfo(owner: string, repo: string, tag: string): Promise<TagInfo | null>;
50
+ /**
51
+ * Get all releases for a repository.
52
+ *
53
+ * @param owner - The repository owner.
54
+ * @param repo - The repository name.
55
+ * @param limit - Maximum number of releases to fetch.
56
+ * @returns Array of release information.
57
+ */
58
+ getAllReleases(owner: string, repo: string, limit?: number): Promise<ReleaseInfo[]>;
59
+ /**
60
+ * Get the latest release for a GitHub repository.
61
+ *
62
+ * @param owner - The repository owner.
63
+ * @param repo - The repository name.
64
+ * @returns Latest release information or null if not found.
65
+ */
66
+ getLatestRelease(owner: string, repo: string): Promise<ReleaseInfo | null>;
67
+ getRateLimitStatus(): {
68
+ remaining: number;
69
+ resetAt: Date;
70
+ };
71
+ /**
72
+ * Check if we should wait before making more requests.
73
+ *
74
+ * @param threshold - Minimum remaining requests before waiting.
75
+ * @returns True if rate limit is below threshold.
76
+ */
77
+ shouldWaitForRateLimit(threshold?: number): boolean;
78
+ }
79
+ export {};
@@ -0,0 +1,187 @@
1
+ import { GraphqlResponseError, graphql } from "@octokit/graphql";
2
+ var GitHubRateLimitError = class extends Error {
3
+ constructor(resetAt) {
4
+ let resetTime = resetAt.toLocaleTimeString();
5
+ super(`GitHub API rate limit exceeded. Resets at ${resetTime}`);
6
+ this.name = "GitHubRateLimitError";
7
+ }
8
+ };
9
+ var Client = class Client {
10
+ graphqlWithAuth;
11
+ rateLimitRemaining = 5e3;
12
+ rateLimitReset = /* @__PURE__ */ new Date();
13
+ constructor(token) {
14
+ let authToken = token ?? process.env["GITHUB_TOKEN"];
15
+ this.graphqlWithAuth = graphql.defaults({ headers: authToken ? { authorization: `token ${authToken}` } : {} });
16
+ if (!authToken) console.warn("No GitHub token found. API rate limits will be restricted.");
17
+ }
18
+ static isRateLimitError(error) {
19
+ if (error instanceof GraphqlResponseError) return error.errors.some((graphQLError) => graphQLError.type === "RATE_LIMITED" || typeof graphQLError.message === "string" && /rate limit/iu.test(graphQLError.message));
20
+ if (error instanceof Error) return /rate limit/iu.test(error.message);
21
+ return false;
22
+ }
23
+ async getTagInfo(owner, repo, tag) {
24
+ try {
25
+ let qualifiedTag = tag.startsWith("refs/tags/") ? tag : `refs/tags/${tag}`;
26
+ let displayTag = tag.replace(/^refs\/tags\//u, "");
27
+ let query = `
28
+ query getTagInfo($owner: String!, $repo: String!, $tag: String!) {
29
+ repository(owner: $owner, name: $repo) {
30
+ ref(qualifiedName: $tag) {
31
+ target {
32
+ oid
33
+ ... on Commit {
34
+ committedDate
35
+ message
36
+ }
37
+ ... on Tag {
38
+ tagger {
39
+ date
40
+ }
41
+ message
42
+ target {
43
+ oid
44
+ }
45
+ }
46
+ }
47
+ }
48
+ }
49
+ rateLimit {
50
+ remaining
51
+ resetAt
52
+ }
53
+ }
54
+ `;
55
+ let response = await this.graphqlWithAuth(query, {
56
+ tag: qualifiedTag,
57
+ owner,
58
+ repo
59
+ });
60
+ this.rateLimitRemaining = response.rateLimit.remaining;
61
+ this.rateLimitReset = new Date(response.rateLimit.resetAt);
62
+ if (!response.repository?.ref?.target) return null;
63
+ let { target } = response.repository.ref;
64
+ if ("committedDate" in target) return {
65
+ date: target.committedDate ? new Date(target.committedDate) : null,
66
+ message: target.message || null,
67
+ sha: target.oid,
68
+ tag: displayTag
69
+ };
70
+ let tagObject = target.target;
71
+ return {
72
+ date: target.tagger?.date ? new Date(target.tagger.date) : null,
73
+ sha: tagObject?.oid ?? target.oid,
74
+ message: target.message ?? null,
75
+ tag: displayTag
76
+ };
77
+ } catch (error) {
78
+ if (Client.isRateLimitError(error)) throw new GitHubRateLimitError(this.rateLimitReset);
79
+ throw error;
80
+ }
81
+ }
82
+ async getAllReleases(owner, repo, limit = 10) {
83
+ try {
84
+ let query = `
85
+ query getAllReleases($owner: String!, $repo: String!, $limit: Int!) {
86
+ repository(owner: $owner, name: $repo) {
87
+ releases(
88
+ first: $limit
89
+ orderBy: { field: CREATED_AT, direction: DESC }
90
+ ) {
91
+ nodes {
92
+ tagName
93
+ tagCommit {
94
+ oid
95
+ }
96
+ name
97
+ description
98
+ isPrerelease
99
+ publishedAt
100
+ url
101
+ }
102
+ }
103
+ }
104
+ rateLimit {
105
+ remaining
106
+ resetAt
107
+ }
108
+ }
109
+ `;
110
+ let response = await this.graphqlWithAuth(query, {
111
+ owner,
112
+ limit,
113
+ repo
114
+ });
115
+ this.rateLimitRemaining = response.rateLimit.remaining;
116
+ this.rateLimitReset = new Date(response.rateLimit.resetAt);
117
+ if (!response.repository?.releases?.nodes) return [];
118
+ return response.repository.releases.nodes.map((release) => ({
119
+ sha: release.tagCommit?.oid ?? null,
120
+ publishedAt: new Date(release.publishedAt),
121
+ description: release.description ?? null,
122
+ name: release.name ?? release.tagName,
123
+ isPrerelease: release.isPrerelease,
124
+ url: release.url,
125
+ version: release.tagName
126
+ }));
127
+ } catch (error) {
128
+ if (Client.isRateLimitError(error)) throw new GitHubRateLimitError(this.rateLimitReset);
129
+ throw error;
130
+ }
131
+ }
132
+ async getLatestRelease(owner, repo) {
133
+ try {
134
+ let query = `
135
+ query getLatestRelease($owner: String!, $repo: String!) {
136
+ repository(owner: $owner, name: $repo) {
137
+ latestRelease {
138
+ tagName
139
+ tagCommit {
140
+ oid
141
+ }
142
+ name
143
+ description
144
+ isPrerelease
145
+ publishedAt
146
+ url
147
+ }
148
+ }
149
+ rateLimit {
150
+ remaining
151
+ resetAt
152
+ }
153
+ }
154
+ `;
155
+ let response = await this.graphqlWithAuth(query, {
156
+ owner,
157
+ repo
158
+ });
159
+ this.rateLimitRemaining = response.rateLimit.remaining;
160
+ this.rateLimitReset = new Date(response.rateLimit.resetAt);
161
+ if (!response.repository?.latestRelease) return null;
162
+ let release = response.repository.latestRelease;
163
+ return {
164
+ sha: release.tagCommit?.oid ?? null,
165
+ publishedAt: new Date(release.publishedAt),
166
+ description: release.description ?? null,
167
+ name: release.name ?? release.tagName,
168
+ isPrerelease: release.isPrerelease,
169
+ url: release.url,
170
+ version: release.tagName
171
+ };
172
+ } catch (error) {
173
+ if (Client.isRateLimitError(error)) throw new GitHubRateLimitError(this.rateLimitReset);
174
+ throw error;
175
+ }
176
+ }
177
+ getRateLimitStatus() {
178
+ return {
179
+ remaining: this.rateLimitRemaining,
180
+ resetAt: this.rateLimitReset
181
+ };
182
+ }
183
+ shouldWaitForRateLimit(threshold = 100) {
184
+ return this.rateLimitRemaining < threshold;
185
+ }
186
+ };
187
+ export { Client };
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Type guard to check if a node has a range property for line number
3
+ * calculation.
4
+ *
5
+ * @param node - The node to check.
6
+ * @returns True if the node has a range property.
7
+ */
8
+ export declare function hasRange(node: unknown): node is {
9
+ range?: [number, number, number];
10
+ };
@@ -0,0 +1,4 @@
1
+ function hasRange(node) {
2
+ return node !== null && typeof node === "object" && "range" in node;
3
+ }
4
+ export { hasRange };
@@ -0,0 +1,8 @@
1
+ import { Node } from 'yaml';
2
+ /**
3
+ * Type guard to check if a node is a YAML Node with toJSON method.
4
+ *
5
+ * @param node - The node to check.
6
+ * @returns True if the node has a toJSON method.
7
+ */
8
+ export declare function isNode(node: unknown): node is Node;
@@ -0,0 +1,4 @@
1
+ function isNode(node) {
2
+ return node !== null && typeof node === "object" && "toJSON" in node && typeof node.toJSON === "function";
3
+ }
4
+ export { isNode };
@@ -0,0 +1,8 @@
1
+ import { Pair } from 'yaml';
2
+ /**
3
+ * Type guard to check if a node is a YAML key-value pair.
4
+ *
5
+ * @param node - The node to check.
6
+ * @returns True if the node is a Pair.
7
+ */
8
+ export declare function isPair(node: unknown): node is Pair;
@@ -0,0 +1,4 @@
1
+ function isPair(node) {
2
+ return node !== null && typeof node === "object" && "key" in node && "value" in node;
3
+ }
4
+ export { isPair };
@@ -0,0 +1,8 @@
1
+ import { Scalar } from 'yaml';
2
+ /**
3
+ * Type guard to check if a node is a YAML scalar value.
4
+ *
5
+ * @param node - The node to check.
6
+ * @returns True if the node is a Scalar.
7
+ */
8
+ export declare function isScalar(node: unknown): node is Scalar;
@@ -0,0 +1,4 @@
1
+ function isScalar(node) {
2
+ return node !== null && typeof node === "object" && "value" in node;
3
+ }
4
+ export { isScalar };
@@ -0,0 +1,8 @@
1
+ import { YAMLMap } from 'yaml';
2
+ /**
3
+ * Type guard to check if a node is a YAML map (object).
4
+ *
5
+ * @param node - The node to check.
6
+ * @returns True if the node is a YAMLMap.
7
+ */
8
+ export declare function isYAMLMap(node: unknown): node is YAMLMap;
@@ -0,0 +1,4 @@
1
+ function isYAMLMap(node) {
2
+ return node !== null && typeof node === "object" && "items" in node && Array.isArray(node.items);
3
+ }
4
+ export { isYAMLMap };
@@ -0,0 +1,8 @@
1
+ import { YAMLSeq } from 'yaml';
2
+ /**
3
+ * Type guard to check if a node is a YAML sequence (array).
4
+ *
5
+ * @param node - The node to check.
6
+ * @returns True if the node is a YAMLSeq.
7
+ */
8
+ export declare function isYAMLSequence(node: unknown): node is YAMLSeq;
@@ -0,0 +1,4 @@
1
+ function isYAMLSequence(node) {
2
+ return node !== null && typeof node === "object" && "items" in node && Array.isArray(node.items);
3
+ }
4
+ export { isYAMLSequence };
@@ -0,0 +1,14 @@
1
+ import { Document } from 'yaml';
2
+ import { GitHubAction } from '../../../types/github-action';
3
+ /**
4
+ * Scans a parsed composite action YAML document for action references.
5
+ *
6
+ * Navigates AST structure `runs -> steps` and extracts `uses` entries with
7
+ * corresponding line numbers when the action defines `using: composite`.
8
+ *
9
+ * @param document - Parsed YAML document of a composite action file.
10
+ * @param content - Original file content.
11
+ * @param filePath - Path of the action file.
12
+ * @returns List of discovered actions.
13
+ */
14
+ export declare function scanCompositeActionAst(document: Document, content: string, filePath: string): GitHubAction[];
@@ -0,0 +1,18 @@
1
+ import { isYAMLMap } from "../guards/is-yaml-map.js";
2
+ import { extractUsesFromSteps } from "../utils/extract-uses-from-steps.js";
3
+ import { findMapPair } from "../utils/find-map-pair.js";
4
+ import { isCompositeActionStructure } from "../../schema/composite/is-composite-action-structure.js";
5
+ import { isCompositeActionRuns } from "../../schema/composite/is-composite-action-runs.js";
6
+ function scanCompositeActionAst(document, content, filePath) {
7
+ let action = document.toJSON();
8
+ if (!isCompositeActionStructure(action)) return [];
9
+ if (!document.contents || !isYAMLMap(document.contents)) return [];
10
+ let runsPair = findMapPair(document.contents, "runs");
11
+ if (!runsPair?.value || !isYAMLMap(runsPair.value)) return [];
12
+ let runsJson = action["runs"];
13
+ if (!runsJson || !isCompositeActionRuns(runsJson) || !runsJson["steps"] || !Array.isArray(runsJson["steps"])) return [];
14
+ let stepsPair = findMapPair(runsPair.value, "steps");
15
+ if (!stepsPair?.value) return [];
16
+ return extractUsesFromSteps(stepsPair.value, filePath, content);
17
+ }
18
+ export { scanCompositeActionAst };
@@ -0,0 +1,14 @@
1
+ import { Document } from 'yaml';
2
+ import { GitHubAction } from '../../../types/github-action';
3
+ /**
4
+ * Scans a parsed workflow YAML document for action references.
5
+ *
6
+ * Navigates AST structure `jobs -> <job> -> steps` and extracts `uses` entries
7
+ * with corresponding line numbers.
8
+ *
9
+ * @param document - Parsed YAML document of a workflow file.
10
+ * @param content - Original file content.
11
+ * @param filePath - Path of the workflow file to scan.
12
+ * @returns List of discovered actions.
13
+ */
14
+ export declare function scanWorkflowAst(document: Document, content: string, filePath: string): GitHubAction[];
@@ -0,0 +1,23 @@
1
+ import { isWorkflowStructure } from "../../schema/workflow/is-workflow-structure.js";
2
+ import { isYAMLMap } from "../guards/is-yaml-map.js";
3
+ import { isNode } from "../guards/is-node.js";
4
+ import { isPair } from "../guards/is-pair.js";
5
+ import { extractUsesFromSteps } from "../utils/extract-uses-from-steps.js";
6
+ import { findMapPair } from "../utils/find-map-pair.js";
7
+ function scanWorkflowAst(document, content, filePath) {
8
+ let workflow = document.toJSON();
9
+ if (!isWorkflowStructure(workflow)) return [];
10
+ if (!document.contents || !isYAMLMap(document.contents)) return [];
11
+ let jobsPair = findMapPair(document.contents, "jobs");
12
+ if (!jobsPair?.value || !isYAMLMap(jobsPair.value)) return [];
13
+ let actions = [];
14
+ for (let jobNode of jobsPair.value.items) {
15
+ if (!isPair(jobNode) || !jobNode.value || !isNode(jobNode.value)) continue;
16
+ if (!isYAMLMap(jobNode.value)) continue;
17
+ let stepsPair = findMapPair(jobNode.value, "steps");
18
+ if (!stepsPair?.value) continue;
19
+ actions.push(...extractUsesFromSteps(stepsPair.value, filePath, content));
20
+ }
21
+ return actions;
22
+ }
23
+ export { scanWorkflowAst };
@@ -0,0 +1,7 @@
1
+ import { ActionUpdate } from '../../../types/action-update';
2
+ /**
3
+ * Apply updates using SHA with version in comment for readability.
4
+ *
5
+ * @param updates - Array of updates to apply.
6
+ */
7
+ export declare function applyUpdates(updates: ActionUpdate[]): Promise<void>;