flowlint 0.5.1 → 0.6.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.
Files changed (108) hide show
  1. package/README.md +45 -355
  2. package/dist/cli.js +163 -21
  3. package/dist/cli.js.map +1 -1
  4. package/package.json +52 -57
  5. package/dist/cli.d.ts +0 -8
  6. package/dist/commands/init.d.ts +0 -8
  7. package/dist/commands/init.js +0 -34
  8. package/dist/commands/init.js.map +0 -1
  9. package/dist/commands/scan.d.ts +0 -11
  10. package/dist/commands/scan.js +0 -104
  11. package/dist/commands/scan.js.map +0 -1
  12. package/dist/packages/config/flowlint-config.d.ts +0 -73
  13. package/dist/packages/config/flowlint-config.js +0 -120
  14. package/dist/packages/config/flowlint-config.js.map +0 -1
  15. package/dist/packages/config/index.d.ts +0 -4
  16. package/dist/packages/config/index.js +0 -21
  17. package/dist/packages/config/index.js.map +0 -1
  18. package/dist/packages/github/client.d.ts +0 -2
  19. package/dist/packages/github/client.js +0 -94
  20. package/dist/packages/github/client.js.map +0 -1
  21. package/dist/packages/logger/index.d.ts +0 -11
  22. package/dist/packages/logger/index.js +0 -40
  23. package/dist/packages/logger/index.js.map +0 -1
  24. package/dist/packages/observability/collectors.d.ts +0 -40
  25. package/dist/packages/observability/collectors.js +0 -75
  26. package/dist/packages/observability/collectors.js.map +0 -1
  27. package/dist/packages/observability/index.d.ts +0 -10
  28. package/dist/packages/observability/index.js +0 -35
  29. package/dist/packages/observability/index.js.map +0 -1
  30. package/dist/packages/observability/metrics.d.ts +0 -119
  31. package/dist/packages/observability/metrics.js +0 -194
  32. package/dist/packages/observability/metrics.js.map +0 -1
  33. package/dist/packages/observability/middleware.d.ts +0 -32
  34. package/dist/packages/observability/middleware.js +0 -58
  35. package/dist/packages/observability/middleware.js.map +0 -1
  36. package/dist/packages/review/analysis-engine.d.ts +0 -19
  37. package/dist/packages/review/analysis-engine.js +0 -111
  38. package/dist/packages/review/analysis-engine.js.map +0 -1
  39. package/dist/packages/review/index.d.ts +0 -12
  40. package/dist/packages/review/index.js +0 -29
  41. package/dist/packages/review/index.js.map +0 -1
  42. package/dist/packages/review/parser-n8n.d.ts +0 -2
  43. package/dist/packages/review/parser-n8n.js +0 -122
  44. package/dist/packages/review/parser-n8n.js.map +0 -1
  45. package/dist/packages/review/providers/github.d.ts +0 -62
  46. package/dist/packages/review/providers/github.js +0 -275
  47. package/dist/packages/review/providers/github.js.map +0 -1
  48. package/dist/packages/review/providers.d.ts +0 -106
  49. package/dist/packages/review/providers.js +0 -12
  50. package/dist/packages/review/providers.js.map +0 -1
  51. package/dist/packages/review/reporter.d.ts +0 -17
  52. package/dist/packages/review/reporter.js +0 -59
  53. package/dist/packages/review/reporter.js.map +0 -1
  54. package/dist/packages/review/rules/index.d.ts +0 -9
  55. package/dist/packages/review/rules/index.js +0 -415
  56. package/dist/packages/review/rules/index.js.map +0 -1
  57. package/dist/packages/review/rules/rule-utils.d.ts +0 -36
  58. package/dist/packages/review/rules/rule-utils.js +0 -75
  59. package/dist/packages/review/rules/rule-utils.js.map +0 -1
  60. package/dist/packages/review/schemas/index.d.ts +0 -17
  61. package/dist/packages/review/schemas/index.js +0 -167
  62. package/dist/packages/review/schemas/index.js.map +0 -1
  63. package/dist/packages/review/schemas/n8n-workflow.schema.json +0 -177
  64. package/dist/packages/review/sniffer.d.ts +0 -15
  65. package/dist/packages/review/sniffer.js +0 -47
  66. package/dist/packages/review/sniffer.js.map +0 -1
  67. package/dist/packages/review/types.d.ts +0 -40
  68. package/dist/packages/review/types.js +0 -3
  69. package/dist/packages/review/types.js.map +0 -1
  70. package/dist/packages/review/utils/findings.d.ts +0 -23
  71. package/dist/packages/review/utils/findings.js +0 -34
  72. package/dist/packages/review/utils/findings.js.map +0 -1
  73. package/dist/packages/review/utils/merge.d.ts +0 -12
  74. package/dist/packages/review/utils/merge.js +0 -40
  75. package/dist/packages/review/utils/merge.js.map +0 -1
  76. package/dist/packages/review/utils.d.ts +0 -60
  77. package/dist/packages/review/utils.js +0 -214
  78. package/dist/packages/review/utils.js.map +0 -1
  79. package/dist/packages/tracing/github-tracer.d.ts +0 -38
  80. package/dist/packages/tracing/github-tracer.js +0 -79
  81. package/dist/packages/tracing/github-tracer.js.map +0 -1
  82. package/dist/packages/tracing/index.d.ts +0 -81
  83. package/dist/packages/tracing/index.js +0 -240
  84. package/dist/packages/tracing/index.js.map +0 -1
  85. package/dist/packages/tracing/tracer.d.ts +0 -30
  86. package/dist/packages/tracing/tracer.js +0 -141
  87. package/dist/packages/tracing/tracer.js.map +0 -1
  88. package/dist/providers/local-config-provider.d.ts +0 -11
  89. package/dist/providers/local-config-provider.js +0 -39
  90. package/dist/providers/local-config-provider.js.map +0 -1
  91. package/dist/providers/local-file-source.d.ts +0 -13
  92. package/dist/providers/local-file-source.js +0 -47
  93. package/dist/providers/local-file-source.js.map +0 -1
  94. package/dist/reporters/console-reporter.d.ts +0 -8
  95. package/dist/reporters/console-reporter.js +0 -75
  96. package/dist/reporters/console-reporter.js.map +0 -1
  97. package/dist/reporters/github-actions-reporter.d.ts +0 -30
  98. package/dist/reporters/github-actions-reporter.js +0 -104
  99. package/dist/reporters/github-actions-reporter.js.map +0 -1
  100. package/dist/reporters/json-reporter.d.ts +0 -14
  101. package/dist/reporters/json-reporter.js +0 -57
  102. package/dist/reporters/json-reporter.js.map +0 -1
  103. package/dist/reporters/junit-reporter.d.ts +0 -25
  104. package/dist/reporters/junit-reporter.js +0 -142
  105. package/dist/reporters/junit-reporter.js.map +0 -1
  106. package/dist/reporters/sarif-reporter.d.ts +0 -21
  107. package/dist/reporters/sarif-reporter.js +0 -125
  108. package/dist/reporters/sarif-reporter.js.map +0 -1
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
- # FlowLint CLI
1
+ # FlowLint CLI
2
2
 
3
- Static analysis tool for n8n workflows - detect issues early, fix them faster.
3
+ Command-line tool for static analysis of n8n workflows.
4
4
 
5
5
  ## Installation
6
6
 
@@ -8,391 +8,81 @@ Static analysis tool for n8n workflows - detect issues early, fix them faster.
8
8
  npm install -g flowlint
9
9
  ```
10
10
 
11
- Or use with npx (no installation required):
11
+ Or use npx:
12
12
 
13
13
  ```bash
14
- npx flowlint scan ./workflows
14
+ npx flowlint scan .
15
15
  ```
16
16
 
17
- ## Quick Start
17
+ ## Usage
18
18
 
19
- Scan workflows in the current directory:
19
+ ### Scan workflows
20
20
 
21
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
22
+ # Scan current directory
105
23
  flowlint scan
106
24
 
107
25
  # 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
26
+ flowlint scan ./workflows
118
27
 
119
- # Generate JUnit XML for Jenkins
120
- flowlint scan --format junit --out-file test-results.xml
28
+ # Output as JSON
29
+ flowlint scan --format json
121
30
 
122
- # Fail CI build if errors found
31
+ # Fail on errors (for CI)
123
32
  flowlint scan --fail-on-error
124
33
  ```
125
34
 
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:
35
+ ### Initialize configuration
165
36
 
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
37
+ ```bash
38
+ flowlint init
274
39
  ```
275
40
 
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
- ```
41
+ Creates a `.flowlint.yml` file in the current directory.
303
42
 
304
43
  ## Configuration
305
44
 
306
- Create a `.flowlint.yml` file in your repository root:
45
+ Create a `.flowlint.yml` file:
307
46
 
308
47
  ```yaml
309
48
  files:
310
49
  include:
311
- - '**/*.json'
50
+ - "**/*.n8n.json"
312
51
  ignore:
313
- - 'node_modules/**'
314
- - 'dist/**'
315
-
316
- report:
317
- annotations: true
318
- summary_limit: 100
52
+ - "node_modules/**"
319
53
 
320
54
  rules:
321
- R1:
55
+ rate_limit_retry:
322
56
  enabled: true
323
- R2:
57
+ error_handling:
324
58
  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** - Idempotency
349
- - **R4** - Secrets
350
- - **R5** - Dead Ends
351
- - **R6** - Long Running
352
- - **R7** - Alert/Log Enforcement
353
- - **R8** - Unused Data
354
- - **R9** - Config Literals
355
- - **R10** - Naming Convention
356
- - **R11** - Deprecated Nodes
357
- - **R12** - Unhandled Error Path
358
- - **R13** - Webhook Acknowledgment
359
- - **R14** - HTTP Retry-After Compliance
360
-
361
- See [RULES.md](https://github.com/Replikanti/flowlint-examples/blob/main/README.md) for detailed rule documentation.
362
-
363
- ## Troubleshooting
59
+ # ... more rules
60
+ ```
61
+
62
+ ## Rules
63
+
64
+ | Rule | Description | Severity |
65
+ |------|-------------|----------|
66
+ | R1 | Rate limit retry | must |
67
+ | R2 | Error handling | must |
68
+ | R3 | Idempotency | should |
69
+ | R4 | Secrets exposure | must |
70
+ | R5 | Dead ends | nit |
71
+ | R6 | Long running | should |
72
+ | R7 | Alert/log enforcement | should |
73
+ | R8 | Unused data | nit |
74
+ | R9 | Config literals | should |
75
+ | R10 | Naming convention | nit |
76
+ | R11 | Deprecated nodes | should |
77
+ | R12 | Unhandled error path | must |
78
+ | R13 | Webhook acknowledgment | must |
79
+ | R14 | Retry-After compliance | should |
364
80
 
365
- ### No workflows found
366
-
367
- Make sure your workflow files match the glob patterns in `.flowlint.yml`:
368
-
369
- ```yaml
370
- files:
371
- include:
372
- - '**/*.json' # Adjust pattern as needed
373
- ```
374
-
375
- ### SARIF upload fails in GitHub Actions
376
-
377
- Ensure you have the correct permissions:
378
-
379
- ```yaml
380
- permissions:
381
- contents: read
382
- security-events: write # Required for SARIF upload
383
- ```
384
-
385
- ### JUnit results not showing in CI
386
-
387
- Make sure the file path in your CI configuration matches the `--out-file` path.
81
+ ## License
388
82
 
389
- ## Links
83
+ MIT
390
84
 
391
- - [Documentation](https://flowlint.dev)
392
- - [GitHub Repository](https://github.com/Replikanti/flowlint-app)
393
- - [Issue Tracker](https://github.com/Replikanti/flowlint-app/issues)
394
- - [npm Package](https://www.npmjs.com/package/flowlint)
395
85
 
396
- ## License
86
+ ## Dependencies
397
87
 
398
- MIT
88
+ This tool depends on [@replikanti/flowlint-core](https://www.npmjs.com/package/@replikanti/flowlint-core) for linting logic.
package/dist/cli.js CHANGED
@@ -1,27 +1,169 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
- /**
4
- * FlowLint CLI
5
- *
6
- * A command-line tool for static analysis of n8n workflow files.
7
- * Can be used locally, in CI pipelines, or pre-commit hooks.
8
- */
9
- Object.defineProperty(exports, "__esModule", { value: true });
10
- const commander_1 = require("commander");
11
- const scan_1 = require("./commands/scan");
12
- const init_1 = require("./commands/init");
13
- const program = new commander_1.Command();
14
- program
15
- .name('flowlint')
16
- .description('Static analysis tool for n8n workflows')
17
- .version('0.3.0');
18
- // Register commands
19
- program.addCommand(scan_1.scanCommand);
20
- program.addCommand(init_1.initCommand);
21
- // Parse and execute
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
18
+ // If the importer is in node compatibility mode or this is not an ESM
19
+ // file that has been converted to a CommonJS file using a Babel-
20
+ // compatible transform (i.e. "__esModule" has not been set), then set
21
+ // "default" to the CommonJS "module.exports" for node compatibility.
22
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
+ mod
24
+ ));
25
+
26
+ // src/cli.ts
27
+ var import_commander3 = require("commander");
28
+
29
+ // src/commands/scan.ts
30
+ var import_commander = require("commander");
31
+ var fs = __toESM(require("fs"));
32
+ var path = __toESM(require("path"));
33
+ var import_glob = require("glob");
34
+ var import_picocolors = __toESM(require("picocolors"));
35
+ var import_flowlint_core = require("@replikanti/flowlint-core");
36
+ var scanCommand = new import_commander.Command("scan").description("Scan workflow files for issues").argument("[path]", "Directory or file to scan", ".").option("--config <path>", "Path to .flowlint.yml config file").option("--format <format>", "Output format: stylish|json", "stylish").option("--fail-on-error", "Exit with code 1 if errors found", false).action(async (scanPath, options) => {
37
+ try {
38
+ const absolutePath = path.resolve(process.cwd(), scanPath);
39
+ const config = options.config ? (0, import_flowlint_core.loadConfig)(options.config) : (0, import_flowlint_core.loadConfig)();
40
+ const patterns = config.files.include.map(
41
+ (p) => path.join(absolutePath, p).replace(/\\/g, "/")
42
+ );
43
+ const ignorePatterns = config.files.ignore.map(
44
+ (p) => path.join(absolutePath, p).replace(/\\/g, "/")
45
+ );
46
+ const files = await (0, import_glob.glob)(patterns, {
47
+ ignore: ignorePatterns,
48
+ nodir: true
49
+ });
50
+ if (files.length === 0) {
51
+ console.log(import_picocolors.default.yellow("No workflow files found."));
52
+ return;
53
+ }
54
+ console.log(import_picocolors.default.blue("Scanning " + files.length + " file(s)..."));
55
+ const allFindings = [];
56
+ let errorCount = 0;
57
+ for (const file of files) {
58
+ try {
59
+ const content = fs.readFileSync(file, "utf-8");
60
+ const relativePath = path.relative(process.cwd(), file);
61
+ const graph = (0, import_flowlint_core.parseN8n)(content);
62
+ const findings = (0, import_flowlint_core.runAllRules)(graph, {
63
+ path: relativePath,
64
+ cfg: config,
65
+ nodeLines: graph.meta.nodeLines
66
+ });
67
+ allFindings.push(...findings);
68
+ } catch (error) {
69
+ if (error instanceof import_flowlint_core.ValidationError) {
70
+ console.log(import_picocolors.default.red("x " + path.relative(process.cwd(), file) + ": Validation error"));
71
+ error.errors.forEach((e) => {
72
+ console.log(import_picocolors.default.gray(" " + e.path + ": " + e.message));
73
+ });
74
+ } else {
75
+ console.log(import_picocolors.default.red("x " + path.relative(process.cwd(), file) + ": " + (error instanceof Error ? error.message : String(error))));
76
+ }
77
+ errorCount++;
78
+ }
79
+ }
80
+ if (options.format === "json") {
81
+ console.log(JSON.stringify({
82
+ findings: allFindings,
83
+ summary: (0, import_flowlint_core.countFindingsBySeverity)(allFindings),
84
+ filesScanned: files.length,
85
+ errors: errorCount
86
+ }, null, 2));
87
+ } else {
88
+ printStylishOutput(allFindings);
89
+ }
90
+ const summary = (0, import_flowlint_core.countFindingsBySeverity)(allFindings);
91
+ console.log("");
92
+ console.log(import_picocolors.default.bold("Summary:"));
93
+ console.log(" Files scanned: " + files.length);
94
+ console.log(" " + import_picocolors.default.red("Errors (must): " + summary.must));
95
+ console.log(" " + import_picocolors.default.yellow("Warnings (should): " + summary.should));
96
+ console.log(" " + import_picocolors.default.blue("Notes (nit): " + summary.nit));
97
+ if (options.failOnError && summary.must > 0) {
98
+ process.exit(1);
99
+ }
100
+ } catch (error) {
101
+ console.error(import_picocolors.default.red("Error:"), error instanceof Error ? error.message : String(error));
102
+ process.exit(2);
103
+ }
104
+ });
105
+ function printStylishOutput(findings) {
106
+ if (findings.length === 0) {
107
+ console.log(import_picocolors.default.green("No issues found!"));
108
+ return;
109
+ }
110
+ const byFile = /* @__PURE__ */ new Map();
111
+ for (const finding of findings) {
112
+ const existing = byFile.get(finding.path) || [];
113
+ existing.push(finding);
114
+ byFile.set(finding.path, existing);
115
+ }
116
+ for (const [file, fileFindings] of byFile) {
117
+ console.log("");
118
+ console.log(import_picocolors.default.underline(file));
119
+ for (const finding of fileFindings) {
120
+ const severityColor = finding.severity === "must" ? import_picocolors.default.red : finding.severity === "should" ? import_picocolors.default.yellow : import_picocolors.default.blue;
121
+ const line = finding.line ? ":" + finding.line : "";
122
+ console.log(
123
+ " " + severityColor(finding.severity.padEnd(6)) + " " + import_picocolors.default.gray(finding.rule) + " " + finding.message + import_picocolors.default.gray(line)
124
+ );
125
+ }
126
+ }
127
+ }
128
+
129
+ // src/commands/init.ts
130
+ var import_commander2 = require("commander");
131
+ var fs2 = __toESM(require("fs"));
132
+ var path2 = __toESM(require("path"));
133
+ var import_picocolors2 = __toESM(require("picocolors"));
134
+ var import_flowlint_core2 = require("@replikanti/flowlint-core");
135
+ var import_yaml = __toESM(require("yaml"));
136
+ var initCommand = new import_commander2.Command("init").description("Initialize FlowLint configuration in the current directory").option("--force", "Overwrite existing config file", false).action((options) => {
137
+ const configPath = path2.join(process.cwd(), ".flowlint.yml");
138
+ if (fs2.existsSync(configPath) && !options.force) {
139
+ console.log(import_picocolors2.default.yellow(".flowlint.yml already exists. Use --force to overwrite."));
140
+ return;
141
+ }
142
+ const userConfig = {
143
+ files: {
144
+ include: import_flowlint_core2.defaultConfig.files.include,
145
+ ignore: import_flowlint_core2.defaultConfig.files.ignore
146
+ },
147
+ rules: Object.fromEntries(
148
+ Object.entries(import_flowlint_core2.defaultConfig.rules).map(([key, value]) => [
149
+ key,
150
+ { enabled: value.enabled }
151
+ ])
152
+ )
153
+ };
154
+ const yamlContent = import_yaml.default.stringify(userConfig);
155
+ fs2.writeFileSync(configPath, yamlContent, "utf-8");
156
+ console.log(import_picocolors2.default.green("Created .flowlint.yml"));
157
+ console.log(import_picocolors2.default.gray("Edit this file to customize FlowLint behavior."));
158
+ });
159
+
160
+ // src/cli.ts
161
+ var program = new import_commander3.Command();
162
+ program.name("flowlint").description("Static analysis tool for n8n workflows").version("0.6.0");
163
+ program.addCommand(scanCommand);
164
+ program.addCommand(initCommand);
22
165
  program.parse(process.argv);
23
- // Show help if no command provided
24
166
  if (!process.argv.slice(2).length) {
25
- program.outputHelp();
167
+ program.outputHelp();
26
168
  }
27
169
  //# sourceMappingURL=cli.js.map
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../../../apps/cli/src/cli.ts"],"names":[],"mappings":";;AAEA;;;;;GAKG;;AAEH,yCAAoC;AACpC,0CAA8C;AAC9C,0CAA8C;AAE9C,MAAM,OAAO,GAAG,IAAI,mBAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,UAAU,CAAC;KAChB,WAAW,CAAC,wCAAwC,CAAC;KACrD,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,oBAAoB;AACpB,OAAO,CAAC,UAAU,CAAC,kBAAW,CAAC,CAAC;AAChC,OAAO,CAAC,UAAU,CAAC,kBAAW,CAAC,CAAC;AAEhC,oBAAoB;AACpB,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;AAE5B,mCAAmC;AACnC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IAClC,OAAO,CAAC,UAAU,EAAE,CAAC;AACvB,CAAC"}
1
+ {"version":3,"sources":["../src/cli.ts","../src/commands/scan.ts","../src/commands/init.ts"],"sourcesContent":["/**\n * FlowLint CLI\n *\n * A command-line tool for static analysis of n8n workflow files.\n * Can be used locally, in CI pipelines, or pre-commit hooks.\n */\n\nimport { Command } from 'commander';\nimport { scanCommand } from './commands/scan';\nimport { initCommand } from './commands/init';\n\nconst program = new Command();\n\nprogram\n .name('flowlint')\n .description('Static analysis tool for n8n workflows')\n .version('0.6.0');\n\n// Register commands\nprogram.addCommand(scanCommand);\nprogram.addCommand(initCommand);\n\n// Parse and execute\nprogram.parse(process.argv);\n\n// Show help if no command provided\nif (!process.argv.slice(2).length) {\n program.outputHelp();\n}","/**\n * scan command - Analyze n8n workflow files in a directory\n */\n\nimport { Command } from 'commander';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { glob } from 'glob';\nimport pc from 'picocolors';\nimport { \n parseN8n, \n runAllRules, \n loadConfig, \n ValidationError,\n countFindingsBySeverity,\n type Finding,\n type FlowLintConfig\n} from '@replikanti/flowlint-core';\n\ninterface ScanOptions {\n config?: string;\n format: string;\n failOnError: boolean;\n}\n\nexport const scanCommand = new Command('scan')\n .description('Scan workflow files for issues')\n .argument('[path]', 'Directory or file to scan', '.')\n .option('--config <path>', 'Path to .flowlint.yml config file')\n .option('--format <format>', 'Output format: stylish|json', 'stylish')\n .option('--fail-on-error', 'Exit with code 1 if errors found', false)\n .action(async (scanPath: string, options: ScanOptions) => {\n try {\n const absolutePath = path.resolve(process.cwd(), scanPath);\n \n // Load configuration\n const config: FlowLintConfig = options.config \n ? loadConfig(options.config)\n : loadConfig();\n\n // Find workflow files\n const patterns = config.files.include.map(p => \n path.join(absolutePath, p).replace(/\\\\/g, '/')\n );\n \n const ignorePatterns = config.files.ignore.map(p =>\n path.join(absolutePath, p).replace(/\\\\/g, '/')\n );\n\n const files = await glob(patterns, { \n ignore: ignorePatterns,\n nodir: true \n });\n\n if (files.length === 0) {\n console.log(pc.yellow('No workflow files found.'));\n return;\n }\n\n console.log(pc.blue('Scanning ' + files.length + ' file(s)...'));\n\n // Analyze files\n const allFindings: Finding[] = [];\n let errorCount = 0;\n\n for (const file of files) {\n try {\n const content = fs.readFileSync(file, 'utf-8');\n const relativePath = path.relative(process.cwd(), file);\n \n const graph = parseN8n(content);\n const findings = runAllRules(graph, {\n path: relativePath,\n cfg: config,\n nodeLines: graph.meta.nodeLines as Record<string, number> | undefined,\n });\n\n allFindings.push(...findings);\n } catch (error) {\n if (error instanceof ValidationError) {\n console.log(pc.red('x ' + path.relative(process.cwd(), file) + ': Validation error'));\n error.errors.forEach(e => {\n console.log(pc.gray(' ' + e.path + ': ' + e.message));\n });\n } else {\n console.log(pc.red('x ' + path.relative(process.cwd(), file) + ': ' + (error instanceof Error ? error.message : String(error))));\n }\n errorCount++;\n }\n }\n\n // Output results\n if (options.format === 'json') {\n console.log(JSON.stringify({\n findings: allFindings,\n summary: countFindingsBySeverity(allFindings),\n filesScanned: files.length,\n errors: errorCount,\n }, null, 2));\n } else {\n // Stylish format\n printStylishOutput(allFindings);\n }\n\n // Summary\n const summary = countFindingsBySeverity(allFindings);\n console.log('');\n console.log(pc.bold('Summary:'));\n console.log(' Files scanned: ' + files.length);\n console.log(' ' + pc.red('Errors (must): ' + summary.must));\n console.log(' ' + pc.yellow('Warnings (should): ' + summary.should));\n console.log(' ' + pc.blue('Notes (nit): ' + summary.nit));\n\n // Exit code\n if (options.failOnError && summary.must > 0) {\n process.exit(1);\n }\n } catch (error) {\n console.error(pc.red('Error:'), error instanceof Error ? error.message : String(error));\n process.exit(2);\n }\n });\n\nfunction printStylishOutput(findings: Finding[]): void {\n if (findings.length === 0) {\n console.log(pc.green('No issues found!'));\n return;\n }\n\n // Group by file\n const byFile = new Map<string, Finding[]>();\n for (const finding of findings) {\n const existing = byFile.get(finding.path) || [];\n existing.push(finding);\n byFile.set(finding.path, existing);\n }\n\n for (const [file, fileFindings] of byFile) {\n console.log('');\n console.log(pc.underline(file));\n \n for (const finding of fileFindings) {\n const severityColor = finding.severity === 'must' \n ? pc.red \n : finding.severity === 'should' \n ? pc.yellow \n : pc.blue;\n \n const line = finding.line ? ':' + finding.line : '';\n console.log(\n ' ' + severityColor(finding.severity.padEnd(6)) + ' ' + pc.gray(finding.rule) + ' ' + finding.message + pc.gray(line)\n );\n }\n }\n}\r\n\r\n\r\n","/**\n * init command - Initialize FlowLint configuration\n */\n\nimport { Command } from 'commander';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport pc from 'picocolors';\nimport { defaultConfig } from '@replikanti/flowlint-core';\nimport YAML from 'yaml';\n\nexport const initCommand = new Command('init')\n .description('Initialize FlowLint configuration in the current directory')\n .option('--force', 'Overwrite existing config file', false)\n .action((options: { force: boolean }) => {\n const configPath = path.join(process.cwd(), '.flowlint.yml');\n\n if (fs.existsSync(configPath) && !options.force) {\n console.log(pc.yellow('.flowlint.yml already exists. Use --force to overwrite.'));\n return;\n }\n\n // Create a simplified config for user\n const userConfig = {\n files: {\n include: defaultConfig.files.include,\n ignore: defaultConfig.files.ignore,\n },\n rules: Object.fromEntries(\n Object.entries(defaultConfig.rules).map(([key, value]) => [\n key,\n { enabled: value.enabled },\n ])\n ),\n };\n\n const yamlContent = YAML.stringify(userConfig);\n fs.writeFileSync(configPath, yamlContent, 'utf-8');\n\n console.log(pc.green('Created .flowlint.yml'));\n console.log(pc.gray('Edit this file to customize FlowLint behavior.'));\n });\r\n\r\n\r\n"],"mappings":"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","names":["import_commander","pc","import_commander","fs","path","import_picocolors","import_flowlint_core","pc","YAML"]}