@factorialco/gat 3.3.1 → 3.4.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/index.d.ts +2 -1
- package/dist/index.js +3 -1
- package/dist/template.d.ts +8 -0
- package/dist/template.js +57 -0
- package/dist/workflow.d.ts +4 -5
- package/dist/workflow.js +4 -46
- package/dist/workflow.spec.js +4 -22
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Workflow } from "./workflow";
|
|
2
2
|
import { type ConcurrencyGroup, type StepsJobOptions, type UsesJobOptions, type Matrix, type Service } from "./job";
|
|
3
3
|
import { type RunStep, type UseStep, type BaseStep } from "./step";
|
|
4
|
-
|
|
4
|
+
import { compileTemplates } from "./template";
|
|
5
|
+
export { Workflow, RunStep, UseStep, BaseStep, ConcurrencyGroup, StepsJobOptions, UsesJobOptions, Matrix, Service, compileTemplates, };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.Workflow = void 0;
|
|
3
|
+
exports.compileTemplates = exports.Workflow = void 0;
|
|
4
4
|
const workflow_1 = require("./workflow");
|
|
5
5
|
Object.defineProperty(exports, "Workflow", { enumerable: true, get: function () { return workflow_1.Workflow; } });
|
|
6
|
+
const template_1 = require("./template");
|
|
7
|
+
Object.defineProperty(exports, "compileTemplates", { enumerable: true, get: function () { return template_1.compileTemplates; } });
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Workflow } from "./workflow";
|
|
2
|
+
interface CompileOptions {
|
|
3
|
+
templates: Record<string, Workflow>;
|
|
4
|
+
lockFilePath: string;
|
|
5
|
+
writeLockFile: boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare const compileTemplates: (options: CompileOptions) => Promise<(string | void)[]>;
|
|
8
|
+
export {};
|
package/dist/template.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.compileTemplates = void 0;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const rest_1 = require("@octokit/rest");
|
|
9
|
+
const createLockFile = async (templates, lockFilePath) => {
|
|
10
|
+
const actions = Object.values(templates).reduce((acc, template) => {
|
|
11
|
+
template.jobs.forEach((job) => {
|
|
12
|
+
if ("steps" in job.options) {
|
|
13
|
+
job.options.steps.forEach((step) => {
|
|
14
|
+
if ("uses" in step) {
|
|
15
|
+
acc.push(step.uses);
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
return acc;
|
|
21
|
+
}, []);
|
|
22
|
+
const uniqueActions = [...new Set(actions)];
|
|
23
|
+
const octokit = process.env.GITHUB_TOKEN
|
|
24
|
+
? new rest_1.Octokit({
|
|
25
|
+
auth: process.env.GITHUB_TOKEN,
|
|
26
|
+
})
|
|
27
|
+
: new rest_1.Octokit();
|
|
28
|
+
const resolvedActions = {};
|
|
29
|
+
await Promise.all(uniqueActions.map(async (action) => {
|
|
30
|
+
const match = action.match(/(?<repository>.*)@(?<version>.*)/);
|
|
31
|
+
if (!match)
|
|
32
|
+
return;
|
|
33
|
+
const { repository, version } = match.groups;
|
|
34
|
+
const [owner, repo] = repository.split("/");
|
|
35
|
+
const response = await octokit.rest.repos.listTags({
|
|
36
|
+
owner,
|
|
37
|
+
repo,
|
|
38
|
+
});
|
|
39
|
+
const tag = response.data.find((tag) => tag.name === version);
|
|
40
|
+
if (!tag) {
|
|
41
|
+
throw new Error(`Unable to retrieve ${action} from Github tags`);
|
|
42
|
+
}
|
|
43
|
+
resolvedActions[action] = `${repository}@${tag.commit.sha}`;
|
|
44
|
+
}));
|
|
45
|
+
fs_1.default.writeFileSync(lockFilePath, JSON.stringify(resolvedActions, null, 2));
|
|
46
|
+
};
|
|
47
|
+
const compileTemplates = async (options) => {
|
|
48
|
+
const { templates, lockFilePath, writeLockFile } = options;
|
|
49
|
+
if (writeLockFile) {
|
|
50
|
+
await createLockFile(templates, lockFilePath);
|
|
51
|
+
}
|
|
52
|
+
const resolvedActions = fs_1.default.existsSync(lockFilePath)
|
|
53
|
+
? JSON.parse(fs_1.default.readFileSync(lockFilePath, "utf8"))
|
|
54
|
+
: {};
|
|
55
|
+
return Promise.all(Object.entries(templates).map(([filePath, template]) => template.compile({ filePath, resolvedActions })));
|
|
56
|
+
};
|
|
57
|
+
exports.compileTemplates = compileTemplates;
|
package/dist/workflow.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { ConcurrencyGroup, Job, StepsJobOptions, StringWithNoSpaces, UsesJobOptions } from "./job";
|
|
2
1
|
import type { Event, EventName, EventOptions } from "./event";
|
|
2
|
+
import { ConcurrencyGroup, Job, StepsJobOptions, StringWithNoSpaces, UsesJobOptions } from "./job";
|
|
3
3
|
import { type Step } from "./step";
|
|
4
4
|
interface DefaultOptions {
|
|
5
5
|
workingDirectory: string;
|
|
@@ -8,10 +8,9 @@ interface EnvVar {
|
|
|
8
8
|
name: string;
|
|
9
9
|
value: string;
|
|
10
10
|
}
|
|
11
|
-
interface
|
|
11
|
+
interface WorkflowCompileOptions {
|
|
12
12
|
filePath?: string;
|
|
13
|
-
|
|
14
|
-
writeLockFile?: boolean;
|
|
13
|
+
resolvedActions?: Record<string, string>;
|
|
15
14
|
}
|
|
16
15
|
export type RunnerDefinition = string | {
|
|
17
16
|
group: string;
|
|
@@ -31,6 +30,6 @@ export declare class Workflow<JobStep extends Step = Step, Runner extends Runner
|
|
|
31
30
|
setEnv(name: string, value: string): this;
|
|
32
31
|
setConcurrencyGroup(concurrencyGroup: ConcurrencyGroup | null): this;
|
|
33
32
|
defaultRunner(): string;
|
|
34
|
-
compile(compileOptions?:
|
|
33
|
+
compile(compileOptions?: WorkflowCompileOptions): Promise<string | void>;
|
|
35
34
|
}
|
|
36
35
|
export {};
|
package/dist/workflow.js
CHANGED
|
@@ -4,60 +4,18 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.Workflow = void 0;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
7
8
|
const js_yaml_1 = require("js-yaml");
|
|
8
9
|
const kebabCase_1 = __importDefault(require("lodash/kebabCase"));
|
|
9
|
-
const fs_1 = __importDefault(require("fs"));
|
|
10
10
|
const path_1 = __importDefault(require("path"));
|
|
11
11
|
const util_1 = require("util");
|
|
12
|
-
const rest_1 = require("@octokit/rest");
|
|
13
12
|
const step_1 = require("./step");
|
|
14
13
|
const writeFilePromise = (0, util_1.promisify)(fs_1.default.writeFile);
|
|
15
|
-
|
|
16
|
-
const supplyChainAttack = async (step, compileOptions) => {
|
|
17
|
-
const { lockFilePath, writeLockFile = false } = compileOptions;
|
|
14
|
+
const supplyChainAttack = async (step, resolvedActions) => {
|
|
18
15
|
if (!(0, step_1.isUseStep)(step))
|
|
19
16
|
return;
|
|
20
|
-
// The user is not interested in frozen sha versions
|
|
21
|
-
if (!lockFilePath)
|
|
22
|
-
return step.uses;
|
|
23
17
|
const uses = step.uses;
|
|
24
|
-
|
|
25
|
-
// The uses is not a valid Github action with a tag
|
|
26
|
-
if (!match)
|
|
27
|
-
return uses;
|
|
28
|
-
const { repository, version } = match.groups;
|
|
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
|
-
}
|
|
55
|
-
const result = `${repository}@${tag.commit.sha}`;
|
|
56
|
-
chainAttackCache[uses] = result;
|
|
57
|
-
if (writeLockFile) {
|
|
58
|
-
fs_1.default.writeFileSync(lockFilePath, JSON.stringify(chainAttackCache, null, 2));
|
|
59
|
-
}
|
|
60
|
-
return result;
|
|
18
|
+
return resolvedActions[uses] ?? uses;
|
|
61
19
|
};
|
|
62
20
|
class Workflow {
|
|
63
21
|
constructor(name) {
|
|
@@ -177,7 +135,7 @@ class Workflow {
|
|
|
177
135
|
"working-directory": workingDirectory,
|
|
178
136
|
"timeout-minutes": timeout,
|
|
179
137
|
...options,
|
|
180
|
-
uses: await supplyChainAttack(step, compileOptions),
|
|
138
|
+
uses: await supplyChainAttack(step, compileOptions.resolvedActions ?? {}),
|
|
181
139
|
};
|
|
182
140
|
})),
|
|
183
141
|
outputs,
|
package/dist/workflow.spec.js
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
3
|
const vitest_1 = require("vitest");
|
|
7
4
|
const workflow_1 = require("./workflow");
|
|
8
|
-
const fs_1 = __importDefault(require("fs"));
|
|
9
5
|
(0, vitest_1.describe)("Workflow", () => {
|
|
10
6
|
(0, vitest_1.it)("generates a simple workflow", async () => {
|
|
11
7
|
const workflow = new workflow_1.Workflow("Simple");
|
|
@@ -282,31 +278,17 @@ exit 0`,
|
|
|
282
278
|
});
|
|
283
279
|
(0, vitest_1.expect)(await workflow.compile()).toMatchSnapshot();
|
|
284
280
|
});
|
|
285
|
-
(0, vitest_1.it)("supports supply chain attack
|
|
281
|
+
(0, vitest_1.it)("supports supply chain attack", async () => {
|
|
286
282
|
const workflow = new workflow_1.Workflow("Supply chain attack");
|
|
287
283
|
workflow.on("push").addJob("job1", {
|
|
288
284
|
steps: [
|
|
289
285
|
{ name: "Do something", uses: "tj-actions/changed-files@v45.0.7" },
|
|
290
286
|
],
|
|
291
287
|
});
|
|
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
288
|
(0, vitest_1.expect)(await workflow.compile({
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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,
|
|
289
|
+
resolvedActions: {
|
|
290
|
+
"tj-actions/changed-files@v45.0.7": "tj-actions/changed-files@youhavebeenhacked",
|
|
291
|
+
},
|
|
310
292
|
})).toMatchSnapshot();
|
|
311
293
|
});
|
|
312
294
|
});
|