actions-up 1.11.0 → 1.12.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.
Files changed (51) hide show
  1. package/dist/cli/index.d.ts +3 -1
  2. package/dist/cli/index.js +84 -93
  3. package/dist/cli/merge-scan-results.d.ts +8 -0
  4. package/dist/cli/merge-scan-results.js +18 -0
  5. package/dist/cli/normalize-update-mode.d.ts +8 -0
  6. package/dist/cli/normalize-update-mode.js +6 -0
  7. package/dist/cli/print-mode-warning.d.ts +16 -0
  8. package/dist/cli/print-mode-warning.js +11 -0
  9. package/dist/cli/print-skipped-warning.d.ts +14 -0
  10. package/dist/cli/print-skipped-warning.js +10 -0
  11. package/dist/cli/resolve-scan-directories.d.ts +31 -0
  12. package/dist/cli/resolve-scan-directories.js +24 -0
  13. package/dist/core/api/check-updates.d.ts +4 -1
  14. package/dist/core/api/check-updates.js +119 -116
  15. package/dist/core/api/get-all-releases.d.ts +2 -1
  16. package/dist/core/api/get-compatible-update.d.ts +37 -0
  17. package/dist/core/api/get-compatible-update.js +40 -0
  18. package/dist/core/api/get-latest-release.d.ts +3 -3
  19. package/dist/core/ast/utils/extract-uses-from-steps.d.ts +12 -4
  20. package/dist/core/constants.d.ts +3 -1
  21. package/dist/core/fs/find-yaml-files-recursive.js +1 -1
  22. package/dist/core/interactive/prompt-update-selection.d.ts +3 -1
  23. package/dist/core/interactive/prompt-update-selection.js +9 -9
  24. package/dist/core/parsing/parse-action-reference.d.ts +16 -12
  25. package/dist/core/scan-github-actions.d.ts +4 -1
  26. package/dist/core/scan-github-actions.js +67 -68
  27. package/dist/core/scan-recursive.js +12 -14
  28. package/dist/core/versions/find-compatible-tag.d.ts +16 -0
  29. package/dist/core/versions/find-compatible-tag.js +27 -0
  30. package/dist/core/versions/get-update-level.d.ts +3 -1
  31. package/dist/core/versions/is-semver-like.d.ts +9 -0
  32. package/dist/core/versions/is-semver-like.js +4 -0
  33. package/dist/core/versions/normalize-version.d.ts +14 -0
  34. package/dist/core/versions/normalize-version.js +9 -0
  35. package/dist/package.js +1 -1
  36. package/dist/types/action-update.d.ts +30 -10
  37. package/dist/types/composite-action-runs.d.ts +12 -4
  38. package/dist/types/composite-action-step.d.ts +24 -8
  39. package/dist/types/composite-action-structure.d.ts +21 -7
  40. package/dist/types/github-action.d.ts +27 -9
  41. package/dist/types/github-client-context.d.ts +24 -8
  42. package/dist/types/github-client.d.ts +24 -8
  43. package/dist/types/release-info.d.ts +24 -8
  44. package/dist/types/scan-result.d.ts +12 -4
  45. package/dist/types/tag-info.d.ts +15 -5
  46. package/dist/types/update-mode.d.ts +3 -1
  47. package/dist/types/workflow-job.d.ts +27 -9
  48. package/dist/types/workflow-step.d.ts +21 -7
  49. package/dist/types/workflow-structure.d.ts +15 -5
  50. package/package.json +3 -8
  51. package/readme.md +53 -18
@@ -1,2 +1,4 @@
1
- /** Run the CLI. */
1
+ /**
2
+ * Run the CLI.
3
+ */
2
4
  export declare function run(): void;
package/dist/cli/index.js CHANGED
@@ -1,143 +1,134 @@
1
1
  import { readInlineVersionComment } from "../core/versions/read-inline-version-comment.js";
2
- import { GITHUB_DIRECTORY } from "../core/constants.js";
3
2
  import { isSha } from "../core/versions/is-sha.js";
4
3
  import { promptUpdateSelection } from "../core/interactive/prompt-update-selection.js";
4
+ import { getCompatibleUpdate } from "../core/api/get-compatible-update.js";
5
+ import { createGitHubClient } from "../core/api/create-github-client.js";
6
+ import { resolveScanDirectories } from "./resolve-scan-directories.js";
5
7
  import { getUpdateLevel } from "../core/versions/get-update-level.js";
6
8
  import { applyUpdates } from "../core/ast/update/apply-updates.js";
9
+ import { printSkippedWarning } from "./print-skipped-warning.js";
10
+ import { normalizeUpdateMode } from "./normalize-update-mode.js";
7
11
  import { shouldIgnore } from "../core/ignore/should-ignore.js";
8
12
  import { checkUpdates } from "../core/api/check-updates.js";
13
+ import { mergeScanResults } from "./merge-scan-results.js";
14
+ import { printModeWarning } from "./print-mode-warning.js";
9
15
  import { scanRecursive } from "../core/scan-recursive.js";
10
16
  import { scanGitHubActions } from "../core/scan-github-actions.js";
11
17
  import "../core/index.js";
12
18
  import { version } from "../package.js";
13
- import { relative, resolve } from "node:path";
14
19
  import { createSpinner } from "nanospinner";
15
20
  import "node:worker_threads";
16
21
  import pc from "picocolors";
17
22
  import cac from "cac";
18
23
  function run() {
19
24
  let b = cac("actions-up");
20
- b.help().version(version).option("--dir <directory>", "Custom directory name (default: .github)").option("--dry-run", "Preview changes without applying them").option("--exclude <regex>", "Exclude actions by regex (repeatable)").option("--include-branches", "Also check actions pinned to branches (default: false)").option("--min-age <days>", "Minimum age in days for updates (default: 0)", { default: 0 }).option("--mode <mode>", "Update mode: major, minor, or patch (default: major)", { default: "major" }).option("--recursive, -r", "Recursively scan directories for YAML files").option("--yes, -y", "Skip all confirmations").command("", "Update GitHub Actions").action(async (g) => {
25
+ b.help().version(version).option("--dir <directory>", "Directory to scan (repeatable). Default: .github, or . with --recursive").option("--dry-run", "Preview changes without applying them").option("--exclude <regex>", "Exclude actions by regex (repeatable)").option("--include-branches", "Also check actions pinned to branches (default: false)").option("--min-age <days>", "Minimum age in days for updates (default: 0)", { default: 0 }).option("--mode <mode>", "Update mode: major, minor, or patch (default: major)", { default: "major" }).option("--recursive, -r", "Recursively scan directories for YAML files").option("--yes, -y", "Skip all confirmations").command("", "Update GitHub Actions").action(async (v) => {
21
26
  console.info(pc.cyan("\n🚀 Actions Up!\n"));
22
- let y = createSpinner("Scanning GitHub Actions...").start(), b = [];
23
- Array.isArray(g.dir) ? b.push(...g.dir) : typeof g.dir == "string" ? b.push(g.dir) : b.push(GITHUB_DIRECTORY);
24
- let x = [...new Set(b.map((e) => {
25
- let d = process.cwd();
26
- return relative(d, resolve(d, e)) || ".";
27
- }))];
27
+ let y = createSpinner("Scanning GitHub Actions...").start(), b = resolveScanDirectories({
28
+ recursive: v.recursive,
29
+ cwd: process.cwd(),
30
+ dir: v.dir
31
+ });
28
32
  try {
29
- let d = mergeScanResults(g.recursive ? await Promise.all(x.map((e) => scanRecursive(process.cwd(), e))) : await Promise.all(x.map((e) => scanGitHubActions(process.cwd(), e)))), _ = d.actions.length, v = d.workflows.size, b = d.compositeActions.size;
30
- if (y.success(`Found ${pc.yellow(_)} actions in ${pc.yellow(v)} workflows and ${pc.yellow(b)} composite actions`), _ === 0) {
33
+ let m = mergeScanResults(v.recursive ? await Promise.all(b.map(({ root: t, dir: u }) => scanRecursive(t, u))) : await Promise.all(b.map(({ root: t, dir: u }) => scanGitHubActions(t, u)))), x = m.actions.length, S = m.workflows.size, C = m.compositeActions.size;
34
+ if (y.success(`Found ${pc.yellow(x)} actions in ${pc.yellow(S)} workflows and ${pc.yellow(C)} composite actions`), x === 0) {
31
35
  console.info(pc.green("\n✨ No GitHub Actions found in this repository"));
32
36
  return;
33
37
  }
34
- let S = d.actions, C = [];
35
- Array.isArray(g.exclude) ? C.push(...g.exclude) : typeof g.exclude == "string" && C.push(g.exclude);
36
- let w = C.flatMap((e) => e.split(",")).map((e) => e.trim()).filter(Boolean);
37
- if (w.length > 0) {
38
- let { parseExcludePatterns: e } = await import("../core/filters/parse-exclude-patterns.js"), d = e(w);
39
- d.length > 0 && (S = S.filter((e) => {
40
- let { name: f } = e;
41
- for (let e of d) if (e.test(f)) return !1;
38
+ let w = m.actions, T = [];
39
+ Array.isArray(v.exclude) ? T.push(...v.exclude) : typeof v.exclude == "string" && T.push(v.exclude);
40
+ let E = T.flatMap((t) => t.split(",")).map((t) => t.trim()).filter(Boolean);
41
+ if (E.length > 0) {
42
+ let { parseExcludePatterns: t } = await import("../core/filters/parse-exclude-patterns.js"), u = t(E);
43
+ u.length > 0 && (w = w.filter((t) => {
44
+ let { name: d } = t;
45
+ for (let t of u) if (t.test(d)) return !1;
42
46
  return !0;
43
47
  }));
44
48
  }
45
- if (y = createSpinner("Checking for updates...").start(), S.length === 0) {
49
+ if (y = createSpinner("Checking for updates...").start(), w.length === 0) {
46
50
  y.success("No actions to check after excludes"), console.info(pc.green("\n✨ Nothing to check after excludes\n"));
47
51
  return;
48
52
  }
49
- let T = g.includeBranches ?? !1, E = await checkUpdates(S, process.env.GITHUB_TOKEN, { includeBranches: T }), D = [];
50
- await Promise.all(E.map(async (e) => {
51
- await shouldIgnore(e.action.file, e.action.line) || D.push(e);
53
+ let D = process.env.GITHUB_TOKEN, O = createGitHubClient(D), k = v.includeBranches ?? !1, A = await checkUpdates(w, D, {
54
+ client: O,
55
+ includeBranches: k
56
+ }), j = [];
57
+ await Promise.all(A.map(async (t) => {
58
+ await shouldIgnore(t.action.file, t.action.line) || j.push(t);
52
59
  }));
53
- let O = D.filter((e) => e.status === "skipped"), k = D.filter((e) => e.hasUpdate), A = g.minAge * 24 * 60 * 60 * 1e3, j = Date.now();
54
- k = k.filter((e) => e.publishedAt ? j - e.publishedAt.getTime() >= A : !0);
55
- let M = normalizeUpdateMode(g.mode), N = [];
56
- if (M !== "major") {
57
- let d = /* @__PURE__ */ new Map(), p = await Promise.all(k.map(async (p) => {
58
- let m = p.currentVersion;
59
- if (isSha(p.currentVersion)) {
60
- let f = await readInlineVersionComment(p.action.file, p.action.line, d);
61
- f && (m = f);
60
+ let M = j.filter((t) => t.status === "skipped"), N = j.filter((t) => t.hasUpdate), P = v.minAge * 24 * 60 * 60 * 1e3, F = Date.now();
61
+ N = N.filter((t) => t.publishedAt ? F - t.publishedAt.getTime() >= P : !0);
62
+ let I = normalizeUpdateMode(v.mode), L = [];
63
+ if (I !== "major") {
64
+ let d = /* @__PURE__ */ new Map(), p = /* @__PURE__ */ new Map(), m = /* @__PURE__ */ new Map(), h = await Promise.all(N.map(async (d) => {
65
+ let f = d.currentVersion;
66
+ if (isSha(d.currentVersion)) {
67
+ let u = await readInlineVersionComment(d.action.file, d.action.line, m);
68
+ u && (f = u);
62
69
  }
63
- let h = getUpdateLevel(m, p.latestVersion);
70
+ let p = getUpdateLevel(f, d.latestVersion);
64
71
  return {
65
- allowed: M === "minor" ? h === "minor" || h === "patch" || h === "none" : h === "patch" || h === "none",
66
- update: p
72
+ effectiveCurrentVersion: f,
73
+ allowed: I === "minor" ? p === "minor" || p === "patch" || p === "none" : p === "patch" || p === "none",
74
+ update: d
67
75
  };
68
- })), m = [];
69
- for (let e of p) e.allowed ? m.push(e.update) : N.push(e.update);
70
- k = m;
76
+ })), g = [], _ = await Promise.all(h.map(async (t) => {
77
+ if (t.allowed) return { update: t.update };
78
+ let u = await getCompatibleUpdate(O, {
79
+ currentVersion: t.effectiveCurrentVersion,
80
+ actionName: t.update.action.name,
81
+ tagsCache: d,
82
+ shaCache: p,
83
+ mode: I
84
+ });
85
+ return u ? { update: {
86
+ ...t.update,
87
+ latestVersion: u.version,
88
+ latestSha: u.sha,
89
+ isBreaking: !1,
90
+ hasUpdate: !0
91
+ } } : { blocked: t.update };
92
+ }));
93
+ for (let t of _) {
94
+ if (t.update) {
95
+ g.push(t.update);
96
+ continue;
97
+ }
98
+ L.push(t.blocked);
99
+ }
100
+ N = g;
71
101
  }
72
- let P = k.filter((e) => e.isBreaking);
73
- if (k.length === 0) {
74
- y.success("All actions are up to date!"), O.length > 0 && printSkippedWarning(O, T), N.length > 0 && printModeWarning(N, M), console.info(pc.green("\n✨ Everything is already at the latest version!\n"));
102
+ let R = N.filter((t) => t.isBreaking);
103
+ if (N.length === 0) {
104
+ y.success("All actions are up to date!"), M.length > 0 && printSkippedWarning(M, k), L.length > 0 && printModeWarning(L, I), console.info(pc.green("\n✨ Everything is already at the latest version!\n"));
75
105
  return;
76
106
  }
77
- if (y.success(`Found ${pc.yellow(k.length)} updates available${P.length > 0 ? ` (${pc.redBright(P.length)} breaking)` : ""}`), O.length > 0 && printSkippedWarning(O, T), N.length > 0 && printModeWarning(N, M), g.dryRun) {
107
+ if (y.success(`Found ${pc.yellow(N.length)} updates available${R.length > 0 ? ` (${pc.redBright(R.length)} breaking)` : ""}`), M.length > 0 && printSkippedWarning(M, k), L.length > 0 && printModeWarning(L, I), v.dryRun) {
78
108
  console.info(pc.yellow("\n📋 Dry Run - No changes will be made\n"));
79
- for (let e of k) console.info(`${pc.cyan(e.action.file ?? "unknown")}:\n${e.action.name}: ${pc.redBright(e.currentVersion)} → ${pc.green(e.latestVersion)} ${e.latestSha ? pc.gray(`(${e.latestSha.slice(0, 7)})`) : ""}\n`);
80
- console.info(pc.gray(`\n${k.length} actions would be updated\n`));
109
+ for (let t of N) console.info(`${pc.cyan(t.action.file ?? "unknown")}:\n${t.action.name}: ${pc.redBright(t.currentVersion)} → ${pc.green(t.latestVersion)} ${t.latestSha ? pc.gray(`(${t.latestSha.slice(0, 7)})`) : ""}\n`);
110
+ console.info(pc.gray(`\n${N.length} actions would be updated\n`));
81
111
  return;
82
112
  }
83
- if (g.yes) {
84
- let e = k.filter((e) => e.latestSha);
85
- if (e.length === 0) {
113
+ if (v.yes) {
114
+ let t = N.filter((t) => t.latestSha);
115
+ if (t.length === 0) {
86
116
  console.info(pc.yellow("\n⚠️ No actions with SHA available for update\n"));
87
117
  return;
88
118
  }
89
- console.info(pc.yellow(`\n🔄 Updating ${e.length} actions...\n`)), await applyUpdates(e), console.info(pc.green("\n✓ Updates applied successfully!"));
119
+ console.info(pc.yellow(`\n🔄 Updating ${t.length} actions...\n`)), await applyUpdates(t), console.info(pc.green("\n✓ Updates applied successfully!"));
90
120
  } else {
91
- (O.length > 0 || N.length > 0) && console.info("");
92
- let e = await promptUpdateSelection(k, { showAge: g.minAge > 0 });
93
- if (!e || e.length === 0) {
121
+ (M.length > 0 || L.length > 0) && console.info("");
122
+ let t = await promptUpdateSelection(N, { showAge: v.minAge > 0 });
123
+ if (!t || t.length === 0) {
94
124
  console.info(pc.gray("\nNo updates applied"));
95
125
  return;
96
126
  }
97
- console.info(pc.yellow(`\n🔄 Updating ${e.length} selected actions...\n`)), await applyUpdates(e), console.info(pc.green("\n✓ Updates applied successfully!"));
127
+ console.info(pc.yellow(`\n🔄 Updating ${t.length} selected actions...\n`)), await applyUpdates(t), console.info(pc.green("\n✓ Updates applied successfully!"));
98
128
  }
99
- } catch (e) {
100
- y.error("Failed"), e instanceof Error && e.name === "GitHubRateLimitError" ? (console.error(pc.yellow("\n⚠️ Rate Limit Exceeded\n")), console.error(e.message), console.error(pc.gray("\nExample: GITHUB_TOKEN=ghp_xxxx actions-up\n"))) : console.error(pc.redBright("\nError:"), e instanceof Error ? e.message : String(e)), process.exit(1);
129
+ } catch (t) {
130
+ y.error("Failed"), t instanceof Error && t.name === "GitHubRateLimitError" ? (console.error(pc.yellow("\n⚠️ Rate Limit Exceeded\n")), console.error(t.message), console.error(pc.gray("\nExample: GITHUB_TOKEN=ghp_xxxx actions-up\n"))) : console.error(pc.redBright("\nError:"), t instanceof Error ? t.message : String(t)), process.exit(1);
101
131
  }
102
132
  }), b.parse();
103
133
  }
104
- function mergeScanResults(e) {
105
- let d = {
106
- compositeActions: /* @__PURE__ */ new Map(),
107
- workflows: /* @__PURE__ */ new Map(),
108
- actions: []
109
- };
110
- for (let f of e) {
111
- for (let [e, p] of f.workflows) d.workflows.set(e, p);
112
- for (let [, e] of f.compositeActions) d.compositeActions.set(e, e);
113
- d.actions.push(...f.actions);
114
- }
115
- let f = /* @__PURE__ */ new Set();
116
- return d.actions = d.actions.filter((e) => {
117
- let d = `${e.file}:${e.line}:${e.name}:${e.version}`;
118
- return f.has(d) ? !1 : (f.add(d), !0);
119
- }), d;
120
- }
121
- function printModeWarning(e, d) {
122
- if (e.length === 0) return;
123
- let f = new Intl.PluralRules("en-US", { type: "cardinal" }).select(e.length) === "one" ? "action" : "actions", p = d === "minor" ? "major" : "major/minor";
124
- console.info(pc.yellow(`\n⚠️ Skipped ${e.length} ${f} due to ${p} updates`));
125
- for (let d of e) {
126
- let e = d.action.uses ?? `${d.action.name}@${d.currentVersion ?? "unknown"}`;
127
- console.info(pc.gray(` • ${e}`));
128
- }
129
- }
130
- function printSkippedWarning(e, d) {
131
- let f = new Intl.PluralRules("en-US", { type: "cardinal" }).select(e.length) === "one" ? "action" : "actions", p = d ? "" : " (use --include-branches to check them)";
132
- console.info(pc.yellow(`\n⚠️ Skipped ${e.length} ${f} pinned to branches${p}`));
133
- for (let d of e) {
134
- let e = d.action.uses ?? `${d.action.name}@${d.currentVersion ?? "unknown"}`;
135
- console.info(pc.gray(` • ${e}`));
136
- }
137
- }
138
- function normalizeUpdateMode(e) {
139
- let d = (e ?? "major").toLowerCase();
140
- if (d === "major" || d === "minor" || d === "patch") return d;
141
- throw Error(`Invalid mode "${e}". Expected "major", "minor", or "patch".`);
142
- }
143
134
  export { run };
@@ -0,0 +1,8 @@
1
+ import { ScanResult } from '../types/scan-result';
2
+ /**
3
+ * Merge multiple scan results into one.
4
+ *
5
+ * @param results - Array of scan results.
6
+ * @returns Merged scan result.
7
+ */
8
+ export declare function mergeScanResults(results: ScanResult[]): ScanResult;
@@ -0,0 +1,18 @@
1
+ function mergeScanResults(e) {
2
+ let t = {
3
+ compositeActions: /* @__PURE__ */ new Map(),
4
+ workflows: /* @__PURE__ */ new Map(),
5
+ actions: []
6
+ };
7
+ for (let [n, r] of e.entries()) {
8
+ for (let [e, i] of r.workflows) t.workflows.set(`${n}:${e}`, i);
9
+ for (let [, e] of r.compositeActions) t.compositeActions.set(`${n}:${e}`, e);
10
+ t.actions.push(...r.actions);
11
+ }
12
+ let n = /* @__PURE__ */ new Set();
13
+ return t.actions = t.actions.filter((e) => {
14
+ let t = `${e.file}:${e.line}:${e.name}:${e.version}`;
15
+ return n.has(t) ? !1 : (n.add(t), !0);
16
+ }), t;
17
+ }
18
+ export { mergeScanResults };
@@ -0,0 +1,8 @@
1
+ import { UpdateMode } from '../types/update-mode';
2
+ /**
3
+ * Normalizes the update mode option.
4
+ *
5
+ * @param mode - Raw mode option.
6
+ * @returns Normalized update mode.
7
+ */
8
+ export declare function normalizeUpdateMode(mode: undefined | string): UpdateMode;
@@ -0,0 +1,6 @@
1
+ function normalizeUpdateMode(e) {
2
+ let t = (e ?? "major").toLowerCase();
3
+ if (t === "major" || t === "minor" || t === "patch") return t;
4
+ throw Error(`Invalid mode "${e}". Expected "major", "minor", or "patch".`);
5
+ }
6
+ export { normalizeUpdateMode };
@@ -0,0 +1,16 @@
1
+ import { UpdateMode } from '../types/update-mode';
2
+ /**
3
+ * Prints a warning message for actions that were skipped due to update mode
4
+ * restrictions.
5
+ *
6
+ * @param blocked - Array of blocked actions with their current versions.
7
+ * @param mode - The current update mode (patch/minor/major).
8
+ */
9
+ export declare function printModeWarning(blocked: {
10
+ action: {
11
+ version?: string | null;
12
+ uses?: string;
13
+ name: string;
14
+ };
15
+ currentVersion: string | null;
16
+ }[], mode: UpdateMode): void;
@@ -0,0 +1,11 @@
1
+ import pc from "picocolors";
2
+ function printModeWarning(t, n) {
3
+ if (t.length === 0) return;
4
+ let r = new Intl.PluralRules("en-US", { type: "cardinal" }).select(t.length) === "one" ? "action" : "actions", i = n === "minor" ? "major" : "major/minor";
5
+ console.info(pc.yellow(`\n⚠️ Skipped ${t.length} ${r} due to ${i} updates`));
6
+ for (let n of t) {
7
+ let t = n.action.uses ?? `${n.action.name}@${n.currentVersion ?? "unknown"}`;
8
+ console.info(pc.gray(` • ${t}`));
9
+ }
10
+ }
11
+ export { printModeWarning };
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Prints a warning message for actions that were skipped during scanning.
3
+ *
4
+ * @param skipped - Array of skipped actions with their current versions.
5
+ * @param includeBranches - Whether branch-pinned actions are being checked.
6
+ */
7
+ export declare function printSkippedWarning(skipped: {
8
+ action: {
9
+ version?: string | null;
10
+ uses?: string;
11
+ name: string;
12
+ };
13
+ currentVersion: string | null;
14
+ }[], includeBranches: boolean): void;
@@ -0,0 +1,10 @@
1
+ import pc from "picocolors";
2
+ function printSkippedWarning(t, n) {
3
+ let r = new Intl.PluralRules("en-US", { type: "cardinal" }).select(t.length) === "one" ? "action" : "actions", i = n ? "" : " (use --include-branches to check them)";
4
+ console.info(pc.yellow(`\n⚠️ Skipped ${t.length} ${r} pinned to branches${i}`));
5
+ for (let n of t) {
6
+ let t = n.action.uses ?? `${n.action.name}@${n.currentVersion ?? "unknown"}`;
7
+ console.info(pc.gray(` • ${t}`));
8
+ }
9
+ }
10
+ export { printSkippedWarning };
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Options for resolving scan directories from CLI flags.
3
+ */
4
+ interface ResolveScanDirectoriesOptions {
5
+ dir?: string[] | string;
6
+ recursive?: boolean;
7
+ cwd: string;
8
+ }
9
+ /**
10
+ * Resolved directory with root and relative directory.
11
+ */
12
+ interface ResolvedDirectory {
13
+ root: string;
14
+ dir: string;
15
+ }
16
+ /**
17
+ * Resolve directories to scan from CLI options.
18
+ *
19
+ * Defaults:
20
+ *
21
+ * - Non-recursive mode: `.github`
22
+ * - Recursive mode without --dir: `.`.
23
+ *
24
+ * @param options - CLI options used to compute scan directories.
25
+ * @param options.cwd - Current working directory.
26
+ * @param options.dir - Optional directory flag value(s).
27
+ * @param options.recursive - Whether recursive mode is enabled.
28
+ * @returns Unique resolved directories.
29
+ */
30
+ export declare function resolveScanDirectories(options: ResolveScanDirectoriesOptions): ResolvedDirectory[];
31
+ export {};
@@ -0,0 +1,24 @@
1
+ import { GITHUB_DIRECTORY } from "../core/constants.js";
2
+ import { basename, dirname, isAbsolute, relative, resolve } from "node:path";
3
+ function resolveScanDirectories(o) {
4
+ let { recursive: s, cwd: c, dir: l } = o, u = [];
5
+ Array.isArray(l) ? u.push(...l) : typeof l == "string" ? u.push(l) : s ? u.push(".") : u.push(GITHUB_DIRECTORY);
6
+ let d = /* @__PURE__ */ new Set(), f = [];
7
+ for (let e of u) {
8
+ let o = resolve(c, e), l = relative(c, o), u = l.startsWith("..") || isAbsolute(l) || resolve(c, l) !== o, p;
9
+ p = s ? {
10
+ root: o,
11
+ dir: "."
12
+ } : u ? {
13
+ dir: basename(o),
14
+ root: dirname(o)
15
+ } : {
16
+ dir: l || ".github",
17
+ root: c
18
+ };
19
+ let m = `${p.root}\0${p.dir}`;
20
+ d.has(m) || (d.add(m), f.push(p));
21
+ }
22
+ return f;
23
+ }
24
+ export { resolveScanDirectories };
@@ -1,3 +1,4 @@
1
+ import { GitHubClient } from '../../types/github-client';
1
2
  import { GitHubAction } from '../../types/github-action';
2
3
  import { ActionUpdate } from '../../types/action-update';
3
4
  /**
@@ -5,9 +6,11 @@ import { ActionUpdate } from '../../types/action-update';
5
6
  *
6
7
  * @param actions - Array of GitHub Actions to check.
7
8
  * @param token - Optional GitHub token for authentication.
8
- * @param options - Additional options (e.g., include branch refs).
9
+ * @param options - Additional options (e.g., include branch refs, shared
10
+ * client).
9
11
  * @returns Array of update information.
10
12
  */
11
13
  export declare function checkUpdates(actions: GitHubAction[], token?: string, options?: {
12
14
  includeBranches?: boolean;
15
+ client?: GitHubClient;
13
16
  }): Promise<ActionUpdate[]>;