@synergenius/flowweaver-pack-github-actions 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 CHANGED
@@ -18,6 +18,8 @@ import type { ExportOptions, ExportArtifacts, DeployInstructions } from '@synerg
18
18
  export declare class GitHubActionsTarget extends BaseCICDTarget {
19
19
  readonly name = "github-actions";
20
20
  readonly description = "GitHub Actions workflow YAML (.github/workflows/)";
21
+ /** Accumulated warnings for the current export run */
22
+ private _warnings;
21
23
  readonly deploySchema: {
22
24
  runner: {
23
25
  type: "string";
@@ -44,6 +46,14 @@ export declare class GitHubActionsTarget extends BaseCICDTarget {
44
46
  private renderWorkflowYAML;
45
47
  private renderTriggers;
46
48
  private renderJob;
49
+ /**
50
+ * Parse a timeout string like "30m", "1h", "1h30m" into minutes.
51
+ */
52
+ private parseTimeoutMinutes;
53
+ /**
54
+ * Translate GitLab-style CI variable conditions to GitHub Actions expressions.
55
+ */
56
+ private translateCondition;
47
57
  private renderStep;
48
58
  private renderCacheStep;
49
59
  /**
package/dist/target.js CHANGED
@@ -21,6 +21,8 @@ import * as path from 'path';
21
21
  export class GitHubActionsTarget extends BaseCICDTarget {
22
22
  name = 'github-actions';
23
23
  description = 'GitHub Actions workflow YAML (.github/workflows/)';
24
+ /** Accumulated warnings for the current export run */
25
+ _warnings = [];
24
26
  deploySchema = {
25
27
  runner: { type: 'string', description: 'GitHub runner label', default: 'ubuntu-latest' },
26
28
  };
@@ -30,6 +32,7 @@ export class GitHubActionsTarget extends BaseCICDTarget {
30
32
  label: { type: 'string', description: 'Step display name' },
31
33
  };
32
34
  async generate(options) {
35
+ this._warnings = [];
33
36
  const filePath = path.resolve(options.sourceFile);
34
37
  const outputDir = path.resolve(options.outputDir);
35
38
  // Parse the workflow file to get AST
@@ -72,6 +75,7 @@ export class GitHubActionsTarget extends BaseCICDTarget {
72
75
  target: this.name,
73
76
  workflowName: options.displayName || targetWorkflows[0].name,
74
77
  entryPoint: files[0].relativePath,
78
+ warnings: this._warnings.length > 0 ? this._warnings : undefined,
75
79
  };
76
80
  }
77
81
  getDeployInstructions(_artifacts) {
@@ -105,6 +109,18 @@ export class GitHubActionsTarget extends BaseCICDTarget {
105
109
  doc.name = ast.name;
106
110
  // on: triggers
107
111
  doc.on = this.renderTriggers(ast.options?.cicd?.triggers || []);
112
+ // env (workflow-level, from @variables)
113
+ if (ast.options?.cicd?.variables && Object.keys(ast.options.cicd.variables).length > 0) {
114
+ doc.env = { ...ast.options.cicd.variables };
115
+ }
116
+ // Warn about @includes (GitLab CI only)
117
+ if (ast.options?.cicd?.includes && ast.options.cicd.includes.length > 0) {
118
+ this._warnings.push(`@includes: GitHub Actions has no equivalent to GitLab CI includes. Use reusable workflows (workflow_call) or composite actions instead.`);
119
+ }
120
+ // Warn about @before_script at workflow level (no GH Actions equivalent)
121
+ if (ast.options?.cicd?.beforeScript && ast.options.cicd.beforeScript.length > 0) {
122
+ this._warnings.push(`Workflow-level @before_script: GitHub Actions has no global before_script. It has been applied per-job instead.`);
123
+ }
108
124
  // concurrency
109
125
  if (ast.options?.cicd?.concurrency) {
110
126
  doc.concurrency = {
@@ -182,12 +198,63 @@ export class GitHubActionsTarget extends BaseCICDTarget {
182
198
  }
183
199
  renderJob(job, ast) {
184
200
  const jobObj = {};
185
- // runs-on
186
- jobObj['runs-on'] = job.runner || 'ubuntu-latest';
201
+ // runs-on (tags override: self-hosted + tag labels)
202
+ if (job.tags && job.tags.length > 0) {
203
+ jobObj['runs-on'] = ['self-hosted', ...job.tags];
204
+ }
205
+ else {
206
+ jobObj['runs-on'] = job.runner || 'ubuntu-latest';
207
+ }
187
208
  // needs
188
209
  if (job.needs.length > 0) {
189
210
  jobObj.needs = job.needs;
190
211
  }
212
+ // continue-on-error (from @job allow_failure)
213
+ if (job.allowFailure) {
214
+ jobObj['continue-on-error'] = true;
215
+ }
216
+ // timeout-minutes (from @job timeout, parse "30m" → 30, "1h" → 60)
217
+ if (job.timeout) {
218
+ jobObj['timeout-minutes'] = this.parseTimeoutMinutes(job.timeout);
219
+ }
220
+ // retry: no native equivalent in GitHub Actions
221
+ if (job.retry !== undefined && job.retry > 0) {
222
+ this._warnings.push(`@job ${job.id} retry=${job.retry}: GitHub Actions has no native job-level retry. Use "Re-run failed jobs" in the UI or the nick-fields/retry action for step-level retry.`);
223
+ }
224
+ // coverage: no native equivalent in GitHub Actions
225
+ if (job.coverage) {
226
+ this._warnings.push(`@job ${job.id} coverage: GitHub Actions has no native coverage regex. Use a coverage action (e.g. codecov/codecov-action) instead.`);
227
+ }
228
+ // extends: no native equivalent in GitHub Actions (GitLab CI only)
229
+ if (job.extends) {
230
+ this._warnings.push(`@job ${job.id} extends="${job.extends}": GitHub Actions has no native extends. Use reusable workflows or composite actions instead.`);
231
+ }
232
+ // if: conditional (from @job rules)
233
+ if (job.rules && job.rules.length > 0) {
234
+ // Combine all rule conditions with || (GitHub Actions only supports a single `if:`)
235
+ const conditions = job.rules
236
+ .filter(r => r.if)
237
+ .map(r => this.translateCondition(r.if));
238
+ if (conditions.length === 1) {
239
+ jobObj.if = conditions[0];
240
+ }
241
+ else if (conditions.length > 1) {
242
+ jobObj.if = conditions.map(c => `(${c})`).join(' || ');
243
+ }
244
+ // Warn about rule properties that don't translate to GitHub Actions
245
+ const hasWhen = job.rules.some(r => r.when);
246
+ const hasChanges = job.rules.some(r => r.changes && r.changes.length > 0);
247
+ if (hasWhen) {
248
+ this._warnings.push(`@job ${job.id} rules when=: GitHub Actions has no native when (manual/delayed/always). Use workflow_dispatch for manual triggers.`);
249
+ }
250
+ if (hasChanges) {
251
+ this._warnings.push(`@job ${job.id} rules changes=: GitHub Actions handles path filtering via on.push.paths, not per-job. Use dorny/paths-filter for per-job path filtering.`);
252
+ }
253
+ }
254
+ // env (from @job variables or @variables)
255
+ if (job.variables && Object.keys(job.variables).length > 0) {
256
+ jobObj.env = { ...job.variables };
257
+ }
191
258
  // environment
192
259
  if (job.environment) {
193
260
  const envConfig = ast.options?.cicd?.environments?.find((e) => e.name === job.environment);
@@ -246,6 +313,13 @@ export class GitHubActionsTarget extends BaseCICDTarget {
246
313
  if (job.cache) {
247
314
  steps.push(this.renderCacheStep(job.cache));
248
315
  }
316
+ // before_script as a setup step
317
+ if (job.beforeScript && job.beforeScript.length > 0) {
318
+ steps.push({
319
+ name: 'Setup',
320
+ run: job.beforeScript.join('\n'),
321
+ });
322
+ }
249
323
  // Node steps
250
324
  for (const step of job.steps) {
251
325
  steps.push(this.renderStep(step));
@@ -266,9 +340,66 @@ export class GitHubActionsTarget extends BaseCICDTarget {
266
340
  steps.push(uploadStep);
267
341
  }
268
342
  }
343
+ // reports: junit → test-reporter, coverage → codecov
344
+ if (job.reports && job.reports.length > 0) {
345
+ for (const report of job.reports) {
346
+ if (report.type === 'junit') {
347
+ steps.push({
348
+ name: 'Test Report',
349
+ uses: 'dorny/test-reporter@v1',
350
+ if: 'always()',
351
+ with: {
352
+ name: 'Test Results',
353
+ path: report.path,
354
+ reporter: 'java-junit',
355
+ },
356
+ });
357
+ }
358
+ else if (report.type === 'cobertura' || report.type === 'coverage') {
359
+ steps.push({
360
+ name: 'Upload Coverage',
361
+ uses: 'codecov/codecov-action@v4',
362
+ with: { files: report.path },
363
+ });
364
+ }
365
+ else {
366
+ steps.push({
367
+ name: `Upload ${report.type} report`,
368
+ uses: 'actions/upload-artifact@v4',
369
+ with: {
370
+ name: `${report.type}-report`,
371
+ path: report.path,
372
+ },
373
+ });
374
+ }
375
+ }
376
+ }
269
377
  jobObj.steps = steps;
270
378
  return jobObj;
271
379
  }
380
+ /**
381
+ * Parse a timeout string like "30m", "1h", "1h30m" into minutes.
382
+ */
383
+ parseTimeoutMinutes(timeout) {
384
+ let minutes = 0;
385
+ const hourMatch = timeout.match(/(\d+)h/);
386
+ const minMatch = timeout.match(/(\d+)m/);
387
+ if (hourMatch)
388
+ minutes += parseInt(hourMatch[1], 10) * 60;
389
+ if (minMatch)
390
+ minutes += parseInt(minMatch[1], 10);
391
+ return minutes || 60; // default 60 if unparseable
392
+ }
393
+ /**
394
+ * Translate GitLab-style CI variable conditions to GitHub Actions expressions.
395
+ */
396
+ translateCondition(condition) {
397
+ return condition
398
+ .replace(/\$CI_COMMIT_BRANCH/g, "github.ref_name")
399
+ .replace(/\$CI_COMMIT_TAG/g, "startsWith(github.ref, 'refs/tags/')")
400
+ .replace(/\$CI_PIPELINE_SOURCE/g, "github.event_name")
401
+ .replace(/==/g, '==');
402
+ }
272
403
  renderStep(step) {
273
404
  const mapping = this.resolveActionMapping(step, 'github-actions');
274
405
  if (mapping?.githubAction) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@synergenius/flowweaver-pack-github-actions",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "GitHub Actions 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
- "typescript": "^5.0.0",
39
- "@synergenius/flow-weaver": "^0.17.1",
40
- "@types/node": "^20.0.0"
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": {