n8n-workflow-validator 1.0.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.
- package/LICENSE +21 -0
- package/README.md +144 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +161 -0
- package/dist/cli.js.map +1 -0
- package/dist/core/fixer.d.ts +7 -0
- package/dist/core/fixer.d.ts.map +1 -0
- package/dist/core/fixer.js +25 -0
- package/dist/core/fixer.js.map +1 -0
- package/dist/core/json-parser.d.ts +9 -0
- package/dist/core/json-parser.d.ts.map +1 -0
- package/dist/core/json-parser.js +56 -0
- package/dist/core/json-parser.js.map +1 -0
- package/dist/core/sanitizer.d.ts +10 -0
- package/dist/core/sanitizer.d.ts.map +1 -0
- package/dist/core/sanitizer.js +58 -0
- package/dist/core/sanitizer.js.map +1 -0
- package/dist/core/types.d.ts +38 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +2 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/validator.d.ts +3 -0
- package/dist/core/validator.d.ts.map +1 -0
- package/dist/core/validator.js +88 -0
- package/dist/core/validator.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/utils/input-reader.d.ts +4 -0
- package/dist/utils/input-reader.d.ts.map +1 -0
- package/dist/utils/input-reader.js +33 -0
- package/dist/utils/input-reader.js.map +1 -0
- package/dist/utils/output.d.ts +3 -0
- package/dist/utils/output.d.ts.map +1 -0
- package/dist/utils/output.js +24 -0
- package/dist/utils/output.js.map +1 -0
- package/package.json +52 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Yigit Konur
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# n8n Workflow Validator
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/n8n-workflow-validator)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
Standalone CLI tool that validates n8n workflow JSON files using the **exact same validation logic** as the official n8n editor.
|
|
7
|
+
|
|
8
|
+
## Quick Start
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
# No installation needed - use with npx
|
|
12
|
+
npx n8n-workflow-validator workflow.json
|
|
13
|
+
|
|
14
|
+
# Auto-fix issues
|
|
15
|
+
npx n8n-workflow-validator workflow.json --fix --out fixed.json
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
### Global Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install -g n8n-workflow-validator
|
|
24
|
+
n8n-validate workflow.json
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Local Development
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
git clone https://github.com/yigitkonur/n8n-workflow-validator.git
|
|
31
|
+
cd n8n-workflow-validator
|
|
32
|
+
npm install
|
|
33
|
+
npm run build
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Usage
|
|
37
|
+
|
|
38
|
+
### With npx (Recommended)
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# Validate a workflow file
|
|
42
|
+
npx n8n-workflow-validator workflow.json
|
|
43
|
+
|
|
44
|
+
# Auto-fix issues
|
|
45
|
+
npx n8n-workflow-validator workflow.json --fix --out fixed.json
|
|
46
|
+
|
|
47
|
+
# From URL
|
|
48
|
+
npx n8n-workflow-validator "https://example.com/workflow.json"
|
|
49
|
+
|
|
50
|
+
# JSON output (for CI/CD)
|
|
51
|
+
npx n8n-workflow-validator workflow.json --json
|
|
52
|
+
|
|
53
|
+
# With repair for malformed JSON
|
|
54
|
+
npx n8n-workflow-validator broken.json --repair --fix
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### After Global Install
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
n8n-validate workflow.json
|
|
61
|
+
n8n-validate workflow.json --fix --out fixed.json
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### From Source
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
node dist/cli.js workflow.json
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## What It Validates
|
|
71
|
+
|
|
72
|
+
Mirrors n8n's 4-stage import validation pipeline:
|
|
73
|
+
|
|
74
|
+
1. **JSON Parsing** - 3-tier fallback (standard → JS object → repair)
|
|
75
|
+
2. **Structure** - nodes array + connections object
|
|
76
|
+
3. **Node Fields** - type, typeVersion, position, parameters
|
|
77
|
+
4. **Known Issues** - Invalid `options` fields in IF/Switch nodes
|
|
78
|
+
|
|
79
|
+
## What It Fixes
|
|
80
|
+
|
|
81
|
+
- ✅ Invalid empty `options` field in IF/Switch nodes (causes "Could not find property option" error)
|
|
82
|
+
- ✅ Missing node names (auto-generated)
|
|
83
|
+
- ✅ Duplicate webhookIds (regenerated)
|
|
84
|
+
- ✅ Missing/duplicate node IDs (regenerated)
|
|
85
|
+
|
|
86
|
+
## Options
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
--repair Repair malformed JSON using jsonrepair
|
|
90
|
+
--accept-js-object Accept JavaScript object literals
|
|
91
|
+
--fix Auto-fix known issues
|
|
92
|
+
--no-sanitize Skip name/ID generation
|
|
93
|
+
--no-regenerate-ids Keep existing IDs
|
|
94
|
+
--json JSON output
|
|
95
|
+
--out FILE Write fixed workflow to FILE
|
|
96
|
+
-h, --help Show help
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Exit Codes
|
|
100
|
+
|
|
101
|
+
- `0` - Valid
|
|
102
|
+
- `1` - Invalid or errors
|
|
103
|
+
|
|
104
|
+
## Architecture
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
src/
|
|
108
|
+
├── core/
|
|
109
|
+
│ ├── json-parser.ts # 3-tier JSON parsing (from n8n-workflow)
|
|
110
|
+
│ ├── types.ts # TypeScript interfaces
|
|
111
|
+
│ ├── validator.ts # Structure validation (from workflows.controller.ts)
|
|
112
|
+
│ ├── fixer.ts # Auto-fix invalid options fields
|
|
113
|
+
│ └── sanitizer.ts # Name/ID generation (from useCanvasOperations.ts)
|
|
114
|
+
├── utils/
|
|
115
|
+
│ ├── input-reader.ts # File/URL/stdin handling
|
|
116
|
+
│ └── output.ts # Formatted output
|
|
117
|
+
├── index.ts # Public API
|
|
118
|
+
└── cli.ts # CLI entry point
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Dependencies
|
|
122
|
+
|
|
123
|
+
- `esprima-next` - Parse JavaScript object literals
|
|
124
|
+
- `jsonrepair` - Repair malformed JSON
|
|
125
|
+
|
|
126
|
+
No n8n dependencies - fully standalone.
|
|
127
|
+
|
|
128
|
+
## Example
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
$ node dist/cli.js workflow.json --fix
|
|
132
|
+
|
|
133
|
+
✅ VALID: workflow.json
|
|
134
|
+
🔧 Fixed 4 issue(s)
|
|
135
|
+
Warnings:
|
|
136
|
+
- Fixed node "check-document-exists": Removed invalid empty 'options' field
|
|
137
|
+
- Fixed node "check-status-pending": Removed invalid empty 'options' field
|
|
138
|
+
- Fixed node "route-by-format": Removed invalid empty 'options' field
|
|
139
|
+
- Fixed node "check-processing-success": Removed invalid empty 'options' field
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## License
|
|
143
|
+
|
|
144
|
+
MIT
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { writeFile } from 'node:fs/promises';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
import { jsonParse } from './core/json-parser.js';
|
|
5
|
+
import { validateWorkflowStructure } from './core/validator.js';
|
|
6
|
+
import { fixInvalidOptionsFields } from './core/fixer.js';
|
|
7
|
+
import { sanitizeWorkflow } from './core/sanitizer.js';
|
|
8
|
+
import { classifyInput, readInput } from './utils/input-reader.js';
|
|
9
|
+
import { outputSummary } from './utils/output.js';
|
|
10
|
+
function parseArgs(argv) {
|
|
11
|
+
const args = argv.slice(2);
|
|
12
|
+
const options = {
|
|
13
|
+
repairJSON: false,
|
|
14
|
+
acceptJSObject: false,
|
|
15
|
+
sanitize: true,
|
|
16
|
+
regenerateIds: true,
|
|
17
|
+
jsonOutput: false,
|
|
18
|
+
outputFile: undefined,
|
|
19
|
+
fix: false,
|
|
20
|
+
};
|
|
21
|
+
let input;
|
|
22
|
+
for (let i = 0; i < args.length; i++) {
|
|
23
|
+
const arg = args[i];
|
|
24
|
+
switch (arg) {
|
|
25
|
+
case '--repair':
|
|
26
|
+
options.repairJSON = true;
|
|
27
|
+
break;
|
|
28
|
+
case '--accept-js-object':
|
|
29
|
+
options.acceptJSObject = true;
|
|
30
|
+
break;
|
|
31
|
+
case '--no-sanitize':
|
|
32
|
+
options.sanitize = false;
|
|
33
|
+
break;
|
|
34
|
+
case '--no-regenerate-ids':
|
|
35
|
+
options.regenerateIds = false;
|
|
36
|
+
break;
|
|
37
|
+
case '--json':
|
|
38
|
+
options.jsonOutput = true;
|
|
39
|
+
break;
|
|
40
|
+
case '--fix':
|
|
41
|
+
options.fix = true;
|
|
42
|
+
break;
|
|
43
|
+
case '--out':
|
|
44
|
+
case '--output':
|
|
45
|
+
options.outputFile = args[i + 1];
|
|
46
|
+
i++;
|
|
47
|
+
break;
|
|
48
|
+
case '--help':
|
|
49
|
+
case '-h':
|
|
50
|
+
printHelp();
|
|
51
|
+
process.exit(0);
|
|
52
|
+
default:
|
|
53
|
+
if (!input && !arg.startsWith('-')) {
|
|
54
|
+
input = arg;
|
|
55
|
+
}
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return { input, options };
|
|
60
|
+
}
|
|
61
|
+
function printHelp() {
|
|
62
|
+
console.log(`
|
|
63
|
+
n8n Workflow JSON Validator
|
|
64
|
+
|
|
65
|
+
Usage:
|
|
66
|
+
n8n-validate <file-or-url> [options]
|
|
67
|
+
|
|
68
|
+
Options:
|
|
69
|
+
--repair Attempt to repair malformed JSON
|
|
70
|
+
--accept-js-object Accept JavaScript object literal syntax
|
|
71
|
+
--fix Auto-fix known issues (invalid options fields)
|
|
72
|
+
--no-sanitize Disable workflow sanitization
|
|
73
|
+
--no-regenerate-ids Do not regenerate node IDs
|
|
74
|
+
--json Output result as JSON
|
|
75
|
+
--out, --output FILE Write fixed workflow to FILE
|
|
76
|
+
-h, --help Show this help
|
|
77
|
+
|
|
78
|
+
Examples:
|
|
79
|
+
n8n-validate workflow.json
|
|
80
|
+
n8n-validate workflow.json --fix --out fixed.json
|
|
81
|
+
n8n-validate "https://example.com/workflow.json" --json
|
|
82
|
+
cat workflow.json | n8n-validate --json
|
|
83
|
+
`);
|
|
84
|
+
}
|
|
85
|
+
async function main() {
|
|
86
|
+
const { input, options } = parseArgs(process.argv);
|
|
87
|
+
const sourceType = classifyInput(input);
|
|
88
|
+
const label = input ?? '<stdin>';
|
|
89
|
+
let raw;
|
|
90
|
+
try {
|
|
91
|
+
raw = await readInput(input, sourceType);
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
const summary = {
|
|
95
|
+
input: label,
|
|
96
|
+
sourceType,
|
|
97
|
+
valid: false,
|
|
98
|
+
errors: [`Failed to read input: ${error?.message ?? String(error)}`],
|
|
99
|
+
warnings: [],
|
|
100
|
+
sanitized: false,
|
|
101
|
+
};
|
|
102
|
+
outputSummary(summary, options.jsonOutput);
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
const parsed = jsonParse(raw.trim(), {
|
|
106
|
+
acceptJSObject: options.acceptJSObject,
|
|
107
|
+
repairJSON: options.repairJSON,
|
|
108
|
+
fallbackValue: null,
|
|
109
|
+
});
|
|
110
|
+
if (!parsed) {
|
|
111
|
+
const summary = {
|
|
112
|
+
input: label,
|
|
113
|
+
sourceType,
|
|
114
|
+
valid: false,
|
|
115
|
+
errors: ['Could not parse workflow JSON'],
|
|
116
|
+
warnings: [],
|
|
117
|
+
sanitized: false,
|
|
118
|
+
};
|
|
119
|
+
outputSummary(summary, options.jsonOutput);
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
let fixedCount = 0;
|
|
123
|
+
const allWarnings = [];
|
|
124
|
+
if (options.fix) {
|
|
125
|
+
const fixResult = fixInvalidOptionsFields(parsed);
|
|
126
|
+
fixedCount = fixResult.fixed;
|
|
127
|
+
allWarnings.push(...fixResult.warnings);
|
|
128
|
+
}
|
|
129
|
+
const structure = validateWorkflowStructure(parsed);
|
|
130
|
+
const allErrors = [...structure.errors];
|
|
131
|
+
allWarnings.push(...structure.warnings);
|
|
132
|
+
if (structure.nodeTypeIssues) {
|
|
133
|
+
allErrors.push(...structure.nodeTypeIssues);
|
|
134
|
+
}
|
|
135
|
+
let workflowOut = parsed;
|
|
136
|
+
if (structure.valid && options.sanitize) {
|
|
137
|
+
const sanitized = sanitizeWorkflow(parsed, { regenerateIds: options.regenerateIds });
|
|
138
|
+
workflowOut = sanitized.workflow;
|
|
139
|
+
allWarnings.push(...sanitized.warnings);
|
|
140
|
+
}
|
|
141
|
+
const summary = {
|
|
142
|
+
input: label,
|
|
143
|
+
sourceType,
|
|
144
|
+
valid: allErrors.length === 0,
|
|
145
|
+
errors: allErrors,
|
|
146
|
+
warnings: allWarnings,
|
|
147
|
+
sanitized: structure.valid && options.sanitize,
|
|
148
|
+
fixed: fixedCount > 0 ? fixedCount : undefined,
|
|
149
|
+
};
|
|
150
|
+
if (options.outputFile && allErrors.length === 0) {
|
|
151
|
+
const outPath = resolve(process.cwd(), options.outputFile);
|
|
152
|
+
await writeFile(outPath, JSON.stringify(workflowOut, null, 2), 'utf8');
|
|
153
|
+
}
|
|
154
|
+
outputSummary(summary, options.jsonOutput);
|
|
155
|
+
process.exit(summary.valid ? 0 : 1);
|
|
156
|
+
}
|
|
157
|
+
main().catch((error) => {
|
|
158
|
+
console.error('Fatal error:', error);
|
|
159
|
+
process.exit(1);
|
|
160
|
+
});
|
|
161
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fixer.d.ts","sourceRoot":"","sources":["../../src/core/fixer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,QAAQ,GAAG,SAAS,CA0BrE"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export function fixInvalidOptionsFields(workflow) {
|
|
2
|
+
const warnings = [];
|
|
3
|
+
let fixed = 0;
|
|
4
|
+
if (!Array.isArray(workflow.nodes)) {
|
|
5
|
+
return { fixed, warnings };
|
|
6
|
+
}
|
|
7
|
+
for (const node of workflow.nodes) {
|
|
8
|
+
if (!node || typeof node !== 'object')
|
|
9
|
+
continue;
|
|
10
|
+
if (node.type === 'n8n-nodes-base.if' || node.type === 'n8n-nodes-base.switch') {
|
|
11
|
+
if (node.parameters &&
|
|
12
|
+
typeof node.parameters === 'object' &&
|
|
13
|
+
'options' in node.parameters &&
|
|
14
|
+
typeof node.parameters.options === 'object' &&
|
|
15
|
+
node.parameters.options !== null &&
|
|
16
|
+
Object.keys(node.parameters.options).length === 0) {
|
|
17
|
+
delete node.parameters.options;
|
|
18
|
+
fixed++;
|
|
19
|
+
warnings.push(`Fixed node "${node.name}": Removed invalid empty 'options' field from parameters root`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return { fixed, warnings };
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=fixer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fixer.js","sourceRoot":"","sources":["../../src/core/fixer.ts"],"names":[],"mappings":"AAOA,MAAM,UAAU,uBAAuB,CAAC,QAAkB;IACxD,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,IAAI,KAAK,GAAG,CAAC,CAAC;IAEd,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACnC,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;IAC7B,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,KAAK,EAAE,CAAC;QAClC,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;YAAE,SAAS;QAEhD,IAAI,IAAI,CAAC,IAAI,KAAK,mBAAmB,IAAI,IAAI,CAAC,IAAI,KAAK,uBAAuB,EAAE,CAAC;YAC/E,IAAI,IAAI,CAAC,UAAU;gBACf,OAAO,IAAI,CAAC,UAAU,KAAK,QAAQ;gBACnC,SAAS,IAAI,IAAI,CAAC,UAAU;gBAC5B,OAAO,IAAI,CAAC,UAAU,CAAC,OAAO,KAAK,QAAQ;gBAC3C,IAAI,CAAC,UAAU,CAAC,OAAO,KAAK,IAAI;gBAChC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACtD,OAAO,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;gBAC/B,KAAK,EAAE,CAAC;gBACR,QAAQ,CAAC,IAAI,CAAC,eAAe,IAAI,CAAC,IAAI,+DAA+D,CAAC,CAAC;YACzG,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;AAC7B,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
interface JSONParseOptions<T> {
|
|
2
|
+
acceptJSObject?: boolean;
|
|
3
|
+
repairJSON?: boolean;
|
|
4
|
+
errorMessage?: string;
|
|
5
|
+
fallbackValue?: T;
|
|
6
|
+
}
|
|
7
|
+
export declare function jsonParse<T>(jsonString: string, options?: JSONParseOptions<T>): T;
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=json-parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json-parser.d.ts","sourceRoot":"","sources":["../../src/core/json-parser.ts"],"names":[],"mappings":"AAIA,UAAU,gBAAgB,CAAC,CAAC;IAC1B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,CAAC,CAAC;CACnB;AA8BD,wBAAgB,SAAS,CAAC,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG,CAAC,CA6BjF"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { parse as esprimaParse, Syntax } from 'esprima-next';
|
|
2
|
+
import { jsonrepair } from 'jsonrepair';
|
|
3
|
+
function syntaxNodeToValue(expression) {
|
|
4
|
+
switch (expression?.type) {
|
|
5
|
+
case Syntax.ObjectExpression:
|
|
6
|
+
return Object.fromEntries(expression.properties
|
|
7
|
+
.filter((prop) => prop.type === Syntax.Property)
|
|
8
|
+
.map(({ key, value }) => [syntaxNodeToValue(key), syntaxNodeToValue(value)]));
|
|
9
|
+
case Syntax.Identifier:
|
|
10
|
+
return expression.name;
|
|
11
|
+
case Syntax.Literal:
|
|
12
|
+
return expression.value;
|
|
13
|
+
case Syntax.ArrayExpression:
|
|
14
|
+
return expression.elements.map((exp) => syntaxNodeToValue(exp));
|
|
15
|
+
default:
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function parseJSObject(objectAsString) {
|
|
20
|
+
const jsExpression = esprimaParse(`(${objectAsString})`).body.find((node) => node.type === Syntax.ExpressionStatement && node.expression.type === Syntax.ObjectExpression);
|
|
21
|
+
return syntaxNodeToValue(jsExpression?.expression);
|
|
22
|
+
}
|
|
23
|
+
export function jsonParse(jsonString, options) {
|
|
24
|
+
try {
|
|
25
|
+
return JSON.parse(jsonString);
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
if (options?.acceptJSObject) {
|
|
29
|
+
try {
|
|
30
|
+
const jsonStringCleaned = parseJSObject(jsonString);
|
|
31
|
+
return jsonStringCleaned;
|
|
32
|
+
}
|
|
33
|
+
catch (e) {
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (options?.repairJSON) {
|
|
37
|
+
try {
|
|
38
|
+
const jsonStringCleaned = jsonrepair(jsonString);
|
|
39
|
+
return JSON.parse(jsonStringCleaned);
|
|
40
|
+
}
|
|
41
|
+
catch (e) {
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (options?.fallbackValue !== undefined) {
|
|
45
|
+
if (options.fallbackValue instanceof Function) {
|
|
46
|
+
return options.fallbackValue();
|
|
47
|
+
}
|
|
48
|
+
return options.fallbackValue;
|
|
49
|
+
}
|
|
50
|
+
else if (options?.errorMessage) {
|
|
51
|
+
throw new Error(options.errorMessage);
|
|
52
|
+
}
|
|
53
|
+
throw error;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=json-parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json-parser.js","sourceRoot":"","sources":["../../src/core/json-parser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,IAAI,YAAY,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAE7D,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AASxC,SAAS,iBAAiB,CAAC,UAA8B;IACvD,QAAQ,UAAU,EAAE,IAAI,EAAE,CAAC;QACzB,KAAK,MAAM,CAAC,gBAAgB;YAC1B,OAAO,MAAM,CAAC,WAAW,CACvB,UAAU,CAAC,UAAU;iBAClB,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,MAAM,CAAC,QAAQ,CAAC;iBAC/C,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC,iBAAiB,CAAC,GAAG,CAAC,EAAE,iBAAiB,CAAC,KAAK,CAAC,CAAC,CAAC,CAC/E,CAAC;QACJ,KAAK,MAAM,CAAC,UAAU;YACpB,OAAO,UAAU,CAAC,IAAI,CAAC;QACzB,KAAK,MAAM,CAAC,OAAO;YACjB,OAAO,UAAU,CAAC,KAAK,CAAC;QAC1B,KAAK,MAAM,CAAC,eAAe;YACzB,OAAO,UAAU,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC,CAAC;QAClE;YACE,OAAO,SAAS,CAAC;IACrB,CAAC;AACH,CAAC;AAED,SAAS,aAAa,CAAC,cAAsB;IAC3C,MAAM,YAAY,GAAG,YAAY,CAAC,IAAI,cAAc,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAChE,CAAC,IAAI,EAA+B,EAAE,CACpC,IAAI,CAAC,IAAI,KAAK,MAAM,CAAC,mBAAmB,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,KAAK,MAAM,CAAC,gBAAgB,CAC/F,CAAC;IAEF,OAAO,iBAAiB,CAAC,YAAY,EAAE,UAAU,CAAW,CAAC;AAC/D,CAAC;AAED,MAAM,UAAU,SAAS,CAAI,UAAkB,EAAE,OAA6B;IAC5E,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,UAAU,CAAM,CAAC;IACrC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,OAAO,EAAE,cAAc,EAAE,CAAC;YAC5B,IAAI,CAAC;gBACH,MAAM,iBAAiB,GAAG,aAAa,CAAC,UAAU,CAAC,CAAC;gBACpD,OAAO,iBAAsB,CAAC;YAChC,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;YACb,CAAC;QACH,CAAC;QACD,IAAI,OAAO,EAAE,UAAU,EAAE,CAAC;YACxB,IAAI,CAAC;gBACH,MAAM,iBAAiB,GAAG,UAAU,CAAC,UAAU,CAAC,CAAC;gBACjD,OAAO,IAAI,CAAC,KAAK,CAAC,iBAAiB,CAAM,CAAC;YAC5C,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;YACb,CAAC;QACH,CAAC;QACD,IAAI,OAAO,EAAE,aAAa,KAAK,SAAS,EAAE,CAAC;YACzC,IAAI,OAAO,CAAC,aAAa,YAAY,QAAQ,EAAE,CAAC;gBAC9C,OAAO,OAAO,CAAC,aAAa,EAAE,CAAC;YACjC,CAAC;YACD,OAAO,OAAO,CAAC,aAAa,CAAC;QAC/B,CAAC;aAAM,IAAI,OAAO,EAAE,YAAY,EAAE,CAAC;YACjC,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;QACxC,CAAC;QAED,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Workflow } from './types.js';
|
|
2
|
+
export interface SanitizeOptions {
|
|
3
|
+
regenerateIds: boolean;
|
|
4
|
+
}
|
|
5
|
+
export interface SanitizeResult {
|
|
6
|
+
workflow: Workflow;
|
|
7
|
+
warnings: string[];
|
|
8
|
+
}
|
|
9
|
+
export declare function sanitizeWorkflow(workflow: Workflow, options: SanitizeOptions): SanitizeResult;
|
|
10
|
+
//# sourceMappingURL=sanitizer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sanitizer.d.ts","sourceRoot":"","sources":["../../src/core/sanitizer.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,MAAM,WAAW,eAAe;IAC9B,aAAa,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,QAAQ,CAAC;IACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAaD,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,eAAe,GAAG,cAAc,CAsD7F"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
function generateUniqueName(base, existing) {
|
|
3
|
+
const cleanBase = base.split('.').pop() || base || 'Node';
|
|
4
|
+
let candidate = cleanBase;
|
|
5
|
+
let counter = 1;
|
|
6
|
+
while (existing.has(candidate)) {
|
|
7
|
+
candidate = `${cleanBase} ${counter}`;
|
|
8
|
+
counter += 1;
|
|
9
|
+
}
|
|
10
|
+
return candidate;
|
|
11
|
+
}
|
|
12
|
+
export function sanitizeWorkflow(workflow, options) {
|
|
13
|
+
const warnings = [];
|
|
14
|
+
if (!Array.isArray(workflow.nodes)) {
|
|
15
|
+
return { workflow, warnings };
|
|
16
|
+
}
|
|
17
|
+
const existingNames = new Set();
|
|
18
|
+
const webhookIdCounts = new Map();
|
|
19
|
+
for (const node of workflow.nodes) {
|
|
20
|
+
if (typeof node.name === 'string') {
|
|
21
|
+
existingNames.add(node.name);
|
|
22
|
+
}
|
|
23
|
+
if (node.webhookId && typeof node.webhookId === 'string') {
|
|
24
|
+
webhookIdCounts.set(node.webhookId, (webhookIdCounts.get(node.webhookId) ?? 0) + 1);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const duplicateWebhookIds = new Set(Array.from(webhookIdCounts.entries())
|
|
28
|
+
.filter(([, count]) => count > 1)
|
|
29
|
+
.map(([id]) => id));
|
|
30
|
+
for (const node of workflow.nodes) {
|
|
31
|
+
if (!node.name || typeof node.name !== 'string') {
|
|
32
|
+
const base = typeof node.type === 'string' ? node.type : 'Node';
|
|
33
|
+
const newName = generateUniqueName(base, existingNames);
|
|
34
|
+
node.name = newName;
|
|
35
|
+
existingNames.add(newName);
|
|
36
|
+
warnings.push(`Node without name received generated name: ${newName}`);
|
|
37
|
+
}
|
|
38
|
+
if (node.webhookId && typeof node.webhookId === 'string' && duplicateWebhookIds.has(node.webhookId)) {
|
|
39
|
+
const oldId = node.webhookId;
|
|
40
|
+
const newId = randomUUID();
|
|
41
|
+
node.webhookId = newId;
|
|
42
|
+
if (node.parameters && typeof node.parameters === 'object' && 'path' in node.parameters) {
|
|
43
|
+
node.parameters.path = newId;
|
|
44
|
+
}
|
|
45
|
+
warnings.push(`WebhookId ${oldId} duplicated in workflow, regenerated to ${newId}`);
|
|
46
|
+
}
|
|
47
|
+
if (options.regenerateIds) {
|
|
48
|
+
if (node.id && typeof node.id === 'string') {
|
|
49
|
+
node.id = randomUUID();
|
|
50
|
+
}
|
|
51
|
+
else if (!node.id) {
|
|
52
|
+
node.id = randomUUID();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return { workflow, warnings };
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=sanitizer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sanitizer.js","sourceRoot":"","sources":["../../src/core/sanitizer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export interface WorkflowNode {
|
|
2
|
+
id?: string;
|
|
3
|
+
name?: string;
|
|
4
|
+
type: string;
|
|
5
|
+
typeVersion: number;
|
|
6
|
+
position: [number, number];
|
|
7
|
+
parameters: Record<string, unknown>;
|
|
8
|
+
credentials?: Record<string, unknown>;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
webhookId?: string;
|
|
11
|
+
[key: string]: unknown;
|
|
12
|
+
}
|
|
13
|
+
export interface Workflow {
|
|
14
|
+
name?: string;
|
|
15
|
+
nodes: WorkflowNode[];
|
|
16
|
+
connections: Record<string, unknown>;
|
|
17
|
+
active?: boolean;
|
|
18
|
+
settings?: Record<string, unknown>;
|
|
19
|
+
pinData?: Record<string, unknown>;
|
|
20
|
+
meta?: Record<string, unknown>;
|
|
21
|
+
[key: string]: unknown;
|
|
22
|
+
}
|
|
23
|
+
export interface ValidationResult {
|
|
24
|
+
valid: boolean;
|
|
25
|
+
errors: string[];
|
|
26
|
+
warnings: string[];
|
|
27
|
+
nodeTypeIssues?: string[];
|
|
28
|
+
}
|
|
29
|
+
export interface ValidationSummary {
|
|
30
|
+
input: string;
|
|
31
|
+
sourceType: 'file' | 'url' | 'stdin';
|
|
32
|
+
valid: boolean;
|
|
33
|
+
errors: string[];
|
|
34
|
+
warnings: string[];
|
|
35
|
+
sanitized: boolean;
|
|
36
|
+
fixed?: number;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,YAAY;IAC3B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,GAAG,KAAK,GAAG,OAAO,CAAC;IACrC,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../../src/core/validator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAY,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE7D,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,OAAO,GAAG,gBAAgB,CAyFzE"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
export function validateWorkflowStructure(data) {
|
|
2
|
+
const errors = [];
|
|
3
|
+
const warnings = [];
|
|
4
|
+
const nodeTypeIssues = [];
|
|
5
|
+
if (typeof data !== 'object' || data === null) {
|
|
6
|
+
errors.push('Workflow must be a JSON object');
|
|
7
|
+
return { valid: false, errors, warnings, nodeTypeIssues };
|
|
8
|
+
}
|
|
9
|
+
const wf = data;
|
|
10
|
+
if (wf.nodes === undefined) {
|
|
11
|
+
errors.push('Missing required property: nodes');
|
|
12
|
+
}
|
|
13
|
+
else if (!Array.isArray(wf.nodes)) {
|
|
14
|
+
errors.push('Property "nodes" must be an array');
|
|
15
|
+
}
|
|
16
|
+
if (wf.connections === undefined) {
|
|
17
|
+
errors.push('Missing required property: connections');
|
|
18
|
+
}
|
|
19
|
+
else if (typeof wf.connections !== 'object' || wf.connections === null) {
|
|
20
|
+
errors.push('Property "connections" must be an object');
|
|
21
|
+
}
|
|
22
|
+
else if (Array.isArray(wf.connections)) {
|
|
23
|
+
errors.push('Property "connections" must be an object, not an array');
|
|
24
|
+
}
|
|
25
|
+
if (Array.isArray(wf.nodes)) {
|
|
26
|
+
for (let i = 0; i < wf.nodes.length; i++) {
|
|
27
|
+
const node = wf.nodes[i];
|
|
28
|
+
if (!node || typeof node !== 'object') {
|
|
29
|
+
errors.push(`Node at index ${i} is not an object`);
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (!node.type) {
|
|
33
|
+
errors.push(`Node at index ${i} (${node.name || 'unnamed'}) missing required field: type`);
|
|
34
|
+
}
|
|
35
|
+
else if (typeof node.type !== 'string') {
|
|
36
|
+
errors.push(`Node at index ${i} (${node.name || 'unnamed'}) field 'type' must be a string`);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
if (!node.type.includes('.')) {
|
|
40
|
+
nodeTypeIssues.push(`Node "${node.name || 'unnamed'}" has invalid type "${node.type}" - must include package prefix (e.g., "n8n-nodes-base.webhook")`);
|
|
41
|
+
}
|
|
42
|
+
else if (node.type.startsWith('nodes-base.')) {
|
|
43
|
+
nodeTypeIssues.push(`Node "${node.name || 'unnamed'}" has invalid type "${node.type}" - should be "n8n-${node.type}"`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (!node.name) {
|
|
47
|
+
warnings.push(`Node at index ${i} (type: ${node.type || 'unknown'}) missing 'name' field - will be auto-generated`);
|
|
48
|
+
}
|
|
49
|
+
if (node.typeVersion === undefined) {
|
|
50
|
+
errors.push(`Node "${node.name || 'unnamed'}" missing required field: typeVersion`);
|
|
51
|
+
}
|
|
52
|
+
else if (typeof node.typeVersion !== 'number') {
|
|
53
|
+
errors.push(`Node "${node.name || 'unnamed'}" field 'typeVersion' must be a number`);
|
|
54
|
+
}
|
|
55
|
+
if (!node.position) {
|
|
56
|
+
errors.push(`Node "${node.name || 'unnamed'}" missing required field: position`);
|
|
57
|
+
}
|
|
58
|
+
else if (!Array.isArray(node.position) || node.position.length !== 2) {
|
|
59
|
+
errors.push(`Node "${node.name || 'unnamed'}" field 'position' must be an array of [x, y]`);
|
|
60
|
+
}
|
|
61
|
+
if (node.parameters === undefined) {
|
|
62
|
+
errors.push(`Node "${node.name || 'unnamed'}" missing required field: parameters`);
|
|
63
|
+
}
|
|
64
|
+
else if (typeof node.parameters !== 'object' || node.parameters === null) {
|
|
65
|
+
errors.push(`Node "${node.name || 'unnamed'}" field 'parameters' must be an object`);
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
if (node.type === 'n8n-nodes-base.if' || node.type === 'n8n-nodes-base.switch') {
|
|
69
|
+
if ('options' in node.parameters &&
|
|
70
|
+
typeof node.parameters.options === 'object' &&
|
|
71
|
+
node.parameters.options !== null &&
|
|
72
|
+
Object.keys(node.parameters.options).length === 0) {
|
|
73
|
+
errors.push(`Node "${node.name || 'unnamed'}" (${node.type}): Invalid empty 'options' field at parameters root. ` +
|
|
74
|
+
`Remove 'parameters.options' - the 'options' field should only exist inside 'conditions', not at root level. ` +
|
|
75
|
+
`This causes "Could not find property option" error when pasting into n8n editor.`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
valid: errors.length === 0,
|
|
83
|
+
errors,
|
|
84
|
+
warnings,
|
|
85
|
+
nodeTypeIssues: nodeTypeIssues.length > 0 ? nodeTypeIssues : undefined
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=validator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validator.js","sourceRoot":"","sources":["../../src/core/validator.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { jsonParse } from './core/json-parser.js';
|
|
2
|
+
export { validateWorkflowStructure } from './core/validator.js';
|
|
3
|
+
export { fixInvalidOptionsFields } from './core/fixer.js';
|
|
4
|
+
export { sanitizeWorkflow } from './core/sanitizer.js';
|
|
5
|
+
export type { Workflow, WorkflowNode, ValidationResult, ValidationSummary } from './core/types.js';
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAC;AAChE,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,YAAY,EAAE,QAAQ,EAAE,YAAY,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { jsonParse } from './core/json-parser.js';
|
|
2
|
+
export { validateWorkflowStructure } from './core/validator.js';
|
|
3
|
+
export { fixInvalidOptionsFields } from './core/fixer.js';
|
|
4
|
+
export { sanitizeWorkflow } from './core/sanitizer.js';
|
|
5
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAC;AAChE,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"input-reader.d.ts","sourceRoot":"","sources":["../../src/utils/input-reader.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,CAAC;AAEjD,wBAAgB,aAAa,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAIvD;AAED,wBAAsB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,UAAU,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CAyBjG"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
const URL_REGEX = /^https?:\/\/.+/i;
|
|
4
|
+
export function classifyInput(input) {
|
|
5
|
+
if (!input)
|
|
6
|
+
return 'stdin';
|
|
7
|
+
const trimmed = input.trim();
|
|
8
|
+
return URL_REGEX.test(trimmed) ? 'url' : 'file';
|
|
9
|
+
}
|
|
10
|
+
export async function readInput(input, sourceType) {
|
|
11
|
+
if (sourceType === 'stdin') {
|
|
12
|
+
return new Promise((resolve, reject) => {
|
|
13
|
+
let data = '';
|
|
14
|
+
process.stdin.setEncoding('utf8');
|
|
15
|
+
process.stdin.on('data', chunk => { data += chunk; });
|
|
16
|
+
process.stdin.on('end', () => resolve(data));
|
|
17
|
+
process.stdin.on('error', reject);
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
if (!input) {
|
|
21
|
+
throw new Error('No input provided');
|
|
22
|
+
}
|
|
23
|
+
if (sourceType === 'url') {
|
|
24
|
+
const response = await fetch(input);
|
|
25
|
+
if (!response.ok) {
|
|
26
|
+
throw new Error(`Failed to fetch URL: ${response.status} ${response.statusText}`);
|
|
27
|
+
}
|
|
28
|
+
return response.text();
|
|
29
|
+
}
|
|
30
|
+
const absPath = resolve(process.cwd(), input);
|
|
31
|
+
return readFile(absPath, 'utf8');
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=input-reader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"input-reader.js","sourceRoot":"","sources":["../../src/utils/input-reader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,MAAM,SAAS,GAAG,iBAAiB,CAAC;AAIpC,MAAM,UAAU,aAAa,CAAC,KAAc;IAC1C,IAAI,CAAC,KAAK;QAAE,OAAO,OAAO,CAAC;IAC3B,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC7B,OAAO,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC;AAClD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,KAAyB,EAAE,UAAqB;IAC9E,IAAI,UAAU,KAAK,OAAO,EAAE,CAAC;QAC3B,OAAO,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAC7C,IAAI,IAAI,GAAG,EAAE,CAAC;YACd,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;YAClC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,GAAG,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YACtD,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;YAC7C,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QACpC,CAAC,CAAC,CAAC;IACL,CAAC;IAED,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC;IACvC,CAAC;IAED,IAAI,UAAU,KAAK,KAAK,EAAE,CAAC;QACzB,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,KAAK,CAAC,CAAC;QACpC,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,wBAAwB,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;QACpF,CAAC;QACD,OAAO,QAAQ,CAAC,IAAI,EAAE,CAAC;IACzB,CAAC;IAED,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,KAAK,CAAC,CAAC;IAC9C,OAAO,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;AACnC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"output.d.ts","sourceRoot":"","sources":["../../src/utils/output.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAE1D,wBAAgB,aAAa,CAAC,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAE,OAAO,GAAG,IAAI,CA0BnF"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export function outputSummary(summary, jsonOutput) {
|
|
2
|
+
if (jsonOutput) {
|
|
3
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
4
|
+
return;
|
|
5
|
+
}
|
|
6
|
+
const prefix = summary.valid ? '✅ VALID' : '❌ INVALID';
|
|
7
|
+
console.log(`${prefix}: ${summary.input}`);
|
|
8
|
+
if (summary.fixed && summary.fixed > 0) {
|
|
9
|
+
console.log(` 🔧 Fixed ${summary.fixed} issue(s)`);
|
|
10
|
+
}
|
|
11
|
+
if (summary.errors.length) {
|
|
12
|
+
console.log(' Errors:');
|
|
13
|
+
for (const err of summary.errors) {
|
|
14
|
+
console.log(` - ${err}`);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
if (summary.warnings.length) {
|
|
18
|
+
console.log(' Warnings:');
|
|
19
|
+
for (const warn of summary.warnings) {
|
|
20
|
+
console.log(` - ${warn}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=output.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"output.js","sourceRoot":"","sources":["../../src/utils/output.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,aAAa,CAAC,OAA0B,EAAE,UAAmB;IAC3E,IAAI,UAAU,EAAE,CAAC;QACf,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QAC9C,OAAO;IACT,CAAC;IAED,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC;IACvD,OAAO,CAAC,GAAG,CAAC,GAAG,MAAM,KAAK,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;IAE3C,IAAI,OAAO,CAAC,KAAK,IAAI,OAAO,CAAC,KAAK,GAAG,CAAC,EAAE,CAAC;QACvC,OAAO,CAAC,GAAG,CAAC,cAAc,OAAO,CAAC,KAAK,WAAW,CAAC,CAAC;IACtD,CAAC;IAED,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;QAC1B,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;QACzB,KAAK,MAAM,GAAG,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;YACjC,OAAO,CAAC,GAAG,CAAC,SAAS,GAAG,EAAE,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC;IAED,IAAI,OAAO,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;QAC5B,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;QAC3B,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;YACpC,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC;QAC/B,CAAC;IACH,CAAC;AACH,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "n8n-workflow-validator",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Standalone CLI validator for n8n workflow JSON files - mirrors official n8n import validation",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"n8n-validate": "dist/cli.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"README.md",
|
|
14
|
+
"LICENSE"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc",
|
|
18
|
+
"dev": "tsc --watch",
|
|
19
|
+
"validate": "node dist/cli.js",
|
|
20
|
+
"test": "node dist/cli.js test/fixtures/*.json"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"n8n",
|
|
24
|
+
"workflow",
|
|
25
|
+
"validation",
|
|
26
|
+
"cli",
|
|
27
|
+
"json",
|
|
28
|
+
"validator",
|
|
29
|
+
"automation"
|
|
30
|
+
],
|
|
31
|
+
"author": "Yigit Konur",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "git+https://github.com/yigitkonur/n8n-workflow-validator.git"
|
|
36
|
+
},
|
|
37
|
+
"bugs": {
|
|
38
|
+
"url": "https://github.com/yigitkonur/n8n-workflow-validator/issues"
|
|
39
|
+
},
|
|
40
|
+
"homepage": "https://github.com/yigitkonur/n8n-workflow-validator#readme",
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=18.0.0"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"esprima-next": "^5.8.4",
|
|
46
|
+
"jsonrepair": "^3.8.0"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@types/node": "^20.0.0",
|
|
50
|
+
"typescript": "^5.3.0"
|
|
51
|
+
}
|
|
52
|
+
}
|