@intentius/chant-lexicon-gitlab 0.0.14 → 0.0.16
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 +4 -3
- package/dist/manifest.json +1 -1
- package/dist/skills/gitlab-ci-patterns.md +309 -0
- package/package.json +27 -24
- package/src/codegen/docs.ts +1 -1
- package/src/plugin.test.ts +1 -1
- package/src/plugin.ts +47 -1
- package/src/skills/gitlab-ci-patterns.md +309 -0
package/dist/integrity.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"algorithm": "xxhash64",
|
|
3
3
|
"artifacts": {
|
|
4
|
-
"manifest.json": "
|
|
4
|
+
"manifest.json": "2b7f06fc4199f834",
|
|
5
5
|
"meta.json": "c663c6c63748a9d0",
|
|
6
6
|
"types/index.d.ts": "64e65524615be023",
|
|
7
7
|
"rules/missing-stage.ts": "6d5379e74209a735",
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
"rules/wgl013.ts": "3519c933e23fc605",
|
|
16
16
|
"rules/wgl014.ts": "6248a852888e8028",
|
|
17
17
|
"rules/yaml-helpers.ts": "b5416b80369484f2",
|
|
18
|
-
"skills/chant-gitlab.md": "4393eb63e0b84b7f"
|
|
18
|
+
"skills/chant-gitlab.md": "4393eb63e0b84b7f",
|
|
19
|
+
"skills/gitlab-ci-patterns.md": "bdb522359253aac8"
|
|
19
20
|
},
|
|
20
|
-
"composite": "
|
|
21
|
+
"composite": "b56a2a2ac9f9f569"
|
|
21
22
|
}
|
package/dist/manifest.json
CHANGED
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
---
|
|
2
|
+
skill: gitlab-ci-patterns
|
|
3
|
+
description: GitLab CI/CD pipeline stages, caching, artifacts, includes, and advanced patterns
|
|
4
|
+
user-invocable: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# GitLab CI/CD Pipeline Patterns
|
|
8
|
+
|
|
9
|
+
## Pipeline Stage Design
|
|
10
|
+
|
|
11
|
+
### Standard Stage Ordering
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { Job, Image, Cache, Artifacts } from "@intentius/chant-lexicon-gitlab";
|
|
15
|
+
|
|
16
|
+
// Stages execute in order. Jobs within a stage run in parallel.
|
|
17
|
+
// Default stages: .pre, build, test, deploy, .post
|
|
18
|
+
|
|
19
|
+
export const lint = new Job({
|
|
20
|
+
stage: "build",
|
|
21
|
+
image: new Image({ name: "node:22-alpine" }),
|
|
22
|
+
script: ["npm ci", "npm run lint"],
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export const test = new Job({
|
|
26
|
+
stage: "test",
|
|
27
|
+
image: new Image({ name: "node:22-alpine" }),
|
|
28
|
+
script: ["npm ci", "npm test"],
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
export const deploy = new Job({
|
|
32
|
+
stage: "deploy",
|
|
33
|
+
script: ["./deploy.sh"],
|
|
34
|
+
rules: [{ if: '$CI_COMMIT_BRANCH == "main"' }],
|
|
35
|
+
});
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Parallel Jobs with needs
|
|
39
|
+
|
|
40
|
+
Use `needs` to create a DAG and skip waiting for the full stage:
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
export const unitTests = new Job({
|
|
44
|
+
stage: "test",
|
|
45
|
+
script: ["npm run test:unit"],
|
|
46
|
+
needs: ["build"],
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
export const integrationTests = new Job({
|
|
50
|
+
stage: "test",
|
|
51
|
+
script: ["npm run test:integration"],
|
|
52
|
+
needs: ["build"],
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
export const deploy = new Job({
|
|
56
|
+
stage: "deploy",
|
|
57
|
+
script: ["./deploy.sh"],
|
|
58
|
+
needs: ["unitTests", "integrationTests"],
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Caching Strategies
|
|
63
|
+
|
|
64
|
+
### Language-Specific Cache Keys
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
import { Cache } from "@intentius/chant-lexicon-gitlab";
|
|
68
|
+
|
|
69
|
+
// Node.js: cache node_modules by lockfile hash
|
|
70
|
+
export const nodeCache = new Cache({
|
|
71
|
+
key: { files: ["package-lock.json"] },
|
|
72
|
+
paths: ["node_modules/"],
|
|
73
|
+
policy: "pull-push",
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Python: cache pip downloads
|
|
77
|
+
export const pipCache = new Cache({
|
|
78
|
+
key: { files: ["requirements.txt"] },
|
|
79
|
+
paths: [".pip-cache/"],
|
|
80
|
+
policy: "pull-push",
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Cache Policies
|
|
85
|
+
|
|
86
|
+
| Policy | Behavior | Use for |
|
|
87
|
+
|--------|----------|---------|
|
|
88
|
+
| `pull-push` | Download and upload cache | Build jobs that install dependencies |
|
|
89
|
+
| `pull` | Download only, never upload | Test/deploy jobs (read from build cache) |
|
|
90
|
+
| `push` | Upload only, never download | Initial cache population |
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
export const build = new Job({
|
|
94
|
+
stage: "build",
|
|
95
|
+
cache: new Cache({
|
|
96
|
+
key: "$CI_COMMIT_REF_SLUG",
|
|
97
|
+
paths: ["node_modules/", "dist/"],
|
|
98
|
+
policy: "pull-push", // build populates cache
|
|
99
|
+
}),
|
|
100
|
+
script: ["npm ci", "npm run build"],
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
export const test = new Job({
|
|
104
|
+
stage: "test",
|
|
105
|
+
cache: new Cache({
|
|
106
|
+
key: "$CI_COMMIT_REF_SLUG",
|
|
107
|
+
paths: ["node_modules/"],
|
|
108
|
+
policy: "pull", // test only reads cache
|
|
109
|
+
}),
|
|
110
|
+
script: ["npm test"],
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Artifacts
|
|
115
|
+
|
|
116
|
+
### Pass Build Output Between Jobs
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
import { Artifacts } from "@intentius/chant-lexicon-gitlab";
|
|
120
|
+
|
|
121
|
+
export const buildArtifacts = new Artifacts({
|
|
122
|
+
paths: ["dist/"],
|
|
123
|
+
expire_in: "1 day",
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
export const testReports = new Artifacts({
|
|
127
|
+
reports: { junit: "coverage/junit.xml", coverage_report: { coverage_format: "cobertura", path: "coverage/cobertura.xml" } },
|
|
128
|
+
paths: ["coverage/"],
|
|
129
|
+
expire_in: "1 week",
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
export const build = new Job({
|
|
133
|
+
stage: "build",
|
|
134
|
+
script: ["npm ci", "npm run build"],
|
|
135
|
+
artifacts: buildArtifacts,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
export const test = new Job({
|
|
139
|
+
stage: "test",
|
|
140
|
+
script: ["npm ci", "npm test -- --coverage"],
|
|
141
|
+
artifacts: testReports,
|
|
142
|
+
needs: ["build"],
|
|
143
|
+
});
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Artifact Types
|
|
147
|
+
|
|
148
|
+
| Type | Purpose | GitLab feature |
|
|
149
|
+
|------|---------|----------------|
|
|
150
|
+
| `junit` | Test results | MR test report widget |
|
|
151
|
+
| `coverage_report` | Code coverage | MR coverage visualization |
|
|
152
|
+
| `dotenv` | Export variables | Pass variables to downstream jobs |
|
|
153
|
+
| `terraform` | Terraform plans | MR Terraform widget |
|
|
154
|
+
|
|
155
|
+
## Include Patterns
|
|
156
|
+
|
|
157
|
+
### Reusable Pipeline Components
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
import { Include } from "@intentius/chant-lexicon-gitlab";
|
|
161
|
+
|
|
162
|
+
// Include from same project
|
|
163
|
+
export const localInclude = new Include({
|
|
164
|
+
local: ".gitlab/ci/deploy.yml",
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// Include from another project
|
|
168
|
+
export const projectInclude = new Include({
|
|
169
|
+
project: "devops/pipeline-templates",
|
|
170
|
+
ref: "main",
|
|
171
|
+
file: "/templates/docker-build.yml",
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// Include from remote URL
|
|
175
|
+
export const remoteInclude = new Include({
|
|
176
|
+
remote: "https://example.com/ci-templates/security-scan.yml",
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// Include a GitLab CI template
|
|
180
|
+
export const templateInclude = new Include({
|
|
181
|
+
template: "Security/SAST.gitlab-ci.yml",
|
|
182
|
+
});
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Composites for Common Pipelines
|
|
186
|
+
|
|
187
|
+
Use composites instead of raw includes for type-safe pipeline generation:
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
import { NodePipeline } from "@intentius/chant-lexicon-gitlab";
|
|
191
|
+
|
|
192
|
+
export const app = NodePipeline({
|
|
193
|
+
nodeVersion: "22",
|
|
194
|
+
installCommand: "npm ci",
|
|
195
|
+
buildScript: "build",
|
|
196
|
+
testScript: "test",
|
|
197
|
+
});
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Rules and Conditional Execution
|
|
201
|
+
|
|
202
|
+
### Branch-Based Rules
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
export const deployStaging = new Job({
|
|
206
|
+
stage: "deploy",
|
|
207
|
+
script: ["./deploy.sh staging"],
|
|
208
|
+
rules: [
|
|
209
|
+
{ if: '$CI_COMMIT_BRANCH == "develop"', when: "on_success" },
|
|
210
|
+
],
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
export const deployProd = new Job({
|
|
214
|
+
stage: "deploy",
|
|
215
|
+
script: ["./deploy.sh production"],
|
|
216
|
+
rules: [
|
|
217
|
+
{ if: '$CI_COMMIT_BRANCH == "main"', when: "manual" },
|
|
218
|
+
],
|
|
219
|
+
});
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### MR vs Branch Pipelines
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
// Run on merge requests only
|
|
226
|
+
export const mrTest = new Job({
|
|
227
|
+
stage: "test",
|
|
228
|
+
script: ["npm test"],
|
|
229
|
+
rules: [{ if: "$CI_MERGE_REQUEST_IID" }],
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
// Run on default branch only
|
|
233
|
+
export const release = new Job({
|
|
234
|
+
stage: "deploy",
|
|
235
|
+
script: ["npm publish"],
|
|
236
|
+
rules: [{ if: '$CI_COMMIT_BRANCH == "main"' }],
|
|
237
|
+
});
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
## Review Apps
|
|
241
|
+
|
|
242
|
+
### Deploy Per-MR Environments
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
import { ReviewApp } from "@intentius/chant-lexicon-gitlab";
|
|
246
|
+
|
|
247
|
+
export const review = ReviewApp({
|
|
248
|
+
name: "review",
|
|
249
|
+
deployScript: "kubectl apply -f manifests.yaml",
|
|
250
|
+
stopScript: "kubectl delete -f manifests.yaml",
|
|
251
|
+
autoStopIn: "1 week",
|
|
252
|
+
});
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
This generates a deploy job with `environment` and a stop job with `action: stop` that triggers when the MR is merged or closed.
|
|
256
|
+
|
|
257
|
+
## Docker Build Pattern
|
|
258
|
+
|
|
259
|
+
### Multi-Stage Build and Push
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
import { DockerBuild } from "@intentius/chant-lexicon-gitlab";
|
|
263
|
+
|
|
264
|
+
export const docker = DockerBuild({
|
|
265
|
+
dockerfile: "Dockerfile",
|
|
266
|
+
context: ".",
|
|
267
|
+
tagLatest: true,
|
|
268
|
+
registry: "$CI_REGISTRY",
|
|
269
|
+
imageName: "$CI_REGISTRY_IMAGE",
|
|
270
|
+
});
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
This generates a job using Docker-in-Docker (`dind`) service with proper `DOCKER_TLS_CERTDIR` configuration.
|
|
274
|
+
|
|
275
|
+
## Matrix Builds
|
|
276
|
+
|
|
277
|
+
### Test Across Multiple Versions
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
export const test = new Job({
|
|
281
|
+
stage: "test",
|
|
282
|
+
parallel: {
|
|
283
|
+
matrix: [
|
|
284
|
+
{ NODE_VERSION: ["18", "20", "22"] },
|
|
285
|
+
],
|
|
286
|
+
},
|
|
287
|
+
image: new Image({ name: "node:${NODE_VERSION}-alpine" }),
|
|
288
|
+
script: ["npm ci", "npm test"],
|
|
289
|
+
});
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
## Pipeline Security
|
|
293
|
+
|
|
294
|
+
### Protected Variables
|
|
295
|
+
|
|
296
|
+
Use protected variables for production secrets. They are only available on protected branches/tags:
|
|
297
|
+
|
|
298
|
+
```typescript
|
|
299
|
+
export const deploy = new Job({
|
|
300
|
+
stage: "deploy",
|
|
301
|
+
script: ["./deploy.sh"],
|
|
302
|
+
variables: { DEPLOY_ENV: "production" },
|
|
303
|
+
rules: [
|
|
304
|
+
{ if: '$CI_COMMIT_BRANCH == "main"', when: "manual" },
|
|
305
|
+
],
|
|
306
|
+
});
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
Set `DEPLOY_TOKEN` as a protected, masked variable in project settings.
|
package/package.json
CHANGED
|
@@ -1,30 +1,33 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intentius/chant-lexicon-gitlab",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.16",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"files": [
|
|
6
|
+
"files": [
|
|
7
|
+
"src/",
|
|
8
|
+
"dist/"
|
|
9
|
+
],
|
|
7
10
|
"publishConfig": {
|
|
8
|
-
|
|
9
|
-
},
|
|
10
|
-
"exports": {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
},
|
|
17
|
-
"scripts": {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
},
|
|
24
|
-
"dependencies": {
|
|
25
|
-
|
|
26
|
-
},
|
|
27
|
-
"devDependencies": {
|
|
28
|
-
|
|
29
|
-
}
|
|
11
|
+
"access": "public"
|
|
12
|
+
},
|
|
13
|
+
"exports": {
|
|
14
|
+
".": "./src/index.ts",
|
|
15
|
+
"./*": "./src/*.ts",
|
|
16
|
+
"./manifest": "./dist/manifest.json",
|
|
17
|
+
"./meta": "./dist/meta.json",
|
|
18
|
+
"./types": "./dist/types/index.d.ts"
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"generate": "bun run src/codegen/generate-cli.ts",
|
|
22
|
+
"bundle": "bun run src/package-cli.ts",
|
|
23
|
+
"validate": "bun run src/validate-cli.ts",
|
|
24
|
+
"docs": "bun run src/codegen/docs-cli.ts",
|
|
25
|
+
"prepack": "bun run generate && bun run bundle && bun run validate"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@intentius/chant": "0.0.15"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"typescript": "^5.9.3"
|
|
32
|
+
}
|
|
30
33
|
}
|
package/src/codegen/docs.ts
CHANGED
|
@@ -624,7 +624,7 @@ Skills are structured markdown documents bundled with a lexicon. When an AI agen
|
|
|
624
624
|
|
|
625
625
|
## Installation
|
|
626
626
|
|
|
627
|
-
When you scaffold a new project with \`chant init --lexicon gitlab\`, the skill is installed to
|
|
627
|
+
When you scaffold a new project with \`chant init --lexicon gitlab\`, the skill is installed to \`skills/chant-gitlab/SKILL.md\` for automatic discovery by Claude Code.
|
|
628
628
|
|
|
629
629
|
For existing projects, create the file manually:
|
|
630
630
|
|
package/src/plugin.test.ts
CHANGED
|
@@ -182,7 +182,7 @@ describe("gitlabPlugin", () => {
|
|
|
182
182
|
|
|
183
183
|
test("returns skills", () => {
|
|
184
184
|
const skills = gitlabPlugin.skills!();
|
|
185
|
-
expect(skills).
|
|
185
|
+
expect(skills.length).toBeGreaterThanOrEqual(2);
|
|
186
186
|
expect(skills[0].name).toBe("chant-gitlab");
|
|
187
187
|
expect(skills[0].description).toBeDefined();
|
|
188
188
|
expect(skills[0].content).toContain("skill: chant-gitlab");
|
package/src/plugin.ts
CHANGED
|
@@ -342,7 +342,7 @@ export const deploy = new Job({
|
|
|
342
342
|
},
|
|
343
343
|
|
|
344
344
|
skills(): SkillDefinition[] {
|
|
345
|
-
|
|
345
|
+
const skills: SkillDefinition[] = [
|
|
346
346
|
{
|
|
347
347
|
name: "chant-gitlab",
|
|
348
348
|
description: "GitLab CI/CD pipeline lifecycle — build, validate, deploy, monitor, rollback, and troubleshoot",
|
|
@@ -944,5 +944,51 @@ curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \\
|
|
|
944
944
|
],
|
|
945
945
|
},
|
|
946
946
|
];
|
|
947
|
+
|
|
948
|
+
// Load file-based skills from src/skills/
|
|
949
|
+
const { readFileSync } = require("fs");
|
|
950
|
+
const { join, dirname } = require("path");
|
|
951
|
+
const { fileURLToPath } = require("url");
|
|
952
|
+
const dir = dirname(fileURLToPath(import.meta.url));
|
|
953
|
+
|
|
954
|
+
const skillFiles = [
|
|
955
|
+
{
|
|
956
|
+
file: "gitlab-ci-patterns.md",
|
|
957
|
+
name: "gitlab-ci-patterns",
|
|
958
|
+
description: "GitLab CI/CD pipeline stages, caching, artifacts, includes, and advanced patterns",
|
|
959
|
+
triggers: [
|
|
960
|
+
{ type: "context" as const, value: "gitlab pipeline" },
|
|
961
|
+
{ type: "context" as const, value: "gitlab cache" },
|
|
962
|
+
{ type: "context" as const, value: "gitlab artifacts" },
|
|
963
|
+
{ type: "context" as const, value: "gitlab include" },
|
|
964
|
+
{ type: "context" as const, value: "gitlab stages" },
|
|
965
|
+
{ type: "context" as const, value: "review app" },
|
|
966
|
+
],
|
|
967
|
+
parameters: [],
|
|
968
|
+
examples: [
|
|
969
|
+
{
|
|
970
|
+
title: "Pipeline with caching",
|
|
971
|
+
input: "Set up a Node.js pipeline with proper caching",
|
|
972
|
+
output: "import { Job, Cache } from \"@intentius/chant-lexicon-gitlab\";\n\nconst cache = new Cache({ key: { files: [\"package-lock.json\"] }, paths: [\"node_modules/\"] });",
|
|
973
|
+
},
|
|
974
|
+
],
|
|
975
|
+
},
|
|
976
|
+
];
|
|
977
|
+
|
|
978
|
+
for (const skill of skillFiles) {
|
|
979
|
+
try {
|
|
980
|
+
const content = readFileSync(join(dir, "skills", skill.file), "utf-8");
|
|
981
|
+
skills.push({
|
|
982
|
+
name: skill.name,
|
|
983
|
+
description: skill.description,
|
|
984
|
+
content,
|
|
985
|
+
triggers: skill.triggers,
|
|
986
|
+
parameters: skill.parameters,
|
|
987
|
+
examples: skill.examples,
|
|
988
|
+
});
|
|
989
|
+
} catch { /* skip missing skills */ }
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
return skills;
|
|
947
993
|
},
|
|
948
994
|
};
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
---
|
|
2
|
+
skill: gitlab-ci-patterns
|
|
3
|
+
description: GitLab CI/CD pipeline stages, caching, artifacts, includes, and advanced patterns
|
|
4
|
+
user-invocable: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# GitLab CI/CD Pipeline Patterns
|
|
8
|
+
|
|
9
|
+
## Pipeline Stage Design
|
|
10
|
+
|
|
11
|
+
### Standard Stage Ordering
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { Job, Image, Cache, Artifacts } from "@intentius/chant-lexicon-gitlab";
|
|
15
|
+
|
|
16
|
+
// Stages execute in order. Jobs within a stage run in parallel.
|
|
17
|
+
// Default stages: .pre, build, test, deploy, .post
|
|
18
|
+
|
|
19
|
+
export const lint = new Job({
|
|
20
|
+
stage: "build",
|
|
21
|
+
image: new Image({ name: "node:22-alpine" }),
|
|
22
|
+
script: ["npm ci", "npm run lint"],
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export const test = new Job({
|
|
26
|
+
stage: "test",
|
|
27
|
+
image: new Image({ name: "node:22-alpine" }),
|
|
28
|
+
script: ["npm ci", "npm test"],
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
export const deploy = new Job({
|
|
32
|
+
stage: "deploy",
|
|
33
|
+
script: ["./deploy.sh"],
|
|
34
|
+
rules: [{ if: '$CI_COMMIT_BRANCH == "main"' }],
|
|
35
|
+
});
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Parallel Jobs with needs
|
|
39
|
+
|
|
40
|
+
Use `needs` to create a DAG and skip waiting for the full stage:
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
export const unitTests = new Job({
|
|
44
|
+
stage: "test",
|
|
45
|
+
script: ["npm run test:unit"],
|
|
46
|
+
needs: ["build"],
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
export const integrationTests = new Job({
|
|
50
|
+
stage: "test",
|
|
51
|
+
script: ["npm run test:integration"],
|
|
52
|
+
needs: ["build"],
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
export const deploy = new Job({
|
|
56
|
+
stage: "deploy",
|
|
57
|
+
script: ["./deploy.sh"],
|
|
58
|
+
needs: ["unitTests", "integrationTests"],
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Caching Strategies
|
|
63
|
+
|
|
64
|
+
### Language-Specific Cache Keys
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
import { Cache } from "@intentius/chant-lexicon-gitlab";
|
|
68
|
+
|
|
69
|
+
// Node.js: cache node_modules by lockfile hash
|
|
70
|
+
export const nodeCache = new Cache({
|
|
71
|
+
key: { files: ["package-lock.json"] },
|
|
72
|
+
paths: ["node_modules/"],
|
|
73
|
+
policy: "pull-push",
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Python: cache pip downloads
|
|
77
|
+
export const pipCache = new Cache({
|
|
78
|
+
key: { files: ["requirements.txt"] },
|
|
79
|
+
paths: [".pip-cache/"],
|
|
80
|
+
policy: "pull-push",
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Cache Policies
|
|
85
|
+
|
|
86
|
+
| Policy | Behavior | Use for |
|
|
87
|
+
|--------|----------|---------|
|
|
88
|
+
| `pull-push` | Download and upload cache | Build jobs that install dependencies |
|
|
89
|
+
| `pull` | Download only, never upload | Test/deploy jobs (read from build cache) |
|
|
90
|
+
| `push` | Upload only, never download | Initial cache population |
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
export const build = new Job({
|
|
94
|
+
stage: "build",
|
|
95
|
+
cache: new Cache({
|
|
96
|
+
key: "$CI_COMMIT_REF_SLUG",
|
|
97
|
+
paths: ["node_modules/", "dist/"],
|
|
98
|
+
policy: "pull-push", // build populates cache
|
|
99
|
+
}),
|
|
100
|
+
script: ["npm ci", "npm run build"],
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
export const test = new Job({
|
|
104
|
+
stage: "test",
|
|
105
|
+
cache: new Cache({
|
|
106
|
+
key: "$CI_COMMIT_REF_SLUG",
|
|
107
|
+
paths: ["node_modules/"],
|
|
108
|
+
policy: "pull", // test only reads cache
|
|
109
|
+
}),
|
|
110
|
+
script: ["npm test"],
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Artifacts
|
|
115
|
+
|
|
116
|
+
### Pass Build Output Between Jobs
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
import { Artifacts } from "@intentius/chant-lexicon-gitlab";
|
|
120
|
+
|
|
121
|
+
export const buildArtifacts = new Artifacts({
|
|
122
|
+
paths: ["dist/"],
|
|
123
|
+
expire_in: "1 day",
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
export const testReports = new Artifacts({
|
|
127
|
+
reports: { junit: "coverage/junit.xml", coverage_report: { coverage_format: "cobertura", path: "coverage/cobertura.xml" } },
|
|
128
|
+
paths: ["coverage/"],
|
|
129
|
+
expire_in: "1 week",
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
export const build = new Job({
|
|
133
|
+
stage: "build",
|
|
134
|
+
script: ["npm ci", "npm run build"],
|
|
135
|
+
artifacts: buildArtifacts,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
export const test = new Job({
|
|
139
|
+
stage: "test",
|
|
140
|
+
script: ["npm ci", "npm test -- --coverage"],
|
|
141
|
+
artifacts: testReports,
|
|
142
|
+
needs: ["build"],
|
|
143
|
+
});
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Artifact Types
|
|
147
|
+
|
|
148
|
+
| Type | Purpose | GitLab feature |
|
|
149
|
+
|------|---------|----------------|
|
|
150
|
+
| `junit` | Test results | MR test report widget |
|
|
151
|
+
| `coverage_report` | Code coverage | MR coverage visualization |
|
|
152
|
+
| `dotenv` | Export variables | Pass variables to downstream jobs |
|
|
153
|
+
| `terraform` | Terraform plans | MR Terraform widget |
|
|
154
|
+
|
|
155
|
+
## Include Patterns
|
|
156
|
+
|
|
157
|
+
### Reusable Pipeline Components
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
import { Include } from "@intentius/chant-lexicon-gitlab";
|
|
161
|
+
|
|
162
|
+
// Include from same project
|
|
163
|
+
export const localInclude = new Include({
|
|
164
|
+
local: ".gitlab/ci/deploy.yml",
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// Include from another project
|
|
168
|
+
export const projectInclude = new Include({
|
|
169
|
+
project: "devops/pipeline-templates",
|
|
170
|
+
ref: "main",
|
|
171
|
+
file: "/templates/docker-build.yml",
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// Include from remote URL
|
|
175
|
+
export const remoteInclude = new Include({
|
|
176
|
+
remote: "https://example.com/ci-templates/security-scan.yml",
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// Include a GitLab CI template
|
|
180
|
+
export const templateInclude = new Include({
|
|
181
|
+
template: "Security/SAST.gitlab-ci.yml",
|
|
182
|
+
});
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Composites for Common Pipelines
|
|
186
|
+
|
|
187
|
+
Use composites instead of raw includes for type-safe pipeline generation:
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
import { NodePipeline } from "@intentius/chant-lexicon-gitlab";
|
|
191
|
+
|
|
192
|
+
export const app = NodePipeline({
|
|
193
|
+
nodeVersion: "22",
|
|
194
|
+
installCommand: "npm ci",
|
|
195
|
+
buildScript: "build",
|
|
196
|
+
testScript: "test",
|
|
197
|
+
});
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Rules and Conditional Execution
|
|
201
|
+
|
|
202
|
+
### Branch-Based Rules
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
export const deployStaging = new Job({
|
|
206
|
+
stage: "deploy",
|
|
207
|
+
script: ["./deploy.sh staging"],
|
|
208
|
+
rules: [
|
|
209
|
+
{ if: '$CI_COMMIT_BRANCH == "develop"', when: "on_success" },
|
|
210
|
+
],
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
export const deployProd = new Job({
|
|
214
|
+
stage: "deploy",
|
|
215
|
+
script: ["./deploy.sh production"],
|
|
216
|
+
rules: [
|
|
217
|
+
{ if: '$CI_COMMIT_BRANCH == "main"', when: "manual" },
|
|
218
|
+
],
|
|
219
|
+
});
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### MR vs Branch Pipelines
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
// Run on merge requests only
|
|
226
|
+
export const mrTest = new Job({
|
|
227
|
+
stage: "test",
|
|
228
|
+
script: ["npm test"],
|
|
229
|
+
rules: [{ if: "$CI_MERGE_REQUEST_IID" }],
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
// Run on default branch only
|
|
233
|
+
export const release = new Job({
|
|
234
|
+
stage: "deploy",
|
|
235
|
+
script: ["npm publish"],
|
|
236
|
+
rules: [{ if: '$CI_COMMIT_BRANCH == "main"' }],
|
|
237
|
+
});
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
## Review Apps
|
|
241
|
+
|
|
242
|
+
### Deploy Per-MR Environments
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
import { ReviewApp } from "@intentius/chant-lexicon-gitlab";
|
|
246
|
+
|
|
247
|
+
export const review = ReviewApp({
|
|
248
|
+
name: "review",
|
|
249
|
+
deployScript: "kubectl apply -f manifests.yaml",
|
|
250
|
+
stopScript: "kubectl delete -f manifests.yaml",
|
|
251
|
+
autoStopIn: "1 week",
|
|
252
|
+
});
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
This generates a deploy job with `environment` and a stop job with `action: stop` that triggers when the MR is merged or closed.
|
|
256
|
+
|
|
257
|
+
## Docker Build Pattern
|
|
258
|
+
|
|
259
|
+
### Multi-Stage Build and Push
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
import { DockerBuild } from "@intentius/chant-lexicon-gitlab";
|
|
263
|
+
|
|
264
|
+
export const docker = DockerBuild({
|
|
265
|
+
dockerfile: "Dockerfile",
|
|
266
|
+
context: ".",
|
|
267
|
+
tagLatest: true,
|
|
268
|
+
registry: "$CI_REGISTRY",
|
|
269
|
+
imageName: "$CI_REGISTRY_IMAGE",
|
|
270
|
+
});
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
This generates a job using Docker-in-Docker (`dind`) service with proper `DOCKER_TLS_CERTDIR` configuration.
|
|
274
|
+
|
|
275
|
+
## Matrix Builds
|
|
276
|
+
|
|
277
|
+
### Test Across Multiple Versions
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
export const test = new Job({
|
|
281
|
+
stage: "test",
|
|
282
|
+
parallel: {
|
|
283
|
+
matrix: [
|
|
284
|
+
{ NODE_VERSION: ["18", "20", "22"] },
|
|
285
|
+
],
|
|
286
|
+
},
|
|
287
|
+
image: new Image({ name: "node:${NODE_VERSION}-alpine" }),
|
|
288
|
+
script: ["npm ci", "npm test"],
|
|
289
|
+
});
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
## Pipeline Security
|
|
293
|
+
|
|
294
|
+
### Protected Variables
|
|
295
|
+
|
|
296
|
+
Use protected variables for production secrets. They are only available on protected branches/tags:
|
|
297
|
+
|
|
298
|
+
```typescript
|
|
299
|
+
export const deploy = new Job({
|
|
300
|
+
stage: "deploy",
|
|
301
|
+
script: ["./deploy.sh"],
|
|
302
|
+
variables: { DEPLOY_ENV: "production" },
|
|
303
|
+
rules: [
|
|
304
|
+
{ if: '$CI_COMMIT_BRANCH == "main"', when: "manual" },
|
|
305
|
+
],
|
|
306
|
+
});
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
Set `DEPLOY_TOKEN` as a protected, masked variable in project settings.
|