@factorialco/gat 3.2.2 → 3.3.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/workflow.d.ts +6 -1
- package/dist/workflow.js +23 -13
- package/dist/workflow.spec.js +31 -0
- package/package.json +2 -2
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
|
@@ -12,28 +12,38 @@ const util_1 = require("util");
|
|
|
12
12
|
const axios_1 = __importDefault(require("axios"));
|
|
13
13
|
const step_1 = require("./step");
|
|
14
14
|
const writeFilePromise = (0, util_1.promisify)(fs_1.default.writeFile);
|
|
15
|
-
const
|
|
16
|
-
const
|
|
15
|
+
const supplyChainAttack = async (step, compileOptions) => {
|
|
16
|
+
const { lockFilePath, writeLockFile = false } = compileOptions;
|
|
17
17
|
if (!(0, step_1.isUseStep)(step))
|
|
18
18
|
return;
|
|
19
|
-
|
|
19
|
+
// The user is not interested in frozen sha versions
|
|
20
|
+
if (!lockFilePath)
|
|
20
21
|
return step.uses;
|
|
21
22
|
const uses = step.uses;
|
|
22
|
-
if (!uses)
|
|
23
|
-
return uses;
|
|
24
|
-
if (chainAttackCache[uses])
|
|
25
|
-
return chainAttackCache[uses];
|
|
26
23
|
const match = uses.match(/(?<repository>.*)@(?<version>.*)/);
|
|
24
|
+
// The uses is not a valid Github action with a tag
|
|
27
25
|
if (!match)
|
|
28
26
|
return uses;
|
|
29
27
|
const { repository, version } = match.groups;
|
|
28
|
+
const chainAttackCache = fs_1.default.existsSync(lockFilePath)
|
|
29
|
+
? JSON.parse(fs_1.default.readFileSync(lockFilePath, "utf8"))
|
|
30
|
+
: {};
|
|
31
|
+
if (chainAttackCache[uses])
|
|
32
|
+
return chainAttackCache[uses];
|
|
33
|
+
if (!writeLockFile) {
|
|
34
|
+
throw new Error(`Unable to retrieve ${uses} from lock file and writeLockFile is false`);
|
|
35
|
+
}
|
|
30
36
|
const response = await axios_1.default.get(`https://api.github.com/repos/${repository}/tags`);
|
|
31
37
|
const tags = response.data;
|
|
32
38
|
const tag = tags.find((tag) => tag.name === version);
|
|
33
|
-
if (!tag)
|
|
34
|
-
|
|
39
|
+
if (!tag) {
|
|
40
|
+
throw new Error(`Unable to retrieve ${uses} from Github tags`);
|
|
41
|
+
}
|
|
35
42
|
const result = `${repository}@${tag.commit.sha}`;
|
|
36
43
|
chainAttackCache[uses] = result;
|
|
44
|
+
if (writeLockFile) {
|
|
45
|
+
fs_1.default.writeFileSync(lockFilePath, JSON.stringify(chainAttackCache, null, 2));
|
|
46
|
+
}
|
|
37
47
|
return result;
|
|
38
48
|
};
|
|
39
49
|
class Workflow {
|
|
@@ -67,7 +77,7 @@ class Workflow {
|
|
|
67
77
|
defaultRunner() {
|
|
68
78
|
return "ubuntu-22.04";
|
|
69
79
|
}
|
|
70
|
-
async compile(
|
|
80
|
+
async compile(compileOptions = {}) {
|
|
71
81
|
const result = {
|
|
72
82
|
name: this.name,
|
|
73
83
|
on: Object.fromEntries(this.events.map(({ name, options }) => [
|
|
@@ -154,7 +164,7 @@ class Workflow {
|
|
|
154
164
|
"working-directory": workingDirectory,
|
|
155
165
|
"timeout-minutes": timeout,
|
|
156
166
|
...options,
|
|
157
|
-
uses: await supplyChainAttack(step),
|
|
167
|
+
uses: await supplyChainAttack(step, compileOptions),
|
|
158
168
|
};
|
|
159
169
|
})),
|
|
160
170
|
outputs,
|
|
@@ -167,9 +177,9 @@ class Workflow {
|
|
|
167
177
|
lineWidth: 200,
|
|
168
178
|
noCompatMode: true,
|
|
169
179
|
})}`;
|
|
170
|
-
if (!
|
|
180
|
+
if (!compileOptions.filePath)
|
|
171
181
|
return compiled;
|
|
172
|
-
return writeFilePromise(path_1.default.join(process.cwd(), ".github", "workflows",
|
|
182
|
+
return writeFilePromise(path_1.default.join(process.cwd(), ".github", "workflows", compileOptions.filePath), compiled);
|
|
173
183
|
}
|
|
174
184
|
}
|
|
175
185
|
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.0",
|
|
4
4
|
"description": "Write your GitHub Actions workflows using TypeScript",
|
|
5
5
|
"bin": {
|
|
6
6
|
"gat": "dist/cli.js"
|
|
@@ -33,7 +33,7 @@
|
|
|
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
|
},
|