@synergenius/flowweaver-pack-gitlab-ci 0.1.2 → 0.1.4
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/target.d.ts +16 -3
- package/dist/target.js +165 -55
- package/package.json +4 -4
package/dist/target.d.ts
CHANGED
|
@@ -17,6 +17,8 @@ import type { ExportOptions, ExportArtifacts, DeployInstructions } from '@synerg
|
|
|
17
17
|
export declare class GitLabCITarget extends BaseCICDTarget {
|
|
18
18
|
readonly name = "gitlab-ci";
|
|
19
19
|
readonly description = "GitLab CI/CD pipeline (.gitlab-ci.yml)";
|
|
20
|
+
/** Accumulated warnings for the current export run */
|
|
21
|
+
private _warnings;
|
|
20
22
|
readonly deploySchema: {
|
|
21
23
|
runner: {
|
|
22
24
|
type: "string";
|
|
@@ -42,14 +44,25 @@ export declare class GitLabCITarget extends BaseCICDTarget {
|
|
|
42
44
|
getDeployInstructions(_artifacts: ExportArtifacts): DeployInstructions;
|
|
43
45
|
private renderPipelineYAML;
|
|
44
46
|
/**
|
|
45
|
-
* Derive stages from job dependency
|
|
46
|
-
*
|
|
47
|
+
* Derive stages from @stage annotations or job dependency depth.
|
|
48
|
+
*
|
|
49
|
+
* When @stage annotations exist, jobs are grouped into named stages:
|
|
50
|
+
* - Jobs with an explicit `stage` field (set by buildJobGraph from @stage/@job)
|
|
51
|
+
* use that stage name directly.
|
|
52
|
+
* - The returned list preserves @stage declaration order.
|
|
53
|
+
*
|
|
54
|
+
* Without @stage annotations, falls back to using each job ID as its own stage
|
|
55
|
+
* (ordered by dependency).
|
|
47
56
|
*/
|
|
48
57
|
private deriveStages;
|
|
49
58
|
/**
|
|
50
|
-
* Derive default image from @deploy annotations or built-in mappings.
|
|
59
|
+
* Derive default image from @runner annotation, @deploy annotations, or built-in mappings.
|
|
51
60
|
*/
|
|
52
61
|
private deriveDefaultImage;
|
|
62
|
+
/**
|
|
63
|
+
* Map GitHub-style runner labels to Docker images.
|
|
64
|
+
*/
|
|
65
|
+
private mapRunnerToImage;
|
|
53
66
|
/**
|
|
54
67
|
* Convert CI/CD triggers to GitLab CI workflow rules.
|
|
55
68
|
*/
|
package/dist/target.js
CHANGED
|
@@ -20,6 +20,8 @@ import * as path from 'path';
|
|
|
20
20
|
export class GitLabCITarget extends BaseCICDTarget {
|
|
21
21
|
name = 'gitlab-ci';
|
|
22
22
|
description = 'GitLab CI/CD pipeline (.gitlab-ci.yml)';
|
|
23
|
+
/** Accumulated warnings for the current export run */
|
|
24
|
+
_warnings = [];
|
|
23
25
|
deploySchema = {
|
|
24
26
|
runner: { type: 'string', description: 'Default Docker image', default: 'ubuntu:latest' },
|
|
25
27
|
};
|
|
@@ -29,6 +31,7 @@ export class GitLabCITarget extends BaseCICDTarget {
|
|
|
29
31
|
label: { type: 'string', description: 'Step display name' },
|
|
30
32
|
};
|
|
31
33
|
async generate(options) {
|
|
34
|
+
this._warnings = [];
|
|
32
35
|
const filePath = path.resolve(options.sourceFile);
|
|
33
36
|
const outputDir = path.resolve(options.outputDir);
|
|
34
37
|
// Parse the workflow file to get AST
|
|
@@ -64,11 +67,18 @@ export class GitLabCITarget extends BaseCICDTarget {
|
|
|
64
67
|
files.push(this.createFile(outputDir, 'SECRETS_SETUP.md', secretsDoc, 'other'));
|
|
65
68
|
}
|
|
66
69
|
}
|
|
70
|
+
// Warn about @concurrency (no direct GitLab equivalent)
|
|
71
|
+
for (const ast of targetWorkflows) {
|
|
72
|
+
if (ast.options?.cicd?.concurrency) {
|
|
73
|
+
this._warnings.push(`@concurrency: GitLab CI has no direct concurrency group equivalent. Use resource_group for serial execution or interruptible for auto-cancellation.`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
67
76
|
return {
|
|
68
77
|
files,
|
|
69
78
|
target: this.name,
|
|
70
79
|
workflowName: options.displayName || targetWorkflows[0].name,
|
|
71
80
|
entryPoint: files[0].relativePath,
|
|
81
|
+
warnings: this._warnings.length > 0 ? this._warnings : undefined,
|
|
72
82
|
};
|
|
73
83
|
}
|
|
74
84
|
getDeployInstructions(_artifacts) {
|
|
@@ -99,14 +109,40 @@ export class GitLabCITarget extends BaseCICDTarget {
|
|
|
99
109
|
// ---------------------------------------------------------------------------
|
|
100
110
|
renderPipelineYAML(ast, jobs) {
|
|
101
111
|
const doc = {};
|
|
102
|
-
//
|
|
103
|
-
const
|
|
112
|
+
// include: directive (from @includes)
|
|
113
|
+
const includes = ast.options?.cicd?.includes;
|
|
114
|
+
if (includes && includes.length > 0) {
|
|
115
|
+
doc.include = includes.map(inc => {
|
|
116
|
+
switch (inc.type) {
|
|
117
|
+
case 'local': return { local: inc.file };
|
|
118
|
+
case 'template': return { template: inc.file };
|
|
119
|
+
case 'remote': return { remote: inc.file };
|
|
120
|
+
case 'project': {
|
|
121
|
+
const obj = { project: inc.project || '', file: inc.file };
|
|
122
|
+
if (inc.ref)
|
|
123
|
+
obj.ref = inc.ref;
|
|
124
|
+
return obj;
|
|
125
|
+
}
|
|
126
|
+
default: return { local: inc.file };
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
// stages (from @stage annotations or derived from dependency depth)
|
|
131
|
+
const stages = this.deriveStages(jobs, ast);
|
|
104
132
|
doc.stages = stages;
|
|
105
133
|
// Default image
|
|
106
134
|
const defaultImage = this.deriveDefaultImage(ast, jobs);
|
|
107
135
|
if (defaultImage) {
|
|
108
136
|
doc.default = { image: defaultImage };
|
|
109
137
|
}
|
|
138
|
+
// Workflow-level variables
|
|
139
|
+
if (ast.options?.cicd?.variables && Object.keys(ast.options.cicd.variables).length > 0) {
|
|
140
|
+
doc.variables = { ...ast.options.cicd.variables };
|
|
141
|
+
}
|
|
142
|
+
// Workflow-level before_script
|
|
143
|
+
if (ast.options?.cicd?.beforeScript && ast.options.cicd.beforeScript.length > 0) {
|
|
144
|
+
doc.before_script = ast.options.cicd.beforeScript;
|
|
145
|
+
}
|
|
110
146
|
// Workflow-level rules (from triggers)
|
|
111
147
|
const rules = this.renderWorkflowRules(ast.options?.cicd?.triggers || []);
|
|
112
148
|
if (rules.length > 0) {
|
|
@@ -123,29 +159,32 @@ export class GitLabCITarget extends BaseCICDTarget {
|
|
|
123
159
|
});
|
|
124
160
|
}
|
|
125
161
|
/**
|
|
126
|
-
* Derive stages from job dependency
|
|
127
|
-
*
|
|
162
|
+
* Derive stages from @stage annotations or job dependency depth.
|
|
163
|
+
*
|
|
164
|
+
* When @stage annotations exist, jobs are grouped into named stages:
|
|
165
|
+
* - Jobs with an explicit `stage` field (set by buildJobGraph from @stage/@job)
|
|
166
|
+
* use that stage name directly.
|
|
167
|
+
* - The returned list preserves @stage declaration order.
|
|
168
|
+
*
|
|
169
|
+
* Without @stage annotations, falls back to using each job ID as its own stage
|
|
170
|
+
* (ordered by dependency).
|
|
128
171
|
*/
|
|
129
|
-
deriveStages(jobs) {
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
return jobId;
|
|
172
|
+
deriveStages(jobs, ast) {
|
|
173
|
+
const declaredStages = ast?.options?.cicd?.stages;
|
|
174
|
+
// If @stage annotations exist, use them
|
|
175
|
+
if (declaredStages && declaredStages.length > 0) {
|
|
176
|
+
const stageNames = declaredStages.map(s => s.name);
|
|
177
|
+
// Collect any stages referenced by jobs that aren't in the declared list
|
|
178
|
+
for (const job of jobs) {
|
|
179
|
+
if (job.stage && !stageNames.includes(job.stage)) {
|
|
180
|
+
stageNames.push(job.stage);
|
|
181
|
+
}
|
|
140
182
|
}
|
|
141
|
-
|
|
142
|
-
const depStages = job.needs.map((dep) => getStage(dep, jobs));
|
|
143
|
-
assigned.set(jobId, jobId);
|
|
144
|
-
return jobId;
|
|
183
|
+
return stageNames;
|
|
145
184
|
}
|
|
146
|
-
//
|
|
185
|
+
// Fallback: use job IDs as stage names, ordered by dependency
|
|
186
|
+
const stages = [];
|
|
147
187
|
for (const job of jobs) {
|
|
148
|
-
getStage(job.id, jobs);
|
|
149
188
|
if (!stages.includes(job.id)) {
|
|
150
189
|
stages.push(job.id);
|
|
151
190
|
}
|
|
@@ -153,10 +192,15 @@ export class GitLabCITarget extends BaseCICDTarget {
|
|
|
153
192
|
return stages;
|
|
154
193
|
}
|
|
155
194
|
/**
|
|
156
|
-
* Derive default image from @deploy annotations or built-in mappings.
|
|
195
|
+
* Derive default image from @runner annotation, @deploy annotations, or built-in mappings.
|
|
157
196
|
*/
|
|
158
|
-
deriveDefaultImage(
|
|
159
|
-
// Check
|
|
197
|
+
deriveDefaultImage(ast, jobs) {
|
|
198
|
+
// Check @runner annotation first
|
|
199
|
+
const runner = ast.options?.cicd?.runner;
|
|
200
|
+
if (runner) {
|
|
201
|
+
return this.mapRunnerToImage(runner);
|
|
202
|
+
}
|
|
203
|
+
// Fall back to NODE_ACTION_MAP step images
|
|
160
204
|
for (const job of jobs) {
|
|
161
205
|
for (const step of job.steps) {
|
|
162
206
|
const mapping = this.resolveActionMapping(step, 'gitlab-ci');
|
|
@@ -166,6 +210,17 @@ export class GitLabCITarget extends BaseCICDTarget {
|
|
|
166
210
|
}
|
|
167
211
|
return undefined;
|
|
168
212
|
}
|
|
213
|
+
/**
|
|
214
|
+
* Map GitHub-style runner labels to Docker images.
|
|
215
|
+
*/
|
|
216
|
+
mapRunnerToImage(runner) {
|
|
217
|
+
const imageMap = {
|
|
218
|
+
'ubuntu-latest': 'ubuntu:latest',
|
|
219
|
+
'ubuntu-22.04': 'ubuntu:22.04',
|
|
220
|
+
'ubuntu-20.04': 'ubuntu:20.04',
|
|
221
|
+
};
|
|
222
|
+
return imageMap[runner] || runner;
|
|
223
|
+
}
|
|
169
224
|
/**
|
|
170
225
|
* Convert CI/CD triggers to GitLab CI workflow rules.
|
|
171
226
|
*/
|
|
@@ -215,24 +270,72 @@ export class GitLabCITarget extends BaseCICDTarget {
|
|
|
215
270
|
}
|
|
216
271
|
renderJob(job, ast, stages) {
|
|
217
272
|
const jobObj = {};
|
|
218
|
-
//
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
jobObj.
|
|
273
|
+
// extends (from @job extends=".template-name")
|
|
274
|
+
if (job.extends) {
|
|
275
|
+
jobObj.extends = job.extends;
|
|
276
|
+
}
|
|
277
|
+
// stage (use explicit stage from @stage assignment, or fall back to job ID)
|
|
278
|
+
jobObj.stage = job.stage || job.id;
|
|
279
|
+
// image (from per-job runner override via @job X runner=Y)
|
|
280
|
+
if (job.runner) {
|
|
281
|
+
jobObj.image = this.mapRunnerToImage(job.runner);
|
|
282
|
+
}
|
|
283
|
+
// tags (from @job tags or @tags)
|
|
284
|
+
if (job.tags && job.tags.length > 0) {
|
|
285
|
+
jobObj.tags = job.tags;
|
|
286
|
+
}
|
|
287
|
+
// matrix strategy (parallel: matrix:)
|
|
288
|
+
if (job.matrix) {
|
|
289
|
+
const matrixEntries = [];
|
|
290
|
+
if (job.matrix.dimensions && Object.keys(job.matrix.dimensions).length > 0) {
|
|
291
|
+
matrixEntries.push(job.matrix.dimensions);
|
|
292
|
+
}
|
|
293
|
+
if (job.matrix.include) {
|
|
294
|
+
for (const inc of job.matrix.include) {
|
|
295
|
+
matrixEntries.push(Object.fromEntries(Object.entries(inc).map(([k, v]) => [k, [v]])));
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
if (matrixEntries.length > 0) {
|
|
299
|
+
jobObj.parallel = { matrix: matrixEntries };
|
|
300
|
+
}
|
|
231
301
|
}
|
|
232
302
|
// needs (for DAG mode instead of stage-based ordering)
|
|
233
303
|
if (job.needs.length > 0) {
|
|
234
304
|
jobObj.needs = job.needs;
|
|
235
305
|
}
|
|
306
|
+
// retry (from @job retry)
|
|
307
|
+
if (job.retry !== undefined) {
|
|
308
|
+
jobObj.retry = { max: job.retry };
|
|
309
|
+
}
|
|
310
|
+
// allow_failure (from @job allow_failure)
|
|
311
|
+
if (job.allowFailure) {
|
|
312
|
+
jobObj.allow_failure = true;
|
|
313
|
+
}
|
|
314
|
+
// timeout (from @job timeout)
|
|
315
|
+
if (job.timeout) {
|
|
316
|
+
jobObj.timeout = job.timeout;
|
|
317
|
+
}
|
|
318
|
+
// rules (from @job rules)
|
|
319
|
+
if (job.rules && job.rules.length > 0) {
|
|
320
|
+
jobObj.rules = job.rules.map(rule => {
|
|
321
|
+
const ruleObj = {};
|
|
322
|
+
if (rule.if)
|
|
323
|
+
ruleObj.if = rule.if;
|
|
324
|
+
if (rule.when)
|
|
325
|
+
ruleObj.when = rule.when;
|
|
326
|
+
if (rule.allowFailure)
|
|
327
|
+
ruleObj.allow_failure = true;
|
|
328
|
+
if (rule.changes)
|
|
329
|
+
ruleObj.changes = rule.changes;
|
|
330
|
+
if (rule.variables)
|
|
331
|
+
ruleObj.variables = rule.variables;
|
|
332
|
+
return ruleObj;
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
// coverage (from @job coverage)
|
|
336
|
+
if (job.coverage) {
|
|
337
|
+
jobObj.coverage = job.coverage;
|
|
338
|
+
}
|
|
236
339
|
// environment
|
|
237
340
|
if (job.environment) {
|
|
238
341
|
const envConfig = ast.options?.cicd?.environments?.find((e) => e.name === job.environment);
|
|
@@ -242,7 +345,6 @@ export class GitLabCITarget extends BaseCICDTarget {
|
|
|
242
345
|
if (envConfig?.reviewers)
|
|
243
346
|
envObj.deployment_tier = 'production';
|
|
244
347
|
jobObj.environment = envObj;
|
|
245
|
-
// Protected environments require manual approval in GitLab
|
|
246
348
|
if (envConfig?.reviewers) {
|
|
247
349
|
jobObj.when = 'manual';
|
|
248
350
|
}
|
|
@@ -251,46 +353,54 @@ export class GitLabCITarget extends BaseCICDTarget {
|
|
|
251
353
|
if (job.services && job.services.length > 0) {
|
|
252
354
|
jobObj.services = job.services.map((svc) => {
|
|
253
355
|
const svcObj = { name: svc.image };
|
|
254
|
-
if (svc.ports) {
|
|
255
|
-
// GitLab services expose the first port automatically
|
|
256
|
-
// Additional port mapping needs alias
|
|
257
|
-
}
|
|
258
356
|
return svcObj;
|
|
259
357
|
});
|
|
260
358
|
}
|
|
261
|
-
// variables (
|
|
359
|
+
// variables (merge secrets + job-level variables)
|
|
360
|
+
const variables = {};
|
|
262
361
|
if (job.secrets.length > 0) {
|
|
263
|
-
// In GitLab, CI/CD variables are automatically available
|
|
264
|
-
// But we document them for clarity
|
|
265
|
-
const variables = {};
|
|
266
362
|
for (const secret of job.secrets) {
|
|
267
363
|
variables[secret] = `$${secret}`;
|
|
268
364
|
}
|
|
365
|
+
}
|
|
366
|
+
if (job.variables) {
|
|
367
|
+
Object.assign(variables, job.variables);
|
|
368
|
+
}
|
|
369
|
+
if (Object.keys(variables).length > 0) {
|
|
269
370
|
jobObj.variables = variables;
|
|
270
371
|
}
|
|
372
|
+
// before_script (from @job or @before_script)
|
|
373
|
+
if (job.beforeScript && job.beforeScript.length > 0) {
|
|
374
|
+
jobObj.before_script = job.beforeScript;
|
|
375
|
+
}
|
|
271
376
|
// cache
|
|
272
377
|
if (job.cache) {
|
|
273
378
|
jobObj.cache = this.renderCache(job.cache);
|
|
274
379
|
}
|
|
275
|
-
// artifacts (upload)
|
|
380
|
+
// artifacts (upload + reports)
|
|
381
|
+
const artifactsObj = {};
|
|
276
382
|
if (job.uploadArtifacts && job.uploadArtifacts.length > 0) {
|
|
277
|
-
|
|
383
|
+
artifactsObj.paths = job.uploadArtifacts.map((a) => a.path);
|
|
278
384
|
const expiry = job.uploadArtifacts[0].retention
|
|
279
385
|
? `${job.uploadArtifacts[0].retention} days`
|
|
280
386
|
: '1 week';
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
};
|
|
387
|
+
artifactsObj.expire_in = expiry;
|
|
388
|
+
}
|
|
389
|
+
if (job.reports && job.reports.length > 0) {
|
|
390
|
+
const reports = {};
|
|
391
|
+
for (const report of job.reports) {
|
|
392
|
+
reports[report.type] = report.path;
|
|
393
|
+
}
|
|
394
|
+
artifactsObj.reports = reports;
|
|
395
|
+
}
|
|
396
|
+
if (Object.keys(artifactsObj).length > 0) {
|
|
397
|
+
jobObj.artifacts = artifactsObj;
|
|
285
398
|
}
|
|
286
399
|
// script (the actual steps)
|
|
287
400
|
const script = [];
|
|
288
|
-
// Download artifacts (GitLab handles this automatically via `needs:`)
|
|
289
|
-
// but we add a comment for clarity if explicit artifacts are expected
|
|
290
401
|
if (job.downloadArtifacts && job.downloadArtifacts.length > 0) {
|
|
291
402
|
script.push(`# Artifacts from: ${job.downloadArtifacts.join(', ')} (downloaded automatically via needs:)`);
|
|
292
403
|
}
|
|
293
|
-
// Step scripts
|
|
294
404
|
for (const step of job.steps) {
|
|
295
405
|
const stepScript = this.renderStepScript(step);
|
|
296
406
|
script.push(...stepScript);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@synergenius/flowweaver-pack-gitlab-ci",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "GitLab CI/CD export target for Flow Weaver",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"flowweaver-marketplace-pack",
|
|
@@ -35,9 +35,9 @@
|
|
|
35
35
|
"prepublishOnly": "npm run build"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"
|
|
39
|
-
"@
|
|
40
|
-
"
|
|
38
|
+
"@synergenius/flow-weaver": "^0.17.4",
|
|
39
|
+
"@types/node": "^20.0.0",
|
|
40
|
+
"typescript": "^5.0.0"
|
|
41
41
|
},
|
|
42
42
|
"license": "SEE LICENSE IN LICENSE",
|
|
43
43
|
"repository": {
|