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 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#with-github-token");
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 GraphQL client with optional authentication. */
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 {};
@@ -1,4 +1,4 @@
1
- import { GraphqlResponseError, graphql } from "@octokit/graphql";
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.graphqlWithAuth = graphql.defaults({ headers: authToken ? { authorization: `token ${authToken}` } : {} });
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 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);
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
- 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
- };
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 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, {
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.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
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 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, {
137
+ let { data: release, headers } = await this.octokit.repos.getLatestRelease({
156
138
  owner,
157
139
  repo
158
140
  });
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;
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
- 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
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.0.0";
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.0.0",
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/graphql": "^9.0.1",
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 with GitHub token:
93
+ Run in your repository root:
106
94
 
107
95
  ```bash
108
- GITHUB_TOKEN=ghp_xxxx npx actions-up
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
- GITHUB_TOKEN=ghp_xxxx npx actions-up --yes
111
+ npx actions-up --yes
124
112
  # or
125
- GITHUB_TOKEN=ghp_xxxx npx actions-up -y
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: