@factorialco/gat 2.0.0 → 2.1.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/README.md +4 -10
- package/dist/step.d.ts +1 -0
- package/dist/step.js +5 -0
- package/dist/workflow.d.ts +3 -3
- package/dist/workflow.js +42 -12
- package/dist/workflow.spec.js +32 -32
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,10 +18,10 @@ npm i -D @factorialco/gat typescript ts-node commander @swc/core
|
|
|
18
18
|
|
|
19
19
|
### Writing a template
|
|
20
20
|
|
|
21
|
-
The `gat` CLI assumes that
|
|
21
|
+
The `gat` CLI assumes that you have a `index.ts` file inside `.github/templates`. Let's create our first template:
|
|
22
22
|
|
|
23
23
|
```ts
|
|
24
|
-
// .github/templates/
|
|
24
|
+
// .github/templates/index.ts
|
|
25
25
|
import { Workflow } from "@factorialco/gat";
|
|
26
26
|
|
|
27
27
|
new Workflow("My first workflow")
|
|
@@ -39,10 +39,10 @@ new Workflow("My first workflow")
|
|
|
39
39
|
},
|
|
40
40
|
],
|
|
41
41
|
})
|
|
42
|
-
.compile();
|
|
42
|
+
.compile("my-first-workflow.yml");
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
-
Notice that you need to call the `compile()` method at the end.
|
|
45
|
+
Notice that you need to call the `compile()` method at the end, passing the file name of the generated Github Actions workflow.
|
|
46
46
|
|
|
47
47
|
### Compiling your templates
|
|
48
48
|
|
|
@@ -52,12 +52,6 @@ You can build your templates running this command in your root folder:
|
|
|
52
52
|
npx gat build
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
-
Alternatively you can also compile a single template:
|
|
56
|
-
|
|
57
|
-
```bash
|
|
58
|
-
npx gat build .github/templates/some-workflow.ts
|
|
59
|
-
```
|
|
60
|
-
|
|
61
55
|
Following the previous example, you should see now a file `.github/workflows/my-first-workflow.yml` like this:
|
|
62
56
|
|
|
63
57
|
```yaml
|
package/dist/step.d.ts
CHANGED
package/dist/step.js
CHANGED
package/dist/workflow.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ConcurrencyGroup, Job, JobOptions, StringWithNoSpaces } from "./job";
|
|
2
2
|
import type { Event, EventName, EventOptions } from "./event";
|
|
3
|
-
import {
|
|
3
|
+
import { Step } from "./step";
|
|
4
4
|
declare const DEFAULT_RUNNERS: string[];
|
|
5
5
|
interface DefaultOptions {
|
|
6
6
|
workingDirectory: string;
|
|
@@ -9,7 +9,7 @@ interface EnvVar {
|
|
|
9
9
|
name: string;
|
|
10
10
|
value: string;
|
|
11
11
|
}
|
|
12
|
-
export declare class Workflow<JobStep extends
|
|
12
|
+
export declare class Workflow<JobStep extends Step = Step, Runner = typeof DEFAULT_RUNNERS, JobName = never> {
|
|
13
13
|
name: string;
|
|
14
14
|
events: Event[];
|
|
15
15
|
jobs: Array<Job<JobStep, Runner, JobName>>;
|
|
@@ -24,6 +24,6 @@ export declare class Workflow<JobStep extends BaseStep = Step, Runner = typeof D
|
|
|
24
24
|
setConcurrencyGroup(concurrencyGroup: ConcurrencyGroup): this;
|
|
25
25
|
defaultRunner(): string;
|
|
26
26
|
private assignRunner;
|
|
27
|
-
compile(filepath?: string): string |
|
|
27
|
+
compile(filepath?: string): Promise<string | void>;
|
|
28
28
|
}
|
|
29
29
|
export {};
|
package/dist/workflow.js
CHANGED
|
@@ -9,8 +9,31 @@ 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 step_1 = require("./step");
|
|
12
13
|
const writeFilePromise = (0, util_1.promisify)(fs_1.default.writeFile);
|
|
13
14
|
const DEFAULT_RUNNERS = ["ubuntu-22.04"];
|
|
15
|
+
const chainAttackCache = {};
|
|
16
|
+
const supplyChainAttack = async (step) => {
|
|
17
|
+
if (!(0, step_1.isUseStep)(step))
|
|
18
|
+
return;
|
|
19
|
+
const uses = step.uses;
|
|
20
|
+
if (!uses)
|
|
21
|
+
return uses;
|
|
22
|
+
if (chainAttackCache[uses])
|
|
23
|
+
return chainAttackCache[uses];
|
|
24
|
+
const match = uses.match(/(?<repository>.*)@(?<version>.*)/);
|
|
25
|
+
if (!match)
|
|
26
|
+
return uses;
|
|
27
|
+
const { repository, version } = match.groups;
|
|
28
|
+
const response = await fetch(`https://api.github.com/repos/${repository}/tags`);
|
|
29
|
+
const tags = (await response.json());
|
|
30
|
+
const tag = tags.find((tag) => tag.name === version);
|
|
31
|
+
if (!tag)
|
|
32
|
+
return uses;
|
|
33
|
+
const result = `${repository}@${tag.commit.sha}`;
|
|
34
|
+
chainAttackCache[uses] = result;
|
|
35
|
+
return result;
|
|
36
|
+
};
|
|
14
37
|
class Workflow {
|
|
15
38
|
constructor(name) {
|
|
16
39
|
this.name = name;
|
|
@@ -47,10 +70,13 @@ class Workflow {
|
|
|
47
70
|
const isSelfHosted = !DEFAULT_RUNNERS.includes(runnerName);
|
|
48
71
|
return isSelfHosted ? ["self-hosted", runnerName] : runnerName;
|
|
49
72
|
}
|
|
50
|
-
compile(filepath) {
|
|
73
|
+
async compile(filepath) {
|
|
51
74
|
const result = {
|
|
52
75
|
name: this.name,
|
|
53
|
-
on: Object.fromEntries(this.events.map(({ name, options }) => [
|
|
76
|
+
on: Object.fromEntries(this.events.map(({ name, options }) => [
|
|
77
|
+
name,
|
|
78
|
+
options ? options : null,
|
|
79
|
+
])),
|
|
54
80
|
concurrency: this.concurrencyGroup
|
|
55
81
|
? {
|
|
56
82
|
group: this.concurrencyGroup.groupSuffix,
|
|
@@ -67,7 +93,7 @@ class Workflow {
|
|
|
67
93
|
env: this.env.length > 0
|
|
68
94
|
? Object.fromEntries(this.env.map(({ name, value }) => [name, value]))
|
|
69
95
|
: undefined,
|
|
70
|
-
jobs: Object.fromEntries(this.jobs.map(({ name, options: { prettyName, permissions, ifExpression, runsOn, matrix, env, steps, dependsOn, services, timeout, concurrency, outputs, workingDirectory, }, }) => [
|
|
96
|
+
jobs: Object.fromEntries(await Promise.all(this.jobs.map(async ({ name, options: { prettyName, permissions, ifExpression, runsOn, matrix, env, steps, dependsOn, services, timeout, concurrency, outputs, workingDirectory, }, }) => [
|
|
71
97
|
name,
|
|
72
98
|
{
|
|
73
99
|
name: prettyName,
|
|
@@ -105,18 +131,22 @@ class Workflow {
|
|
|
105
131
|
},
|
|
106
132
|
}
|
|
107
133
|
: undefined,
|
|
108
|
-
steps: steps.map((
|
|
109
|
-
id,
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
134
|
+
steps: await Promise.all(steps.map(async (step) => {
|
|
135
|
+
const { id, name, ifExpression, workingDirectory, continueOnError, timeout, ...options } = step;
|
|
136
|
+
return {
|
|
137
|
+
id,
|
|
138
|
+
name,
|
|
139
|
+
if: ifExpression,
|
|
140
|
+
"continue-on-error": continueOnError,
|
|
141
|
+
"working-directory": workingDirectory,
|
|
142
|
+
"timeout-minutes": timeout,
|
|
143
|
+
...options,
|
|
144
|
+
uses: await supplyChainAttack(step),
|
|
145
|
+
};
|
|
116
146
|
})),
|
|
117
147
|
outputs,
|
|
118
148
|
},
|
|
119
|
-
])),
|
|
149
|
+
]))),
|
|
120
150
|
};
|
|
121
151
|
const compiled = `# Workflow automatically generated by gat\n# DO NOT CHANGE THIS FILE MANUALLY\n\n${(0, js_yaml_1.dump)(result, {
|
|
122
152
|
noRefs: true,
|
package/dist/workflow.spec.js
CHANGED
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
const vitest_1 = require("vitest");
|
|
4
4
|
const workflow_1 = require("./workflow");
|
|
5
5
|
(0, vitest_1.describe)("Workflow", () => {
|
|
6
|
-
(0, vitest_1.it)("generates a simple workflow", () => {
|
|
6
|
+
(0, vitest_1.it)("generates a simple workflow", async () => {
|
|
7
7
|
const workflow = new workflow_1.Workflow("Simple");
|
|
8
8
|
workflow
|
|
9
9
|
.on("pull_request", { types: ["opened"] })
|
|
@@ -14,9 +14,9 @@ const workflow_1 = require("./workflow");
|
|
|
14
14
|
steps: [{ name: "Do something else", run: "exit 0" }],
|
|
15
15
|
dependsOn: ["job1"],
|
|
16
16
|
});
|
|
17
|
-
(0, vitest_1.expect)(workflow.compile()).toMatchSnapshot();
|
|
17
|
+
(0, vitest_1.expect)(await workflow.compile()).toMatchSnapshot();
|
|
18
18
|
});
|
|
19
|
-
(0, vitest_1.it)("allows multiple events", () => {
|
|
19
|
+
(0, vitest_1.it)("allows multiple events", async () => {
|
|
20
20
|
const workflow = new workflow_1.Workflow("Multiple events");
|
|
21
21
|
workflow
|
|
22
22
|
.on("push", { branches: ["main"] })
|
|
@@ -24,9 +24,9 @@ const workflow_1 = require("./workflow");
|
|
|
24
24
|
.addJob("job1", {
|
|
25
25
|
steps: [{ name: "Do something", run: "exit 0" }],
|
|
26
26
|
});
|
|
27
|
-
(0, vitest_1.expect)(workflow.compile()).toMatchSnapshot();
|
|
27
|
+
(0, vitest_1.expect)(await workflow.compile()).toMatchSnapshot();
|
|
28
28
|
});
|
|
29
|
-
(0, vitest_1.it)("allows declaring default options", () => {
|
|
29
|
+
(0, vitest_1.it)("allows declaring default options", async () => {
|
|
30
30
|
const workflow = new workflow_1.Workflow("Default options");
|
|
31
31
|
workflow
|
|
32
32
|
.on("push", { branches: ["main"] })
|
|
@@ -36,9 +36,9 @@ const workflow_1 = require("./workflow");
|
|
|
36
36
|
.addJob("job1", {
|
|
37
37
|
steps: [{ name: "Do something", run: "exit 0" }],
|
|
38
38
|
});
|
|
39
|
-
(0, vitest_1.expect)(workflow.compile()).toMatchSnapshot();
|
|
39
|
+
(0, vitest_1.expect)(await workflow.compile()).toMatchSnapshot();
|
|
40
40
|
});
|
|
41
|
-
(0, vitest_1.it)("allows declaring environment variables", () => {
|
|
41
|
+
(0, vitest_1.it)("allows declaring environment variables", async () => {
|
|
42
42
|
const workflow = new workflow_1.Workflow("With Environment variables");
|
|
43
43
|
workflow
|
|
44
44
|
.on("push")
|
|
@@ -52,9 +52,9 @@ const workflow_1 = require("./workflow");
|
|
|
52
52
|
},
|
|
53
53
|
],
|
|
54
54
|
});
|
|
55
|
-
(0, vitest_1.expect)(workflow.compile()).toMatchSnapshot();
|
|
55
|
+
(0, vitest_1.expect)(await workflow.compile()).toMatchSnapshot();
|
|
56
56
|
});
|
|
57
|
-
(0, vitest_1.it)("allows using a concurrency group", () => {
|
|
57
|
+
(0, vitest_1.it)("allows using a concurrency group", async () => {
|
|
58
58
|
const workflow = new workflow_1.Workflow("Concurrency group");
|
|
59
59
|
workflow.on("push").addJob("job1", {
|
|
60
60
|
concurrency: {
|
|
@@ -67,9 +67,9 @@ const workflow_1 = require("./workflow");
|
|
|
67
67
|
},
|
|
68
68
|
],
|
|
69
69
|
});
|
|
70
|
-
(0, vitest_1.expect)(workflow.compile()).toMatchSnapshot();
|
|
70
|
+
(0, vitest_1.expect)(await workflow.compile()).toMatchSnapshot();
|
|
71
71
|
});
|
|
72
|
-
(0, vitest_1.it)("allows using outputs", () => {
|
|
72
|
+
(0, vitest_1.it)("allows using outputs", async () => {
|
|
73
73
|
const workflow = new workflow_1.Workflow("Using outputs");
|
|
74
74
|
workflow.on("push").addJob("job1", {
|
|
75
75
|
steps: [
|
|
@@ -82,9 +82,9 @@ const workflow_1 = require("./workflow");
|
|
|
82
82
|
"random-number": "${{ steps.random-number.outputs.random-number }}",
|
|
83
83
|
},
|
|
84
84
|
});
|
|
85
|
-
(0, vitest_1.expect)(workflow.compile()).toMatchSnapshot();
|
|
85
|
+
(0, vitest_1.expect)(await workflow.compile()).toMatchSnapshot();
|
|
86
86
|
});
|
|
87
|
-
(0, vitest_1.it)("allows conditional jobs", () => {
|
|
87
|
+
(0, vitest_1.it)("allows conditional jobs", async () => {
|
|
88
88
|
const workflow = new workflow_1.Workflow("Conditional job");
|
|
89
89
|
workflow.on("push").addJob("job1", {
|
|
90
90
|
ifExpression: "${{ github.ref != 'refs/heads/main' }}",
|
|
@@ -94,9 +94,9 @@ const workflow_1 = require("./workflow");
|
|
|
94
94
|
},
|
|
95
95
|
],
|
|
96
96
|
});
|
|
97
|
-
(0, vitest_1.expect)(workflow.compile()).toMatchSnapshot();
|
|
97
|
+
(0, vitest_1.expect)(await workflow.compile()).toMatchSnapshot();
|
|
98
98
|
});
|
|
99
|
-
(0, vitest_1.it)("allows a job matrix", () => {
|
|
99
|
+
(0, vitest_1.it)("allows a job matrix", async () => {
|
|
100
100
|
const workflow = new workflow_1.Workflow("Conditional job");
|
|
101
101
|
workflow.on("push").addJob("job1", {
|
|
102
102
|
matrix: {
|
|
@@ -124,9 +124,9 @@ const workflow_1 = require("./workflow");
|
|
|
124
124
|
},
|
|
125
125
|
],
|
|
126
126
|
});
|
|
127
|
-
(0, vitest_1.expect)(workflow.compile()).toMatchSnapshot();
|
|
127
|
+
(0, vitest_1.expect)(await workflow.compile()).toMatchSnapshot();
|
|
128
128
|
});
|
|
129
|
-
(0, vitest_1.it)("allows uses steps", () => {
|
|
129
|
+
(0, vitest_1.it)("allows uses steps", async () => {
|
|
130
130
|
const workflow = new workflow_1.Workflow("Uses steps");
|
|
131
131
|
workflow
|
|
132
132
|
.on("push")
|
|
@@ -142,9 +142,9 @@ const workflow_1 = require("./workflow");
|
|
|
142
142
|
},
|
|
143
143
|
],
|
|
144
144
|
});
|
|
145
|
-
(0, vitest_1.expect)(workflow.compile()).toMatchSnapshot();
|
|
145
|
+
(0, vitest_1.expect)(await workflow.compile()).toMatchSnapshot();
|
|
146
146
|
});
|
|
147
|
-
(0, vitest_1.it)("allows custom types in a workflow", () => {
|
|
147
|
+
(0, vitest_1.it)("allows custom types in a workflow", async () => {
|
|
148
148
|
const workflow = new workflow_1.Workflow("With custom types");
|
|
149
149
|
workflow.on("push").addJob("job1", {
|
|
150
150
|
runsOn: "standard-runner",
|
|
@@ -160,9 +160,9 @@ const workflow_1 = require("./workflow");
|
|
|
160
160
|
},
|
|
161
161
|
],
|
|
162
162
|
});
|
|
163
|
-
(0, vitest_1.expect)(workflow.compile()).toMatchSnapshot();
|
|
163
|
+
(0, vitest_1.expect)(await workflow.compile()).toMatchSnapshot();
|
|
164
164
|
});
|
|
165
|
-
(0, vitest_1.it)("support workflow dispatch event", () => {
|
|
165
|
+
(0, vitest_1.it)("support workflow dispatch event", async () => {
|
|
166
166
|
const workflow = new workflow_1.Workflow("Workflow dispatch");
|
|
167
167
|
workflow
|
|
168
168
|
.on("workflow_dispatch", {
|
|
@@ -181,26 +181,26 @@ const workflow_1 = require("./workflow");
|
|
|
181
181
|
.addJob("job1", {
|
|
182
182
|
steps: [{ name: "Do something", run: "exit 0" }],
|
|
183
183
|
});
|
|
184
|
-
(0, vitest_1.expect)(workflow.compile()).toMatchSnapshot();
|
|
184
|
+
(0, vitest_1.expect)(await workflow.compile()).toMatchSnapshot();
|
|
185
185
|
});
|
|
186
|
-
(0, vitest_1.it)("supports schedule event", () => {
|
|
186
|
+
(0, vitest_1.it)("supports schedule event", async () => {
|
|
187
187
|
const workflow = new workflow_1.Workflow("Schedule")
|
|
188
188
|
.on("schedule", [{ cron: "0 4 * * 1-5" }])
|
|
189
189
|
.addJob("job1", {
|
|
190
190
|
steps: [{ name: "Do something", run: "exit 0" }],
|
|
191
191
|
});
|
|
192
|
-
(0, vitest_1.expect)(workflow.compile()).toMatchSnapshot();
|
|
192
|
+
(0, vitest_1.expect)(await workflow.compile()).toMatchSnapshot();
|
|
193
193
|
});
|
|
194
|
-
(0, vitest_1.it)("supports a pretty name for the job", () => {
|
|
194
|
+
(0, vitest_1.it)("supports a pretty name for the job", async () => {
|
|
195
195
|
const workflow = new workflow_1.Workflow("Job with pretty name")
|
|
196
196
|
.on("push")
|
|
197
197
|
.addJob("job1", {
|
|
198
198
|
prettyName: "My pretty name",
|
|
199
199
|
steps: [{ name: "Do something", run: "exit 0" }],
|
|
200
200
|
});
|
|
201
|
-
(0, vitest_1.expect)(workflow.compile()).toMatchSnapshot();
|
|
201
|
+
(0, vitest_1.expect)(await workflow.compile()).toMatchSnapshot();
|
|
202
202
|
});
|
|
203
|
-
(0, vitest_1.it)("allows permissions into jobs", () => {
|
|
203
|
+
(0, vitest_1.it)("allows permissions into jobs", async () => {
|
|
204
204
|
const workflow = new workflow_1.Workflow("Job with permissions")
|
|
205
205
|
.on("push")
|
|
206
206
|
.addJob("job1", {
|
|
@@ -210,9 +210,9 @@ const workflow_1 = require("./workflow");
|
|
|
210
210
|
},
|
|
211
211
|
steps: [{ name: "Do something", run: "exit 0" }],
|
|
212
212
|
});
|
|
213
|
-
(0, vitest_1.expect)(workflow.compile()).toMatchSnapshot();
|
|
213
|
+
(0, vitest_1.expect)(await workflow.compile()).toMatchSnapshot();
|
|
214
214
|
});
|
|
215
|
-
(0, vitest_1.it)("allows multiline strings", () => {
|
|
215
|
+
(0, vitest_1.it)("allows multiline strings", async () => {
|
|
216
216
|
const workflow = new workflow_1.Workflow("Multiline strings")
|
|
217
217
|
.on("push")
|
|
218
218
|
.addJob("job1", {
|
|
@@ -224,9 +224,9 @@ exit 0`,
|
|
|
224
224
|
},
|
|
225
225
|
],
|
|
226
226
|
});
|
|
227
|
-
(0, vitest_1.expect)(workflow.compile()).toMatchSnapshot();
|
|
227
|
+
(0, vitest_1.expect)(await workflow.compile()).toMatchSnapshot();
|
|
228
228
|
});
|
|
229
|
-
(0, vitest_1.it)("allows concurrency groups at workflow level", () => {
|
|
229
|
+
(0, vitest_1.it)("allows concurrency groups at workflow level", async () => {
|
|
230
230
|
const workflow = new workflow_1.Workflow("Concurrency at workflow level")
|
|
231
231
|
.on("push")
|
|
232
232
|
.setConcurrencyGroup({
|
|
@@ -241,6 +241,6 @@ exit 0`,
|
|
|
241
241
|
},
|
|
242
242
|
],
|
|
243
243
|
});
|
|
244
|
-
(0, vitest_1.expect)(workflow.compile()).toMatchSnapshot();
|
|
244
|
+
(0, vitest_1.expect)(await workflow.compile()).toMatchSnapshot();
|
|
245
245
|
});
|
|
246
246
|
});
|