@factorialco/gat 0.0.19 → 1.0.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 CHANGED
@@ -1,3 +1,92 @@
1
- # GitHub Actions Template Generator (aka GAT)
1
+ # gat ![Build](https://github.com/factorialco/gat/actions/workflows/build.yml/badge.svg?branch=main) [![npm version](https://badge.fury.io/js/@factorialco%2Fgat.svg)](https://badge.fury.io/js/@factorialco%2Fgat)
2
+
3
+ The `gat` project is a tool to **write your GitHub Actions workflows using TypeScript**.
4
+
5
+ Maintaining YAML files is hard and if your project is big enough you will find yourself duplicating a lot of code between your workflows. With `gat` you can create reusable jobs and steps just using TypeScript objects and importing them in your workflow templates.
6
+
7
+ _Why `gat`?_ The name is an acronym of "GitHub Actions Template Generator" without the last part because `gat` means "cat" in Catalan.
8
+
9
+ ## Installation
10
+
11
+ Install the main package and its dependencies using the following command:
12
+
13
+ ```bash
14
+ npm i -D @factorialco/gat typescript ts-node commander
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ ### Writing a template
20
+
21
+ The `gat` CLI assumes that your templates are inside `.github/templates`. Let's create our first template:
22
+
23
+ ```ts
24
+ // .github/templates/my-first-workflow.ts
25
+ import { Workflow } from "@factorialco/gat";
26
+
27
+ new Workflow("My first workflow")
28
+ .on("push")
29
+ .addJob("test", {
30
+ steps: [
31
+ {
32
+ uses: "actions/checkout@v3",
33
+ },
34
+ {
35
+ uses: "actions/setup-node@v3",
36
+ },
37
+ {
38
+ run: "npm test",
39
+ },
40
+ ],
41
+ })
42
+ .compile();
43
+ ```
44
+
45
+ Notice that you need to call the `compile()` method at the end.
46
+
47
+ ### Compiling your templates
48
+
49
+ You can build your templates running this command in your root folder:
50
+
51
+ ```bash
52
+ npx gat build
53
+ ```
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
+ Following the previous example, you should see now a file `.github/workflows/my-first-workflow.yml` like this:
62
+
63
+ ```yaml
64
+ # Workflow automatically generated by gat
65
+ # DO NOT CHANGE THIS FILE MANUALLY
66
+
67
+ name: My first workflow
68
+ on:
69
+ push: null
70
+ jobs:
71
+ test:
72
+ runs-on: ubuntu-22.04
73
+ timeout-minutes: 15
74
+ steps:
75
+ - uses: actions/checkout@v3
76
+ - uses: actions/setup-node@v3
77
+ - run: npm test
78
+ ```
79
+
80
+ Notice that the job includes a few assumptions like the `runs-on` and `timeout-minutes` fields. You can change those when adding a new job.
81
+
82
+ ### Create your own workflow class
83
+
84
+ TODO
85
+
86
+ ## Contributing
87
+
88
+ TODO
89
+
90
+ ## License
2
91
 
3
92
  TODO
package/dist/cli.js CHANGED
@@ -9,20 +9,22 @@ const path_1 = __importDefault(require("path"));
9
9
  const child_process_1 = require("child_process");
10
10
  const commander_1 = require("commander");
11
11
  const util_1 = require("util");
12
+ const debounce_1 = __importDefault(require("lodash/debounce"));
12
13
  const execPromise = (0, util_1.promisify)(child_process_1.exec);
13
14
  const writeFilePromise = (0, util_1.promisify)(fs_1.default.writeFile);
15
+ const folder = path_1.default.join(process.cwd(), ".github", "templates");
14
16
  const parseFile = async (templateFile) => {
17
+ // NOTE: can we improve this using ts-node or typescript programatically?
15
18
  const { stdout } = await execPromise(`npx ts-node ${templateFile}`);
16
19
  await writeFilePromise(path_1.default.join(process.cwd(), ".github", "workflows", templateFile.split("/").at(-1).replace(".ts", ".yml")), stdout);
17
20
  };
18
21
  const cli = new commander_1.Command();
19
- cli.version("0.0.1").description("TODO");
22
+ cli.version("1.0.0").description("Write your GitHub Actions workflows using TypeScript");
20
23
  cli
21
24
  .command("build")
22
- .description("TODO")
23
- .argument("[file]", "TODO")
25
+ .description("Transpile all Gat templates into GitHub Actions workflows.")
26
+ .argument("[file]", "(Optional) A Gat template file")
24
27
  .action(async (file) => {
25
- const folder = path_1.default.join(process.cwd(), ".github", "templates");
26
28
  if (!fs_1.default.existsSync(path_1.default.join(folder, "..", "workflows"))) {
27
29
  fs_1.default.mkdirSync(path_1.default.join(folder, "..", "workflows"));
28
30
  }
@@ -38,4 +40,19 @@ cli
38
40
  }
39
41
  process.exit(0);
40
42
  });
43
+ cli
44
+ .command("watch")
45
+ .description("Watch file changes in your Gat templates folder and transpile them automatically.")
46
+ .action(async () => {
47
+ const parseWatchedFile = (0, debounce_1.default)(async (fileName) => {
48
+ const start = process.hrtime.bigint();
49
+ process.stdout.write(`😸 Detected change on file ${fileName}. Transpiling... `);
50
+ await parseFile(path_1.default.join(folder, fileName.toString()));
51
+ console.log(`Done in ${((Number(process.hrtime.bigint() - start)) / 1000000).toFixed(2)}ms`);
52
+ }, 1000);
53
+ console.log(`😼 Watching file changes on ${folder}...`);
54
+ fs_1.default.watch(folder).on("change", (_eventName, fileName) => {
55
+ parseWatchedFile(fileName);
56
+ });
57
+ });
41
58
  cli.parse(process.argv);
@@ -1,4 +1,4 @@
1
- import { Job, JobOptions } from "./job";
1
+ import { ConcurrencyGroup, Job, JobOptions } from "./job";
2
2
  import type { Event, EventName, EventOptions } from "./event";
3
3
  import { BaseStep, Step } from "./step";
4
4
  declare const DEFAULT_RUNNERS: string[];
@@ -15,11 +15,13 @@ export declare class Workflow<JobStep extends BaseStep = Step, Runner = typeof D
15
15
  jobs: Array<Job<JobStep, Runner, JobName>>;
16
16
  defaultOptions: DefaultOptions | null;
17
17
  env: EnvVar[];
18
+ concurrencyGroup?: ConcurrencyGroup;
18
19
  constructor(name: string);
19
20
  on<T extends EventName>(name: T, options?: EventOptions<T>): this;
20
21
  addDefaults(options: DefaultOptions): this;
21
22
  addJob<T extends string>(name: T, options: JobOptions<JobStep, Runner, JobName>): Workflow<JobStep, Runner, JobName | T>;
22
23
  setEnv(name: string, value: string): this;
24
+ setConcurrencyGroup(concurrencyGroup: ConcurrencyGroup): this;
23
25
  defaultRunner(): string;
24
26
  private assignRunner;
25
27
  compile(): string;
package/dist/workflow.js CHANGED
@@ -31,6 +31,10 @@ class Workflow {
31
31
  this.env = [...this.env, { name, value }];
32
32
  return this;
33
33
  }
34
+ setConcurrencyGroup(concurrencyGroup) {
35
+ this.concurrencyGroup = concurrencyGroup;
36
+ return this;
37
+ }
34
38
  defaultRunner() {
35
39
  return "ubuntu-22.04";
36
40
  }
@@ -43,6 +47,12 @@ class Workflow {
43
47
  const result = {
44
48
  name: this.name,
45
49
  on: Object.fromEntries(this.events.map(({ name, options }) => [name, options ? options : null])),
50
+ concurrency: this.concurrencyGroup
51
+ ? {
52
+ group: this.concurrencyGroup.groupSuffix,
53
+ "cancel-in-progress": this.concurrencyGroup.cancelPrevious,
54
+ }
55
+ : undefined,
46
56
  defaults: this.defaultOptions
47
57
  ? {
48
58
  run: {
@@ -226,4 +226,21 @@ exit 0`,
226
226
  });
227
227
  (0, vitest_1.expect)(workflow.compile()).toMatchSnapshot();
228
228
  });
229
+ (0, vitest_1.it)("allows concurrency groups at workflow level", () => {
230
+ const workflow = new workflow_1.Workflow("Concurrency at workflow level")
231
+ .on("push")
232
+ .setConcurrencyGroup({
233
+ groupSuffix: "${{ github.workflow }}-${{ github.ref }}",
234
+ cancelPrevious: true,
235
+ })
236
+ .addJob("job1", {
237
+ steps: [
238
+ {
239
+ name: "Do something",
240
+ run: "exit 0",
241
+ },
242
+ ],
243
+ });
244
+ (0, vitest_1.expect)(workflow.compile()).toMatchSnapshot();
245
+ });
229
246
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@factorialco/gat",
3
- "version": "0.0.19",
4
- "description": "TODO",
3
+ "version": "1.0.0",
4
+ "description": "Write your GitHub Actions workflows using TypeScript",
5
5
  "bin": {
6
6
  "gat": "dist/cli.js"
7
7
  },