Skip to content

Commit df32a6d

Browse files
committed
Refactor dynamicInclude to return the Config instance for consistency with the fluent builder pattern
1 parent 8074252 commit df32a6d

File tree

4 files changed

+154
-1
lines changed

4 files changed

+154
-1
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
"@noxify/gitlab-ci-builder": patch
3+
---
4+
5+
Change `dynamicInclude` config functions to return the `Config` instance for consistency with the fluent builder pattern.
6+
7+
Included config files should now return the config:
8+
9+
```ts
10+
export default function (config: Config) {
11+
config.stages("build")
12+
return config
13+
}
14+
```

README.md

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,141 @@ config.job("build", {
220220

221221
**Recommendation:** Use GitLab's `extends` keyword instead of YAML merge operators to maintain clearer relationships in the generated TypeScript code.
222222

223+
### Dynamic TypeScript Includes
224+
225+
The `dynamicInclude` method allows you to modularize your GitLab CI configuration by splitting it across multiple TypeScript files. Each included file can export a configuration function that receives the main `Config` instance.
226+
227+
#### Basic Usage
228+
229+
```ts
230+
import { Config } from "./src"
231+
232+
const config = new Config()
233+
234+
// Include all config files from a directory
235+
await config.dynamicInclude(process.cwd(), ["configs/**/*.ts"])
236+
237+
console.log(config.getPlainObject())
238+
```
239+
240+
#### Creating Included Config Files
241+
242+
Included files can use either a **default export** (preferred) or a **named `extendConfig` export**. The exported function receives the `Config` instance, mutates it, and returns it for consistency with the fluent API.
243+
244+
**Option 1: Default Export (Recommended)**
245+
246+
````ts
247+
**Option 1: Default Export (Recommended)**
248+
249+
```ts
250+
// configs/build-jobs.ts
251+
import type { Config } from "gitlab-ci-builder"
252+
253+
export default function (config: Config) {
254+
// Mutate the config instance directly
255+
config.stages("build")
256+
257+
config.template(".node-base", {
258+
image: "node:22",
259+
before_script: ["npm ci"],
260+
})
261+
262+
config.extends(".node-base", "build", {
263+
stage: "build",
264+
script: ["npm run build"],
265+
})
266+
267+
return config
268+
}
269+
````
270+
271+
**Option 2: Named Export**
272+
273+
```ts
274+
// configs/test-jobs.ts
275+
import type { Config } from "gitlab-ci-builder"
276+
277+
export function extendConfig(config: Config) {
278+
config.stages("test")
279+
280+
config.job("unit-test", {
281+
stage: "test",
282+
script: ["npm run test:unit"],
283+
})
284+
285+
config.job("integration-test", {
286+
stage: "test",
287+
script: ["npm run test:integration"],
288+
})
289+
290+
return config
291+
}
292+
```
293+
294+
#### Complete Example
295+
296+
**Main configuration file:**
297+
298+
```ts
299+
// build-pipeline.ts
300+
import { Config, writeYamlFile } from "./src"
301+
302+
async function main() {
303+
const config = new Config()
304+
305+
// Set up base configuration
306+
config.stages("prepare", "build", "test", "deploy")
307+
config.variable("DOCKER_DRIVER", "overlay2")
308+
309+
// Include additional configurations from separate files
310+
await config.dynamicInclude(process.cwd(), [
311+
"configs/build-jobs.ts",
312+
"configs/test-jobs.ts",
313+
"configs/deploy-jobs.ts",
314+
])
315+
316+
// Write the final pipeline configuration
317+
await writeYamlFile(".gitlab-ci.yml", config.getPlainObject())
318+
}
319+
320+
main()
321+
```
322+
323+
**Separate config files:**
324+
325+
```ts
326+
// configs/deploy-jobs.ts
327+
import type { Config } from "gitlab-ci-builder"
328+
329+
export default function (config: Config) {
330+
config.job("deploy-staging", {
331+
stage: "deploy",
332+
script: ["kubectl apply -f k8s/staging/"],
333+
environment: { name: "staging" },
334+
only: { refs: ["develop"] },
335+
})
336+
337+
config.job("deploy-production", {
338+
stage: "deploy",
339+
script: ["kubectl apply -f k8s/production/"],
340+
environment: { name: "production" },
341+
only: { refs: ["main"] },
342+
when: "manual",
343+
})
344+
345+
return config
346+
}
347+
```
348+
349+
#### Benefits
350+
351+
- **Modularity**: Split large pipelines into focused, manageable files
352+
- **Reusability**: Share common job configurations across multiple pipelines
353+
- **Team collaboration**: Different teams can maintain their own config files
354+
- **Type safety**: Full TypeScript support with autocomplete and type checking
355+
356+
**Note:** If both default and named exports are present, the default export takes precedence.
357+
223358
## API Reference
224359

225360
This reference summarizes the primary `Config` API surface. Method signatures reflect

src/config.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export type MacroArgs = unknown
2020
// async work and return a Promise.
2121
export type MaybeAsync<T> = T | Promise<T>
2222

23-
export type ExtendConfigFunction = (config: Config) => MaybeAsync<void>
23+
export type ExtendConfigFunction = (config: Config) => MaybeAsync<Config>
2424

2525
/**
2626
* A global OOP-style GitLab CI configurator.
@@ -365,6 +365,8 @@ export class Config {
365365
throw new Error(`The exported function is not a function!`)
366366
}
367367

368+
// Call the function and await the result (Config instance is returned but not used)
369+
// The function mutates `this` Config instance directly
368370
await extendFn(this)
369371
}
370372
}

tests/dynamic-include.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ describe("Config - include", () => {
5252
it("should call extendConfig for each matched file", async () => {
5353
const mockExtendConfig = vi.fn((cfg: Config) => {
5454
cfg.job("included-job", { script: ["echo included"] })
55+
return cfg
5556
})
5657

5758
vi.mocked(globSync).mockReturnValue(["/fake/file1.ts", "/fake/file2.ts"])
@@ -127,6 +128,7 @@ describe("Config - include", () => {
127128
it("should support default export", async () => {
128129
const mockExtendConfig = vi.fn((cfg: Config) => {
129130
cfg.job("default-job", { script: ["echo default"] })
131+
return cfg
130132
})
131133

132134
vi.mocked(globSync).mockReturnValue(["/fake/default.ts"])

0 commit comments

Comments
 (0)