actions-up 1.12.0 → 1.13.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/build-json-report.d.ts +257 -0
  2. package/dist/cli/build-json-report.js +64 -0
  3. package/dist/cli/index.d.ts +3 -1
  4. package/dist/cli/index.js +132 -73
  5. package/dist/cli/resolve-scan-directories.d.ts +6 -2
  6. package/dist/cli/validate-cli-options.d.ts +20 -0
  7. package/dist/cli/validate-cli-options.js +4 -0
  8. package/dist/core/api/check-updates.js +68 -55
  9. package/dist/core/api/get-all-releases.d.ts +2 -1
  10. package/dist/core/api/get-compatible-update.d.ts +15 -5
  11. package/dist/core/api/get-latest-release.d.ts +3 -3
  12. package/dist/core/ast/utils/extract-uses-from-steps.d.ts +12 -4
  13. package/dist/core/constants.d.ts +3 -1
  14. package/dist/core/fs/find-yaml-files-recursive.js +1 -1
  15. package/dist/core/interactive/prompt-update-selection.d.ts +3 -1
  16. package/dist/core/interactive/prompt-update-selection.js +9 -9
  17. package/dist/core/parsing/parse-action-reference.d.ts +16 -12
  18. package/dist/core/scan-github-actions.d.ts +4 -1
  19. package/dist/core/scan-github-actions.js +67 -67
  20. package/dist/core/versions/get-update-level.d.ts +3 -1
  21. package/dist/package.js +1 -1
  22. package/dist/types/action-update.d.ts +30 -10
  23. package/dist/types/composite-action-runs.d.ts +12 -4
  24. package/dist/types/composite-action-step.d.ts +24 -8
  25. package/dist/types/composite-action-structure.d.ts +21 -7
  26. package/dist/types/github-action.d.ts +27 -9
  27. package/dist/types/github-client-context.d.ts +24 -8
  28. package/dist/types/github-client.d.ts +24 -8
  29. package/dist/types/release-info.d.ts +24 -8
  30. package/dist/types/scan-result.d.ts +12 -4
  31. package/dist/types/tag-info.d.ts +15 -5
  32. package/dist/types/update-mode.d.ts +3 -1
  33. package/dist/types/workflow-job.d.ts +27 -9
  34. package/dist/types/workflow-step.d.ts +21 -7
  35. package/dist/types/workflow-structure.d.ts +15 -5
  36. package/package.json +3 -8
  37. package/readme.md +94 -71
@@ -2,19 +2,19 @@ import { normalizeVersion } from "../versions/normalize-version.js";
2
2
  import { isSemverLike } from "../versions/is-semver-like.js";
3
3
  import { createGitHubClient } from "./create-github-client.js";
4
4
  import semver from "semver";
5
- async function checkUpdates(i, o, c) {
6
- let l = c?.client ?? createGitHubClient(o), u = c?.includeBranches ?? !1, d = i.filter((e) => e.type === "external" || e.type === "reusable-workflow");
7
- if (d.length === 0) return [];
8
- let f = /* @__PURE__ */ new Map();
9
- for (let e of d) {
10
- let t = f.get(e.name) ?? [];
11
- t.push(e), f.set(e.name, t);
5
+ async function checkUpdates(i, o, l) {
6
+ let u = l?.client ?? createGitHubClient(o), d = l?.includeBranches ?? !1, f = i.filter((e) => e.type === "external" || e.type === "reusable-workflow");
7
+ if (f.length === 0) return [];
8
+ let p = /* @__PURE__ */ new Map();
9
+ for (let e of f) {
10
+ let t = p.get(e.name) ?? [];
11
+ t.push(e), p.set(e.name, t);
12
12
  }
13
- let p = {
13
+ let m = {
14
14
  rateLimitError: null,
15
15
  rateLimitHit: !1
16
- }, m = await [...f.keys()].reduce((n, i) => n.then(async (n) => {
17
- if (p.rateLimitHit) return [...n, {
16
+ }, h = await [...p.keys()].reduce((n, i) => n.then(async (n) => {
17
+ if (m.rateLimitHit) return [...n, {
18
18
  publishedAt: null,
19
19
  version: null,
20
20
  actionName: i,
@@ -27,16 +27,16 @@ async function checkUpdates(i, o, c) {
27
27
  actionName: i,
28
28
  sha: null
29
29
  }];
30
- let [o, c] = a;
31
- if (!o || !c) return [...n, {
30
+ let [o, l] = a;
31
+ if (!o || !l) return [...n, {
32
32
  publishedAt: null,
33
33
  version: null,
34
34
  actionName: i,
35
35
  sha: null
36
36
  }];
37
37
  try {
38
- let a = f.get(i)[0]?.version;
39
- if (a && !isSha(a) && !isSemverLike(a) && await l.getRefType(o, c, a) === "branch" && !u) return [...n, {
38
+ let a = p.get(i)[0]?.version;
39
+ if (a && !isSha(a) && !isSemverLike(a) && await u.getRefType(o, l, a) === "branch" && !d) return [...n, {
40
40
  skipReason: "branch",
41
41
  status: "skipped",
42
42
  publishedAt: null,
@@ -44,37 +44,39 @@ async function checkUpdates(i, o, c) {
44
44
  actionName: i,
45
45
  sha: null
46
46
  }];
47
- let d = await l.getLatestRelease(o, c);
48
- if (!d) {
49
- let e = await l.getAllReleases(o, c, 1);
50
- d = e.find((e) => !e.isPrerelease) ?? e[0] ?? null;
47
+ let f = await u.getLatestRelease(o, l);
48
+ if (!f) {
49
+ let e = await u.getAllReleases(o, l, 1);
50
+ f = e.find((e) => !e.isPrerelease) ?? e[0] ?? null;
51
51
  }
52
- if (d) {
53
- let { publishedAt: a, version: s, sha: u } = d, f = !1;
52
+ if (f) {
53
+ let { publishedAt: a, version: s, sha: d } = f, p = !1;
54
54
  {
55
55
  let n = normalizeVersion(s), i = !!(s && s.trim() !== ""), a = i && /^v?\d+$/u.test(s.trim()), o = semver.valid(n);
56
- f = !i || a || !o || !isSemverLike(s);
56
+ p = !i || a || !o || !isSemverLike(s);
57
57
  }
58
- if (f) {
59
- let a = await l.getAllTags(o, c, 30);
58
+ if (p) {
59
+ let a = await u.getAllTags(o, l, 30);
60
60
  if (a.length > 0) {
61
- let u = a.filter((e) => isSemverLike(e.tag)).map((t) => ({
61
+ let d = a.filter((e) => isSemverLike(e.tag)).map((t) => ({
62
62
  v: semver.valid(normalizeVersion(t.tag)),
63
63
  raw: t
64
64
  }));
65
- if (u.length > 0) {
66
- u.sort((e, t) => {
65
+ if (d.length > 0) {
66
+ d.sort((e, t) => {
67
67
  let n = semver.rcompare(e.v, t.v);
68
68
  if (n !== 0) return n;
69
69
  let i = /\d+\.\d+/u.test(e.raw.tag) ? 1 : 0;
70
70
  return (/\d+\.\d+/u.test(t.raw.tag) ? 1 : 0) - i;
71
71
  });
72
- let t = u[0].raw, a = semver.valid(normalizeVersion(s) ?? void 0);
73
- if (!a || semver.gt(u[0].v, a) || semver.eq(u[0].v, a) && /\d+\.\d+/u.test(t.tag)) {
72
+ let t = d[0].raw, a = semver.valid(normalizeVersion(s) ?? void 0);
73
+ if (!a || semver.gt(d[0].v, a) || semver.eq(d[0].v, a) && /\d+\.\d+/u.test(t.tag)) {
74
74
  let e = t.tag, r = t.sha?.length ? t.sha : null;
75
75
  if (!r && e) try {
76
- r = await l.getTagSha(o, c, e);
77
- } catch {}
76
+ r = await u.getTagSha(o, l, e);
77
+ } catch (e) {
78
+ if (isRateLimitError(e)) throw e;
79
+ }
78
80
  return [...n, {
79
81
  version: e,
80
82
  publishedAt: null,
@@ -85,20 +87,26 @@ async function checkUpdates(i, o, c) {
85
87
  }
86
88
  }
87
89
  }
88
- if (!u && s) try {
89
- u = await l.getTagSha(o, c, s);
90
- } catch {}
90
+ if (s) {
91
+ let e = d;
92
+ try {
93
+ d = await u.getTagSha(o, l, s) ?? e;
94
+ } catch (t) {
95
+ if (isRateLimitError(t)) throw t;
96
+ d = e;
97
+ }
98
+ }
91
99
  return [...n, {
92
100
  status: "ok",
93
101
  publishedAt: a,
94
102
  actionName: i,
95
103
  version: s,
96
- sha: u
104
+ sha: d
97
105
  }];
98
106
  }
99
- let p = await l.getAllTags(o, c, 30);
100
- if (p.length > 0) {
101
- let a = p.filter((e) => isSemverLike(e.tag)).map((t) => ({
107
+ let m = await u.getAllTags(o, l, 30);
108
+ if (m.length > 0) {
109
+ let a = m.filter((e) => isSemverLike(e.tag)).map((t) => ({
102
110
  v: semver.valid(normalizeVersion(t.tag)),
103
111
  raw: t
104
112
  })), s;
@@ -107,17 +115,19 @@ async function checkUpdates(i, o, c) {
107
115
  if (n !== 0) return n;
108
116
  let i = /\d+\.\d+/u.test(e.raw.tag) ? 1 : 0;
109
117
  return (/\d+\.\d+/u.test(t.raw.tag) ? 1 : 0) - i;
110
- }), s = a[0].raw) : s = p[0];
111
- let u = s.tag, d = s.sha?.length ? s.sha : null;
112
- if (!d && u) try {
113
- d = await l.getTagSha(o, c, u);
114
- } catch {}
118
+ }), s = a[0].raw) : s = m[0];
119
+ let d = s.tag, f = s.sha?.length ? s.sha : null;
120
+ if (!f && d) try {
121
+ f = await u.getTagSha(o, l, d);
122
+ } catch (e) {
123
+ if (isRateLimitError(e)) throw e;
124
+ }
115
125
  return [...n, {
116
126
  status: "ok",
117
127
  publishedAt: null,
118
128
  actionName: i,
119
- version: u,
120
- sha: d
129
+ version: d,
130
+ sha: f
121
131
  }];
122
132
  }
123
133
  return [...n, {
@@ -127,7 +137,7 @@ async function checkUpdates(i, o, c) {
127
137
  sha: null
128
138
  }];
129
139
  } catch (e) {
130
- return e instanceof Error && e.name === "GitHubRateLimitError" ? (p.rateLimitHit = !0, p.rateLimitError = e, [...n, {
140
+ return e instanceof Error && e.name === "GitHubRateLimitError" ? (m.rateLimitHit = !0, m.rateLimitError = e, [...n, {
131
141
  publishedAt: null,
132
142
  version: null,
133
143
  actionName: i,
@@ -140,12 +150,12 @@ async function checkUpdates(i, o, c) {
140
150
  }]);
141
151
  }
142
152
  }), Promise.resolve([]));
143
- if (p.rateLimitError) {
144
- let e = !!(o ?? process.env.GITHUB_TOKEN), t = `${p.rateLimitError.message || "GitHub API rate limit exceeded."}\n${e ? "Wait for reset or reduce request rate." : "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"}`, n = Error(t);
153
+ if (m.rateLimitError) {
154
+ let e = !!(o ?? process.env.GITHUB_TOKEN), t = `${m.rateLimitError.message || "GitHub API rate limit exceeded."}\n${e ? "Wait for reset or reduce request rate." : "Please set GITHUB_TOKEN environment variable to increase the limit.\nSee: https://github.com/azat-io/actions-up?tab=readme-ov-file#github-token"}`, n = Error(t);
145
155
  throw n.name = "GitHubRateLimitError", n;
146
156
  }
147
- let h = /* @__PURE__ */ new Map();
148
- for (let e of m) h.set(e.actionName, {
157
+ let g = /* @__PURE__ */ new Map();
158
+ for (let e of h) g.set(e.actionName, {
149
159
  publishedAt: e.publishedAt,
150
160
  actionName: e.actionName,
151
161
  skipReason: e.skipReason,
@@ -153,23 +163,23 @@ async function checkUpdates(i, o, c) {
153
163
  status: e.status,
154
164
  sha: e.sha
155
165
  });
156
- let g = [];
157
- for (let e of d) {
158
- let t = h.get(e.name);
159
- t ? g.push(createUpdate(e, {
166
+ let _ = [];
167
+ for (let e of f) {
168
+ let t = g.get(e.name);
169
+ t ? _.push(createUpdate(e, {
160
170
  publishedAt: t.publishedAt,
161
171
  version: t.version,
162
172
  sha: t.sha
163
173
  }, {
164
174
  skipReason: t.skipReason,
165
175
  status: t.status
166
- })) : g.push(createUpdate(e, {
176
+ })) : _.push(createUpdate(e, {
167
177
  publishedAt: null,
168
178
  version: null,
169
179
  sha: null
170
180
  }));
171
181
  }
172
- return g;
182
+ return _;
173
183
  }
174
184
  function createUpdate(t, n, i = {}) {
175
185
  let { version: a, sha: c, publishedAt: l } = n, u = t.version ?? "unknown", d = normalizeVersion(u), f = a ? normalizeVersion(a) : null, p = i.status ?? "ok", m = i.skipReason, h = !1, g = !1;
@@ -216,4 +226,7 @@ function isSha(e) {
216
226
  let t = e.replace(/^v/u, "");
217
227
  return /^[0-9a-f]{7,40}$/iu.test(t);
218
228
  }
229
+ function isRateLimitError(e) {
230
+ return e instanceof Error && e.name === "GitHubRateLimitError";
231
+ }
219
232
  export { checkUpdates };
@@ -4,7 +4,8 @@ import { ReleaseInfo } from '../../types/release-info';
4
4
  * Fetch releases for a repository.
5
5
  *
6
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.
7
+ * looks like a SHA; callers can resolve the tag via git refs later when
8
+ * pinning.
8
9
  *
9
10
  * @param context - Client context.
10
11
  * @param parameters - Request parameters.
@@ -2,15 +2,25 @@ import { GitHubClient } from '../../types/github-client';
2
2
  import { UpdateMode } from '../../types/update-mode';
3
3
  import { TagInfo } from '../../types/tag-info';
4
4
  interface GetCompatibleUpdateParameters {
5
- /** Optional in-memory cache for resolved tag SHAs. */
5
+ /**
6
+ * Optional in-memory cache for resolved tag SHAs.
7
+ */
6
8
  shaCache?: Map<string, string | null>;
7
- /** Update mode that limits which tag can be selected. */
9
+ /**
10
+ * Update mode that limits which tag can be selected.
11
+ */
8
12
  mode: Exclude<UpdateMode, 'major'>;
9
- /** Optional in-memory cache for action tags. */
13
+ /**
14
+ * Optional in-memory cache for action tags.
15
+ */
10
16
  tagsCache?: Map<string, TagInfo[]>;
11
- /** Current action version used as compatibility baseline. */
17
+ /**
18
+ * Current action version used as compatibility baseline.
19
+ */
12
20
  currentVersion: string | null;
13
- /** Action name in `owner/repo` format (path suffix is allowed). */
21
+ /**
22
+ * Action name in `owner/repo` format (path suffix is allowed).
23
+ */
14
24
  actionName: string;
15
25
  }
16
26
  /**
@@ -3,9 +3,9 @@ import { ReleaseInfo } from '../../types/release-info';
3
3
  /**
4
4
  * Fetch the latest release for a repository.
5
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.
6
+ * If the latest release does not exist (404), returns null. The commit SHA may
7
+ * be taken from target_commitish when it looks like a SHA; callers can resolve
8
+ * the tag via git refs later when pinning.
9
9
  *
10
10
  * @param context - Client context.
11
11
  * @param owner - Repository owner.
@@ -1,12 +1,20 @@
1
1
  import { GitHubAction } from '../../../types/github-action';
2
2
  interface ExtractUsesOptions {
3
- /** YAML sequence node containing workflow/action steps. */
3
+ /**
4
+ * YAML sequence node containing workflow/action steps.
5
+ */
4
6
  stepsNode: unknown;
5
- /** Path of the file being scanned (for metadata). */
7
+ /**
8
+ * Path of the file being scanned (for metadata).
9
+ */
6
10
  filePath: string;
7
- /** Name of the job containing these steps (for workflows). */
11
+ /**
12
+ * Name of the job containing these steps (for workflows).
13
+ */
8
14
  jobName?: string;
9
- /** Original YAML file content (for line number calculation). */
15
+ /**
16
+ * Original YAML file content (for line number calculation).
17
+ */
10
18
  content: string;
11
19
  }
12
20
  /**
@@ -1,4 +1,6 @@
1
- /** Constants for directory names used in the project. */
1
+ /**
2
+ * Constants for directory names used in the project.
3
+ */
2
4
  export declare const GITHUB_DIRECTORY: ".github";
3
5
  export declare const WORKFLOWS_DIRECTORY: "workflows";
4
6
  export declare const ACTIONS_DIRECTORY: "actions";
@@ -1,6 +1,6 @@
1
1
  import { isYamlFile } from "./is-yaml-file.js";
2
- import { lstat, readdir } from "node:fs/promises";
3
2
  import { join } from "node:path";
3
+ import { lstat, readdir } from "node:fs/promises";
4
4
  async function findYamlFilesRecursive(i) {
5
5
  let a = [], o = /* @__PURE__ */ new Set();
6
6
  async function s(i) {
@@ -1,6 +1,8 @@
1
1
  import { ActionUpdate } from '../../types/action-update';
2
2
  interface PromptUpdateSelectionOptions {
3
- /** Whether to show the Age column. */
3
+ /**
4
+ * Whether to show the Age column.
5
+ */
4
6
  showAge?: boolean;
5
7
  }
6
8
  export declare function promptUpdateSelection(updates: ActionUpdate[], options?: PromptUpdateSelectionOptions): Promise<ActionUpdate[] | null>;
@@ -4,10 +4,10 @@ import { GITHUB_DIRECTORY } from "../constants.js";
4
4
  import { isSha } from "../versions/is-sha.js";
5
5
  import { stripAnsi } from "./strip-ansi.js";
6
6
  import { padString } from "./pad-string.js";
7
+ import path from "node:path";
7
8
  import "node:worker_threads";
8
9
  import pc from "picocolors";
9
10
  import enquirer from "enquirer";
10
- import path from "node:path";
11
11
  var MIN_ACTION_WIDTH = 40, MIN_JOB_WIDTH = 4, MIN_CURRENT_WIDTH = 16, MAX_VERSION_WIDTH = 7;
12
12
  async function promptUpdateSelection(g, v = {}) {
13
13
  let { showAge: y = !1 } = v;
@@ -41,8 +41,8 @@ async function promptUpdateSelection(g, v = {}) {
41
41
  };
42
42
  })), C = [], w = stripAnsi("Action").length, T = stripAnsi("Current").length, E = stripAnsi("Job").length, D = 0, O = !1;
43
43
  for (let [t, a] of b.entries()) {
44
- let o = a.action.name, u = S[t], d = u.display, f = a.action.job ?? "–";
45
- if (w = Math.max(w, o.length), T = Math.max(T, stripAnsi(d).length, u.versionForPadding && u.shortSha ? stripAnsi(`${padString(u.versionForPadding, D + 1)}${pc.gray(`(${u.shortSha})`)}`).length : 0), E = Math.max(E, f.length), a.latestVersion) {
44
+ let o = a.action.name, l = S[t], d = l.display, f = a.action.job ?? "–";
45
+ if (w = Math.max(w, o.length), T = Math.max(T, stripAnsi(d).length, l.versionForPadding && l.shortSha ? stripAnsi(`${padString(l.versionForPadding, D + 1)}${pc.gray(`(${l.shortSha})`)}`).length : 0), E = Math.max(E, f.length), a.latestVersion) {
46
46
  let o = formatVersion(a.latestVersion, S[t]?.effectiveForDiff ?? a.currentVersion);
47
47
  D = Math.max(D, stripAnsi(o).length);
48
48
  }
@@ -56,7 +56,7 @@ async function promptUpdateSelection(g, v = {}) {
56
56
  console.warn(`Unexpected missing group for file: ${a}`);
57
57
  continue;
58
58
  }
59
- let s = [], u = o;
59
+ let s = [], l = o;
60
60
  s.push({
61
61
  current: "Current",
62
62
  action: "Action",
@@ -65,10 +65,10 @@ async function promptUpdateSelection(g, v = {}) {
65
65
  job: "Job",
66
66
  age: "Age"
67
67
  });
68
- for (let { update: t, index: a } of u) {
69
- let o = !!t.latestSha, u = S[a], d = u.display;
70
- u.versionForPadding && u.shortSha && (d = `${padString(u.versionForPadding, M + 1)}${pc.gray(`(${u.shortSha})`)}`);
71
- let f = u.effectiveForDiff ?? t.currentVersion, p = formatVersion(t.latestVersion, f), m = t.action.name;
68
+ for (let { update: t, index: a } of l) {
69
+ let o = !!t.latestSha, l = S[a], d = l.display;
70
+ l.versionForPadding && l.shortSha && (d = `${padString(l.versionForPadding, M + 1)}${pc.gray(`(${l.shortSha})`)}`);
71
+ let f = l.effectiveForDiff ?? t.currentVersion, p = formatVersion(t.latestVersion, f), m = t.action.name;
72
72
  if (t.latestSha) {
73
73
  let i = t.latestSha.slice(0, 7);
74
74
  p = `${padString(p, M + 1)}${pc.gray(`(${i})`)}`;
@@ -101,7 +101,7 @@ async function promptUpdateSelection(g, v = {}) {
101
101
  name: ""
102
102
  });
103
103
  else {
104
- let { update: i, index: a } = u[t - 1], s = !!i.latestSha, c = s && !i.isBreaking;
104
+ let { update: i, index: a } = l[t - 1], s = !!i.latestSha, c = s && !i.isBreaking;
105
105
  _.push({
106
106
  message: o,
107
107
  value: String(a),
@@ -4,18 +4,22 @@ import { GitHubAction } from '../../types/github-action';
4
4
  * object.
5
5
  *
6
6
  * @example
7
- * const action = parseActionReference(
8
- * 'actions/checkout@v3',
9
- * 'workflow.yml',
10
- * 10,
11
- * )
12
- * // Returns: {
13
- * // type: 'external',
14
- * // name: 'actions/checkout',
15
- * // version: 'v3',
16
- * // file: 'workflow.yml',
17
- * // line: 10,
18
- * // }
7
+ *
8
+ * ```ts
9
+ * const action = parseActionReference(
10
+ * 'actions/checkout@v3',
11
+ * 'workflow.yml',
12
+ * 10,
13
+ * )
14
+ * // Returns:
15
+ * // {
16
+ * // type: 'external',
17
+ * // name: 'actions/checkout',
18
+ * // version: 'v3',
19
+ * // file: 'workflow.yml',
20
+ * // line: 10,
21
+ * // }
22
+ * ```
19
23
  *
20
24
  * @param reference - The action reference string to parse. Can be:
21
25
  *
@@ -4,7 +4,10 @@ import { ScanResult } from '../types/scan-result';
4
4
  * actions.
5
5
  *
6
6
  * @example
7
- * const result = await scanGitHubActions('/path/to/repo')
7
+ *
8
+ * ```ts
9
+ * const result = await scanGitHubActions('/path/to/repo')
10
+ * ```
8
11
  *
9
12
  * @param rootPath - The root path of the repository to scan. Defaults to
10
13
  * current working directory.