@factorialco/gat 3.2.2 → 3.3.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.
- package/dist/workflow.d.ts +6 -1
- package/dist/workflow.js +40 -17
- package/dist/workflow.spec.js +31 -0
- package/package.json +3 -3
package/dist/workflow.d.ts
CHANGED
|
@@ -8,6 +8,11 @@ interface EnvVar {
|
|
|
8
8
|
name: string;
|
|
9
9
|
value: string;
|
|
10
10
|
}
|
|
11
|
+
interface CompileOptions {
|
|
12
|
+
filePath?: string;
|
|
13
|
+
lockFilePath?: string;
|
|
14
|
+
writeLockFile?: boolean;
|
|
15
|
+
}
|
|
11
16
|
export type RunnerDefinition = string | {
|
|
12
17
|
group: string;
|
|
13
18
|
labels?: string[];
|
|
@@ -26,6 +31,6 @@ export declare class Workflow<JobStep extends Step = Step, Runner extends Runner
|
|
|
26
31
|
setEnv(name: string, value: string): this;
|
|
27
32
|
setConcurrencyGroup(concurrencyGroup: ConcurrencyGroup | null): this;
|
|
28
33
|
defaultRunner(): string;
|
|
29
|
-
compile(
|
|
34
|
+
compile(compileOptions?: CompileOptions): Promise<string | void>;
|
|
30
35
|
}
|
|
31
36
|
export {};
|
package/dist/workflow.js
CHANGED
|
@@ -9,31 +9,54 @@ const kebabCase_1 = __importDefault(require("lodash/kebabCase"));
|
|
|
9
9
|
const fs_1 = __importDefault(require("fs"));
|
|
10
10
|
const path_1 = __importDefault(require("path"));
|
|
11
11
|
const util_1 = require("util");
|
|
12
|
-
const
|
|
12
|
+
const rest_1 = require("@octokit/rest");
|
|
13
13
|
const step_1 = require("./step");
|
|
14
14
|
const writeFilePromise = (0, util_1.promisify)(fs_1.default.writeFile);
|
|
15
|
-
|
|
16
|
-
const supplyChainAttack = async (step,
|
|
15
|
+
let firstCompileCall = true;
|
|
16
|
+
const supplyChainAttack = async (step, compileOptions) => {
|
|
17
|
+
const { lockFilePath, writeLockFile = false } = compileOptions;
|
|
17
18
|
if (!(0, step_1.isUseStep)(step))
|
|
18
19
|
return;
|
|
19
|
-
|
|
20
|
+
// The user is not interested in frozen sha versions
|
|
21
|
+
if (!lockFilePath)
|
|
20
22
|
return step.uses;
|
|
21
23
|
const uses = step.uses;
|
|
22
|
-
if (!uses)
|
|
23
|
-
return uses;
|
|
24
|
-
if (chainAttackCache[uses])
|
|
25
|
-
return chainAttackCache[uses];
|
|
26
24
|
const match = uses.match(/(?<repository>.*)@(?<version>.*)/);
|
|
25
|
+
// The uses is not a valid Github action with a tag
|
|
27
26
|
if (!match)
|
|
28
27
|
return uses;
|
|
29
28
|
const { repository, version } = match.groups;
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
29
|
+
if (firstCompileCall && writeLockFile && fs_1.default.existsSync(lockFilePath)) {
|
|
30
|
+
firstCompileCall = false;
|
|
31
|
+
fs_1.default.rmSync(lockFilePath);
|
|
32
|
+
}
|
|
33
|
+
const chainAttackCache = fs_1.default.existsSync(lockFilePath)
|
|
34
|
+
? JSON.parse(fs_1.default.readFileSync(lockFilePath, "utf8"))
|
|
35
|
+
: {};
|
|
36
|
+
if (chainAttackCache[uses])
|
|
37
|
+
return chainAttackCache[uses];
|
|
38
|
+
if (!writeLockFile) {
|
|
39
|
+
throw new Error(`Unable to retrieve ${uses} from lock file and writeLockFile is false`);
|
|
40
|
+
}
|
|
41
|
+
const [owner, repo] = repository.split("/");
|
|
42
|
+
const octokit = process.env.GITHUB_TOKEN
|
|
43
|
+
? new rest_1.Octokit({
|
|
44
|
+
auth: process.env.GITHUB_TOKEN,
|
|
45
|
+
})
|
|
46
|
+
: new rest_1.Octokit();
|
|
47
|
+
const response = await octokit.rest.repos.listTags({
|
|
48
|
+
owner,
|
|
49
|
+
repo,
|
|
50
|
+
});
|
|
51
|
+
const tag = response.data.find((tag) => tag.name === version);
|
|
52
|
+
if (!tag) {
|
|
53
|
+
throw new Error(`Unable to retrieve ${uses} from Github tags`);
|
|
54
|
+
}
|
|
35
55
|
const result = `${repository}@${tag.commit.sha}`;
|
|
36
56
|
chainAttackCache[uses] = result;
|
|
57
|
+
if (writeLockFile) {
|
|
58
|
+
fs_1.default.writeFileSync(lockFilePath, JSON.stringify(chainAttackCache, null, 2));
|
|
59
|
+
}
|
|
37
60
|
return result;
|
|
38
61
|
};
|
|
39
62
|
class Workflow {
|
|
@@ -67,7 +90,7 @@ class Workflow {
|
|
|
67
90
|
defaultRunner() {
|
|
68
91
|
return "ubuntu-22.04";
|
|
69
92
|
}
|
|
70
|
-
async compile(
|
|
93
|
+
async compile(compileOptions = {}) {
|
|
71
94
|
const result = {
|
|
72
95
|
name: this.name,
|
|
73
96
|
on: Object.fromEntries(this.events.map(({ name, options }) => [
|
|
@@ -154,7 +177,7 @@ class Workflow {
|
|
|
154
177
|
"working-directory": workingDirectory,
|
|
155
178
|
"timeout-minutes": timeout,
|
|
156
179
|
...options,
|
|
157
|
-
uses: await supplyChainAttack(step),
|
|
180
|
+
uses: await supplyChainAttack(step, compileOptions),
|
|
158
181
|
};
|
|
159
182
|
})),
|
|
160
183
|
outputs,
|
|
@@ -167,9 +190,9 @@ class Workflow {
|
|
|
167
190
|
lineWidth: 200,
|
|
168
191
|
noCompatMode: true,
|
|
169
192
|
})}`;
|
|
170
|
-
if (!
|
|
193
|
+
if (!compileOptions.filePath)
|
|
171
194
|
return compiled;
|
|
172
|
-
return writeFilePromise(path_1.default.join(process.cwd(), ".github", "workflows",
|
|
195
|
+
return writeFilePromise(path_1.default.join(process.cwd(), ".github", "workflows", compileOptions.filePath), compiled);
|
|
173
196
|
}
|
|
174
197
|
}
|
|
175
198
|
exports.Workflow = Workflow;
|
package/dist/workflow.spec.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
const vitest_1 = require("vitest");
|
|
4
7
|
const workflow_1 = require("./workflow");
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
5
9
|
(0, vitest_1.describe)("Workflow", () => {
|
|
6
10
|
(0, vitest_1.it)("generates a simple workflow", async () => {
|
|
7
11
|
const workflow = new workflow_1.Workflow("Simple");
|
|
@@ -278,4 +282,31 @@ exit 0`,
|
|
|
278
282
|
});
|
|
279
283
|
(0, vitest_1.expect)(await workflow.compile()).toMatchSnapshot();
|
|
280
284
|
});
|
|
285
|
+
(0, vitest_1.it)("supports supply chain attack, without writing the lock file", async () => {
|
|
286
|
+
const workflow = new workflow_1.Workflow("Supply chain attack");
|
|
287
|
+
workflow.on("push").addJob("job1", {
|
|
288
|
+
steps: [
|
|
289
|
+
{ name: "Do something", uses: "tj-actions/changed-files@v45.0.7" },
|
|
290
|
+
],
|
|
291
|
+
});
|
|
292
|
+
fs_1.default.writeFileSync("/tmp/lockfile1.json", JSON.stringify({
|
|
293
|
+
"tj-actions/changed-files@v45.0.7": "tj-actions/changed-files@youhavebeenhacked",
|
|
294
|
+
}, null, 2));
|
|
295
|
+
(0, vitest_1.expect)(await workflow.compile({
|
|
296
|
+
lockFilePath: "/tmp/lockfile1.json",
|
|
297
|
+
writeLockFile: false,
|
|
298
|
+
})).toMatchSnapshot();
|
|
299
|
+
});
|
|
300
|
+
(0, vitest_1.it)("supports supply chain attack, with writing the lock file", async () => {
|
|
301
|
+
const workflow = new workflow_1.Workflow("Supply chain attack");
|
|
302
|
+
workflow.on("push").addJob("job1", {
|
|
303
|
+
steps: [
|
|
304
|
+
{ name: "Do something", uses: "tj-actions/changed-files@v45.0.7" },
|
|
305
|
+
],
|
|
306
|
+
});
|
|
307
|
+
(0, vitest_1.expect)(await workflow.compile({
|
|
308
|
+
lockFilePath: "/tmp/lockfile2.json",
|
|
309
|
+
writeLockFile: true,
|
|
310
|
+
})).toMatchSnapshot();
|
|
311
|
+
});
|
|
281
312
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@factorialco/gat",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.1",
|
|
4
4
|
"description": "Write your GitHub Actions workflows using TypeScript",
|
|
5
5
|
"bin": {
|
|
6
6
|
"gat": "dist/cli.js"
|
|
@@ -33,12 +33,12 @@
|
|
|
33
33
|
"eslint": "^8.23.0",
|
|
34
34
|
"eslint-config-prettier": "^9.0.0",
|
|
35
35
|
"prettier": "^3.0.3",
|
|
36
|
-
"tsx": "^
|
|
36
|
+
"tsx": "^4.19.3",
|
|
37
37
|
"typescript": "^5.2.2",
|
|
38
38
|
"vitest": "^0.34.6"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"
|
|
41
|
+
"@octokit/rest": "^21.1.1",
|
|
42
42
|
"js-yaml": "^4.1.0",
|
|
43
43
|
"lodash": "^4.17.21"
|
|
44
44
|
},
|