Skip to content

Commit 6c9d68b

Browse files
committed
fix import issues
1 parent 491ec44 commit 6c9d68b

File tree

6 files changed

+137
-13
lines changed

6 files changed

+137
-13
lines changed

.changeset/floppy-ants-tell.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@noxify/gitlab-ci-builder": patch
3+
---
4+
5+
exclude anchor definitions without a valid job definition

README.md

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,89 @@ await importYamlFile(".gitlab-ci.yml", "gitlab-ci.config.ts")
137137

138138
This enables easy migration from YAML to TypeScript-based configurations.
139139

140+
#### YAML Anchor Handling & Extends
141+
142+
The import functionality handles both GitLab CI's native `extends` keyword and YAML anchors/aliases:
143+
144+
##### Using `extends` (Recommended)
145+
146+
When your YAML uses GitLab's `extends` keyword, the import preserves the reference:
147+
148+
```yaml
149+
.base:
150+
image: node:22
151+
tags:
152+
- docker
153+
154+
build:
155+
extends: .base # GitLab CI extends
156+
script:
157+
- npm run build
158+
```
159+
160+
Generated output uses `extends` property:
161+
162+
```ts
163+
config.template(".base", {
164+
image: "node:22",
165+
tags: ["docker"],
166+
})
167+
168+
config.job("build", {
169+
extends: ".base", // Preserved!
170+
script: ["npm run build"],
171+
})
172+
173+
// Or use the extends() helper method:
174+
config.extends(".base", "build", {
175+
script: ["npm run build"],
176+
})
177+
```
178+
179+
Both approaches produce equivalent output. The `extends()` helper is more concise when you want to explicitly show the inheritance relationship.
180+
181+
##### Using YAML Anchors & Merges
182+
183+
When using YAML merge operators (`<<: *anchor`), values are resolved and inlined:
184+
185+
- **Anchor definitions** (`&anchor_name`) containing only primitive values (arrays, strings) are filtered out
186+
- **References** (`*anchor_name`) and **merges** (`<<: *anchor_name`) are resolved by the YAML parser and inlined
187+
- Only anchor definitions that are valid job/template objects are included as templates
188+
189+
```yaml
190+
.tags_test: &tags_test # Filtered out (array-only anchor)
191+
- test1
192+
- test2
193+
194+
.base: &base_config # Included (valid template)
195+
image: node:22
196+
tags:
197+
- docker
198+
199+
build:
200+
<<: *base_config # Values merged inline
201+
tags: *tags_test # Reference resolved
202+
script:
203+
- npm run build
204+
```
205+
206+
Generated output has resolved values:
207+
208+
```ts
209+
config.template(".base", {
210+
image: "node:22",
211+
tags: ["docker"],
212+
})
213+
214+
config.job("build", {
215+
image: "node:22", // Inlined from .base
216+
tags: ["test1", "test2"], // Resolved from .tags_test
217+
script: ["npm run build"],
218+
})
219+
```
220+
221+
**Recommendation:** Use GitLab's `extends` keyword instead of YAML merge operators to maintain clearer relationships in the generated TypeScript code.
222+
140223
## API Reference
141224

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

pnpm-lock.yaml

Lines changed: 19 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/import.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,17 @@ export function fromYaml(yamlContent: string): string {
9191
}
9292

9393
// Jobs (separate templates and regular jobs)
94-
const templateKeys = Object.keys(jobs).filter((k) => k.startsWith("."))
95-
const jobKeys = Object.keys(jobs).filter((k) => !k.startsWith("."))
94+
// Filter out anchor-only definitions (non-object values like arrays or strings)
95+
const isValidJobDefinition = (value: unknown): value is Record<string, unknown> => {
96+
return typeof value === "object" && value !== null && !Array.isArray(value)
97+
}
98+
99+
const templateKeys = Object.keys(jobs).filter(
100+
(k) => k.startsWith(".") && isValidJobDefinition(jobs[k]),
101+
)
102+
const jobKeys = Object.keys(jobs).filter(
103+
(k) => !k.startsWith(".") && isValidJobDefinition(jobs[k]),
104+
)
96105

97106
// Templates first
98107
for (const key of templateKeys) {

src/types/main.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ interface GitLabCi {
4747
* @see https://docs.gitlab.com/ee/ci/yaml/#tags
4848
*/
4949
tags?: string[]
50-
cache?: CacheDefinition
50+
cache?: CacheDefinition | CacheDefinition[]
5151
artifacts?: ArtifactsDefinition
5252
retry?: RetryDefinition
5353
/**

tests/import.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,10 @@ deploy-job:
305305

306306
it("should handle anchors", () => {
307307
const yaml = `
308+
.tags_test: &tags_test
309+
- test1
310+
- test2
311+
308312
.job_template: &job_configuration
309313
script:
310314
- test project
@@ -334,8 +338,22 @@ test:mysql:
334338

335339
const ts = fromYaml(yaml)
336340

341+
// Anchor-only definitions (.tags_test) should be ignored
342+
expect(ts).not.toContain('config.template(".tags_test"')
343+
344+
// Templates with actual job definitions should be included
345+
expect(ts).toContain('config.template(".postgres_services"')
346+
expect(ts).toContain('config.template(".mysql_services"')
347+
expect(ts).toContain('config.template(".job_template"')
348+
349+
// Jobs should reference the merged anchor values
350+
expect(ts).toContain('config.job("test:postgres"')
351+
expect(ts).toContain('config.job("test:mysql"')
337352
expect(ts).toContain('services: ["postgres", "ruby"]')
338353
expect(ts).toContain('services: ["mysql", "ruby"]')
354+
355+
// Merged script from anchor should be present
356+
expect(ts).toContain('"test project"')
339357
})
340358
})
341359
})

0 commit comments

Comments
 (0)