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 +0 -0
- package/dist/cli/index.js +2 -2
- package/dist/core/api/check-updates.js +0 -1
- package/dist/core/api/make-request.js +1 -2
- package/dist/core/api/resolve-github-token-sync.js +7 -7
- package/dist/core/ast/scanners/scan-workflow-ast.js +7 -9
- package/dist/core/ast/update/apply-updates.js +1 -1
- package/dist/core/interactive/pad-string.js +2 -4
- package/dist/core/interactive/prompt-update-selection.js +93 -92
- package/dist/core/scan-github-actions.d.ts +3 -1
- package/dist/core/scan-github-actions.js +43 -56
- package/dist/package.js +1 -1
- package/package.json +2 -2
- package/readme.md +8 -0
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,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"),
|
|
23
|
-
if (
|
|
24
|
-
let
|
|
25
|
-
for (let e of
|
|
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
|
-
|
|
28
|
+
i = n.groups.name.toLowerCase();
|
|
29
29
|
continue;
|
|
30
30
|
}
|
|
31
|
-
if (
|
|
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 (
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
let
|
|
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
|
-
|
|
17
|
-
d.push(...extractUsesFromSteps(o.value, c, s));
|
|
15
|
+
o?.value && u.push(...extractUsesFromSteps(o.value, c, s));
|
|
18
16
|
}
|
|
19
|
-
return
|
|
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(`(
|
|
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)
|
|
4
|
-
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
o
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
}),
|
|
23
|
+
}), p.set(s, c);
|
|
23
24
|
}
|
|
24
|
-
let
|
|
25
|
-
let
|
|
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:
|
|
28
|
-
display:
|
|
28
|
+
effectiveForDiff: a,
|
|
29
|
+
display: i
|
|
29
30
|
};
|
|
30
|
-
let
|
|
31
|
-
if (
|
|
32
|
-
let
|
|
33
|
-
|
|
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:
|
|
37
|
-
display:
|
|
37
|
+
effectiveForDiff: a,
|
|
38
|
+
display: i
|
|
38
39
|
};
|
|
39
|
-
})),
|
|
40
|
-
for (let [e,
|
|
41
|
-
let
|
|
42
|
-
|
|
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
|
|
45
|
-
for (let [
|
|
46
|
-
let
|
|
47
|
-
if (!
|
|
48
|
-
console.warn(`Unexpected missing group for file: ${
|
|
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
|
|
52
|
-
|
|
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:
|
|
59
|
-
let
|
|
60
|
-
if (
|
|
61
|
-
let e =
|
|
62
|
-
|
|
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
|
-
|
|
65
|
-
action:
|
|
66
|
-
target:
|
|
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:
|
|
69
|
+
current: l
|
|
69
70
|
});
|
|
70
71
|
}
|
|
71
|
-
let
|
|
72
|
-
for (let [e,
|
|
73
|
-
let
|
|
74
|
-
if (
|
|
75
|
-
message: pc.gray(` ā ${
|
|
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
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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:
|
|
89
|
+
enabled: c
|
|
91
90
|
});
|
|
92
91
|
}
|
|
93
92
|
}
|
|
94
|
-
|
|
95
|
-
message: pc.gray(
|
|
96
|
-
value: `label|${
|
|
97
|
-
choices:
|
|
98
|
-
name: `label|${
|
|
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
|
-
}),
|
|
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,
|
|
110
|
-
if (
|
|
111
|
-
let e = (
|
|
112
|
-
return ` ${pc.gray(
|
|
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 ` ${
|
|
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:
|
|
136
|
-
}, { selected:
|
|
137
|
-
for (let e of
|
|
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
|
|
140
|
-
for (let { update: e, index:
|
|
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
|
|
144
|
-
Number.isFinite(
|
|
142
|
+
let i = Number.parseInt(e, 10);
|
|
143
|
+
Number.isFinite(i) && a.add(i);
|
|
145
144
|
}
|
|
146
|
-
let
|
|
147
|
-
for (let [e,
|
|
148
|
-
return
|
|
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
|
|
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,
|
|
153
|
+
async function tryReadInlineVersionComment(e, i) {
|
|
155
154
|
try {
|
|
156
|
-
if (!e || !
|
|
157
|
-
let
|
|
158
|
-
if (
|
|
159
|
-
let
|
|
160
|
-
if (
|
|
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,
|
|
163
|
+
function formatTableRow(e, i, a) {
|
|
165
164
|
return [
|
|
166
|
-
padString(e.action,
|
|
167
|
-
padString(e.current,
|
|
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
|
-
|
|
174
|
-
|
|
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
|
|
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
|
-
},
|
|
13
|
-
function
|
|
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
|
|
18
|
-
if (!g
|
|
19
|
-
function
|
|
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
|
|
23
|
-
if (!g(h, y)) return m;
|
|
22
|
+
let b = join(v, WORKFLOWS_DIRECTORY);
|
|
24
23
|
try {
|
|
25
|
-
if ((await stat(
|
|
26
|
-
let e = (await readdir(
|
|
27
|
-
let
|
|
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
|
|
28
|
+
let l = await scanWorkflowFile(o);
|
|
35
29
|
return {
|
|
36
|
-
path: `${
|
|
30
|
+
path: `${m}/${WORKFLOWS_DIRECTORY}/${e}`,
|
|
37
31
|
success: !0,
|
|
38
|
-
actions:
|
|
32
|
+
actions: l
|
|
39
33
|
};
|
|
40
34
|
} catch {
|
|
41
35
|
return {
|
|
42
|
-
path: `${
|
|
36
|
+
path: `${m}/${WORKFLOWS_DIRECTORY}/${e}`,
|
|
43
37
|
success: !1,
|
|
44
38
|
actions: []
|
|
45
39
|
};
|
|
46
40
|
}
|
|
47
|
-
}),
|
|
48
|
-
for (let e of
|
|
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
|
|
52
|
-
if (!g(h, b)) return m;
|
|
45
|
+
let x = join(v, ACTIONS_DIRECTORY);
|
|
53
46
|
try {
|
|
54
|
-
if ((await stat(
|
|
55
|
-
let
|
|
56
|
-
if (!
|
|
57
|
-
let
|
|
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(
|
|
61
|
-
let
|
|
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
|
-
|
|
55
|
+
u = await scanActionFile(c);
|
|
66
56
|
} catch {
|
|
67
57
|
try {
|
|
68
|
-
|
|
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: `${
|
|
76
|
-
name:
|
|
77
|
-
actions:
|
|
64
|
+
path: `${m}/${ACTIONS_DIRECTORY}/${o}`,
|
|
65
|
+
name: o,
|
|
66
|
+
actions: u
|
|
78
67
|
};
|
|
79
68
|
} catch {
|
|
80
69
|
return null;
|
|
81
70
|
}
|
|
82
|
-
}),
|
|
83
|
-
for (let e of
|
|
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(
|
|
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
|
|
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(
|
|
96
|
-
|
|
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 &&
|
|
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(
|
|
118
|
-
|
|
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
|
|
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"),
|
|
139
|
-
if (
|
|
140
|
-
let
|
|
141
|
-
if (
|
|
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.
|
|
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.
|
|
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.
|
|
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
|