@synergenius/flowweaver-pack-gitlab-ci 0.1.1 → 0.1.3
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/README.md +63 -0
- package/dist/target.d.ts +9 -2
- package/dist/target.js +118 -43
- package/package.json +2 -2
package/README.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# @synergenius/flowweaver-pack-gitlab-ci
|
|
2
|
+
|
|
3
|
+
GitLab CI/CD export target for [Flow Weaver](https://github.com/synergenius-fw/flow-weaver).
|
|
4
|
+
|
|
5
|
+
Generates native `.gitlab-ci.yml` files from Flow Weaver CI/CD workflows. No runtime dependency — outputs pure GitLab CI YAML.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @synergenius/flowweaver-pack-gitlab-ci
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
This package is a **marketplace pack** — once installed, Flow Weaver automatically discovers it via `createTargetRegistry()`.
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
### CLI
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# Export a CI/CD workflow as GitLab CI YAML
|
|
21
|
+
npx flow-weaver export my-pipeline.ts --target gitlab-ci
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Programmatic
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
import { createTargetRegistry } from '@synergenius/flow-weaver/deployment';
|
|
28
|
+
|
|
29
|
+
const registry = await createTargetRegistry(process.cwd());
|
|
30
|
+
const gitlab = registry.get('gitlab-ci');
|
|
31
|
+
|
|
32
|
+
const artifacts = await gitlab.generate({
|
|
33
|
+
sourceFile: 'my-pipeline.ts',
|
|
34
|
+
workflowName: 'myPipeline',
|
|
35
|
+
displayName: 'my-pipeline',
|
|
36
|
+
outputDir: './dist/gitlab-ci',
|
|
37
|
+
});
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## What it generates
|
|
41
|
+
|
|
42
|
+
- `.gitlab-ci.yml` — Native GitLab CI configuration
|
|
43
|
+
- `SECRETS_SETUP.md` — Documentation for required CI/CD variables
|
|
44
|
+
|
|
45
|
+
### Mapping
|
|
46
|
+
|
|
47
|
+
| Flow Weaver | GitLab CI |
|
|
48
|
+
|-------------|-----------|
|
|
49
|
+
| `[job: "name"]` annotation | Job with `stage:` |
|
|
50
|
+
| `@path` dependencies | Stage ordering |
|
|
51
|
+
| `@secret NAME` | `$NAME` variable |
|
|
52
|
+
| `@cache` | Native `cache:` keyword |
|
|
53
|
+
| `@artifact` | Native `artifacts:` keyword |
|
|
54
|
+
| `@trigger push` | `rules:` conditions |
|
|
55
|
+
| `@environment` | `environment:` with optional `when: manual` |
|
|
56
|
+
|
|
57
|
+
## Requirements
|
|
58
|
+
|
|
59
|
+
- `@synergenius/flow-weaver` >= 0.14.0
|
|
60
|
+
|
|
61
|
+
## License
|
|
62
|
+
|
|
63
|
+
See [LICENSE](./LICENSE).
|
package/dist/target.d.ts
CHANGED
|
@@ -42,8 +42,15 @@ export declare class GitLabCITarget extends BaseCICDTarget {
|
|
|
42
42
|
getDeployInstructions(_artifacts: ExportArtifacts): DeployInstructions;
|
|
43
43
|
private renderPipelineYAML;
|
|
44
44
|
/**
|
|
45
|
-
* Derive stages from job dependency
|
|
46
|
-
*
|
|
45
|
+
* Derive stages from @stage annotations or job dependency depth.
|
|
46
|
+
*
|
|
47
|
+
* When @stage annotations exist, jobs are grouped into named stages:
|
|
48
|
+
* - Jobs with an explicit `stage` field (set by buildJobGraph from @stage/@job)
|
|
49
|
+
* use that stage name directly.
|
|
50
|
+
* - The returned list preserves @stage declaration order.
|
|
51
|
+
*
|
|
52
|
+
* Without @stage annotations, falls back to using each job ID as its own stage
|
|
53
|
+
* (ordered by dependency).
|
|
47
54
|
*/
|
|
48
55
|
private deriveStages;
|
|
49
56
|
/**
|
package/dist/target.js
CHANGED
|
@@ -99,14 +99,40 @@ export class GitLabCITarget extends BaseCICDTarget {
|
|
|
99
99
|
// ---------------------------------------------------------------------------
|
|
100
100
|
renderPipelineYAML(ast, jobs) {
|
|
101
101
|
const doc = {};
|
|
102
|
-
//
|
|
103
|
-
const
|
|
102
|
+
// include: directive (from @includes)
|
|
103
|
+
const includes = ast.options?.cicd?.includes;
|
|
104
|
+
if (includes && includes.length > 0) {
|
|
105
|
+
doc.include = includes.map(inc => {
|
|
106
|
+
switch (inc.type) {
|
|
107
|
+
case 'local': return { local: inc.file };
|
|
108
|
+
case 'template': return { template: inc.file };
|
|
109
|
+
case 'remote': return { remote: inc.file };
|
|
110
|
+
case 'project': {
|
|
111
|
+
const obj = { project: inc.project || '', file: inc.file };
|
|
112
|
+
if (inc.ref)
|
|
113
|
+
obj.ref = inc.ref;
|
|
114
|
+
return obj;
|
|
115
|
+
}
|
|
116
|
+
default: return { local: inc.file };
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
// stages (from @stage annotations or derived from dependency depth)
|
|
121
|
+
const stages = this.deriveStages(jobs, ast);
|
|
104
122
|
doc.stages = stages;
|
|
105
123
|
// Default image
|
|
106
124
|
const defaultImage = this.deriveDefaultImage(ast, jobs);
|
|
107
125
|
if (defaultImage) {
|
|
108
126
|
doc.default = { image: defaultImage };
|
|
109
127
|
}
|
|
128
|
+
// Workflow-level variables
|
|
129
|
+
if (ast.options?.cicd?.variables && Object.keys(ast.options.cicd.variables).length > 0) {
|
|
130
|
+
doc.variables = { ...ast.options.cicd.variables };
|
|
131
|
+
}
|
|
132
|
+
// Workflow-level before_script
|
|
133
|
+
if (ast.options?.cicd?.beforeScript && ast.options.cicd.beforeScript.length > 0) {
|
|
134
|
+
doc.before_script = ast.options.cicd.beforeScript;
|
|
135
|
+
}
|
|
110
136
|
// Workflow-level rules (from triggers)
|
|
111
137
|
const rules = this.renderWorkflowRules(ast.options?.cicd?.triggers || []);
|
|
112
138
|
if (rules.length > 0) {
|
|
@@ -123,29 +149,32 @@ export class GitLabCITarget extends BaseCICDTarget {
|
|
|
123
149
|
});
|
|
124
150
|
}
|
|
125
151
|
/**
|
|
126
|
-
* Derive stages from job dependency
|
|
127
|
-
*
|
|
152
|
+
* Derive stages from @stage annotations or job dependency depth.
|
|
153
|
+
*
|
|
154
|
+
* When @stage annotations exist, jobs are grouped into named stages:
|
|
155
|
+
* - Jobs with an explicit `stage` field (set by buildJobGraph from @stage/@job)
|
|
156
|
+
* use that stage name directly.
|
|
157
|
+
* - The returned list preserves @stage declaration order.
|
|
158
|
+
*
|
|
159
|
+
* Without @stage annotations, falls back to using each job ID as its own stage
|
|
160
|
+
* (ordered by dependency).
|
|
128
161
|
*/
|
|
129
|
-
deriveStages(jobs) {
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
return jobId;
|
|
162
|
+
deriveStages(jobs, ast) {
|
|
163
|
+
const declaredStages = ast?.options?.cicd?.stages;
|
|
164
|
+
// If @stage annotations exist, use them
|
|
165
|
+
if (declaredStages && declaredStages.length > 0) {
|
|
166
|
+
const stageNames = declaredStages.map(s => s.name);
|
|
167
|
+
// Collect any stages referenced by jobs that aren't in the declared list
|
|
168
|
+
for (const job of jobs) {
|
|
169
|
+
if (job.stage && !stageNames.includes(job.stage)) {
|
|
170
|
+
stageNames.push(job.stage);
|
|
171
|
+
}
|
|
140
172
|
}
|
|
141
|
-
|
|
142
|
-
const depStages = job.needs.map((dep) => getStage(dep, jobs));
|
|
143
|
-
assigned.set(jobId, jobId);
|
|
144
|
-
return jobId;
|
|
173
|
+
return stageNames;
|
|
145
174
|
}
|
|
146
|
-
//
|
|
175
|
+
// Fallback: use job IDs as stage names, ordered by dependency
|
|
176
|
+
const stages = [];
|
|
147
177
|
for (const job of jobs) {
|
|
148
|
-
getStage(job.id, jobs);
|
|
149
178
|
if (!stages.includes(job.id)) {
|
|
150
179
|
stages.push(job.id);
|
|
151
180
|
}
|
|
@@ -215,12 +244,14 @@ export class GitLabCITarget extends BaseCICDTarget {
|
|
|
215
244
|
}
|
|
216
245
|
renderJob(job, ast, stages) {
|
|
217
246
|
const jobObj = {};
|
|
218
|
-
//
|
|
219
|
-
|
|
247
|
+
// extends (from @job extends=".template-name")
|
|
248
|
+
if (job.extends) {
|
|
249
|
+
jobObj.extends = job.extends;
|
|
250
|
+
}
|
|
251
|
+
// stage (use explicit stage from @stage assignment, or fall back to job ID)
|
|
252
|
+
jobObj.stage = job.stage || job.id;
|
|
220
253
|
// image (from runner or default)
|
|
221
254
|
if (job.runner && job.runner !== 'ubuntu-latest') {
|
|
222
|
-
// GitLab uses Docker images, not runner labels
|
|
223
|
-
// Map common GitHub runners to Docker images
|
|
224
255
|
const imageMap = {
|
|
225
256
|
'ubuntu-latest': 'ubuntu:latest',
|
|
226
257
|
'ubuntu-22.04': 'ubuntu:22.04',
|
|
@@ -229,10 +260,47 @@ export class GitLabCITarget extends BaseCICDTarget {
|
|
|
229
260
|
const image = imageMap[job.runner] || job.runner;
|
|
230
261
|
jobObj.image = image;
|
|
231
262
|
}
|
|
263
|
+
// tags (from @job tags or @tags)
|
|
264
|
+
if (job.tags && job.tags.length > 0) {
|
|
265
|
+
jobObj.tags = job.tags;
|
|
266
|
+
}
|
|
232
267
|
// needs (for DAG mode instead of stage-based ordering)
|
|
233
268
|
if (job.needs.length > 0) {
|
|
234
269
|
jobObj.needs = job.needs;
|
|
235
270
|
}
|
|
271
|
+
// retry (from @job retry)
|
|
272
|
+
if (job.retry !== undefined) {
|
|
273
|
+
jobObj.retry = { max: job.retry };
|
|
274
|
+
}
|
|
275
|
+
// allow_failure (from @job allow_failure)
|
|
276
|
+
if (job.allowFailure) {
|
|
277
|
+
jobObj.allow_failure = true;
|
|
278
|
+
}
|
|
279
|
+
// timeout (from @job timeout)
|
|
280
|
+
if (job.timeout) {
|
|
281
|
+
jobObj.timeout = job.timeout;
|
|
282
|
+
}
|
|
283
|
+
// rules (from @job rules)
|
|
284
|
+
if (job.rules && job.rules.length > 0) {
|
|
285
|
+
jobObj.rules = job.rules.map(rule => {
|
|
286
|
+
const ruleObj = {};
|
|
287
|
+
if (rule.if)
|
|
288
|
+
ruleObj.if = rule.if;
|
|
289
|
+
if (rule.when)
|
|
290
|
+
ruleObj.when = rule.when;
|
|
291
|
+
if (rule.allowFailure)
|
|
292
|
+
ruleObj.allow_failure = true;
|
|
293
|
+
if (rule.changes)
|
|
294
|
+
ruleObj.changes = rule.changes;
|
|
295
|
+
if (rule.variables)
|
|
296
|
+
ruleObj.variables = rule.variables;
|
|
297
|
+
return ruleObj;
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
// coverage (from @job coverage)
|
|
301
|
+
if (job.coverage) {
|
|
302
|
+
jobObj.coverage = job.coverage;
|
|
303
|
+
}
|
|
236
304
|
// environment
|
|
237
305
|
if (job.environment) {
|
|
238
306
|
const envConfig = ast.options?.cicd?.environments?.find((e) => e.name === job.environment);
|
|
@@ -242,7 +310,6 @@ export class GitLabCITarget extends BaseCICDTarget {
|
|
|
242
310
|
if (envConfig?.reviewers)
|
|
243
311
|
envObj.deployment_tier = 'production';
|
|
244
312
|
jobObj.environment = envObj;
|
|
245
|
-
// Protected environments require manual approval in GitLab
|
|
246
313
|
if (envConfig?.reviewers) {
|
|
247
314
|
jobObj.when = 'manual';
|
|
248
315
|
}
|
|
@@ -251,46 +318,54 @@ export class GitLabCITarget extends BaseCICDTarget {
|
|
|
251
318
|
if (job.services && job.services.length > 0) {
|
|
252
319
|
jobObj.services = job.services.map((svc) => {
|
|
253
320
|
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
321
|
return svcObj;
|
|
259
322
|
});
|
|
260
323
|
}
|
|
261
|
-
// variables (
|
|
324
|
+
// variables (merge secrets + job-level variables)
|
|
325
|
+
const variables = {};
|
|
262
326
|
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
327
|
for (const secret of job.secrets) {
|
|
267
328
|
variables[secret] = `$${secret}`;
|
|
268
329
|
}
|
|
330
|
+
}
|
|
331
|
+
if (job.variables) {
|
|
332
|
+
Object.assign(variables, job.variables);
|
|
333
|
+
}
|
|
334
|
+
if (Object.keys(variables).length > 0) {
|
|
269
335
|
jobObj.variables = variables;
|
|
270
336
|
}
|
|
337
|
+
// before_script (from @job or @before_script)
|
|
338
|
+
if (job.beforeScript && job.beforeScript.length > 0) {
|
|
339
|
+
jobObj.before_script = job.beforeScript;
|
|
340
|
+
}
|
|
271
341
|
// cache
|
|
272
342
|
if (job.cache) {
|
|
273
343
|
jobObj.cache = this.renderCache(job.cache);
|
|
274
344
|
}
|
|
275
|
-
// artifacts (upload)
|
|
345
|
+
// artifacts (upload + reports)
|
|
346
|
+
const artifactsObj = {};
|
|
276
347
|
if (job.uploadArtifacts && job.uploadArtifacts.length > 0) {
|
|
277
|
-
|
|
348
|
+
artifactsObj.paths = job.uploadArtifacts.map((a) => a.path);
|
|
278
349
|
const expiry = job.uploadArtifacts[0].retention
|
|
279
350
|
? `${job.uploadArtifacts[0].retention} days`
|
|
280
351
|
: '1 week';
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
};
|
|
352
|
+
artifactsObj.expire_in = expiry;
|
|
353
|
+
}
|
|
354
|
+
if (job.reports && job.reports.length > 0) {
|
|
355
|
+
const reports = {};
|
|
356
|
+
for (const report of job.reports) {
|
|
357
|
+
reports[report.type] = report.path;
|
|
358
|
+
}
|
|
359
|
+
artifactsObj.reports = reports;
|
|
360
|
+
}
|
|
361
|
+
if (Object.keys(artifactsObj).length > 0) {
|
|
362
|
+
jobObj.artifacts = artifactsObj;
|
|
285
363
|
}
|
|
286
364
|
// script (the actual steps)
|
|
287
365
|
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
366
|
if (job.downloadArtifacts && job.downloadArtifacts.length > 0) {
|
|
291
367
|
script.push(`# Artifacts from: ${job.downloadArtifacts.join(', ')} (downloaded automatically via needs:)`);
|
|
292
368
|
}
|
|
293
|
-
// Step scripts
|
|
294
369
|
for (const step of job.steps) {
|
|
295
370
|
const stepScript = this.renderStepScript(step);
|
|
296
371
|
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.3",
|
|
4
4
|
"description": "GitLab CI/CD export target for Flow Weaver",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"flowweaver-marketplace-pack",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"typescript": "^5.0.0",
|
|
39
|
-
"@synergenius/flow-weaver": "^0.
|
|
39
|
+
"@synergenius/flow-weaver": "^0.17.3",
|
|
40
40
|
"@types/node": "^20.0.0"
|
|
41
41
|
},
|
|
42
42
|
"license": "SEE LICENSE IN LICENSE",
|