actions-up 1.0.0 → 1.1.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.
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
  }
@@ -1,4 +1,3 @@
1
- /** Processed release information with normalized types. */
2
1
  interface ReleaseInfo {
3
2
  /** Release description or null if not provided. */
4
3
  description: string | null;
@@ -19,18 +18,19 @@ interface ReleaseInfo {
19
18
  interface TagInfo {
20
19
  /** Tag or commit message, null if not provided. */
21
20
  message: string | null;
21
+ /** Git commit SHA that this tag points to. */
22
+ sha: string | null;
22
23
  /** Date when the tag was created or committed. */
23
24
  date: Date | null;
24
25
  /** Tag name (e.g., 'v1.2.3'). */
25
26
  tag: string;
26
- /** Git commit SHA that this tag points to. */
27
- sha: string;
28
27
  }
29
- /** GitHub GraphQL client with optional authentication. */
28
+ /** GitHub REST API client with optional authentication. */
30
29
  export declare class Client {
31
- private readonly graphqlWithAuth;
32
- private rateLimitRemaining;
30
+ private readonly baseUrl;
31
+ private readonly token;
33
32
  private rateLimitReset;
33
+ private rateLimitRemaining;
34
34
  /**
35
35
  * Creates a new GitHub API client.
36
36
  *
@@ -75,5 +75,7 @@ export declare class Client {
75
75
  * @returns True if rate limit is below threshold.
76
76
  */
77
77
  shouldWaitForRateLimit(threshold?: number): boolean;
78
+ private makeRequest;
79
+ private updateRateLimitInfo;
78
80
  }
79
81
  export {};
@@ -1,4 +1,3 @@
1
- import { GraphqlResponseError, graphql } from "@octokit/graphql";
2
1
  var GitHubRateLimitError = class extends Error {
3
2
  constructor(resetAt) {
4
3
  let resetTime = resetAt.toLocaleTimeString();
@@ -7,73 +6,76 @@ var GitHubRateLimitError = class extends Error {
7
6
  }
8
7
  };
9
8
  var Client = class Client {
10
- graphqlWithAuth;
11
- rateLimitRemaining = 5e3;
9
+ baseUrl = "https://api.github.com";
10
+ token;
12
11
  rateLimitReset = /* @__PURE__ */ new Date();
12
+ rateLimitRemaining = 60;
13
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.");
14
+ this.token = token ?? process.env["GITHUB_TOKEN"];
15
+ if (!this.token) console.warn("No GitHub token found. API rate limits will be restricted.");
16
+ this.rateLimitRemaining = this.token ? 5e3 : 60;
17
17
  }
18
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);
19
+ if (error && typeof error === "object") {
20
+ let maybeAny = error;
21
+ let message = typeof maybeAny.message === "string" ? maybeAny.message.toLowerCase() : "";
22
+ let status = typeof maybeAny.status === "number" ? maybeAny.status : void 0;
23
+ return message.includes("rate limit") || message.includes("api rate limit") || status === 403;
24
+ }
21
25
  return false;
22
26
  }
23
27
  async getTagInfo(owner, repo, tag) {
24
28
  try {
25
- let qualifiedTag = tag.startsWith("refs/tags/") ? tag : `refs/tags/${tag}`;
26
29
  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
- };
30
+ try {
31
+ let releaseResp = await this.makeRequest(`/repos/${owner}/${repo}/releases/tags/${displayTag}`);
32
+ let releaseData = releaseResp.data;
33
+ let sha = null;
34
+ if (releaseData.target_commitish) try {
35
+ let commitResp = await this.makeRequest(`/repos/${owner}/${repo}/commits/${releaseData.target_commitish}`);
36
+ let commitData = commitResp.data;
37
+ ({sha} = commitData);
38
+ } catch {
39
+ sha = releaseData.target_commitish;
40
+ }
41
+ return {
42
+ date: releaseData.published_at ? new Date(releaseData.published_at) : null,
43
+ sha: sha ?? releaseData.target_commitish,
44
+ message: releaseData.body ?? null,
45
+ tag: displayTag
46
+ };
47
+ } catch (releaseError) {
48
+ if (releaseError && typeof releaseError === "object" && "status" in releaseError && releaseError.status === 404) try {
49
+ let referenceResp = await this.makeRequest(`/repos/${owner}/${repo}/git/refs/tags/${displayTag}`);
50
+ let referenceData = referenceResp.data;
51
+ let { sha } = referenceData.object;
52
+ let message = null;
53
+ let date = null;
54
+ if (referenceData.object.type === "tag") try {
55
+ let tagResp = await this.makeRequest(`/repos/${owner}/${repo}/git/tags/${sha}`);
56
+ let tagData = tagResp.data;
57
+ ({sha} = tagData.object);
58
+ ({message} = tagData);
59
+ date = tagData.tagger.date ? new Date(tagData.tagger.date) : null;
60
+ } catch {}
61
+ else try {
62
+ let commitResp = await this.makeRequest(`/repos/${owner}/${repo}/git/commits/${sha}`);
63
+ let commitData = commitResp.data;
64
+ ({message} = commitData);
65
+ date = commitData.author.date ? new Date(commitData.author.date) : null;
66
+ } catch {}
67
+ return {
68
+ tag: displayTag,
69
+ message,
70
+ date,
71
+ sha
72
+ };
73
+ } catch (tagError) {
74
+ if (tagError && typeof tagError === "object" && "status" in tagError && tagError.status === 404) return null;
75
+ throw tagError;
76
+ }
77
+ throw releaseError;
78
+ }
77
79
  } catch (error) {
78
80
  if (Client.isRateLimitError(error)) throw new GitHubRateLimitError(this.rateLimitReset);
79
81
  throw error;
@@ -81,49 +83,28 @@ var Client = class Client {
81
83
  }
82
84
  async getAllReleases(owner, repo, limit = 10) {
83
85
  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
86
+ let releasesResp = await this.makeRequest(`/repos/${owner}/${repo}/releases?per_page=${limit}`);
87
+ let releases = releasesResp.data;
88
+ let releaseInfos = [];
89
+ await Promise.all(releases.map(async (release) => {
90
+ let sha = null;
91
+ if (release.tag_name) try {
92
+ let tagInfo = await this.getTagInfo(owner, repo, release.tag_name);
93
+ if (tagInfo) ({sha} = tagInfo);
94
+ } catch {
95
+ sha = release.target_commitish;
96
+ }
97
+ releaseInfos.push({
98
+ publishedAt: new Date(release.published_at),
99
+ name: release.name ?? release.tag_name,
100
+ description: release.body ?? null,
101
+ isPrerelease: release.prerelease,
102
+ version: release.tag_name,
103
+ url: release.html_url,
104
+ sha
105
+ });
126
106
  }));
107
+ return releaseInfos;
127
108
  } catch (error) {
128
109
  if (Client.isRateLimitError(error)) throw new GitHubRateLimitError(this.rateLimitReset);
129
110
  throw error;
@@ -131,45 +112,26 @@ var Client = class Client {
131
112
  }
132
113
  async getLatestRelease(owner, repo) {
133
114
  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;
115
+ let releaseResp = await this.makeRequest(`/repos/${owner}/${repo}/releases/latest`);
116
+ let release = releaseResp.data;
117
+ let sha = null;
118
+ if (release.tag_name) try {
119
+ let tagInfo = await this.getTagInfo(owner, repo, release.tag_name);
120
+ if (tagInfo) ({sha} = tagInfo);
121
+ } catch {
122
+ sha = release.target_commitish;
123
+ }
163
124
  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
125
+ publishedAt: new Date(release.published_at),
126
+ name: release.name ?? release.tag_name,
127
+ description: release.body ?? null,
128
+ isPrerelease: release.prerelease,
129
+ version: release.tag_name,
130
+ url: release.html_url,
131
+ sha
171
132
  };
172
133
  } catch (error) {
134
+ if (error && typeof error === "object" && "status" in error && error.status === 404) return null;
173
135
  if (Client.isRateLimitError(error)) throw new GitHubRateLimitError(this.rateLimitReset);
174
136
  throw error;
175
137
  }
@@ -183,5 +145,43 @@ var Client = class Client {
183
145
  shouldWaitForRateLimit(threshold = 100) {
184
146
  return this.rateLimitRemaining < threshold;
185
147
  }
148
+ async makeRequest(path, options = {}) {
149
+ let headers = {
150
+ Accept: "application/vnd.github.v3+json",
151
+ "User-Agent": "actions-up",
152
+ ...options.headers
153
+ };
154
+ if (this.token) headers["Authorization"] = `Bearer ${this.token}`;
155
+ let response = await fetch(`${this.baseUrl}${path}`, {
156
+ ...options,
157
+ headers
158
+ });
159
+ let responseHeaders = {};
160
+ for (let [key, value] of response.headers.entries()) responseHeaders[key] = value;
161
+ this.updateRateLimitInfo(responseHeaders);
162
+ if (!response.ok) {
163
+ let error = /* @__PURE__ */ new Error(`GitHub API error: ${response.status} ${response.statusText}`);
164
+ error.status = response.status;
165
+ if (response.status === 403) {
166
+ let text = await response.text();
167
+ if (text.includes("rate limit") || text.includes("API rate limit")) error.message = "API rate limit exceeded";
168
+ }
169
+ throw error;
170
+ }
171
+ let data = await response.json();
172
+ return {
173
+ headers: responseHeaders,
174
+ data
175
+ };
176
+ }
177
+ updateRateLimitInfo(headers) {
178
+ let remaining = headers["x-ratelimit-remaining"];
179
+ if (remaining !== void 0) this.rateLimitRemaining = typeof remaining === "string" ? Number.parseInt(remaining, 10) : remaining;
180
+ let reset = headers["x-ratelimit-reset"];
181
+ if (reset !== void 0) {
182
+ let resetTime = typeof reset === "string" ? Number.parseInt(reset, 10) : reset;
183
+ this.rateLimitReset = /* @__PURE__ */ new Date(resetTime * 1e3);
184
+ }
185
+ }
186
186
  };
187
187
  export { Client };
package/dist/package.js CHANGED
@@ -1,2 +1,2 @@
1
- const version = "1.0.0";
1
+ const version = "1.1.1";
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.1",
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,6 @@
36
36
  "./dist"
37
37
  ],
38
38
  "dependencies": {
39
- "@octokit/graphql": "^9.0.1",
40
39
  "cac": "^6.7.14",
41
40
  "enquirer": "^2.4.1",
42
41
  "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: