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
@@ -0,0 +1,257 @@
1
+ import { ActionUpdate } from '../types/action-update';
2
+ import { ScanResult } from '../types/scan-result';
3
+ import { UpdateMode } from '../types/update-mode';
4
+ /**
5
+ * High-level status of a JSON report.
6
+ */
7
+ export type JsonReportStatus = 'updates-available' | 'no-actions-found' | 'nothing-to-check' | 'up-to-date';
8
+ /**
9
+ * Options used to build a JSON report from the current CLI state.
10
+ */
11
+ interface BuildJsonReportOptions {
12
+ /**
13
+ * Updates excluded by the selected update mode.
14
+ */
15
+ blockedByMode: ActionUpdate[];
16
+ /**
17
+ * Number of actions that were actually checked after excludes.
18
+ */
19
+ actionsToCheckCount: number;
20
+ /**
21
+ * Regex patterns supplied through `--exclude`.
22
+ */
23
+ excludePatterns: string[];
24
+ /**
25
+ * Whether branch references were included in update checks.
26
+ */
27
+ includeBranches: boolean;
28
+ /**
29
+ * Updates that remain actionable after all filters.
30
+ */
31
+ outdated: ActionUpdate[];
32
+ /**
33
+ * Final report status.
34
+ */
35
+ status: JsonReportStatus;
36
+ /**
37
+ * Updates skipped during processing, such as branch references.
38
+ */
39
+ skipped: ActionUpdate[];
40
+ /**
41
+ * Aggregate scan result for the current run.
42
+ */
43
+ scanResult: ScanResult;
44
+ /**
45
+ * Directories resolved for scanning.
46
+ */
47
+ directories: string[];
48
+ /**
49
+ * Whether recursive scanning mode is enabled.
50
+ */
51
+ recursive: boolean;
52
+ /**
53
+ * Effective update mode for the run.
54
+ */
55
+ mode: UpdateMode;
56
+ /**
57
+ * Minimum age filter in days.
58
+ */
59
+ minAge: number;
60
+ /**
61
+ * Working directory used for relative path normalization.
62
+ */
63
+ cwd?: string;
64
+ }
65
+ /**
66
+ * Serialized update entry in the JSON report.
67
+ */
68
+ interface JsonReportUpdate {
69
+ /**
70
+ * Reason why this entry was skipped, if any.
71
+ */
72
+ skipReason: ActionUpdate['skipReason'] | null;
73
+ /**
74
+ * Processing status for this entry.
75
+ */
76
+ status: NonNullable<ActionUpdate['status']>;
77
+ /**
78
+ * Version currently used in the workflow or action file.
79
+ */
80
+ currentVersion: string | null;
81
+ /**
82
+ * Latest version found for this dependency.
83
+ */
84
+ latestVersion: string | null;
85
+ /**
86
+ * Release publication date in ISO format.
87
+ */
88
+ publishedAt: string | null;
89
+ /**
90
+ * Original action reference metadata.
91
+ */
92
+ action: JsonReportAction;
93
+ /**
94
+ * Resolved SHA for the target version.
95
+ */
96
+ latestSha: string | null;
97
+ /**
98
+ * Whether this update crosses a major version boundary.
99
+ */
100
+ isBreaking: boolean;
101
+ /**
102
+ * Whether an update is available for this entry.
103
+ */
104
+ hasUpdate: boolean;
105
+ }
106
+ /**
107
+ * Aggregate counts included in the JSON report.
108
+ */
109
+ interface JsonReportSummary {
110
+ /**
111
+ * Number of composite actions discovered during scanning.
112
+ */
113
+ totalCompositeActions: number;
114
+ /**
115
+ * Number of breaking updates among actionable updates.
116
+ */
117
+ totalBreakingUpdates: number;
118
+ /**
119
+ * Number of actions checked after excludes.
120
+ */
121
+ totalActionsChecked: number;
122
+ /**
123
+ * Number of updates filtered out by `--mode`.
124
+ */
125
+ totalBlockedByMode: number;
126
+ /**
127
+ * Number of workflows discovered during scanning.
128
+ */
129
+ totalWorkflows: number;
130
+ /**
131
+ * Total number of action references found during scanning.
132
+ */
133
+ totalActions: number;
134
+ /**
135
+ * Number of skipped entries in the report.
136
+ */
137
+ totalSkipped: number;
138
+ /**
139
+ * Number of actionable updates in the report.
140
+ */
141
+ totalUpdates: number;
142
+ }
143
+ /**
144
+ * Serialized action reference included in each update entry.
145
+ */
146
+ interface JsonReportAction {
147
+ /**
148
+ * Action reference type detected during scanning.
149
+ */
150
+ type: ActionUpdate['action']['type'];
151
+ /**
152
+ * Original version or ref from the file, if available.
153
+ */
154
+ version: string | null;
155
+ /**
156
+ * Relative or absolute file path for the action reference.
157
+ */
158
+ file: string | null;
159
+ /**
160
+ * Line number of the action reference.
161
+ */
162
+ line: number | null;
163
+ /**
164
+ * Original `uses` value, if available.
165
+ */
166
+ uses: string | null;
167
+ /**
168
+ * Workflow job name, when applicable.
169
+ */
170
+ job: string | null;
171
+ /**
172
+ * Full `owner/repo@ref` string, when available.
173
+ */
174
+ ref: string | null;
175
+ /**
176
+ * Normalized action name.
177
+ */
178
+ name: string;
179
+ }
180
+ /**
181
+ * Effective CLI options serialized into the report.
182
+ */
183
+ interface JsonReportOptions {
184
+ /**
185
+ * Regex patterns supplied through `--exclude`.
186
+ */
187
+ excludePatterns: string[];
188
+ /**
189
+ * Whether branch references were checked.
190
+ */
191
+ includeBranches: boolean;
192
+ /**
193
+ * Resolved scan directories.
194
+ */
195
+ directories: string[];
196
+ /**
197
+ * Whether recursive scanning mode is enabled.
198
+ */
199
+ recursive: boolean;
200
+ /**
201
+ * Effective update mode.
202
+ */
203
+ mode: UpdateMode;
204
+ /**
205
+ * Indicates that JSON mode never applies changes.
206
+ */
207
+ reportOnly: true;
208
+ /**
209
+ * Minimum age filter in days.
210
+ */
211
+ minAge: number;
212
+ /**
213
+ * Indicates that this payload came from `--json`.
214
+ */
215
+ json: true;
216
+ }
217
+ /**
218
+ * Top-level machine-readable report emitted by `--json`.
219
+ */
220
+ interface JsonReport {
221
+ /**
222
+ * Entries filtered out by the selected update mode.
223
+ */
224
+ blockedByMode: JsonReportUpdate[];
225
+ /**
226
+ * Entries skipped during update checks.
227
+ */
228
+ skipped: JsonReportUpdate[];
229
+ /**
230
+ * Actionable updates after filtering.
231
+ */
232
+ updates: JsonReportUpdate[];
233
+ /**
234
+ * Effective options that shaped the report.
235
+ */
236
+ options: JsonReportOptions;
237
+ /**
238
+ * Aggregate counts for the current run.
239
+ */
240
+ summary: JsonReportSummary;
241
+ /**
242
+ * Overall outcome for the current run.
243
+ */
244
+ status: JsonReportStatus;
245
+ /**
246
+ * Version of the JSON payload schema.
247
+ */
248
+ schemaVersion: 1;
249
+ }
250
+ /**
251
+ * Build the machine-readable JSON report returned by `--json`.
252
+ *
253
+ * @param options - Current CLI state and computed update data.
254
+ * @returns Serializable JSON payload for stdout.
255
+ */
256
+ export declare function buildJsonReport(options: BuildJsonReportOptions): JsonReport;
257
+ export {};
@@ -0,0 +1,64 @@
1
+ import { isAbsolute, relative, resolve } from "node:path";
2
+ function buildJsonReport(e) {
3
+ let n = resolve(e.cwd ?? process.cwd());
4
+ return {
5
+ summary: {
6
+ totalBreakingUpdates: e.outdated.filter((e) => e.isBreaking).length,
7
+ totalCompositeActions: e.scanResult.compositeActions.size,
8
+ totalWorkflows: e.scanResult.workflows.size,
9
+ totalActionsChecked: e.actionsToCheckCount,
10
+ totalBlockedByMode: e.blockedByMode.length,
11
+ totalActions: e.scanResult.actions.length,
12
+ totalUpdates: e.outdated.length,
13
+ totalSkipped: e.skipped.length
14
+ },
15
+ options: {
16
+ directories: e.directories.map((e) => serializeDirectoryPath(e, n)),
17
+ excludePatterns: e.excludePatterns,
18
+ includeBranches: e.includeBranches,
19
+ recursive: e.recursive,
20
+ minAge: e.minAge,
21
+ mode: e.mode,
22
+ reportOnly: !0,
23
+ json: !0
24
+ },
25
+ blockedByMode: e.blockedByMode.map((e) => serializeUpdate(e, n)),
26
+ updates: e.outdated.map((e) => serializeUpdate(e, n)),
27
+ skipped: e.skipped.map((e) => serializeUpdate(e, n)),
28
+ status: e.status,
29
+ schemaVersion: 1
30
+ };
31
+ }
32
+ function serializeUpdate(e, n) {
33
+ return {
34
+ action: {
35
+ file: serializePath(e.action.file, n),
36
+ version: e.action.version ?? null,
37
+ line: e.action.line ?? null,
38
+ uses: e.action.uses ?? null,
39
+ job: e.action.job ?? null,
40
+ ref: e.action.ref ?? null,
41
+ name: e.action.name,
42
+ type: e.action.type
43
+ },
44
+ publishedAt: e.publishedAt?.toISOString() ?? null,
45
+ currentVersion: e.currentVersion,
46
+ skipReason: e.skipReason ?? null,
47
+ latestVersion: e.latestVersion,
48
+ isBreaking: e.isBreaking,
49
+ status: e.status ?? "ok",
50
+ hasUpdate: e.hasUpdate,
51
+ latestSha: e.latestSha
52
+ };
53
+ }
54
+ function serializePath(r, i, a = null) {
55
+ if (!r) return null;
56
+ if (!isAbsolute(r)) return r;
57
+ let o = relative(i, r);
58
+ return o === "" ? a ?? r : o.startsWith("..") || isAbsolute(o) ? r : o;
59
+ }
60
+ function serializeDirectoryPath(r, i) {
61
+ let a = relative(i, r);
62
+ return a === "" ? "." : a.startsWith("..") || isAbsolute(a) ? r : a;
63
+ }
64
+ export { buildJsonReport };
@@ -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
@@ -8,127 +8,186 @@ import { getUpdateLevel } from "../core/versions/get-update-level.js";
8
8
  import { applyUpdates } from "../core/ast/update/apply-updates.js";
9
9
  import { printSkippedWarning } from "./print-skipped-warning.js";
10
10
  import { normalizeUpdateMode } from "./normalize-update-mode.js";
11
+ import { validateCliOptions } from "./validate-cli-options.js";
11
12
  import { shouldIgnore } from "../core/ignore/should-ignore.js";
12
13
  import { checkUpdates } from "../core/api/check-updates.js";
13
14
  import { mergeScanResults } from "./merge-scan-results.js";
14
15
  import { printModeWarning } from "./print-mode-warning.js";
15
16
  import { scanRecursive } from "../core/scan-recursive.js";
17
+ import { buildJsonReport } from "./build-json-report.js";
16
18
  import { scanGitHubActions } from "../core/scan-github-actions.js";
17
19
  import "../core/index.js";
18
20
  import { version } from "../package.js";
19
21
  import { createSpinner } from "nanospinner";
22
+ import { resolve } from "node:path";
20
23
  import "node:worker_threads";
21
24
  import pc from "picocolors";
22
25
  import cac from "cac";
23
26
  function run() {
24
- let b = cac("actions-up");
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) => {
26
- console.info(pc.cyan("\n🚀 Actions Up!\n"));
27
- let y = createSpinner("Scanning GitHub Actions...").start(), b = resolveScanDirectories({
28
- recursive: v.recursive,
27
+ let C = cac("actions-up");
28
+ C.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("--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("--recursive, -r", "Recursively scan directories for YAML files").option("--yes, -y", "Skip all confirmations").command("", "Update GitHub Actions").action(async (b) => {
29
+ let S = b.json ?? !1, C = null, w = resolveScanDirectories({
30
+ recursive: b.recursive,
29
31
  cwd: process.cwd(),
30
- dir: v.dir
31
- });
32
+ dir: b.dir
33
+ }), T = w.map(({ root: e, dir: f }) => resolve(e, f)), E = b.includeBranches ?? !1, D = normalizeUpdateMode(b.mode), O = [];
34
+ Array.isArray(b.exclude) ? O.push(...b.exclude) : typeof b.exclude == "string" && O.push(b.exclude);
35
+ let k = O.flatMap((e) => e.split(",")).map((e) => e.trim()).filter(Boolean);
32
36
  try {
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) {
37
+ validateCliOptions({
38
+ yes: b.yes,
39
+ json: S
40
+ }), S || (console.info(pc.cyan("\n🚀 Actions Up!\n")), C = createSpinner("Scanning GitHub Actions...").start());
41
+ function g({ actionsToCheckCount: e, blockedByMode: f = [], outdated: p = [], skipped: m = [], scanResult: h, status: g }) {
42
+ process.stdout.write(`${JSON.stringify(buildJsonReport({
43
+ recursive: b.recursive ?? !1,
44
+ excludePatterns: k,
45
+ directories: T,
46
+ minAge: b.minAge,
47
+ actionsToCheckCount: e,
48
+ includeBranches: E,
49
+ blockedByMode: f,
50
+ scanResult: h,
51
+ outdated: p,
52
+ skipped: m,
53
+ status: g,
54
+ mode: D
55
+ }), null, 2)}\n`);
56
+ }
57
+ let y = mergeScanResults(b.recursive ? await Promise.all(w.map(({ root: e, dir: f }) => scanRecursive(e, f))) : await Promise.all(w.map(({ root: e, dir: f }) => scanGitHubActions(e, f)))), x = y.actions.length, O = y.workflows.size, A = y.compositeActions.size;
58
+ if (C?.success(`Found ${pc.yellow(x)} actions in ${pc.yellow(O)} workflows and ${pc.yellow(A)} composite actions`), x === 0) {
59
+ if (S) {
60
+ g({
61
+ status: "no-actions-found",
62
+ actionsToCheckCount: 0,
63
+ scanResult: y
64
+ });
65
+ return;
66
+ }
35
67
  console.info(pc.green("\n✨ No GitHub Actions found in this repository"));
36
68
  return;
37
69
  }
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;
70
+ let j = y.actions;
71
+ if (k.length > 0) {
72
+ let { parseExcludePatterns: e } = await import("../core/filters/parse-exclude-patterns.js"), f = e(k);
73
+ f.length > 0 && (j = j.filter((e) => {
74
+ let { name: p } = e;
75
+ for (let e of f) if (e.test(p)) return !1;
46
76
  return !0;
47
77
  }));
48
78
  }
49
- if (y = createSpinner("Checking for updates...").start(), w.length === 0) {
50
- y.success("No actions to check after excludes"), console.info(pc.green("\n✨ Nothing to check after excludes\n"));
79
+ if (S || (C = createSpinner("Checking for updates...").start()), j.length === 0) {
80
+ if (C?.success("No actions to check after excludes"), S) {
81
+ g({
82
+ status: "nothing-to-check",
83
+ actionsToCheckCount: 0,
84
+ scanResult: y
85
+ });
86
+ return;
87
+ }
88
+ console.info(pc.green("\n✨ Nothing to check after excludes\n"));
51
89
  return;
52
90
  }
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);
91
+ let M = process.env.GITHUB_TOKEN, N = createGitHubClient(M), P = await checkUpdates(j, M, {
92
+ client: N,
93
+ includeBranches: E
94
+ }), F = [];
95
+ await Promise.all(P.map(async (e) => {
96
+ await shouldIgnore(e.action.file, e.action.line) || F.push(e);
59
97
  }));
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);
98
+ let I = F.filter((e) => e.status === "skipped"), L = F.filter((e) => e.hasUpdate), R = b.minAge * 24 * 60 * 60 * 1e3, z = Date.now();
99
+ L = L.filter((e) => e.publishedAt ? z - e.publishedAt.getTime() >= R : !0);
100
+ let B = [];
101
+ if (D !== "major") {
102
+ let p = /* @__PURE__ */ new Map(), h = /* @__PURE__ */ new Map(), g = /* @__PURE__ */ new Map(), _ = await Promise.all(L.map(async (p) => {
103
+ let m = p.currentVersion;
104
+ if (isSha(p.currentVersion)) {
105
+ let f = await readInlineVersionComment(p.action.file, p.action.line, g);
106
+ f && (m = f);
69
107
  }
70
- let p = getUpdateLevel(f, d.latestVersion);
108
+ let h = getUpdateLevel(m, p.latestVersion);
71
109
  return {
72
- effectiveCurrentVersion: f,
73
- allowed: I === "minor" ? p === "minor" || p === "patch" || p === "none" : p === "patch" || p === "none",
74
- update: d
110
+ effectiveCurrentVersion: m,
111
+ allowed: D === "minor" ? h === "minor" || h === "patch" || h === "none" : h === "patch" || h === "none",
112
+ update: p
75
113
  };
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
114
+ })), v = [], y = await Promise.all(_.map(async (e) => {
115
+ if (e.allowed) return { update: e.update };
116
+ let f = await getCompatibleUpdate(N, {
117
+ currentVersion: e.effectiveCurrentVersion,
118
+ actionName: e.update.action.name,
119
+ tagsCache: p,
120
+ shaCache: h,
121
+ mode: D
84
122
  });
85
- return u ? { update: {
86
- ...t.update,
87
- latestVersion: u.version,
88
- latestSha: u.sha,
123
+ return f ? { update: {
124
+ ...e.update,
125
+ latestVersion: f.version,
126
+ latestSha: f.sha,
89
127
  isBreaking: !1,
90
128
  hasUpdate: !0
91
- } } : { blocked: t.update };
129
+ } } : { blocked: e.update };
92
130
  }));
93
- for (let t of _) {
94
- if (t.update) {
95
- g.push(t.update);
131
+ for (let e of y) {
132
+ if (e.update) {
133
+ v.push(e.update);
96
134
  continue;
97
135
  }
98
- L.push(t.blocked);
136
+ B.push(e.blocked);
137
+ }
138
+ L = v;
139
+ }
140
+ let V = L.filter((e) => e.isBreaking);
141
+ if (L.length === 0) {
142
+ if (C?.success("All actions are up to date!"), S) {
143
+ g({
144
+ actionsToCheckCount: j.length,
145
+ status: "up-to-date",
146
+ blockedByMode: B,
147
+ scanResult: y,
148
+ skipped: I
149
+ });
150
+ return;
99
151
  }
100
- N = g;
152
+ I.length > 0 && printSkippedWarning(I, E), B.length > 0 && printModeWarning(B, D), console.info(pc.green("\n✨ Everything is already at the latest version!\n"));
153
+ return;
101
154
  }
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"));
155
+ if (C?.success(`Found ${pc.yellow(L.length)} updates available${V.length > 0 ? ` (${pc.redBright(V.length)} breaking)` : ""}`), S) {
156
+ g({
157
+ actionsToCheckCount: j.length,
158
+ status: "updates-available",
159
+ blockedByMode: B,
160
+ scanResult: y,
161
+ outdated: L,
162
+ skipped: I
163
+ });
105
164
  return;
106
165
  }
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) {
166
+ if (I.length > 0 && printSkippedWarning(I, E), B.length > 0 && printModeWarning(B, D), b.dryRun) {
108
167
  console.info(pc.yellow("\n📋 Dry Run - No changes will be made\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`));
168
+ for (let e of L) 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`);
169
+ console.info(pc.gray(`\n${L.length} actions would be updated\n`));
111
170
  return;
112
171
  }
113
- if (v.yes) {
114
- let t = N.filter((t) => t.latestSha);
115
- if (t.length === 0) {
172
+ if (b.yes) {
173
+ let e = L.filter((e) => e.latestSha);
174
+ if (e.length === 0) {
116
175
  console.info(pc.yellow("\n⚠️ No actions with SHA available for update\n"));
117
176
  return;
118
177
  }
119
- console.info(pc.yellow(`\n🔄 Updating ${t.length} actions...\n`)), await applyUpdates(t), console.info(pc.green("\n✓ Updates applied successfully!"));
178
+ console.info(pc.yellow(`\n🔄 Updating ${e.length} actions...\n`)), await applyUpdates(e), console.info(pc.green("\n✓ Updates applied successfully!"));
120
179
  } else {
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) {
180
+ (I.length > 0 || B.length > 0) && console.info("");
181
+ let e = await promptUpdateSelection(L, { showAge: b.minAge > 0 });
182
+ if (!e || e.length === 0) {
124
183
  console.info(pc.gray("\nNo updates applied"));
125
184
  return;
126
185
  }
127
- console.info(pc.yellow(`\n🔄 Updating ${t.length} selected actions...\n`)), await applyUpdates(t), console.info(pc.green("\n✓ Updates applied successfully!"));
186
+ console.info(pc.yellow(`\n🔄 Updating ${e.length} selected actions...\n`)), await applyUpdates(e), console.info(pc.green("\n✓ Updates applied successfully!"));
128
187
  }
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);
188
+ } catch (e) {
189
+ C?.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);
131
190
  }
132
- }), b.parse();
191
+ }), C.parse();
133
192
  }
134
193
  export { run };
@@ -1,10 +1,14 @@
1
- /** Options for resolving scan directories from CLI flags. */
1
+ /**
2
+ * Options for resolving scan directories from CLI flags.
3
+ */
2
4
  interface ResolveScanDirectoriesOptions {
3
5
  dir?: string[] | string;
4
6
  recursive?: boolean;
5
7
  cwd: string;
6
8
  }
7
- /** Resolved directory with root and relative directory. */
9
+ /**
10
+ * Resolved directory with root and relative directory.
11
+ */
8
12
  interface ResolvedDirectory {
9
13
  root: string;
10
14
  dir: string;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Minimal subset of CLI flags that require cross-option validation.
3
+ */
4
+ interface ValidateCliOptionsInput {
5
+ /**
6
+ * Whether JSON report mode is enabled.
7
+ */
8
+ json?: boolean;
9
+ /**
10
+ * Whether auto-apply mode is enabled.
11
+ */
12
+ yes?: boolean;
13
+ }
14
+ /**
15
+ * Validate combinations of CLI flags before running the pipeline.
16
+ *
17
+ * @param options - Parsed CLI flags relevant to cross-option validation.
18
+ */
19
+ export declare function validateCliOptions(options: ValidateCliOptionsInput): void;
20
+ export {};
@@ -0,0 +1,4 @@
1
+ function validateCliOptions(e) {
2
+ if (e.json && e.yes) throw Error("--json cannot be used with --yes");
3
+ }
4
+ export { validateCliOptions };