@intentius/chant-lexicon-gitlab 0.0.15 → 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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "algorithm": "xxhash64",
3
3
  "artifacts": {
4
- "manifest.json": "ca4a4caa423e76f8",
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": "37c2ff19bbda42a0"
21
+ "composite": "b56a2a2ac9f9f569"
21
22
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitlab",
3
- "version": "0.0.15",
3
+ "version": "0.0.16",
4
4
  "chantVersion": ">=0.1.0",
5
5
  "namespace": "GitLab",
6
6
  "intrinsics": [
@@ -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,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentius/chant-lexicon-gitlab",
3
- "version": "0.0.15",
3
+ "version": "0.0.16",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "files": [
@@ -22,7 +22,7 @@
22
22
  "bundle": "bun run src/package-cli.ts",
23
23
  "validate": "bun run src/validate-cli.ts",
24
24
  "docs": "bun run src/codegen/docs-cli.ts",
25
- "prepack": "bun run bundle && bun run validate"
25
+ "prepack": "bun run generate && bun run bundle && bun run validate"
26
26
  },
27
27
  "dependencies": {
28
28
  "@intentius/chant": "0.0.15"
@@ -182,7 +182,7 @@ describe("gitlabPlugin", () => {
182
182
 
183
183
  test("returns skills", () => {
184
184
  const skills = gitlabPlugin.skills!();
185
- expect(skills).toHaveLength(1);
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
- return [
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.