@synergenius/flow-weaver 0.14.2 → 0.15.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.
@@ -1,374 +0,0 @@
1
- /**
2
- * GitLab CI Export Target
3
- *
4
- * Generates .gitlab-ci.yml from a Flow Weaver CI/CD workflow.
5
- * No FW runtime dependency — outputs native GitLab CI YAML.
6
- *
7
- * Key differences from GitHub Actions:
8
- * - `stage:` instead of job `needs:` (generates `stages:` list)
9
- * - `$CI_VARIABLE` instead of `${{ secrets.NAME }}`
10
- * - `cache:` and `artifacts:` as native YAML keywords (no separate actions)
11
- * - `services:` as native keyword
12
- * - `rules:` for conditional execution
13
- * - `environment:` as native keyword with `url:` and `when: manual` for approval
14
- */
15
- import { stringify as yamlStringify } from 'yaml';
16
- import { isCICDWorkflow } from '../../validation/cicd-detection.js';
17
- import { BaseCICDTarget, } from './cicd-base.js';
18
- import { parseWorkflow } from '../../api/index.js';
19
- import * as path from 'path';
20
- export class GitLabCITarget extends BaseCICDTarget {
21
- name = 'gitlab-ci';
22
- description = 'GitLab CI/CD pipeline (.gitlab-ci.yml)';
23
- deploySchema = {
24
- runner: { type: 'string', description: 'Default Docker image', default: 'ubuntu:latest' },
25
- };
26
- nodeTypeDeploySchema = {
27
- script: { type: 'string[]', description: 'GitLab CI script commands' },
28
- image: { type: 'string', description: 'Docker image override' },
29
- label: { type: 'string', description: 'Step display name' },
30
- };
31
- async generate(options) {
32
- const filePath = path.resolve(options.sourceFile);
33
- const outputDir = path.resolve(options.outputDir);
34
- // Parse the workflow file to get AST
35
- const parseResult = await parseWorkflow(filePath, { nodeTypesOnly: false });
36
- if (parseResult.errors.length > 0) {
37
- throw new Error(`Parse errors: ${parseResult.errors.join('; ')}`);
38
- }
39
- const allWorkflows = parseResult.allWorkflows || [];
40
- const targetWorkflows = options.workflowName
41
- ? allWorkflows.filter((w) => w.name === options.workflowName || w.functionName === options.workflowName)
42
- : allWorkflows.filter((w) => isCICDWorkflow(w));
43
- if (targetWorkflows.length === 0) {
44
- throw new Error('No CI/CD workflows found. Ensure workflow has CI/CD annotations (@secret, @runner, @trigger, [job:], etc.)');
45
- }
46
- const files = [];
47
- for (const ast of targetWorkflows) {
48
- // Build job graph
49
- const jobs = this.buildJobGraph(ast);
50
- // Resolve secrets
51
- this.resolveJobSecrets(jobs, ast, (name) => `$${name}`);
52
- // Inject artifacts
53
- const artifacts = ast.options?.cicd?.artifacts || [];
54
- this.injectArtifactSteps(jobs, artifacts);
55
- // Apply workflow options
56
- this.applyWorkflowOptions(jobs, ast);
57
- // Generate YAML
58
- const yamlContent = this.renderPipelineYAML(ast, jobs);
59
- files.push(this.createFile(outputDir, '.gitlab-ci.yml', yamlContent, 'config'));
60
- // Generate secrets doc if secrets exist
61
- const secrets = ast.options?.cicd?.secrets || [];
62
- if (secrets.length > 0) {
63
- const secretsDoc = this.generateSecretsDoc(secrets, 'gitlab-ci');
64
- files.push(this.createFile(outputDir, 'SECRETS_SETUP.md', secretsDoc, 'other'));
65
- }
66
- }
67
- return {
68
- files,
69
- target: this.name,
70
- workflowName: options.displayName || targetWorkflows[0].name,
71
- entryPoint: files[0].relativePath,
72
- };
73
- }
74
- getDeployInstructions(_artifacts) {
75
- return {
76
- title: 'Deploy GitLab CI Pipeline',
77
- prerequisites: [
78
- 'GitLab repository',
79
- 'CI/CD variables configured (see SECRETS_SETUP.md)',
80
- 'GitLab Runner available (shared or project-specific)',
81
- ],
82
- steps: [
83
- 'Copy .gitlab-ci.yml to your repository root',
84
- 'Configure required variables in GitLab (Settings > CI/CD > Variables)',
85
- 'Push to trigger the pipeline',
86
- ],
87
- localTestSteps: [
88
- 'Install gitlab-runner: brew install gitlab-runner',
89
- 'Run locally: gitlab-runner exec docker <job-name>',
90
- ],
91
- links: [
92
- { label: 'GitLab CI/CD Docs', url: 'https://docs.gitlab.com/ee/ci/' },
93
- { label: 'GitLab CI Lint', url: 'https://docs.gitlab.com/ee/ci/lint.html' },
94
- ],
95
- };
96
- }
97
- // ---------------------------------------------------------------------------
98
- // Private: YAML Rendering
99
- // ---------------------------------------------------------------------------
100
- renderPipelineYAML(ast, jobs) {
101
- const doc = {};
102
- // stages (derived from job dependency order)
103
- const stages = this.deriveStages(jobs);
104
- doc.stages = stages;
105
- // Default image
106
- const defaultImage = this.deriveDefaultImage(ast, jobs);
107
- if (defaultImage) {
108
- doc.default = { image: defaultImage };
109
- }
110
- // Workflow-level rules (from triggers)
111
- const rules = this.renderWorkflowRules(ast.options?.cicd?.triggers || []);
112
- if (rules.length > 0) {
113
- doc.workflow = { rules };
114
- }
115
- // Job definitions
116
- for (const job of jobs) {
117
- doc[job.id] = this.renderJob(job, ast, stages);
118
- }
119
- return yamlStringify(doc, {
120
- lineWidth: 120,
121
- defaultStringType: 'PLAIN',
122
- defaultKeyType: 'PLAIN',
123
- });
124
- }
125
- /**
126
- * Derive stages from job dependency order.
127
- * Jobs with no deps → stage 1, jobs depending on stage 1 → stage 2, etc.
128
- */
129
- deriveStages(jobs) {
130
- const stages = [];
131
- const assigned = new Map();
132
- // Assign stages based on dependency depth
133
- function getStage(jobId, jobs) {
134
- if (assigned.has(jobId))
135
- return assigned.get(jobId);
136
- const job = jobs.find((j) => j.id === jobId);
137
- if (!job || job.needs.length === 0) {
138
- assigned.set(jobId, jobId);
139
- return jobId;
140
- }
141
- // Stage is one after the latest dependency
142
- const depStages = job.needs.map((dep) => getStage(dep, jobs));
143
- assigned.set(jobId, jobId);
144
- return jobId;
145
- }
146
- // Simple: use job IDs as stage names, ordered by dependency
147
- for (const job of jobs) {
148
- getStage(job.id, jobs);
149
- if (!stages.includes(job.id)) {
150
- stages.push(job.id);
151
- }
152
- }
153
- return stages;
154
- }
155
- /**
156
- * Derive default image from @deploy annotations or built-in mappings.
157
- */
158
- deriveDefaultImage(_ast, jobs) {
159
- // Check steps for @deploy gitlab-ci image or built-in mapping
160
- for (const job of jobs) {
161
- for (const step of job.steps) {
162
- const mapping = this.resolveActionMapping(step, 'gitlab-ci');
163
- if (mapping?.gitlabImage)
164
- return mapping.gitlabImage;
165
- }
166
- }
167
- return undefined;
168
- }
169
- /**
170
- * Convert CI/CD triggers to GitLab CI workflow rules.
171
- */
172
- renderWorkflowRules(triggers) {
173
- if (triggers.length === 0)
174
- return [];
175
- const rules = [];
176
- for (const trigger of triggers) {
177
- switch (trigger.type) {
178
- case 'push':
179
- if (trigger.branches) {
180
- for (const branch of trigger.branches) {
181
- rules.push({
182
- if: `$CI_COMMIT_BRANCH == "${branch}"`,
183
- when: 'always',
184
- });
185
- }
186
- }
187
- else {
188
- rules.push({ if: '$CI_PIPELINE_SOURCE == "push"', when: 'always' });
189
- }
190
- break;
191
- case 'pull_request':
192
- rules.push({
193
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"',
194
- when: 'always',
195
- });
196
- break;
197
- case 'schedule':
198
- rules.push({
199
- if: '$CI_PIPELINE_SOURCE == "schedule"',
200
- when: 'always',
201
- });
202
- break;
203
- case 'dispatch':
204
- rules.push({
205
- if: '$CI_PIPELINE_SOURCE == "web" || $CI_PIPELINE_SOURCE == "api"',
206
- when: 'always',
207
- });
208
- break;
209
- case 'tag':
210
- rules.push({ if: '$CI_COMMIT_TAG', when: 'always' });
211
- break;
212
- }
213
- }
214
- return rules;
215
- }
216
- renderJob(job, ast, stages) {
217
- const jobObj = {};
218
- // stage
219
- jobObj.stage = job.id;
220
- // image (from runner or default)
221
- if (job.runner && job.runner !== 'ubuntu-latest') {
222
- // GitLab uses Docker images, not runner labels
223
- // Map common GitHub runners to Docker images
224
- const imageMap = {
225
- 'ubuntu-latest': 'ubuntu:latest',
226
- 'ubuntu-22.04': 'ubuntu:22.04',
227
- 'ubuntu-20.04': 'ubuntu:20.04',
228
- };
229
- const image = imageMap[job.runner] || job.runner;
230
- jobObj.image = image;
231
- }
232
- // needs (for DAG mode instead of stage-based ordering)
233
- if (job.needs.length > 0) {
234
- jobObj.needs = job.needs;
235
- }
236
- // environment
237
- if (job.environment) {
238
- const envConfig = ast.options?.cicd?.environments?.find((e) => e.name === job.environment);
239
- const envObj = { name: job.environment };
240
- if (envConfig?.url)
241
- envObj.url = envConfig.url;
242
- if (envConfig?.reviewers)
243
- envObj.deployment_tier = 'production';
244
- jobObj.environment = envObj;
245
- // Protected environments require manual approval in GitLab
246
- if (envConfig?.reviewers) {
247
- jobObj.when = 'manual';
248
- }
249
- }
250
- // services
251
- if (job.services && job.services.length > 0) {
252
- jobObj.services = job.services.map((svc) => {
253
- 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
- return svcObj;
259
- });
260
- }
261
- // variables (from secrets)
262
- 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
- for (const secret of job.secrets) {
267
- variables[secret] = `$${secret}`;
268
- }
269
- jobObj.variables = variables;
270
- }
271
- // cache
272
- if (job.cache) {
273
- jobObj.cache = this.renderCache(job.cache);
274
- }
275
- // artifacts (upload)
276
- if (job.uploadArtifacts && job.uploadArtifacts.length > 0) {
277
- const paths = job.uploadArtifacts.map((a) => a.path);
278
- const expiry = job.uploadArtifacts[0].retention
279
- ? `${job.uploadArtifacts[0].retention} days`
280
- : '1 week';
281
- jobObj.artifacts = {
282
- paths,
283
- expire_in: expiry,
284
- };
285
- }
286
- // script (the actual steps)
287
- 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
- if (job.downloadArtifacts && job.downloadArtifacts.length > 0) {
291
- script.push(`# Artifacts from: ${job.downloadArtifacts.join(', ')} (downloaded automatically via needs:)`);
292
- }
293
- // Step scripts
294
- for (const step of job.steps) {
295
- const stepScript = this.renderStepScript(step);
296
- script.push(...stepScript);
297
- }
298
- jobObj.script = script;
299
- return jobObj;
300
- }
301
- renderStepScript(step) {
302
- const mapping = this.resolveActionMapping(step, 'gitlab-ci');
303
- const lines = [];
304
- // Add env vars as export statements if present
305
- if (step.env) {
306
- for (const [key, value] of Object.entries(step.env)) {
307
- lines.push(`export ${key}="${value}"`);
308
- }
309
- }
310
- if (mapping?.gitlabScript) {
311
- lines.push(`# ${mapping.label || step.name}`);
312
- lines.push(...mapping.gitlabScript);
313
- }
314
- else {
315
- // Unknown node type — generate TODO
316
- lines.push(`# TODO: Implement '${step.id}' (node type: ${step.nodeType})`);
317
- lines.push(`echo "Step: ${step.name}"`);
318
- }
319
- return lines;
320
- }
321
- renderCache(cache) {
322
- const cacheObj = {};
323
- switch (cache.strategy) {
324
- case 'npm':
325
- cacheObj.key = {
326
- files: [cache.key || 'package-lock.json'],
327
- };
328
- cacheObj.paths = [cache.path || 'node_modules/'];
329
- break;
330
- case 'pip':
331
- cacheObj.key = {
332
- files: [cache.key || 'requirements.txt'],
333
- };
334
- cacheObj.paths = [cache.path || '.pip-cache/'];
335
- break;
336
- default:
337
- cacheObj.key = cache.key || '$CI_COMMIT_REF_SLUG';
338
- cacheObj.paths = [cache.path || '.cache/'];
339
- }
340
- return cacheObj;
341
- }
342
- /**
343
- * Apply workflow-level options to jobs.
344
- */
345
- applyWorkflowOptions(jobs, ast) {
346
- const cicd = ast.options?.cicd;
347
- if (!cicd)
348
- return;
349
- // Apply cache to all jobs
350
- if (cicd.caches && cicd.caches.length > 0) {
351
- for (const job of jobs) {
352
- if (!job.cache) {
353
- job.cache = cicd.caches[0];
354
- }
355
- }
356
- }
357
- // Apply services to all jobs
358
- if (cicd.services && cicd.services.length > 0) {
359
- for (const job of jobs) {
360
- if (!job.services) {
361
- job.services = cicd.services;
362
- }
363
- }
364
- }
365
- // Apply matrix (GitLab uses `parallel: matrix:`)
366
- if (cicd.matrix) {
367
- const rootJobs = jobs.filter((j) => j.needs.length === 0);
368
- for (const job of rootJobs) {
369
- job.matrix = cicd.matrix;
370
- }
371
- }
372
- }
373
- }
374
- //# sourceMappingURL=gitlab-ci.js.map
@@ -1,63 +0,0 @@
1
- /**
2
- * Inngest export target
3
- *
4
- * Generates durable, event-driven functions using the Inngest platform.
5
- * Each workflow becomes an Inngest function triggered by a custom event,
6
- * served via an HTTP endpoint compatible with any framework (Express, Next.js, etc.).
7
- */
8
- import { BaseExportTarget, type ExportOptions, type ExportArtifacts, type DeployInstructions, type CompiledWorkflow, type MultiWorkflowArtifacts, type NodeTypeInfo, type NodeTypeExportOptions, type NodeTypeArtifacts, type BundleWorkflow, type BundleNodeType, type BundleArtifacts } from './base.js';
9
- /**
10
- * Inngest export target — durable, event-driven functions
11
- */
12
- export declare class InngestTarget extends BaseExportTarget {
13
- readonly name = "inngest";
14
- readonly description = "Inngest \u2014 durable, event-driven workflow functions";
15
- readonly deploySchema: {
16
- durableSteps: {
17
- type: "boolean";
18
- description: string;
19
- default: boolean;
20
- };
21
- framework: {
22
- type: "string";
23
- description: string;
24
- };
25
- serve: {
26
- type: "boolean";
27
- description: string;
28
- default: boolean;
29
- };
30
- retries: {
31
- type: "number";
32
- description: string;
33
- default: number;
34
- };
35
- triggerEvent: {
36
- type: "string";
37
- description: string;
38
- };
39
- };
40
- /**
41
- * Sanitize a name into a valid Inngest ID (lowercase, alphanumeric + hyphens)
42
- */
43
- private toInngestId;
44
- /**
45
- * Convert a name to a valid JS variable name
46
- */
47
- private toVarName;
48
- generate(options: ExportOptions): Promise<ExportArtifacts>;
49
- /**
50
- * Generate the shallow (template-based) handler content.
51
- * Wraps the entire workflow in a single step.run() call.
52
- */
53
- private generateShallowHandler;
54
- /**
55
- * Generate OpenAPI specification for a single workflow
56
- */
57
- private generateOpenAPISpec;
58
- generateMultiWorkflow(workflows: CompiledWorkflow[], options: ExportOptions): Promise<MultiWorkflowArtifacts>;
59
- generateNodeTypeService(nodeTypes: NodeTypeInfo[], options: NodeTypeExportOptions): Promise<NodeTypeArtifacts>;
60
- generateBundle(workflows: BundleWorkflow[], nodeTypes: BundleNodeType[], options: ExportOptions): Promise<BundleArtifacts>;
61
- getDeployInstructions(artifacts: ExportArtifacts): DeployInstructions;
62
- }
63
- //# sourceMappingURL=inngest.d.ts.map