Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions .changeset/child-pipeline-visualization.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
---
"@noxify/gitlab-ci-builder": minor
---

Add child pipeline visualization and fluent API support

**New Features:**

- Added `childPipeline()` method to define child pipelines via callback API
- Added `writeYamlFiles()` method to automatically write parent and all child pipeline YAML files
- Child pipelines are now fully visualized in Mermaid diagrams, ASCII trees, and stage tables
- Child pipelines defined via callback are tracked and don't require filesystem access for visualization

**API Changes:**

- Added `ChildPipelineConfig` interface to track child pipeline configurations
- Extended `PipelineState` with `childPipelines` map and getter methods
- Added public getters to `ConfigBuilder`: `jobs`, `templates`, `stages`, `jobOptionsMap`
- Extended `VisualizationParams` with `trackedChildPipelines` parameter
- Enhanced `extractChildPipelines` to prioritize tracked configs over file system parsing

**Visualization Enhancements:**

- `generateMermaidDiagram` shows child pipelines as subgraphs with dotted trigger edges
- `generateAsciiTree` displays child pipelines with 🔀 indicator
- `generateStageTable` includes child pipeline jobs with separator rows and proper indentation
- Added `TriggerInfo` interface to track trigger configurations in `ExtendsGraphNode`
- Extended `buildExtendsGraph` to extract trigger information from job definitions

**Example:**

```typescript
config.childPipeline(
"trigger:deploy",
(child) => {
child.stages("deploy")
child.job("deploy:prod", { script: ["./deploy.sh"] })
return child
},
{
strategy: "depend",
outputPath: "ci/deploy-pipeline.yml",
},
)

await config.writeYamlFiles(".")
// Writes: .gitlab-ci.yml + ci/deploy-pipeline.yml
```
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
22
24
2 changes: 1 addition & 1 deletion .vscode/extensions.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@
"foxundermoon.shell-format",
"bierner.comment-tagged-templates"
]
}
}
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,4 @@
"files.associations": {
"*.css": "tailwindcss"
}
}
}
155 changes: 153 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,16 @@ types, proper extends resolution, and a simple builder surface.
## Features

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

## Limitations
Expand Down Expand Up @@ -460,6 +461,156 @@ export default function (config: ConfigBuilder) {

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

## Child Pipelines

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.

### Basic Example

```ts
import { ConfigBuilder } from "@noxify/gitlab-ci-builder"

const config = new ConfigBuilder().stages("build", "test", "deploy").job("build", {
stage: "build",
script: ["npm run build"],
})

// Define a child pipeline with callback
config.childPipeline("security-scan", (child) => {
child.stages("scan", "report")
child.job("sast", {
stage: "scan",
script: ["npm audit", "npm run security-scan"],
})
child.job("report", {
stage: "report",
script: ["npm run generate-report"],
})
})

// Write parent and all child pipeline files
const files = await config.writeYamlFiles("./pipelines")
// Returns: { parent: "pipelines/.gitlab-ci.yml", children: ["pipelines/security-scan-pipeline.yml"] }
```

### Advanced Configuration

Customize child pipeline behavior with options:

```ts
config.childPipeline(
"deploy-environments",
(child) => {
child.stages("dev", "staging", "prod")
child.job("deploy-dev", {
stage: "dev",
script: ["deploy.sh dev"],
})
child.job("deploy-staging", {
stage: "staging",
script: ["deploy.sh staging"],
when: "manual",
})
child.job("deploy-prod", {
stage: "prod",
script: ["deploy.sh production"],
when: "manual",
})
},
{
// Custom output path for child pipeline YAML
outputPath: "pipelines/deploy.yml",

// Strategy for parent pipeline to wait for child
strategy: "depend", // or "mirror"

// Forward variables to child pipeline
forward: {
pipelineVariables: true,
yamlVariables: ["CI_ENVIRONMENT", "DEPLOY_TOKEN"],
},

// Additional trigger job options
jobOptions: {
stage: "deploy",
rules: [{ if: "$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH" }],
},
},
)
```

### Dynamic Child Pipelines

Generate child pipelines dynamically based on runtime conditions:

```ts
interface Application {
name: string
enabled: boolean
scanType: string
}

const applications: Application[] = [
{ name: "web-app", enabled: true, scanType: "sast" },
{ name: "api-service", enabled: true, scanType: "dast" },
{ name: "mobile-app", enabled: false, scanType: "sast" },
]

const config = new ConfigBuilder().stages("scan")

// Generate child pipelines for enabled applications only
applications
.filter((app) => app.enabled)
.forEach((app) => {
config.childPipeline(`scan-${app.name}`, (child) => {
child.stages("prepare", "scan", "report")
child.job(`${app.scanType}-scan`, {
stage: "scan",
script: [`run-${app.scanType}-scan.sh ${app.name}`],
})
child.job("upload-results", {
stage: "report",
script: ["upload-results.sh"],
artifacts: { reports: { [app.scanType]: "results.json" } },
})
})
})
```

### Accessing Child Pipelines

Retrieve child pipeline configurations programmatically:

```ts
// Get specific child pipeline
const childConfig = config.getChildPipeline("security-scan")
if (childConfig) {
console.log(childConfig.jobs) // Access jobs map
console.log(childConfig.stages) // Access stages array
}

// Access all child pipelines
const allChildren = config.childPipelines
allChildren.forEach(([name, child]) => {
console.log(`Child: ${name}, Jobs: ${child.builder.jobs.size}`)
})
```

### Visualization Integration

Child pipelines are automatically included in all visualization formats:

```ts
// Mermaid diagram shows child pipelines as subgraphs
const mermaid = config.generateMermaidDiagram()

// ASCII tree shows child pipelines with 🔀 indicator
const ascii = config.generateAsciiTree()

// Stage table separates child pipelines with headers
const table = config.generateStageTable()
```

## Visualization

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.
Expand Down
35 changes: 17 additions & 18 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,37 +71,36 @@
"commander": "^14.0.2",
"deepmerge": "4.3.1",
"js-yaml": "4.1.1",
"oo-ascii-tree": "^1.120.0",
"oo-ascii-tree": "^1.121.0",
"read-pkg": "^10.0.0",
"tinyglobby": "0.2.15",
"typescript": "5.9.3",
"zod": "4.1.13"
},
"devDependencies": {
"@changesets/cli": "2.29.7",
"@changesets/cli": "2.29.8",
"@eslint/compat": "2.0.0",
"@eslint/js": "9.39.1",
"@eslint/js": "9.39.2",
"@ianvs/prettier-plugin-sort-imports": "4.7.0",
"@types/js-yaml": "4.0.9",
"@types/node": "22.19.1",
"@vitest/coverage-v8": "4.0.13",
"dedent": "^1.7.0",
"eslint": "9.39.1",
"eslint-config-turbo": "2.6.1",
"@types/node": "24.10.4",
"@vitest/coverage-v8": "4.0.16",
"dedent": "^1.7.1",
"eslint": "9.39.2",
"eslint-config-turbo": "2.6.3",
"eslint-plugin-import": "2.32.0",
"eslint-plugin-package-json": "0.85.0",
"json-schema-to-typescript": "15.0.4",
"jsonc-eslint-parser": "2.4.1",
"memfs": "4.51.0",
"msw": "^2.12.3",
"prettier": "3.6.2",
"tsdown": "0.16.6",
"tsx": "4.20.6",
"typescript-eslint": "8.47.0",
"vite-tsconfig-paths": "^5.1.4",
"vitest": "4.0.13"
"jsonc-eslint-parser": "2.4.2",
"memfs": "4.51.1",
"msw": "^2.12.4",
"prettier": "3.7.4",
"tsdown": "0.18.1",
"tsx": "4.21.0",
"typescript-eslint": "8.50.0",
"vitest": "4.0.16"
},
"packageManager": "pnpm@10.23.0",
"packageManager": "pnpm@10.26.0",
"engines": {
"node": ">=22"
},
Expand Down
Loading
Loading