actions-up 1.13.0 → 1.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/dist/cli/build-json-report.d.ts +57 -36
  2. package/dist/cli/build-json-report.js +22 -18
  3. package/dist/cli/index.js +137 -122
  4. package/dist/cli/merge-scan-results.js +2 -2
  5. package/dist/cli/normalize-update-mode.js +2 -2
  6. package/dist/cli/normalize-update-style.d.ts +8 -0
  7. package/dist/cli/normalize-update-style.js +6 -0
  8. package/dist/cli/print-mode-warning.js +5 -5
  9. package/dist/cli/print-skipped-warning.d.ts +4 -1
  10. package/dist/cli/print-skipped-warning.js +10 -6
  11. package/dist/cli/resolve-scan-directories.js +8 -8
  12. package/dist/cli/validate-cli-options.js +2 -2
  13. package/dist/core/api/check-updates.d.ts +2 -0
  14. package/dist/core/api/check-updates.js +120 -96
  15. package/dist/core/api/create-github-client.js +26 -26
  16. package/dist/core/api/get-all-releases.js +12 -12
  17. package/dist/core/api/get-all-tags.js +4 -4
  18. package/dist/core/api/get-compatible-update.js +6 -6
  19. package/dist/core/api/get-latest-release.js +15 -15
  20. package/dist/core/api/get-reference-type.js +5 -5
  21. package/dist/core/api/get-tag-info.js +14 -14
  22. package/dist/core/api/get-tag-sha.js +7 -7
  23. package/dist/core/api/internal-rate-limit-error.js +2 -2
  24. package/dist/core/api/make-request.js +4 -4
  25. package/dist/core/api/resolve-github-token-sync.js +7 -7
  26. package/dist/core/api/update-rate-limit-info.js +3 -6
  27. package/dist/core/ast/guards/has-range.js +2 -2
  28. package/dist/core/ast/guards/is-node.js +2 -2
  29. package/dist/core/ast/guards/is-pair.js +2 -2
  30. package/dist/core/ast/guards/is-scalar.js +2 -2
  31. package/dist/core/ast/guards/is-yaml-map.js +2 -2
  32. package/dist/core/ast/guards/is-yaml-sequence.js +2 -2
  33. package/dist/core/ast/scanners/scan-composite-action-ast.js +13 -13
  34. package/dist/core/ast/scanners/scan-workflow-ast.js +21 -21
  35. package/dist/core/ast/update/apply-updates.d.ts +1 -1
  36. package/dist/core/ast/update/apply-updates.js +35 -22
  37. package/dist/core/ast/utils/extract-uses-from-steps.js +14 -14
  38. package/dist/core/ast/utils/find-map-pair.js +6 -6
  39. package/dist/core/ast/utils/get-line-number.js +4 -4
  40. package/dist/core/constants.js +2 -2
  41. package/dist/core/filters/parse-exclude-patterns.js +2 -2
  42. package/dist/core/fs/find-yaml-files-recursive.js +9 -9
  43. package/dist/core/fs/is-yaml-file.js +2 -2
  44. package/dist/core/fs/read-yaml-document.js +6 -6
  45. package/dist/core/ignore/should-ignore.js +4 -4
  46. package/dist/core/index.js +5 -5
  47. package/dist/core/interactive/format-version.js +14 -14
  48. package/dist/core/interactive/pad-string.js +4 -4
  49. package/dist/core/interactive/prompt-update-selection.js +162 -135
  50. package/dist/core/interactive/strip-ansi.js +2 -2
  51. package/dist/core/parsing/parse-action-reference.js +2 -2
  52. package/dist/core/scan-action-file.js +6 -6
  53. package/dist/core/scan-github-actions.js +83 -83
  54. package/dist/core/scan-recursive.js +24 -24
  55. package/dist/core/scan-workflow-file.js +6 -6
  56. package/dist/core/schema/composite/is-composite-action-runs.js +2 -2
  57. package/dist/core/schema/composite/is-composite-action-structure.js +2 -2
  58. package/dist/core/schema/workflow/is-workflow-structure.js +2 -2
  59. package/dist/core/updates/resolve-target-reference.d.ts +10 -0
  60. package/dist/core/updates/resolve-target-reference.js +24 -0
  61. package/dist/core/versions/find-compatible-tag.js +16 -16
  62. package/dist/core/versions/get-update-level.js +8 -8
  63. package/dist/core/versions/is-semver-like.js +2 -2
  64. package/dist/core/versions/is-sha.js +2 -2
  65. package/dist/core/versions/normalize-version.js +4 -4
  66. package/dist/core/versions/read-inline-version-comment.js +4 -4
  67. package/dist/package.js +2 -2
  68. package/dist/types/action-update.d.ts +16 -1
  69. package/dist/types/update-style.d.ts +4 -0
  70. package/package.json +2 -2
  71. package/readme.md +24 -5
@@ -1,156 +1,156 @@
1
- import { ACTIONS_DIRECTORY, GITHUB_DIRECTORY, WORKFLOWS_DIRECTORY } from "./constants.js";
2
- import { isYamlFile } from "./fs/is-yaml-file.js";
3
- import { scanWorkflowFile } from "./scan-workflow-file.js";
4
- import { scanActionFile } from "./scan-action-file.js";
5
- import { isAbsolute, join, relative, resolve } from "node:path";
6
- import { readFile, readdir, stat } from "node:fs/promises";
7
- async function scanGitHubActions(p = process.cwd(), m = GITHUB_DIRECTORY) {
1
+ import { ACTIONS_DIRECTORY as e, GITHUB_DIRECTORY as t, WORKFLOWS_DIRECTORY as n } from "./constants.js";
2
+ import { isYamlFile as r } from "./fs/is-yaml-file.js";
3
+ import { scanWorkflowFile as i } from "./scan-workflow-file.js";
4
+ import { scanActionFile as a } from "./scan-action-file.js";
5
+ import { isAbsolute as o, join as s, relative as c, resolve as l } from "node:path";
6
+ import { readFile as u, readdir as d, stat as f } from "node:fs/promises";
7
+ async function p(u = process.cwd(), p = t) {
8
8
  let h = {
9
9
  compositeActions: /* @__PURE__ */ new Map(),
10
10
  workflows: /* @__PURE__ */ new Map(),
11
11
  actions: []
12
- }, g = resolve(p);
13
- function _(e, a) {
14
- let o = relative(e, a);
15
- return o !== "" && !o.startsWith("..") && !isAbsolute(o);
12
+ }, g = l(u);
13
+ function _(e, t) {
14
+ let n = c(e, t);
15
+ return n !== "" && !n.startsWith("..") && !o(n);
16
16
  }
17
- let v = join(g, m);
17
+ let v = s(g, p);
18
18
  function y(e) {
19
19
  return e.includes("..") || e.includes("/") || e.includes("\\") ? (console.warn(`Skipping invalid name: ${e}`), !1) : !0;
20
20
  }
21
21
  async function b(e) {
22
22
  try {
23
- let a = await stat(e);
24
- return typeof a.isFile == "function" ? a.isFile() : !1;
23
+ let t = await f(e);
24
+ return typeof t.isFile == "function" ? t.isFile() : !1;
25
25
  } catch {
26
26
  return !1;
27
27
  }
28
28
  }
29
- let x = join(v, WORKFLOWS_DIRECTORY);
29
+ let x = s(v, n);
30
30
  try {
31
- if ((await stat(x)).isDirectory()) {
32
- let e = (await readdir(x)).filter((e) => y(e) ? isYamlFile(e) : !1).map(async (e) => {
33
- let a = join(x, e);
31
+ if ((await f(x)).isDirectory()) {
32
+ let e = (await d(x)).filter((e) => y(e) ? r(e) : !1).map(async (e) => {
33
+ let t = s(x, e);
34
34
  try {
35
- let s = await scanWorkflowFile(a);
35
+ let r = await i(t);
36
36
  return {
37
- path: `${m}/${WORKFLOWS_DIRECTORY}/${e}`,
37
+ path: `${p}/${n}/${e}`,
38
38
  success: !0,
39
- actions: s
39
+ actions: r
40
40
  };
41
41
  } catch {
42
42
  return {
43
- path: `${m}/${WORKFLOWS_DIRECTORY}/${e}`,
43
+ path: `${p}/${n}/${e}`,
44
44
  success: !1,
45
45
  actions: []
46
46
  };
47
47
  }
48
- }), a = await Promise.all(e);
49
- for (let e of a) e.success && e.path && (e.actions.length > 0 ? (h.workflows.set(e.path, e.actions), h.actions.push(...e.actions)) : h.workflows.set(e.path, []));
48
+ }), t = await Promise.all(e);
49
+ for (let e of t) e.success && e.path && (e.actions.length > 0 ? (h.workflows.set(e.path, e.actions), h.actions.push(...e.actions)) : h.workflows.set(e.path, []));
50
50
  }
51
51
  } catch {}
52
52
  try {
53
- let e = join(g, "action.yml"), a = join(g, "action.yaml"), o = null, s = [];
53
+ let e = s(g, "action.yml"), t = s(g, "action.yaml"), n = null, r = [];
54
54
  if (await b(e)) try {
55
- s = await scanActionFile(e), o = e;
55
+ r = await a(e), n = e;
56
56
  } catch {
57
- o = null;
57
+ n = null;
58
58
  }
59
- if (!o && await b(a)) try {
60
- s = await scanActionFile(a), o = a;
59
+ if (!n && await b(t)) try {
60
+ r = await a(t), n = t;
61
61
  } catch {
62
- o = null;
62
+ n = null;
63
63
  }
64
- if (o) {
65
- let e = relative(g, o);
66
- h.compositeActions.set(e, e), s.length > 0 && h.actions.push(...s);
64
+ if (n) {
65
+ let e = c(g, n);
66
+ h.compositeActions.set(e, e), r.length > 0 && h.actions.push(...r);
67
67
  }
68
68
  } catch {}
69
- let S = join(v, ACTIONS_DIRECTORY);
69
+ let S = s(v, e);
70
70
  try {
71
- if ((await stat(S)).isDirectory()) {
72
- let a = (await readdir(S)).map(async (a) => {
73
- if (!y(a)) return null;
74
- let o = join(S, a);
71
+ if ((await f(S)).isDirectory()) {
72
+ let t = (await d(S)).map(async (t) => {
73
+ if (!y(t)) return null;
74
+ let n = s(S, t);
75
75
  try {
76
- if (!(await stat(o)).isDirectory()) return null;
77
- let s = join(o, "action.yml"), c = [];
76
+ if (!(await f(n)).isDirectory()) return null;
77
+ let r = s(n, "action.yml"), i = [];
78
78
  try {
79
- c = await scanActionFile(s);
79
+ i = await a(r);
80
80
  } catch {
81
81
  try {
82
- s = join(o, "action.yaml"), c = await scanActionFile(s);
82
+ r = s(n, "action.yaml"), i = await a(r);
83
83
  } catch {
84
84
  return null;
85
85
  }
86
86
  }
87
87
  return {
88
- path: `${m}/${ACTIONS_DIRECTORY}/${a}`,
89
- name: a,
90
- actions: c
88
+ path: `${p}/${e}/${t}`,
89
+ name: t,
90
+ actions: i
91
91
  };
92
92
  } catch {
93
93
  return null;
94
94
  }
95
- }), o = await Promise.all(a);
96
- for (let e of o) e && (h.compositeActions.set(e.name, e.path), h.actions.push(...e.actions));
95
+ }), n = await Promise.all(t);
96
+ for (let e of n) e && (h.compositeActions.set(e.name, e.path), h.actions.push(...e.actions));
97
97
  }
98
98
  } catch {}
99
99
  try {
100
- let e = await getCurrentRepoSlug(g);
100
+ let e = await m(g);
101
101
  if (e) {
102
102
  if (process.env.ACTIONS_UP_TEST_THROW === "1") throw Error("test");
103
- let a = /* @__PURE__ */ new Set(), o = [];
104
- for (let s of h.actions) {
105
- if (s.type !== "external") continue;
106
- let c = s.name.split("/");
107
- if (c.length < 3 || `${c[0]}/${c[1]}` !== e) continue;
108
- let l = join(g, ...c.slice(2));
109
- _(g, l) && (a.has(l) || (a.add(l), o.push(l)));
103
+ let t = /* @__PURE__ */ new Set(), n = [];
104
+ for (let r of h.actions) {
105
+ if (r.type !== "external") continue;
106
+ let i = r.name.split("/");
107
+ if (i.length < 3 || `${i[0]}/${i[1]}` !== e) continue;
108
+ let a = s(g, ...i.slice(2));
109
+ _(g, a) && (t.has(a) || (t.add(a), n.push(a)));
110
110
  }
111
- async function s() {
112
- if (o.length === 0) return;
113
- let c = o.splice(0), u = await Promise.all(c.map(async (o) => {
111
+ async function r() {
112
+ if (n.length === 0) return;
113
+ let i = n.splice(0), o = await Promise.all(i.map(async (n) => {
114
114
  try {
115
- let s = join(o, "action.yml"), c = join(o, "action.yaml"), u = s;
115
+ let r = s(n, "action.yml"), i = s(n, "action.yaml"), o = r;
116
116
  try {
117
- if (!(await stat(s)).isFile()) throw Error("not a file");
117
+ if (!(await f(r)).isFile()) throw Error("not a file");
118
118
  } catch {
119
- if (!(await stat(c)).isFile()) throw Error("not a file");
120
- u = c;
119
+ if (!(await f(i)).isFile()) throw Error("not a file");
120
+ o = i;
121
121
  }
122
- let d = await scanActionFile(u);
123
- d.length > 0 && h.actions.push(...d);
124
- let f = [];
125
- for (let o of d) {
126
- if (o.type !== "external") continue;
127
- let s = o.name.split("/");
128
- if (s.length < 3 || `${s[0]}/${s[1]}` !== e) continue;
129
- let c = join(g, ...s.slice(2));
130
- _(g, c) && (a.has(c) || (a.add(c), f.push(c)));
122
+ let c = await a(o);
123
+ c.length > 0 && h.actions.push(...c);
124
+ let l = [];
125
+ for (let n of c) {
126
+ if (n.type !== "external") continue;
127
+ let r = n.name.split("/");
128
+ if (r.length < 3 || `${r[0]}/${r[1]}` !== e) continue;
129
+ let i = s(g, ...r.slice(2));
130
+ _(g, i) && (t.has(i) || (t.add(i), l.push(i)));
131
131
  }
132
- return f;
132
+ return l;
133
133
  } catch {
134
134
  return [];
135
135
  }
136
136
  }));
137
- for (let e of u) for (let a of e) o.push(a);
138
- await s();
137
+ for (let e of o) for (let t of e) n.push(t);
138
+ await r();
139
139
  }
140
- await s();
140
+ await r();
141
141
  }
142
142
  } catch {}
143
143
  return h;
144
144
  }
145
- async function getCurrentRepoSlug(e) {
146
- let a = process.env.GITHUB_REPOSITORY;
147
- if (a && /^[^\s/]+\/[^\s/]+$/u.test(a)) return a;
145
+ async function m(e) {
146
+ let t = process.env.GITHUB_REPOSITORY;
147
+ if (t && /^[^\s/]+\/[^\s/]+$/u.test(t)) return t;
148
148
  try {
149
- let a = await readFile(join(e, ".git", "config"), "utf8"), o = a.match(/\[remote "origin"\][\s\S]*?url\s*=\s*(?<url>.+)/u)?.groups?.url?.trim();
150
- if (o ||= a.match(/url\s*=\s*(?<url>.+)/u)?.groups?.url?.trim(), !o) return null;
151
- let s = o.match(/github\.com[/:](?<owner>[^/]+)\/(?<repo>[^./]+)(?:\.git)?$/u);
152
- if (s?.groups) return `${s.groups.owner}/${s.groups.repo}`;
149
+ let t = await u(s(e, ".git", "config"), "utf8"), n = t.match(/\[remote "origin"\][\s\S]*?url\s*=\s*(?<url>.+)/u)?.groups?.url?.trim();
150
+ if (n ||= t.match(/url\s*=\s*(?<url>.+)/u)?.groups?.url?.trim(), !n) return null;
151
+ let r = n.match(/github\.com[/:](?<owner>[^/]+)\/(?<repo>[^./]+)(?:\.git)?$/u);
152
+ if (r?.groups) return `${r.groups.owner}/${r.groups.repo}`;
153
153
  } catch {}
154
154
  return null;
155
155
  }
156
- export { scanGitHubActions };
156
+ export { p as scanGitHubActions };
@@ -1,46 +1,46 @@
1
- import { isCompositeActionStructure } from "./schema/composite/is-composite-action-structure.js";
2
- import { scanCompositeActionAst } from "./ast/scanners/scan-composite-action-ast.js";
3
- import { isWorkflowStructure } from "./schema/workflow/is-workflow-structure.js";
4
- import { findYamlFilesRecursive } from "./fs/find-yaml-files-recursive.js";
5
- import { scanWorkflowAst } from "./ast/scanners/scan-workflow-ast.js";
6
- import { readYamlDocument } from "./fs/read-yaml-document.js";
7
- import { dirname, relative, resolve } from "node:path";
8
- async function scanRecursive(u, d) {
1
+ import { isCompositeActionStructure as e } from "./schema/composite/is-composite-action-structure.js";
2
+ import { scanCompositeActionAst as t } from "./ast/scanners/scan-composite-action-ast.js";
3
+ import { isWorkflowStructure as n } from "./schema/workflow/is-workflow-structure.js";
4
+ import { findYamlFilesRecursive as r } from "./fs/find-yaml-files-recursive.js";
5
+ import { scanWorkflowAst as i } from "./ast/scanners/scan-workflow-ast.js";
6
+ import { readYamlDocument as a } from "./fs/read-yaml-document.js";
7
+ import { dirname as o, relative as s, resolve as c } from "node:path";
8
+ async function l(l, d) {
9
9
  let f = {
10
10
  compositeActions: /* @__PURE__ */ new Map(),
11
11
  workflows: /* @__PURE__ */ new Map(),
12
12
  actions: []
13
- }, p = resolve(u), m = resolve(p, d), h;
13
+ }, p = c(l), m = c(p, d), h;
14
14
  try {
15
- h = await findYamlFilesRecursive(m);
15
+ h = await r(m);
16
16
  } catch {
17
17
  return f;
18
18
  }
19
- let g = h.map(async (o) => {
20
- let s = relative(p, o);
19
+ let g = h.map(async (r) => {
20
+ let o = s(p, r);
21
21
  try {
22
- let { document: c, content: l } = await readYamlDocument(o), u = c.toJSON();
23
- if (isWorkflowStructure(u) && hasKey(u, "jobs")) return {
22
+ let { document: s, content: c } = await a(r), l = s.toJSON();
23
+ if (n(l) && u(l, "jobs")) return {
24
24
  type: "workflow",
25
- path: s,
26
- actions: scanWorkflowAst(c, l, o)
25
+ path: o,
26
+ actions: i(s, c, r)
27
27
  };
28
- if (isCompositeActionStructure(u) && hasKey(u, "runs")) return {
28
+ if (e(l) && u(l, "runs")) return {
29
29
  type: "action",
30
- path: s,
31
- actions: scanCompositeActionAst(c, l, o)
30
+ path: o,
31
+ actions: t(s, c, r)
32
32
  };
33
33
  } catch {}
34
34
  return null;
35
35
  }), _ = await Promise.all(g);
36
36
  for (let e of _) if (e) if (e.type === "workflow") f.workflows.set(e.path, e.actions), f.actions.push(...e.actions);
37
37
  else {
38
- let i = dirname(e.path), a = i === "." || i === "" ? e.path : i;
39
- f.compositeActions.set(a, e.path), f.actions.push(...e.actions);
38
+ let t = o(e.path), n = t === "." || t === "" ? e.path : t;
39
+ f.compositeActions.set(n, e.path), f.actions.push(...e.actions);
40
40
  }
41
41
  return f;
42
42
  }
43
- function hasKey(e, i) {
44
- return typeof e == "object" && !!e && i in e;
43
+ function u(e, t) {
44
+ return typeof e == "object" && !!e && t in e;
45
45
  }
46
- export { scanRecursive };
46
+ export { l as scanRecursive };
@@ -1,7 +1,7 @@
1
- import { scanWorkflowAst } from "./ast/scanners/scan-workflow-ast.js";
2
- import { readYamlDocument } from "./fs/read-yaml-document.js";
3
- async function scanWorkflowFile(n) {
4
- let { document: r, content: i } = await readYamlDocument(n);
5
- return scanWorkflowAst(r, i, n);
1
+ import { scanWorkflowAst as e } from "./ast/scanners/scan-workflow-ast.js";
2
+ import { readYamlDocument as t } from "./fs/read-yaml-document.js";
3
+ async function n(n) {
4
+ let { document: r, content: i } = await t(n);
5
+ return e(r, i, n);
6
6
  }
7
- export { scanWorkflowFile };
7
+ export { n as scanWorkflowFile };
@@ -1,4 +1,4 @@
1
- function isCompositeActionRuns(e) {
1
+ function e(e) {
2
2
  return typeof e != "object" || !e || Array.isArray(e) ? !1 : "using" in e;
3
3
  }
4
- export { isCompositeActionRuns };
4
+ export { e as isCompositeActionRuns };
@@ -1,6 +1,6 @@
1
- function isCompositeActionStructure(e) {
1
+ function e(e) {
2
2
  if (typeof e != "object" || !e || Array.isArray(e)) return !1;
3
3
  let t = e;
4
4
  return "name" in t || "description" in t || "runs" in t;
5
5
  }
6
- export { isCompositeActionStructure };
6
+ export { e as isCompositeActionStructure };
@@ -1,6 +1,6 @@
1
- function isWorkflowStructure(e) {
1
+ function e(e) {
2
2
  if (typeof e != "object" || !e || Array.isArray(e)) return !1;
3
3
  let t = e;
4
4
  return "on" in t || "name" in t || "jobs" in t;
5
5
  }
6
- export { isWorkflowStructure };
6
+ export { e as isWorkflowStructure };
@@ -0,0 +1,10 @@
1
+ import { ActionUpdate } from '../../types/action-update';
2
+ import { UpdateStyle } from '../../types/update-style';
3
+ /**
4
+ * Resolve the final reference that should be written back to the workflow.
5
+ *
6
+ * @param update - Update entry enriched with lookup data.
7
+ * @param style - Effective update style.
8
+ * @returns Update entry with resolved target reference fields.
9
+ */
10
+ export declare function resolveTargetReference(update: ActionUpdate, style: UpdateStyle): ActionUpdate;
@@ -0,0 +1,24 @@
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,
8
+ targetRefStyle: null,
9
+ targetRef: null
10
+ } : e.currentRefType === "tag" && e.latestVersion ? {
11
+ ...e,
12
+ targetRef: e.latestVersion,
13
+ targetRefStyle: "tag"
14
+ } : {
15
+ ...e,
16
+ targetRefStyle: null,
17
+ targetRef: null
18
+ } : {
19
+ ...e,
20
+ targetRefStyle: null,
21
+ targetRef: null
22
+ };
23
+ }
24
+ export { e as resolveTargetReference };
@@ -1,27 +1,27 @@
1
- import { normalizeVersion } from "./normalize-version.js";
2
- import { isSemverLike } from "./is-semver-like.js";
3
- import semver from "semver";
4
- function findCompatibleTag(r, a, o) {
5
- if (!a || !isSemverLike(a) || r.length === 0) return null;
6
- let s = semver.valid(normalizeVersion(a));
1
+ import { normalizeVersion as e } from "./normalize-version.js";
2
+ import { isSemverLike as t } from "./is-semver-like.js";
3
+ import n from "semver";
4
+ function r(r, a, o) {
5
+ if (!a || !t(a) || r.length === 0) return null;
6
+ let s = n.valid(e(a));
7
7
  if (!s) return null;
8
- let c = semver.major(s), l = semver.minor(s), u = [];
8
+ let c = n.major(s), l = n.minor(s), u = [];
9
9
  for (let i of r) {
10
- if (!isSemverLike(i.tag)) continue;
11
- let r = semver.valid(normalizeVersion(i.tag));
12
- r && semver.gt(r, s) && semver.major(r) === c && (o === "patch" && semver.minor(r) !== l || u.push({
10
+ if (!t(i.tag)) continue;
11
+ let r = n.valid(e(i.tag));
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
15
15
  }));
16
16
  }
17
- return u.length === 0 ? null : (u.sort((e, n) => {
18
- let r = semver.rcompare(e.parsed, n.parsed);
17
+ return u.length === 0 ? null : (u.sort((e, t) => {
18
+ let r = n.rcompare(e.parsed, t.parsed);
19
19
  if (r !== 0) return r;
20
- let a = getSemverSpecificity(e.tag.tag);
21
- return getSemverSpecificity(n.tag.tag) - a;
20
+ let a = i(e.tag.tag);
21
+ return i(t.tag.tag) - a;
22
22
  }), u[0].tag);
23
23
  }
24
- function getSemverSpecificity(e) {
24
+ function i(e) {
25
25
  return e.replace(/^v/u, "").split(".").length;
26
26
  }
27
- export { findCompatibleTag };
27
+ export { r as findCompatibleTag };
@@ -1,10 +1,10 @@
1
- import semver from "semver";
2
- function getUpdateLevel(t, r) {
1
+ import e from "semver";
2
+ function t(t, r) {
3
3
  if (!t || !r) return "unknown";
4
- let i = normalizeVersion(t), a = normalizeVersion(r);
4
+ let i = n(t), a = n(r);
5
5
  if (!i || !a) return "unknown";
6
- if (semver.eq(i, a)) return "none";
7
- let o = semver.diff(i, a);
6
+ if (e.eq(i, a)) return "none";
7
+ let o = e.diff(i, a);
8
8
  if (!o) return "none";
9
9
  switch (o) {
10
10
  case "premajor":
@@ -16,8 +16,8 @@ function getUpdateLevel(t, r) {
16
16
  default: return "unknown";
17
17
  }
18
18
  }
19
- function normalizeVersion(t) {
20
- let n = semver.coerce(t);
19
+ function n(t) {
20
+ let n = e.coerce(t);
21
21
  return n ? n.version : null;
22
22
  }
23
- export { getUpdateLevel };
23
+ export { t as getUpdateLevel };
@@ -1,4 +1,4 @@
1
- function isSemverLike(e) {
1
+ function e(e) {
2
2
  return typeof e == "string" && /^v?\d+(?:\.\d+){0,2}$/u.test(e.trim());
3
3
  }
4
- export { isSemverLike };
4
+ export { e as isSemverLike };
@@ -1,6 +1,6 @@
1
- function isSha(e) {
1
+ function e(e) {
2
2
  if (!e) return !1;
3
3
  let t = e.replace(/^v/u, "");
4
4
  return /^[0-9a-f]{7,40}$/iu.test(t);
5
5
  }
6
- export { isSha };
6
+ export { e as isSha };
@@ -1,9 +1,9 @@
1
- import semver from "semver";
2
- function normalizeVersion(t) {
1
+ import e from "semver";
2
+ function t(t) {
3
3
  if (!t) return null;
4
4
  let n = t.replace(/^v/u, "");
5
5
  if (/^[0-9a-f]{7,40}$/iu.test(n)) return t;
6
- let r = semver.coerce(n);
6
+ let r = e.coerce(n);
7
7
  return r ? r.version : t;
8
8
  }
9
- export { normalizeVersion };
9
+ export { t as normalizeVersion };
@@ -1,9 +1,9 @@
1
- import { readFile } from "node:fs/promises";
2
- async function readInlineVersionComment(t, n, r) {
1
+ import { readFile as e } from "node:fs/promises";
2
+ async function t(t, n, r) {
3
3
  try {
4
4
  if (!t || !n || n <= 0) return null;
5
5
  let i = r?.get(t);
6
- i === void 0 && (i = await readFile(t, "utf8"), r && r.set(t, i));
6
+ i === void 0 && (i = await e(t, "utf8"), r && r.set(t, i));
7
7
  let a = i.split("\n"), o = n - 1;
8
8
  if (o < 0 || o >= a.length) return null;
9
9
  let s = a[o].match(/#\s*(?<version>[Vv]?\d+(?:\.\d+){0,2}(?:[+-][\w\-.]+)?)/u);
@@ -11,4 +11,4 @@ async function readInlineVersionComment(t, n, r) {
11
11
  } catch {}
12
12
  return null;
13
13
  }
14
- export { readInlineVersionComment };
14
+ export { t as readInlineVersionComment };
package/dist/package.js CHANGED
@@ -1,2 +1,2 @@
1
- const version = "1.13.0";
2
- export { version };
1
+ var e = "1.14.1";
2
+ export { e as version };
@@ -6,7 +6,17 @@ export interface ActionUpdate {
6
6
  /**
7
7
  * Reason for skipping the update check.
8
8
  */
9
- skipReason?: 'unknown' | 'branch'
9
+ skipReason?: 'unsupported-style' | 'unknown' | 'branch'
10
+
11
+ /**
12
+ * Detected style of the current reference in the source file.
13
+ */
14
+ currentRefType?: 'unknown' | 'branch' | 'sha' | 'tag'
15
+
16
+ /**
17
+ * Style of the final reference that should be written back to the file.
18
+ */
19
+ targetRefStyle?: 'sha' | 'tag' | null
10
20
 
11
21
  /**
12
22
  * Current version string.
@@ -23,6 +33,11 @@ export interface ActionUpdate {
23
33
  */
24
34
  status?: 'skipped' | 'ok'
25
35
 
36
+ /**
37
+ * Final reference that should be written back to the file.
38
+ */
39
+ targetRef?: string | null
40
+
26
41
  /**
27
42
  * SHA hash of the latest version.
28
43
  */
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Strategy used when writing updated action references back to workflow files.
3
+ */
4
+ export type UpdateStyle = 'preserve' | 'sha'
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "actions-up",
3
- "version": "1.13.0",
4
- "description": "Interactive CLI tool to update GitHub Actions to latest versions with SHA pinning",
3
+ "version": "1.14.1",
4
+ "description": "Interactive CLI tool to update GitHub Actions with SHA pinning or preserved refs",
5
5
  "keywords": [
6
6
  "github-actions",
7
7
  "actions",
package/readme.md CHANGED
@@ -16,7 +16,7 @@ Actions Up scans your workflows and composite actions to discover every
16
16
  referenced GitHub Action, then checks for newer releases.
17
17
 
18
18
  Interactively upgrade and pin actions to exact commit SHAs for secure,
19
- reproducible CI and low-friction maintenance.
19
+ reproducible CI, or preserve tag-style references when you need to stay on tags.
20
20
 
21
21
  ## Features
22
22
 
@@ -25,8 +25,8 @@ reproducible CI and low-friction maintenance.
25
25
  `action.yml`/`action.yaml`)
26
26
  - **Reusable Workflows**: Detects and updates reusable workflow calls at the job
27
27
  level
28
- - **SHA pinning**: Updates actions to use commit SHA instead of tags for better
29
- security
28
+ - **Flexible update styles**: Use SHA pinning by default, or preserve tag-style
29
+ references with `--style preserve`
30
30
  - **Batch Updates**: Update multiple actions at once
31
31
  - **Interactive Selection**: Choose which actions to update
32
32
  - **Breaking Changes Detection**: Warns about major version updates
@@ -124,7 +124,7 @@ This will:
124
124
  plus root `action.yml`/`action.yaml`
125
125
  2. Check for available updates
126
126
  3. Show an interactive list to select updates
127
- 4. Apply selected updates with SHA pinning
127
+ 4. Apply selected updates with SHA pinning by default
128
128
 
129
129
  ### Auto-Update Mode
130
130
 
@@ -200,6 +200,24 @@ In `minor` and `patch` modes, Actions Up tries to find the newest compatible tag
200
200
  first (for example, from `@v4` in `minor` mode it will choose the latest
201
201
  `v4.x.y`). If no compatible version exists, that action is skipped.
202
202
 
203
+ ### Update Style
204
+
205
+ By default, Actions Up writes updates as pinned SHAs:
206
+
207
+ ```bash
208
+ npx actions-up --style sha
209
+ ```
210
+
211
+ Use `--style preserve` to keep the current reference style:
212
+
213
+ ```bash
214
+ npx actions-up --style preserve
215
+ ```
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.
220
+
203
221
  ## GitHub Actions Integration
204
222
 
205
223
  ### Automated PR Checks
@@ -500,7 +518,8 @@ Ignore comments (file/block/next-line/inline):
500
518
  Interactive CLI for developers who want control over GitHub Actions updates.
501
519
 
502
520
  - **vs. Dependabot/Renovate:** Dependabot and Renovate update via pull requests;
503
- Actions Up is an interactive CLI with explicit SHA pinning.
521
+ Actions Up is an interactive CLI with explicit SHA pinning by default and an
522
+ opt-in preserve mode for tag users.
504
523
  - **vs. pinact:** pinact is a CLI to pin and update Actions and reusable
505
524
  workflows; Actions Up adds interactive selection and major update warnings.
506
525
  - **Zero-config:** `npx actions-up` runs immediately.