@intentius/chant-lexicon-gitlab 0.101.0 → 0.104.0
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.
- package/dist/codegen/naming.d.ts.map +1 -1
- package/dist/components/generate-pipeline.d.ts.map +1 -1
- package/dist/composites/node-pipeline.d.ts +16 -16
- package/dist/composites/node-pipeline.d.ts.map +1 -1
- package/dist/generated/index.d.ts +19 -19
- package/dist/generated/index.d.ts.map +1 -1
- package/dist/import/generator.d.ts.map +1 -1
- package/dist/import/parser.d.ts.map +1 -1
- package/dist/integrity.json +10 -10
- package/dist/intrinsics.d.ts.map +1 -1
- package/dist/lint/post-synth/wgl013.d.ts +3 -1
- package/dist/lint/post-synth/wgl013.d.ts.map +1 -1
- package/dist/lint/post-synth/wgl017.d.ts.map +1 -1
- package/dist/lint/post-synth/wgl020.d.ts.map +1 -1
- package/dist/lint/post-synth/wgl021.d.ts.map +1 -1
- package/dist/lint/post-synth/wgl050.d.ts.map +1 -1
- package/dist/lint/post-synth/yaml-helpers.d.ts +57 -8
- package/dist/lint/post-synth/yaml-helpers.d.ts.map +1 -1
- package/dist/manifest.json +1 -1
- package/dist/migrate/from-github/provenance.d.ts.map +1 -1
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/rules/wgl013.ts +4 -2
- package/dist/rules/wgl017.ts +3 -11
- package/dist/rules/wgl020.ts +4 -5
- package/dist/rules/wgl021.ts +4 -5
- package/dist/rules/wgl048.ts +2 -2
- package/dist/rules/wgl049.ts +4 -4
- package/dist/rules/wgl050.ts +5 -8
- package/dist/rules/yaml-helpers.ts +176 -113
- package/dist/variables.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/components/__fixtures__/pr-loop.gitlab.golden.yml +43 -0
- package/src/components/generate-op-pipeline.ts +4 -4
- package/src/components/generate-pipeline.test.ts +33 -0
- package/src/components/generate-pipeline.ts +117 -11
- package/src/components/generate-pr-pipeline.test.ts +70 -0
- package/src/lint/post-synth/post-synth.test.ts +29 -0
- package/src/lint/post-synth/wgl013.test.ts +53 -0
- package/src/lint/post-synth/wgl013.ts +4 -2
- package/src/lint/post-synth/wgl014.test.ts +23 -0
- package/src/lint/post-synth/wgl015.test.ts +35 -0
- package/src/lint/post-synth/wgl017.test.ts +14 -0
- package/src/lint/post-synth/wgl017.ts +3 -11
- package/src/lint/post-synth/wgl020.test.ts +26 -0
- package/src/lint/post-synth/wgl020.ts +4 -5
- package/src/lint/post-synth/wgl021.test.ts +29 -0
- package/src/lint/post-synth/wgl021.ts +4 -5
- package/src/lint/post-synth/wgl028.test.ts +27 -0
- package/src/lint/post-synth/wgl042.test.ts +14 -0
- package/src/lint/post-synth/wgl048.test.ts +14 -0
- package/src/lint/post-synth/wgl048.ts +2 -2
- package/src/lint/post-synth/wgl049.test.ts +25 -0
- package/src/lint/post-synth/wgl049.ts +4 -4
- package/src/lint/post-synth/wgl050.test.ts +16 -0
- package/src/lint/post-synth/wgl050.ts +5 -8
- package/src/lint/post-synth/yaml-helpers.test.ts +249 -0
- package/src/lint/post-synth/yaml-helpers.ts +176 -113
- package/src/migrate/from-github/fixtures/syntax-mapping/03-matrix/expected.gitlab-ci.yml +3 -1
- package/src/serializer.ts +5 -5
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Helpers for parsing serialized GitLab CI YAML in post-synth checks.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Jobs, stages and global variables are read with the structural parser
|
|
5
|
+
* (`parseYAML`, js-yaml with chant's schema). Checks that pattern-match a
|
|
6
|
+
* job's text get it from {@link extractJobSection}, which cuts the document
|
|
7
|
+
* at top-level keys rather than at blank lines, so it does not depend on the
|
|
8
|
+
* serializer's spacing.
|
|
8
9
|
*/
|
|
9
10
|
|
|
11
|
+
import { parseYAML } from "@intentius/chant/yaml";
|
|
12
|
+
|
|
10
13
|
export { getPrimaryOutput } from "@intentius/chant/lint/post-synth";
|
|
11
14
|
|
|
12
15
|
/**
|
|
@@ -22,7 +25,15 @@ export interface ParsedJob {
|
|
|
22
25
|
name: string;
|
|
23
26
|
stage?: string;
|
|
24
27
|
rules?: ParsedRule[];
|
|
28
|
+
/**
|
|
29
|
+
* Same-pipeline jobs this job needs, in every form GitLab accepts: a plain
|
|
30
|
+
* name, `- job: name` (with `artifacts:`, `optional:`, `parallel:`), and
|
|
31
|
+
* `needs: []`. Cross-pipeline needs (`pipeline:` or `project:`) name jobs
|
|
32
|
+
* in another pipeline and are left out.
|
|
33
|
+
*/
|
|
25
34
|
needs?: string[];
|
|
35
|
+
/** The subset of {@link needs} declared `optional: true`. */
|
|
36
|
+
optionalNeeds?: string[];
|
|
26
37
|
extends?: string[];
|
|
27
38
|
}
|
|
28
39
|
|
|
@@ -32,104 +43,115 @@ export interface ParsedRule {
|
|
|
32
43
|
}
|
|
33
44
|
|
|
34
45
|
/**
|
|
35
|
-
*
|
|
46
|
+
* Top-level keys that are global keywords, not jobs. `image`, `services`,
|
|
47
|
+
* `cache`, `before_script` and `after_script` are the deprecated global
|
|
48
|
+
* forms of the `default:` keywords; `spec` is a CI component's header.
|
|
36
49
|
*/
|
|
37
|
-
export
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
50
|
+
export const RESERVED_TOP_LEVEL_KEYS: ReadonlySet<string> = new Set([
|
|
51
|
+
"stages",
|
|
52
|
+
"variables",
|
|
53
|
+
"default",
|
|
54
|
+
"include",
|
|
55
|
+
"workflow",
|
|
56
|
+
"image",
|
|
57
|
+
"services",
|
|
58
|
+
"cache",
|
|
59
|
+
"before_script",
|
|
60
|
+
"after_script",
|
|
61
|
+
"spec",
|
|
62
|
+
]);
|
|
63
|
+
|
|
64
|
+
/** Parse the pipeline document; `undefined` when the YAML cannot be read. */
|
|
65
|
+
export function parsePipeline(yaml: string): Record<string, unknown> | undefined {
|
|
66
|
+
try {
|
|
67
|
+
const doc = parseYAML(yaml);
|
|
68
|
+
return doc && typeof doc === "object" && !Array.isArray(doc) ? doc : undefined;
|
|
69
|
+
} catch {
|
|
70
|
+
return undefined;
|
|
45
71
|
}
|
|
46
|
-
return stages;
|
|
47
72
|
}
|
|
48
73
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
export function extractJobs(yaml: string): Map<string, ParsedJob> {
|
|
53
|
-
const jobs = new Map<string, ParsedJob>();
|
|
74
|
+
function isMapping(v: unknown): v is Record<string, unknown> {
|
|
75
|
+
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
76
|
+
}
|
|
54
77
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
78
|
+
function scalarString(v: unknown): string | undefined {
|
|
79
|
+
if (typeof v === "string") return v;
|
|
80
|
+
if (typeof v === "number" || typeof v === "boolean") return String(v);
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
60
83
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
84
|
+
/**
|
|
85
|
+
* Every job in the pipeline as `[id, rawJobObject]`: each top-level key whose
|
|
86
|
+
* value is a mapping, minus the global keywords. Hidden jobs (ids starting
|
|
87
|
+
* with `.`, used as `extends:` templates) are included.
|
|
88
|
+
*/
|
|
89
|
+
export function jobEntries(yaml: string): Array<[string, Record<string, unknown>]> {
|
|
90
|
+
const doc = parsePipeline(yaml);
|
|
91
|
+
if (!doc) return [];
|
|
92
|
+
const out: Array<[string, Record<string, unknown>]> = [];
|
|
93
|
+
for (const [name, val] of Object.entries(doc)) {
|
|
94
|
+
if (RESERVED_TOP_LEVEL_KEYS.has(name)) continue;
|
|
95
|
+
if (isMapping(val)) out.push([name, val]);
|
|
96
|
+
}
|
|
97
|
+
return out;
|
|
98
|
+
}
|
|
64
99
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
100
|
+
/**
|
|
101
|
+
* Extract stages list from serialized YAML (block or flow list).
|
|
102
|
+
*/
|
|
103
|
+
export function extractStages(yaml: string): string[] {
|
|
104
|
+
const stages = parsePipeline(yaml)?.stages;
|
|
105
|
+
if (!Array.isArray(stages)) return [];
|
|
106
|
+
return stages.map(scalarString).filter((s): s is string => s !== undefined);
|
|
107
|
+
}
|
|
68
108
|
|
|
109
|
+
/**
|
|
110
|
+
* Extract jobs with their stage, `needs:` and `extends:` from serialized YAML.
|
|
111
|
+
*
|
|
112
|
+
* Built on the structural parser, so every job id is read (capitals, spaces,
|
|
113
|
+
* colons, quoted ids) and every `needs:` entry is read whatever keys follow
|
|
114
|
+
* `job:` in its map. The line-based version this replaced skipped ids that
|
|
115
|
+
* start with a capital and stopped reading a `needs:` list at an
|
|
116
|
+
* `artifacts: true` line (#3256). YAML the parser cannot read gives no jobs.
|
|
117
|
+
*/
|
|
118
|
+
export function extractJobs(yaml: string): Map<string, ParsedJob> {
|
|
119
|
+
const jobs = new Map<string, ParsedJob>();
|
|
120
|
+
for (const [name, obj] of jobEntries(yaml)) {
|
|
69
121
|
const job: ParsedJob = { name };
|
|
70
122
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const val = extendsMatch[1].trim();
|
|
83
|
-
if (val.startsWith("[")) {
|
|
84
|
-
// Inline array: [.a, .b]
|
|
85
|
-
job.extends = val.slice(1, -1).split(",").map((s) => s.trim().replace(/^'|'$/g, ""));
|
|
86
|
-
} else {
|
|
87
|
-
job.extends = [val.replace(/^'|'$/g, "")];
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// needs: block (list form)
|
|
92
|
-
if (line.match(/^\s+needs:$/)) {
|
|
93
|
-
inNeeds = true;
|
|
94
|
-
job.needs = [];
|
|
95
|
-
continue;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
if (inNeeds) {
|
|
99
|
-
// - job-name (simple string form)
|
|
100
|
-
const simpleNeed = line.match(/^\s+- ([a-z][a-z0-9_.-]*)$/);
|
|
101
|
-
if (simpleNeed) {
|
|
102
|
-
job.needs!.push(simpleNeed[1]);
|
|
103
|
-
continue;
|
|
104
|
-
}
|
|
105
|
-
// - 'job-name' (quoted string form)
|
|
106
|
-
const quotedNeed = line.match(/^\s+- '([^']+)'$/);
|
|
107
|
-
if (quotedNeed) {
|
|
108
|
-
job.needs!.push(quotedNeed[1]);
|
|
109
|
-
continue;
|
|
110
|
-
}
|
|
111
|
-
// - job: job-name (object form)
|
|
112
|
-
const objectNeed = line.match(/^\s+- job:\s+(.+)$/);
|
|
113
|
-
if (objectNeed) {
|
|
114
|
-
job.needs!.push(objectNeed[1].trim().replace(/^'|'$/g, ""));
|
|
123
|
+
const stage = scalarString(obj.stage);
|
|
124
|
+
if (stage !== undefined) job.stage = stage;
|
|
125
|
+
|
|
126
|
+
if (obj.needs !== undefined && obj.needs !== null) {
|
|
127
|
+
const entries = Array.isArray(obj.needs) ? obj.needs : [obj.needs];
|
|
128
|
+
const needs: string[] = [];
|
|
129
|
+
const optional: string[] = [];
|
|
130
|
+
for (const entry of entries) {
|
|
131
|
+
const plain = scalarString(entry);
|
|
132
|
+
if (plain !== undefined) {
|
|
133
|
+
needs.push(plain);
|
|
115
134
|
continue;
|
|
116
135
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
136
|
+
if (!isMapping(entry)) continue;
|
|
137
|
+
// Cross-pipeline needs name a job in another pipeline.
|
|
138
|
+
if ("pipeline" in entry || "project" in entry) continue;
|
|
139
|
+
const target = scalarString(entry.job);
|
|
140
|
+
if (target === undefined) continue;
|
|
141
|
+
needs.push(target);
|
|
142
|
+
if (entry.optional === true) optional.push(target);
|
|
121
143
|
}
|
|
144
|
+
job.needs = needs;
|
|
145
|
+
if (optional.length > 0) job.optionalNeeds = optional;
|
|
146
|
+
}
|
|
122
147
|
|
|
123
|
-
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
job.needs = inlineNeeds[1].split(",").map((s) => s.trim().replace(/^'|'$/g, ""));
|
|
127
|
-
}
|
|
148
|
+
if (obj.extends !== undefined && obj.extends !== null) {
|
|
149
|
+
const ext = Array.isArray(obj.extends) ? obj.extends : [obj.extends];
|
|
150
|
+
job.extends = ext.map(scalarString).filter((s): s is string => s !== undefined);
|
|
128
151
|
}
|
|
129
152
|
|
|
130
153
|
jobs.set(name, job);
|
|
131
154
|
}
|
|
132
|
-
|
|
133
155
|
return jobs;
|
|
134
156
|
}
|
|
135
157
|
|
|
@@ -143,34 +165,83 @@ export function hasInclude(yaml: string): boolean {
|
|
|
143
165
|
}
|
|
144
166
|
|
|
145
167
|
/**
|
|
146
|
-
* Extract global variables from serialized YAML.
|
|
168
|
+
* Extract global variables from serialized YAML. A variable in the expanded
|
|
169
|
+
* form (`value:` / `description:` / `options:`) maps to its `value`.
|
|
147
170
|
*/
|
|
148
171
|
export function extractGlobalVariables(yaml: string): Map<string, string> {
|
|
149
172
|
const vars = new Map<string, string>();
|
|
150
|
-
const
|
|
151
|
-
if (!
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
if (kv) {
|
|
156
|
-
vars.set(kv[1], kv[2].trim().replace(/^['"]|['"]$/g, ""));
|
|
157
|
-
}
|
|
173
|
+
const variables = parsePipeline(yaml)?.variables;
|
|
174
|
+
if (!isMapping(variables)) return vars;
|
|
175
|
+
for (const [key, val] of Object.entries(variables)) {
|
|
176
|
+
const v = isMapping(val) ? scalarString(val.value) : scalarString(val);
|
|
177
|
+
vars.set(key, v ?? "");
|
|
158
178
|
}
|
|
159
179
|
return vars;
|
|
160
180
|
}
|
|
161
181
|
|
|
162
182
|
/**
|
|
163
|
-
*
|
|
183
|
+
* The key of a top-level mapping line (`build:`, `Build_Docs:`,
|
|
184
|
+
* `build:linux:`, `"deploy prod":`), unquoted, or undefined when the line
|
|
185
|
+
* does not start a top-level key.
|
|
164
186
|
*/
|
|
165
|
-
export function
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
return
|
|
187
|
+
export function topLevelKey(line: string): string | undefined {
|
|
188
|
+
if (line === "" || /^[\s#]/.test(line) || /^(---|\.\.\.)(\s|$)/.test(line) || line.startsWith("- ")) return undefined;
|
|
189
|
+
const dq = line.match(/^"((?:[^"\\]|\\.)*)"\s*:(?:\s|$)/);
|
|
190
|
+
if (dq) {
|
|
191
|
+
try {
|
|
192
|
+
return JSON.parse(`"${dq[1]}"`) as string;
|
|
193
|
+
} catch {
|
|
194
|
+
return dq[1];
|
|
171
195
|
}
|
|
172
196
|
}
|
|
173
|
-
|
|
197
|
+
const sq = line.match(/^'((?:[^']|'')*)'\s*:(?:\s|$)/);
|
|
198
|
+
if (sq) return sq[1].replace(/''/g, "'");
|
|
199
|
+
const plain = line.match(/^(.+?)\s*:(?:\s|$)/);
|
|
200
|
+
return plain ? plain[1] : undefined;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** A top-level section of the document: its key and its raw text. */
|
|
204
|
+
export interface TopLevelSection {
|
|
205
|
+
key: string;
|
|
206
|
+
text: string;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Split the document into top-level sections. A section starts at a line
|
|
211
|
+
* holding a top-level key and runs to the next one; blank lines and
|
|
212
|
+
* column-0 comments at its end are dropped. Unlike splitting on blank
|
|
213
|
+
* lines, this does not depend on the serializer's spacing.
|
|
214
|
+
*/
|
|
215
|
+
export function topLevelSections(yaml: string): TopLevelSection[] {
|
|
216
|
+
const lines = yaml.replace(/\r\n?/g, "\n").split("\n");
|
|
217
|
+
const out: TopLevelSection[] = [];
|
|
218
|
+
let key: string | undefined;
|
|
219
|
+
let start = 0;
|
|
220
|
+
const flush = (end: number) => {
|
|
221
|
+
if (key === undefined) return;
|
|
222
|
+
let last = end;
|
|
223
|
+
while (last > start + 1 && (lines[last - 1].trim() === "" || lines[last - 1].startsWith("#"))) last--;
|
|
224
|
+
out.push({ key, text: lines.slice(start, last).join("\n") });
|
|
225
|
+
};
|
|
226
|
+
for (let i = 0; i < lines.length; i++) {
|
|
227
|
+
const line = lines[i];
|
|
228
|
+
const k = topLevelKey(line);
|
|
229
|
+
const boundary = k !== undefined || /^(---|\.\.\.)(\s|$)/.test(line);
|
|
230
|
+
if (!boundary) continue;
|
|
231
|
+
flush(i);
|
|
232
|
+
key = k;
|
|
233
|
+
start = i;
|
|
234
|
+
}
|
|
235
|
+
flush(lines.length);
|
|
236
|
+
return out;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Extract the full section text for a given job name (exact key match, so
|
|
241
|
+
* `build` never returns the `build:linux` section).
|
|
242
|
+
*/
|
|
243
|
+
export function extractJobSection(yaml: string, jobName: string): string | null {
|
|
244
|
+
return topLevelSections(yaml).find((s) => s.key === jobName)?.text ?? null;
|
|
174
245
|
}
|
|
175
246
|
|
|
176
247
|
/**
|
|
@@ -241,10 +312,8 @@ export interface ImageRef {
|
|
|
241
312
|
*/
|
|
242
313
|
export function extractImageRefs(yaml: string): ImageRef[] {
|
|
243
314
|
const refs: ImageRef[] = [];
|
|
244
|
-
for (const section of yaml
|
|
315
|
+
for (const { key: job, text: section } of topLevelSections(yaml)) {
|
|
245
316
|
const lines = section.split("\n");
|
|
246
|
-
const top = lines[0]?.match(/^(\.?[a-z][a-z0-9_.-]*):/i);
|
|
247
|
-
const job = top ? top[1] : "default";
|
|
248
317
|
|
|
249
318
|
for (let i = 0; i < lines.length; i++) {
|
|
250
319
|
// image: as object (next indented `name:`) or inline string
|
|
@@ -285,7 +354,7 @@ export interface IncludeEntry {
|
|
|
285
354
|
*/
|
|
286
355
|
export function extractIncludes(yaml: string): IncludeEntry[] {
|
|
287
356
|
const entries: IncludeEntry[] = [];
|
|
288
|
-
const section = yaml
|
|
357
|
+
const section = topLevelSections(yaml).find((s) => s.key === "include")?.text;
|
|
289
358
|
if (!section) return entries;
|
|
290
359
|
|
|
291
360
|
// Inline string form: `include: <value>` (value on the same line, not a list)
|
|
@@ -342,11 +411,8 @@ export interface IdTokenDecl {
|
|
|
342
411
|
*/
|
|
343
412
|
export function extractIdTokens(yaml: string): IdTokenDecl[] {
|
|
344
413
|
const out: IdTokenDecl[] = [];
|
|
345
|
-
for (const section of yaml
|
|
414
|
+
for (const { key: job, text: section } of topLevelSections(yaml)) {
|
|
346
415
|
const lines = section.split("\n");
|
|
347
|
-
const top = lines[0]?.match(/^(\.?[a-z][a-z0-9_.-]*):/i);
|
|
348
|
-
if (!top) continue;
|
|
349
|
-
const job = top[1];
|
|
350
416
|
|
|
351
417
|
let inIdTokens = false;
|
|
352
418
|
let idTokensIndent = -1;
|
|
@@ -408,11 +474,8 @@ export interface ScriptCommand {
|
|
|
408
474
|
*/
|
|
409
475
|
export function extractScriptCommands(yaml: string): ScriptCommand[] {
|
|
410
476
|
const out: ScriptCommand[] = [];
|
|
411
|
-
for (const section of yaml
|
|
477
|
+
for (const { key: job, text: section } of topLevelSections(yaml)) {
|
|
412
478
|
const lines = section.split("\n");
|
|
413
|
-
const top = lines[0]?.match(/^(\.?[a-z][a-z0-9_.-]*):/i);
|
|
414
|
-
if (!top) continue;
|
|
415
|
-
const job = top[1];
|
|
416
479
|
|
|
417
480
|
let inScript = false;
|
|
418
481
|
let scriptIndent = -1;
|
package/src/serializer.ts
CHANGED
|
@@ -13,7 +13,7 @@ import type { Serializer } from "@intentius/chant/serializer";
|
|
|
13
13
|
import type { LexiconOutput } from "@intentius/chant/lexicon-output";
|
|
14
14
|
import { walkValue, type SerializerVisitor } from "@intentius/chant/serializer-walker";
|
|
15
15
|
import { INTRINSIC_MARKER } from "@intentius/chant/intrinsic";
|
|
16
|
-
import {
|
|
16
|
+
import { emitYAMLEntry } from "@intentius/chant/yaml";
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* GitLab CI visitor for the generic serializer walker.
|
|
@@ -129,7 +129,7 @@ export const gitlabSerializer: Serializer = {
|
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
131
|
if (stages.size > 0) {
|
|
132
|
-
sections.push("stages
|
|
132
|
+
sections.push(emitYAMLEntry("stages", [...stages]));
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
// Emit defaults
|
|
@@ -139,7 +139,7 @@ export const gitlabSerializer: Serializer = {
|
|
|
139
139
|
entityNames,
|
|
140
140
|
) as Record<string, unknown> | undefined;
|
|
141
141
|
if (converted) {
|
|
142
|
-
sections.push("default
|
|
142
|
+
sections.push(emitYAMLEntry("default", converted));
|
|
143
143
|
}
|
|
144
144
|
}
|
|
145
145
|
|
|
@@ -150,7 +150,7 @@ export const gitlabSerializer: Serializer = {
|
|
|
150
150
|
entityNames,
|
|
151
151
|
) as Record<string, unknown> | undefined;
|
|
152
152
|
if (converted) {
|
|
153
|
-
sections.push("workflow
|
|
153
|
+
sections.push(emitYAMLEntry("workflow", converted));
|
|
154
154
|
}
|
|
155
155
|
}
|
|
156
156
|
|
|
@@ -163,7 +163,7 @@ export const gitlabSerializer: Serializer = {
|
|
|
163
163
|
if (converted) {
|
|
164
164
|
// Convert job name from camelCase to kebab-case for YAML
|
|
165
165
|
const yamlName = name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
|
|
166
|
-
sections.push(
|
|
166
|
+
sections.push(emitYAMLEntry(yamlName, converted));
|
|
167
167
|
}
|
|
168
168
|
}
|
|
169
169
|
|