actions-up 1.4.1 → 1.5.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.
package/bin/actions-up.js CHANGED
File without changes
package/dist/cli/index.js CHANGED
@@ -11,11 +11,11 @@ import pc from "picocolors";
11
11
  import cac from "cac";
12
12
  function run() {
13
13
  let l = cac("actions-up");
14
- l.help().version(version).option("--dry-run", "Preview changes without applying them").option("--exclude <regex>", "Exclude actions by regex (repeatable)").option("--yes, -y", "Skip all confirmations").command("", "Update GitHub Actions").action(async (s) => {
14
+ l.help().version(version).option("--dir <directory>", "Custom directory name (default: .github)").option("--dry-run", "Preview changes without applying them").option("--exclude <regex>", "Exclude actions by regex (repeatable)").option("--yes, -y", "Skip all confirmations").command("", "Update GitHub Actions").action(async (s) => {
15
15
  console.info(pc.cyan("\nšŸš€ Actions Up!\n"));
16
16
  let c = createSpinner("Scanning GitHub Actions...").start();
17
17
  try {
18
- let l = await scanGitHubActions(process.cwd()), u = l.actions.length, d = l.workflows.size, f = l.compositeActions.size;
18
+ let l = await scanGitHubActions(process.cwd(), s.dir), u = l.actions.length, d = l.workflows.size, f = l.compositeActions.size;
19
19
  if (c.success(`Found ${pc.yellow(u)} actions in ${pc.yellow(d)} workflows and ${pc.yellow(f)} composite actions`), u === 0) {
20
20
  console.info(pc.green("\n✨ No GitHub Actions found in this repository"));
21
21
  return;
@@ -163,7 +163,6 @@ function createUpdate(e, r, i) {
163
163
  };
164
164
  }
165
165
  function compareSha(e, n) {
166
- if (!e || !n) return !1;
167
166
  let r = e.replace(/^v/u, ""), i = n.replace(/^v/u, ""), a = Math.min(r.length, i.length);
168
167
  return a < 7 ? !1 : r.slice(0, Math.max(0, a)).toLowerCase() === i.slice(0, Math.max(0, a)).toLowerCase();
169
168
  }
@@ -19,10 +19,9 @@ async function makeRequest(t, n, r = {}) {
19
19
  }
20
20
  throw e;
21
21
  }
22
- let s = await a.json();
23
22
  return {
24
23
  headers: o,
25
- data: s
24
+ data: await a.json()
26
25
  };
27
26
  }
28
27
  export { makeRequest };
@@ -19,20 +19,20 @@ function resolveGitHubTokenSync() {
19
19
  if (e) return e;
20
20
  } catch {}
21
21
  try {
22
- let t = join(process.cwd(), ".git", "config"), r = readFileSync(t, "utf8"), i = r.match(/^\s*(?:github\.(?:oauth-token|token)|hub\.oauthtoken)\s*=\s*(?<token>\S[^\n\r]*)$/mu)?.groups?.token?.trim();
23
- if (i) return i;
24
- let a = null;
25
- for (let e of r.split(/\r?\n/u)) {
22
+ let t = readFileSync(join(process.cwd(), ".git", "config"), "utf8"), r = t.match(/^\s*(?:github\.(?:oauth-token|token)|hub\.oauthtoken)\s*=\s*(?<token>\S[^\n\r]*)$/mu)?.groups?.token?.trim();
23
+ if (r) return r;
24
+ let i = null;
25
+ for (let e of t.split(/\r?\n/u)) {
26
26
  let t = e.trim(), n = t.match(/^\[(?<name>[^\]]+)\]$/u);
27
27
  if (n?.groups) {
28
- a = n.groups.name.toLowerCase();
28
+ i = n.groups.name.toLowerCase();
29
29
  continue;
30
30
  }
31
- if (a === "github") {
31
+ if (i === "github") {
32
32
  let e = t.match(/^(?:oauth-token|token)\s*=\s*(?<val>\S[^\n\r]*)$/u);
33
33
  if (e?.groups?.val) return e.groups.val.trim();
34
34
  }
35
- if (a === "hub") {
35
+ if (i === "hub") {
36
36
  let e = t.match(/^oauthtoken\s*=\s*(?<val>\S[^\n\r]*)$/u);
37
37
  if (e?.groups?.val) return e.groups.val.trim();
38
38
  }
@@ -5,17 +5,15 @@ import { isPair } from "../guards/is-pair.js";
5
5
  import { extractUsesFromSteps } from "../utils/extract-uses-from-steps.js";
6
6
  import { findMapPair } from "../utils/find-map-pair.js";
7
7
  function scanWorkflowAst(o, s, c) {
8
- let l = o.toJSON();
9
- if (!isWorkflowStructure(l) || !o.contents || !isYAMLMap(o.contents)) return [];
10
- let u = findMapPair(o.contents, "jobs");
11
- if (!u?.value || !isYAMLMap(u.value)) return [];
12
- let d = [];
13
- for (let e of u.value.items) {
8
+ if (!isWorkflowStructure(o.toJSON()) || !o.contents || !isYAMLMap(o.contents)) return [];
9
+ let l = findMapPair(o.contents, "jobs");
10
+ if (!l?.value || !isYAMLMap(l.value)) return [];
11
+ let u = [];
12
+ for (let e of l.value.items) {
14
13
  if (!isPair(e) || !e.value || !isNode(e.value) || !isYAMLMap(e.value)) continue;
15
14
  let o = findMapPair(e.value, "steps");
16
- if (!o?.value) continue;
17
- d.push(...extractUsesFromSteps(o.value, c, s));
15
+ o?.value && u.push(...extractUsesFromSteps(o.value, c, s));
18
16
  }
19
- return d;
17
+ return u;
20
18
  }
21
19
  export { scanWorkflowAst };
@@ -27,7 +27,7 @@ async function applyUpdates(n) {
27
27
  console.error(`Invalid SHA format: ${e.latestSha}`);
28
28
  continue;
29
29
  }
30
- let a = r ? String.raw`(?=[^\S\r\n]|$|#)` : "", o = RegExp(`(^\\s*-?\\s*uses:\\s*)(['"]?)(${n})@${r}\\2${a}([^\\S\\r\\n]*#[^\\r\\n]*)?`, "gm"), s = `$1$2$3@${e.latestSha}$2 # ${e.latestVersion}`;
30
+ let a = r ? String.raw`(?=[^\S\r\n]|$|#)` : "", o = new RegExp(String.raw`(^\s*-?\s*uses:\s*)(['"]?)(${n})@${r}\2${a}([^\S\r\n]*#[^\r\n]*)?`, "gm"), s = `$1$2$3@${e.latestSha}$2 # ${e.latestVersion}`;
31
31
  i = i.replace(o, s);
32
32
  }
33
33
  await writeFile(n, i, "utf8");
@@ -1,8 +1,6 @@
1
1
  import { stripAnsi } from "./strip-ansi.js";
2
2
  function padString(t, n) {
3
- let r = stripAnsi(t), i = n - r.length;
4
- if (i <= 0) return t;
5
- let a = " ".repeat(i);
6
- return t + a;
3
+ let r = n - stripAnsi(t).length;
4
+ return r <= 0 ? t : t + " ".repeat(r);
7
5
  }
8
6
  export { padString };
@@ -7,98 +7,97 @@ import pc from "picocolors";
7
7
  import { readFile } from "node:fs/promises";
8
8
  import enquirer from "enquirer";
9
9
  import path from "node:path";
10
- async function promptUpdateSelection(a) {
11
- if (a.length === 0) return null;
12
- let s = a.filter((e) => e.hasUpdate);
13
- if (s.length === 0) return console.info(pc.green("āœ“ All actions are up to date!")), null;
14
- let u = /* @__PURE__ */ new Map();
15
- for (let [e, i] of s.entries()) {
16
- let a = i.action.file ?? "unknown file", o = path.relative(path.join(process.cwd(), GITHUB_DIRECTORY), a);
17
- o === "" && (o = a);
18
- let s = u.get(o) ?? [];
19
- s.push({
20
- update: i,
10
+ var MIN_ACTION_WIDTH = 56, MIN_CURRENT_WIDTH = 16;
11
+ async function promptUpdateSelection(o) {
12
+ if (o.length === 0) return null;
13
+ let c = o.filter((e) => e.hasUpdate);
14
+ if (c.length === 0) return console.info(pc.green("āœ“ All actions are up to date!")), null;
15
+ let p = /* @__PURE__ */ new Map();
16
+ for (let [e, a] of c.entries()) {
17
+ let o = a.action.file ?? "unknown file", s = path.relative(path.join(process.cwd(), GITHUB_DIRECTORY), o);
18
+ s === "" && (s = o);
19
+ let c = p.get(s) ?? [];
20
+ c.push({
21
+ update: a,
21
22
  index: e
22
- }), u.set(o, s);
23
+ }), p.set(s, c);
23
24
  }
24
- let p = await Promise.all(s.map(async (e) => {
25
- let r = formatVersionOrSha(e.currentVersion), i = e.currentVersion ?? void 0;
25
+ let g = await Promise.all(c.map(async (e) => {
26
+ let i = formatVersionOrSha(e.currentVersion), a = e.currentVersion ?? void 0;
26
27
  if (!e.currentVersion || !isSha(e.currentVersion)) return {
27
- effectiveForDiff: i,
28
- display: r
28
+ effectiveForDiff: a,
29
+ display: i
29
30
  };
30
- let a = await tryReadInlineVersionComment(e.action.file, e.action.line);
31
- if (a) {
32
- let s = e.currentVersion.slice(0, 7);
33
- r = `${formatVersionOrSha(a)} ${pc.gray(`(${s})`)}`, i = a;
31
+ let o = await tryReadInlineVersionComment(e.action.file, e.action.line);
32
+ if (o) {
33
+ let c = e.currentVersion.slice(0, 7);
34
+ i = `${formatVersionOrSha(o)} ${pc.gray(`(${c})`)}`, a = o;
34
35
  }
35
36
  return {
36
- effectiveForDiff: i,
37
- display: r
37
+ effectiveForDiff: a,
38
+ display: i
38
39
  };
39
- })), m = [], h = stripAnsi("Action").length, g = stripAnsi("Current").length;
40
- for (let [e, r] of s.entries()) {
41
- let a = r.action.name, o = p[e].display;
42
- h = Math.max(h, a.length), g = Math.max(g, stripAnsi(o).length);
40
+ })), _ = [], v = stripAnsi("Action").length, y = stripAnsi("Current").length;
41
+ for (let [e, i] of c.entries()) {
42
+ let o = i.action.name, s = g[e].display;
43
+ v = Math.max(v, o.length), y = Math.max(y, stripAnsi(s).length);
43
44
  }
44
- let _ = Math.max(h, 56), v = Math.max(g, 16), y = [...u.keys()].toSorted();
45
- for (let [r, i] of y.entries()) {
46
- let a = u.get(i);
47
- if (!a) {
48
- console.warn(`Unexpected missing group for file: ${i}`);
45
+ let b = Math.max(v, MIN_ACTION_WIDTH), x = Math.max(y, MIN_CURRENT_WIDTH), S = [...p.keys()].toSorted();
46
+ for (let [i, a] of S.entries()) {
47
+ let o = p.get(a);
48
+ if (!o) {
49
+ console.warn(`Unexpected missing group for file: ${a}`);
49
50
  continue;
50
51
  }
51
- let s = [], c = a;
52
- s.push({
52
+ let c = [], l = o;
53
+ c.push({
53
54
  current: "Current",
54
55
  action: "Action",
55
56
  target: "Target",
56
57
  arrow: "āÆ"
57
58
  });
58
- for (let { update: r, index: i } of c) {
59
- let a = !!r.latestSha, c = p[i].display, l = p[i]?.effectiveForDiff ?? r.currentVersion, u = formatVersion(r.latestVersion, l), d = r.action.name;
60
- if (r.latestSha) {
61
- let e = r.latestSha.slice(0, 7);
62
- u = `${u} ${pc.gray(`(${e})`)}`;
59
+ for (let { update: i, index: a } of l) {
60
+ let o = !!i.latestSha, l = g[a].display, u = g[a]?.effectiveForDiff ?? i.currentVersion, d = formatVersion(i.latestVersion, u), f = i.action.name;
61
+ if (i.latestSha) {
62
+ let e = i.latestSha.slice(0, 7);
63
+ d = `${d} ${pc.gray(`(${e})`)}`;
63
64
  }
64
- a || (u = pc.gray(u), c = pc.gray(c), d = pc.gray(d)), s.push({
65
- action: d,
66
- target: u,
65
+ o || (d = pc.gray(d), l = pc.gray(l), f = pc.gray(f)), c.push({
66
+ action: f,
67
+ target: d,
67
68
  arrow: "āÆ",
68
- current: c
69
+ current: l
69
70
  });
70
71
  }
71
- let l = Math.max(_, 56), d = Math.max(v, 16), f = [];
72
- for (let [e, r] of s.entries()) {
73
- let i = e === 0, a = formatTableRow(r, l, d);
74
- if (i) f.push({
75
- message: pc.gray(` ā—‹ ${a}`),
72
+ let u = Math.max(b, MIN_ACTION_WIDTH), m = Math.max(x, MIN_CURRENT_WIDTH), h = [];
73
+ for (let [e, i] of c.entries()) {
74
+ let a = e === 0, o = formatTableRow(i, u, m);
75
+ if (a) h.push({
76
+ message: pc.gray(` ā—‹ ${o}`),
76
77
  role: "separator",
77
78
  indent: "",
78
79
  name: ""
79
80
  });
80
81
  else {
81
- let r = c[e - 1];
82
- if (!r) continue;
83
- let { update: i, index: o } = r, s = !!i.latestSha, l = s && !i.isBreaking;
84
- f.push({
85
- message: a,
86
- value: String(o),
87
- name: String(o),
82
+ let { update: i, index: a } = l[e - 1], s = !!i.latestSha, c = s && !i.isBreaking;
83
+ h.push({
84
+ message: o,
85
+ value: String(a),
86
+ name: String(a),
88
87
  disabled: !s,
89
88
  indent: "",
90
- enabled: l
89
+ enabled: c
91
90
  });
92
91
  }
93
92
  }
94
- m.push({
95
- message: pc.gray(i),
96
- value: `label|${i}`,
97
- choices: f,
98
- name: `label|${i}`,
93
+ _.push({
94
+ message: pc.gray(a),
95
+ value: `label|${a}`,
96
+ choices: h,
97
+ name: `label|${a}`,
99
98
  isGroupLabel: !0,
100
99
  enabled: !1
101
- }), r < y.length - 1 && m.push({
100
+ }), i < S.length - 1 && _.push({
102
101
  role: "separator",
103
102
  message: " ",
104
103
  name: ""
@@ -106,22 +105,22 @@ async function promptUpdateSelection(a) {
106
105
  }
107
106
  try {
108
107
  let e = {
109
- indicator(e, r) {
110
- if (r.isGroupLabel) {
111
- let e = (r.choices ?? []).filter((e) => !("role" in e)), i = e.length, a = e.filter((e) => !!e.enabled).length === i ? "ā—" : "ā—‹";
112
- return ` ${pc.gray(a)}`;
108
+ indicator(e, i) {
109
+ if (i.isGroupLabel) {
110
+ let e = (i.choices ?? []).filter((e) => !("role" in e)), a = e.length, o = e.filter((e) => !!e.enabled).length === a ? "ā—" : "ā—‹";
111
+ return ` ${pc.gray(o)}`;
113
112
  }
114
- return ` ${r.enabled ? "ā—" : "ā—‹"}`;
113
+ return ` ${i.enabled ? "ā—" : "ā—‹"}`;
115
114
  },
116
115
  message: `Choose which actions to update (Press ${pc.cyan("<space>")} to select, ${pc.cyan("<a>")} to toggle all, ${pc.cyan("<i>")} to invert selection)`,
117
- cancel() {
118
- return console.info(pc.yellow("\nSelection cancelled")), null;
119
- },
120
116
  styles: {
121
117
  success: pc.reset,
122
118
  em: pc.bgBlack,
123
119
  dark: pc.reset
124
120
  },
121
+ cancel() {
122
+ return logSelectionCancelled(), null;
123
+ },
125
124
  j() {
126
125
  return this.down?.() ?? Promise.resolve([]);
127
126
  },
@@ -132,49 +131,51 @@ async function promptUpdateSelection(a) {
132
131
  type: "multiselect",
133
132
  name: "selected",
134
133
  pointer: "āÆ",
135
- choices: m
136
- }, { selected: r } = await enquirer.prompt(e), i = /* @__PURE__ */ new Set();
137
- for (let e of r) {
134
+ choices: _
135
+ }, { selected: i } = await enquirer.prompt(e), a = /* @__PURE__ */ new Set();
136
+ for (let e of i) {
138
137
  if (e.startsWith("label|")) {
139
- let r = e.slice(6), a = u.get(r) ?? [];
140
- for (let { update: e, index: r } of a) e.latestSha && i.add(r);
138
+ let i = e.slice(6), o = p.get(i) ?? [];
139
+ for (let { update: e, index: i } of o) e.latestSha && a.add(i);
141
140
  continue;
142
141
  }
143
- let r = Number.parseInt(e, 10);
144
- Number.isFinite(r) && i.add(r);
142
+ let i = Number.parseInt(e, 10);
143
+ Number.isFinite(i) && a.add(i);
145
144
  }
146
- let a = [];
147
- for (let [e, r] of s.entries()) i.has(e) && r.latestSha && a.push(r);
148
- return a.length === 0 ? (console.info(pc.yellow("\nNo actions selected")), null) : a;
145
+ let o = [];
146
+ for (let [e, i] of c.entries()) a.has(e) && i.latestSha && o.push(i);
147
+ return o.length === 0 ? (console.info(pc.yellow("\nNo actions selected")), null) : o;
149
148
  } catch (e) {
150
- if (e instanceof Error && (e.message.includes("cancelled") || e.message.includes("ESC") || e.name === "ExitPromptError")) return console.info(pc.yellow("\nSelection cancelled")), null;
149
+ if (e instanceof Error && (e.message.includes("cancelled") || e.message.includes("ESC") || e.name === "ExitPromptError")) return logSelectionCancelled(), null;
151
150
  throw console.error(pc.red("Unexpected error during selection:"), e), e;
152
151
  }
153
152
  }
154
- async function tryReadInlineVersionComment(e, r) {
153
+ async function tryReadInlineVersionComment(e, i) {
155
154
  try {
156
- if (!e || !r || r <= 0) return null;
157
- let i = (await readFile(e, "utf8")).split("\n"), a = r - 1;
158
- if (a < 0 || a >= i.length) return null;
159
- let o = i[a].match(/#\s*(?<version>[Vv]?\d+(?:\.\d+){0,2}(?:[+-][\w\-.]+)?)/u);
160
- if (o?.groups?.version) return o.groups.version;
155
+ if (!e || !i || i <= 0) return null;
156
+ let a = (await readFile(e, "utf8")).split("\n"), o = i - 1;
157
+ if (o < 0 || o >= a.length) return null;
158
+ let s = a[o].match(/#\s*(?<version>[Vv]?\d+(?:\.\d+){0,2}(?:[+-][\w\-.]+)?)/u);
159
+ if (s?.groups?.version) return s.groups.version;
161
160
  } catch {}
162
161
  return null;
163
162
  }
164
- function formatTableRow(e, r, i) {
163
+ function formatTableRow(e, i, a) {
165
164
  return [
166
- padString(e.action, r),
167
- padString(e.current, i),
165
+ padString(e.action, i),
166
+ padString(e.current, a),
168
167
  e.arrow,
169
168
  e.target
170
169
  ].join(" ").replace(/\s+$/u, "");
171
170
  }
172
171
  function isSha(e) {
173
- if (!e) return !1;
174
- let r = e.replace(/^v/u, "");
175
- return /^[0-9a-f]{7,40}$/iu.test(r);
172
+ let i = e.replace(/^v/u, "");
173
+ return /^[0-9a-f]{7,40}$/iu.test(i);
176
174
  }
177
175
  function formatVersionOrSha(e) {
178
176
  return e ? isSha(e) ? e.slice(0, 7) : e.replace(/^v/u, "") : pc.gray("unknown");
179
177
  }
178
+ function logSelectionCancelled() {
179
+ console.info(`\r\u001B[K${pc.yellow("Selection cancelled")}`);
180
+ }
180
181
  export { promptUpdateSelection };
@@ -8,10 +8,12 @@ import { ScanResult } from '../types/scan-result';
8
8
  *
9
9
  * @param rootPath - The root path of the repository to scan. Defaults to
10
10
  * current working directory.
11
+ * @param ciDirectory - The CI directory name (e.g., '.github' or '.gitea').
12
+ * Defaults to '.github'.
11
13
  * @returns A promise that resolves to a ScanResult containing:
12
14
  *
13
15
  * - Workflows: Map of workflow file paths to their referenced actions
14
16
  * - CompositeActions: Map of composite action names to their directory paths
15
17
  * - Actions: Flat array of all discovered GitHub Actions.
16
18
  */
17
- export declare function scanGitHubActions(rootPath?: string): Promise<ScanResult>;
19
+ export declare function scanGitHubActions(rootPath?: string, ciDirectory?: string): Promise<ScanResult>;
@@ -4,97 +4,85 @@ import { scanActionFile } from "./scan-action-file.js";
4
4
  import { isYamlFile } from "./fs/is-yaml-file.js";
5
5
  import { readFile, readdir, stat } from "node:fs/promises";
6
6
  import { isAbsolute, join, relative, resolve } from "node:path";
7
- async function scanGitHubActions(d = process.cwd()) {
8
- let m = {
7
+ async function scanGitHubActions(d = process.cwd(), m = GITHUB_DIRECTORY) {
8
+ let h = {
9
9
  compositeActions: /* @__PURE__ */ new Map(),
10
10
  workflows: /* @__PURE__ */ new Map(),
11
11
  actions: []
12
- }, h = resolve(d);
13
- function g(e, o) {
12
+ }, g = resolve(d);
13
+ function _(e, o) {
14
14
  let s = relative(e, o);
15
15
  return s !== "" && !s.startsWith("..") && !isAbsolute(s);
16
16
  }
17
- let _ = join(h, GITHUB_DIRECTORY);
18
- if (!g(h, _)) throw Error("Invalid path: detected path traversal attempt");
19
- function v(e) {
17
+ let v = join(g, m);
18
+ if (!_(g, v)) throw Error("Invalid path: detected path traversal attempt");
19
+ function y(e) {
20
20
  return e.includes("..") || e.includes("/") || e.includes("\\") ? (console.warn(`Skipping invalid name: ${e}`), !1) : !0;
21
21
  }
22
- let y = join(_, WORKFLOWS_DIRECTORY);
23
- if (!g(h, y)) return m;
22
+ let b = join(v, WORKFLOWS_DIRECTORY);
24
23
  try {
25
- if ((await stat(y)).isDirectory()) {
26
- let e = (await readdir(y)).filter((e) => v(e) ? isYamlFile(e) : !1).map(async (e) => {
27
- let l = join(y, e);
28
- if (!g(y, l)) return console.warn(`Skipping file outside workflows directory: ${e}`), {
29
- success: !1,
30
- actions: [],
31
- path: ""
32
- };
24
+ if ((await stat(b)).isDirectory()) {
25
+ let e = (await readdir(b)).filter((e) => y(e) ? isYamlFile(e) : !1).map(async (e) => {
26
+ let o = join(b, e);
33
27
  try {
34
- let u = await scanWorkflowFile(l);
28
+ let l = await scanWorkflowFile(o);
35
29
  return {
36
- path: `${GITHUB_DIRECTORY}/${WORKFLOWS_DIRECTORY}/${e}`,
30
+ path: `${m}/${WORKFLOWS_DIRECTORY}/${e}`,
37
31
  success: !0,
38
- actions: u
32
+ actions: l
39
33
  };
40
34
  } catch {
41
35
  return {
42
- path: `${GITHUB_DIRECTORY}/${WORKFLOWS_DIRECTORY}/${e}`,
36
+ path: `${m}/${WORKFLOWS_DIRECTORY}/${e}`,
43
37
  success: !1,
44
38
  actions: []
45
39
  };
46
40
  }
47
- }), l = await Promise.all(e);
48
- for (let e of l) e.success && e.path && (e.actions.length > 0 ? (m.workflows.set(e.path, e.actions), m.actions.push(...e.actions)) : m.workflows.set(e.path, []));
41
+ }), o = await Promise.all(e);
42
+ for (let e of o) 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, []));
49
43
  }
50
44
  } catch {}
51
- let b = join(_, ACTIONS_DIRECTORY);
52
- if (!g(h, b)) return m;
45
+ let x = join(v, ACTIONS_DIRECTORY);
53
46
  try {
54
- if ((await stat(b)).isDirectory()) {
55
- let s = (await readdir(b)).map(async (s) => {
56
- if (!v(s)) return null;
57
- let c = join(b, s);
58
- if (!g(b, c)) return console.warn(`Skipping subdirectory outside actions path: ${s}`), null;
47
+ if ((await stat(x)).isDirectory()) {
48
+ let o = (await readdir(x)).map(async (o) => {
49
+ if (!y(o)) return null;
50
+ let s = join(x, o);
59
51
  try {
60
- if (!(await stat(c)).isDirectory()) return null;
61
- let u = join(c, "action.yml");
62
- if (!g(c, u)) return null;
63
- let d = [];
52
+ if (!(await stat(s)).isDirectory()) return null;
53
+ let c = join(s, "action.yml"), u = [];
64
54
  try {
65
- d = await scanActionFile(u);
55
+ u = await scanActionFile(c);
66
56
  } catch {
67
57
  try {
68
- if (u = join(c, "action.yaml"), !g(c, u)) return null;
69
- d = await scanActionFile(u);
58
+ c = join(s, "action.yaml"), u = await scanActionFile(c);
70
59
  } catch {
71
60
  return null;
72
61
  }
73
62
  }
74
63
  return {
75
- path: `${GITHUB_DIRECTORY}/${ACTIONS_DIRECTORY}/${s}`,
76
- name: s,
77
- actions: d
64
+ path: `${m}/${ACTIONS_DIRECTORY}/${o}`,
65
+ name: o,
66
+ actions: u
78
67
  };
79
68
  } catch {
80
69
  return null;
81
70
  }
82
- }), c = await Promise.all(s);
83
- for (let e of c) e && (m.compositeActions.set(e.name, e.path), m.actions.push(...e.actions));
71
+ }), s = await Promise.all(o);
72
+ for (let e of s) e && (h.compositeActions.set(e.name, e.path), h.actions.push(...e.actions));
84
73
  }
85
74
  } catch {}
86
75
  try {
87
- let e = await getCurrentRepoSlug(h);
76
+ let e = await getCurrentRepoSlug(g);
88
77
  if (e) {
89
78
  if (process.env.ACTIONS_UP_TEST_THROW === "1") throw Error("test");
90
79
  let o = /* @__PURE__ */ new Set(), s = [];
91
- for (let c of m.actions) {
80
+ for (let c of h.actions) {
92
81
  if (c.type !== "external") continue;
93
82
  let l = c.name.split("/");
94
83
  if (l.length < 3 || `${l[0]}/${l[1]}` !== e) continue;
95
- let u = join(h, ...l.slice(2));
96
- if (!g(h, u) || o.has(u)) continue;
97
- o.add(u), s.push(u);
84
+ let u = join(g, ...l.slice(2));
85
+ _(g, u) && (o.has(u) || (o.add(u), s.push(u)));
98
86
  }
99
87
  async function c() {
100
88
  if (s.length === 0) return;
@@ -108,15 +96,14 @@ async function scanGitHubActions(d = process.cwd()) {
108
96
  d = u;
109
97
  }
110
98
  let f = await scanActionFile(d);
111
- f.length > 0 && m.actions.push(...f);
99
+ f.length > 0 && h.actions.push(...f);
112
100
  let p = [];
113
101
  for (let s of f) {
114
102
  if (s.type !== "external") continue;
115
103
  let c = s.name.split("/");
116
104
  if (c.length < 3 || `${c[0]}/${c[1]}` !== e) continue;
117
- let l = join(h, ...c.slice(2));
118
- if (!g(h, l) || o.has(l)) continue;
119
- o.add(l), p.push(l);
105
+ let l = join(g, ...c.slice(2));
106
+ _(g, l) && (o.has(l) || (o.add(l), p.push(l)));
120
107
  }
121
108
  return p;
122
109
  } catch {
@@ -129,16 +116,16 @@ async function scanGitHubActions(d = process.cwd()) {
129
116
  await c();
130
117
  }
131
118
  } catch {}
132
- return m;
119
+ return h;
133
120
  }
134
121
  async function getCurrentRepoSlug(e) {
135
122
  let o = process.env.GITHUB_REPOSITORY;
136
123
  if (o && /^[^\s/]+\/[^\s/]+$/u.test(o)) return o;
137
124
  try {
138
- let o = join(e, ".git", "config"), s = await readFile(o, "utf8"), c = s.match(/\[remote "origin"\][\s\S]*?url\s*=\s*(?<url>.+)/u)?.groups?.url?.trim();
139
- if (c ||= s.match(/url\s*=\s*(?<url>.+)/u)?.groups?.url?.trim(), !c) return null;
140
- let l = c.match(/github\.com[/:](?<owner>[^/]+)\/(?<repo>[^./]+)(?:\.git)?$/u);
141
- if (l?.groups) return `${l.groups.owner}/${l.groups.repo}`;
125
+ let o = await readFile(join(e, ".git", "config"), "utf8"), s = o.match(/\[remote "origin"\][\s\S]*?url\s*=\s*(?<url>.+)/u)?.groups?.url?.trim();
126
+ if (s ||= o.match(/url\s*=\s*(?<url>.+)/u)?.groups?.url?.trim(), !s) return null;
127
+ let c = s.match(/github\.com[/:](?<owner>[^/]+)\/(?<repo>[^./]+)(?:\.git)?$/u);
128
+ if (c?.groups) return `${c.groups.owner}/${c.groups.repo}`;
142
129
  } catch {}
143
130
  return null;
144
131
  }
package/dist/package.js CHANGED
@@ -1,2 +1,2 @@
1
- const version = "1.4.1";
1
+ const version = "1.5.0";
2
2
  export { version };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "actions-up",
3
- "version": "1.4.1",
3
+ "version": "1.5.0",
4
4
  "description": "Interactive CLI tool to update GitHub Actions to latest versions with SHA pinning",
5
5
  "keywords": [
6
6
  "github-actions",
@@ -40,7 +40,7 @@
40
40
  "enquirer": "^2.4.1",
41
41
  "nanospinner": "^1.2.2",
42
42
  "picocolors": "^1.1.1",
43
- "semver": "^7.7.2",
43
+ "semver": "^7.7.3",
44
44
  "yaml": "^2.8.1"
45
45
  },
46
46
  "engines": {
package/readme.md CHANGED
@@ -122,6 +122,14 @@ Check for updates without making any changes:
122
122
  npx actions-up --dry-run
123
123
  ```
124
124
 
125
+ ### Custom Directory
126
+
127
+ By default, Actions Up scans the `.github` directory. You can specify a different directory (e.g., for Gitea):
128
+
129
+ ```bash
130
+ npx actions-up --dir .gitea
131
+ ```
132
+
125
133
  ## GitHub Actions Integration
126
134
 
127
135
  ### Automated PR Checks