Skip to content

Commit b6d0966

Browse files
authored
Child pipeline support (#19)
* update deps * Add child pipeline visualization and fluent API support * Refactor CI configuration and visualization examples; remove unused files and improve path handling * add missing tests
1 parent f05af6b commit b6d0966

19 files changed

+2802
-497
lines changed
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
---
2+
"@noxify/gitlab-ci-builder": minor
3+
---
4+
5+
Add child pipeline visualization and fluent API support
6+
7+
**New Features:**
8+
9+
- Added `childPipeline()` method to define child pipelines via callback API
10+
- Added `writeYamlFiles()` method to automatically write parent and all child pipeline YAML files
11+
- Child pipelines are now fully visualized in Mermaid diagrams, ASCII trees, and stage tables
12+
- Child pipelines defined via callback are tracked and don't require filesystem access for visualization
13+
14+
**API Changes:**
15+
16+
- Added `ChildPipelineConfig` interface to track child pipeline configurations
17+
- Extended `PipelineState` with `childPipelines` map and getter methods
18+
- Added public getters to `ConfigBuilder`: `jobs`, `templates`, `stages`, `jobOptionsMap`
19+
- Extended `VisualizationParams` with `trackedChildPipelines` parameter
20+
- Enhanced `extractChildPipelines` to prioritize tracked configs over file system parsing
21+
22+
**Visualization Enhancements:**
23+
24+
- `generateMermaidDiagram` shows child pipelines as subgraphs with dotted trigger edges
25+
- `generateAsciiTree` displays child pipelines with 🔀 indicator
26+
- `generateStageTable` includes child pipeline jobs with separator rows and proper indentation
27+
- Added `TriggerInfo` interface to track trigger configurations in `ExtendsGraphNode`
28+
- Extended `buildExtendsGraph` to extract trigger information from job definitions
29+
30+
**Example:**
31+
32+
```typescript
33+
config.childPipeline(
34+
"trigger:deploy",
35+
(child) => {
36+
child.stages("deploy")
37+
child.job("deploy:prod", { script: ["./deploy.sh"] })
38+
return child
39+
},
40+
{
41+
strategy: "depend",
42+
outputPath: "ci/deploy-pipeline.yml",
43+
},
44+
)
45+
46+
await config.writeYamlFiles(".")
47+
// Writes: .gitlab-ci.yml + ci/deploy-pipeline.yml
48+
```

.nvmrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
22
1+
24

.vscode/extensions.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@
99
"foxundermoon.shell-format",
1010
"bierner.comment-tagged-templates"
1111
]
12-
}
12+
}

.vscode/settings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,4 @@
3838
"files.associations": {
3939
"*.css": "tailwindcss"
4040
}
41-
}
41+
}

README.md

Lines changed: 153 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,16 @@ types, proper extends resolution, and a simple builder surface.
99
## Features
1010

1111
- Fluent TypeScript API to declare `stages`, `jobs`, `templates`, `variables` and `include` entries
12+
- **Child pipeline support**: Define and manage child pipelines programmatically with callback-based API
13+
- **Multi-file output**: Generate parent and child pipeline YAML files with `writeYamlFiles()`
1214
- **Command-line interface**: Visualize pipelines directly from the terminal with `gitlab-ci-builder visualize`
1315
- **Import existing YAML**: Convert `.gitlab-ci.yml` files to TypeScript code using the builder API
1416
- **Export to YAML**: Generate properly formatted YAML with customizable key ordering and spacing
1517
- **Robust extends resolution**: Proper topological sorting, cycle detection, and merge strategies
16-
- **Visualization tools**: Generate Mermaid diagrams, ASCII trees, and stage tables to visualize pipeline structure
18+
- **Visualization tools**: Generate Mermaid diagrams, ASCII trees, and stage tables to visualize pipeline structure (including child pipelines)
1719
- **Authentication support**: Access private repositories and includes with GitLab tokens
1820
- Supports reusable template jobs (hidden jobs starting with `.`) with deep-merge semantics
1921
- Dynamic TypeScript-based includes: import other files and apply their configuration functions
20-
- Comprehensive test coverage (241 tests, 86%+ coverage)
2122
- Small and dependency-light implementation
2223

2324
## Limitations
@@ -460,6 +461,156 @@ export default function (config: ConfigBuilder) {
460461

461462
**Note:** If both default and named exports are present, the default export takes precedence.
462463

464+
## Child Pipelines
465+
466+
Define child pipelines programmatically using a callback-based API. This eliminates the need to manually manage separate YAML files and automatically generates the trigger jobs.
467+
468+
### Basic Example
469+
470+
```ts
471+
import { ConfigBuilder } from "@noxify/gitlab-ci-builder"
472+
473+
const config = new ConfigBuilder().stages("build", "test", "deploy").job("build", {
474+
stage: "build",
475+
script: ["npm run build"],
476+
})
477+
478+
// Define a child pipeline with callback
479+
config.childPipeline("security-scan", (child) => {
480+
child.stages("scan", "report")
481+
child.job("sast", {
482+
stage: "scan",
483+
script: ["npm audit", "npm run security-scan"],
484+
})
485+
child.job("report", {
486+
stage: "report",
487+
script: ["npm run generate-report"],
488+
})
489+
})
490+
491+
// Write parent and all child pipeline files
492+
const files = await config.writeYamlFiles("./pipelines")
493+
// Returns: { parent: "pipelines/.gitlab-ci.yml", children: ["pipelines/security-scan-pipeline.yml"] }
494+
```
495+
496+
### Advanced Configuration
497+
498+
Customize child pipeline behavior with options:
499+
500+
```ts
501+
config.childPipeline(
502+
"deploy-environments",
503+
(child) => {
504+
child.stages("dev", "staging", "prod")
505+
child.job("deploy-dev", {
506+
stage: "dev",
507+
script: ["deploy.sh dev"],
508+
})
509+
child.job("deploy-staging", {
510+
stage: "staging",
511+
script: ["deploy.sh staging"],
512+
when: "manual",
513+
})
514+
child.job("deploy-prod", {
515+
stage: "prod",
516+
script: ["deploy.sh production"],
517+
when: "manual",
518+
})
519+
},
520+
{
521+
// Custom output path for child pipeline YAML
522+
outputPath: "pipelines/deploy.yml",
523+
524+
// Strategy for parent pipeline to wait for child
525+
strategy: "depend", // or "mirror"
526+
527+
// Forward variables to child pipeline
528+
forward: {
529+
pipelineVariables: true,
530+
yamlVariables: ["CI_ENVIRONMENT", "DEPLOY_TOKEN"],
531+
},
532+
533+
// Additional trigger job options
534+
jobOptions: {
535+
stage: "deploy",
536+
rules: [{ if: "$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH" }],
537+
},
538+
},
539+
)
540+
```
541+
542+
### Dynamic Child Pipelines
543+
544+
Generate child pipelines dynamically based on runtime conditions:
545+
546+
```ts
547+
interface Application {
548+
name: string
549+
enabled: boolean
550+
scanType: string
551+
}
552+
553+
const applications: Application[] = [
554+
{ name: "web-app", enabled: true, scanType: "sast" },
555+
{ name: "api-service", enabled: true, scanType: "dast" },
556+
{ name: "mobile-app", enabled: false, scanType: "sast" },
557+
]
558+
559+
const config = new ConfigBuilder().stages("scan")
560+
561+
// Generate child pipelines for enabled applications only
562+
applications
563+
.filter((app) => app.enabled)
564+
.forEach((app) => {
565+
config.childPipeline(`scan-${app.name}`, (child) => {
566+
child.stages("prepare", "scan", "report")
567+
child.job(`${app.scanType}-scan`, {
568+
stage: "scan",
569+
script: [`run-${app.scanType}-scan.sh ${app.name}`],
570+
})
571+
child.job("upload-results", {
572+
stage: "report",
573+
script: ["upload-results.sh"],
574+
artifacts: { reports: { [app.scanType]: "results.json" } },
575+
})
576+
})
577+
})
578+
```
579+
580+
### Accessing Child Pipelines
581+
582+
Retrieve child pipeline configurations programmatically:
583+
584+
```ts
585+
// Get specific child pipeline
586+
const childConfig = config.getChildPipeline("security-scan")
587+
if (childConfig) {
588+
console.log(childConfig.jobs) // Access jobs map
589+
console.log(childConfig.stages) // Access stages array
590+
}
591+
592+
// Access all child pipelines
593+
const allChildren = config.childPipelines
594+
allChildren.forEach(([name, child]) => {
595+
console.log(`Child: ${name}, Jobs: ${child.builder.jobs.size}`)
596+
})
597+
```
598+
599+
### Visualization Integration
600+
601+
Child pipelines are automatically included in all visualization formats:
602+
603+
```ts
604+
// Mermaid diagram shows child pipelines as subgraphs
605+
const mermaid = config.generateMermaidDiagram()
606+
607+
// ASCII tree shows child pipelines with 🔀 indicator
608+
const ascii = config.generateAsciiTree()
609+
610+
// Stage table separates child pipelines with headers
611+
const table = config.generateStageTable()
612+
```
613+
463614
## Visualization
464615

465616
The builder provides powerful visualization tools to help understand and document your pipeline structure. You can generate Mermaid diagrams, ASCII trees, and stage tables that show job relationships, inheritance chains, and stage organization.

package.json

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -71,37 +71,36 @@
7171
"commander": "^14.0.2",
7272
"deepmerge": "4.3.1",
7373
"js-yaml": "4.1.1",
74-
"oo-ascii-tree": "^1.120.0",
74+
"oo-ascii-tree": "^1.121.0",
7575
"read-pkg": "^10.0.0",
7676
"tinyglobby": "0.2.15",
7777
"typescript": "5.9.3",
7878
"zod": "4.1.13"
7979
},
8080
"devDependencies": {
81-
"@changesets/cli": "2.29.7",
81+
"@changesets/cli": "2.29.8",
8282
"@eslint/compat": "2.0.0",
83-
"@eslint/js": "9.39.1",
83+
"@eslint/js": "9.39.2",
8484
"@ianvs/prettier-plugin-sort-imports": "4.7.0",
8585
"@types/js-yaml": "4.0.9",
86-
"@types/node": "22.19.1",
87-
"@vitest/coverage-v8": "4.0.13",
88-
"dedent": "^1.7.0",
89-
"eslint": "9.39.1",
90-
"eslint-config-turbo": "2.6.1",
86+
"@types/node": "24.10.4",
87+
"@vitest/coverage-v8": "4.0.16",
88+
"dedent": "^1.7.1",
89+
"eslint": "9.39.2",
90+
"eslint-config-turbo": "2.6.3",
9191
"eslint-plugin-import": "2.32.0",
9292
"eslint-plugin-package-json": "0.85.0",
9393
"json-schema-to-typescript": "15.0.4",
94-
"jsonc-eslint-parser": "2.4.1",
95-
"memfs": "4.51.0",
96-
"msw": "^2.12.3",
97-
"prettier": "3.6.2",
98-
"tsdown": "0.16.6",
99-
"tsx": "4.20.6",
100-
"typescript-eslint": "8.47.0",
101-
"vite-tsconfig-paths": "^5.1.4",
102-
"vitest": "4.0.13"
94+
"jsonc-eslint-parser": "2.4.2",
95+
"memfs": "4.51.1",
96+
"msw": "^2.12.4",
97+
"prettier": "3.7.4",
98+
"tsdown": "0.18.1",
99+
"tsx": "4.21.0",
100+
"typescript-eslint": "8.50.0",
101+
"vitest": "4.0.16"
103102
},
104-
"packageManager": "pnpm@10.23.0",
103+
"packageManager": "pnpm@10.26.0",
105104
"engines": {
106105
"node": ">=22"
107106
},

0 commit comments

Comments
 (0)