@intentius/chant-lexicon-github 0.106.1 → 0.106.3
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/integrity.json +2 -2
- package/dist/manifest.json +1 -1
- package/package.json +2 -2
- package/src/components/generate-pipeline.member.test.ts +1 -1
- package/src/components/generate-pipeline.ts +2 -2
- package/src/components/generate-pr-pipeline.test.ts +16 -5
- package/src/composites/composites.test.ts +128 -125
package/dist/integrity.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"algorithm": "sha256",
|
|
3
3
|
"artifacts": {
|
|
4
|
-
"manifest.json": "
|
|
4
|
+
"manifest.json": "bc9354c7588b77975a1fc52ebe5fb14915137fa87a6982c32c464f12c30c2b80",
|
|
5
5
|
"meta.json": "c50e0ef3845c370c3ad2210b8bba455980417c0f818aa6ed46e06d4fb8b1b9f9",
|
|
6
6
|
"types/index.d.ts": "bdd54a4145a39a9dd4f7bbc8c500191acb6bc9057a87da0040475afac84db397",
|
|
7
7
|
"rules/deprecated-action-version.ts": "d41e6e532ab7f623af1bee4ac5279fcb2baada7defa1c5d022a5bc71983e8797",
|
|
@@ -78,5 +78,5 @@
|
|
|
78
78
|
"skills/chant-github-patterns.md": "bb3abef289a8fdfcf07d6bb2d7289dcb2f38bc0cb0321ea320b78b45a6f548c0",
|
|
79
79
|
"skills/chant-github-security.md": "aab111cb0871cad30281ce48d7da23663689619351029219e2be019a1a61e394"
|
|
80
80
|
},
|
|
81
|
-
"composite": "
|
|
81
|
+
"composite": "3b71f70f79d394ab86c7cd10ffc18228599568f05f9588335786c50ce73dc3a2"
|
|
82
82
|
}
|
package/dist/manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intentius/chant-lexicon-github",
|
|
3
|
-
"version": "0.106.
|
|
3
|
+
"version": "0.106.3",
|
|
4
4
|
"description": "GitHub Actions lexicon for chant — declarative IaC in TypeScript",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://intentius.io/chant",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"typescript": "^5.9.3"
|
|
62
62
|
},
|
|
63
63
|
"peerDependencies": {
|
|
64
|
-
"@intentius/chant": "^0.106.
|
|
64
|
+
"@intentius/chant": "^0.106.3",
|
|
65
65
|
"typescript": "^5.9.3"
|
|
66
66
|
}
|
|
67
67
|
}
|
|
@@ -49,7 +49,7 @@ describe("generateGithubPipeline for a workspace member", () => {
|
|
|
49
49
|
test("run steps start in the member's directory, and the name carries the member", () => {
|
|
50
50
|
const doc = parseYAML(generateGithubPipeline(components, { env: "staging", member: api }).yaml) as unknown as Doc;
|
|
51
51
|
expect(doc.defaults).toEqual({ run: { "working-directory": "services/api" } });
|
|
52
|
-
expect(doc.name).toBe("chant-components
|
|
52
|
+
expect(doc.name).toBe("chant-components.api.staging");
|
|
53
53
|
});
|
|
54
54
|
|
|
55
55
|
test("artifact paths move under the member's directory, where the run steps write them", () => {
|
|
@@ -257,7 +257,7 @@ function prLoopGithubDoc(
|
|
|
257
257
|
},
|
|
258
258
|
};
|
|
259
259
|
return {
|
|
260
|
-
name: member ? `chant-pr
|
|
260
|
+
name: member ? `chant-pr.${member.name}.${env}` : `chant-pr-${env}`,
|
|
261
261
|
environment: env,
|
|
262
262
|
on: { pull_request: { branches: [branch] }, push: { branches: [branch] } },
|
|
263
263
|
...(rooted ? {} : { defaults: { run: { "working-directory": member!.dir } } }),
|
|
@@ -534,7 +534,7 @@ function scopeToMember(doc: GithubPipelineDoc, member: PipelineMember): GithubPi
|
|
|
534
534
|
}
|
|
535
535
|
return {
|
|
536
536
|
...doc,
|
|
537
|
-
name: `chant-components
|
|
537
|
+
name: `chant-components.${member.name}.${doc.environment}`,
|
|
538
538
|
...(rooted ? {} : { defaults: { run: { "working-directory": member.dir } } }),
|
|
539
539
|
jobsDoc,
|
|
540
540
|
};
|
|
@@ -11,6 +11,7 @@ import { join } from "node:path";
|
|
|
11
11
|
import { describe, expect, test } from "vitest";
|
|
12
12
|
import { parseYAML } from "@intentius/chant/yaml";
|
|
13
13
|
import type { DriverComponent } from "@intentius/chant/components/driver";
|
|
14
|
+
import { prApplyGroup, prApplyRecordKey } from "@intentius/chant/components/pr-pipeline";
|
|
14
15
|
import { generateGithubPipeline } from "./generate-pipeline";
|
|
15
16
|
|
|
16
17
|
const ESTATE: DriverComponent[] = [
|
|
@@ -117,7 +118,7 @@ describe("the GitHub pull-request workflow", () => {
|
|
|
117
118
|
});
|
|
118
119
|
|
|
119
120
|
test("inside a workspace member: runs in the member's directory and keeps its own gate, note and apply group (#3465)", () => {
|
|
120
|
-
const member = { name: "network", dir: "infra/network", file: ".github/workflows/chant-pr
|
|
121
|
+
const member = { name: "network", dir: "infra/network", file: ".github/workflows/chant-pr.network.prod.yml" };
|
|
121
122
|
const scoped = generateGithubPipeline(ESTATE, { env: "prod", prLoop: {}, member });
|
|
122
123
|
const mdoc = parseYAML(scoped.yaml) as {
|
|
123
124
|
name: string;
|
|
@@ -125,7 +126,7 @@ describe("the GitHub pull-request workflow", () => {
|
|
|
125
126
|
defaults?: { run: Record<string, string> };
|
|
126
127
|
jobs: Record<string, Job>;
|
|
127
128
|
};
|
|
128
|
-
expect(mdoc.name).toBe("chant-pr
|
|
129
|
+
expect(mdoc.name).toBe("chant-pr.network.prod");
|
|
129
130
|
// No path filter: a change outside the member can reach it.
|
|
130
131
|
expect(mdoc.on).toEqual({ pull_request: { branches: ["main"] }, push: { branches: ["main"] } });
|
|
131
132
|
expect(mdoc.defaults).toEqual({ run: { "working-directory": "infra/network" } });
|
|
@@ -133,21 +134,31 @@ describe("the GitHub pull-request workflow", () => {
|
|
|
133
134
|
expect(plan.run).toBe('chant components pr-plan --base "$BASE_SHA" --pr "$PR_NUMBER" --env prod --gate pr-apply --output .chant/pr --forge github --member network');
|
|
134
135
|
const apply = mdoc.jobs.apply.steps.find((s) => s.run?.includes("pr-apply"))!;
|
|
135
136
|
expect(apply.run).toContain("--member network");
|
|
136
|
-
expect(mdoc.jobs.apply.concurrency).toEqual({ group: "chant-apply
|
|
137
|
+
expect(mdoc.jobs.apply.concurrency).toEqual({ group: "chant-apply.network.prod", "cancel-in-progress": false });
|
|
137
138
|
// The record path is relative to the member for --resume and to the repository for the cache.
|
|
138
139
|
expect(apply.run).toContain("--resume .chant/pr-resume/pr-apply.json");
|
|
139
140
|
const restore = mdoc.jobs.apply.steps.find((s) => s.uses === "actions/cache/restore@v4")!;
|
|
140
|
-
expect(restore.with).toMatchObject({ path: "infra/network/.chant/pr-resume/pr-apply.json", "restore-keys": "chant-apply
|
|
141
|
+
expect(restore.with).toMatchObject({ path: "infra/network/.chant/pr-resume/pr-apply.json", "restore-keys": "chant-apply.network.prod-${{ github.sha }}-" });
|
|
141
142
|
for (const job of Object.values(mdoc.jobs)) {
|
|
142
143
|
const keep = job.steps.find((s) => s.uses?.startsWith("actions/upload-artifact"))!;
|
|
143
144
|
expect(keep.with?.path).toBe("infra/network/.chant/pr");
|
|
144
145
|
}
|
|
145
146
|
});
|
|
146
147
|
|
|
148
|
+
test("no two member and environment pairs share an apply group or record key", () => {
|
|
149
|
+
expect(prApplyGroup("b-c", "a")).toBe("chant-apply.a.b-c");
|
|
150
|
+
expect(prApplyGroup("c", "a-b")).toBe("chant-apply.a-b.c");
|
|
151
|
+
expect(prApplyGroup("b-c", "a")).not.toBe(prApplyGroup("c", "a-b"));
|
|
152
|
+
expect(prApplyRecordKey("b-c", "abc", "a")).not.toBe(prApplyRecordKey("c", "abc", "a-b"));
|
|
153
|
+
// A single project's group never looks like a member's.
|
|
154
|
+
expect(prApplyGroup("a-b-c")).toBe("chant-apply-a-b-c");
|
|
155
|
+
expect(prApplyGroup("a.b-c")).not.toBe(prApplyGroup("b-c", "a"));
|
|
156
|
+
});
|
|
157
|
+
|
|
147
158
|
test("a member at the workspace root keeps the root's paths but its own names", () => {
|
|
148
159
|
const yaml = generateGithubPipeline(ESTATE, { env: "prod", prLoop: {}, member: { name: "root", dir: "." } }).yaml;
|
|
149
160
|
const mdoc = parseYAML(yaml) as { name: string; defaults?: unknown; jobs: Record<string, Job> };
|
|
150
|
-
expect(mdoc.name).toBe("chant-pr
|
|
161
|
+
expect(mdoc.name).toBe("chant-pr.root.prod");
|
|
151
162
|
expect(mdoc.defaults).toBeUndefined();
|
|
152
163
|
expect(yaml).toContain("--member root");
|
|
153
164
|
expect(yaml).toContain("path: .chant/pr");
|
|
@@ -13,30 +13,33 @@ import { DockerBuild } from "./docker-build";
|
|
|
13
13
|
import { DeployEnvironment } from "./deploy-environment";
|
|
14
14
|
import { GoCI } from "./go-ci";
|
|
15
15
|
|
|
16
|
+
// The generated resource classes type `props` as unknown; the tests read it as a record.
|
|
17
|
+
const propsOf = (entity: unknown): Record<string, any> => (entity as { props: Record<string, any> }).props;
|
|
18
|
+
|
|
16
19
|
// ── Checkout ────────────────────────────────────────────────────────
|
|
17
20
|
|
|
18
21
|
describe("Checkout", () => {
|
|
19
22
|
test("returns step with checkout action", () => {
|
|
20
23
|
const result = Checkout({});
|
|
21
24
|
expect(result.step).toBeDefined();
|
|
22
|
-
expect(result.step.
|
|
23
|
-
expect(result.step.
|
|
25
|
+
expect(propsOf(result.step).uses).toBe("actions/checkout@v7");
|
|
26
|
+
expect(propsOf(result.step).name).toBe("Checkout");
|
|
24
27
|
});
|
|
25
28
|
|
|
26
29
|
test("passes ref as with input", () => {
|
|
27
30
|
const result = Checkout({ ref: "develop" });
|
|
28
|
-
expect(result.step.
|
|
29
|
-
expect((result.step.
|
|
31
|
+
expect(propsOf(result.step).with).toBeDefined();
|
|
32
|
+
expect((propsOf(result.step).with as Record<string, string>).ref).toBe("develop");
|
|
30
33
|
});
|
|
31
34
|
|
|
32
35
|
test("passes fetchDepth as with input", () => {
|
|
33
36
|
const result = Checkout({ fetchDepth: 0 });
|
|
34
|
-
expect((result.step.
|
|
37
|
+
expect((propsOf(result.step).with as Record<string, string>)["fetch-depth"]).toBe("0");
|
|
35
38
|
});
|
|
36
39
|
|
|
37
40
|
test("omits with when no optional props", () => {
|
|
38
41
|
const result = Checkout({});
|
|
39
|
-
expect(result.step.
|
|
42
|
+
expect(propsOf(result.step).with).toBeUndefined();
|
|
40
43
|
});
|
|
41
44
|
});
|
|
42
45
|
|
|
@@ -45,18 +48,18 @@ describe("Checkout", () => {
|
|
|
45
48
|
describe("SetupNode", () => {
|
|
46
49
|
test("returns step with setup-node action", () => {
|
|
47
50
|
const result = SetupNode({});
|
|
48
|
-
expect(result.step.
|
|
49
|
-
expect(result.step.
|
|
51
|
+
expect(propsOf(result.step).uses).toBe("actions/setup-node@v7");
|
|
52
|
+
expect(propsOf(result.step).name).toBe("Setup Node.js");
|
|
50
53
|
});
|
|
51
54
|
|
|
52
55
|
test("passes nodeVersion", () => {
|
|
53
56
|
const result = SetupNode({ nodeVersion: "22" });
|
|
54
|
-
expect((result.step.
|
|
57
|
+
expect((propsOf(result.step).with as Record<string, string>)["node-version"]).toBe("22");
|
|
55
58
|
});
|
|
56
59
|
|
|
57
60
|
test("passes cache option", () => {
|
|
58
61
|
const result = SetupNode({ cache: "npm" });
|
|
59
|
-
expect((result.step.
|
|
62
|
+
expect((propsOf(result.step).with as Record<string, string>).cache).toBe("npm");
|
|
60
63
|
});
|
|
61
64
|
});
|
|
62
65
|
|
|
@@ -65,12 +68,12 @@ describe("SetupNode", () => {
|
|
|
65
68
|
describe("SetupGo", () => {
|
|
66
69
|
test("returns step with setup-go action", () => {
|
|
67
70
|
const result = SetupGo({});
|
|
68
|
-
expect(result.step.
|
|
71
|
+
expect(propsOf(result.step).uses).toBe("actions/setup-go@v5");
|
|
69
72
|
});
|
|
70
73
|
|
|
71
74
|
test("passes goVersion", () => {
|
|
72
75
|
const result = SetupGo({ goVersion: "1.22" });
|
|
73
|
-
expect((result.step.
|
|
76
|
+
expect((propsOf(result.step).with as Record<string, string>)["go-version"]).toBe("1.22");
|
|
74
77
|
});
|
|
75
78
|
});
|
|
76
79
|
|
|
@@ -79,12 +82,12 @@ describe("SetupGo", () => {
|
|
|
79
82
|
describe("SetupPython", () => {
|
|
80
83
|
test("returns step with setup-python action", () => {
|
|
81
84
|
const result = SetupPython({});
|
|
82
|
-
expect(result.step.
|
|
85
|
+
expect(propsOf(result.step).uses).toBe("actions/setup-python@v5");
|
|
83
86
|
});
|
|
84
87
|
|
|
85
88
|
test("passes pythonVersion", () => {
|
|
86
89
|
const result = SetupPython({ pythonVersion: "3.12" });
|
|
87
|
-
expect((result.step.
|
|
90
|
+
expect((propsOf(result.step).with as Record<string, string>)["python-version"]).toBe("3.12");
|
|
88
91
|
});
|
|
89
92
|
});
|
|
90
93
|
|
|
@@ -93,9 +96,9 @@ describe("SetupPython", () => {
|
|
|
93
96
|
describe("CacheAction", () => {
|
|
94
97
|
test("returns step with cache action", () => {
|
|
95
98
|
const result = CacheAction({ path: "node_modules", key: "npm-${{ hashFiles('package-lock.json') }}" });
|
|
96
|
-
expect(result.step.
|
|
97
|
-
expect((result.step.
|
|
98
|
-
expect((result.step.
|
|
99
|
+
expect(propsOf(result.step).uses).toBe("actions/cache@v4");
|
|
100
|
+
expect((propsOf(result.step).with as Record<string, string>).path).toBe("node_modules");
|
|
101
|
+
expect((propsOf(result.step).with as Record<string, string>).key).toBe("npm-${{ hashFiles('package-lock.json') }}");
|
|
99
102
|
});
|
|
100
103
|
});
|
|
101
104
|
|
|
@@ -104,14 +107,14 @@ describe("CacheAction", () => {
|
|
|
104
107
|
describe("UploadArtifact", () => {
|
|
105
108
|
test("returns step with upload action", () => {
|
|
106
109
|
const result = UploadArtifact({ name: "build", path: "dist/" });
|
|
107
|
-
expect(result.step.
|
|
108
|
-
expect((result.step.
|
|
109
|
-
expect((result.step.
|
|
110
|
+
expect(propsOf(result.step).uses).toBe("actions/upload-artifact@v4");
|
|
111
|
+
expect((propsOf(result.step).with as Record<string, string>).name).toBe("build");
|
|
112
|
+
expect((propsOf(result.step).with as Record<string, string>).path).toBe("dist/");
|
|
110
113
|
});
|
|
111
114
|
|
|
112
115
|
test("passes retentionDays", () => {
|
|
113
116
|
const result = UploadArtifact({ name: "build", path: "dist/", retentionDays: 7 });
|
|
114
|
-
expect((result.step.
|
|
117
|
+
expect((propsOf(result.step).with as Record<string, string>)["retention-days"]).toBe("7");
|
|
115
118
|
});
|
|
116
119
|
});
|
|
117
120
|
|
|
@@ -120,8 +123,8 @@ describe("UploadArtifact", () => {
|
|
|
120
123
|
describe("DownloadArtifact", () => {
|
|
121
124
|
test("returns step with download action", () => {
|
|
122
125
|
const result = DownloadArtifact({ name: "build" });
|
|
123
|
-
expect(result.step.
|
|
124
|
-
expect((result.step.
|
|
126
|
+
expect(propsOf(result.step).uses).toBe("actions/download-artifact@v4");
|
|
127
|
+
expect((propsOf(result.step).with as Record<string, string>).name).toBe("build");
|
|
125
128
|
});
|
|
126
129
|
});
|
|
127
130
|
|
|
@@ -136,39 +139,39 @@ describe("NodeCI", () => {
|
|
|
136
139
|
|
|
137
140
|
test("workflow has CI name", () => {
|
|
138
141
|
const result = NodeCI({});
|
|
139
|
-
expect(result.workflow.
|
|
142
|
+
expect(propsOf(result.workflow).name).toBe("CI");
|
|
140
143
|
});
|
|
141
144
|
|
|
142
145
|
test("job has ubuntu-latest runner", () => {
|
|
143
146
|
const result = NodeCI({});
|
|
144
|
-
expect(result.job
|
|
147
|
+
expect(propsOf(result.job)["runs-on"]).toBe("ubuntu-latest");
|
|
145
148
|
});
|
|
146
149
|
|
|
147
150
|
test("job has steps array", () => {
|
|
148
151
|
const result = NodeCI({});
|
|
149
|
-
expect(Array.isArray(result.job.
|
|
150
|
-
expect((result.job.
|
|
152
|
+
expect(Array.isArray(propsOf(result.job).steps)).toBe(true);
|
|
153
|
+
expect((propsOf(result.job).steps as unknown[]).length).toBe(5);
|
|
151
154
|
});
|
|
152
155
|
|
|
153
156
|
test("uses default npm commands", () => {
|
|
154
157
|
const result = NodeCI({});
|
|
155
|
-
const steps = result.job.
|
|
158
|
+
const steps = propsOf(result.job).steps as Array<{ props: Record<string, unknown> }>;
|
|
156
159
|
const installStep = steps[2];
|
|
157
|
-
expect(installStep.
|
|
160
|
+
expect(propsOf(installStep).run).toBe("npm ci");
|
|
158
161
|
});
|
|
159
162
|
|
|
160
163
|
test("uses pnpm when specified", () => {
|
|
161
164
|
const result = NodeCI({ packageManager: "pnpm" });
|
|
162
|
-
const steps = result.job.
|
|
165
|
+
const steps = propsOf(result.job).steps as Array<{ props: Record<string, unknown> }>;
|
|
163
166
|
const installStep = steps[2];
|
|
164
|
-
expect(installStep.
|
|
167
|
+
expect(propsOf(installStep).run).toBe("pnpm install");
|
|
165
168
|
});
|
|
166
169
|
|
|
167
170
|
test("uses custom installCommand", () => {
|
|
168
171
|
const result = NodeCI({ installCommand: "yarn install --frozen-lockfile" });
|
|
169
|
-
const steps = result.job.
|
|
172
|
+
const steps = propsOf(result.job).steps as Array<{ props: Record<string, unknown> }>;
|
|
170
173
|
const installStep = steps[2];
|
|
171
|
-
expect(installStep.
|
|
174
|
+
expect(propsOf(installStep).run).toBe("yarn install --frozen-lockfile");
|
|
172
175
|
});
|
|
173
176
|
});
|
|
174
177
|
|
|
@@ -184,98 +187,98 @@ describe("NodePipeline", () => {
|
|
|
184
187
|
|
|
185
188
|
test("workflow has Node Pipeline name", () => {
|
|
186
189
|
const result = NodePipeline({});
|
|
187
|
-
expect(result.workflow.
|
|
190
|
+
expect(propsOf(result.workflow).name).toBe("Node Pipeline");
|
|
188
191
|
});
|
|
189
192
|
|
|
190
193
|
test("buildJob has 5 steps (checkout, setup, install, build, upload)", () => {
|
|
191
194
|
const result = NodePipeline({});
|
|
192
|
-
const steps = result.buildJob.
|
|
195
|
+
const steps = propsOf(result.buildJob).steps as unknown[];
|
|
193
196
|
expect(steps.length).toBe(5);
|
|
194
197
|
});
|
|
195
198
|
|
|
196
199
|
test("testJob has 5 steps (checkout, setup, install, download, test)", () => {
|
|
197
200
|
const result = NodePipeline({});
|
|
198
|
-
const steps = result.testJob.
|
|
201
|
+
const steps = propsOf(result.testJob).steps as unknown[];
|
|
199
202
|
expect(steps.length).toBe(5);
|
|
200
203
|
});
|
|
201
204
|
|
|
202
205
|
test("testJob depends on build", () => {
|
|
203
206
|
const result = NodePipeline({});
|
|
204
|
-
expect(result.testJob.
|
|
207
|
+
expect(propsOf(result.testJob).needs).toEqual(["build"]);
|
|
205
208
|
});
|
|
206
209
|
|
|
207
210
|
test("uses npm ci by default", () => {
|
|
208
211
|
const result = NodePipeline({});
|
|
209
|
-
const steps = result.buildJob.
|
|
212
|
+
const steps = propsOf(result.buildJob).steps as Array<{ props: Record<string, unknown> }>;
|
|
210
213
|
expect(steps[2].props.run).toBe("npm ci");
|
|
211
214
|
});
|
|
212
215
|
|
|
213
216
|
test("uses pnpm install with frozen lockfile", () => {
|
|
214
217
|
const result = NodePipeline({ packageManager: "pnpm" });
|
|
215
|
-
const steps = result.buildJob.
|
|
218
|
+
const steps = propsOf(result.buildJob).steps as Array<{ props: Record<string, unknown> }>;
|
|
216
219
|
expect(steps[2].props.run).toBe("pnpm install --frozen-lockfile");
|
|
217
220
|
});
|
|
218
221
|
|
|
219
222
|
test("uses yarn install with frozen lockfile", () => {
|
|
220
223
|
const result = NodePipeline({ packageManager: "yarn" });
|
|
221
|
-
const steps = result.buildJob.
|
|
224
|
+
const steps = propsOf(result.buildJob).steps as Array<{ props: Record<string, unknown> }>;
|
|
222
225
|
expect(steps[2].props.run).toBe("yarn install --frozen-lockfile");
|
|
223
226
|
});
|
|
224
227
|
|
|
225
228
|
test("bun uses oven-sh/setup-bun instead of setup-node", () => {
|
|
226
229
|
const result = NodePipeline({ packageManager: "bun" });
|
|
227
|
-
const steps = result.buildJob.
|
|
230
|
+
const steps = propsOf(result.buildJob).steps as Array<{ props: Record<string, unknown> }>;
|
|
228
231
|
expect(steps[1].props.uses).toBe("oven-sh/setup-bun@v2");
|
|
229
232
|
expect(steps[1].props.name).toBe("Setup Bun");
|
|
230
233
|
});
|
|
231
234
|
|
|
232
235
|
test("bun uses bun install with frozen lockfile", () => {
|
|
233
236
|
const result = NodePipeline({ packageManager: "bun" });
|
|
234
|
-
const steps = result.buildJob.
|
|
237
|
+
const steps = propsOf(result.buildJob).steps as Array<{ props: Record<string, unknown> }>;
|
|
235
238
|
expect(steps[2].props.run).toBe("bun install --frozen-lockfile");
|
|
236
239
|
});
|
|
237
240
|
|
|
238
241
|
test("npm setup step has cache: npm", () => {
|
|
239
242
|
const result = NodePipeline({ packageManager: "npm" });
|
|
240
|
-
const steps = result.buildJob.
|
|
243
|
+
const steps = propsOf(result.buildJob).steps as Array<{ props: Record<string, unknown> }>;
|
|
241
244
|
expect((steps[1].props.with as Record<string, string>).cache).toBe("npm");
|
|
242
245
|
});
|
|
243
246
|
|
|
244
247
|
test("upload step uses upload-artifact with configured name", () => {
|
|
245
248
|
const result = NodePipeline({ artifactName: "my-build" });
|
|
246
|
-
const steps = result.buildJob.
|
|
249
|
+
const steps = propsOf(result.buildJob).steps as Array<{ props: Record<string, unknown> }>;
|
|
247
250
|
expect(steps[4].props.uses).toBe("actions/upload-artifact@v4");
|
|
248
251
|
expect((steps[4].props.with as Record<string, string>).name).toBe("my-build");
|
|
249
252
|
});
|
|
250
253
|
|
|
251
254
|
test("download step in testJob uses configured artifact name", () => {
|
|
252
255
|
const result = NodePipeline({ artifactName: "my-build" });
|
|
253
|
-
const steps = result.testJob.
|
|
256
|
+
const steps = propsOf(result.testJob).steps as Array<{ props: Record<string, unknown> }>;
|
|
254
257
|
expect(steps[3].props.uses).toBe("actions/download-artifact@v4");
|
|
255
258
|
expect((steps[3].props.with as Record<string, string>).name).toBe("my-build");
|
|
256
259
|
});
|
|
257
260
|
|
|
258
261
|
test("uses custom runsOn", () => {
|
|
259
262
|
const result = NodePipeline({ runsOn: "self-hosted" });
|
|
260
|
-
expect(result.buildJob
|
|
261
|
-
expect(result.testJob
|
|
263
|
+
expect(propsOf(result.buildJob)["runs-on"]).toBe("self-hosted");
|
|
264
|
+
expect(propsOf(result.testJob)["runs-on"]).toBe("self-hosted");
|
|
262
265
|
});
|
|
263
266
|
|
|
264
267
|
test("uses custom installCommand", () => {
|
|
265
268
|
const result = NodePipeline({ installCommand: "npm install --legacy-peer-deps" });
|
|
266
|
-
const steps = result.buildJob.
|
|
269
|
+
const steps = propsOf(result.buildJob).steps as Array<{ props: Record<string, unknown> }>;
|
|
267
270
|
expect(steps[2].props.run).toBe("npm install --legacy-peer-deps");
|
|
268
271
|
});
|
|
269
272
|
|
|
270
273
|
test("build step uses configured script", () => {
|
|
271
274
|
const result = NodePipeline({ buildScript: "compile" });
|
|
272
|
-
const steps = result.buildJob.
|
|
275
|
+
const steps = propsOf(result.buildJob).steps as Array<{ props: Record<string, unknown> }>;
|
|
273
276
|
expect(steps[3].props.run).toBe("npm run compile");
|
|
274
277
|
});
|
|
275
278
|
|
|
276
279
|
test("test step uses configured script", () => {
|
|
277
280
|
const result = NodePipeline({ testScript: "test:ci" });
|
|
278
|
-
const steps = result.testJob.
|
|
281
|
+
const steps = propsOf(result.testJob).steps as Array<{ props: Record<string, unknown> }>;
|
|
279
282
|
expect(steps[4].props.run).toBe("npm run test:ci");
|
|
280
283
|
});
|
|
281
284
|
});
|
|
@@ -285,7 +288,7 @@ describe("NodePipeline", () => {
|
|
|
285
288
|
describe("BunPipeline", () => {
|
|
286
289
|
test("defaults to bun package manager", () => {
|
|
287
290
|
const result = BunPipeline({});
|
|
288
|
-
const steps = result.buildJob.
|
|
291
|
+
const steps = propsOf(result.buildJob).steps as Array<{ props: Record<string, unknown> }>;
|
|
289
292
|
expect(steps[1].props.uses).toBe("oven-sh/setup-bun@v2");
|
|
290
293
|
expect(steps[2].props.run).toBe("bun install --frozen-lockfile");
|
|
291
294
|
});
|
|
@@ -294,7 +297,7 @@ describe("BunPipeline", () => {
|
|
|
294
297
|
describe("PnpmPipeline", () => {
|
|
295
298
|
test("defaults to pnpm package manager", () => {
|
|
296
299
|
const result = PnpmPipeline({});
|
|
297
|
-
const steps = result.buildJob.
|
|
300
|
+
const steps = propsOf(result.buildJob).steps as Array<{ props: Record<string, unknown> }>;
|
|
298
301
|
expect((steps[1].props.with as Record<string, string>).cache).toBe("pnpm");
|
|
299
302
|
expect(steps[2].props.run).toBe("pnpm install --frozen-lockfile");
|
|
300
303
|
});
|
|
@@ -303,7 +306,7 @@ describe("PnpmPipeline", () => {
|
|
|
303
306
|
describe("YarnPipeline", () => {
|
|
304
307
|
test("defaults to yarn package manager", () => {
|
|
305
308
|
const result = YarnPipeline({});
|
|
306
|
-
const steps = result.buildJob.
|
|
309
|
+
const steps = propsOf(result.buildJob).steps as Array<{ props: Record<string, unknown> }>;
|
|
307
310
|
expect((steps[1].props.with as Record<string, string>).cache).toBe("yarn");
|
|
308
311
|
expect(steps[2].props.run).toBe("yarn install --frozen-lockfile");
|
|
309
312
|
});
|
|
@@ -320,7 +323,7 @@ describe("PythonCI", () => {
|
|
|
320
323
|
|
|
321
324
|
test("workflow has Python CI name", () => {
|
|
322
325
|
const result = PythonCI({});
|
|
323
|
-
expect(result.workflow.
|
|
326
|
+
expect(propsOf(result.workflow).name).toBe("Python CI");
|
|
324
327
|
});
|
|
325
328
|
|
|
326
329
|
test("includes lintJob by default", () => {
|
|
@@ -335,66 +338,66 @@ describe("PythonCI", () => {
|
|
|
335
338
|
|
|
336
339
|
test("testJob uses setup-python with default version", () => {
|
|
337
340
|
const result = PythonCI({});
|
|
338
|
-
const steps = result.testJob.
|
|
341
|
+
const steps = propsOf(result.testJob).steps as Array<{ props: Record<string, unknown> }>;
|
|
339
342
|
const setupStep = steps[1];
|
|
340
|
-
expect(setupStep.
|
|
341
|
-
expect((setupStep.
|
|
343
|
+
expect(propsOf(setupStep).uses).toBe("actions/setup-python@v5");
|
|
344
|
+
expect((propsOf(setupStep).with as Record<string, string>)["python-version"]).toBe("3.12");
|
|
342
345
|
});
|
|
343
346
|
|
|
344
347
|
test("testJob uses pip cache by default", () => {
|
|
345
348
|
const result = PythonCI({});
|
|
346
|
-
const steps = result.testJob.
|
|
349
|
+
const steps = propsOf(result.testJob).steps as Array<{ props: Record<string, unknown> }>;
|
|
347
350
|
expect((steps[1].props.with as Record<string, string>).cache).toBe("pip");
|
|
348
351
|
});
|
|
349
352
|
|
|
350
353
|
test("poetry mode uses poetry cache", () => {
|
|
351
354
|
const result = PythonCI({ usePoetry: true });
|
|
352
|
-
const steps = result.testJob.
|
|
355
|
+
const steps = propsOf(result.testJob).steps as Array<{ props: Record<string, unknown> }>;
|
|
353
356
|
expect((steps[1].props.with as Record<string, string>).cache).toBe("poetry");
|
|
354
357
|
});
|
|
355
358
|
|
|
356
359
|
test("poetry mode installs poetry first", () => {
|
|
357
360
|
const result = PythonCI({ usePoetry: true });
|
|
358
|
-
const steps = result.testJob.
|
|
361
|
+
const steps = propsOf(result.testJob).steps as Array<{ props: Record<string, unknown> }>;
|
|
359
362
|
expect(steps[2].props.run).toBe("pip install poetry");
|
|
360
363
|
expect(steps[3].props.run).toBe("poetry install");
|
|
361
364
|
});
|
|
362
365
|
|
|
363
366
|
test("pip mode installs from requirements.txt", () => {
|
|
364
367
|
const result = PythonCI({});
|
|
365
|
-
const steps = result.testJob.
|
|
368
|
+
const steps = propsOf(result.testJob).steps as Array<{ props: Record<string, unknown> }>;
|
|
366
369
|
expect(steps[2].props.run).toBe("pip install -r requirements.txt");
|
|
367
370
|
});
|
|
368
371
|
|
|
369
372
|
test("uses custom requirements file", () => {
|
|
370
373
|
const result = PythonCI({ requirementsFile: "requirements-dev.txt" });
|
|
371
|
-
const steps = result.testJob.
|
|
374
|
+
const steps = propsOf(result.testJob).steps as Array<{ props: Record<string, unknown> }>;
|
|
372
375
|
expect(steps[2].props.run).toBe("pip install -r requirements-dev.txt");
|
|
373
376
|
});
|
|
374
377
|
|
|
375
378
|
test("test step uses configured command", () => {
|
|
376
379
|
const result = PythonCI({ testCommand: "python -m pytest" });
|
|
377
|
-
const steps = result.testJob.
|
|
380
|
+
const steps = propsOf(result.testJob).steps as Array<{ props: Record<string, unknown> }>;
|
|
378
381
|
const lastStep = steps[steps.length - 1];
|
|
379
|
-
expect(lastStep.
|
|
382
|
+
expect(propsOf(lastStep).run).toBe("python -m pytest");
|
|
380
383
|
});
|
|
381
384
|
|
|
382
385
|
test("lint step uses configured command", () => {
|
|
383
386
|
const result = PythonCI({ lintCommand: "flake8 ." });
|
|
384
|
-
const steps = result.lintJob
|
|
387
|
+
const steps = propsOf(result.lintJob!).steps as Array<{ props: Record<string, unknown> }>;
|
|
385
388
|
const lastStep = steps[steps.length - 1];
|
|
386
|
-
expect(lastStep.
|
|
389
|
+
expect(propsOf(lastStep).run).toBe("flake8 .");
|
|
387
390
|
});
|
|
388
391
|
|
|
389
392
|
test("uses custom python version", () => {
|
|
390
393
|
const result = PythonCI({ pythonVersion: "3.11" });
|
|
391
|
-
const steps = result.testJob.
|
|
394
|
+
const steps = propsOf(result.testJob).steps as Array<{ props: Record<string, unknown> }>;
|
|
392
395
|
expect((steps[1].props.with as Record<string, string>)["python-version"]).toBe("3.11");
|
|
393
396
|
});
|
|
394
397
|
|
|
395
398
|
test("uses custom runsOn", () => {
|
|
396
399
|
const result = PythonCI({ runsOn: "macos-latest" });
|
|
397
|
-
expect(result.testJob
|
|
400
|
+
expect(propsOf(result.testJob)["runs-on"]).toBe("macos-latest");
|
|
398
401
|
});
|
|
399
402
|
});
|
|
400
403
|
|
|
@@ -409,90 +412,90 @@ describe("DockerBuild", () => {
|
|
|
409
412
|
|
|
410
413
|
test("workflow has Docker Build name", () => {
|
|
411
414
|
const result = DockerBuild({});
|
|
412
|
-
expect(result.workflow.
|
|
415
|
+
expect(propsOf(result.workflow).name).toBe("Docker Build");
|
|
413
416
|
});
|
|
414
417
|
|
|
415
418
|
test("workflow has packages write permission", () => {
|
|
416
419
|
const result = DockerBuild({});
|
|
417
|
-
expect((result.workflow.
|
|
418
|
-
expect((result.workflow.
|
|
420
|
+
expect((propsOf(result.workflow).permissions as Record<string, string>).packages).toBe("write");
|
|
421
|
+
expect((propsOf(result.workflow).permissions as Record<string, string>).contents).toBe("read");
|
|
419
422
|
});
|
|
420
423
|
|
|
421
424
|
test("job has 5 steps", () => {
|
|
422
425
|
const result = DockerBuild({});
|
|
423
|
-
const steps = result.job.
|
|
426
|
+
const steps = propsOf(result.job).steps as unknown[];
|
|
424
427
|
expect(steps.length).toBe(5);
|
|
425
428
|
});
|
|
426
429
|
|
|
427
430
|
test("uses docker/login-action", () => {
|
|
428
431
|
const result = DockerBuild({});
|
|
429
|
-
const steps = result.job.
|
|
432
|
+
const steps = propsOf(result.job).steps as Array<{ props: Record<string, unknown> }>;
|
|
430
433
|
expect(steps[1].props.uses).toBe("docker/login-action@v3");
|
|
431
434
|
});
|
|
432
435
|
|
|
433
436
|
test("uses docker/setup-buildx-action", () => {
|
|
434
437
|
const result = DockerBuild({});
|
|
435
|
-
const steps = result.job.
|
|
438
|
+
const steps = propsOf(result.job).steps as Array<{ props: Record<string, unknown> }>;
|
|
436
439
|
expect(steps[2].props.uses).toBe("docker/setup-buildx-action@v3");
|
|
437
440
|
});
|
|
438
441
|
|
|
439
442
|
test("uses docker/metadata-action", () => {
|
|
440
443
|
const result = DockerBuild({});
|
|
441
|
-
const steps = result.job.
|
|
444
|
+
const steps = propsOf(result.job).steps as Array<{ props: Record<string, unknown> }>;
|
|
442
445
|
expect(steps[3].props.uses).toBe("docker/metadata-action@v5");
|
|
443
446
|
});
|
|
444
447
|
|
|
445
448
|
test("uses docker/build-push-action", () => {
|
|
446
449
|
const result = DockerBuild({});
|
|
447
|
-
const steps = result.job.
|
|
450
|
+
const steps = propsOf(result.job).steps as Array<{ props: Record<string, unknown> }>;
|
|
448
451
|
expect(steps[4].props.uses).toBe("docker/build-push-action@v6");
|
|
449
452
|
});
|
|
450
453
|
|
|
451
454
|
test("login step uses ghcr.io by default", () => {
|
|
452
455
|
const result = DockerBuild({});
|
|
453
|
-
const steps = result.job.
|
|
456
|
+
const steps = propsOf(result.job).steps as Array<{ props: Record<string, unknown> }>;
|
|
454
457
|
expect((steps[1].props.with as Record<string, string>).registry).toBe("ghcr.io");
|
|
455
458
|
});
|
|
456
459
|
|
|
457
460
|
test("uses custom registry", () => {
|
|
458
461
|
const result = DockerBuild({ registry: "docker.io" });
|
|
459
|
-
const steps = result.job.
|
|
462
|
+
const steps = propsOf(result.job).steps as Array<{ props: Record<string, unknown> }>;
|
|
460
463
|
expect((steps[1].props.with as Record<string, string>).registry).toBe("docker.io");
|
|
461
464
|
});
|
|
462
465
|
|
|
463
466
|
test("build-push step has push: true by default", () => {
|
|
464
467
|
const result = DockerBuild({});
|
|
465
|
-
const steps = result.job.
|
|
468
|
+
const steps = propsOf(result.job).steps as Array<{ props: Record<string, unknown> }>;
|
|
466
469
|
expect((steps[4].props.with as Record<string, string>).push).toBe("true");
|
|
467
470
|
});
|
|
468
471
|
|
|
469
472
|
test("build-push step uses configured dockerfile", () => {
|
|
470
473
|
const result = DockerBuild({ dockerfile: "Dockerfile.prod" });
|
|
471
|
-
const steps = result.job.
|
|
474
|
+
const steps = propsOf(result.job).steps as Array<{ props: Record<string, unknown> }>;
|
|
472
475
|
expect((steps[4].props.with as Record<string, string>).file).toBe("Dockerfile.prod");
|
|
473
476
|
});
|
|
474
477
|
|
|
475
478
|
test("build-push step includes platforms when specified", () => {
|
|
476
479
|
const result = DockerBuild({ platforms: ["linux/amd64", "linux/arm64"] });
|
|
477
|
-
const steps = result.job.
|
|
480
|
+
const steps = propsOf(result.job).steps as Array<{ props: Record<string, unknown> }>;
|
|
478
481
|
expect((steps[4].props.with as Record<string, string>).platforms).toBe("linux/amd64,linux/arm64");
|
|
479
482
|
});
|
|
480
483
|
|
|
481
484
|
test("build-push step omits platforms when not specified", () => {
|
|
482
485
|
const result = DockerBuild({});
|
|
483
|
-
const steps = result.job.
|
|
486
|
+
const steps = propsOf(result.job).steps as Array<{ props: Record<string, unknown> }>;
|
|
484
487
|
expect((steps[4].props.with as Record<string, string>).platforms).toBeUndefined();
|
|
485
488
|
});
|
|
486
489
|
|
|
487
490
|
test("build-push step includes build-args when specified", () => {
|
|
488
491
|
const result = DockerBuild({ buildArgs: { NODE_ENV: "production", VERSION: "1.0" } });
|
|
489
|
-
const steps = result.job.
|
|
492
|
+
const steps = propsOf(result.job).steps as Array<{ props: Record<string, unknown> }>;
|
|
490
493
|
expect((steps[4].props.with as Record<string, string>)["build-args"]).toBe("NODE_ENV=production\nVERSION=1.0");
|
|
491
494
|
});
|
|
492
495
|
|
|
493
496
|
test("uses custom runsOn", () => {
|
|
494
497
|
const result = DockerBuild({ runsOn: "self-hosted" });
|
|
495
|
-
expect(result.job
|
|
498
|
+
expect(propsOf(result.job)["runs-on"]).toBe("self-hosted");
|
|
496
499
|
});
|
|
497
500
|
});
|
|
498
501
|
|
|
@@ -507,7 +510,7 @@ describe("DeployEnvironment", () => {
|
|
|
507
510
|
|
|
508
511
|
test("deployJob has environment with name", () => {
|
|
509
512
|
const result = DeployEnvironment({ name: "staging", deployScript: "npm run deploy" });
|
|
510
|
-
expect((result.deployJob.
|
|
513
|
+
expect((propsOf(result.deployJob).environment as Record<string, string>).name).toBe("staging");
|
|
511
514
|
});
|
|
512
515
|
|
|
513
516
|
test("deployJob has environment url when specified", () => {
|
|
@@ -516,12 +519,12 @@ describe("DeployEnvironment", () => {
|
|
|
516
519
|
deployScript: "npm run deploy",
|
|
517
520
|
url: "https://staging.example.com",
|
|
518
521
|
});
|
|
519
|
-
expect((result.deployJob.
|
|
522
|
+
expect((propsOf(result.deployJob).environment as Record<string, string>).url).toBe("https://staging.example.com");
|
|
520
523
|
});
|
|
521
524
|
|
|
522
525
|
test("deployJob has concurrency group", () => {
|
|
523
526
|
const result = DeployEnvironment({ name: "staging", deployScript: "npm run deploy" });
|
|
524
|
-
const concurrency = result.deployJob.
|
|
527
|
+
const concurrency = propsOf(result.deployJob).concurrency as Record<string, unknown>;
|
|
525
528
|
expect(concurrency.group).toBe("deploy-staging");
|
|
526
529
|
expect(concurrency["cancel-in-progress"]).toBe(true);
|
|
527
530
|
});
|
|
@@ -532,13 +535,13 @@ describe("DeployEnvironment", () => {
|
|
|
532
535
|
deployScript: "npm run deploy",
|
|
533
536
|
concurrencyGroup: "custom-group",
|
|
534
537
|
});
|
|
535
|
-
const concurrency = result.deployJob.
|
|
538
|
+
const concurrency = propsOf(result.deployJob).concurrency as Record<string, unknown>;
|
|
536
539
|
expect(concurrency.group).toBe("custom-group");
|
|
537
540
|
});
|
|
538
541
|
|
|
539
542
|
test("deployJob has checkout + deploy steps", () => {
|
|
540
543
|
const result = DeployEnvironment({ name: "staging", deployScript: "npm run deploy" });
|
|
541
|
-
const steps = result.deployJob.
|
|
544
|
+
const steps = propsOf(result.deployJob).steps as Array<{ props: Record<string, unknown> }>;
|
|
542
545
|
expect(steps.length).toBe(2);
|
|
543
546
|
expect(steps[0].props.uses).toBe("actions/checkout@v7");
|
|
544
547
|
expect(steps[1].props.run).toBe("npm run deploy");
|
|
@@ -549,7 +552,7 @@ describe("DeployEnvironment", () => {
|
|
|
549
552
|
name: "staging",
|
|
550
553
|
deployScript: ["npm run build", "npm run deploy"],
|
|
551
554
|
});
|
|
552
|
-
const steps = result.deployJob.
|
|
555
|
+
const steps = propsOf(result.deployJob).steps as Array<{ props: Record<string, unknown> }>;
|
|
553
556
|
expect(steps.length).toBe(3);
|
|
554
557
|
expect(steps[1].props.run).toBe("npm run build");
|
|
555
558
|
expect(steps[2].props.run).toBe("npm run deploy");
|
|
@@ -557,7 +560,7 @@ describe("DeployEnvironment", () => {
|
|
|
557
560
|
|
|
558
561
|
test("cleanupJob has default cleanup script", () => {
|
|
559
562
|
const result = DeployEnvironment({ name: "staging", deployScript: "npm run deploy" });
|
|
560
|
-
const steps = result.cleanupJob.
|
|
563
|
+
const steps = propsOf(result.cleanupJob).steps as Array<{ props: Record<string, unknown> }>;
|
|
561
564
|
expect(steps[1].props.run).toBe('echo "Cleaning up..."');
|
|
562
565
|
});
|
|
563
566
|
|
|
@@ -567,13 +570,13 @@ describe("DeployEnvironment", () => {
|
|
|
567
570
|
deployScript: "npm run deploy",
|
|
568
571
|
cleanupScript: "npm run teardown",
|
|
569
572
|
});
|
|
570
|
-
const steps = result.cleanupJob.
|
|
573
|
+
const steps = propsOf(result.cleanupJob).steps as Array<{ props: Record<string, unknown> }>;
|
|
571
574
|
expect(steps[1].props.run).toBe("npm run teardown");
|
|
572
575
|
});
|
|
573
576
|
|
|
574
577
|
test("cleanupJob has environment with name", () => {
|
|
575
578
|
const result = DeployEnvironment({ name: "production", deployScript: "npm run deploy" });
|
|
576
|
-
expect((result.cleanupJob.
|
|
579
|
+
expect((propsOf(result.cleanupJob).environment as Record<string, string>).name).toBe("production");
|
|
577
580
|
});
|
|
578
581
|
|
|
579
582
|
test("uses custom runsOn", () => {
|
|
@@ -582,8 +585,8 @@ describe("DeployEnvironment", () => {
|
|
|
582
585
|
deployScript: "npm run deploy",
|
|
583
586
|
runsOn: "self-hosted",
|
|
584
587
|
});
|
|
585
|
-
expect(result.deployJob
|
|
586
|
-
expect(result.cleanupJob
|
|
588
|
+
expect(propsOf(result.deployJob)["runs-on"]).toBe("self-hosted");
|
|
589
|
+
expect(propsOf(result.cleanupJob)["runs-on"]).toBe("self-hosted");
|
|
587
590
|
});
|
|
588
591
|
});
|
|
589
592
|
|
|
@@ -599,7 +602,7 @@ describe("GoCI", () => {
|
|
|
599
602
|
|
|
600
603
|
test("workflow has Go CI name", () => {
|
|
601
604
|
const result = GoCI({});
|
|
602
|
-
expect(result.workflow.
|
|
605
|
+
expect(propsOf(result.workflow).name).toBe("Go CI");
|
|
603
606
|
});
|
|
604
607
|
|
|
605
608
|
test("includes lintJob by default", () => {
|
|
@@ -614,63 +617,63 @@ describe("GoCI", () => {
|
|
|
614
617
|
|
|
615
618
|
test("buildJob has 3 steps (checkout, setup-go, build)", () => {
|
|
616
619
|
const result = GoCI({});
|
|
617
|
-
const steps = result.buildJob.
|
|
620
|
+
const steps = propsOf(result.buildJob).steps as unknown[];
|
|
618
621
|
expect(steps.length).toBe(3);
|
|
619
622
|
});
|
|
620
623
|
|
|
621
624
|
test("testJob has 3 steps (checkout, setup-go, test)", () => {
|
|
622
625
|
const result = GoCI({});
|
|
623
|
-
const steps = result.testJob.
|
|
626
|
+
const steps = propsOf(result.testJob).steps as unknown[];
|
|
624
627
|
expect(steps.length).toBe(3);
|
|
625
628
|
});
|
|
626
629
|
|
|
627
630
|
test("buildJob uses setup-go with default version", () => {
|
|
628
631
|
const result = GoCI({});
|
|
629
|
-
const steps = result.buildJob.
|
|
632
|
+
const steps = propsOf(result.buildJob).steps as Array<{ props: Record<string, unknown> }>;
|
|
630
633
|
expect(steps[1].props.uses).toBe("actions/setup-go@v5");
|
|
631
634
|
expect((steps[1].props.with as Record<string, string>)["go-version"]).toBe("1.22");
|
|
632
635
|
});
|
|
633
636
|
|
|
634
637
|
test("uses custom go version", () => {
|
|
635
638
|
const result = GoCI({ goVersion: "1.21" });
|
|
636
|
-
const steps = result.buildJob.
|
|
639
|
+
const steps = propsOf(result.buildJob).steps as Array<{ props: Record<string, unknown> }>;
|
|
637
640
|
expect((steps[1].props.with as Record<string, string>)["go-version"]).toBe("1.21");
|
|
638
641
|
});
|
|
639
642
|
|
|
640
643
|
test("buildJob runs go build by default", () => {
|
|
641
644
|
const result = GoCI({});
|
|
642
|
-
const steps = result.buildJob.
|
|
645
|
+
const steps = propsOf(result.buildJob).steps as Array<{ props: Record<string, unknown> }>;
|
|
643
646
|
expect(steps[2].props.run).toBe("go build ./...");
|
|
644
647
|
});
|
|
645
648
|
|
|
646
649
|
test("testJob runs go test with race detector by default", () => {
|
|
647
650
|
const result = GoCI({});
|
|
648
|
-
const steps = result.testJob.
|
|
651
|
+
const steps = propsOf(result.testJob).steps as Array<{ props: Record<string, unknown> }>;
|
|
649
652
|
expect(steps[2].props.run).toBe("go test ./... -v -race");
|
|
650
653
|
});
|
|
651
654
|
|
|
652
655
|
test("uses custom build command", () => {
|
|
653
656
|
const result = GoCI({ buildCommand: "go build -o bin/app ./cmd/app" });
|
|
654
|
-
const steps = result.buildJob.
|
|
657
|
+
const steps = propsOf(result.buildJob).steps as Array<{ props: Record<string, unknown> }>;
|
|
655
658
|
expect(steps[2].props.run).toBe("go build -o bin/app ./cmd/app");
|
|
656
659
|
});
|
|
657
660
|
|
|
658
661
|
test("uses custom test command", () => {
|
|
659
662
|
const result = GoCI({ testCommand: "go test -short ./..." });
|
|
660
|
-
const steps = result.testJob.
|
|
663
|
+
const steps = propsOf(result.testJob).steps as Array<{ props: Record<string, unknown> }>;
|
|
661
664
|
expect(steps[2].props.run).toBe("go test -short ./...");
|
|
662
665
|
});
|
|
663
666
|
|
|
664
667
|
test("lintJob uses golangci-lint-action", () => {
|
|
665
668
|
const result = GoCI({});
|
|
666
|
-
const steps = result.lintJob
|
|
669
|
+
const steps = propsOf(result.lintJob!).steps as Array<{ props: Record<string, unknown> }>;
|
|
667
670
|
expect(steps[2].props.uses).toBe("golangci/golangci-lint-action@v6");
|
|
668
671
|
});
|
|
669
672
|
|
|
670
673
|
test("uses custom runsOn", () => {
|
|
671
674
|
const result = GoCI({ runsOn: "macos-latest" });
|
|
672
|
-
expect(result.buildJob
|
|
673
|
-
expect(result.testJob
|
|
675
|
+
expect(propsOf(result.buildJob)["runs-on"]).toBe("macos-latest");
|
|
676
|
+
expect(propsOf(result.testJob)["runs-on"]).toBe("macos-latest");
|
|
674
677
|
});
|
|
675
678
|
});
|
|
676
679
|
|
|
@@ -682,8 +685,8 @@ describe("defaults overrides", () => {
|
|
|
682
685
|
ref: "main",
|
|
683
686
|
defaults: { step: { id: "my-checkout" } },
|
|
684
687
|
});
|
|
685
|
-
expect(result.step.
|
|
686
|
-
expect(result.step.
|
|
688
|
+
expect(propsOf(result.step).id).toBe("my-checkout");
|
|
689
|
+
expect(propsOf(result.step).uses).toBe("actions/checkout@v7");
|
|
687
690
|
});
|
|
688
691
|
|
|
689
692
|
test("SetupNode defaults override step props", () => {
|
|
@@ -691,8 +694,8 @@ describe("defaults overrides", () => {
|
|
|
691
694
|
nodeVersion: "20",
|
|
692
695
|
defaults: { step: { id: "setup" } },
|
|
693
696
|
});
|
|
694
|
-
expect(result.step.
|
|
695
|
-
expect(result.step.
|
|
697
|
+
expect(propsOf(result.step).id).toBe("setup");
|
|
698
|
+
expect(propsOf(result.step).name).toBe("Setup Node.js");
|
|
696
699
|
});
|
|
697
700
|
|
|
698
701
|
test("NodeCI defaults override job and workflow props", () => {
|
|
@@ -702,8 +705,8 @@ describe("defaults overrides", () => {
|
|
|
702
705
|
workflow: { concurrency: "ci-${{ github.ref }}" } as any,
|
|
703
706
|
},
|
|
704
707
|
});
|
|
705
|
-
expect(result.job
|
|
706
|
-
expect(result.workflow.
|
|
708
|
+
expect(propsOf(result.job)["timeout-minutes"]).toBe(30);
|
|
709
|
+
expect(propsOf(result.workflow).concurrency).toBe("ci-${{ github.ref }}");
|
|
707
710
|
});
|
|
708
711
|
|
|
709
712
|
test("NodePipeline defaults override individual jobs", () => {
|
|
@@ -713,8 +716,8 @@ describe("defaults overrides", () => {
|
|
|
713
716
|
testJob: { "timeout-minutes": 20 } as any,
|
|
714
717
|
},
|
|
715
718
|
});
|
|
716
|
-
expect(result.buildJob
|
|
717
|
-
expect(result.testJob
|
|
719
|
+
expect(propsOf(result.buildJob)["timeout-minutes"]).toBe(15);
|
|
720
|
+
expect(propsOf(result.testJob)["timeout-minutes"]).toBe(20);
|
|
718
721
|
});
|
|
719
722
|
|
|
720
723
|
test("DockerBuild defaults override job and workflow", () => {
|
|
@@ -724,8 +727,8 @@ describe("defaults overrides", () => {
|
|
|
724
727
|
workflow: { concurrency: "docker" } as any,
|
|
725
728
|
},
|
|
726
729
|
});
|
|
727
|
-
expect(result.job
|
|
728
|
-
expect(result.workflow.
|
|
730
|
+
expect(propsOf(result.job)["timeout-minutes"]).toBe(60);
|
|
731
|
+
expect(propsOf(result.workflow).concurrency).toBe("docker");
|
|
729
732
|
});
|
|
730
733
|
|
|
731
734
|
test("DeployEnvironment defaults override deploy and cleanup jobs", () => {
|
|
@@ -737,8 +740,8 @@ describe("defaults overrides", () => {
|
|
|
737
740
|
cleanupJob: { "timeout-minutes": 10 } as any,
|
|
738
741
|
},
|
|
739
742
|
});
|
|
740
|
-
expect(result.deployJob
|
|
741
|
-
expect(result.cleanupJob
|
|
743
|
+
expect(propsOf(result.deployJob)["timeout-minutes"]).toBe(30);
|
|
744
|
+
expect(propsOf(result.cleanupJob)["timeout-minutes"]).toBe(10);
|
|
742
745
|
});
|
|
743
746
|
|
|
744
747
|
test("GoCI defaults override workflow name", () => {
|
|
@@ -747,7 +750,7 @@ describe("defaults overrides", () => {
|
|
|
747
750
|
workflow: { name: "Custom Go CI" } as any,
|
|
748
751
|
},
|
|
749
752
|
});
|
|
750
|
-
expect(result.workflow.
|
|
753
|
+
expect(propsOf(result.workflow).name).toBe("Custom Go CI");
|
|
751
754
|
});
|
|
752
755
|
|
|
753
756
|
test("PythonCI defaults override testJob", () => {
|
|
@@ -756,7 +759,7 @@ describe("defaults overrides", () => {
|
|
|
756
759
|
testJob: { "timeout-minutes": 45 } as any,
|
|
757
760
|
},
|
|
758
761
|
});
|
|
759
|
-
expect(result.testJob
|
|
762
|
+
expect(propsOf(result.testJob)["timeout-minutes"]).toBe(45);
|
|
760
763
|
});
|
|
761
764
|
|
|
762
765
|
test("UploadArtifact defaults override step props", () => {
|
|
@@ -765,8 +768,8 @@ describe("defaults overrides", () => {
|
|
|
765
768
|
path: "dist/",
|
|
766
769
|
defaults: { step: { id: "upload" } },
|
|
767
770
|
});
|
|
768
|
-
expect(result.step.
|
|
769
|
-
expect(result.step.
|
|
771
|
+
expect(propsOf(result.step).id).toBe("upload");
|
|
772
|
+
expect(propsOf(result.step).uses).toBe("actions/upload-artifact@v4");
|
|
770
773
|
});
|
|
771
774
|
|
|
772
775
|
test("CacheAction defaults override step props", () => {
|
|
@@ -775,7 +778,7 @@ describe("defaults overrides", () => {
|
|
|
775
778
|
key: "cache-key",
|
|
776
779
|
defaults: { step: { id: "cache-step" } },
|
|
777
780
|
});
|
|
778
|
-
expect(result.step.
|
|
779
|
-
expect(result.step.
|
|
781
|
+
expect(propsOf(result.step).id).toBe("cache-step");
|
|
782
|
+
expect(propsOf(result.step).uses).toBe("actions/cache@v4");
|
|
780
783
|
});
|
|
781
784
|
});
|