actions-up 1.3.1 → 1.4.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/dist/cli/index.js +41 -41
- package/dist/core/api/check-updates.js +135 -179
- package/dist/core/api/create-github-client.js +28 -29
- package/dist/core/api/get-all-releases.js +20 -27
- package/dist/core/api/get-all-tags.js +5 -7
- package/dist/core/api/get-latest-release.js +16 -19
- package/dist/core/api/get-reference-type.js +6 -12
- package/dist/core/api/get-tag-info.js +47 -76
- package/dist/core/api/get-tag-sha.js +12 -22
- package/dist/core/api/internal-rate-limit-error.js +3 -4
- package/dist/core/api/make-request.js +18 -21
- package/dist/core/api/resolve-github-token-sync.js +20 -26
- package/dist/core/api/update-rate-limit-info.js +7 -7
- package/dist/core/ast/guards/has-range.js +2 -2
- package/dist/core/ast/guards/is-node.js +2 -2
- package/dist/core/ast/guards/is-pair.js +2 -2
- package/dist/core/ast/guards/is-scalar.js +2 -2
- package/dist/core/ast/guards/is-yaml-map.js +2 -2
- package/dist/core/ast/guards/is-yaml-sequence.js +2 -2
- package/dist/core/ast/scanners/scan-composite-action-ast.js +9 -11
- package/dist/core/ast/scanners/scan-workflow-ast.js +12 -14
- package/dist/core/ast/update/apply-updates.js +24 -27
- package/dist/core/ast/utils/extract-uses-from-steps.js +12 -14
- package/dist/core/ast/utils/find-map-pair.js +2 -5
- package/dist/core/ast/utils/get-line-number.js +4 -4
- package/dist/core/constants.js +1 -3
- package/dist/core/filters/parse-exclude-patterns.d.ts +17 -0
- package/dist/core/filters/parse-exclude-patterns.js +23 -0
- package/dist/core/fs/is-yaml-file.js +2 -2
- package/dist/core/fs/read-yaml-document.js +4 -5
- package/dist/core/ignore/should-ignore.d.ts +23 -0
- package/dist/core/ignore/should-ignore.js +14 -0
- package/dist/core/interactive/format-version.js +16 -30
- package/dist/core/interactive/pad-string.js +5 -6
- package/dist/core/interactive/prompt-update-selection.js +106 -163
- package/dist/core/interactive/strip-ansi.js +10 -17
- package/dist/core/parsing/parse-action-reference.js +23 -23
- package/dist/core/scan-action-file.js +3 -3
- package/dist/core/scan-github-actions.js +87 -136
- package/dist/core/scan-workflow-file.js +3 -3
- package/dist/core/schema/composite/is-composite-action-runs.js +2 -4
- package/dist/core/schema/composite/is-composite-action-structure.js +4 -4
- package/dist/core/schema/workflow/is-workflow-structure.js +4 -4
- package/dist/package.js +1 -1
- package/package.json +1 -1
- package/readme.md +55 -8
|
@@ -1,34 +1,34 @@
|
|
|
1
|
-
function parseActionReference(
|
|
2
|
-
if (!
|
|
3
|
-
if (
|
|
1
|
+
function parseActionReference(e, t, n) {
|
|
2
|
+
if (!e || e.trim() === "") return null;
|
|
3
|
+
if (e.startsWith("docker://")) return {
|
|
4
4
|
version: void 0,
|
|
5
|
-
name:
|
|
5
|
+
name: e,
|
|
6
6
|
type: "docker",
|
|
7
|
-
file,
|
|
8
|
-
line
|
|
7
|
+
file: t,
|
|
8
|
+
line: n
|
|
9
9
|
};
|
|
10
|
-
if (
|
|
10
|
+
if (e.startsWith("./") || e.startsWith("../")) return {
|
|
11
11
|
version: void 0,
|
|
12
|
-
name:
|
|
12
|
+
name: e,
|
|
13
13
|
type: "local",
|
|
14
|
-
file,
|
|
15
|
-
line
|
|
14
|
+
file: t,
|
|
15
|
+
line: n
|
|
16
16
|
};
|
|
17
|
-
let
|
|
18
|
-
if (
|
|
19
|
-
let [
|
|
20
|
-
if (!
|
|
21
|
-
let
|
|
22
|
-
if (
|
|
23
|
-
let [
|
|
24
|
-
if (!
|
|
25
|
-
for (let
|
|
17
|
+
let r = e.split("@");
|
|
18
|
+
if (r.length !== 2) return null;
|
|
19
|
+
let [i, a] = r;
|
|
20
|
+
if (!i || !a) return null;
|
|
21
|
+
let o = i.split("/");
|
|
22
|
+
if (o.length < 2) return null;
|
|
23
|
+
let [s, c] = o;
|
|
24
|
+
if (!s || !c) return null;
|
|
25
|
+
for (let e of o.slice(2)) if (!e) return null;
|
|
26
26
|
return {
|
|
27
27
|
type: "external",
|
|
28
|
-
name:
|
|
29
|
-
version,
|
|
30
|
-
file,
|
|
31
|
-
line
|
|
28
|
+
name: i,
|
|
29
|
+
version: a,
|
|
30
|
+
file: t,
|
|
31
|
+
line: n
|
|
32
32
|
};
|
|
33
33
|
}
|
|
34
34
|
export { parseActionReference };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readYamlDocument } from "./fs/read-yaml-document.js";
|
|
2
2
|
import { scanCompositeActionAst } from "./ast/scanners/scan-composite-action-ast.js";
|
|
3
|
-
async function scanActionFile(
|
|
4
|
-
let { document, content } = await readYamlDocument(
|
|
5
|
-
return scanCompositeActionAst(
|
|
3
|
+
async function scanActionFile(n) {
|
|
4
|
+
let { document: r, content: i } = await readYamlDocument(n);
|
|
5
|
+
return scanCompositeActionAst(r, i, n);
|
|
6
6
|
}
|
|
7
7
|
export { scanActionFile };
|
|
@@ -4,190 +4,141 @@ 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(
|
|
8
|
-
let
|
|
7
|
+
async function scanGitHubActions(d = process.cwd()) {
|
|
8
|
+
let m = {
|
|
9
9
|
compositeActions: /* @__PURE__ */ new Map(),
|
|
10
10
|
workflows: /* @__PURE__ */ new Map(),
|
|
11
11
|
actions: []
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
return relativePath !== "" && !relativePath.startsWith("..") && !isAbsolute(relativePath);
|
|
12
|
+
}, h = resolve(d);
|
|
13
|
+
function g(e, o) {
|
|
14
|
+
let s = relative(e, o);
|
|
15
|
+
return s !== "" && !s.startsWith("..") && !isAbsolute(s);
|
|
17
16
|
}
|
|
18
|
-
let
|
|
19
|
-
if (!
|
|
20
|
-
function
|
|
21
|
-
|
|
22
|
-
console.warn(`Skipping invalid name: ${name}`);
|
|
23
|
-
return false;
|
|
24
|
-
}
|
|
25
|
-
return true;
|
|
17
|
+
let _ = join(h, GITHUB_DIRECTORY);
|
|
18
|
+
if (!g(h, _)) throw Error("Invalid path: detected path traversal attempt");
|
|
19
|
+
function v(e) {
|
|
20
|
+
return e.includes("..") || e.includes("/") || e.includes("\\") ? (console.warn(`Skipping invalid name: ${e}`), !1) : !0;
|
|
26
21
|
}
|
|
27
|
-
let
|
|
28
|
-
if (!
|
|
22
|
+
let y = join(_, WORKFLOWS_DIRECTORY);
|
|
23
|
+
if (!g(h, y)) return m;
|
|
29
24
|
try {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (!isWithin(workflowsPath, filePath)) {
|
|
39
|
-
console.warn(`Skipping file outside workflows directory: ${file}`);
|
|
40
|
-
return {
|
|
41
|
-
success: false,
|
|
42
|
-
actions: [],
|
|
43
|
-
path: ""
|
|
44
|
-
};
|
|
45
|
-
}
|
|
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
|
+
};
|
|
46
33
|
try {
|
|
47
|
-
let
|
|
34
|
+
let u = await scanWorkflowFile(l);
|
|
48
35
|
return {
|
|
49
|
-
path: `${GITHUB_DIRECTORY}/${WORKFLOWS_DIRECTORY}/${
|
|
50
|
-
success:
|
|
51
|
-
actions
|
|
36
|
+
path: `${GITHUB_DIRECTORY}/${WORKFLOWS_DIRECTORY}/${e}`,
|
|
37
|
+
success: !0,
|
|
38
|
+
actions: u
|
|
52
39
|
};
|
|
53
40
|
} catch {
|
|
54
41
|
return {
|
|
55
|
-
path: `${GITHUB_DIRECTORY}/${WORKFLOWS_DIRECTORY}/${
|
|
56
|
-
success:
|
|
42
|
+
path: `${GITHUB_DIRECTORY}/${WORKFLOWS_DIRECTORY}/${e}`,
|
|
43
|
+
success: !1,
|
|
57
44
|
actions: []
|
|
58
45
|
};
|
|
59
46
|
}
|
|
60
|
-
});
|
|
61
|
-
let
|
|
62
|
-
for (let workflow of workflowResults) if (workflow.success && workflow.path) if (workflow.actions.length > 0) {
|
|
63
|
-
result.workflows.set(workflow.path, workflow.actions);
|
|
64
|
-
result.actions.push(...workflow.actions);
|
|
65
|
-
} else result.workflows.set(workflow.path, []);
|
|
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, []));
|
|
66
49
|
}
|
|
67
50
|
} catch {}
|
|
68
|
-
let
|
|
69
|
-
if (!
|
|
51
|
+
let b = join(_, ACTIONS_DIRECTORY);
|
|
52
|
+
if (!g(h, b)) return m;
|
|
70
53
|
try {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if (!
|
|
76
|
-
let subdirPath = join(actionsPath, subdir);
|
|
77
|
-
if (!isWithin(actionsPath, subdirPath)) {
|
|
78
|
-
console.warn(`Skipping subdirectory outside actions path: ${subdir}`);
|
|
79
|
-
return null;
|
|
80
|
-
}
|
|
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;
|
|
81
59
|
try {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
let actions = [];
|
|
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 = [];
|
|
87
64
|
try {
|
|
88
|
-
|
|
65
|
+
d = await scanActionFile(u);
|
|
89
66
|
} catch {
|
|
90
67
|
try {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
actions = await scanActionFile(actionFilePath);
|
|
68
|
+
if (u = join(c, "action.yaml"), !g(c, u)) return null;
|
|
69
|
+
d = await scanActionFile(u);
|
|
94
70
|
} catch {
|
|
95
71
|
return null;
|
|
96
72
|
}
|
|
97
73
|
}
|
|
98
74
|
return {
|
|
99
|
-
path: `${GITHUB_DIRECTORY}/${ACTIONS_DIRECTORY}/${
|
|
100
|
-
name:
|
|
101
|
-
actions
|
|
75
|
+
path: `${GITHUB_DIRECTORY}/${ACTIONS_DIRECTORY}/${s}`,
|
|
76
|
+
name: s,
|
|
77
|
+
actions: d
|
|
102
78
|
};
|
|
103
79
|
} catch {
|
|
104
80
|
return null;
|
|
105
81
|
}
|
|
106
|
-
});
|
|
107
|
-
let
|
|
108
|
-
for (let actionResult of actionResults) if (actionResult) {
|
|
109
|
-
result.compositeActions.set(actionResult.name, actionResult.path);
|
|
110
|
-
result.actions.push(...actionResult.actions);
|
|
111
|
-
}
|
|
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));
|
|
112
84
|
}
|
|
113
85
|
} catch {}
|
|
114
86
|
try {
|
|
115
|
-
let
|
|
116
|
-
if (
|
|
117
|
-
if (process.env
|
|
118
|
-
let
|
|
119
|
-
let
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
let compositeDirectory = join(normalizedRoot, ...segs.slice(2));
|
|
127
|
-
if (!isWithin(normalizedRoot, compositeDirectory)) continue;
|
|
128
|
-
if (seenCompositeDirectories.has(compositeDirectory)) continue;
|
|
129
|
-
seenCompositeDirectories.add(compositeDirectory);
|
|
130
|
-
queue.push(compositeDirectory);
|
|
87
|
+
let e = await getCurrentRepoSlug(h);
|
|
88
|
+
if (e) {
|
|
89
|
+
if (process.env.ACTIONS_UP_TEST_THROW === "1") throw Error("test");
|
|
90
|
+
let o = /* @__PURE__ */ new Set(), s = [];
|
|
91
|
+
for (let c of m.actions) {
|
|
92
|
+
if (c.type !== "external") continue;
|
|
93
|
+
let l = c.name.split("/");
|
|
94
|
+
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);
|
|
131
98
|
}
|
|
132
|
-
async function
|
|
133
|
-
if (
|
|
134
|
-
let
|
|
135
|
-
let discoveredNext = await Promise.all(batch.map(async (directory) => {
|
|
99
|
+
async function c() {
|
|
100
|
+
if (s.length === 0) return;
|
|
101
|
+
let u = s.splice(0), d = await Promise.all(u.map(async (s) => {
|
|
136
102
|
try {
|
|
137
|
-
let
|
|
138
|
-
let yamlPath = join(directory, "action.yaml");
|
|
139
|
-
let filePath = ymlPath;
|
|
103
|
+
let c = join(s, "action.yml"), u = join(s, "action.yaml"), d = c;
|
|
140
104
|
try {
|
|
141
|
-
|
|
142
|
-
if (!fileInfo.isFile()) throw new Error("not a file");
|
|
105
|
+
if (!(await stat(c)).isFile()) throw Error("not a file");
|
|
143
106
|
} catch {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
filePath = yamlPath;
|
|
107
|
+
if (!(await stat(u)).isFile()) throw Error("not a file");
|
|
108
|
+
d = u;
|
|
147
109
|
}
|
|
148
|
-
let
|
|
149
|
-
|
|
150
|
-
let
|
|
151
|
-
for (let
|
|
152
|
-
if (
|
|
153
|
-
let
|
|
154
|
-
if (
|
|
155
|
-
let
|
|
156
|
-
if (
|
|
157
|
-
|
|
158
|
-
if (!isWithin(normalizedRoot, nextDirectory)) continue;
|
|
159
|
-
if (seenCompositeDirectories.has(nextDirectory)) continue;
|
|
160
|
-
seenCompositeDirectories.add(nextDirectory);
|
|
161
|
-
nextDirectories.push(nextDirectory);
|
|
110
|
+
let f = await scanActionFile(d);
|
|
111
|
+
f.length > 0 && m.actions.push(...f);
|
|
112
|
+
let p = [];
|
|
113
|
+
for (let s of f) {
|
|
114
|
+
if (s.type !== "external") continue;
|
|
115
|
+
let c = s.name.split("/");
|
|
116
|
+
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);
|
|
162
120
|
}
|
|
163
|
-
return
|
|
121
|
+
return p;
|
|
164
122
|
} catch {
|
|
165
123
|
return [];
|
|
166
124
|
}
|
|
167
125
|
}));
|
|
168
|
-
for (let
|
|
169
|
-
await
|
|
126
|
+
for (let e of d) for (let o of e) s.push(o);
|
|
127
|
+
await c();
|
|
170
128
|
}
|
|
171
|
-
await
|
|
129
|
+
await c();
|
|
172
130
|
}
|
|
173
131
|
} catch {}
|
|
174
|
-
return
|
|
132
|
+
return m;
|
|
175
133
|
}
|
|
176
|
-
async function getCurrentRepoSlug(
|
|
177
|
-
let
|
|
178
|
-
if (
|
|
134
|
+
async function getCurrentRepoSlug(e) {
|
|
135
|
+
let o = process.env.GITHUB_REPOSITORY;
|
|
136
|
+
if (o && /^[^\s/]+\/[^\s/]+$/u.test(o)) return o;
|
|
179
137
|
try {
|
|
180
|
-
let
|
|
181
|
-
|
|
182
|
-
let
|
|
183
|
-
|
|
184
|
-
if (!url) {
|
|
185
|
-
let anyUrlMatch = content.match(/url\s*=\s*(?<url>.+)/u);
|
|
186
|
-
url = anyUrlMatch?.groups?.["url"]?.trim();
|
|
187
|
-
}
|
|
188
|
-
if (!url) return null;
|
|
189
|
-
let httpsMatch = url.match(/github\.com[/:](?<owner>[^/]+)\/(?<repo>[^./]+)(?:\.git)?$/u);
|
|
190
|
-
if (httpsMatch?.groups) return `${httpsMatch.groups["owner"]}/${httpsMatch.groups["repo"]}`;
|
|
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}`;
|
|
191
142
|
} catch {}
|
|
192
143
|
return null;
|
|
193
144
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { scanWorkflowAst } from "./ast/scanners/scan-workflow-ast.js";
|
|
2
2
|
import { readYamlDocument } from "./fs/read-yaml-document.js";
|
|
3
|
-
async function scanWorkflowFile(
|
|
4
|
-
let { document, content } = await readYamlDocument(
|
|
5
|
-
return scanWorkflowAst(
|
|
3
|
+
async function scanWorkflowFile(n) {
|
|
4
|
+
let { document: r, content: i } = await readYamlDocument(n);
|
|
5
|
+
return scanWorkflowAst(r, i, n);
|
|
6
6
|
}
|
|
7
7
|
export { scanWorkflowFile };
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
function isCompositeActionRuns(
|
|
2
|
-
|
|
3
|
-
let object = value;
|
|
4
|
-
return "using" in object;
|
|
1
|
+
function isCompositeActionRuns(e) {
|
|
2
|
+
return typeof e != "object" || !e || Array.isArray(e) ? !1 : "using" in e;
|
|
5
3
|
}
|
|
6
4
|
export { isCompositeActionRuns };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
function isCompositeActionStructure(
|
|
2
|
-
if (
|
|
3
|
-
let
|
|
4
|
-
return "name" in
|
|
1
|
+
function isCompositeActionStructure(e) {
|
|
2
|
+
if (typeof e != "object" || !e || Array.isArray(e)) return !1;
|
|
3
|
+
let t = e;
|
|
4
|
+
return "name" in t || "description" in t || "runs" in t;
|
|
5
5
|
}
|
|
6
6
|
export { isCompositeActionStructure };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
function isWorkflowStructure(
|
|
2
|
-
if (
|
|
3
|
-
let
|
|
4
|
-
return "on" in
|
|
1
|
+
function isWorkflowStructure(e) {
|
|
2
|
+
if (typeof e != "object" || !e || Array.isArray(e)) return !1;
|
|
3
|
+
let t = e;
|
|
4
|
+
return "on" in t || "name" in t || "jobs" in t;
|
|
5
5
|
}
|
|
6
6
|
export { isWorkflowStructure };
|
package/dist/package.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const version = "1.
|
|
1
|
+
const version = "1.4.0";
|
|
2
2
|
export { version };
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
<img
|
|
4
4
|
src="https://raw.githubusercontent.com/azat-io/actions-up/main/assets/logo.svg"
|
|
5
|
-
alt="Actions Up
|
|
5
|
+
alt="Actions Up logo"
|
|
6
6
|
width="160"
|
|
7
7
|
height="160"
|
|
8
8
|
align="right"
|
|
@@ -19,7 +19,7 @@ Interactively upgrade and pin actions to exact commit SHAs for secure, reproduci
|
|
|
19
19
|
## Features
|
|
20
20
|
|
|
21
21
|
- **Auto-discovery**: Scans all workflows (`.github/workflows/*.yml`) and composite actions (`.github/actions/*/action.yml`)
|
|
22
|
-
- **SHA
|
|
22
|
+
- **SHA pinning**: Updates actions to use commit SHA instead of tags for better security
|
|
23
23
|
- **Batch Updates**: Update multiple actions at once
|
|
24
24
|
- **Interactive Selection**: Choose which actions to update
|
|
25
25
|
- **Breaking Changes Detection**: Warns about major version updates
|
|
@@ -41,7 +41,7 @@ Interactively upgrade and pin actions to exact commit SHAs for secure, reproduci
|
|
|
41
41
|
/>
|
|
42
42
|
<img
|
|
43
43
|
src="https://raw.githubusercontent.com/azat-io/actions-up/main/assets/example-light.webp"
|
|
44
|
-
alt="Actions Up interactive example"
|
|
44
|
+
alt="Actions Up! interactive example"
|
|
45
45
|
width="820"
|
|
46
46
|
/>
|
|
47
47
|
</picture>
|
|
@@ -209,7 +209,7 @@ jobs:
|
|
|
209
209
|
echo "#### Option 2: Manual Update"
|
|
210
210
|
echo "1. Review each update in the table above"
|
|
211
211
|
echo "2. For breaking changes, click the Release Notes link to review changes"
|
|
212
|
-
echo "3. Edit the
|
|
212
|
+
echo "3. Edit the workflows and update the version numbers"
|
|
213
213
|
echo "4. Test the changes in your CI/CD pipeline"
|
|
214
214
|
echo ""
|
|
215
215
|
echo "---"
|
|
@@ -233,7 +233,7 @@ jobs:
|
|
|
233
233
|
echo ""
|
|
234
234
|
echo "### All GitHub Actions in this repository are up to date!"
|
|
235
235
|
echo ""
|
|
236
|
-
echo "No action required. Your
|
|
236
|
+
echo "No action required. Your workflows are using the latest versions of all GitHub Actions."
|
|
237
237
|
} > actions-up-report.md
|
|
238
238
|
|
|
239
239
|
echo "has-updates=false" >> $GITHUB_OUTPUT
|
|
@@ -335,7 +335,7 @@ jobs:
|
|
|
335
335
|
echo "::error:: Found ${{ steps.actions-check.outputs.update-count }} outdated GitHub Actions. Please update them before merging."
|
|
336
336
|
echo ""
|
|
337
337
|
echo "You can update them by running: npx actions-up"
|
|
338
|
-
echo "Or manually update the versions in your
|
|
338
|
+
echo "Or manually update the versions in your workflows."
|
|
339
339
|
exit 1
|
|
340
340
|
````
|
|
341
341
|
|
|
@@ -408,12 +408,59 @@ Or in GitHub Actions:
|
|
|
408
408
|
run: npx actions-up --dry-run
|
|
409
409
|
```
|
|
410
410
|
|
|
411
|
+
### Skipping Updates
|
|
412
|
+
|
|
413
|
+
Skip updates using CLI excludes and YAML ignore comments. Excludes run first, then ignore comments.
|
|
414
|
+
|
|
415
|
+
#### CLI Excludes
|
|
416
|
+
|
|
417
|
+
Skip actions by name using regular expressions. Patterns are matched against the full action name (`owner/repo[/path]`).
|
|
418
|
+
|
|
419
|
+
- Repeatable flag: `--exclude <regex>` (can be used multiple times)
|
|
420
|
+
- Comma-separated list is supported inside a single flag
|
|
421
|
+
- Forms:
|
|
422
|
+
- Plain string compiled as case-insensitive regex: `my-org/.*`
|
|
423
|
+
- Literal with flags: `/^actions\/internal-.+$/i`
|
|
424
|
+
|
|
425
|
+
Examples:
|
|
426
|
+
|
|
427
|
+
```bash
|
|
428
|
+
npx actions-up --exclude "my-org/.*"
|
|
429
|
+
npx actions-up --exclude ".*/internal-.*" --exclude "/^acme\/.+$/i"
|
|
430
|
+
# or
|
|
431
|
+
npx actions-up --exclude "my-org/.*, .*/internal-.*"
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
#### Ignore Comments
|
|
435
|
+
|
|
436
|
+
You can skip specific actions or files using YAML comments. Ignored items are hidden in dry-run and interactive modes and are not updated with `--yes`.
|
|
437
|
+
|
|
438
|
+
- Ignore whole file: `# actions-up-ignore-file`
|
|
439
|
+
- Block ignore: `# actions-up-ignore-start` … `# actions-up-ignore-end`
|
|
440
|
+
- Next line: `# actions-up-ignore-next-line`
|
|
441
|
+
- Inline on the same line: append `# actions-up-ignore`
|
|
442
|
+
|
|
443
|
+
Example:
|
|
444
|
+
|
|
445
|
+
```yaml
|
|
446
|
+
# actions-up-ignore-file
|
|
447
|
+
|
|
448
|
+
# actions-up-ignore-next-line
|
|
449
|
+
- uses: actions/checkout@v3
|
|
450
|
+
|
|
451
|
+
- uses: actions/setup-node@v3 # actions-up-ignore
|
|
452
|
+
|
|
453
|
+
# actions-up-ignore-start
|
|
454
|
+
- uses: actions/cache@v3
|
|
455
|
+
# actions-up-ignore-end
|
|
456
|
+
```
|
|
457
|
+
|
|
411
458
|
## Security
|
|
412
459
|
|
|
413
460
|
Actions Up promotes security best practices:
|
|
414
461
|
|
|
415
|
-
- **SHA
|
|
416
|
-
- **Version
|
|
462
|
+
- **SHA pinning**: Uses commit SHA instead of mutable tags
|
|
463
|
+
- **Version comment**: Adds the released version next to the pinned SHA for readability
|
|
417
464
|
- **No Auto-Updates**: Full control over what gets updated
|
|
418
465
|
- **Breaking Change Warnings**: Alerts you to major version updates that may require configuration changes
|
|
419
466
|
|