actions-up 1.14.2 → 1.15.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.
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Options for anchoring directory inputs at the repository root.
3
+ */
4
+ interface AnchorDirectoryInputsOptions {
5
+ /**
6
+ * Raw --dir value(s), or undefined when the flag was not provided.
7
+ */
8
+ dir?: string[] | string;
9
+ /**
10
+ * Repository root detected by walking up, or null when none was found.
11
+ */
12
+ root: string | null;
13
+ /**
14
+ * Current working directory the CLI was invoked from.
15
+ */
16
+ cwd: string;
17
+ }
18
+ /**
19
+ * Anchors relative scan directories at the detected repository root.
20
+ *
21
+ * When the CLI runs from a nested subdirectory, the default `.github` and any
22
+ * simple relative `--dir` values are resolved against the repository root
23
+ * instead of the current directory. Absolute and parent-relative (`..`) values
24
+ * are intentional and left untouched.
25
+ *
26
+ * @param options - Detected root, current directory, and raw --dir value(s).
27
+ * @returns The directory input(s) to feed into resolveScanDirectories.
28
+ */
29
+ export declare function anchorDirectoryInputs(options: AnchorDirectoryInputsOptions): undefined | string[] | string;
30
+ export {};
@@ -0,0 +1,13 @@
1
+ import { GITHUB_DIRECTORY as e } from "../core/constants.js";
2
+ import { isAbsolute as t, join as n } from "node:path";
3
+ function r(r) {
4
+ let { root: i, cwd: a, dir: o } = r;
5
+ if (!i || i === a) return o;
6
+ let s = [];
7
+ return Array.isArray(o) ? s.push(...o) : typeof o == "string" && s.push(o), s.length === 0 && (s = ["."]), s.map((r) => {
8
+ if (t(r) || r.startsWith("..")) return r;
9
+ let a = n(i, r);
10
+ return a === i ? n(i, e) : a;
11
+ });
12
+ }
13
+ export { r as anchorDirectoryInputs };
package/dist/cli/index.js CHANGED
@@ -6,203 +6,218 @@ import { getCompatibleUpdate as i } from "../core/api/get-compatible-update.js";
6
6
  import { createGitHubClient as a } from "../core/api/create-github-client.js";
7
7
  import { resolveScanDirectories as o } from "./resolve-scan-directories.js";
8
8
  import { getUpdateLevel as s } from "../core/versions/get-update-level.js";
9
- import { applyUpdates as c } from "../core/ast/update/apply-updates.js";
10
- import { normalizeUpdateStyle as l } from "./normalize-update-style.js";
11
- import { printSkippedWarning as u } from "./print-skipped-warning.js";
12
- import { normalizeUpdateMode as d } from "./normalize-update-mode.js";
13
- import { validateCliOptions as f } from "./validate-cli-options.js";
14
- import { shouldIgnore as p } from "../core/ignore/should-ignore.js";
15
- import { checkUpdates as m } from "../core/api/check-updates.js";
16
- import { mergeScanResults as h } from "./merge-scan-results.js";
17
- import { printModeWarning as g } from "./print-mode-warning.js";
18
- import { scanRecursive as _ } from "../core/scan-recursive.js";
19
- import { buildJsonReport as v } from "./build-json-report.js";
20
- import { scanGitHubActions as y } from "../core/scan-github-actions.js";
9
+ import { anchorDirectoryInputs as c } from "./anchor-directory-inputs.js";
10
+ import { applyUpdates as l } from "../core/ast/update/apply-updates.js";
11
+ import { normalizeUpdateStyle as u } from "./normalize-update-style.js";
12
+ import { printSkippedWarning as d } from "./print-skipped-warning.js";
13
+ import { normalizeUpdateMode as f } from "./normalize-update-mode.js";
14
+ import { validateCliOptions as p } from "./validate-cli-options.js";
15
+ import { shouldIgnore as m } from "../core/ignore/should-ignore.js";
16
+ import { findRepoRoot as h } from "../core/fs/find-repo-root.js";
17
+ import { checkUpdates as g } from "../core/api/check-updates.js";
18
+ import { mergeScanResults as _ } from "./merge-scan-results.js";
19
+ import { printModeWarning as v } from "./print-mode-warning.js";
20
+ import { scanRecursive as y } from "../core/scan-recursive.js";
21
+ import { buildJsonReport as b } from "./build-json-report.js";
22
+ import { parseArguments as x } from "./parse-arguments.js";
23
+ import { scanGitHubActions as S } from "../core/scan-github-actions.js";
21
24
  import "../core/index.js";
22
- import { version as b } from "../package.js";
23
- import { createSpinner as x } from "nanospinner";
24
- import { resolve as S } from "node:path";
25
+ import { version as C } from "../package.js";
26
+ import { createSpinner as w } from "nanospinner";
27
+ import { resolve as T } from "node:path";
25
28
  import "node:worker_threads";
26
- import C from "picocolors";
27
- import w from "cac";
28
- function T() {
29
- let T = w("actions-up");
30
- T.help().version(b).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("--json", "Output update information as machine-readable JSON").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("--style <style>", "Update style: sha or preserve (default: sha)", { default: "sha" }).option("--recursive, -r", "Recursively scan directories for YAML files").option("--yes, -y", "Skip all confirmations").command("", "Update GitHub Actions").action(async (b) => {
31
- let w = b.json ?? !1, T = null, E = o({
32
- recursive: b.recursive,
33
- cwd: process.cwd(),
34
- dir: b.dir
35
- }), D = E.map(({ root: e, dir: t }) => S(e, t)), O = b.includeBranches ?? !1, k = d(b.mode), A = l(b.style), j = [];
36
- Array.isArray(b.exclude) ? j.push(...b.exclude) : typeof b.exclude == "string" && j.push(b.exclude);
37
- let M = j.flatMap((e) => e.split(",")).map((e) => e.trim()).filter(Boolean);
38
- try {
39
- f({
40
- yes: b.yes,
41
- json: w
42
- }), w || (console.info(C.cyan("\n🚀 Actions Up!\n")), T = x("Scanning GitHub Actions...").start());
43
- function o({ actionsToCheckCount: e, blockedByMode: t = [], outdated: n = [], skipped: r = [], scanResult: i, status: a }) {
44
- process.stdout.write(`${JSON.stringify(v({
45
- recursive: b.recursive ?? !1,
46
- excludePatterns: M,
47
- directories: D,
48
- minAge: b.minAge,
49
- actionsToCheckCount: e,
50
- includeBranches: O,
51
- blockedByMode: t,
52
- scanResult: i,
53
- outdated: n,
54
- skipped: r,
55
- status: a,
56
- style: A,
57
- mode: k
58
- }), null, 2)}\n`);
59
- }
60
- let l = h(b.recursive ? await Promise.all(E.map(({ root: e, dir: t }) => _(e, t))) : await Promise.all(E.map(({ root: e, dir: t }) => y(e, t)))), d = l.actions.length, S = l.workflows.size, j = l.compositeActions.size;
61
- if (T?.success(`Found ${C.yellow(d)} actions in ${C.yellow(S)} workflows and ${C.yellow(j)} composite actions`), d === 0) {
62
- if (w) {
63
- o({
64
- status: "no-actions-found",
65
- actionsToCheckCount: 0,
66
- scanResult: l
67
- });
68
- return;
69
- }
70
- console.info(C.green("\n✨ No GitHub Actions found in this repository"));
29
+ import E from "picocolors";
30
+ function D() {
31
+ let e = x(process.argv.slice(2), C);
32
+ if (e.kind === "help" || e.kind === "version") {
33
+ console.info(e.text);
34
+ return;
35
+ }
36
+ e.kind === "error" && (console.error(E.redBright("\nError:"), e.message), process.exit(1)), O(e.options);
37
+ }
38
+ async function O(x) {
39
+ let C = x.json ?? !1, D = x.quiet ?? !1, O = null, k = process.cwd(), A = x.recursive ? null : await h(k), j = o({
40
+ dir: c({
41
+ dir: x.dir,
42
+ root: A,
43
+ cwd: k
44
+ }),
45
+ recursive: x.recursive,
46
+ cwd: k
47
+ }), M = j.map(({ root: e, dir: t }) => T(e, t)), N = x.includeBranches ?? !1, P = f(x.mode), F = u(x.style), I = [];
48
+ Array.isArray(x.exclude) ? I.push(...x.exclude) : typeof x.exclude == "string" && I.push(x.exclude);
49
+ let L = I.flatMap((e) => e.split(",")).map((e) => e.trim()).filter(Boolean);
50
+ try {
51
+ p({
52
+ yes: x.yes,
53
+ json: C
54
+ }), C || (console.info(E.cyan("\n🚀 Actions Up!\n")), O = w("Scanning GitHub Actions...").start());
55
+ function o({ actionsToCheckCount: e, blockedByMode: t = [], outdated: n = [], skipped: r = [], scanResult: i, status: a }) {
56
+ process.stdout.write(`${JSON.stringify(b({
57
+ recursive: x.recursive ?? !1,
58
+ excludePatterns: L,
59
+ directories: M,
60
+ minAge: x.minAge,
61
+ actionsToCheckCount: e,
62
+ includeBranches: N,
63
+ blockedByMode: t,
64
+ scanResult: i,
65
+ outdated: n,
66
+ skipped: r,
67
+ status: a,
68
+ style: F,
69
+ mode: P
70
+ }), null, 2)}\n`);
71
+ }
72
+ let c = _(x.recursive ? await Promise.all(j.map(({ root: e, dir: t }) => y(e, t))) : await Promise.all(j.map(({ root: e, dir: t }) => S(e, t)))), u = c.actions.length, f = c.workflows.size, h = c.compositeActions.size;
73
+ if (O?.success(`Found ${E.yellow(u)} actions in ${E.yellow(f)} workflows and ${E.yellow(h)} composite actions`), u === 0) {
74
+ if (C) {
75
+ o({
76
+ status: "no-actions-found",
77
+ actionsToCheckCount: 0,
78
+ scanResult: c
79
+ });
71
80
  return;
72
81
  }
73
- let N = l.actions;
74
- if (M.length > 0) {
75
- let { parseExcludePatterns: e } = await import("../core/filters/parse-exclude-patterns.js"), t = e(M);
76
- t.length > 0 && (N = N.filter((e) => {
77
- let { name: n } = e;
78
- for (let e of t) if (e.test(n)) return !1;
79
- return !0;
80
- }));
81
- }
82
- if (w || (T = x("Checking for updates...").start()), N.length === 0) {
83
- if (T?.success("No actions to check after excludes"), w) {
84
- o({
85
- status: "nothing-to-check",
86
- actionsToCheckCount: 0,
87
- scanResult: l
88
- });
89
- return;
90
- }
91
- console.info(C.green("\n✨ Nothing to check after excludes\n"));
82
+ console.info(E.green("\n✨ No GitHub Actions found in this repository"));
83
+ return;
84
+ }
85
+ let T = c.actions;
86
+ if (L.length > 0) {
87
+ let { parseExcludePatterns: e } = await import("../core/filters/parse-exclude-patterns.js"), t = e(L);
88
+ t.length > 0 && (T = T.filter((e) => {
89
+ let { name: n } = e;
90
+ for (let e of t) if (e.test(n)) return !1;
91
+ return !0;
92
+ }));
93
+ }
94
+ if (C || (O = w("Checking for updates...").start()), T.length === 0) {
95
+ if (O?.success("No actions to check after excludes"), C) {
96
+ o({
97
+ status: "nothing-to-check",
98
+ actionsToCheckCount: 0,
99
+ scanResult: c
100
+ });
92
101
  return;
93
102
  }
94
- let P = process.env.GITHUB_TOKEN, F = a(P), I = await m(N, P, {
95
- client: F,
96
- includeBranches: O,
97
- style: A
98
- }), L = [];
99
- await Promise.all(I.map(async (e) => {
100
- await p(e.action.file, e.action.line) || L.push(e);
101
- }));
102
- let R = L.filter((e) => e.status === "skipped"), z = L.filter((e) => e.hasUpdate), B = b.minAge * 24 * 60 * 60 * 1e3, V = Date.now();
103
- z = z.filter((e) => e.publishedAt ? V - e.publishedAt.getTime() >= B : !0);
104
- let H = [];
105
- if (k !== "major") {
106
- let n = /* @__PURE__ */ new Map(), r = /* @__PURE__ */ new Map(), a = /* @__PURE__ */ new Map(), o = await Promise.all(z.map(async (n) => {
107
- let r = n.currentVersion;
108
- if (t(n.currentVersion)) {
109
- let t = await e(n.action.file, n.action.line, a);
110
- t && (r = t);
111
- }
112
- let i = s(r, n.latestVersion);
113
- return {
114
- effectiveCurrentVersion: r,
115
- allowed: k === "minor" ? i === "minor" || i === "patch" || i === "none" : i === "patch" || i === "none",
116
- update: n
117
- };
118
- })), c = [], l = await Promise.all(o.map(async (e) => {
119
- if (e.allowed) return { update: e.update };
120
- let t = await i(F, {
121
- currentVersion: e.effectiveCurrentVersion,
122
- actionName: e.update.action.name,
123
- tagsCache: n,
124
- shaCache: r,
125
- mode: k
126
- });
127
- return t ? { update: {
128
- ...e.update,
129
- latestVersion: t.version,
130
- latestSha: t.sha,
131
- isBreaking: !1,
132
- hasUpdate: !0
133
- } } : { blocked: e.update };
134
- }));
135
- for (let e of l) {
136
- if (e.update) {
137
- c.push(e.update);
138
- continue;
139
- }
140
- H.push(e.blocked);
103
+ console.info(E.green("\n✨ Nothing to check after excludes\n"));
104
+ return;
105
+ }
106
+ let k = process.env.GITHUB_TOKEN, A = a(k), I = await g(T, k, {
107
+ client: A,
108
+ includeBranches: N,
109
+ style: F
110
+ }), R = [];
111
+ await Promise.all(I.map(async (e) => {
112
+ await m(e.action.file, e.action.line) || R.push(e);
113
+ }));
114
+ let z = R.filter((e) => e.status === "skipped"), B = R.filter((e) => e.hasUpdate), V = x.minAge * 24 * 60 * 60 * 1e3, H = Date.now();
115
+ B = B.filter((e) => e.publishedAt ? H - e.publishedAt.getTime() >= V : !0);
116
+ let U = [];
117
+ if (P !== "major") {
118
+ let n = /* @__PURE__ */ new Map(), r = /* @__PURE__ */ new Map(), a = /* @__PURE__ */ new Map(), o = await Promise.all(B.map(async (n) => {
119
+ let r = n.currentVersion;
120
+ if (t(n.currentVersion)) {
121
+ let t = await e(n.action.file, n.action.line, a);
122
+ t && (r = t);
141
123
  }
142
- z = c;
143
- }
144
- z = z.map((e) => r(e, A));
145
- let U = z.filter((e) => !e.targetRef).map((e) => ({
146
- ...e,
147
- skipReason: "unsupported-style",
148
- status: "skipped",
149
- hasUpdate: !1
124
+ let i = s(r, n.latestVersion), o = (P === "minor" ? [
125
+ "minor",
126
+ "patch",
127
+ "none"
128
+ ] : ["patch", "none"]).includes(i);
129
+ return {
130
+ effectiveCurrentVersion: r,
131
+ allowed: o,
132
+ update: n
133
+ };
134
+ })), c = [], l = await Promise.all(o.map(async (e) => {
135
+ if (e.allowed) return { update: e.update };
136
+ let t = await i(A, {
137
+ currentVersion: e.effectiveCurrentVersion,
138
+ actionName: e.update.action.name,
139
+ tagsCache: n,
140
+ shaCache: r,
141
+ mode: P
142
+ });
143
+ return t ? { update: {
144
+ ...e.update,
145
+ latestVersion: t.version,
146
+ latestSha: t.sha,
147
+ isBreaking: !1,
148
+ hasUpdate: !0
149
+ } } : { blocked: e.update };
150
150
  }));
151
- R.push(...U), z = z.filter((e) => e.targetRef);
152
- let W = z.filter((e) => e.isBreaking);
153
- if (z.length === 0) {
154
- if (T?.success("All actions are up to date!"), w) {
155
- o({
156
- actionsToCheckCount: N.length,
157
- status: "up-to-date",
158
- blockedByMode: H,
159
- scanResult: l,
160
- skipped: R
161
- });
162
- return;
151
+ for (let e of l) {
152
+ if (e.update) {
153
+ c.push(e.update);
154
+ continue;
163
155
  }
164
- R.length > 0 && u(R, O, A), H.length > 0 && g(H, k), console.info(C.green("\n✨ Everything is already at the latest version!\n"));
165
- return;
156
+ U.push(e.blocked);
166
157
  }
167
- if (T?.success(`Found ${C.yellow(z.length)} updates available${W.length > 0 ? ` (${C.redBright(W.length)} breaking)` : ""}`), w) {
158
+ B = c;
159
+ }
160
+ B = B.map((e) => r(e, F));
161
+ let W = B.filter((e) => !e.targetRef).map((e) => ({
162
+ ...e,
163
+ skipReason: "unsupported-style",
164
+ status: "skipped",
165
+ hasUpdate: !1
166
+ }));
167
+ if (z.push(...W), B = B.filter((e) => e.targetRef), B.length === 0) {
168
+ if (O?.success("All actions are up to date!"), C) {
168
169
  o({
169
- actionsToCheckCount: N.length,
170
- status: "updates-available",
171
- blockedByMode: H,
172
- scanResult: l,
173
- outdated: z,
174
- skipped: R
170
+ actionsToCheckCount: T.length,
171
+ status: "up-to-date",
172
+ blockedByMode: U,
173
+ scanResult: c,
174
+ skipped: z
175
175
  });
176
176
  return;
177
177
  }
178
- if (R.length > 0 && u(R, O, A), H.length > 0 && g(H, k), b.dryRun) {
179
- console.info(C.yellow("\n📋 Dry Run - No changes will be made\n"));
180
- for (let e of z) {
181
- let t = e.targetRefStyle === "sha" && e.targetRef ? `${e.latestVersion} ${C.gray(`(${e.targetRef.slice(0, 7)})`)}` : e.targetRef ?? e.latestVersion;
182
- console.info(`${C.cyan(e.action.file ?? "unknown")}:\n${e.action.name}: ${C.redBright(e.currentVersion)} ${C.green(t)}\n`);
183
- }
184
- console.info(C.gray(`\n${z.length} actions would be updated\n`));
178
+ !D && z.length > 0 && d(z, N, F), !D && U.length > 0 && v(U, P), console.info(E.green("\n✨ Everything is already at the latest version!\n"));
179
+ return;
180
+ }
181
+ let G = B.filter((e) => e.isBreaking);
182
+ if (O?.success(`Found ${E.yellow(B.length)} updates available${G.length > 0 ? ` (${E.redBright(G.length)} breaking)` : ""}`), C) {
183
+ o({
184
+ actionsToCheckCount: T.length,
185
+ status: "updates-available",
186
+ blockedByMode: U,
187
+ scanResult: c,
188
+ outdated: B,
189
+ skipped: z
190
+ });
191
+ return;
192
+ }
193
+ if (!D && z.length > 0 && d(z, N, F), !D && U.length > 0 && v(U, P), x.dryRun) {
194
+ console.info(E.yellow("\n📋 Dry Run - No changes will be made\n"));
195
+ for (let e of B) {
196
+ let t = e.targetRefStyle === "sha" && e.targetRef ? `${e.latestVersion} ${E.gray(`(${e.targetRef.slice(0, 7)})`)}` : e.targetRef ?? e.latestVersion;
197
+ console.info(`${E.cyan(e.action.file ?? "unknown")}:\n${e.action.name}: ${E.redBright(e.currentVersion)} → ${E.green(t)}\n`);
198
+ }
199
+ console.info(E.gray(`\n${B.length} actions would be updated\n`));
200
+ return;
201
+ }
202
+ if (x.yes) {
203
+ let e = B.filter((e) => e.targetRef);
204
+ if (e.length === 0) {
205
+ console.info(E.yellow("\n⚠️ No actionable updates available\n"));
185
206
  return;
186
207
  }
187
- if (b.yes) {
188
- let e = z.filter((e) => e.targetRef);
189
- if (e.length === 0) {
190
- console.info(C.yellow("\n⚠️ No actionable updates available\n"));
191
- return;
192
- }
193
- console.info(C.yellow(`\n🔄 Updating ${e.length} actions...\n`)), await c(e), console.info(C.green("\n✓ Updates applied successfully!"));
194
- } else {
195
- (R.length > 0 || H.length > 0) && console.info("");
196
- let e = await n(z, { showAge: b.minAge > 0 });
197
- if (!e || e.length === 0) {
198
- console.info(C.gray("\nNo updates applied"));
199
- return;
200
- }
201
- console.info(C.yellow(`\n🔄 Updating ${e.length} selected actions...\n`)), await c(e), console.info(C.green("\n✓ Updates applied successfully!"));
208
+ console.info(E.yellow(`\n🔄 Updating ${e.length} actions...\n`)), await l(e);
209
+ } else {
210
+ !D && (z.length > 0 || U.length > 0) && console.info("");
211
+ let e = await n(B, { showAge: x.minAge > 0 });
212
+ if (!e || e.length === 0) {
213
+ console.info(E.gray("\nNo updates applied"));
214
+ return;
202
215
  }
203
- } catch (e) {
204
- T?.error("Failed"), e instanceof Error && e.name === "GitHubRateLimitError" ? (console.error(C.yellow("\n⚠️ Rate Limit Exceeded\n")), console.error(e.message), console.error(C.gray("\nExample: GITHUB_TOKEN=ghp_xxxx actions-up\n"))) : console.error(C.redBright("\nError:"), e instanceof Error ? e.message : String(e)), process.exit(1);
216
+ console.info(E.yellow(`\n🔄 Updating ${e.length} selected actions...\n`)), await l(e);
205
217
  }
206
- }), T.parse();
218
+ console.info(E.green("\n✓ Updates applied successfully!"));
219
+ } catch (e) {
220
+ O?.error("Failed"), e instanceof Error && e.name === "GitHubRateLimitError" ? (console.error(E.yellow("\n⚠️ Rate Limit Exceeded\n")), console.error(e.message), console.error(E.gray("\nExample: GITHUB_TOKEN=ghp_xxxx actions-up\n"))) : console.error(E.redBright("\nError:"), e instanceof Error ? e.message : String(e)), process.exit(1);
221
+ }
207
222
  }
208
- export { T as run };
223
+ export { D as run };
@@ -1,6 +1,10 @@
1
1
  function e(e) {
2
2
  let t = (e ?? "major").toLowerCase();
3
- if (t === "major" || t === "minor" || t === "patch") return t;
3
+ if ([
4
+ "major",
5
+ "minor",
6
+ "patch"
7
+ ].includes(t)) return t;
4
8
  throw Error(`Invalid mode "${e}". Expected "major", "minor", or "patch".`);
5
9
  }
6
10
  export { e as normalizeUpdateMode };
@@ -0,0 +1,78 @@
1
+ /**
2
+ * CLI Options.
3
+ */
4
+ export interface CLIOptions {
5
+ /**
6
+ * Regex patterns to exclude actions by name (repeatable).
7
+ */
8
+ exclude?: string[] | string;
9
+ /**
10
+ * Whether to include branch references in update checks.
11
+ */
12
+ includeBranches?: boolean;
13
+ /**
14
+ * Custom directory name (e.g., '.gitea' instead of '.github').
15
+ */
16
+ dir?: string[] | string;
17
+ /**
18
+ * Recursively scan directories for YAML files.
19
+ */
20
+ recursive?: boolean;
21
+ /**
22
+ * Suppress skipped and blocked-update warnings.
23
+ */
24
+ quiet?: boolean;
25
+ /**
26
+ * Preview changes without applying them.
27
+ */
28
+ dryRun: boolean;
29
+ /**
30
+ * Update style (sha or preserve).
31
+ */
32
+ style?: string;
33
+ /**
34
+ * Output a machine-readable JSON report.
35
+ */
36
+ json?: boolean;
37
+ /**
38
+ * Minimum age in days for updates.
39
+ */
40
+ minAge: number;
41
+ /**
42
+ * Update mode (major, minor, patch).
43
+ */
44
+ mode?: string;
45
+ /**
46
+ * Skip all confirmations.
47
+ */
48
+ yes: boolean;
49
+ }
50
+ /**
51
+ * Result of parsing CLI arguments. `kind` discriminates what the caller should
52
+ * do next.
53
+ */
54
+ export type ParseArgumentsResult = {
55
+ options: CLIOptions;
56
+ kind: 'options';
57
+ } | {
58
+ message: string;
59
+ kind: 'error';
60
+ } | {
61
+ kind: 'version';
62
+ text: string;
63
+ } | {
64
+ kind: 'help';
65
+ text: string;
66
+ };
67
+ /**
68
+ * Parse CLI arguments into normalized options.
69
+ *
70
+ * Reproduces the previous cac behavior without its runtime magic: numeric
71
+ * coercion for `--min-age` and the option defaults are applied manually here,
72
+ * and kebab-case flags are mapped to the camelCase option shape.
73
+ *
74
+ * @param argv - Raw arguments, typically `process.argv.slice(2)`.
75
+ * @param appVersion - Version string used for `--version`.
76
+ * @returns A discriminated result describing what the caller should do.
77
+ */
78
+ export declare function parseArguments(argv: string[], appVersion: string): ParseArgumentsResult;
@@ -0,0 +1,81 @@
1
+ import { parseArgs as e } from "node:util";
2
+ var t = "Usage:\n $ actions-up [options]\n\nOptions:\n --dir <directory> Directory to scan (repeatable). Default: .github, or . with --recursive\n --dry-run Preview changes without applying them\n --exclude <regex> Exclude actions by regex (repeatable)\n --include-branches Also check actions pinned to branches (default: false)\n --json Output update information as machine-readable JSON\n --min-age <days> Minimum age in days for updates (default: 0)\n --mode <mode> Update mode: major, minor, or patch (default: major)\n --style <style> Update style: sha or preserve (default: sha)\n -r, --recursive Recursively scan directories for YAML files\n -y, --yes Skip all confirmations\n -q, --quiet Suppress skipped/blocked warnings\n -h, --help Display this message\n -v, --version Display version number", n = {
3
+ exclude: {
4
+ type: "string",
5
+ multiple: !0
6
+ },
7
+ recursive: {
8
+ type: "boolean",
9
+ short: "r"
10
+ },
11
+ version: {
12
+ type: "boolean",
13
+ short: "v"
14
+ },
15
+ "include-branches": { type: "boolean" },
16
+ dir: {
17
+ type: "string",
18
+ multiple: !0
19
+ },
20
+ quiet: {
21
+ type: "boolean",
22
+ short: "q"
23
+ },
24
+ help: {
25
+ type: "boolean",
26
+ short: "h"
27
+ },
28
+ yes: {
29
+ type: "boolean",
30
+ short: "y"
31
+ },
32
+ "dry-run": { type: "boolean" },
33
+ "min-age": { type: "string" },
34
+ style: { type: "string" },
35
+ json: { type: "boolean" },
36
+ mode: { type: "string" }
37
+ };
38
+ function r(r, i) {
39
+ try {
40
+ let { values: a } = e({
41
+ allowPositionals: !1,
42
+ options: n,
43
+ strict: !0,
44
+ args: r
45
+ });
46
+ if (a.help) return {
47
+ text: t,
48
+ kind: "help"
49
+ };
50
+ if (a.version) return {
51
+ text: `actions-up/${i} ${process.platform}-${process.arch} node-${process.version}`,
52
+ kind: "version"
53
+ };
54
+ let o = a["min-age"], s = o === void 0 ? 0 : Number(o);
55
+ return !Number.isFinite(s) || s < 0 ? {
56
+ message: `Invalid --min-age "${o}". Expected a non-negative number.`,
57
+ kind: "error"
58
+ } : {
59
+ options: {
60
+ includeBranches: a["include-branches"],
61
+ dryRun: a["dry-run"] ?? !1,
62
+ style: a.style ?? "sha",
63
+ mode: a.mode ?? "major",
64
+ recursive: a.recursive,
65
+ yes: a.yes ?? !1,
66
+ exclude: a.exclude,
67
+ quiet: a.quiet,
68
+ json: a.json,
69
+ dir: a.dir,
70
+ minAge: s
71
+ },
72
+ kind: "options"
73
+ };
74
+ } catch (e) {
75
+ return {
76
+ message: e.message,
77
+ kind: "error"
78
+ };
79
+ }
80
+ }
81
+ export { r as parseArguments };
@@ -3,173 +3,183 @@ import { preserveTagFormat as t } from "../versions/preserve-tag-format.js";
3
3
  import { normalizeVersion as n } from "../versions/normalize-version.js";
4
4
  import { createGitHubClient as r } from "./create-github-client.js";
5
5
  import i from "semver";
6
- async function a(t, a, s) {
7
- let d = s?.client ?? r(a), f = s?.includeBranches ?? !1, p = s?.style ?? "sha", m = t.filter((e) => e.type === "external" || e.type === "reusable-workflow");
8
- if (m.length === 0) return [];
9
- let h = /* @__PURE__ */ new Map();
10
- for (let e of m) {
11
- let t = h.get(e.name) ?? [];
12
- t.push(e), h.set(e.name, t);
6
+ var a = class extends Error {
7
+ constructor(e, t) {
8
+ super(e, t), this.name = "GitHubRateLimitError";
13
9
  }
14
- let g = {
10
+ };
11
+ async function o(t, o, c) {
12
+ let f = c?.client ?? r(o), p = c?.includeBranches ?? !1, m = c?.style ?? "sha", h = t.filter((e) => e.type === "external" || e.type === "reusable-workflow");
13
+ if (h.length === 0) return [];
14
+ let g = /* @__PURE__ */ new Map();
15
+ for (let e of h) {
16
+ let t = g.get(e.name) ?? [];
17
+ t.push(e), g.set(e.name, t);
18
+ }
19
+ let _ = {
15
20
  rateLimitError: null,
16
21
  rateLimitHit: !1
17
- }, _ = await [...h.keys()].reduce((t, r) => t.then(async (t) => {
18
- if (g.rateLimitHit) return [...t, {
22
+ }, v = [];
23
+ async function y(t) {
24
+ if (_.rateLimitHit) return {
19
25
  currentRefType: "unknown",
20
26
  publishedAt: null,
21
27
  version: null,
22
- actionName: r,
28
+ actionName: t,
23
29
  sha: null
24
- }];
25
- let a = r.split("/");
26
- if (a.length < 2) return [...t, {
30
+ };
31
+ let r = t.split("/");
32
+ if (r.length < 2) return {
27
33
  currentRefType: "unknown",
28
34
  publishedAt: null,
29
35
  version: null,
30
- actionName: r,
36
+ actionName: t,
31
37
  sha: null
32
- }];
33
- let [o, s] = a;
34
- if (!o || !s) return [...t, {
38
+ };
39
+ let [a, o] = r;
40
+ if (!a || !o) return {
35
41
  currentRefType: "unknown",
36
42
  publishedAt: null,
37
43
  version: null,
38
- actionName: r,
44
+ actionName: t,
39
45
  sha: null
40
- }];
46
+ };
41
47
  try {
42
- let a = h.get(r)[0]?.version, p = l(a);
43
- if (a && !c(a) && !e(a)) {
44
- let e = await d.getRefType(o, s, a);
45
- if (p = e === "branch" || e === "tag" ? e : p, e === "branch" && !f) return [...t, {
46
- currentRefType: p,
48
+ let r = g.get(t)[0]?.version, s = u(r);
49
+ if (r && !l(r) && !e(r)) {
50
+ let e = await f.getRefType(a, o, r);
51
+ if (s = e === "branch" || e === "tag" ? e : s, e === "branch" && !p) return {
52
+ currentRefType: s,
47
53
  skipReason: "branch",
48
54
  status: "skipped",
49
55
  publishedAt: null,
50
56
  version: null,
51
- actionName: r,
57
+ actionName: t,
52
58
  sha: null
53
- }];
59
+ };
54
60
  }
55
- let m = await d.getLatestRelease(o, s);
56
- if (!m) {
57
- let e = await d.getAllReleases(o, s, 1);
58
- m = e.find((e) => !e.isPrerelease) ?? e[0] ?? null;
61
+ let c = await f.getLatestRelease(a, o);
62
+ if (!c) {
63
+ let e = await f.getAllReleases(a, o, 1);
64
+ c = e.find((e) => !e.isPrerelease) ?? e[0] ?? null;
59
65
  }
60
- if (m) {
61
- let { publishedAt: a, version: c, sha: l } = m, f = !1;
66
+ if (c) {
67
+ let { publishedAt: r, version: l, sha: u } = c, p = !1;
62
68
  {
63
- let t = n(c), r = !!(c && c.trim() !== ""), a = r && /^v?\d+$/u.test(c.trim()), o = i.valid(t);
64
- f = !r || a || !o || !e(c);
69
+ let t = n(l), r = !!(l && l.trim() !== ""), a = r && /^v?\d+$/u.test(l.trim()), o = i.valid(t);
70
+ p = !r || a || !o || !e(l);
65
71
  }
66
- if (f) {
67
- let a = await d.getAllTags(o, s, 30);
68
- if (a.length > 0) {
69
- let l = a.filter((t) => e(t.tag)).map((e) => ({
72
+ if (p) {
73
+ let r = await f.getAllTags(a, o, 30);
74
+ if (r.length > 0) {
75
+ let c = r.filter((t) => e(t.tag)).map((e) => ({
70
76
  v: i.valid(n(e.tag)),
71
77
  raw: e
72
78
  }));
73
- if (l.length > 0) {
74
- l.sort((e, t) => {
79
+ if (c.length > 0) {
80
+ c.sort((e, t) => {
75
81
  let n = i.rcompare(e.v, t.v);
76
82
  if (n !== 0) return n;
77
83
  let r = +!!/\d+\.\d+/u.test(e.raw.tag);
78
84
  return +!!/\d+\.\d+/u.test(t.raw.tag) - r;
79
85
  });
80
- let e = l[0].raw, a = i.valid(n(c) ?? void 0);
81
- if (!a || i.gt(l[0].v, a) || i.eq(l[0].v, a) && /\d+\.\d+/u.test(e.tag)) {
82
- let n = e.tag, i = e.sha?.length ? e.sha : null;
83
- if (!i && n) try {
84
- i = await d.getTagSha(o, s, n);
86
+ let e = c[0].raw, r = i.valid(n(l) ?? void 0);
87
+ if (!r || i.gt(c[0].v, r) || i.eq(c[0].v, r) && /\d+\.\d+/u.test(e.tag)) {
88
+ let n = e.tag, r = e.sha?.length ? e.sha : null;
89
+ if (!r && n) try {
90
+ r = await f.getTagSha(a, o, n);
85
91
  } catch (e) {
86
- if (u(e)) throw e;
92
+ if (d(e)) throw e;
87
93
  }
88
- return [...t, {
89
- currentRefType: p,
94
+ return {
95
+ currentRefType: s,
90
96
  version: n,
91
97
  publishedAt: null,
92
- sha: i,
93
- actionName: r
94
- }];
98
+ sha: r,
99
+ actionName: t
100
+ };
95
101
  }
96
102
  }
97
103
  }
98
104
  }
99
- if (c) {
100
- let e = l;
105
+ if (l) {
106
+ let e = u;
101
107
  try {
102
- l = await d.getTagSha(o, s, c) ?? e;
108
+ u = await f.getTagSha(a, o, l) ?? e;
103
109
  } catch (t) {
104
- if (u(t)) throw t;
105
- l = e;
110
+ if (d(t)) throw t;
111
+ u = e;
106
112
  }
107
113
  }
108
- return [...t, {
109
- currentRefType: p,
114
+ return {
115
+ currentRefType: s,
110
116
  status: "ok",
111
- publishedAt: a,
112
- actionName: r,
113
- version: c,
114
- sha: l
115
- }];
117
+ publishedAt: r,
118
+ actionName: t,
119
+ version: l,
120
+ sha: u
121
+ };
116
122
  }
117
- let g = await d.getAllTags(o, s, 30);
118
- if (g.length > 0) {
119
- let a = g.filter((t) => e(t.tag)).map((e) => ({
123
+ let m = await f.getAllTags(a, o, 30);
124
+ if (m.length > 0) {
125
+ let r = m.filter((t) => e(t.tag)).map((e) => ({
120
126
  v: i.valid(n(e.tag)),
121
127
  raw: e
122
128
  })), c;
123
- a.length > 0 ? (a.sort((e, t) => {
129
+ r.length > 0 ? (r.sort((e, t) => {
124
130
  let n = i.rcompare(e.v, t.v);
125
131
  if (n !== 0) return n;
126
132
  let r = +!!/\d+\.\d+/u.test(e.raw.tag);
127
133
  return +!!/\d+\.\d+/u.test(t.raw.tag) - r;
128
- }), c = a[0].raw) : c = g[0];
129
- let l = c.tag, f = c.sha?.length ? c.sha : null;
130
- if (!f && l) try {
131
- f = await d.getTagSha(o, s, l);
134
+ }), c = r[0].raw) : c = m[0];
135
+ let l = c.tag, u = c.sha?.length ? c.sha : null;
136
+ if (!u && l) try {
137
+ u = await f.getTagSha(a, o, l);
132
138
  } catch (e) {
133
- if (u(e)) throw e;
139
+ if (d(e)) throw e;
134
140
  }
135
- return [...t, {
136
- currentRefType: p,
141
+ return {
142
+ currentRefType: s,
137
143
  status: "ok",
138
144
  publishedAt: null,
139
- actionName: r,
145
+ actionName: t,
140
146
  version: l,
141
- sha: f
142
- }];
147
+ sha: u
148
+ };
143
149
  }
144
- return [...t, {
145
- currentRefType: p,
150
+ return {
151
+ currentRefType: s,
146
152
  publishedAt: null,
147
153
  version: null,
148
- actionName: r,
154
+ actionName: t,
149
155
  sha: null
150
- }];
156
+ };
151
157
  } catch (e) {
152
- return e instanceof Error && e.name === "GitHubRateLimitError" ? (g.rateLimitHit = !0, g.rateLimitError = e, [...t, {
158
+ return e instanceof Error && e.name === "GitHubRateLimitError" ? (_.rateLimitHit = !0, _.rateLimitError = e, {
153
159
  currentRefType: "unknown",
154
160
  publishedAt: null,
155
161
  version: null,
156
- actionName: r,
162
+ actionName: t,
157
163
  sha: null
158
- }]) : (console.warn(`Failed to check ${r}:`, e), [...t, {
164
+ }) : (console.warn(`Failed to check ${t}:`, e), {
159
165
  currentRefType: "unknown",
160
166
  publishedAt: null,
161
167
  version: null,
162
- actionName: r,
168
+ actionName: t,
163
169
  sha: null
164
- }]);
170
+ });
165
171
  }
166
- }), Promise.resolve([]));
167
- if (g.rateLimitError) {
168
- let e = !!(a ?? process.env.GITHUB_TOKEN), t = `${g.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);
169
- throw n.name = "GitHubRateLimitError", n;
170
172
  }
171
- let v = /* @__PURE__ */ new Map();
172
- for (let e of _) v.set(e.actionName, {
173
+ async function b(e) {
174
+ let t = e.next();
175
+ t.done || (v.push(await y(t.value)), await b(e));
176
+ }
177
+ if (await b(g.keys()), _.rateLimitError) {
178
+ let e = !!(o ?? process.env.GITHUB_TOKEN);
179
+ throw new a(`${_.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"}`);
180
+ }
181
+ let x = /* @__PURE__ */ new Map();
182
+ for (let e of v) x.set(e.actionName, {
173
183
  currentRefType: e.currentRefType,
174
184
  publishedAt: e.publishedAt,
175
185
  actionName: e.actionName,
@@ -178,10 +188,10 @@ async function a(t, a, s) {
178
188
  status: e.status,
179
189
  sha: e.sha
180
190
  });
181
- let y = [];
182
- for (let e of m) {
183
- let t = v.get(e.name);
184
- t ? y.push(o(e, {
191
+ let S = [];
192
+ for (let e of h) {
193
+ let t = x.get(e.name);
194
+ t ? S.push(s(e, {
185
195
  publishedAt: t.publishedAt,
186
196
  version: t.version,
187
197
  sha: t.sha
@@ -189,20 +199,20 @@ async function a(t, a, s) {
189
199
  currentRefType: t.currentRefType,
190
200
  skipReason: t.skipReason,
191
201
  status: t.status,
192
- style: p
193
- })) : y.push(o(e, {
202
+ style: m
203
+ })) : S.push(s(e, {
194
204
  publishedAt: null,
195
205
  version: null,
196
206
  sha: null
197
207
  }, {
198
- currentRefType: l(e.version),
199
- style: p
208
+ currentRefType: u(e.version),
209
+ style: m
200
210
  }));
201
211
  }
202
- return y;
212
+ return S;
203
213
  }
204
- function o(e, r, a) {
205
- let { version: o, sha: l, publishedAt: u } = r, d = e.version ?? "unknown", f = n(d), p = a.currentRefType, { style: m } = a, h = (m === "preserve" && p === "tag" ? t(d, o) : null) ?? o, g = h ? n(h) : null, _ = a.status ?? "ok", v = a.skipReason, y = !1, b = !1;
214
+ function s(e, r, a) {
215
+ let { version: o, sha: s, publishedAt: u } = r, d = e.version ?? "unknown", f = n(d), p = a.currentRefType, { style: m } = a, h = (m === "preserve" && p === "tag" ? t(d, o) : null) ?? o, g = h ? n(h) : null, _ = a.status ?? "ok", v = a.skipReason;
206
216
  if (_ === "skipped") return {
207
217
  currentRefType: p,
208
218
  currentVersion: d,
@@ -211,11 +221,12 @@ function o(e, r, a) {
211
221
  latestVersion: o,
212
222
  publishedAt: u,
213
223
  skipReason: v,
214
- latestSha: l,
224
+ latestSha: s,
215
225
  action: e,
216
226
  status: _
217
227
  };
218
- if (f && c(f)) l ? y = !s(f, l) : g && (y = !0);
228
+ let y = !1, b = !1;
229
+ if (f && l(f)) s ? y = !c(f, s) : g && (y = !0);
219
230
  else if (f && g) {
220
231
  let t = i.valid(f), n = i.valid(g);
221
232
  if (t && n) {
@@ -223,7 +234,7 @@ function o(e, r, a) {
223
234
  let e = i.major(t);
224
235
  b = i.major(n) > e;
225
236
  }
226
- !y && i.eq(t, n) && !c(e.version) && l && m === "sha" && (y = !0, b = !1);
237
+ !y && i.eq(t, n) && !l(e.version) && s && m === "sha" && (y = !0, b = !1);
227
238
  } else f !== g && (y = !0);
228
239
  }
229
240
  return {
@@ -233,25 +244,25 @@ function o(e, r, a) {
233
244
  publishedAt: u,
234
245
  isBreaking: b,
235
246
  skipReason: v,
236
- latestSha: l,
247
+ latestSha: s,
237
248
  hasUpdate: y,
238
249
  action: e,
239
250
  status: _
240
251
  };
241
252
  }
242
- function s(e, t) {
253
+ function c(e, t) {
243
254
  let n = e.replace(/^v/u, ""), r = t.replace(/^v/u, ""), i = Math.min(n.length, r.length);
244
255
  return i < 7 ? !1 : n.slice(0, Math.max(0, i)).toLowerCase() === r.slice(0, Math.max(0, i)).toLowerCase();
245
256
  }
246
- function c(e) {
257
+ function l(e) {
247
258
  if (!e) return !1;
248
259
  let t = e.replace(/^v/u, "");
249
260
  return /^[0-9a-f]{7,40}$/iu.test(t);
250
261
  }
251
- function l(t) {
252
- return t ? c(t) ? "sha" : e(t) ? "tag" : "unknown" : "unknown";
262
+ function u(t) {
263
+ return t ? l(t) ? "sha" : e(t) ? "tag" : "unknown" : "unknown";
253
264
  }
254
- function u(e) {
265
+ function d(e) {
255
266
  return e instanceof Error && e.name === "GitHubRateLimitError";
256
267
  }
257
- export { a as checkUpdates };
268
+ export { o as checkUpdates };
@@ -1,3 +1,3 @@
1
1
  export declare class GitHubRateLimitError extends Error {
2
- constructor(resetAt: Date);
2
+ constructor(resetAt: Date, options?: ErrorOptions);
3
3
  }
@@ -1,7 +1,7 @@
1
1
  var e = class extends Error {
2
- constructor(e) {
3
- let t = e.toLocaleTimeString();
4
- super(`GitHub API rate limit exceeded. Resets at ${t}`), this.name = "GitHubRateLimitError";
2
+ constructor(e, t) {
3
+ let n = e.toLocaleTimeString();
4
+ super(`GitHub API rate limit exceeded. Resets at ${n}`, t), this.name = "GitHubRateLimitError";
5
5
  }
6
6
  };
7
7
  export { e as GitHubRateLimitError };
@@ -9,8 +9,7 @@ async function t(t, n, r = {}) {
9
9
  let a = await fetch(`${t.baseUrl}${n}`, {
10
10
  ...r,
11
11
  headers: i
12
- }), o = {};
13
- for (let [e, t] of a.headers.entries()) o[e] = t;
12
+ }), o = Object.fromEntries(a.headers.entries());
14
13
  if (e(t, o), !a.ok) {
15
14
  let e = /* @__PURE__ */ Error(`GitHub API error: ${a.status} ${a.statusText}`);
16
15
  if (e.status = a.status, a.status === 403) {
@@ -31,8 +31,7 @@ function r() {
31
31
  if (i === "github") {
32
32
  let e = t.match(/^(?:oauth-token|token)\s*=\s*(?<val>\S[^\n\r]*)$/u);
33
33
  if (e?.groups?.val) return e.groups.val.trim();
34
- }
35
- if (i === "hub") {
34
+ } else if (i === "hub") {
36
35
  let e = t.match(/^oauthtoken\s*=\s*(?<val>\S[^\n\r]*)$/u);
37
36
  if (e?.groups?.val) return e.groups.val.trim();
38
37
  }
@@ -7,7 +7,7 @@ async function n(n) {
7
7
  let n = i.get(t) ?? [];
8
8
  n.push(e), i.set(t, n);
9
9
  }
10
- let a = [...i.entries()].map(async ([n, i]) => {
10
+ let a = [...i].map(async ([n, i]) => {
11
11
  let a = await e(n, "utf8");
12
12
  for (let e of i) {
13
13
  let t = e.targetRef ?? e.latestSha, n = e.targetRefStyle ?? (e.latestSha ? "sha" : null);
@@ -15,11 +15,12 @@ async function n(n) {
15
15
  function i(e) {
16
16
  return e.replaceAll(/[$()*+\-./?[\\\]^{|}]/gu, String.raw`\$&`);
17
17
  }
18
- let o = i(e.action.name), s = e.currentVersion ? i(e.currentVersion) : "", c = s ? String.raw`(?=(?:['"]|[ \t\]}{,#]|$))` : "";
18
+ let o = i(e.action.name);
19
19
  if (o.includes("\n") || o.includes("\r")) {
20
20
  console.error(`Invalid action name: ${e.action.name}`);
21
21
  continue;
22
22
  }
23
+ let s = e.currentVersion ? i(e.currentVersion) : "";
23
24
  if (s && (s.includes("\n") || s.includes("\r"))) {
24
25
  console.error(`Invalid version: ${e.currentVersion}`);
25
26
  continue;
@@ -32,7 +33,7 @@ async function n(n) {
32
33
  console.error(`Invalid SHA format: ${t}`);
33
34
  continue;
34
35
  }
35
- let l = String.raw`['"]?\buses\b['"]?\s*:\s*`, u = String.raw`(?:^[^\S\n]*(?:-[^\S\n]*)?|[{\[,][^\S\n]*)` + l, d = new RegExp(String.raw`(?<prefix>${u})` + String.raw`(?<quote>['"]?)` + String.raw`(?<name>${o})@${s}${c}` + String.raw`\k<quote>` + String.raw`(?<after>[ \t\]}{,]*)` + String.raw`(?<comment>[^\S\r\n]*#[^\r\n]*)?`, "gm");
36
+ let c = s ? String.raw`(?=(?:['"]|[ \t\]}{,#]|$))` : "", l = String.raw`['"]?\buses\b['"]?\s*:\s*`, u = String.raw`(?:^[^\S\n]*(?:-[^\S\n]*)?|[{\[,][^\S\n]*)` + l, d = RegExp(`(?<prefix>${u})(?<quote>['"]?)(?<name>${o})@${s}${c}${String.raw`\k<quote>`}${String.raw`(?<after>[ \t\]}{,]*)`}${String.raw`(?<comment>[^\S\r\n]*#[^\r\n]*)?`}`, "gm");
36
37
  a = a.replace(d, (i, ...a) => {
37
38
  let o = a.at(-3), c = a.at(-2), l = a.at(-1), u = c.indexOf("\n", o + i.length), d = (u === -1 ? c.slice(o + i.length) : c.slice(o + i.length, u)).trim().length > 0, f = l.after.endsWith(" ") ? "" : " ", p = "";
38
39
  if (n === "sha") p = d && !l.comment && s !== "" ? "" : `${f}# ${e.latestVersion}`;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Walks up from a starting directory to find the repository root.
3
+ *
4
+ * The root is the nearest ancestor (including the start) that contains a `.git`
5
+ * entry — a directory in a normal clone or a file in a git worktree — or, as a
6
+ * fallback for non-git checkouts, a `.github` directory. Returns null when no
7
+ * such ancestor exists up to the filesystem root.
8
+ *
9
+ * @param startDirectory - Absolute path to start searching from.
10
+ * @returns The repository root directory, or null when none is found.
11
+ */
12
+ export declare function findRepoRoot(startDirectory: string): Promise<string | null>;
@@ -0,0 +1,17 @@
1
+ import "../constants.js";
2
+ import { dirname as e, join as t, resolve as n } from "node:path";
3
+ import { stat as r } from "node:fs/promises";
4
+ async function i(r) {
5
+ let o = n(r);
6
+ if (await a(t(o, ".git")) || await a(t(o, ".github"))) return o;
7
+ let s = e(o);
8
+ return s === o ? null : await i(s);
9
+ }
10
+ async function a(e) {
11
+ try {
12
+ return await r(e), !0;
13
+ } catch {
14
+ return !1;
15
+ }
16
+ }
17
+ export { i as findRepoRoot };
@@ -4,7 +4,7 @@ function n(n, a) {
4
4
  if (!n) return e.gray("unknown");
5
5
  let o = t.parse(n), s = a ? t.parse(r(a)) : null;
6
6
  if (!s || !o) return n;
7
- let c = t.diff(r(a), n), l = s.major === 0 ? e.yellowBright : e.gray, u = [
7
+ let c = t.diff(r(a), n), l = e[s.major === 0 ? "yellowBright" : "gray"], u = [
8
8
  o.major,
9
9
  o.minor,
10
10
  o.patch
@@ -43,13 +43,13 @@ async function p(p, T = {}) {
43
43
  for (let [e, n] of D.entries()) {
44
44
  let r = n.action.name, o = k[e], c = o.display, l = n.action.job ?? "–";
45
45
  if (j = Math.max(j, r.length), M = Math.max(M, i(c).length, o.versionForPadding && o.shortSha ? i(`${a(o.versionForPadding, P + 1)}${s.gray(`(${o.shortSha})`)}`).length : 0), N = Math.max(N, l.length), n.latestVersion) {
46
- let r = t(n.targetRefStyle === "tag" && n.targetRef ? n.targetRef : n.latestVersion, k[e]?.effectiveForDiff ?? n.currentVersion);
47
- P = Math.max(P, i(r).length);
46
+ let r = n[n.targetRefStyle === "tag" && n.targetRef ? "targetRef" : "latestVersion"], a = t(r, k[e]?.effectiveForDiff ?? n.currentVersion);
47
+ P = Math.max(P, i(a).length);
48
48
  }
49
49
  let u = k[e]?.versionForPadding;
50
50
  u && (P = Math.max(P, i(u).length)), n.publishedAt && (F = !0);
51
51
  }
52
- let I = Math.max(j, l), L = Math.max(M, d), R = Math.max(N, u), z = Math.min(P, f), B = z + 1 + 9, V = E && F ? 6 : 0, H = [...O.keys()].toSorted();
52
+ let I = Math.max(j, l), L = Math.max(M, d), R = Math.max(N, u), z = Math.min(P, f), B = z + 1 + 9, V = E && F ? 6 : 0, H = O.keys().toArray().toSorted();
53
53
  for (let [e, n] of H.entries()) {
54
54
  let r = O.get(n);
55
55
  if (!r) {
@@ -129,7 +129,7 @@ async function p(p, T = {}) {
129
129
  let e = {
130
130
  indicator(e, t) {
131
131
  if (t.isGroupLabel) {
132
- let e = (t.choices ?? []).filter((e) => !("role" in e)), n = e.length, r = e.filter((e) => !!e.enabled).length === n ? "●" : "○";
132
+ let e = (t.choices ?? []).filter((e) => !("role" in e)), n = e.length, r = e.filter((e) => e.enabled).length === n ? "●" : "○";
133
133
  return ` ${s.gray(r)}`;
134
134
  }
135
135
  return ` ${t.enabled ? "●" : "○"}`;
@@ -215,7 +215,7 @@ function S(e) {
215
215
  return e.targetRef ? e.targetRef : e.latestSha;
216
216
  }
217
217
  function C() {
218
- console.info(`\r\u001B[K${s.yellow("Selection cancelled")}`);
218
+ console.info(`\r\u{1B}[K${s.yellow("Selection cancelled")}`);
219
219
  }
220
220
  function w(e) {
221
221
  return !!S(e);
@@ -110,7 +110,9 @@ async function p(u = process.cwd(), p = t) {
110
110
  }
111
111
  async function r() {
112
112
  if (n.length === 0) return;
113
- let i = n.splice(0), o = await Promise.all(i.map(async (n) => {
113
+ let i = [...n];
114
+ n.length = 0;
115
+ let o = await Promise.all(i.map(async (n) => {
114
116
  try {
115
117
  let r = s(n, "action.yml"), i = s(n, "action.yaml"), o = r;
116
118
  try {
@@ -33,14 +33,17 @@ async function l(l, d) {
33
33
  } catch {}
34
34
  return null;
35
35
  }), _ = await Promise.all(g);
36
- for (let e of _) if (e) if (e.type === "workflow") f.workflows.set(e.path, e.actions), f.actions.push(...e.actions);
37
- else {
38
- let t = o(e.path), n = t === "." || t === "" ? e.path : t;
39
- f.compositeActions.set(n, e.path), f.actions.push(...e.actions);
36
+ for (let e of _) if (e) {
37
+ if (e.type === "workflow") f.workflows.set(e.path, e.actions);
38
+ else {
39
+ let t = o(e.path), n = t === "." || t === "" ? e.path : t;
40
+ f.compositeActions.set(n, e.path);
41
+ }
42
+ f.actions.push(...e.actions);
40
43
  }
41
44
  return f;
42
45
  }
43
46
  function u(e, t) {
44
- return typeof e == "object" && !!e && t in e;
47
+ return typeof e == "object" && !!e && Object.hasOwn(e, t);
45
48
  }
46
49
  export { l as scanRecursive };
package/dist/package.js CHANGED
@@ -1,2 +1,2 @@
1
- var e = "1.14.2";
1
+ var e = "1.15.0";
2
2
  export { e as version };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "actions-up",
3
- "version": "1.14.2",
3
+ "version": "1.15.0",
4
4
  "description": "Interactive CLI tool to update GitHub Actions with SHA pinning or preserved refs",
5
5
  "keywords": [
6
6
  "github-actions",
@@ -36,14 +36,13 @@
36
36
  "./dist"
37
37
  ],
38
38
  "dependencies": {
39
- "cac": "^7.0.0",
40
39
  "enquirer": "^2.4.1",
41
40
  "nanospinner": "^1.2.2",
42
41
  "picocolors": "^1.1.1",
43
- "semver": "^7.8.0",
42
+ "semver": "^7.8.5",
44
43
  "yaml": "^2.9.0"
45
44
  },
46
45
  "engines": {
47
- "node": "^18.0.0 || >=20.0.0"
46
+ "node": "^18.3.0 || >=20.0.0"
48
47
  }
49
48
  }
package/readme.md CHANGED
@@ -187,6 +187,16 @@ skipped to avoid changing intentionally floating references. Skipped entries are
187
187
  listed in the output. To include them in update checks, pass
188
188
  `--include-branches`.
189
189
 
190
+ ### Quiet Mode
191
+
192
+ Use `--quiet` (`-q`) to hide the skipped and blocked-update warnings (for
193
+ example, actions intentionally pinned to branches). Other output — results,
194
+ applied updates, and errors — is unchanged.
195
+
196
+ ```bash
197
+ npx actions-up --yes --quiet
198
+ ```
199
+
190
200
  ### Update Mode
191
201
 
192
202
  By default, Actions Up allows major updates. Use `--mode` to limit updates: