flowlint 0.3.7 → 0.3.8

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 ADDED
@@ -0,0 +1,390 @@
1
+ # FlowLint CLI
2
+
3
+ Static analysis tool for n8n workflows - detect issues early, fix them faster.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install -g flowlint
9
+ ```
10
+
11
+ Or use with npx (no installation required):
12
+
13
+ ```bash
14
+ npx flowlint scan ./workflows
15
+ ```
16
+
17
+ ## Quick Start
18
+
19
+ Scan workflows in the current directory:
20
+
21
+ ```bash
22
+ flowlint scan .
23
+ ```
24
+
25
+ Scan a specific directory:
26
+
27
+ ```bash
28
+ flowlint scan ./workflows
29
+ ```
30
+
31
+ Scan with a custom config file:
32
+
33
+ ```bash
34
+ flowlint scan . --config .flowlint.yml
35
+ ```
36
+
37
+ ## Output Formats
38
+
39
+ FlowLint supports multiple output formats for different use cases:
40
+
41
+ ### Stylish (Default)
42
+
43
+ Human-readable console output with colors and formatting:
44
+
45
+ ```bash
46
+ flowlint scan .
47
+ ```
48
+
49
+ ### JSON
50
+
51
+ Machine-readable JSON output:
52
+
53
+ ```bash
54
+ flowlint scan . --format json
55
+ flowlint scan . --format json --out-file report.json
56
+ ```
57
+
58
+ ### SARIF
59
+
60
+ SARIF 2.1.0 format for GitHub Code Scanning and other security platforms:
61
+
62
+ ```bash
63
+ flowlint scan . --format sarif --out-file results.sarif
64
+ ```
65
+
66
+ ### JUnit XML
67
+
68
+ JUnit XML format for CI/CD platforms (Jenkins, GitLab CI, CircleCI, etc.):
69
+
70
+ ```bash
71
+ flowlint scan . --format junit --out-file results.xml
72
+ ```
73
+
74
+ ### GitHub Actions
75
+
76
+ GitHub Actions workflow commands for inline annotations in workflow logs:
77
+
78
+ ```bash
79
+ flowlint scan . --format github-actions
80
+ ```
81
+
82
+ ## Command Reference
83
+
84
+ ### `scan`
85
+
86
+ Scan workflow files for issues.
87
+
88
+ ```bash
89
+ flowlint scan [path] [options]
90
+ ```
91
+
92
+ **Arguments:**
93
+ - `path` - Directory to scan (default: current directory)
94
+
95
+ **Options:**
96
+ - `--config <path>` - Path to `.flowlint.yml` config file
97
+ - `--format <format>` - Output format: `stylish`, `json`, `sarif`, `junit`, `github-actions` (default: `stylish`)
98
+ - `--out-file <path>` - Write results to file (format inferred from extension or `--format`)
99
+ - `--fail-on-error` - Exit with code 1 if errors found
100
+
101
+ **Examples:**
102
+
103
+ ```bash
104
+ # Scan current directory with default config
105
+ flowlint scan
106
+
107
+ # Scan specific directory
108
+ flowlint scan ./my-workflows
109
+
110
+ # Use custom config
111
+ flowlint scan --config custom-config.yml
112
+
113
+ # Output JSON to file
114
+ flowlint scan --format json --out-file report.json
115
+
116
+ # Generate SARIF for GitHub Code Scanning
117
+ flowlint scan --format sarif --out-file results.sarif
118
+
119
+ # Generate JUnit XML for Jenkins
120
+ flowlint scan --format junit --out-file test-results.xml
121
+
122
+ # Fail CI build if errors found
123
+ flowlint scan --fail-on-error
124
+ ```
125
+
126
+ ## CI/CD Integration
127
+
128
+ ### GitHub Actions
129
+
130
+ #### Option 1: SARIF Upload (Recommended)
131
+
132
+ Upload results to GitHub Code Scanning for permanent PR annotations:
133
+
134
+ ```yaml
135
+ name: FlowLint
136
+
137
+ on:
138
+ pull_request:
139
+ paths:
140
+ - '**.json'
141
+
142
+ jobs:
143
+ flowlint:
144
+ runs-on: ubuntu-latest
145
+ permissions:
146
+ contents: read
147
+ security-events: write # Required for SARIF upload
148
+ steps:
149
+ - uses: actions/checkout@v4
150
+
151
+ - name: Run FlowLint
152
+ run: npx flowlint scan --format sarif --out-file results.sarif
153
+ continue-on-error: true # Don't fail workflow on findings
154
+
155
+ - name: Upload SARIF results
156
+ uses: github/codeql-action/upload-sarif@v3
157
+ if: always()
158
+ with:
159
+ sarif_file: results.sarif
160
+ ```
161
+
162
+ #### Option 2: Workflow Annotations
163
+
164
+ Show findings directly in workflow logs:
165
+
166
+ ```yaml
167
+ name: FlowLint
168
+
169
+ on:
170
+ pull_request:
171
+ paths:
172
+ - '**.json'
173
+
174
+ jobs:
175
+ flowlint:
176
+ runs-on: ubuntu-latest
177
+ steps:
178
+ - uses: actions/checkout@v4
179
+
180
+ - name: Run FlowLint
181
+ run: npx flowlint scan --format github-actions --fail-on-error
182
+ ```
183
+
184
+ #### Option 3: Both (Best of Both Worlds)
185
+
186
+ Combine both approaches for immediate feedback and permanent annotations:
187
+
188
+ ```yaml
189
+ name: FlowLint
190
+
191
+ on:
192
+ pull_request:
193
+ paths:
194
+ - '**.json'
195
+
196
+ jobs:
197
+ flowlint:
198
+ runs-on: ubuntu-latest
199
+ permissions:
200
+ contents: read
201
+ security-events: write
202
+ steps:
203
+ - uses: actions/checkout@v4
204
+
205
+ - name: Run FlowLint (Workflow Annotations)
206
+ run: npx flowlint scan --format github-actions
207
+ continue-on-error: true
208
+
209
+ - name: Run FlowLint (SARIF)
210
+ run: npx flowlint scan --format sarif --out-file results.sarif
211
+ continue-on-error: true
212
+
213
+ - name: Upload SARIF results
214
+ uses: github/codeql-action/upload-sarif@v3
215
+ if: always()
216
+ with:
217
+ sarif_file: results.sarif
218
+
219
+ - name: Check for blocking issues
220
+ run: npx flowlint scan --fail-on-error
221
+ ```
222
+
223
+ ### GitLab CI
224
+
225
+ ```yaml
226
+ flowlint:
227
+ stage: test
228
+ image: node:22
229
+ script:
230
+ - npx flowlint scan --format junit --out-file flowlint-results.xml
231
+ artifacts:
232
+ when: always
233
+ reports:
234
+ junit: flowlint-results.xml
235
+ ```
236
+
237
+ ### Jenkins
238
+
239
+ ```groovy
240
+ pipeline {
241
+ agent any
242
+
243
+ stages {
244
+ stage('FlowLint') {
245
+ steps {
246
+ sh 'npx flowlint scan --format junit --out-file flowlint-results.xml'
247
+ }
248
+ post {
249
+ always {
250
+ junit 'flowlint-results.xml'
251
+ }
252
+ }
253
+ }
254
+ }
255
+ }
256
+ ```
257
+
258
+ ### CircleCI
259
+
260
+ ```yaml
261
+ version: 2.1
262
+
263
+ jobs:
264
+ flowlint:
265
+ docker:
266
+ - image: cimg/node:22.0
267
+ steps:
268
+ - checkout
269
+ - run:
270
+ name: Run FlowLint
271
+ command: npx flowlint scan --format junit --out-file test-results/flowlint.xml
272
+ - store_test_results:
273
+ path: test-results
274
+ ```
275
+
276
+ ### Azure Pipelines
277
+
278
+ ```yaml
279
+ trigger:
280
+ branches:
281
+ include:
282
+ - main
283
+ - develop
284
+
285
+ pool:
286
+ vmImage: 'ubuntu-latest'
287
+
288
+ steps:
289
+ - task: NodeTool@0
290
+ inputs:
291
+ versionSpec: '22.x'
292
+
293
+ - script: npx flowlint scan --format junit --out-file flowlint-results.xml
294
+ displayName: 'Run FlowLint'
295
+
296
+ - task: PublishTestResults@2
297
+ condition: always()
298
+ inputs:
299
+ testResultsFormat: 'JUnit'
300
+ testResultsFiles: 'flowlint-results.xml'
301
+ failTaskOnFailedTests: true
302
+ ```
303
+
304
+ ## Configuration
305
+
306
+ Create a `.flowlint.yml` file in your repository root:
307
+
308
+ ```yaml
309
+ files:
310
+ include:
311
+ - '**/*.json'
312
+ ignore:
313
+ - 'node_modules/**'
314
+ - 'dist/**'
315
+
316
+ report:
317
+ annotations: true
318
+ summary_limit: 100
319
+
320
+ rules:
321
+ R1:
322
+ enabled: true
323
+ R2:
324
+ enabled: true
325
+ # ... etc
326
+ ```
327
+
328
+ See [FlowLint documentation](https://flowlint.dev) for complete configuration reference.
329
+
330
+ ## Exit Codes
331
+
332
+ - `0` - Success (no errors or only warnings/suggestions)
333
+ - `1` - Analysis found blocking issues (when using `--fail-on-error`)
334
+ - `2` - Runtime error (invalid config, file not found, etc.)
335
+
336
+ ## Rule Severity Levels
337
+
338
+ - **must** - Blocks PR (errors) - Critical issues that must be fixed
339
+ - **should** - Warnings - Important issues that should be addressed
340
+ - **nit** - Suggestions - Minor improvements
341
+
342
+ ## Supported Rules
343
+
344
+ FlowLint currently implements the following rules:
345
+
346
+ - **R1** - Rate Limit & Retry
347
+ - **R2** - Error Handling
348
+ - **R3** - Dead End Detection
349
+ - **R4** - Credential Validation
350
+ - **R5** - Idempotency
351
+ - **R6** - Stop & Error Nodes
352
+
353
+ See [RULES.md](https://github.com/Replikanti/flowlint-app/blob/main/RULES.md) for detailed rule documentation.
354
+
355
+ ## Troubleshooting
356
+
357
+ ### No workflows found
358
+
359
+ Make sure your workflow files match the glob patterns in `.flowlint.yml`:
360
+
361
+ ```yaml
362
+ files:
363
+ include:
364
+ - '**/*.json' # Adjust pattern as needed
365
+ ```
366
+
367
+ ### SARIF upload fails in GitHub Actions
368
+
369
+ Ensure you have the correct permissions:
370
+
371
+ ```yaml
372
+ permissions:
373
+ contents: read
374
+ security-events: write # Required for SARIF upload
375
+ ```
376
+
377
+ ### JUnit results not showing in CI
378
+
379
+ Make sure the file path in your CI configuration matches the `--out-file` path.
380
+
381
+ ## Links
382
+
383
+ - [Documentation](https://flowlint.dev)
384
+ - [GitHub Repository](https://github.com/Replikanti/flowlint-app)
385
+ - [Issue Tracker](https://github.com/Replikanti/flowlint-app/issues)
386
+ - [npm Package](https://www.npmjs.com/package/flowlint)
387
+
388
+ ## License
389
+
390
+ MIT
@@ -19,29 +19,54 @@ const local_file_source_1 = require("../providers/local-file-source");
19
19
  const local_config_provider_1 = require("../providers/local-config-provider");
20
20
  const console_reporter_1 = require("../reporters/console-reporter");
21
21
  const json_reporter_1 = require("../reporters/json-reporter");
22
+ const sarif_reporter_1 = require("../reporters/sarif-reporter");
23
+ const junit_reporter_1 = require("../reporters/junit-reporter");
24
+ const github_actions_reporter_1 = require("../reporters/github-actions-reporter");
22
25
  const review_1 = require("../packages/review");
23
26
  exports.scanCommand = new commander_1.Command('scan')
24
27
  .description('Scan workflow files for issues')
25
28
  .argument('[path]', 'Directory to scan', '.')
26
29
  .option('--config <path>', 'Path to .flowlint.yml config file')
27
- .option('--format <format>', 'Output format: stylish|json', 'stylish')
28
- .option('--out-file <path>', 'Write JSON results to file (implies --format json)')
30
+ .option('--format <format>', 'Output format: stylish|json|sarif|junit|github-actions', 'stylish')
31
+ .option('--out-file <path>', 'Write results to file (format inferred from extension or --format)')
29
32
  .option('--fail-on-error', 'Exit with code 1 if errors found')
30
33
  .action(async (scanPath, options) => {
31
34
  try {
32
35
  const absolutePath = path_1.default.resolve(process.cwd(), scanPath);
33
36
  const configPath = options.config ? path_1.default.resolve(process.cwd(), options.config) : undefined;
34
- const format = options.outFile ? 'json' : options.format;
37
+ // Determine format from options or file extension
38
+ let format = options.format;
39
+ if (options.outFile && !options.format) {
40
+ format = inferFormatFromFilename(options.outFile);
41
+ }
42
+ // Validate format
43
+ const validFormats = ['stylish', 'json', 'sarif', 'junit', 'github-actions'];
44
+ if (!validFormats.includes(format)) {
45
+ console.error(`Invalid format: ${format}. Valid formats: ${validFormats.join(', ')}`);
46
+ process.exit(2);
47
+ }
35
48
  // Initialize providers
36
49
  const fileSource = new local_file_source_1.LocalFileSource(absolutePath);
37
50
  const configProvider = new local_config_provider_1.LocalConfigProvider(configPath);
38
51
  // Choose reporter based on format
39
52
  let reporter;
40
- if (format === 'json' || options.outFile) {
41
- reporter = new json_reporter_1.JsonReporter(options.outFile);
42
- }
43
- else {
44
- reporter = new console_reporter_1.ConsoleReporter();
53
+ switch (format) {
54
+ case 'json':
55
+ reporter = new json_reporter_1.JsonReporter(options.outFile);
56
+ break;
57
+ case 'sarif':
58
+ reporter = new sarif_reporter_1.SarifReporter(options.outFile);
59
+ break;
60
+ case 'junit':
61
+ reporter = new junit_reporter_1.JunitReporter(options.outFile);
62
+ break;
63
+ case 'github-actions':
64
+ reporter = new github_actions_reporter_1.GithubActionsReporter();
65
+ break;
66
+ case 'stylish':
67
+ default:
68
+ reporter = new console_reporter_1.ConsoleReporter();
69
+ break;
45
70
  }
46
71
  // Run analysis
47
72
  const engine = new review_1.DefaultAnalysisEngine(fileSource, configProvider, reporter);
@@ -56,4 +81,24 @@ exports.scanCommand = new commander_1.Command('scan')
56
81
  process.exit(2);
57
82
  }
58
83
  });
84
+ /**
85
+ * Infer output format from filename extension
86
+ */
87
+ function inferFormatFromFilename(filename) {
88
+ const ext = path_1.default.extname(filename).toLowerCase();
89
+ switch (ext) {
90
+ case '.json':
91
+ // Check if it's SARIF based on filename
92
+ if (filename.includes('sarif')) {
93
+ return 'sarif';
94
+ }
95
+ return 'json';
96
+ case '.sarif':
97
+ return 'sarif';
98
+ case '.xml':
99
+ return 'junit';
100
+ default:
101
+ return 'json'; // Default to JSON
102
+ }
103
+ }
59
104
  //# sourceMappingURL=scan.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"scan.js","sourceRoot":"","sources":["../../../../../apps/cli/src/commands/scan.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;;;;;AAEH,yCAAoC;AACpC,gDAAwB;AACxB,sEAAiE;AACjE,8EAAyE;AACzE,oEAAgE;AAChE,8DAA0D;AAC1D,4CAAwD;AAE3C,QAAA,WAAW,GAAG,IAAI,mBAAO,CAAC,MAAM,CAAC;KAC3C,WAAW,CAAC,gCAAgC,CAAC;KAC7C,QAAQ,CAAC,QAAQ,EAAE,mBAAmB,EAAE,GAAG,CAAC;KAC5C,MAAM,CAAC,iBAAiB,EAAE,mCAAmC,CAAC;KAC9D,MAAM,CAAC,mBAAmB,EAAE,6BAA6B,EAAE,SAAS,CAAC;KACrE,MAAM,CAAC,mBAAmB,EAAE,oDAAoD,CAAC;KACjF,MAAM,CAAC,iBAAiB,EAAE,kCAAkC,CAAC;KAC7D,MAAM,CAAC,KAAK,EAAE,QAAgB,EAAE,OAAY,EAAE,EAAE;IAC/C,IAAI,CAAC;QACH,MAAM,YAAY,GAAG,cAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,QAAQ,CAAC,CAAC;QAC3D,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,cAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAC5F,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC;QAEzD,uBAAuB;QACvB,MAAM,UAAU,GAAG,IAAI,mCAAe,CAAC,YAAY,CAAC,CAAC;QACrD,MAAM,cAAc,GAAG,IAAI,2CAAmB,CAAC,UAAU,CAAC,CAAC;QAE3D,kCAAkC;QAClC,IAAI,QAAQ,CAAC;QACb,IAAI,MAAM,KAAK,MAAM,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;YACzC,QAAQ,GAAG,IAAI,4BAAY,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAC/C,CAAC;aAAM,CAAC;YACN,QAAQ,GAAG,IAAI,kCAAe,EAAE,CAAC;QACnC,CAAC;QAED,eAAe;QACf,MAAM,MAAM,GAAG,IAAI,8BAAqB,CAAC,UAAU,EAAE,cAAc,EAAE,QAAQ,CAAC,CAAC;QAC/E,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,OAAO,EAAE,CAAC;QAEvC,6BAA6B;QAC7B,IAAI,OAAO,CAAC,WAAW,IAAI,OAAO,CAAC,iBAAiB,EAAE,CAAC;YACrD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QAChF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"scan.js","sourceRoot":"","sources":["../../../../../apps/cli/src/commands/scan.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * GitHub Actions Reporter
3
+ * Outputs findings using GitHub Actions workflow commands
4
+ * Suitable for:
5
+ * - GitHub Actions workflows
6
+ * - Immediate inline annotations in workflow logs
7
+ * - Complementary to SARIF (SARIF for Code Scanning, this for workflow logs)
8
+ *
9
+ * Format: ::error file={file},line={line}::{message}
10
+ * Docs: https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions
11
+ */
12
+ import type { AnalysisResult, Reporter } from 'packages/review/providers';
13
+ export declare class GithubActionsReporter implements Reporter {
14
+ report(results: AnalysisResult[]): Promise<void>;
15
+ private outputWorkflowCommand;
16
+ private severityToCommand;
17
+ private buildProperties;
18
+ private buildMessage;
19
+ private outputSummary;
20
+ /**
21
+ * Escape property values for GitHub Actions workflow commands
22
+ * Properties use comma as delimiter, so commas must be escaped
23
+ */
24
+ private escapeProperty;
25
+ /**
26
+ * Escape data values for GitHub Actions workflow commands
27
+ * Data values support newlines but special characters must be escaped
28
+ */
29
+ private escapeData;
30
+ }
@@ -0,0 +1,104 @@
1
+ "use strict";
2
+ /**
3
+ * GitHub Actions Reporter
4
+ * Outputs findings using GitHub Actions workflow commands
5
+ * Suitable for:
6
+ * - GitHub Actions workflows
7
+ * - Immediate inline annotations in workflow logs
8
+ * - Complementary to SARIF (SARIF for Code Scanning, this for workflow logs)
9
+ *
10
+ * Format: ::error file={file},line={line}::{message}
11
+ * Docs: https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions
12
+ */
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.GithubActionsReporter = void 0;
15
+ const findings_1 = require("../packages/review/utils/findings");
16
+ class GithubActionsReporter {
17
+ async report(results) {
18
+ const allFindings = results.flatMap((r) => r.findings);
19
+ // Output workflow commands for each finding
20
+ for (const result of results) {
21
+ for (const finding of result.findings) {
22
+ this.outputWorkflowCommand(finding);
23
+ }
24
+ }
25
+ // Output summary
26
+ this.outputSummary(allFindings);
27
+ }
28
+ outputWorkflowCommand(finding) {
29
+ const command = this.severityToCommand(finding.severity);
30
+ const properties = this.buildProperties(finding);
31
+ const message = this.buildMessage(finding);
32
+ console.log(`::${command} ${properties}::${message}`);
33
+ }
34
+ severityToCommand(severity) {
35
+ switch (severity) {
36
+ case 'must':
37
+ return 'error';
38
+ case 'should':
39
+ return 'warning';
40
+ case 'nit':
41
+ return 'notice';
42
+ }
43
+ }
44
+ buildProperties(finding) {
45
+ const props = [];
46
+ props.push(`file=${this.escapeProperty(finding.path)}`);
47
+ if (finding.line) {
48
+ props.push(`line=${finding.line}`);
49
+ }
50
+ props.push(`title=${this.escapeProperty(finding.rule)}`);
51
+ return props.join(',');
52
+ }
53
+ buildMessage(finding) {
54
+ let message = finding.message;
55
+ if (finding.nodeId) {
56
+ message += ` (Node: ${finding.nodeId})`;
57
+ }
58
+ if (finding.raw_details) {
59
+ // GitHub Actions workflow commands support newlines in messages
60
+ message += `\n\n${finding.raw_details}`;
61
+ }
62
+ if (finding.documentationUrl) {
63
+ message += `\n\nSee: ${finding.documentationUrl}`;
64
+ }
65
+ return this.escapeData(message);
66
+ }
67
+ outputSummary(findings) {
68
+ const summary = (0, findings_1.countFindingsBySeverity)(findings);
69
+ console.log();
70
+ console.log('::group::FlowLint Summary');
71
+ if (summary.total === 0) {
72
+ console.log('✓ No issues found');
73
+ }
74
+ else {
75
+ console.log(`Total findings: ${summary.total}`);
76
+ if (summary.must > 0) {
77
+ console.log(` - Errors (must-fix): ${summary.must}`);
78
+ }
79
+ if (summary.should > 0) {
80
+ console.log(` - Warnings (should-fix): ${summary.should}`);
81
+ }
82
+ if (summary.nit > 0) {
83
+ console.log(` - Notices (nit): ${summary.nit}`);
84
+ }
85
+ }
86
+ console.log('::endgroup::');
87
+ }
88
+ /**
89
+ * Escape property values for GitHub Actions workflow commands
90
+ * Properties use comma as delimiter, so commas must be escaped
91
+ */
92
+ escapeProperty(value) {
93
+ return value.replace(/%/g, '%25').replace(/,/g, '%2C').replace(/:/g, '%3A');
94
+ }
95
+ /**
96
+ * Escape data values for GitHub Actions workflow commands
97
+ * Data values support newlines but special characters must be escaped
98
+ */
99
+ escapeData(value) {
100
+ return value.replace(/%/g, '%25').replace(/\r/g, '%0D').replace(/\n/g, '%0A');
101
+ }
102
+ }
103
+ exports.GithubActionsReporter = GithubActionsReporter;
104
+ //# sourceMappingURL=github-actions-reporter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github-actions-reporter.js","sourceRoot":"","sources":["../../../../../apps/cli/src/reporters/github-actions-reporter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * JUnit Reporter
3
+ * Outputs findings in JUnit XML format
4
+ * Suitable for:
5
+ * - Jenkins
6
+ * - GitLab CI
7
+ * - CircleCI
8
+ * - Azure Pipelines
9
+ * - Other CI/CD platforms that support JUnit test results
10
+ *
11
+ * Each workflow file becomes a test suite
12
+ * Each finding becomes a test failure
13
+ */
14
+ import type { AnalysisResult, Reporter } from 'packages/review/providers';
15
+ export declare class JunitReporter implements Reporter {
16
+ private outputFile?;
17
+ constructor(outputFile?: string | undefined);
18
+ report(results: AnalysisResult[]): Promise<void>;
19
+ private buildJunitXml;
20
+ private addTestSuite;
21
+ private addTestCase;
22
+ private buildFailureMessage;
23
+ private buildFailureDetails;
24
+ private severityToFailureType;
25
+ }
@@ -0,0 +1,142 @@
1
+ "use strict";
2
+ /**
3
+ * JUnit Reporter
4
+ * Outputs findings in JUnit XML format
5
+ * Suitable for:
6
+ * - Jenkins
7
+ * - GitLab CI
8
+ * - CircleCI
9
+ * - Azure Pipelines
10
+ * - Other CI/CD platforms that support JUnit test results
11
+ *
12
+ * Each workflow file becomes a test suite
13
+ * Each finding becomes a test failure
14
+ */
15
+ var __importDefault = (this && this.__importDefault) || function (mod) {
16
+ return (mod && mod.__esModule) ? mod : { "default": mod };
17
+ };
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.JunitReporter = void 0;
20
+ const fs_1 = __importDefault(require("fs"));
21
+ const xmlbuilder2_1 = require("xmlbuilder2");
22
+ class JunitReporter {
23
+ constructor(outputFile) {
24
+ this.outputFile = outputFile;
25
+ }
26
+ async report(results) {
27
+ const xml = this.buildJunitXml(results);
28
+ if (this.outputFile) {
29
+ // Write to file
30
+ fs_1.default.writeFileSync(this.outputFile, xml, 'utf8');
31
+ console.log(`✓ JUnit report written to ${this.outputFile}`);
32
+ }
33
+ else {
34
+ // Print to stdout
35
+ console.log(xml);
36
+ }
37
+ }
38
+ buildJunitXml(results) {
39
+ const timestamp = new Date().toISOString();
40
+ // Count total tests: each finding is a test, or 1 passing test if no findings
41
+ const totalTests = results.reduce((sum, r) => sum + (r.findings.length === 0 ? 1 : r.findings.length), 0);
42
+ const totalFailures = results.reduce((sum, r) => sum + r.findings.length, 0);
43
+ const totalTime = 0; // We don't track execution time
44
+ // Create root element
45
+ const root = (0, xmlbuilder2_1.create)({ version: '1.0', encoding: 'UTF-8' })
46
+ .ele('testsuites', {
47
+ name: 'FlowLint',
48
+ tests: totalTests,
49
+ failures: totalFailures,
50
+ errors: 0,
51
+ time: totalTime,
52
+ timestamp,
53
+ });
54
+ // Add a test suite for each file
55
+ for (const result of results) {
56
+ this.addTestSuite(root, result);
57
+ }
58
+ return root.end({ prettyPrint: true });
59
+ }
60
+ addTestSuite(parent, result) {
61
+ const { file, findings } = result;
62
+ // If no findings, create one passing test case
63
+ const testCount = findings.length === 0 ? 1 : findings.length;
64
+ const failureCount = findings.length; // All findings are failures
65
+ const testsuite = parent.ele('testsuite', {
66
+ name: file.path,
67
+ tests: testCount,
68
+ failures: failureCount,
69
+ errors: 0,
70
+ time: 0,
71
+ timestamp: new Date().toISOString(),
72
+ });
73
+ // If no findings, add a passing test
74
+ if (findings.length === 0) {
75
+ testsuite.ele('testcase', {
76
+ name: 'No issues found',
77
+ classname: file.path,
78
+ time: 0,
79
+ });
80
+ }
81
+ else {
82
+ // Add a test case for each finding
83
+ for (const finding of findings) {
84
+ this.addTestCase(testsuite, finding, file.path);
85
+ }
86
+ }
87
+ }
88
+ addTestCase(parent, finding, filePath) {
89
+ const testcase = parent.ele('testcase', {
90
+ name: finding.rule,
91
+ classname: filePath,
92
+ time: 0,
93
+ });
94
+ // Add failure element
95
+ const failureMessage = this.buildFailureMessage(finding);
96
+ const failureType = this.severityToFailureType(finding.severity);
97
+ testcase.ele('failure', {
98
+ message: failureMessage,
99
+ type: failureType,
100
+ }).txt(this.buildFailureDetails(finding));
101
+ }
102
+ buildFailureMessage(finding) {
103
+ const location = finding.line ? `:${finding.line}` : '';
104
+ return `[${finding.severity.toUpperCase()}] ${finding.message} (${finding.path}${location})`;
105
+ }
106
+ buildFailureDetails(finding) {
107
+ const parts = [];
108
+ parts.push(`Rule: ${finding.rule}`);
109
+ parts.push(`Severity: ${finding.severity.toUpperCase()}`);
110
+ parts.push(`File: ${finding.path}`);
111
+ if (finding.line) {
112
+ parts.push(`Line: ${finding.line}`);
113
+ }
114
+ if (finding.nodeId) {
115
+ parts.push(`Node ID: ${finding.nodeId}`);
116
+ }
117
+ parts.push('');
118
+ parts.push(finding.message);
119
+ if (finding.raw_details) {
120
+ parts.push('');
121
+ parts.push('Details:');
122
+ parts.push(finding.raw_details);
123
+ }
124
+ if (finding.documentationUrl) {
125
+ parts.push('');
126
+ parts.push(`Documentation: ${finding.documentationUrl}`);
127
+ }
128
+ return parts.join('\n');
129
+ }
130
+ severityToFailureType(severity) {
131
+ switch (severity) {
132
+ case 'must':
133
+ return 'FlowLintError';
134
+ case 'should':
135
+ return 'FlowLintWarning';
136
+ case 'nit':
137
+ return 'FlowLintSuggestion';
138
+ }
139
+ }
140
+ }
141
+ exports.JunitReporter = JunitReporter;
142
+ //# sourceMappingURL=junit-reporter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"junit-reporter.js","sourceRoot":"","sources":["../../../../../apps/cli/src/reporters/junit-reporter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * SARIF Reporter
3
+ * Outputs findings in SARIF 2.1.0 format
4
+ * Suitable for:
5
+ * - GitHub Code Scanning
6
+ * - Azure DevOps
7
+ * - Security scanners and analysis platforms
8
+ * - CI/CD pipelines
9
+ *
10
+ * Spec: https://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html
11
+ */
12
+ import type { AnalysisResult, Reporter } from 'packages/review/providers';
13
+ export declare class SarifReporter implements Reporter {
14
+ private outputFile?;
15
+ constructor(outputFile?: string | undefined);
16
+ report(results: AnalysisResult[]): Promise<void>;
17
+ private buildSarifLog;
18
+ private extractUniqueRules;
19
+ private findingToSarifResult;
20
+ private severityToSarifLevel;
21
+ }
@@ -0,0 +1,125 @@
1
+ "use strict";
2
+ /**
3
+ * SARIF Reporter
4
+ * Outputs findings in SARIF 2.1.0 format
5
+ * Suitable for:
6
+ * - GitHub Code Scanning
7
+ * - Azure DevOps
8
+ * - Security scanners and analysis platforms
9
+ * - CI/CD pipelines
10
+ *
11
+ * Spec: https://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html
12
+ */
13
+ var __importDefault = (this && this.__importDefault) || function (mod) {
14
+ return (mod && mod.__esModule) ? mod : { "default": mod };
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.SarifReporter = void 0;
18
+ const fs_1 = __importDefault(require("fs"));
19
+ class SarifReporter {
20
+ constructor(outputFile) {
21
+ this.outputFile = outputFile;
22
+ }
23
+ async report(results) {
24
+ const sarifLog = this.buildSarifLog(results);
25
+ const json = JSON.stringify(sarifLog, null, 2);
26
+ if (this.outputFile) {
27
+ // Write to file
28
+ fs_1.default.writeFileSync(this.outputFile, json, 'utf8');
29
+ console.log(`✓ SARIF report written to ${this.outputFile}`);
30
+ }
31
+ else {
32
+ // Print to stdout
33
+ console.log(json);
34
+ }
35
+ }
36
+ buildSarifLog(results) {
37
+ const allFindings = results.flatMap((r) => r.findings);
38
+ // Extract unique rules
39
+ const uniqueRules = this.extractUniqueRules(allFindings);
40
+ // Build artifacts list (analyzed files)
41
+ const artifacts = results.map((r) => ({
42
+ location: {
43
+ uri: r.file.path,
44
+ },
45
+ }));
46
+ // Build SARIF results
47
+ const sarifResults = [];
48
+ for (const result of results) {
49
+ for (const finding of result.findings) {
50
+ sarifResults.push(this.findingToSarifResult(finding));
51
+ }
52
+ }
53
+ return {
54
+ version: '2.1.0',
55
+ $schema: 'https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json',
56
+ runs: [
57
+ {
58
+ tool: {
59
+ driver: {
60
+ name: 'FlowLint',
61
+ version: '0.3.8',
62
+ informationUri: 'https://flowlint.dev',
63
+ rules: uniqueRules,
64
+ },
65
+ },
66
+ results: sarifResults,
67
+ artifacts,
68
+ },
69
+ ],
70
+ };
71
+ }
72
+ extractUniqueRules(findings) {
73
+ const ruleMap = new Map();
74
+ for (const finding of findings) {
75
+ if (!ruleMap.has(finding.rule)) {
76
+ ruleMap.set(finding.rule, {
77
+ id: finding.rule,
78
+ shortDescription: {
79
+ text: finding.message,
80
+ },
81
+ helpUri: finding.documentationUrl,
82
+ });
83
+ }
84
+ }
85
+ return Array.from(ruleMap.values());
86
+ }
87
+ findingToSarifResult(finding) {
88
+ const result = {
89
+ ruleId: finding.rule,
90
+ level: this.severityToSarifLevel(finding.severity),
91
+ message: {
92
+ text: finding.raw_details ? `${finding.message}\n\n${finding.raw_details}` : finding.message,
93
+ },
94
+ locations: [
95
+ {
96
+ physicalLocation: {
97
+ artifactLocation: {
98
+ uri: finding.path,
99
+ },
100
+ region: finding.line ? { startLine: finding.line } : undefined,
101
+ },
102
+ },
103
+ ],
104
+ };
105
+ // Add nodeId as property if present
106
+ if (finding.nodeId) {
107
+ result.properties = {
108
+ nodeId: finding.nodeId,
109
+ };
110
+ }
111
+ return result;
112
+ }
113
+ severityToSarifLevel(severity) {
114
+ switch (severity) {
115
+ case 'must':
116
+ return 'error';
117
+ case 'should':
118
+ return 'warning';
119
+ case 'nit':
120
+ return 'note';
121
+ }
122
+ }
123
+ }
124
+ exports.SarifReporter = SarifReporter;
125
+ //# sourceMappingURL=sarif-reporter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sarif-reporter.js","sourceRoot":"","sources":["../../../../../apps/cli/src/reporters/sarif-reporter.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flowlint",
3
- "version": "0.3.7",
3
+ "version": "0.3.8",
4
4
  "description": "Static analysis tool for n8n workflows - detect issues early, fix them faster",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",
@@ -27,6 +27,7 @@
27
27
  "micromatch": "^4.0.8",
28
28
  "pino": "^10.1.0",
29
29
  "pino-pretty": "^13.1.2",
30
+ "xmlbuilder2": "^4.0.3",
30
31
  "yaml": "^2.4.0"
31
32
  },
32
33
  "keywords": [