actions-up 1.0.0 ā 1.1.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 +7 -1
- package/dist/core/api/check-updates.js +1 -1
- package/dist/core/api/client.d.ts +4 -3
- package/dist/core/api/client.js +125 -129
- package/dist/package.js +1 -1
- package/package.json +2 -2
- package/readme.md +15 -37
package/dist/cli/index.js
CHANGED
|
@@ -10,7 +10,7 @@ import pc from "picocolors";
|
|
|
10
10
|
import cac from "cac";
|
|
11
11
|
function run() {
|
|
12
12
|
let cli = cac("actions-up");
|
|
13
|
-
cli.help().version(version).option("--yes, -y", "Skip all confirmations").command("", "Update GitHub Actions").action(async (options) => {
|
|
13
|
+
cli.help().version(version).option("--yes, -y", "Skip all confirmations").option("--dry-run", "Preview changes without applying them").command("", "Update GitHub Actions").action(async (options) => {
|
|
14
14
|
console.info(pc.cyan("\nš Actions Up!\n"));
|
|
15
15
|
let spinner = createSpinner("Scanning GitHub Actions...").start();
|
|
16
16
|
try {
|
|
@@ -33,6 +33,12 @@ function run() {
|
|
|
33
33
|
return;
|
|
34
34
|
}
|
|
35
35
|
spinner.success(`Found ${pc.yellow(outdated.length)} updates available${breaking.length > 0 ? ` (${pc.red(breaking.length)} breaking)` : ""}`);
|
|
36
|
+
if (options.dryRun) {
|
|
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`);
|
|
39
|
+
console.info(pc.gray(`\n${outdated.length} actions would be updated\n`));
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
36
42
|
if (options.yes) {
|
|
37
43
|
let toUpdate = outdated.filter((update) => update.latestSha);
|
|
38
44
|
if (toUpdate.length === 0) {
|
|
@@ -69,7 +69,7 @@ async function checkUpdates(actions, token) {
|
|
|
69
69
|
}
|
|
70
70
|
}), Promise.resolve([]));
|
|
71
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#
|
|
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#using-github-token-for-higher-rate-limits");
|
|
73
73
|
error.name = "GitHubRateLimitError";
|
|
74
74
|
throw error;
|
|
75
75
|
}
|
|
@@ -26,11 +26,11 @@ interface TagInfo {
|
|
|
26
26
|
/** Git commit SHA that this tag points to. */
|
|
27
27
|
sha: string;
|
|
28
28
|
}
|
|
29
|
-
/** GitHub
|
|
29
|
+
/** GitHub REST API client with optional authentication. */
|
|
30
30
|
export declare class Client {
|
|
31
|
-
private readonly graphqlWithAuth;
|
|
32
|
-
private rateLimitRemaining;
|
|
33
31
|
private rateLimitReset;
|
|
32
|
+
private rateLimitRemaining;
|
|
33
|
+
private readonly octokit;
|
|
34
34
|
/**
|
|
35
35
|
* Creates a new GitHub API client.
|
|
36
36
|
*
|
|
@@ -75,5 +75,6 @@ export declare class Client {
|
|
|
75
75
|
* @returns True if rate limit is below threshold.
|
|
76
76
|
*/
|
|
77
77
|
shouldWaitForRateLimit(threshold?: number): boolean;
|
|
78
|
+
private updateRateLimitInfo;
|
|
78
79
|
}
|
|
79
80
|
export {};
|
package/dist/core/api/client.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Octokit } from "@octokit/rest";
|
|
2
2
|
var GitHubRateLimitError = class extends Error {
|
|
3
3
|
constructor(resetAt) {
|
|
4
4
|
let resetTime = resetAt.toLocaleTimeString();
|
|
@@ -7,73 +7,93 @@ var GitHubRateLimitError = class extends Error {
|
|
|
7
7
|
}
|
|
8
8
|
};
|
|
9
9
|
var Client = class Client {
|
|
10
|
-
graphqlWithAuth;
|
|
11
|
-
rateLimitRemaining = 5e3;
|
|
12
10
|
rateLimitReset = /* @__PURE__ */ new Date();
|
|
11
|
+
rateLimitRemaining = 60;
|
|
12
|
+
octokit;
|
|
13
13
|
constructor(token) {
|
|
14
14
|
let authToken = token ?? process.env["GITHUB_TOKEN"];
|
|
15
|
-
this.
|
|
15
|
+
this.octokit = new Octokit({ auth: authToken ?? void 0 });
|
|
16
16
|
if (!authToken) console.warn("No GitHub token found. API rate limits will be restricted.");
|
|
17
|
+
this.rateLimitRemaining = authToken ? 5e3 : 60;
|
|
17
18
|
}
|
|
18
19
|
static isRateLimitError(error) {
|
|
19
|
-
if (error
|
|
20
|
-
|
|
20
|
+
if (error && typeof error === "object") {
|
|
21
|
+
let maybeAny = error;
|
|
22
|
+
let message = typeof maybeAny.message === "string" ? maybeAny.message.toLowerCase() : "";
|
|
23
|
+
let status = typeof maybeAny.status === "number" ? maybeAny.status : void 0;
|
|
24
|
+
return message.includes("rate limit") || message.includes("api rate limit") || status === 403;
|
|
25
|
+
}
|
|
21
26
|
return false;
|
|
22
27
|
}
|
|
23
28
|
async getTagInfo(owner, repo, tag) {
|
|
24
29
|
try {
|
|
25
|
-
let qualifiedTag = tag.startsWith("refs/tags/") ? tag : `refs/tags/${tag}`;
|
|
26
30
|
let displayTag = tag.replace(/^refs\/tags\//u, "");
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
31
|
+
try {
|
|
32
|
+
let { headers: releaseHeaders, data: releaseData } = await this.octokit.repos.getReleaseByTag({
|
|
33
|
+
tag: displayTag,
|
|
34
|
+
owner,
|
|
35
|
+
repo
|
|
36
|
+
});
|
|
37
|
+
this.updateRateLimitInfo(releaseHeaders);
|
|
38
|
+
let sha = null;
|
|
39
|
+
if (releaseData.target_commitish) try {
|
|
40
|
+
let { data: commitData } = await this.octokit.repos.getCommit({
|
|
41
|
+
ref: releaseData.target_commitish,
|
|
42
|
+
owner,
|
|
43
|
+
repo
|
|
44
|
+
});
|
|
45
|
+
({sha} = commitData);
|
|
46
|
+
} catch {
|
|
47
|
+
sha = releaseData.target_commitish;
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
date: releaseData.published_at ? new Date(releaseData.published_at) : null,
|
|
51
|
+
sha: sha ?? releaseData.target_commitish,
|
|
52
|
+
message: releaseData.body ?? null,
|
|
53
|
+
tag: displayTag
|
|
54
|
+
};
|
|
55
|
+
} catch (releaseError) {
|
|
56
|
+
if (releaseError instanceof Error && "status" in releaseError && releaseError.status === 404) try {
|
|
57
|
+
let { headers: referenceHeaders, data: referenceData } = await this.octokit.git.getRef({
|
|
58
|
+
ref: `tags/${displayTag}`,
|
|
59
|
+
owner,
|
|
60
|
+
repo
|
|
61
|
+
});
|
|
62
|
+
this.updateRateLimitInfo(referenceHeaders);
|
|
63
|
+
let { sha } = referenceData.object;
|
|
64
|
+
let message = null;
|
|
65
|
+
let date = null;
|
|
66
|
+
if (referenceData.object.type === "tag") try {
|
|
67
|
+
let { data: tagData } = await this.octokit.git.getTag({
|
|
68
|
+
tag_sha: sha,
|
|
69
|
+
owner,
|
|
70
|
+
repo
|
|
71
|
+
});
|
|
72
|
+
({sha} = tagData.object);
|
|
73
|
+
message = tagData.message || null;
|
|
74
|
+
date = tagData.tagger.date ? new Date(tagData.tagger.date) : null;
|
|
75
|
+
} catch {}
|
|
76
|
+
else if (referenceData.object.type === "commit") try {
|
|
77
|
+
let { data: commitData } = await this.octokit.git.getCommit({
|
|
78
|
+
commit_sha: sha,
|
|
79
|
+
owner,
|
|
80
|
+
repo
|
|
81
|
+
});
|
|
82
|
+
({message} = commitData);
|
|
83
|
+
date = commitData.author.date ? new Date(commitData.author.date) : null;
|
|
84
|
+
} catch {}
|
|
85
|
+
return {
|
|
86
|
+
tag: displayTag,
|
|
87
|
+
message,
|
|
88
|
+
date,
|
|
89
|
+
sha
|
|
90
|
+
};
|
|
91
|
+
} catch (tagError) {
|
|
92
|
+
if (tagError instanceof Error && "status" in tagError && tagError.status === 404) return null;
|
|
93
|
+
throw tagError;
|
|
94
|
+
}
|
|
95
|
+
throw releaseError;
|
|
96
|
+
}
|
|
77
97
|
} catch (error) {
|
|
78
98
|
if (Client.isRateLimitError(error)) throw new GitHubRateLimitError(this.rateLimitReset);
|
|
79
99
|
throw error;
|
|
@@ -81,49 +101,32 @@ var Client = class Client {
|
|
|
81
101
|
}
|
|
82
102
|
async getAllReleases(owner, repo, limit = 10) {
|
|
83
103
|
try {
|
|
84
|
-
let
|
|
85
|
-
|
|
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, {
|
|
104
|
+
let { data: releases, headers } = await this.octokit.repos.listReleases({
|
|
105
|
+
per_page: limit,
|
|
111
106
|
owner,
|
|
112
|
-
limit,
|
|
113
107
|
repo
|
|
114
108
|
});
|
|
115
|
-
this.
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
109
|
+
this.updateRateLimitInfo(headers);
|
|
110
|
+
let releaseInfos = [];
|
|
111
|
+
await Promise.all(releases.map(async (release) => {
|
|
112
|
+
let sha = null;
|
|
113
|
+
if (release.tag_name) try {
|
|
114
|
+
let tagInfo = await this.getTagInfo(owner, repo, release.tag_name);
|
|
115
|
+
if (tagInfo) ({sha} = tagInfo);
|
|
116
|
+
} catch {
|
|
117
|
+
sha = release.target_commitish || null;
|
|
118
|
+
}
|
|
119
|
+
releaseInfos.push({
|
|
120
|
+
publishedAt: new Date(release.published_at),
|
|
121
|
+
name: release.name ?? release.tag_name,
|
|
122
|
+
description: release.body ?? null,
|
|
123
|
+
isPrerelease: release.prerelease,
|
|
124
|
+
version: release.tag_name,
|
|
125
|
+
url: release.html_url,
|
|
126
|
+
sha
|
|
127
|
+
});
|
|
126
128
|
}));
|
|
129
|
+
return releaseInfos;
|
|
127
130
|
} catch (error) {
|
|
128
131
|
if (Client.isRateLimitError(error)) throw new GitHubRateLimitError(this.rateLimitReset);
|
|
129
132
|
throw error;
|
|
@@ -131,45 +134,29 @@ var Client = class Client {
|
|
|
131
134
|
}
|
|
132
135
|
async getLatestRelease(owner, repo) {
|
|
133
136
|
try {
|
|
134
|
-
let
|
|
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, {
|
|
137
|
+
let { data: release, headers } = await this.octokit.repos.getLatestRelease({
|
|
156
138
|
owner,
|
|
157
139
|
repo
|
|
158
140
|
});
|
|
159
|
-
this.
|
|
160
|
-
|
|
161
|
-
if (
|
|
162
|
-
|
|
141
|
+
this.updateRateLimitInfo(headers);
|
|
142
|
+
let sha = null;
|
|
143
|
+
if (release.tag_name) try {
|
|
144
|
+
let tagInfo = await this.getTagInfo(owner, repo, release.tag_name);
|
|
145
|
+
if (tagInfo) ({sha} = tagInfo);
|
|
146
|
+
} catch {
|
|
147
|
+
sha = release.target_commitish || null;
|
|
148
|
+
}
|
|
163
149
|
return {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
description: release.
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
url: release.
|
|
170
|
-
|
|
150
|
+
publishedAt: new Date(release.published_at),
|
|
151
|
+
name: release.name ?? release.tag_name,
|
|
152
|
+
description: release.body ?? null,
|
|
153
|
+
isPrerelease: release.prerelease,
|
|
154
|
+
version: release.tag_name,
|
|
155
|
+
url: release.html_url,
|
|
156
|
+
sha
|
|
171
157
|
};
|
|
172
158
|
} catch (error) {
|
|
159
|
+
if (error instanceof Error && "status" in error && error.status === 404) return null;
|
|
173
160
|
if (Client.isRateLimitError(error)) throw new GitHubRateLimitError(this.rateLimitReset);
|
|
174
161
|
throw error;
|
|
175
162
|
}
|
|
@@ -183,5 +170,14 @@ var Client = class Client {
|
|
|
183
170
|
shouldWaitForRateLimit(threshold = 100) {
|
|
184
171
|
return this.rateLimitRemaining < threshold;
|
|
185
172
|
}
|
|
173
|
+
updateRateLimitInfo(headers) {
|
|
174
|
+
let remaining = headers["x-ratelimit-remaining"];
|
|
175
|
+
if (remaining !== void 0) this.rateLimitRemaining = typeof remaining === "string" ? Number.parseInt(remaining, 10) : remaining;
|
|
176
|
+
let reset = headers["x-ratelimit-reset"];
|
|
177
|
+
if (reset !== void 0) {
|
|
178
|
+
let resetTime = typeof reset === "string" ? Number.parseInt(reset, 10) : reset;
|
|
179
|
+
this.rateLimitReset = /* @__PURE__ */ new Date(resetTime * 1e3);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
186
182
|
};
|
|
187
183
|
export { Client };
|
package/dist/package.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const version = "1.
|
|
1
|
+
const version = "1.1.0";
|
|
2
2
|
export { version };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "actions-up",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Interactive CLI tool to update GitHub Actions to latest versions with SHA pinning",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"github-actions",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"./dist"
|
|
37
37
|
],
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@octokit/
|
|
39
|
+
"@octokit/rest": "^22.0.0",
|
|
40
40
|
"cac": "^6.7.14",
|
|
41
41
|
"enquirer": "^2.4.1",
|
|
42
42
|
"nanospinner": "^1.2.2",
|
package/readme.md
CHANGED
|
@@ -66,18 +66,6 @@ Actions Up transforms a painful manual process into a delightful experience:
|
|
|
66
66
|
| Risk using vulnerable versions | SHA pinning for maximum security |
|
|
67
67
|
| 30+ minutes per repository | Under 1 minute total |
|
|
68
68
|
|
|
69
|
-
## GitHub Token Required
|
|
70
|
-
|
|
71
|
-
> **Important**: GitHub API has strict rate limits (60 requests/hour without token vs 5000 with token).
|
|
72
|
-
> A GitHub token is **practically required** for using Actions Up.
|
|
73
|
-
|
|
74
|
-
### Quick Token Setup
|
|
75
|
-
|
|
76
|
-
[Create a GitHub Personal Access Token](https://github.com/settings/tokens/new?scopes=public_repo&description=actions-up).
|
|
77
|
-
|
|
78
|
-
- For public repositories: Select `public_repo` scope
|
|
79
|
-
- For private repositories: Select `repo` scope
|
|
80
|
-
|
|
81
69
|
## Installation
|
|
82
70
|
|
|
83
71
|
Quick use (no installation)
|
|
@@ -102,10 +90,10 @@ npm install --save-dev actions-up
|
|
|
102
90
|
|
|
103
91
|
### Interactive Mode (Default)
|
|
104
92
|
|
|
105
|
-
Run in your repository root
|
|
93
|
+
Run in your repository root:
|
|
106
94
|
|
|
107
95
|
```bash
|
|
108
|
-
|
|
96
|
+
npx actions-up
|
|
109
97
|
```
|
|
110
98
|
|
|
111
99
|
This will:
|
|
@@ -120,30 +108,9 @@ This will:
|
|
|
120
108
|
Skip all prompts and update everything:
|
|
121
109
|
|
|
122
110
|
```bash
|
|
123
|
-
|
|
111
|
+
npx actions-up --yes
|
|
124
112
|
# or
|
|
125
|
-
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
## Pro Tips
|
|
129
|
-
|
|
130
|
-
### Shell Aliases
|
|
131
|
-
|
|
132
|
-
Add to your `.zshrc`, `.bashrc` or `.config/fish/config.fish`:
|
|
133
|
-
|
|
134
|
-
```bash
|
|
135
|
-
# Basic alias with token from environment
|
|
136
|
-
export GITHUB_TOKEN=ghp_xxxx # Add this once to your shell config
|
|
137
|
-
alias actions-up='GITHUB_TOKEN=$GITHUB_TOKEN npx actions-up'
|
|
138
|
-
|
|
139
|
-
# With token from file
|
|
140
|
-
alias actions-up='GITHUB_TOKEN=$(cat ~/.github-token) npx actions-up'
|
|
141
|
-
|
|
142
|
-
# With 1Password CLI
|
|
143
|
-
alias actions-up='GITHUB_TOKEN=$(op read "op://Personal/GitHub/token") npx actions-up'
|
|
144
|
-
|
|
145
|
-
# With macOS Keychain
|
|
146
|
-
alias actions-up='GITHUB_TOKEN=$(security find-generic-password -w -s "github-token") npx actions-up'
|
|
113
|
+
npx actions-up -y
|
|
147
114
|
```
|
|
148
115
|
|
|
149
116
|
## Example
|
|
@@ -158,6 +125,17 @@ alias actions-up='GITHUB_TOKEN=$(security find-generic-password -w -s "github-to
|
|
|
158
125
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
|
159
126
|
```
|
|
160
127
|
|
|
128
|
+
## Advanced Usage
|
|
129
|
+
|
|
130
|
+
### Using GitHub Token for Higher Rate Limits
|
|
131
|
+
|
|
132
|
+
While Actions Up works without authentication, providing a GitHub token increases API rate limits from 60 to 5000 requests per hour, useful for large projects:
|
|
133
|
+
|
|
134
|
+
[Create a GitHub Personal Access Token](https://github.com/settings/tokens/new?scopes=public_repo&description=actions-up).
|
|
135
|
+
|
|
136
|
+
- For public repositories: Select `public_repo` scope
|
|
137
|
+
- For private repositories: Select `repo` scope
|
|
138
|
+
|
|
161
139
|
## Security
|
|
162
140
|
|
|
163
141
|
Actions Up promotes security best practices:
|