actions-up 1.14.1 → 1.14.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -17,192 +17,197 @@ import { mergeScanResults as h } from "./merge-scan-results.js";
17
17
  import { printModeWarning as g } from "./print-mode-warning.js";
18
18
  import { scanRecursive as _ } from "../core/scan-recursive.js";
19
19
  import { buildJsonReport as v } from "./build-json-report.js";
20
- import { scanGitHubActions as y } from "../core/scan-github-actions.js";
20
+ import { parseArguments as y } from "./parse-arguments.js";
21
+ import { scanGitHubActions as b } from "../core/scan-github-actions.js";
21
22
  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";
23
+ import { version as x } from "../package.js";
24
+ import { createSpinner as S } from "nanospinner";
25
+ import { resolve as C } from "node:path";
25
26
  import "node:worker_threads";
26
- import C from "picocolors";
27
- import w from "cac";
27
+ import w from "picocolors";
28
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
+ let e = y(process.argv.slice(2), x);
30
+ if (e.kind === "help" || e.kind === "version") {
31
+ console.info(e.text);
32
+ return;
33
+ }
34
+ e.kind === "error" && (console.error(w.redBright("\nError:"), e.message), process.exit(1)), E(e.options);
35
+ }
36
+ async function E(y) {
37
+ let x = y.json ?? !1, T = null, E = o({
38
+ recursive: y.recursive,
39
+ cwd: process.cwd(),
40
+ dir: y.dir
41
+ }), D = E.map(({ root: e, dir: t }) => C(e, t)), O = y.includeBranches ?? !1, k = d(y.mode), A = l(y.style), j = [];
42
+ Array.isArray(y.exclude) ? j.push(...y.exclude) : typeof y.exclude == "string" && j.push(y.exclude);
43
+ let M = j.flatMap((e) => e.split(",")).map((e) => e.trim()).filter(Boolean);
44
+ try {
45
+ f({
46
+ yes: y.yes,
47
+ json: x
48
+ }), x || (console.info(w.cyan("\nšŸš€ Actions Up!\n")), T = S("Scanning GitHub Actions...").start());
49
+ function o({ actionsToCheckCount: e, blockedByMode: t = [], outdated: n = [], skipped: r = [], scanResult: i, status: a }) {
50
+ process.stdout.write(`${JSON.stringify(v({
51
+ recursive: y.recursive ?? !1,
52
+ excludePatterns: M,
53
+ directories: D,
54
+ minAge: y.minAge,
55
+ actionsToCheckCount: e,
56
+ includeBranches: O,
57
+ blockedByMode: t,
58
+ scanResult: i,
59
+ outdated: n,
60
+ skipped: r,
61
+ status: a,
62
+ style: A,
63
+ mode: k
64
+ }), null, 2)}\n`);
65
+ }
66
+ let l = h(y.recursive ? await Promise.all(E.map(({ root: e, dir: t }) => _(e, t))) : await Promise.all(E.map(({ root: e, dir: t }) => b(e, t)))), d = l.actions.length, C = l.workflows.size, j = l.compositeActions.size;
67
+ if (T?.success(`Found ${w.yellow(d)} actions in ${w.yellow(C)} workflows and ${w.yellow(j)} composite actions`), d === 0) {
68
+ if (x) {
69
+ o({
70
+ status: "no-actions-found",
71
+ actionsToCheckCount: 0,
72
+ scanResult: l
73
+ });
71
74
  return;
72
75
  }
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"));
76
+ console.info(w.green("\n✨ No GitHub Actions found in this repository"));
77
+ return;
78
+ }
79
+ let N = l.actions;
80
+ if (M.length > 0) {
81
+ let { parseExcludePatterns: e } = await import("../core/filters/parse-exclude-patterns.js"), t = e(M);
82
+ t.length > 0 && (N = N.filter((e) => {
83
+ let { name: n } = e;
84
+ for (let e of t) if (e.test(n)) return !1;
85
+ return !0;
86
+ }));
87
+ }
88
+ if (x || (T = S("Checking for updates...").start()), N.length === 0) {
89
+ if (T?.success("No actions to check after excludes"), x) {
90
+ o({
91
+ status: "nothing-to-check",
92
+ actionsToCheckCount: 0,
93
+ scanResult: l
94
+ });
92
95
  return;
93
96
  }
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);
97
+ console.info(w.green("\n✨ Nothing to check after excludes\n"));
98
+ return;
99
+ }
100
+ let P = process.env.GITHUB_TOKEN, F = a(P), I = await m(N, P, {
101
+ client: F,
102
+ includeBranches: O,
103
+ style: A
104
+ }), L = [];
105
+ await Promise.all(I.map(async (e) => {
106
+ await p(e.action.file, e.action.line) || L.push(e);
107
+ }));
108
+ let R = L.filter((e) => e.status === "skipped"), z = L.filter((e) => e.hasUpdate), B = y.minAge * 24 * 60 * 60 * 1e3, V = Date.now();
109
+ z = z.filter((e) => e.publishedAt ? V - e.publishedAt.getTime() >= B : !0);
110
+ let H = [];
111
+ if (k !== "major") {
112
+ let n = /* @__PURE__ */ new Map(), r = /* @__PURE__ */ new Map(), a = /* @__PURE__ */ new Map(), o = await Promise.all(z.map(async (n) => {
113
+ let r = n.currentVersion;
114
+ if (t(n.currentVersion)) {
115
+ let t = await e(n.action.file, n.action.line, a);
116
+ t && (r = t);
141
117
  }
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
118
+ let i = s(r, n.latestVersion);
119
+ return {
120
+ effectiveCurrentVersion: r,
121
+ allowed: k === "minor" ? i === "minor" || i === "patch" || i === "none" : i === "patch" || i === "none",
122
+ update: n
123
+ };
124
+ })), c = [], l = await Promise.all(o.map(async (e) => {
125
+ if (e.allowed) return { update: e.update };
126
+ let t = await i(F, {
127
+ currentVersion: e.effectiveCurrentVersion,
128
+ actionName: e.update.action.name,
129
+ tagsCache: n,
130
+ shaCache: r,
131
+ mode: k
132
+ });
133
+ return t ? { update: {
134
+ ...e.update,
135
+ latestVersion: t.version,
136
+ latestSha: t.sha,
137
+ isBreaking: !1,
138
+ hasUpdate: !0
139
+ } } : { blocked: e.update };
150
140
  }));
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;
141
+ for (let e of l) {
142
+ if (e.update) {
143
+ c.push(e.update);
144
+ continue;
163
145
  }
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;
146
+ H.push(e.blocked);
166
147
  }
167
- if (T?.success(`Found ${C.yellow(z.length)} updates available${W.length > 0 ? ` (${C.redBright(W.length)} breaking)` : ""}`), w) {
148
+ z = c;
149
+ }
150
+ z = z.map((e) => r(e, A));
151
+ let U = z.filter((e) => !e.targetRef).map((e) => ({
152
+ ...e,
153
+ skipReason: "unsupported-style",
154
+ status: "skipped",
155
+ hasUpdate: !1
156
+ }));
157
+ R.push(...U), z = z.filter((e) => e.targetRef);
158
+ let W = z.filter((e) => e.isBreaking);
159
+ if (z.length === 0) {
160
+ if (T?.success("All actions are up to date!"), x) {
168
161
  o({
169
162
  actionsToCheckCount: N.length,
170
- status: "updates-available",
163
+ status: "up-to-date",
171
164
  blockedByMode: H,
172
165
  scanResult: l,
173
- outdated: z,
174
166
  skipped: R
175
167
  });
176
168
  return;
177
169
  }
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`));
170
+ R.length > 0 && u(R, O, A), H.length > 0 && g(H, k), console.info(w.green("\n✨ Everything is already at the latest version!\n"));
171
+ return;
172
+ }
173
+ if (T?.success(`Found ${w.yellow(z.length)} updates available${W.length > 0 ? ` (${w.redBright(W.length)} breaking)` : ""}`), x) {
174
+ o({
175
+ actionsToCheckCount: N.length,
176
+ status: "updates-available",
177
+ blockedByMode: H,
178
+ scanResult: l,
179
+ outdated: z,
180
+ skipped: R
181
+ });
182
+ return;
183
+ }
184
+ if (R.length > 0 && u(R, O, A), H.length > 0 && g(H, k), y.dryRun) {
185
+ console.info(w.yellow("\nšŸ“‹ Dry Run - No changes will be made\n"));
186
+ for (let e of z) {
187
+ let t = e.targetRefStyle === "sha" && e.targetRef ? `${e.latestVersion} ${w.gray(`(${e.targetRef.slice(0, 7)})`)}` : e.targetRef ?? e.latestVersion;
188
+ console.info(`${w.cyan(e.action.file ?? "unknown")}:\n${e.action.name}: ${w.redBright(e.currentVersion)} → ${w.green(t)}\n`);
189
+ }
190
+ console.info(w.gray(`\n${z.length} actions would be updated\n`));
191
+ return;
192
+ }
193
+ if (y.yes) {
194
+ let e = z.filter((e) => e.targetRef);
195
+ if (e.length === 0) {
196
+ console.info(w.yellow("\nāš ļø No actionable updates available\n"));
185
197
  return;
186
198
  }
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!"));
199
+ console.info(w.yellow(`\nšŸ”„ Updating ${e.length} actions...\n`)), await c(e), console.info(w.green("\nāœ“ Updates applied successfully!"));
200
+ } else {
201
+ (R.length > 0 || H.length > 0) && console.info("");
202
+ let e = await n(z, { showAge: y.minAge > 0 });
203
+ if (!e || e.length === 0) {
204
+ console.info(w.gray("\nNo updates applied"));
205
+ return;
202
206
  }
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);
207
+ console.info(w.yellow(`\nšŸ”„ Updating ${e.length} selected actions...\n`)), await c(e), console.info(w.green("\nāœ“ Updates applied successfully!"));
205
208
  }
206
- }), T.parse();
209
+ } catch (e) {
210
+ T?.error("Failed"), e instanceof Error && e.name === "GitHubRateLimitError" ? (console.error(w.yellow("\nāš ļø Rate Limit Exceeded\n")), console.error(e.message), console.error(w.gray("\nExample: GITHUB_TOKEN=ghp_xxxx actions-up\n"))) : console.error(w.redBright("\nError:"), e instanceof Error ? e.message : String(e)), process.exit(1);
211
+ }
207
212
  }
208
213
  export { T as run };
@@ -0,0 +1,74 @@
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
+ * Preview changes without applying them.
23
+ */
24
+ dryRun: boolean;
25
+ /**
26
+ * Update style (sha or preserve).
27
+ */
28
+ style?: string;
29
+ /**
30
+ * Output a machine-readable JSON report.
31
+ */
32
+ json?: boolean;
33
+ /**
34
+ * Minimum age in days for updates.
35
+ */
36
+ minAge: number;
37
+ /**
38
+ * Update mode (major, minor, patch).
39
+ */
40
+ mode?: string;
41
+ /**
42
+ * Skip all confirmations.
43
+ */
44
+ yes: boolean;
45
+ }
46
+ /**
47
+ * Result of parsing CLI arguments. `kind` discriminates what the caller should
48
+ * do next.
49
+ */
50
+ export type ParseArgumentsResult = {
51
+ options: CLIOptions;
52
+ kind: 'options';
53
+ } | {
54
+ message: string;
55
+ kind: 'error';
56
+ } | {
57
+ kind: 'version';
58
+ text: string;
59
+ } | {
60
+ kind: 'help';
61
+ text: string;
62
+ };
63
+ /**
64
+ * Parse CLI arguments into normalized options.
65
+ *
66
+ * Reproduces the previous cac behavior without its runtime magic: numeric
67
+ * coercion for `--min-age` and the option defaults are applied manually here,
68
+ * and kebab-case flags are mapped to the camelCase option shape.
69
+ *
70
+ * @param argv - Raw arguments, typically `process.argv.slice(2)`.
71
+ * @param appVersion - Version string used for `--version`.
72
+ * @returns A discriminated result describing what the caller should do.
73
+ */
74
+ export declare function parseArguments(argv: string[], appVersion: string): ParseArgumentsResult;
@@ -0,0 +1,76 @@
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 -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
+ help: {
21
+ type: "boolean",
22
+ short: "h"
23
+ },
24
+ yes: {
25
+ type: "boolean",
26
+ short: "y"
27
+ },
28
+ "dry-run": { type: "boolean" },
29
+ "min-age": { type: "string" },
30
+ style: { type: "string" },
31
+ json: { type: "boolean" },
32
+ mode: { type: "string" }
33
+ };
34
+ function r(r, i) {
35
+ try {
36
+ let { values: a } = e({
37
+ allowPositionals: !1,
38
+ options: n,
39
+ strict: !0,
40
+ args: r
41
+ });
42
+ if (a.help) return {
43
+ text: t,
44
+ kind: "help"
45
+ };
46
+ if (a.version) return {
47
+ text: `actions-up/${i} ${process.platform}-${process.arch} node-${process.version}`,
48
+ kind: "version"
49
+ };
50
+ let o = a["min-age"], s = o === void 0 ? 0 : Number(o);
51
+ return !Number.isFinite(s) || s < 0 ? {
52
+ message: `Invalid --min-age "${o}". Expected a non-negative number.`,
53
+ kind: "error"
54
+ } : {
55
+ options: {
56
+ includeBranches: a["include-branches"],
57
+ dryRun: a["dry-run"] ?? !1,
58
+ style: a.style ?? "sha",
59
+ mode: a.mode ?? "major",
60
+ recursive: a.recursive,
61
+ yes: a.yes ?? !1,
62
+ exclude: a.exclude,
63
+ json: a.json,
64
+ dir: a.dir,
65
+ minAge: s
66
+ },
67
+ kind: "options"
68
+ };
69
+ } catch (e) {
70
+ return {
71
+ message: e.message,
72
+ kind: "error"
73
+ };
74
+ }
75
+ }
76
+ export { r as parseArguments };
@@ -1,9 +1,10 @@
1
- import { normalizeVersion as e } from "../versions/normalize-version.js";
2
- import { isSemverLike as t } from "../versions/is-semver-like.js";
3
- import { createGitHubClient as n } from "./create-github-client.js";
4
- import r from "semver";
5
- async function i(i, o, u) {
6
- let d = u?.client ?? n(o), f = u?.includeBranches ?? !1, p = u?.style ?? "sha", m = i.filter((e) => e.type === "external" || e.type === "reusable-workflow");
1
+ import { isSemverLike as e } from "../versions/is-semver-like.js";
2
+ import { preserveTagFormat as t } from "../versions/preserve-tag-format.js";
3
+ import { normalizeVersion as n } from "../versions/normalize-version.js";
4
+ import { createGitHubClient as r } from "./create-github-client.js";
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");
7
8
  if (m.length === 0) return [];
8
9
  let h = /* @__PURE__ */ new Map();
9
10
  for (let e of m) {
@@ -13,158 +14,158 @@ async function i(i, o, u) {
13
14
  let g = {
14
15
  rateLimitError: null,
15
16
  rateLimitHit: !1
16
- }, _ = await [...h.keys()].reduce((n, i) => n.then(async (n) => {
17
- if (g.rateLimitHit) return [...n, {
17
+ }, _ = await [...h.keys()].reduce((t, r) => t.then(async (t) => {
18
+ if (g.rateLimitHit) return [...t, {
18
19
  currentRefType: "unknown",
19
20
  publishedAt: null,
20
21
  version: null,
21
- actionName: i,
22
+ actionName: r,
22
23
  sha: null
23
24
  }];
24
- let a = i.split("/");
25
- if (a.length < 2) return [...n, {
25
+ let a = r.split("/");
26
+ if (a.length < 2) return [...t, {
26
27
  currentRefType: "unknown",
27
28
  publishedAt: null,
28
29
  version: null,
29
- actionName: i,
30
+ actionName: r,
30
31
  sha: null
31
32
  }];
32
- let [o, u] = a;
33
- if (!o || !u) return [...n, {
33
+ let [o, s] = a;
34
+ if (!o || !s) return [...t, {
34
35
  currentRefType: "unknown",
35
36
  publishedAt: null,
36
37
  version: null,
37
- actionName: i,
38
+ actionName: r,
38
39
  sha: null
39
40
  }];
40
41
  try {
41
- let a = h.get(i)[0]?.version, p = c(a);
42
- if (a && !s(a) && !t(a)) {
43
- let e = await d.getRefType(o, u, a);
44
- if (p = e === "branch" || e === "tag" ? e : p, e === "branch" && !f) return [...n, {
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, {
45
46
  currentRefType: p,
46
47
  skipReason: "branch",
47
48
  status: "skipped",
48
49
  publishedAt: null,
49
50
  version: null,
50
- actionName: i,
51
+ actionName: r,
51
52
  sha: null
52
53
  }];
53
54
  }
54
- let m = await d.getLatestRelease(o, u);
55
+ let m = await d.getLatestRelease(o, s);
55
56
  if (!m) {
56
- let e = await d.getAllReleases(o, u, 1);
57
+ let e = await d.getAllReleases(o, s, 1);
57
58
  m = e.find((e) => !e.isPrerelease) ?? e[0] ?? null;
58
59
  }
59
60
  if (m) {
60
- let { publishedAt: a, version: s, sha: c } = m, f = !1;
61
+ let { publishedAt: a, version: c, sha: l } = m, f = !1;
61
62
  {
62
- let n = e(s), i = !!(s && s.trim() !== ""), a = i && /^v?\d+$/u.test(s.trim()), o = r.valid(n);
63
- f = !i || a || !o || !t(s);
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);
64
65
  }
65
66
  if (f) {
66
- let a = await d.getAllTags(o, u, 30);
67
+ let a = await d.getAllTags(o, s, 30);
67
68
  if (a.length > 0) {
68
- let c = a.filter((e) => t(e.tag)).map((t) => ({
69
- v: r.valid(e(t.tag)),
70
- raw: t
69
+ let l = a.filter((t) => e(t.tag)).map((e) => ({
70
+ v: i.valid(n(e.tag)),
71
+ raw: e
71
72
  }));
72
- if (c.length > 0) {
73
- c.sort((e, t) => {
74
- let n = r.rcompare(e.v, t.v);
73
+ if (l.length > 0) {
74
+ l.sort((e, t) => {
75
+ let n = i.rcompare(e.v, t.v);
75
76
  if (n !== 0) return n;
76
- let i = +!!/\d+\.\d+/u.test(e.raw.tag);
77
- return +!!/\d+\.\d+/u.test(t.raw.tag) - i;
77
+ let r = +!!/\d+\.\d+/u.test(e.raw.tag);
78
+ return +!!/\d+\.\d+/u.test(t.raw.tag) - r;
78
79
  });
79
- let t = c[0].raw, a = r.valid(e(s) ?? void 0);
80
- if (!a || r.gt(c[0].v, a) || r.eq(c[0].v, a) && /\d+\.\d+/u.test(t.tag)) {
81
- let e = t.tag, r = t.sha?.length ? t.sha : null;
82
- if (!r && e) try {
83
- r = await d.getTagSha(o, u, e);
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);
84
85
  } catch (e) {
85
- if (l(e)) throw e;
86
+ if (u(e)) throw e;
86
87
  }
87
- return [...n, {
88
+ return [...t, {
88
89
  currentRefType: p,
89
- version: e,
90
+ version: n,
90
91
  publishedAt: null,
91
- sha: r,
92
- actionName: i
92
+ sha: i,
93
+ actionName: r
93
94
  }];
94
95
  }
95
96
  }
96
97
  }
97
98
  }
98
- if (s) {
99
- let e = c;
99
+ if (c) {
100
+ let e = l;
100
101
  try {
101
- c = await d.getTagSha(o, u, s) ?? e;
102
+ l = await d.getTagSha(o, s, c) ?? e;
102
103
  } catch (t) {
103
- if (l(t)) throw t;
104
- c = e;
104
+ if (u(t)) throw t;
105
+ l = e;
105
106
  }
106
107
  }
107
- return [...n, {
108
+ return [...t, {
108
109
  currentRefType: p,
109
110
  status: "ok",
110
111
  publishedAt: a,
111
- actionName: i,
112
- version: s,
113
- sha: c
112
+ actionName: r,
113
+ version: c,
114
+ sha: l
114
115
  }];
115
116
  }
116
- let g = await d.getAllTags(o, u, 30);
117
+ let g = await d.getAllTags(o, s, 30);
117
118
  if (g.length > 0) {
118
- let a = g.filter((e) => t(e.tag)).map((t) => ({
119
- v: r.valid(e(t.tag)),
120
- raw: t
121
- })), s;
119
+ let a = g.filter((t) => e(t.tag)).map((e) => ({
120
+ v: i.valid(n(e.tag)),
121
+ raw: e
122
+ })), c;
122
123
  a.length > 0 ? (a.sort((e, t) => {
123
- let n = r.rcompare(e.v, t.v);
124
+ let n = i.rcompare(e.v, t.v);
124
125
  if (n !== 0) return n;
125
- let i = +!!/\d+\.\d+/u.test(e.raw.tag);
126
- return +!!/\d+\.\d+/u.test(t.raw.tag) - i;
127
- }), s = a[0].raw) : s = g[0];
128
- let c = s.tag, f = s.sha?.length ? s.sha : null;
129
- if (!f && c) try {
130
- f = await d.getTagSha(o, u, c);
126
+ let r = +!!/\d+\.\d+/u.test(e.raw.tag);
127
+ 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);
131
132
  } catch (e) {
132
- if (l(e)) throw e;
133
+ if (u(e)) throw e;
133
134
  }
134
- return [...n, {
135
+ return [...t, {
135
136
  currentRefType: p,
136
137
  status: "ok",
137
138
  publishedAt: null,
138
- actionName: i,
139
- version: c,
139
+ actionName: r,
140
+ version: l,
140
141
  sha: f
141
142
  }];
142
143
  }
143
- return [...n, {
144
+ return [...t, {
144
145
  currentRefType: p,
145
146
  publishedAt: null,
146
147
  version: null,
147
- actionName: i,
148
+ actionName: r,
148
149
  sha: null
149
150
  }];
150
151
  } catch (e) {
151
- return e instanceof Error && e.name === "GitHubRateLimitError" ? (g.rateLimitHit = !0, g.rateLimitError = e, [...n, {
152
+ return e instanceof Error && e.name === "GitHubRateLimitError" ? (g.rateLimitHit = !0, g.rateLimitError = e, [...t, {
152
153
  currentRefType: "unknown",
153
154
  publishedAt: null,
154
155
  version: null,
155
- actionName: i,
156
+ actionName: r,
156
157
  sha: null
157
- }]) : (console.warn(`Failed to check ${i}:`, e), [...n, {
158
+ }]) : (console.warn(`Failed to check ${r}:`, e), [...t, {
158
159
  currentRefType: "unknown",
159
160
  publishedAt: null,
160
161
  version: null,
161
- actionName: i,
162
+ actionName: r,
162
163
  sha: null
163
164
  }]);
164
165
  }
165
166
  }), Promise.resolve([]));
166
167
  if (g.rateLimitError) {
167
- let e = !!(o ?? 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);
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);
168
169
  throw n.name = "GitHubRateLimitError", n;
169
170
  }
170
171
  let v = /* @__PURE__ */ new Map();
@@ -180,7 +181,7 @@ async function i(i, o, u) {
180
181
  let y = [];
181
182
  for (let e of m) {
182
183
  let t = v.get(e.name);
183
- t ? y.push(a(e, {
184
+ t ? y.push(o(e, {
184
185
  publishedAt: t.publishedAt,
185
186
  version: t.version,
186
187
  sha: t.sha
@@ -189,68 +190,68 @@ async function i(i, o, u) {
189
190
  skipReason: t.skipReason,
190
191
  status: t.status,
191
192
  style: p
192
- })) : y.push(a(e, {
193
+ })) : y.push(o(e, {
193
194
  publishedAt: null,
194
195
  version: null,
195
196
  sha: null
196
197
  }, {
197
- currentRefType: c(e.version),
198
+ currentRefType: l(e.version),
198
199
  style: p
199
200
  }));
200
201
  }
201
202
  return y;
202
203
  }
203
- function a(t, n, i) {
204
- let { version: a, sha: c, publishedAt: l } = n, u = t.version ?? "unknown", d = e(u), f = a ? e(a) : null, p = i.currentRefType, { style: m } = i, h = i.status ?? "ok", g = i.skipReason, _ = !1, v = !1;
205
- if (h === "skipped") return {
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;
206
+ if (_ === "skipped") return {
206
207
  currentRefType: p,
207
- currentVersion: u,
208
+ currentVersion: d,
208
209
  isBreaking: !1,
209
210
  hasUpdate: !1,
210
- latestVersion: a,
211
- publishedAt: l,
212
- skipReason: g,
213
- latestSha: c,
214
- action: t,
215
- status: h
211
+ latestVersion: o,
212
+ publishedAt: u,
213
+ skipReason: v,
214
+ latestSha: l,
215
+ action: e,
216
+ status: _
216
217
  };
217
- if (d && s(d)) c ? _ = !o(d, c) : f && (_ = !0);
218
- else if (d && f) {
219
- let e = r.valid(d), n = r.valid(f);
220
- if (e && n) {
221
- if (_ = r.lt(e, n), _) {
222
- let t = r.major(e);
223
- v = r.major(n) > t;
218
+ if (f && c(f)) l ? y = !s(f, l) : g && (y = !0);
219
+ else if (f && g) {
220
+ let t = i.valid(f), n = i.valid(g);
221
+ if (t && n) {
222
+ if (y = i.lt(t, n), y) {
223
+ let e = i.major(t);
224
+ b = i.major(n) > e;
224
225
  }
225
- !_ && r.eq(e, n) && !s(t.version) && c && m === "sha" && (_ = !0, v = !1);
226
- } else d !== f && (_ = !0);
226
+ !y && i.eq(t, n) && !c(e.version) && l && m === "sha" && (y = !0, b = !1);
227
+ } else f !== g && (y = !0);
227
228
  }
228
229
  return {
229
230
  currentRefType: p,
230
- currentVersion: u,
231
- latestVersion: a,
232
- publishedAt: l,
233
- isBreaking: v,
234
- skipReason: g,
235
- latestSha: c,
236
- hasUpdate: _,
237
- action: t,
238
- status: h
231
+ currentVersion: d,
232
+ latestVersion: o,
233
+ publishedAt: u,
234
+ isBreaking: b,
235
+ skipReason: v,
236
+ latestSha: l,
237
+ hasUpdate: y,
238
+ action: e,
239
+ status: _
239
240
  };
240
241
  }
241
- function o(e, t) {
242
+ function s(e, t) {
242
243
  let n = e.replace(/^v/u, ""), r = t.replace(/^v/u, ""), i = Math.min(n.length, r.length);
243
244
  return i < 7 ? !1 : n.slice(0, Math.max(0, i)).toLowerCase() === r.slice(0, Math.max(0, i)).toLowerCase();
244
245
  }
245
- function s(e) {
246
+ function c(e) {
246
247
  if (!e) return !1;
247
248
  let t = e.replace(/^v/u, "");
248
249
  return /^[0-9a-f]{7,40}$/iu.test(t);
249
250
  }
250
- function c(e) {
251
- return e ? s(e) ? "sha" : t(e) ? "tag" : "unknown" : "unknown";
251
+ function l(t) {
252
+ return t ? c(t) ? "sha" : e(t) ? "tag" : "unknown" : "unknown";
252
253
  }
253
- function l(e) {
254
+ function u(e) {
254
255
  return e instanceof Error && e.name === "GitHubRateLimitError";
255
256
  }
256
- export { i as checkUpdates };
257
+ export { a as checkUpdates };
@@ -15,7 +15,7 @@ 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) : "";
18
+ let o = i(e.action.name), s = e.currentVersion ? i(e.currentVersion) : "", c = s ? String.raw`(?=(?:['"]|[ \t\]}{,#]|$))` : "";
19
19
  if (o.includes("\n") || o.includes("\r")) {
20
20
  console.error(`Invalid action name: ${e.action.name}`);
21
21
  continue;
@@ -32,8 +32,8 @@ async function n(n) {
32
32
  console.error(`Invalid SHA format: ${t}`);
33
33
  continue;
34
34
  }
35
- let c = String.raw`['"]?\buses\b['"]?\s*:\s*`, l = String.raw`(?:^[^\S\n]*(?:-[^\S\n]*)?|[{\[,][^\S\n]*)` + c, u = new RegExp(String.raw`(?<prefix>${l})` + String.raw`(?<quote>['"]?)` + String.raw`(?<name>${o})@${s}` + String.raw`\k<quote>` + String.raw`(?<after>[ \t\]}{,]*)` + String.raw`(?<comment>[^\S\r\n]*#[^\r\n]*)?`, "gm");
36
- a = a.replace(u, (i, ...a) => {
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
+ a = a.replace(d, (i, ...a) => {
37
37
  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
38
  if (n === "sha") p = d && !l.comment && s !== "" ? "" : `${f}# ${e.latestVersion}`;
39
39
  else if (l.comment && !r(l.comment)) {
@@ -1,24 +1,35 @@
1
- function e(e, t) {
2
- return e.hasUpdate ? t === "sha" || e.currentRefType === "sha" ? e.latestSha ? {
3
- ...e,
4
- targetRef: e.latestSha,
5
- targetRefStyle: "sha"
6
- } : {
7
- ...e,
1
+ import { preserveTagFormat as e } from "../versions/preserve-tag-format.js";
2
+ function t(t, n) {
3
+ if (!t.hasUpdate) return {
4
+ ...t,
8
5
  targetRefStyle: null,
9
6
  targetRef: null
10
- } : e.currentRefType === "tag" && e.latestVersion ? {
11
- ...e,
12
- targetRef: e.latestVersion,
13
- targetRefStyle: "tag"
7
+ };
8
+ if (n === "sha" || t.currentRefType === "sha") return t.latestSha ? {
9
+ ...t,
10
+ targetRef: t.latestSha,
11
+ targetRefStyle: "sha"
14
12
  } : {
15
- ...e,
13
+ ...t,
16
14
  targetRefStyle: null,
17
15
  targetRef: null
18
- } : {
19
- ...e,
16
+ };
17
+ if (t.currentRefType === "tag" && t.latestVersion) {
18
+ let n = e(t.currentVersion, t.latestVersion);
19
+ return n ? {
20
+ ...t,
21
+ targetRef: n,
22
+ targetRefStyle: "tag"
23
+ } : {
24
+ ...t,
25
+ targetRefStyle: null,
26
+ targetRef: null
27
+ };
28
+ }
29
+ return {
30
+ ...t,
20
31
  targetRefStyle: null,
21
32
  targetRef: null
22
33
  };
23
34
  }
24
- export { e as resolveTargetReference };
35
+ export { t as resolveTargetReference };
@@ -1,14 +1,14 @@
1
- import { normalizeVersion as e } from "./normalize-version.js";
2
- import { isSemverLike as t } from "./is-semver-like.js";
1
+ import { isSemverLike as e } from "./is-semver-like.js";
2
+ import { normalizeVersion as t } from "./normalize-version.js";
3
3
  import n from "semver";
4
4
  function r(r, a, o) {
5
- if (!a || !t(a) || r.length === 0) return null;
6
- let s = n.valid(e(a));
5
+ if (!a || !e(a) || r.length === 0) return null;
6
+ let s = n.valid(t(a));
7
7
  if (!s) return null;
8
8
  let c = n.major(s), l = n.minor(s), u = [];
9
9
  for (let i of r) {
10
- if (!t(i.tag)) continue;
11
- let r = n.valid(e(i.tag));
10
+ if (!e(i.tag)) continue;
11
+ let r = n.valid(t(i.tag));
12
12
  r && n.gt(r, s) && n.major(r) === c && (o === "patch" && n.minor(r) !== l || u.push({
13
13
  tag: i,
14
14
  parsed: r
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Preserve the semver granularity of the current tag when projecting a newer
3
+ * tag reference.
4
+ *
5
+ * Examples:
6
+ *
7
+ * - `v6` + `v7.0.2` -> `v7`
8
+ * - `v6.1` + `v6.2.3` -> `v6.2`
9
+ * - `v6.1.4` + `v6.2.3` -> `v6.2.3`.
10
+ *
11
+ * Returns null when the target tag cannot be preserved safely.
12
+ *
13
+ * @param currentVersion - Current tag reference found in the workflow.
14
+ * @param latestVersion - Latest resolved tag reference.
15
+ * @returns Preserved tag reference or null when preservation is unsafe.
16
+ */
17
+ export declare function preserveTagFormat(currentVersion: undefined | string | null, latestVersion: undefined | string | null): string | null;
@@ -0,0 +1,11 @@
1
+ import { isSemverLike as e } from "./is-semver-like.js";
2
+ function t(t, n) {
3
+ if (!t || !n) return null;
4
+ let r = t.trim(), i = n.trim();
5
+ if (!e(r) || !e(i)) return null;
6
+ let a = r.startsWith("v");
7
+ if (a !== i.startsWith("v")) return null;
8
+ let o = r.replace(/^v/u, "").split("."), s = i.replace(/^v/u, "").split(".");
9
+ return s.length < o.length ? null : `${a ? "v" : ""}${s.slice(0, o.length).join(".")}`;
10
+ }
11
+ export { t as preserveTagFormat };
package/dist/package.js CHANGED
@@ -1,2 +1,2 @@
1
- var e = "1.14.1";
1
+ var e = "1.14.3";
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.1",
3
+ "version": "1.14.3",
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.7.4",
44
- "yaml": "^2.8.3"
42
+ "semver": "^7.8.4",
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
@@ -3,9 +3,8 @@
3
3
  <img
4
4
  src="https://raw.githubusercontent.com/azat-io/actions-up/main/assets/logo.svg"
5
5
  alt="Actions Up logo"
6
- width="160"
7
- height="160"
8
6
  align="right"
7
+ width="160"
9
8
  />
10
9
 
11
10
  [![Version](https://img.shields.io/npm/v/actions-up.svg?color=fff&labelColor=4493f8)](https://npmjs.com/package/actions-up)
@@ -102,7 +101,8 @@ Per-project
102
101
  npm install --save-dev actions-up
103
102
  ```
104
103
 
105
- Alternatively, you can install Actions Up with [Homebrew](https://brew.sh)
104
+ Alternatively, you can install Actions Up with
105
+ [Homebrew](https://formulae.brew.sh/formula/actions-up)
106
106
 
107
107
  ```bash
108
108
  brew install actions-up
@@ -214,9 +214,11 @@ Use `--style preserve` to keep the current reference style:
214
214
  npx actions-up --style preserve
215
215
  ```
216
216
 
217
- `preserve` keeps tag references on tags and SHA references on SHAs. For example,
218
- `actions/checkout@v5` updates to `actions/checkout@v6.0.2`, while a SHA-pinned
219
- action continues updating to the latest resolved SHA.
217
+ `preserve` keeps tag references on tags and SHA references on SHAs. Tag refs
218
+ also keep their granularity, so `actions/checkout@v5` updates to
219
+ `actions/checkout@v6`, while `actions/checkout@v5.0` updates to
220
+ `actions/checkout@v6.0`. A SHA-pinned action continues updating to the latest
221
+ resolved SHA.
220
222
 
221
223
  ## GitHub Actions Integration
222
224