@replikanti/flowlint-core 0.6.1 → 0.7.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/dist/index.d.mts +16 -1
- package/dist/index.d.ts +16 -1
- package/dist/index.js +59 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +59 -0
- package/dist/index.mjs.map +1 -1
- package/package.json +57 -57
package/dist/index.d.mts
CHANGED
|
@@ -227,4 +227,19 @@ declare function countFindingsBySeverity(findings: Finding[]): FindingsSummary;
|
|
|
227
227
|
*/
|
|
228
228
|
declare function sortFindingsBySeverity(findings: Finding[]): Finding[];
|
|
229
229
|
|
|
230
|
-
|
|
230
|
+
type Conclusion = 'action_required' | 'neutral' | 'success' | 'failure';
|
|
231
|
+
declare function buildCheckOutput({ findings, cfg, summaryOverride, conclusionOverride, }: {
|
|
232
|
+
findings: Finding[];
|
|
233
|
+
cfg: FlowLintConfig;
|
|
234
|
+
summaryOverride?: string;
|
|
235
|
+
conclusionOverride?: Conclusion;
|
|
236
|
+
}): {
|
|
237
|
+
conclusion: Conclusion;
|
|
238
|
+
output: {
|
|
239
|
+
title: string;
|
|
240
|
+
summary: string;
|
|
241
|
+
};
|
|
242
|
+
};
|
|
243
|
+
declare function buildAnnotations(findings: Finding[]): any[];
|
|
244
|
+
|
|
245
|
+
export { type Edge, type FilesConfig, type Finding, type FindingSeverity, type FlowLintConfig, type Graph, type NodeRef, type PRFile, type ReportConfig, type RuleConfig, type RuleContext$1 as RuleContext, type RuleRunner, ValidationError, buildAnnotations, buildCheckOutput, countFindingsBySeverity, defaultConfig, flattenConnections, getExampleLink, isApiNode, isErrorProneNode, isMutationNode, isNotificationNode, isTerminalNode, loadConfig, parseConfig, parseN8n, runAllRules, sortFindingsBySeverity, validateConfig, validateN8nWorkflow };
|
package/dist/index.d.ts
CHANGED
|
@@ -227,4 +227,19 @@ declare function countFindingsBySeverity(findings: Finding[]): FindingsSummary;
|
|
|
227
227
|
*/
|
|
228
228
|
declare function sortFindingsBySeverity(findings: Finding[]): Finding[];
|
|
229
229
|
|
|
230
|
-
|
|
230
|
+
type Conclusion = 'action_required' | 'neutral' | 'success' | 'failure';
|
|
231
|
+
declare function buildCheckOutput({ findings, cfg, summaryOverride, conclusionOverride, }: {
|
|
232
|
+
findings: Finding[];
|
|
233
|
+
cfg: FlowLintConfig;
|
|
234
|
+
summaryOverride?: string;
|
|
235
|
+
conclusionOverride?: Conclusion;
|
|
236
|
+
}): {
|
|
237
|
+
conclusion: Conclusion;
|
|
238
|
+
output: {
|
|
239
|
+
title: string;
|
|
240
|
+
summary: string;
|
|
241
|
+
};
|
|
242
|
+
};
|
|
243
|
+
declare function buildAnnotations(findings: Finding[]): any[];
|
|
244
|
+
|
|
245
|
+
export { type Edge, type FilesConfig, type Finding, type FindingSeverity, type FlowLintConfig, type Graph, type NodeRef, type PRFile, type ReportConfig, type RuleConfig, type RuleContext$1 as RuleContext, type RuleRunner, ValidationError, buildAnnotations, buildCheckOutput, countFindingsBySeverity, defaultConfig, flattenConnections, getExampleLink, isApiNode, isErrorProneNode, isMutationNode, isNotificationNode, isTerminalNode, loadConfig, parseConfig, parseN8n, runAllRules, sortFindingsBySeverity, validateConfig, validateN8nWorkflow };
|
package/dist/index.js
CHANGED
|
@@ -31,6 +31,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
ValidationError: () => ValidationError,
|
|
34
|
+
buildAnnotations: () => buildAnnotations,
|
|
35
|
+
buildCheckOutput: () => buildCheckOutput,
|
|
34
36
|
countFindingsBySeverity: () => countFindingsBySeverity,
|
|
35
37
|
defaultConfig: () => defaultConfig,
|
|
36
38
|
flattenConnections: () => flattenConnections,
|
|
@@ -1206,4 +1208,61 @@ function sortFindingsBySeverity(findings) {
|
|
|
1206
1208
|
const order = getSeverityOrder();
|
|
1207
1209
|
return [...findings].sort((a, b) => order[a.severity] - order[b.severity]);
|
|
1208
1210
|
}
|
|
1211
|
+
|
|
1212
|
+
// src/reporter/reporter.ts
|
|
1213
|
+
function buildCheckOutput({
|
|
1214
|
+
findings,
|
|
1215
|
+
cfg,
|
|
1216
|
+
summaryOverride,
|
|
1217
|
+
conclusionOverride
|
|
1218
|
+
}) {
|
|
1219
|
+
const summary = summaryOverride ?? summarize(findings);
|
|
1220
|
+
const conclusion = conclusionOverride ?? inferConclusion(findings);
|
|
1221
|
+
return {
|
|
1222
|
+
conclusion,
|
|
1223
|
+
output: {
|
|
1224
|
+
title: process.env.CHECK_TITLE || "FlowLint findings",
|
|
1225
|
+
summary
|
|
1226
|
+
}
|
|
1227
|
+
};
|
|
1228
|
+
}
|
|
1229
|
+
function buildAnnotations(findings) {
|
|
1230
|
+
const severityOrder = ["must", "should", "nit"];
|
|
1231
|
+
const ordered = [...findings].sort((a, b) => severityOrder.indexOf(a.severity) - severityOrder.indexOf(b.severity));
|
|
1232
|
+
return ordered.map((finding) => {
|
|
1233
|
+
const line = finding.line ?? 1;
|
|
1234
|
+
let rawDetails = finding.raw_details;
|
|
1235
|
+
if (finding.documentationUrl) {
|
|
1236
|
+
const docLine = `See examples: ${finding.documentationUrl}`;
|
|
1237
|
+
rawDetails = rawDetails ? `${docLine}
|
|
1238
|
+
|
|
1239
|
+
${rawDetails}` : docLine;
|
|
1240
|
+
}
|
|
1241
|
+
return {
|
|
1242
|
+
path: finding.path,
|
|
1243
|
+
start_line: line,
|
|
1244
|
+
end_line: line,
|
|
1245
|
+
annotation_level: mapSeverity(finding.severity),
|
|
1246
|
+
message: `${finding.rule}: ${finding.message}`,
|
|
1247
|
+
raw_details: rawDetails?.slice(0, 64e3)
|
|
1248
|
+
};
|
|
1249
|
+
});
|
|
1250
|
+
}
|
|
1251
|
+
function inferConclusion(findings) {
|
|
1252
|
+
if (findings.some((f) => f.severity === "must")) return "failure";
|
|
1253
|
+
if (findings.some((f) => f.severity === "should")) return "neutral";
|
|
1254
|
+
return "success";
|
|
1255
|
+
}
|
|
1256
|
+
function summarize(findings) {
|
|
1257
|
+
if (findings.length === 0) return "No issues found.";
|
|
1258
|
+
const must = findings.filter((f) => f.severity === "must").length;
|
|
1259
|
+
const should = findings.filter((f) => f.severity === "should").length;
|
|
1260
|
+
const nit = findings.filter((f) => f.severity === "nit").length;
|
|
1261
|
+
return `${must} must-fix, ${should} should-fix, ${nit} nit.`;
|
|
1262
|
+
}
|
|
1263
|
+
function mapSeverity(severity) {
|
|
1264
|
+
if (severity === "must") return "failure";
|
|
1265
|
+
if (severity === "should") return "warning";
|
|
1266
|
+
return "notice";
|
|
1267
|
+
}
|
|
1209
1268
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/parser/parser-n8n.ts","../src/schemas/index.ts","../src/schemas/n8n-workflow.schema.json","../src/utils/utils.ts","../src/rules/rule-utils.ts","../src/rules/index.ts","../src/config/default-config.ts","../src/config/loader.ts","../src/utils/findings.ts"],"sourcesContent":["/**\n * @flowlint/core - Core linting engine for n8n workflows\n * \n * This package provides the core functionality for analyzing n8n workflows:\n * - Parsing n8n workflow JSON/YAML files\n * - Running linting rules\n * - Generating findings/reports\n * - Configuration management\n */\n\n// Parser\nexport { parseN8n } from './parser/parser-n8n';\n\n// Rules\nexport { runAllRules } from './rules';\n\n// Schemas\nexport { validateN8nWorkflow, ValidationError } from './schemas';\n\n// Config\nexport { \n defaultConfig, \n loadConfig, \n parseConfig, \n validateConfig,\n type FlowLintConfig,\n type RuleConfig,\n type FilesConfig,\n type ReportConfig,\n} from './config';\n\n// Types\nexport type {\n Finding,\n FindingSeverity,\n Graph,\n NodeRef,\n Edge,\n RuleContext,\n RuleRunner,\n PRFile,\n} from './types';\n\n// Utils\nexport { \n flattenConnections, \n isErrorProneNode, \n getExampleLink,\n isApiNode,\n isMutationNode,\n isNotificationNode,\n isTerminalNode,\n} from './utils/utils';\nexport { countFindingsBySeverity, sortFindingsBySeverity } from './utils/findings';\r\n","import YAML from 'yaml';\nimport type { Graph, NodeRef, Edge } from '../types';\nimport { validateN8nWorkflow } from '../schemas';\nimport { flattenConnections, isErrorProneNode } from '../utils/utils';\n\nexport function parseN8n(doc: string): Graph {\n let parsed: any;\n try {\n parsed = JSON.parse(doc);\n } catch {\n parsed = YAML.parse(doc);\n }\n\n // Validate workflow structure before parsing\n validateN8nWorkflow(parsed);\n\n const nodes: NodeRef[] = parsed.nodes.map((node: any, idx: number) => {\n const nodeId = node.id || node.name || `node-${idx}`;\n const flags: NodeRef['flags'] = {\n continueOnFail: node.continueOnFail,\n retryOnFail: node.retryOnFail ?? node.settings?.retryOnFail,\n waitBetweenTries: node.waitBetweenTries ?? node.settings?.waitBetweenTries,\n maxTries: node.maxTries ?? node.settings?.maxTries,\n };\n const hasFlags =\n flags.continueOnFail !== undefined ||\n flags.retryOnFail !== undefined ||\n flags.waitBetweenTries !== undefined;\n\n return {\n id: nodeId,\n type: node.type,\n name: node.name,\n params: node.parameters,\n cred: node.credentials,\n flags: hasFlags ? flags : undefined,\n };\n });\n\n const nameToId = new Map<string, string>();\n for (const node of nodes) {\n if (node.id) nameToId.set(node.id, node.id);\n if (node.name) nameToId.set(node.name, node.id);\n }\n\n const lines = doc.split(/\\r?\\n/);\n const idLine = new Map<string, number>();\n const nameLine = new Map<string, number>();\n lines.forEach((line, idx) => {\n const idMatch = line.match(/\"id\":\\s*\"([^\"]+)\"/);\n if (idMatch) idLine.set(idMatch[1], idx + 1);\n const nameMatch = line.match(/\"name\":\\s*\"([^\"]+)\"/);\n if (nameMatch) nameLine.set(nameMatch[1], idx + 1);\n });\n\n const nodeLines = new Map<string, number>();\n for (const node of nodes) {\n const lineNumber = (node.name && nameLine.get(node.name)) || idLine.get(node.id);\n if (lineNumber) {\n nodeLines.set(node.id, lineNumber);\n }\n }\n\n const nodeById = new Map<string, NodeRef>();\n for (const node of nodes) {\n nodeById.set(node.id, node);\n }\n\n const resolveEdgeType = (\n connectionType: string,\n outputIndex: number | undefined,\n sourceType?: string,\n ): Edge['on'] => {\n if (connectionType === 'error') return 'error';\n if (connectionType === 'timeout') return 'timeout';\n\n if (connectionType === 'main') {\n if (\n typeof outputIndex === 'number' &&\n outputIndex > 0 &&\n sourceType &&\n isErrorProneNode(sourceType)\n ) {\n return 'error';\n }\n return 'success';\n }\n\n return 'success';\n };\n\n const edges: Edge[] = [];\n Object.entries(parsed.connections || {}).forEach(([from, exits]) => {\n if (!exits) {\n return;\n }\n const exitChannels = exits as Record<string, any>;\n Object.entries(exitChannels).forEach(([exitType, conn]) => {\n const sourceId = nameToId.get(from) ?? from;\n const sourceNode = nodeById.get(sourceId);\n\n const enqueueEdges = (value: any, outputIndex?: number) => {\n flattenConnections(value).forEach((link) => {\n if (!link || typeof link !== 'object') return;\n const targetId = nameToId.get(link.node) ?? link.node;\n if (!targetId) return;\n\n const edgeType = resolveEdgeType(exitType, outputIndex, sourceNode?.type);\n edges.push({ from: sourceId, to: targetId, on: edgeType });\n });\n };\n\n if (Array.isArray(conn)) {\n conn.forEach((entry, index) => enqueueEdges(entry, index));\n } else {\n enqueueEdges(conn);\n }\n });\n });\n\n return {\n nodes,\n edges,\n meta: {\n credentials: !!parsed.credentials,\n nodeLines: Object.fromEntries(nodeLines),\n },\n };\n}\n\r\n","import Ajv, { type ValidateFunction } from 'ajv';\nimport addFormats from 'ajv-formats';\nimport workflowSchema from './n8n-workflow.schema.json';\nimport { flattenConnections, buildValidationErrors } from '../utils/utils';\n\n// Custom error class for validation failures\nexport class ValidationError extends Error {\n constructor(\n public errors: Array<{\n path: string;\n message: string;\n suggestion?: string;\n }>\n ) {\n super(`Workflow validation failed: ${errors.length} error(s)`);\n this.name = 'ValidationError';\n }\n}\n\n// Dummy validator that always passes\nconst createDummyValidator = (): ValidateFunction => {\n const v: any = () => true;\n v.errors = [];\n return v as ValidateFunction;\n};\n\n// Singleton instance\nlet validatorInstance: ValidateFunction | null = null;\n\nfunction getValidator(): ValidateFunction {\n if (validatorInstance) return validatorInstance;\n\n // Detect Node.js environment safely\n // Use optional chaining to satisfy SonarQube\n const isNode = typeof process !== 'undefined' && process?.versions?.node != null;\n\n if (isNode) {\n try {\n const ajv = new Ajv({\n allErrors: true,\n strict: false,\n verbose: true,\n code: { source: true, es5: true }\n });\n addFormats(ajv);\n validatorInstance = ajv.compile(workflowSchema);\n } catch (error) {\n // Fallback to dummy validator if compilation fails (e.g. due to strict CSP in some environments)\n console.warn('Failed to compile JSON schema validator, falling back to dummy validator:', error);\n validatorInstance = createDummyValidator();\n }\n } else {\n validatorInstance = createDummyValidator();\n }\n\n return validatorInstance;\n}\n\n/**\n * Throws a ValidationError if the provided set contains items.\n * Centralizes the pattern of checking validation results and throwing errors.\n * \n * @param items - Set of items that represent validation failures\n * @param config - Configuration for building error messages\n * @throws ValidationError if items set is not empty\n */\nfunction throwIfInvalid<T>(\n items: Set<T>,\n config: {\n path: string;\n messageTemplate: (item: T) => string;\n suggestionTemplate: (item: T) => string;\n }\n): void {\n if (items.size > 0) {\n const errors = buildValidationErrors(items, config);\n throw new ValidationError(errors);\n }\n}\n\n/**\n * Check for duplicate node IDs in the workflow\n */\nfunction checkDuplicateNodeIds(data: any): void {\n if (!Array.isArray(data.nodes)) return;\n\n const seen = new Set<string>();\n const duplicates = new Set<string>();\n\n for (const node of data.nodes) {\n if (node.id && seen.has(node.id)) {\n duplicates.add(node.id);\n }\n if (node.id) {\n seen.add(node.id);\n }\n }\n\n throwIfInvalid(duplicates, {\n path: 'nodes[].id',\n messageTemplate: (id) => `Duplicate node ID: \"${id}\"`,\n suggestionTemplate: (id) => `Each node must have a unique ID. Remove or rename the duplicate node with ID \"${id}\".`,\n });\n}\n\n/**\n * Check for orphaned connections (references to non-existent nodes)\n */\nfunction checkOrphanedConnections(data: any): void {\n if (!data.connections || !Array.isArray(data.nodes)) return;\n\n const nodeIds = new Set<string>();\n const nodeNames = new Set<string>();\n\n // Collect all node IDs and names\n for (const node of data.nodes) {\n if (node.id) nodeIds.add(node.id);\n if (node.name) nodeNames.add(node.name);\n }\n\n const orphanedRefs = new Set<string>();\n\n // Check all connection targets\n Object.entries(data.connections).forEach(([sourceId, channels]) => {\n // Check if source exists\n if (!nodeIds.has(sourceId) && !nodeNames.has(sourceId)) {\n orphanedRefs.add(sourceId);\n }\n\n // Check targets\n if (typeof channels === 'object' && channels !== null) {\n Object.values(channels).forEach((connArray: any) => {\n const flatConnections = flattenConnections(connArray);\n flatConnections.forEach((conn: any) => {\n if (conn?.node) {\n if (!nodeIds.has(conn.node) && !nodeNames.has(conn.node)) {\n orphanedRefs.add(conn.node);\n }\n }\n });\n });\n }\n });\n\n throwIfInvalid(orphanedRefs, {\n path: 'connections',\n messageTemplate: (ref) => `Orphaned connection reference: \"${ref}\"`,\n suggestionTemplate: (ref) => `Connection references node \"${ref}\" which does not exist. Add the missing node or remove the invalid connection.`,\n });\n}\n\n/**\n * Validate n8n workflow structure\n * Throws ValidationError with detailed messages if validation fails\n */\nexport function validateN8nWorkflow(data: any): void {\n const validate = getValidator();\n\n // Basic schema validation\n if (!validate(data)) {\n const errors = (validate.errors || []).map((err: any) => {\n const path = err.instancePath || err.schemaPath;\n const message = err.message || 'Validation error';\n let suggestion = '';\n\n // Provide helpful suggestions based on error type\n if (err.keyword === 'required') {\n const missing = err.params?.missingProperty;\n suggestion = `Add the required field \"${missing}\" to the workflow.`;\n } else if (err.keyword === 'type') {\n const expected = err.params?.type;\n suggestion = `The field should be of type \"${expected}\".`;\n } else if (err.keyword === 'minLength') {\n suggestion = 'This field cannot be empty.';\n }\n\n return { path, message, suggestion };\n });\n\n throw new ValidationError(errors);\n }\n\n // Additional custom validations\n checkDuplicateNodeIds(data);\n checkOrphanedConnections(data);\n}\n\r\n","{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": \"https://flowlint.dev/schemas/n8n-workflow.json\",\n \"title\": \"n8n Workflow Schema\",\n \"description\": \"JSON Schema for n8n workflow files (v1.x)\",\n \"type\": \"object\",\n \"required\": [\"nodes\", \"connections\"],\n \"properties\": {\n \"name\": {\n \"type\": \"string\",\n \"description\": \"Workflow name\"\n },\n \"nodes\": {\n \"type\": \"array\",\n \"description\": \"Array of workflow nodes\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\"type\", \"name\"],\n \"properties\": {\n \"id\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"Unique node identifier\"\n },\n \"type\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"Node type (e.g., n8n-nodes-base.httpRequest)\"\n },\n \"name\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"Human-readable node name\"\n },\n \"parameters\": {\n \"type\": \"object\",\n \"description\": \"Node-specific configuration parameters\"\n },\n \"credentials\": {\n \"type\": \"object\",\n \"description\": \"Credential references for this node\"\n },\n \"position\": {\n \"type\": \"array\",\n \"description\": \"X,Y coordinates for UI placement\",\n \"items\": {\n \"type\": \"number\"\n },\n \"minItems\": 2,\n \"maxItems\": 2\n },\n \"continueOnFail\": {\n \"type\": \"boolean\",\n \"description\": \"Whether to continue execution on node failure\"\n },\n \"disabled\": {\n \"type\": \"boolean\",\n \"description\": \"Whether the node is disabled\"\n },\n \"notesInFlow\": {\n \"type\": \"boolean\"\n },\n \"notes\": {\n \"type\": \"string\"\n },\n \"typeVersion\": {\n \"type\": \"number\",\n \"description\": \"Version of the node type\"\n }\n },\n \"additionalProperties\": true\n }\n },\n \"connections\": {\n \"type\": \"object\",\n \"description\": \"Map of node connections (source node ID -> connection details)\",\n \"patternProperties\": {\n \"^.*$\": {\n \"type\": \"object\",\n \"description\": \"Connection channels for a source node\",\n \"patternProperties\": {\n \"^(main|error|timeout|.*?)$\": {\n \"description\": \"Connection array for this channel\",\n \"oneOf\": [\n {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\"node\"],\n \"properties\": {\n \"node\": {\n \"type\": \"string\",\n \"description\": \"Target node ID or name\"\n },\n \"type\": {\n \"type\": \"string\",\n \"description\": \"Connection type\"\n },\n \"index\": {\n \"type\": \"number\",\n \"description\": \"Output index\"\n }\n }\n }\n },\n {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\"node\"],\n \"properties\": {\n \"node\": {\n \"type\": \"string\"\n },\n \"type\": {\n \"type\": \"string\"\n },\n \"index\": {\n \"type\": \"number\"\n }\n }\n }\n }\n }\n ]\n }\n }\n }\n }\n },\n \"active\": {\n \"type\": \"boolean\",\n \"description\": \"Whether the workflow is active\"\n },\n \"settings\": {\n \"type\": \"object\",\n \"description\": \"Workflow settings\"\n },\n \"tags\": {\n \"type\": \"array\",\n \"description\": \"Workflow tags\",\n \"items\": {\n \"oneOf\": [\n { \"type\": \"string\" },\n {\n \"type\": \"object\",\n \"required\": [\"name\"],\n \"properties\": {\n \"id\": { \"type\": \"string\" },\n \"name\": { \"type\": \"string\" }\n },\n \"additionalProperties\": true\n }\n ]\n }\n },\n \"pinData\": {\n \"type\": \"object\",\n \"description\": \"Pinned execution data for testing\"\n },\n \"versionId\": {\n \"type\": \"string\",\n \"description\": \"Workflow version identifier\"\n },\n \"id\": {\n \"type\": [\"string\", \"number\"],\n \"description\": \"Workflow ID\"\n },\n \"meta\": {\n \"type\": \"object\",\n \"description\": \"Metadata\"\n }\n },\n \"additionalProperties\": true\n}\n","import type { Graph, NodeRef } from '../types';\n\n/**\n * Shared utility functions for workflow parsing and validation\n */\n\n\n/**\n * Helper to flatten nested connection arrays from n8n workflow connections.\n * Connections can be nested in various ways (arrays of arrays, objects with node properties).\n * This recursively flattens them to a simple array of connection objects.\n *\n * @param value - The connection value to flatten (can be array, object, or primitive)\n * @returns Array of connection objects with 'node' property\n */\nexport function flattenConnections(value: any): any[] {\n if (!value) return [];\n if (Array.isArray(value)) {\n return value.flatMap((entry) => flattenConnections(entry));\n }\n if (typeof value === 'object' && 'node' in value) {\n return [value];\n }\n return [];\n}\n\n/**\n * Build validation error objects from a collection of items using provided templates.\n * This utility eliminates code duplication in validation error construction.\n *\n * @template T - Type of items to process\n * @param items - Set or array of items to convert to validation errors\n * @param errorConfig - Configuration object containing:\n * - path: The JSON path where the error occurred\n * - messageTemplate: Function to generate error message for each item\n * - suggestionTemplate: Function to generate actionable suggestion for each item\n * @returns Array of validation error objects with path, message, and suggestion fields\n *\n * @example\n * ```typescript\n * const duplicates = new Set(['node1', 'node2']);\n * const errors = buildValidationErrors(duplicates, {\n * path: 'nodes[].id',\n * messageTemplate: (id) => `Duplicate node ID: \"${id}\"`, \n * suggestionTemplate: (id) => `Remove or rename the duplicate node with ID \"${id}\".`\n * });\n * ```\n */\nexport function buildValidationErrors<T>(\n items: Set<T> | T[],\n errorConfig: {\n path: string;\n messageTemplate: (item: T) => string;\n suggestionTemplate: (item: T) => string;\n }\n): Array<{ path: string; message: string; suggestion: string }> {\n const itemArray = Array.isArray(items) ? items : Array.from(items);\n return itemArray.map((item) => ({\n path: errorConfig.path,\n message: errorConfig.messageTemplate(item),\n suggestion: errorConfig.suggestionTemplate(item),\n }));\n}\n\nexport function collectStrings(value: unknown, out: string[] = []): string[] {\n if (typeof value === 'string') out.push(value);\n else if (Array.isArray(value)) value.forEach((entry) => collectStrings(entry, out));\n else if (value && typeof value === 'object')\n Object.values(value).forEach((entry) => collectStrings(entry, out));\n return out;\n}\n\nexport function toRegex(pattern: string): RegExp {\n let source = pattern;\n let flags = '';\n if (source.startsWith('(?i)')) {\n source = source.slice(4);\n flags += 'i';\n }\n return new RegExp(source, flags);\n}\n\nexport function isApiNode(type: string) {\n return /http|request|google|facebook|ads/i.test(type);\n}\n\nexport function isMutationNode(type: string) {\n return /write|insert|update|delete|post|put|patch|database|mongo|supabase|sheet/i.test(type);\n}\n\nexport function isErrorProneNode(type: string) {\n return isApiNode(type) || isMutationNode(type) || /execute|workflow|function/i.test(type);\n}\n\nexport function isNotificationNode(type: string) {\n return /slack|discord|email|gotify|mattermost|microsoftTeams|pushbullet|pushover|rocketchat|zulip|telegram/i.test(\n type,\n );\n}\n\nexport function isErrorHandlerNode(type: string, name?: string) {\n const normalizedType = type.toLowerCase();\n if (normalizedType.includes('stopanderror')) return true;\n if (normalizedType.includes('errorhandler')) return true;\n if (normalizedType.includes('raiseerror')) return true;\n\n const normalizedName = name?.toLowerCase() ?? '';\n if (normalizedName.includes('stop and error')) return true;\n if (normalizedName.includes('error handler')) return true;\n\n return false;\n}\n\nexport function isRejoinNode(graph: Graph, nodeId: string): boolean {\n const incoming = graph.edges.filter((e) => e.to === nodeId);\n if (incoming.length <= 1) return false;\n const hasErrorEdge = incoming.some((e) => e.on === 'error');\n const hasSuccessEdge = incoming.some((e) => e.on !== 'error');\n return hasErrorEdge && hasSuccessEdge;\n}\n\nexport function isMeaningfulConsumer(node: NodeRef): boolean {\n // A meaningful consumer is a node that has an external side-effect.\n return (\n isMutationNode(node.type) || // Writes to a DB, sheet, etc.\n isNotificationNode(node.type) || // Sends a message to Slack, email, etc.\n isApiNode(node.type) || // Calls an external API\n /respondToWebhook/i.test(node.type) // Specifically nodes that send a response back.\n );\n}\n\nexport function containsCandidate(value: unknown, candidates: string[]): boolean {\n if (!value || !candidates.length) return false;\n\n const queue: unknown[] = [value];\n const candidateRegex = new RegExp(`(${candidates.join('|')})`, 'i');\n\n while (queue.length > 0) {\n const current = queue.shift();\n\n if (typeof current === 'string') {\n if (candidateRegex.test(current)) return true;\n } else if (Array.isArray(current)) {\n queue.push(...current);\n } else if (current && typeof current === 'object') {\n for (const [key, val] of Object.entries(current)) {\n if (candidateRegex.test(key)) return true;\n queue.push(val);\n }\n }\n }\n\n return false;\n}\n\nconst TERMINAL_NODE_PATTERNS = [\n 'respond', 'reply', 'end', 'stop', 'terminate', 'return', 'sticky', 'note', 'noop', 'no operation',\n 'slack', 'email', 'discord', 'teams', 'webhook', 'telegram', 'pushbullet', 'mattermost', 'notifier', 'notification', 'alert', 'sms', 'call',\n];\n\nexport function isTerminalNode(type: string, name?: string) {\n const label = `${type} ${name ?? ''}`.toLowerCase();\n return TERMINAL_NODE_PATTERNS.some((pattern) => label.includes(pattern));\n}\n\nexport function readNumber(source: any, paths: string[]): number | undefined {\n for (const path of paths) {\n const value = path.split('.').reduce<any>((acc, key) => (acc ? acc[key] : undefined), source);\n if (typeof value === 'number') return value;\n if (typeof value === 'string' && !Number.isNaN(Number(value))) return Number(value);\n }\n return undefined;\n}\n\nexport function findAllDownstreamNodes(graph: Graph, startNodeId: string): Set<string> {\n const visited = new Set<string>();\n const queue: string[] = [startNodeId];\n visited.add(startNodeId);\n\n let head = 0;\n while (head < queue.length) {\n const currentId = queue[head++]!;\n const outgoing = graph.edges.filter((e) => e.from === currentId);\n for (const edge of outgoing) {\n if (!visited.has(edge.to)) {\n visited.add(edge.to);\n queue.push(edge.to);\n }\n }\n }\n return visited;\n}\n\nexport function findAllUpstreamNodes(graph: Graph, startNodeId: string): Set<string> {\n const visited = new Set<string>();\n const queue: string[] = [startNodeId];\n visited.add(startNodeId);\n\n let head = 0;\n while (head < queue.length) {\n const currentId = queue[head++]!;\n const incoming = graph.edges.filter((e) => e.to === currentId);\n for (const edge of incoming) {\n if (!visited.has(edge.from)) {\n visited.add(edge.from);\n queue.push(edge.from);\n }\n }\n }\n return visited;\n}\n\nexport const EXAMPLES_BASE_URL = \"https://github.com/Replikanti/flowlint-examples/tree/main\";\n\nexport function getExampleLink(ruleId: string): string {\n return `${EXAMPLES_BASE_URL}/${ruleId}`;\n}\n\r\n","import type { Graph, Finding, NodeRef, FindingSeverity } from '../types';\nimport type { FlowLintConfig } from '../config';\nimport { collectStrings, toRegex } from '../utils/utils';\n\ntype Rule = string;\ntype RuleContext = { path: string; cfg: FlowLintConfig; nodeLines?: Record<string, number> };\ntype RuleRunner = (graph: Graph, ctx: RuleContext) => Finding[];\ntype NodeRuleLogic = (node: NodeRef, graph: Graph, ctx: RuleContext) => Finding | Finding[] | null;\n\n/**\n * A higher-order function to create a rule that iterates over each node in the graph.\n * It abstracts the boilerplate of checking if the rule is enabled and iterating through nodes.\n *\n * @param ruleId - The ID of the rule (e.g., 'R1').\n * @param configKey - The key in the FlowLintConfig rules object.\n * @param logic - The function containing the core logic to be executed for each node.\n * @returns A RuleRunner function.\n */\nexport function createNodeRule(\n ruleId: Rule,\n configKey: keyof FlowLintConfig['rules'],\n logic: NodeRuleLogic,\n): RuleRunner {\n return (graph: Graph, ctx: RuleContext): Finding[] => {\n const ruleConfig = ctx.cfg.rules[configKey] as { enabled?: boolean };\n if (!ruleConfig?.enabled) {\n return [];\n }\n\n const findings: Finding[] = [];\n for (const node of graph.nodes) {\n const result = logic(node, graph, ctx);\n if (result) {\n if (Array.isArray(result)) {\n findings.push(...result);\n } else {\n findings.push(result);\n }\n }\n }\n return findings;\n };\n}\n\ntype HardcodedStringRuleOptions = {\n ruleId: Rule;\n severity: FindingSeverity;\n configKey: 'secrets' | 'config_literals';\n messageFn: (node: NodeRef, value: string) => string;\n details: string;\n};\n\n/**\n * Creates a rule that checks for hardcoded strings in node parameters based on a denylist of regex patterns.\n * This is used to create R4 (Secrets) and R9 (Config Literals).\n *\n * @param options - The configuration for the hardcoded string rule.\n * @returns A RuleRunner function.\n */\nexport function createHardcodedStringRule({\n ruleId,\n severity,\n configKey,\n messageFn,\n details,\n}: HardcodedStringRuleOptions): RuleRunner {\n const logic: NodeRuleLogic = (node, graph, ctx) => {\n const cfg = ctx.cfg.rules[configKey];\n if (!cfg.denylist_regex?.length) {\n return null;\n }\n const regexes = cfg.denylist_regex.map((pattern) => toRegex(pattern));\n\n const findings: Finding[] = [];\n const strings = collectStrings(node.params);\n\n for (const value of strings) {\n // Ignore expressions and empty strings\n if (!value || value.includes('{{')) {\n continue;\n }\n\n if (regexes.some((regex) => regex.test(value))) {\n findings.push({\n rule: ruleId,\n severity,\n path: ctx.path,\n message: messageFn(node, value),\n nodeId: node.id,\n line: ctx.nodeLines?.[node.id],\n raw_details: details,\n });\n // Only report one finding per node to avoid noise\n break;\n }\n }\n return findings;\n };\n\n return createNodeRule(ruleId, configKey, logic);\n}\n\r\n","import type { Graph, Finding, NodeRef } from '../types';\nimport type { FlowLintConfig } from '../config';\nimport { createNodeRule, createHardcodedStringRule } from './rule-utils';\nimport {\n isApiNode,\n isMutationNode,\n isErrorProneNode,\n isNotificationNode,\n isErrorHandlerNode,\n isRejoinNode,\n isMeaningfulConsumer,\n isTerminalNode,\n readNumber,\n findAllDownstreamNodes,\n findAllUpstreamNodes,\n containsCandidate,\n} from '../utils/utils';\n\ntype RuleContext = { path: string; cfg: FlowLintConfig; nodeLines?: Record<string, number> };\n\ntype RuleRunner = (graph: Graph, ctx: RuleContext) => Finding[];\n\n// --- Rule Definitions using Helpers ---\n\nconst r1Retry = createNodeRule('R1', 'rate_limit_retry', (node, graph, ctx) => {\n if (!isApiNode(node.type)) return null;\n\n const params = (node.params ?? {}) as Record<string, unknown>;\n const options = ((params as any).options ?? {}) as Record<string, unknown>;\n\n const retryCandidates: unknown[] = [\n options.retryOnFail,\n (params as any).retryOnFail,\n node.flags?.retryOnFail,\n ];\n\n const retryOnFail = retryCandidates.find((value) => value !== undefined && value !== null);\n\n if (retryOnFail === true) {\n return null;\n }\n\n if (typeof retryOnFail === 'string') {\n const normalized = retryOnFail.trim().toLowerCase();\n if (retryOnFail.includes('{{') || normalized === 'true') {\n return null;\n }\n }\n\n return {\n rule: 'R1',\n severity: 'must',\n path: ctx.path,\n message: `Node ${node.name || node.id} is missing retry/backoff configuration`,\n raw_details: `In the node properties, enable \"Retry on Fail\" under Options.`,\n nodeId: node.id,\n line: ctx.nodeLines?.[node.id],\n };\n});\n\nconst r2ErrorHandling = createNodeRule('R2', 'error_handling', (node, graph, ctx) => {\n if (ctx.cfg.rules.error_handling.forbid_continue_on_fail && node.flags?.continueOnFail) {\n return {\n rule: 'R2',\n severity: 'must',\n path: ctx.path,\n message: `Node ${node.name || node.id} has continueOnFail enabled (disable it and route errors explicitly)`,\n nodeId: node.id,\n line: ctx.nodeLines?.[node.id],\n raw_details:\n 'Open the node in n8n and disable \"Continue On Fail\" (Options > Continue On Fail). Route failures down an explicit error branch instead.',\n };\n }\n return null;\n});\n\nconst r4Secrets = createHardcodedStringRule({\n ruleId: 'R4',\n severity: 'must',\n configKey: 'secrets',\n messageFn: (node) => `Node ${node.name || node.id} contains a hardcoded secret (move it to credentials/env vars)`,\n details: 'Move API keys/tokens into Credentials or environment variables; the workflow should only reference {{$credentials.*}} expressions.',\n});\n\nconst r9ConfigLiterals = createHardcodedStringRule({\n ruleId: 'R9',\n severity: 'should',\n configKey: 'config_literals',\n messageFn: (node, value) => `Node ${node.name || node.id} contains env-specific literal \"${value.substring(0, 40)}\" (move to expression/credential)`,\n details: 'Move environment-specific URLs/IDs into expressions or credentials (e.g., {{$env.API_BASE_URL}}) so the workflow is portable.',\n});\n\nconst r10NamingConvention = createNodeRule('R10', 'naming_convention', (node, graph, ctx) => {\n const genericNames = new Set(ctx.cfg.rules.naming_convention.generic_names ?? []);\n if (!node.name || genericNames.has(node.name.toLowerCase())) {\n return {\n rule: 'R10',\n severity: 'nit',\n path: ctx.path,\n message: `Node ${node.id} uses a generic name \"${node.name ?? ''}\" (rename it to describe the action)`,\n nodeId: node.id,\n line: ctx.nodeLines?.[node.id],\n raw_details: 'Rename the node to describe its purpose (e.g., \"Check subscription status\" instead of \"IF\") for easier reviews and debugging.',\n };\n }\n return null;\n});\n\nconst DEPRECATED_NODES: Record<string, string> = {\n 'n8n-nodes-base.splitInBatches': 'Use Loop over items instead',\n 'n8n-nodes-base.executeWorkflow': 'Use Execute Workflow (Sub-Workflow) instead',\n};\n\nconst r11DeprecatedNodes = createNodeRule('R11', 'deprecated_nodes', (node, graph, ctx) => {\n if (DEPRECATED_NODES[node.type]) {\n return {\n rule: 'R11',\n severity: 'should',\n path: ctx.path,\n message: `Node ${node.name || node.id} uses deprecated type ${node.type} (replace with ${DEPRECATED_NODES[node.type]})`,\n nodeId: node.id,\n line: ctx.nodeLines?.[node.id],\n raw_details: `Replace this node with ${DEPRECATED_NODES[node.type]} so future n8n upgrades don’t break the workflow.`,\n };\n }\n return null;\n});\n\nconst r12UnhandledErrorPath = createNodeRule('R12', 'unhandled_error_path', (node, graph, ctx) => {\n if (!isErrorProneNode(node.type)) return null;\n\n const hasErrorPath = graph.edges.some((edge) => {\n if (edge.from !== node.id) return false;\n if (edge.on === 'error') return true;\n\n const targetNode = graph.nodes.find((candidate) => candidate.id === edge.to);\n return targetNode ? isErrorHandlerNode(targetNode.type, targetNode.name) : false;\n });\n\n if (!hasErrorPath) {\n return {\n rule: 'R12',\n severity: 'must',\n path: ctx.path,\n message: `Node ${node.name || node.id} has no error branch (add a red connector to handler)`,\n nodeId: node.id,\n line: ctx.nodeLines?.[node.id],\n raw_details:\n 'Add an error (red) branch to a Stop and Error or logging/alert node so failures do not disappear silently.',\n };\n }\n return null;\n});\n\nfunction r13WebhookAcknowledgment(graph: Graph, ctx: RuleContext): Finding[] {\n const cfg = ctx.cfg.rules.webhook_acknowledgment;\n if (!cfg?.enabled) return [];\n\n const findings: Finding[] = [];\n\n // Find all webhook trigger nodes (not respondToWebhook)\n const webhookNodes = graph.nodes.filter((node) =>\n node.type === 'n8n-nodes-base.webhook' ||\n (node.type.includes('webhook') && !node.type.includes('respondToWebhook'))\n );\n\n for (const webhookNode of webhookNodes) {\n // Get immediate downstream nodes\n const directDownstream = graph.edges\n .filter((edge) => edge.from === webhookNode.id)\n .map((edge) => graph.nodes.find((n) => n.id === edge.to))\n .filter((n): n is NodeRef => !!n);\n\n if (directDownstream.length === 0) continue;\n\n // Check if first downstream is \"Respond to Webhook\"\n const hasImmediateResponse = directDownstream.some((node) =>\n node.type === 'n8n-nodes-base.respondToWebhook' ||\n /respond.*webhook/i.test(node.type) ||\n /respond.*webhook/i.test(node.name || '')\n );\n\n if (hasImmediateResponse) continue; // Good pattern - immediate acknowledgment\n\n // Check if any downstream node is \"heavy\"\n const heavyNodeTypes = cfg.heavy_node_types || [\n 'n8n-nodes-base.httpRequest',\n 'n8n-nodes-base.postgres',\n 'n8n-nodes-base.mysql',\n 'n8n-nodes-base.mongodb',\n 'n8n-nodes-base.openAi',\n 'n8n-nodes-base.anthropic',\n ];\n\n const hasHeavyProcessing = directDownstream.some((node) =>\n heavyNodeTypes.includes(node.type) || /loop|batch/i.test(node.type)\n );\n\n if (hasHeavyProcessing) {\n findings.push({\n rule: 'R13',\n severity: 'must',\n path: ctx.path,\n message: `Webhook \"${webhookNode.name || webhookNode.id}\" performs heavy processing before acknowledgment (risk of timeout/duplicates)`,\n nodeId: webhookNode.id,\n line: ctx.nodeLines?.[webhookNode.id],\n raw_details: `Add a \"Respond to Webhook\" node immediately after the webhook trigger (return 200/204), then perform heavy processing. This prevents webhook timeouts and duplicate events.`,\n });\n }\n }\n\n return findings;\n}\n\nconst r14RetryAfterCompliance = createNodeRule('R14', 'retry_after_compliance', (node, graph, ctx) => {\n // Only check HTTP request nodes\n if (!isApiNode(node.type)) return null;\n\n const params = (node.params ?? {}) as Record<string, unknown>;\n const options = ((params as any).options ?? {}) as Record<string, unknown>;\n\n // Check if retry is enabled\n const retryCandidates: unknown[] = [\n options.retryOnFail,\n (params as any).retryOnFail,\n node.flags?.retryOnFail,\n ];\n\n const retryOnFail = retryCandidates.find((value) => value !== undefined && value !== null);\n\n // If retry is disabled or explicitly false, skip this rule\n if (!retryOnFail || retryOnFail === false) return null;\n\n // If retryOnFail is explicitly a string expression, skip if it's not \"true\"\n if (typeof retryOnFail === 'string') {\n const normalized = retryOnFail.trim().toLowerCase();\n if (retryOnFail.includes('{{') && normalized !== 'true') {\n return null; // Dynamic expression, assume it might handle retry-after\n }\n }\n\n // Check waitBetweenTries specifically (Pragmatic fix for n8n UI limitations)\n const waitBetweenTries = node.flags?.waitBetweenTries;\n if (waitBetweenTries !== undefined && waitBetweenTries !== null) {\n // If it's a static number (or numeric string), we accept it because n8n UI\n // often prevents using expressions here. We prioritize allowing retries (R1)\n // over strict Retry-After compliance if the platform limits the user.\n if (typeof waitBetweenTries === 'number') return null;\n if (\n typeof waitBetweenTries === 'string' &&\n !isNaN(Number(waitBetweenTries)) &&\n !waitBetweenTries.includes('{{')\n ) {\n return null;\n }\n }\n\n // Check if there's an expression/code that references retry-after\n const nodeStr = JSON.stringify(node);\n const hasRetryAfterLogic = /retry[-_]?after|retryafter/i.test(nodeStr);\n\n if (hasRetryAfterLogic) {\n return null; // Good - respects Retry-After\n }\n\n // Flag as violation\n return {\n rule: 'R14',\n severity: 'should',\n path: ctx.path,\n message: `Node ${node.name || node.id} has retry logic but ignores Retry-After headers (429/503 responses)`,\n raw_details: `Add expression to parse Retry-After header: const retryAfter = $json.headers['retry-after']; const delay = retryAfter ? (parseInt(retryAfter) || new Date(retryAfter) - Date.now()) : Math.min(1000 * Math.pow(2, $execution.retryCount), 60000); This prevents API bans and respects server rate limits.`,\n nodeId: node.id,\n line: ctx.nodeLines?.[node.id],\n };\n});\n\n\n// --- Rules with custom logic (not fitting the simple node-by-node pattern) ---\n\nfunction r3Idempotency(graph: Graph, ctx: RuleContext): Finding[] {\n const cfg = ctx.cfg.rules.idempotency;\n if (!cfg?.enabled) return [];\n\n const hasIngress = graph.nodes.some((node) => /webhook|trigger|start/i.test(node.type));\n if (!hasIngress) return [];\n\n const mutationNodes = graph.nodes.filter((node) => isMutationNode(node.type));\n if (!mutationNodes.length) return [];\n\n const findings: Finding[] = [];\n\n for (const mutationNode of mutationNodes) {\n const upstreamNodeIds = findAllUpstreamNodes(graph, mutationNode.id);\n const upstreamNodes = graph.nodes.filter((n) => upstreamNodeIds.has(n.id));\n\n const hasGuard = upstreamNodes.some((p) =>\n containsCandidate(p.params, cfg.key_field_candidates ?? []),\n );\n\n if (!hasGuard) {\n findings.push({\n rule: 'R3',\n severity: 'must',\n path: ctx.path,\n message: `The mutation path ending at \"${\n mutationNode.name || mutationNode.id\n }\" appears to be missing an idempotency guard.`,\n raw_details: `Ensure one of the upstream nodes or the mutation node itself uses an idempotency key, such as one of: ${(cfg.key_field_candidates ?? []).join(\n ', ',\n )}`,\n nodeId: mutationNode.id,\n line: ctx.nodeLines?.[mutationNode.id],\n });\n }\n }\n\n return findings;\n}\n\nfunction r5DeadEnds(graph: Graph, ctx: RuleContext): Finding[] {\n const cfg = ctx.cfg.rules.dead_ends;\n if (!cfg?.enabled) return [];\n if (graph.nodes.length <= 1) return [];\n\n const outgoing = new Map<string, number>();\n for (const node of graph.nodes) outgoing.set(node.id, 0);\n for (const edge of graph.edges) outgoing.set(edge.from, (outgoing.get(edge.from) || 0) + 1);\n\n const findings: Finding[] = [];\n for (const node of graph.nodes) {\n if ((outgoing.get(node.id) || 0) === 0 && !isTerminalNode(node.type, node.name)) {\n findings.push({\n rule: 'R5',\n severity: 'nit',\n path: ctx.path,\n message: `Node ${node.name || node.id} has no outgoing connections (either wire it up or remove it)`,\n nodeId: node.id,\n line: ctx.nodeLines?.[node.id],\n raw_details: 'Either remove this node as dead code or connect it to the next/safe step so the workflow can continue.',\n });\n }\n }\n return findings;\n}\n\nfunction r6LongRunning(graph: Graph, ctx: RuleContext): Finding[] {\n const cfg = ctx.cfg.rules.long_running;\n if (!cfg?.enabled) return [];\n const findings: Finding[] = [];\n const loopNodes = graph.nodes.filter((node) => /loop|batch|while|repeat/i.test(node.type));\n\n for (const node of loopNodes) {\n const iterations = readNumber(node.params, [\n 'maxIterations',\n 'maxIteration',\n 'limit',\n 'options.maxIterations',\n ]);\n\n if (!iterations || (cfg.max_iterations && iterations > cfg.max_iterations)) {\n findings.push({\n rule: 'R6',\n severity: 'should',\n path: ctx.path,\n message: `Node ${node.name || node.id} allows ${\n iterations ?? 'unbounded'\n } iterations (limit ${cfg.max_iterations}; set a lower cap)`,\n nodeId: node.id,\n line: ctx.nodeLines?.[node.id],\n raw_details: `Set Options > Max iterations to ≤ ${cfg.max_iterations} or split the processing into smaller batches.`,\n });\n }\n\n if (cfg.timeout_ms) {\n const timeout = readNumber(node.params, ['timeout', 'timeoutMs', 'options.timeout']);\n if (timeout && timeout > cfg.timeout_ms) {\n findings.push({\n rule: 'R6',\n severity: 'should',\n path: ctx.path,\n message: `Node ${node.name || node.id} uses timeout ${timeout}ms (limit ${\n cfg.timeout_ms\n }ms; shorten the timeout or break work apart)`,\n nodeId: node.id,\n line: ctx.nodeLines?.[node.id],\n raw_details: `Lower the timeout to ≤ ${cfg.timeout_ms}ms or split the workflow so no single step blocks for too long.`,\n });\n }\n }\n }\n\n return findings;\n}\n\nfunction r7AlertLogEnforcement(graph: Graph, ctx: RuleContext): Finding[] {\n const cfg = ctx.cfg.rules.alert_log_enforcement;\n if (!cfg?.enabled) return [];\n\n const findings: Finding[] = [];\n const errorEdges = graph.edges.filter((edge) => edge.on === 'error');\n\n for (const edge of errorEdges) {\n const fromNode = graph.nodes.find((n) => n.id === edge.from)!;\n let isHandled = false;\n const queue: string[] = [edge.to];\n const visited = new Set<string>([edge.to]);\n\n let head = 0;\n while (head < queue.length) {\n const currentId = queue[head++]!;\n const currentNode = graph.nodes.find((n) => n.id === currentId)!;\n\n if (isNotificationNode(currentNode.type) || isErrorHandlerNode(currentNode.type, currentNode.name)) {\n isHandled = true;\n break; // Found a handler, stop searching this path\n }\n\n if (isRejoinNode(graph, currentId)) {\n continue; // It's a rejoin point, but not a handler, so stop traversing this path\n }\n\n // Add successors to queue\n const outgoing = graph.edges.filter((e) => e.from === currentId);\n for (const outEdge of outgoing) {\n if (!visited.has(outEdge.to)) {\n visited.add(outEdge.to);\n queue.push(outEdge.to);\n }\n }\n }\n\n if (!isHandled) {\n findings.push({\n rule: 'R7',\n severity: 'should',\n path: ctx.path,\n message: `Error path from node ${\n fromNode.name || fromNode.id\n } has no log/alert before rejoining (add notification node)`,\n nodeId: fromNode.id,\n line: ctx.nodeLines?.[fromNode.id],\n raw_details: 'Add a Slack/Email/Log node on the error branch before it rejoins the main flow so failures leave an audit trail.',\n });\n }\n }\n return findings;\n}\n\nfunction r8UnusedData(graph: Graph, ctx: RuleContext): Finding[] {\n const cfg = ctx.cfg.rules.unused_data;\n if (!cfg?.enabled) return [];\n\n const findings: Finding[] = [];\n for (const node of graph.nodes) {\n // If a node has no successors, R5 handles it. If it's a terminal node, its \"use\" is to end the flow.\n if (isTerminalNode(node.type, node.name) || !graph.edges.some((e) => e.from === node.id)) {\n continue;\n }\n\n const downstreamNodes = findAllDownstreamNodes(graph, node.id);\n downstreamNodes.delete(node.id);\n\n const leadsToConsumer = [...downstreamNodes].some((id) => {\n const downstreamNode = graph.nodes.find((n) => n.id === id)!;\n return isMeaningfulConsumer(downstreamNode);\n });\n\n if (!leadsToConsumer) {\n findings.push({\n rule: 'R8',\n severity: 'nit',\n path: ctx.path,\n message: `Node \"${node.name || node.id}\" produces data that never reaches any consumer`,\n nodeId: node.id,\n line: ctx.nodeLines?.[node.id],\n raw_details: 'Wire this branch into a consumer (DB/API/response) or remove it—otherwise the data produced here is never used.',\n });\n }\n }\n return findings;\n}\n\n// --- Rule Registration ---\n\nconst rules: RuleRunner[] = [\n r1Retry,\n r2ErrorHandling,\n r3Idempotency,\n r4Secrets,\n r5DeadEnds,\n r6LongRunning,\n r7AlertLogEnforcement,\n r8UnusedData,\n r9ConfigLiterals,\n r10NamingConvention,\n r11DeprecatedNodes,\n r12UnhandledErrorPath,\n r13WebhookAcknowledgment,\n r14RetryAfterCompliance,\n];\n\nexport function runAllRules(graph: Graph, ctx: RuleContext): Finding[] {\n return rules.flatMap((rule) => rule(graph, ctx));\n}\r\n\r\n","// Types for FlowLint configuration\n\nexport interface RateLimitRetryConfig {\n enabled: boolean;\n max_concurrency?: number;\n default_retry?: { count: number; strategy: string; base_ms: number };\n}\n\nexport interface ErrorHandlingConfig {\n enabled: boolean;\n forbid_continue_on_fail?: boolean;\n}\n\nexport interface IdempotencyConfig {\n enabled: boolean;\n key_field_candidates?: string[];\n}\n\nexport interface SecretsConfig {\n enabled: boolean;\n denylist_regex?: string[];\n}\n\nexport interface DeadEndsConfig {\n enabled: boolean;\n}\n\nexport interface LongRunningConfig {\n enabled: boolean;\n max_iterations?: number;\n timeout_ms?: number;\n}\n\nexport interface UnusedDataConfig {\n enabled: boolean;\n}\n\nexport interface UnhandledErrorPathConfig {\n enabled: boolean;\n}\n\nexport interface AlertLogEnforcementConfig {\n enabled: boolean;\n}\n\nexport interface DeprecatedNodesConfig {\n enabled: boolean;\n}\n\nexport interface NamingConventionConfig {\n enabled: boolean;\n generic_names?: string[];\n}\n\nexport interface ConfigLiteralsConfig {\n enabled: boolean;\n denylist_regex?: string[];\n}\n\nexport interface WebhookAcknowledgmentConfig {\n enabled: boolean;\n heavy_node_types?: string[];\n}\n\nexport interface RetryAfterComplianceConfig {\n enabled: boolean;\n suggest_exponential_backoff?: boolean;\n suggest_jitter?: boolean;\n}\n\nexport interface RulesConfig {\n rate_limit_retry: RateLimitRetryConfig;\n error_handling: ErrorHandlingConfig;\n idempotency: IdempotencyConfig;\n secrets: SecretsConfig;\n dead_ends: DeadEndsConfig;\n long_running: LongRunningConfig;\n unused_data: UnusedDataConfig;\n unhandled_error_path: UnhandledErrorPathConfig;\n alert_log_enforcement: AlertLogEnforcementConfig;\n deprecated_nodes: DeprecatedNodesConfig;\n naming_convention: NamingConventionConfig;\n config_literals: ConfigLiteralsConfig;\n webhook_acknowledgment: WebhookAcknowledgmentConfig;\n retry_after_compliance: RetryAfterComplianceConfig;\n}\n\nexport interface FilesConfig {\n include: string[];\n ignore: string[];\n}\n\nexport interface ReportConfig {\n annotations: boolean;\n summary_limit: number;\n}\n\nexport interface FlowLintConfig {\n files: FilesConfig;\n report: ReportConfig;\n rules: RulesConfig;\n}\n\n// Keep backward compatible type\nexport type RuleConfig = { enabled: boolean; [key: string]: unknown };\n\nexport const defaultConfig: FlowLintConfig = {\n files: {\n include: ['**/*.n8n.json', '**/workflows/*.json', '**/workflows/**/*.json', '**/*.n8n.yaml', '**/*.json'],\n ignore: [\n 'samples/**',\n '**/*.spec.json',\n 'node_modules/**',\n 'package*.json',\n 'tsconfig*.json',\n '.flowlint.yml',\n '.github/**',\n '.husky/**',\n '.vscode/**',\n 'infra/**',\n '*.config.js',\n '*.config.ts',\n '**/*.lock',\n ],\n },\n report: { annotations: true, summary_limit: 25 },\n rules: {\n rate_limit_retry: {\n enabled: true,\n max_concurrency: 5,\n default_retry: { count: 3, strategy: 'exponential', base_ms: 500 },\n },\n error_handling: { enabled: true, forbid_continue_on_fail: true },\n idempotency: { enabled: true, key_field_candidates: ['eventId', 'messageId'] },\n secrets: { enabled: true, denylist_regex: ['(?i)api[_-]?key', 'Bearer '] },\n dead_ends: { enabled: true },\n long_running: { enabled: true, max_iterations: 1000, timeout_ms: 300000 },\n unused_data: { enabled: true },\n unhandled_error_path: { enabled: true },\n alert_log_enforcement: { enabled: true },\n deprecated_nodes: { enabled: true },\n naming_convention: {\n enabled: true,\n generic_names: ['http request', 'set', 'if', 'merge', 'switch', 'no-op', 'start'],\n },\n config_literals: {\n enabled: true,\n denylist_regex: [\n '(?i)\\\\b(dev|development)\\\\b',\n '(?i)\\\\b(stag|staging)\\\\b',\n '(?i)\\\\b(prod|production)\\\\b',\n '(?i)\\\\b(test|testing)\\\\b',\n ],\n },\n webhook_acknowledgment: {\n enabled: true,\n heavy_node_types: [\n 'n8n-nodes-base.httpRequest',\n 'n8n-nodes-base.postgres',\n 'n8n-nodes-base.mysql',\n 'n8n-nodes-base.mongodb',\n 'n8n-nodes-base.openAi',\n 'n8n-nodes-base.anthropic',\n 'n8n-nodes-base.huggingFace',\n ],\n },\n retry_after_compliance: {\n enabled: true,\n suggest_exponential_backoff: true,\n suggest_jitter: true,\n },\n },\n};\r\n","/**\n * Isomorphic config loader for FlowLint\n * Works in both Node.js and browser environments\n */\n\nimport YAML from 'yaml';\nimport { defaultConfig, type FlowLintConfig } from './default-config';\n\n/**\n * Deep merge configuration objects\n */\nfunction deepMerge<T>(base: T, override: Record<string, unknown>): T {\n const baseCopy = JSON.parse(JSON.stringify(base));\n if (!override) return baseCopy;\n return mergeInto(baseCopy as Record<string, unknown>, override) as T;\n}\n\nfunction mergeInto(target: Record<string, unknown>, source: Record<string, unknown>): Record<string, unknown> {\n for (const [key, value] of Object.entries(source)) {\n if (value === undefined || value === null) continue;\n if (Array.isArray(value)) {\n target[key] = value;\n } else if (typeof value === 'object') {\n if (typeof target[key] !== 'object' || target[key] === null) {\n target[key] = {};\n }\n mergeInto(target[key] as Record<string, unknown>, value as Record<string, unknown>);\n } else {\n target[key] = value;\n }\n }\n return target;\n}\n\n/**\n * Parse config from YAML string\n */\nexport function parseConfig(content: string): FlowLintConfig {\n const parsed = (YAML.parse(content) as Record<string, unknown>) || {};\n return deepMerge(defaultConfig, parsed);\n}\n\n/**\n * Load config - isomorphic function\n * In browser: returns defaultConfig (no filesystem access)\n * In Node.js: optionally loads from file path\n */\nexport function loadConfig(configPath?: string): FlowLintConfig {\n // Browser detection - return default config\n if (typeof globalThis !== 'undefined' && 'window' in globalThis) {\n return defaultConfig;\n }\n\n // Node.js: if path provided, try to load\n if (configPath) {\n return loadConfigFromFile(configPath);\n }\n\n // Try to find config in current directory\n return loadConfigFromCwd();\n}\n\n/**\n * Load config from a specific file path (Node.js only)\n */\nfunction loadConfigFromFile(configPath: string): FlowLintConfig {\n try {\n // Dynamic require to avoid bundling fs\n const fs = require('fs');\n \n if (!fs.existsSync(configPath)) {\n return defaultConfig;\n }\n \n const content = fs.readFileSync(configPath, 'utf-8');\n return parseConfig(content);\n } catch {\n return defaultConfig;\n }\n}\n\n/**\n * Find and load config from current working directory (Node.js only)\n */\nfunction loadConfigFromCwd(): FlowLintConfig {\n try {\n const fs = require('fs');\n const path = require('path');\n \n const candidates = ['.flowlint.yml', '.flowlint.yaml', 'flowlint.config.yml'];\n const cwd = process.cwd();\n \n for (const candidate of candidates) {\n const configPath = path.join(cwd, candidate);\n if (fs.existsSync(configPath)) {\n const content = fs.readFileSync(configPath, 'utf-8');\n return parseConfig(content);\n }\n }\n \n return defaultConfig;\n } catch {\n return defaultConfig;\n }\n}\n\n/**\n * Validate config structure\n */\nexport function validateConfig(config: unknown): config is FlowLintConfig {\n if (!config || typeof config !== 'object') return false;\n const c = config as Record<string, unknown>;\n return (\n 'files' in c &&\n 'report' in c &&\n 'rules' in c &&\n typeof c.files === 'object' &&\n typeof c.report === 'object' &&\n typeof c.rules === 'object'\n );\n}\r\n\r\n\r\n","/**\n * Findings utilities\n * Shared logic for processing and analyzing findings across both review engine and CLI\n */\n\nimport type { Finding } from '../types';\n\nexport interface FindingsSummary {\n must: number;\n should: number;\n nit: number;\n total: number;\n}\n\n/**\n * Count findings by severity level\n */\nexport function countFindingsBySeverity(findings: Finding[]): FindingsSummary {\n return {\n must: findings.filter((f) => f.severity === 'must').length,\n should: findings.filter((f) => f.severity === 'should').length,\n nit: findings.filter((f) => f.severity === 'nit').length,\n total: findings.length,\n };\n}\n\n/**\n * Get severity order for sorting\n */\nexport function getSeverityOrder(): Record<string, number> {\n return { must: 0, should: 1, nit: 2 };\n}\n\n/**\n * Sort findings by severity\n */\nexport function sortFindingsBySeverity(findings: Finding[]): Finding[] {\n const order = getSeverityOrder();\n return [...findings].sort((a, b) => order[a.severity] - order[b.severity]);\n}\n"],"mappings":"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","names":["Ajv","addFormats","YAML","import_yaml","YAML"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/parser/parser-n8n.ts","../src/schemas/index.ts","../src/schemas/n8n-workflow.schema.json","../src/utils/utils.ts","../src/rules/rule-utils.ts","../src/rules/index.ts","../src/config/default-config.ts","../src/config/loader.ts","../src/utils/findings.ts","../src/reporter/reporter.ts"],"sourcesContent":["/**\n * @flowlint/core - Core linting engine for n8n workflows\n * \n * This package provides the core functionality for analyzing n8n workflows:\n * - Parsing n8n workflow JSON/YAML files\n * - Running linting rules\n * - Generating findings/reports\n * - Configuration management\n */\n\n// Parser\nexport { parseN8n } from './parser/parser-n8n';\n\n// Rules\nexport { runAllRules } from './rules';\n\n// Schemas\nexport { validateN8nWorkflow, ValidationError } from './schemas';\n\n// Config\nexport { \n defaultConfig, \n loadConfig, \n parseConfig, \n validateConfig,\n type FlowLintConfig,\n type RuleConfig,\n type FilesConfig,\n type ReportConfig,\n} from './config';\n\n// Types\nexport type {\n Finding,\n FindingSeverity,\n Graph,\n NodeRef,\n Edge,\n RuleContext,\n RuleRunner,\n PRFile,\n} from './types';\n\n// Utils\nexport { \n flattenConnections, \n isErrorProneNode, \n getExampleLink,\n isApiNode,\n isMutationNode,\n isNotificationNode,\n isTerminalNode,\n} from './utils/utils';\nexport { countFindingsBySeverity, sortFindingsBySeverity } from './utils/findings';\r\nexport { buildCheckOutput, buildAnnotations } from './reporter/reporter';\r\n","import YAML from 'yaml';\nimport type { Graph, NodeRef, Edge } from '../types';\nimport { validateN8nWorkflow } from '../schemas';\nimport { flattenConnections, isErrorProneNode } from '../utils/utils';\n\nexport function parseN8n(doc: string): Graph {\n let parsed: any;\n try {\n parsed = JSON.parse(doc);\n } catch {\n parsed = YAML.parse(doc);\n }\n\n // Validate workflow structure before parsing\n validateN8nWorkflow(parsed);\n\n const nodes: NodeRef[] = parsed.nodes.map((node: any, idx: number) => {\n const nodeId = node.id || node.name || `node-${idx}`;\n const flags: NodeRef['flags'] = {\n continueOnFail: node.continueOnFail,\n retryOnFail: node.retryOnFail ?? node.settings?.retryOnFail,\n waitBetweenTries: node.waitBetweenTries ?? node.settings?.waitBetweenTries,\n maxTries: node.maxTries ?? node.settings?.maxTries,\n };\n const hasFlags =\n flags.continueOnFail !== undefined ||\n flags.retryOnFail !== undefined ||\n flags.waitBetweenTries !== undefined;\n\n return {\n id: nodeId,\n type: node.type,\n name: node.name,\n params: node.parameters,\n cred: node.credentials,\n flags: hasFlags ? flags : undefined,\n };\n });\n\n const nameToId = new Map<string, string>();\n for (const node of nodes) {\n if (node.id) nameToId.set(node.id, node.id);\n if (node.name) nameToId.set(node.name, node.id);\n }\n\n const lines = doc.split(/\\r?\\n/);\n const idLine = new Map<string, number>();\n const nameLine = new Map<string, number>();\n lines.forEach((line, idx) => {\n const idMatch = line.match(/\"id\":\\s*\"([^\"]+)\"/);\n if (idMatch) idLine.set(idMatch[1], idx + 1);\n const nameMatch = line.match(/\"name\":\\s*\"([^\"]+)\"/);\n if (nameMatch) nameLine.set(nameMatch[1], idx + 1);\n });\n\n const nodeLines = new Map<string, number>();\n for (const node of nodes) {\n const lineNumber = (node.name && nameLine.get(node.name)) || idLine.get(node.id);\n if (lineNumber) {\n nodeLines.set(node.id, lineNumber);\n }\n }\n\n const nodeById = new Map<string, NodeRef>();\n for (const node of nodes) {\n nodeById.set(node.id, node);\n }\n\n const resolveEdgeType = (\n connectionType: string,\n outputIndex: number | undefined,\n sourceType?: string,\n ): Edge['on'] => {\n if (connectionType === 'error') return 'error';\n if (connectionType === 'timeout') return 'timeout';\n\n if (connectionType === 'main') {\n if (\n typeof outputIndex === 'number' &&\n outputIndex > 0 &&\n sourceType &&\n isErrorProneNode(sourceType)\n ) {\n return 'error';\n }\n return 'success';\n }\n\n return 'success';\n };\n\n const edges: Edge[] = [];\n Object.entries(parsed.connections || {}).forEach(([from, exits]) => {\n if (!exits) {\n return;\n }\n const exitChannels = exits as Record<string, any>;\n Object.entries(exitChannels).forEach(([exitType, conn]) => {\n const sourceId = nameToId.get(from) ?? from;\n const sourceNode = nodeById.get(sourceId);\n\n const enqueueEdges = (value: any, outputIndex?: number) => {\n flattenConnections(value).forEach((link) => {\n if (!link || typeof link !== 'object') return;\n const targetId = nameToId.get(link.node) ?? link.node;\n if (!targetId) return;\n\n const edgeType = resolveEdgeType(exitType, outputIndex, sourceNode?.type);\n edges.push({ from: sourceId, to: targetId, on: edgeType });\n });\n };\n\n if (Array.isArray(conn)) {\n conn.forEach((entry, index) => enqueueEdges(entry, index));\n } else {\n enqueueEdges(conn);\n }\n });\n });\n\n return {\n nodes,\n edges,\n meta: {\n credentials: !!parsed.credentials,\n nodeLines: Object.fromEntries(nodeLines),\n },\n };\n}\n\r\n","import Ajv, { type ValidateFunction } from 'ajv';\nimport addFormats from 'ajv-formats';\nimport workflowSchema from './n8n-workflow.schema.json';\nimport { flattenConnections, buildValidationErrors } from '../utils/utils';\n\n// Custom error class for validation failures\nexport class ValidationError extends Error {\n constructor(\n public errors: Array<{\n path: string;\n message: string;\n suggestion?: string;\n }>\n ) {\n super(`Workflow validation failed: ${errors.length} error(s)`);\n this.name = 'ValidationError';\n }\n}\n\n// Dummy validator that always passes\nconst createDummyValidator = (): ValidateFunction => {\n const v: any = () => true;\n v.errors = [];\n return v as ValidateFunction;\n};\n\n// Singleton instance\nlet validatorInstance: ValidateFunction | null = null;\n\nfunction getValidator(): ValidateFunction {\n if (validatorInstance) return validatorInstance;\n\n // Detect Node.js environment safely\n // Use optional chaining to satisfy SonarQube\n const isNode = typeof process !== 'undefined' && process?.versions?.node != null;\n\n if (isNode) {\n try {\n const ajv = new Ajv({\n allErrors: true,\n strict: false,\n verbose: true,\n code: { source: true, es5: true }\n });\n addFormats(ajv);\n validatorInstance = ajv.compile(workflowSchema);\n } catch (error) {\n // Fallback to dummy validator if compilation fails (e.g. due to strict CSP in some environments)\n console.warn('Failed to compile JSON schema validator, falling back to dummy validator:', error);\n validatorInstance = createDummyValidator();\n }\n } else {\n validatorInstance = createDummyValidator();\n }\n\n return validatorInstance;\n}\n\n/**\n * Throws a ValidationError if the provided set contains items.\n * Centralizes the pattern of checking validation results and throwing errors.\n * \n * @param items - Set of items that represent validation failures\n * @param config - Configuration for building error messages\n * @throws ValidationError if items set is not empty\n */\nfunction throwIfInvalid<T>(\n items: Set<T>,\n config: {\n path: string;\n messageTemplate: (item: T) => string;\n suggestionTemplate: (item: T) => string;\n }\n): void {\n if (items.size > 0) {\n const errors = buildValidationErrors(items, config);\n throw new ValidationError(errors);\n }\n}\n\n/**\n * Check for duplicate node IDs in the workflow\n */\nfunction checkDuplicateNodeIds(data: any): void {\n if (!Array.isArray(data.nodes)) return;\n\n const seen = new Set<string>();\n const duplicates = new Set<string>();\n\n for (const node of data.nodes) {\n if (node.id && seen.has(node.id)) {\n duplicates.add(node.id);\n }\n if (node.id) {\n seen.add(node.id);\n }\n }\n\n throwIfInvalid(duplicates, {\n path: 'nodes[].id',\n messageTemplate: (id) => `Duplicate node ID: \"${id}\"`,\n suggestionTemplate: (id) => `Each node must have a unique ID. Remove or rename the duplicate node with ID \"${id}\".`,\n });\n}\n\n/**\n * Check for orphaned connections (references to non-existent nodes)\n */\nfunction checkOrphanedConnections(data: any): void {\n if (!data.connections || !Array.isArray(data.nodes)) return;\n\n const nodeIds = new Set<string>();\n const nodeNames = new Set<string>();\n\n // Collect all node IDs and names\n for (const node of data.nodes) {\n if (node.id) nodeIds.add(node.id);\n if (node.name) nodeNames.add(node.name);\n }\n\n const orphanedRefs = new Set<string>();\n\n // Check all connection targets\n Object.entries(data.connections).forEach(([sourceId, channels]) => {\n // Check if source exists\n if (!nodeIds.has(sourceId) && !nodeNames.has(sourceId)) {\n orphanedRefs.add(sourceId);\n }\n\n // Check targets\n if (typeof channels === 'object' && channels !== null) {\n Object.values(channels).forEach((connArray: any) => {\n const flatConnections = flattenConnections(connArray);\n flatConnections.forEach((conn: any) => {\n if (conn?.node) {\n if (!nodeIds.has(conn.node) && !nodeNames.has(conn.node)) {\n orphanedRefs.add(conn.node);\n }\n }\n });\n });\n }\n });\n\n throwIfInvalid(orphanedRefs, {\n path: 'connections',\n messageTemplate: (ref) => `Orphaned connection reference: \"${ref}\"`,\n suggestionTemplate: (ref) => `Connection references node \"${ref}\" which does not exist. Add the missing node or remove the invalid connection.`,\n });\n}\n\n/**\n * Validate n8n workflow structure\n * Throws ValidationError with detailed messages if validation fails\n */\nexport function validateN8nWorkflow(data: any): void {\n const validate = getValidator();\n\n // Basic schema validation\n if (!validate(data)) {\n const errors = (validate.errors || []).map((err: any) => {\n const path = err.instancePath || err.schemaPath;\n const message = err.message || 'Validation error';\n let suggestion = '';\n\n // Provide helpful suggestions based on error type\n if (err.keyword === 'required') {\n const missing = err.params?.missingProperty;\n suggestion = `Add the required field \"${missing}\" to the workflow.`;\n } else if (err.keyword === 'type') {\n const expected = err.params?.type;\n suggestion = `The field should be of type \"${expected}\".`;\n } else if (err.keyword === 'minLength') {\n suggestion = 'This field cannot be empty.';\n }\n\n return { path, message, suggestion };\n });\n\n throw new ValidationError(errors);\n }\n\n // Additional custom validations\n checkDuplicateNodeIds(data);\n checkOrphanedConnections(data);\n}\n\r\n","{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": \"https://flowlint.dev/schemas/n8n-workflow.json\",\n \"title\": \"n8n Workflow Schema\",\n \"description\": \"JSON Schema for n8n workflow files (v1.x)\",\n \"type\": \"object\",\n \"required\": [\"nodes\", \"connections\"],\n \"properties\": {\n \"name\": {\n \"type\": \"string\",\n \"description\": \"Workflow name\"\n },\n \"nodes\": {\n \"type\": \"array\",\n \"description\": \"Array of workflow nodes\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\"type\", \"name\"],\n \"properties\": {\n \"id\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"Unique node identifier\"\n },\n \"type\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"Node type (e.g., n8n-nodes-base.httpRequest)\"\n },\n \"name\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"Human-readable node name\"\n },\n \"parameters\": {\n \"type\": \"object\",\n \"description\": \"Node-specific configuration parameters\"\n },\n \"credentials\": {\n \"type\": \"object\",\n \"description\": \"Credential references for this node\"\n },\n \"position\": {\n \"type\": \"array\",\n \"description\": \"X,Y coordinates for UI placement\",\n \"items\": {\n \"type\": \"number\"\n },\n \"minItems\": 2,\n \"maxItems\": 2\n },\n \"continueOnFail\": {\n \"type\": \"boolean\",\n \"description\": \"Whether to continue execution on node failure\"\n },\n \"disabled\": {\n \"type\": \"boolean\",\n \"description\": \"Whether the node is disabled\"\n },\n \"notesInFlow\": {\n \"type\": \"boolean\"\n },\n \"notes\": {\n \"type\": \"string\"\n },\n \"typeVersion\": {\n \"type\": \"number\",\n \"description\": \"Version of the node type\"\n }\n },\n \"additionalProperties\": true\n }\n },\n \"connections\": {\n \"type\": \"object\",\n \"description\": \"Map of node connections (source node ID -> connection details)\",\n \"patternProperties\": {\n \"^.*$\": {\n \"type\": \"object\",\n \"description\": \"Connection channels for a source node\",\n \"patternProperties\": {\n \"^(main|error|timeout|.*?)$\": {\n \"description\": \"Connection array for this channel\",\n \"oneOf\": [\n {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\"node\"],\n \"properties\": {\n \"node\": {\n \"type\": \"string\",\n \"description\": \"Target node ID or name\"\n },\n \"type\": {\n \"type\": \"string\",\n \"description\": \"Connection type\"\n },\n \"index\": {\n \"type\": \"number\",\n \"description\": \"Output index\"\n }\n }\n }\n },\n {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\"node\"],\n \"properties\": {\n \"node\": {\n \"type\": \"string\"\n },\n \"type\": {\n \"type\": \"string\"\n },\n \"index\": {\n \"type\": \"number\"\n }\n }\n }\n }\n }\n ]\n }\n }\n }\n }\n },\n \"active\": {\n \"type\": \"boolean\",\n \"description\": \"Whether the workflow is active\"\n },\n \"settings\": {\n \"type\": \"object\",\n \"description\": \"Workflow settings\"\n },\n \"tags\": {\n \"type\": \"array\",\n \"description\": \"Workflow tags\",\n \"items\": {\n \"oneOf\": [\n { \"type\": \"string\" },\n {\n \"type\": \"object\",\n \"required\": [\"name\"],\n \"properties\": {\n \"id\": { \"type\": \"string\" },\n \"name\": { \"type\": \"string\" }\n },\n \"additionalProperties\": true\n }\n ]\n }\n },\n \"pinData\": {\n \"type\": \"object\",\n \"description\": \"Pinned execution data for testing\"\n },\n \"versionId\": {\n \"type\": \"string\",\n \"description\": \"Workflow version identifier\"\n },\n \"id\": {\n \"type\": [\"string\", \"number\"],\n \"description\": \"Workflow ID\"\n },\n \"meta\": {\n \"type\": \"object\",\n \"description\": \"Metadata\"\n }\n },\n \"additionalProperties\": true\n}\n","import type { Graph, NodeRef } from '../types';\n\n/**\n * Shared utility functions for workflow parsing and validation\n */\n\n\n/**\n * Helper to flatten nested connection arrays from n8n workflow connections.\n * Connections can be nested in various ways (arrays of arrays, objects with node properties).\n * This recursively flattens them to a simple array of connection objects.\n *\n * @param value - The connection value to flatten (can be array, object, or primitive)\n * @returns Array of connection objects with 'node' property\n */\nexport function flattenConnections(value: any): any[] {\n if (!value) return [];\n if (Array.isArray(value)) {\n return value.flatMap((entry) => flattenConnections(entry));\n }\n if (typeof value === 'object' && 'node' in value) {\n return [value];\n }\n return [];\n}\n\n/**\n * Build validation error objects from a collection of items using provided templates.\n * This utility eliminates code duplication in validation error construction.\n *\n * @template T - Type of items to process\n * @param items - Set or array of items to convert to validation errors\n * @param errorConfig - Configuration object containing:\n * - path: The JSON path where the error occurred\n * - messageTemplate: Function to generate error message for each item\n * - suggestionTemplate: Function to generate actionable suggestion for each item\n * @returns Array of validation error objects with path, message, and suggestion fields\n *\n * @example\n * ```typescript\n * const duplicates = new Set(['node1', 'node2']);\n * const errors = buildValidationErrors(duplicates, {\n * path: 'nodes[].id',\n * messageTemplate: (id) => `Duplicate node ID: \"${id}\"`, \n * suggestionTemplate: (id) => `Remove or rename the duplicate node with ID \"${id}\".`\n * });\n * ```\n */\nexport function buildValidationErrors<T>(\n items: Set<T> | T[],\n errorConfig: {\n path: string;\n messageTemplate: (item: T) => string;\n suggestionTemplate: (item: T) => string;\n }\n): Array<{ path: string; message: string; suggestion: string }> {\n const itemArray = Array.isArray(items) ? items : Array.from(items);\n return itemArray.map((item) => ({\n path: errorConfig.path,\n message: errorConfig.messageTemplate(item),\n suggestion: errorConfig.suggestionTemplate(item),\n }));\n}\n\nexport function collectStrings(value: unknown, out: string[] = []): string[] {\n if (typeof value === 'string') out.push(value);\n else if (Array.isArray(value)) value.forEach((entry) => collectStrings(entry, out));\n else if (value && typeof value === 'object')\n Object.values(value).forEach((entry) => collectStrings(entry, out));\n return out;\n}\n\nexport function toRegex(pattern: string): RegExp {\n let source = pattern;\n let flags = '';\n if (source.startsWith('(?i)')) {\n source = source.slice(4);\n flags += 'i';\n }\n return new RegExp(source, flags);\n}\n\nexport function isApiNode(type: string) {\n return /http|request|google|facebook|ads/i.test(type);\n}\n\nexport function isMutationNode(type: string) {\n return /write|insert|update|delete|post|put|patch|database|mongo|supabase|sheet/i.test(type);\n}\n\nexport function isErrorProneNode(type: string) {\n return isApiNode(type) || isMutationNode(type) || /execute|workflow|function/i.test(type);\n}\n\nexport function isNotificationNode(type: string) {\n return /slack|discord|email|gotify|mattermost|microsoftTeams|pushbullet|pushover|rocketchat|zulip|telegram/i.test(\n type,\n );\n}\n\nexport function isErrorHandlerNode(type: string, name?: string) {\n const normalizedType = type.toLowerCase();\n if (normalizedType.includes('stopanderror')) return true;\n if (normalizedType.includes('errorhandler')) return true;\n if (normalizedType.includes('raiseerror')) return true;\n\n const normalizedName = name?.toLowerCase() ?? '';\n if (normalizedName.includes('stop and error')) return true;\n if (normalizedName.includes('error handler')) return true;\n\n return false;\n}\n\nexport function isRejoinNode(graph: Graph, nodeId: string): boolean {\n const incoming = graph.edges.filter((e) => e.to === nodeId);\n if (incoming.length <= 1) return false;\n const hasErrorEdge = incoming.some((e) => e.on === 'error');\n const hasSuccessEdge = incoming.some((e) => e.on !== 'error');\n return hasErrorEdge && hasSuccessEdge;\n}\n\nexport function isMeaningfulConsumer(node: NodeRef): boolean {\n // A meaningful consumer is a node that has an external side-effect.\n return (\n isMutationNode(node.type) || // Writes to a DB, sheet, etc.\n isNotificationNode(node.type) || // Sends a message to Slack, email, etc.\n isApiNode(node.type) || // Calls an external API\n /respondToWebhook/i.test(node.type) // Specifically nodes that send a response back.\n );\n}\n\nexport function containsCandidate(value: unknown, candidates: string[]): boolean {\n if (!value || !candidates.length) return false;\n\n const queue: unknown[] = [value];\n const candidateRegex = new RegExp(`(${candidates.join('|')})`, 'i');\n\n while (queue.length > 0) {\n const current = queue.shift();\n\n if (typeof current === 'string') {\n if (candidateRegex.test(current)) return true;\n } else if (Array.isArray(current)) {\n queue.push(...current);\n } else if (current && typeof current === 'object') {\n for (const [key, val] of Object.entries(current)) {\n if (candidateRegex.test(key)) return true;\n queue.push(val);\n }\n }\n }\n\n return false;\n}\n\nconst TERMINAL_NODE_PATTERNS = [\n 'respond', 'reply', 'end', 'stop', 'terminate', 'return', 'sticky', 'note', 'noop', 'no operation',\n 'slack', 'email', 'discord', 'teams', 'webhook', 'telegram', 'pushbullet', 'mattermost', 'notifier', 'notification', 'alert', 'sms', 'call',\n];\n\nexport function isTerminalNode(type: string, name?: string) {\n const label = `${type} ${name ?? ''}`.toLowerCase();\n return TERMINAL_NODE_PATTERNS.some((pattern) => label.includes(pattern));\n}\n\nexport function readNumber(source: any, paths: string[]): number | undefined {\n for (const path of paths) {\n const value = path.split('.').reduce<any>((acc, key) => (acc ? acc[key] : undefined), source);\n if (typeof value === 'number') return value;\n if (typeof value === 'string' && !Number.isNaN(Number(value))) return Number(value);\n }\n return undefined;\n}\n\nexport function findAllDownstreamNodes(graph: Graph, startNodeId: string): Set<string> {\n const visited = new Set<string>();\n const queue: string[] = [startNodeId];\n visited.add(startNodeId);\n\n let head = 0;\n while (head < queue.length) {\n const currentId = queue[head++]!;\n const outgoing = graph.edges.filter((e) => e.from === currentId);\n for (const edge of outgoing) {\n if (!visited.has(edge.to)) {\n visited.add(edge.to);\n queue.push(edge.to);\n }\n }\n }\n return visited;\n}\n\nexport function findAllUpstreamNodes(graph: Graph, startNodeId: string): Set<string> {\n const visited = new Set<string>();\n const queue: string[] = [startNodeId];\n visited.add(startNodeId);\n\n let head = 0;\n while (head < queue.length) {\n const currentId = queue[head++]!;\n const incoming = graph.edges.filter((e) => e.to === currentId);\n for (const edge of incoming) {\n if (!visited.has(edge.from)) {\n visited.add(edge.from);\n queue.push(edge.from);\n }\n }\n }\n return visited;\n}\n\nexport const EXAMPLES_BASE_URL = \"https://github.com/Replikanti/flowlint-examples/tree/main\";\n\nexport function getExampleLink(ruleId: string): string {\n return `${EXAMPLES_BASE_URL}/${ruleId}`;\n}\n\r\n","import type { Graph, Finding, NodeRef, FindingSeverity } from '../types';\nimport type { FlowLintConfig } from '../config';\nimport { collectStrings, toRegex } from '../utils/utils';\n\ntype Rule = string;\ntype RuleContext = { path: string; cfg: FlowLintConfig; nodeLines?: Record<string, number> };\ntype RuleRunner = (graph: Graph, ctx: RuleContext) => Finding[];\ntype NodeRuleLogic = (node: NodeRef, graph: Graph, ctx: RuleContext) => Finding | Finding[] | null;\n\n/**\n * A higher-order function to create a rule that iterates over each node in the graph.\n * It abstracts the boilerplate of checking if the rule is enabled and iterating through nodes.\n *\n * @param ruleId - The ID of the rule (e.g., 'R1').\n * @param configKey - The key in the FlowLintConfig rules object.\n * @param logic - The function containing the core logic to be executed for each node.\n * @returns A RuleRunner function.\n */\nexport function createNodeRule(\n ruleId: Rule,\n configKey: keyof FlowLintConfig['rules'],\n logic: NodeRuleLogic,\n): RuleRunner {\n return (graph: Graph, ctx: RuleContext): Finding[] => {\n const ruleConfig = ctx.cfg.rules[configKey] as { enabled?: boolean };\n if (!ruleConfig?.enabled) {\n return [];\n }\n\n const findings: Finding[] = [];\n for (const node of graph.nodes) {\n const result = logic(node, graph, ctx);\n if (result) {\n if (Array.isArray(result)) {\n findings.push(...result);\n } else {\n findings.push(result);\n }\n }\n }\n return findings;\n };\n}\n\ntype HardcodedStringRuleOptions = {\n ruleId: Rule;\n severity: FindingSeverity;\n configKey: 'secrets' | 'config_literals';\n messageFn: (node: NodeRef, value: string) => string;\n details: string;\n};\n\n/**\n * Creates a rule that checks for hardcoded strings in node parameters based on a denylist of regex patterns.\n * This is used to create R4 (Secrets) and R9 (Config Literals).\n *\n * @param options - The configuration for the hardcoded string rule.\n * @returns A RuleRunner function.\n */\nexport function createHardcodedStringRule({\n ruleId,\n severity,\n configKey,\n messageFn,\n details,\n}: HardcodedStringRuleOptions): RuleRunner {\n const logic: NodeRuleLogic = (node, graph, ctx) => {\n const cfg = ctx.cfg.rules[configKey];\n if (!cfg.denylist_regex?.length) {\n return null;\n }\n const regexes = cfg.denylist_regex.map((pattern) => toRegex(pattern));\n\n const findings: Finding[] = [];\n const strings = collectStrings(node.params);\n\n for (const value of strings) {\n // Ignore expressions and empty strings\n if (!value || value.includes('{{')) {\n continue;\n }\n\n if (regexes.some((regex) => regex.test(value))) {\n findings.push({\n rule: ruleId,\n severity,\n path: ctx.path,\n message: messageFn(node, value),\n nodeId: node.id,\n line: ctx.nodeLines?.[node.id],\n raw_details: details,\n });\n // Only report one finding per node to avoid noise\n break;\n }\n }\n return findings;\n };\n\n return createNodeRule(ruleId, configKey, logic);\n}\n\r\n","import type { Graph, Finding, NodeRef } from '../types';\nimport type { FlowLintConfig } from '../config';\nimport { createNodeRule, createHardcodedStringRule } from './rule-utils';\nimport {\n isApiNode,\n isMutationNode,\n isErrorProneNode,\n isNotificationNode,\n isErrorHandlerNode,\n isRejoinNode,\n isMeaningfulConsumer,\n isTerminalNode,\n readNumber,\n findAllDownstreamNodes,\n findAllUpstreamNodes,\n containsCandidate,\n} from '../utils/utils';\n\ntype RuleContext = { path: string; cfg: FlowLintConfig; nodeLines?: Record<string, number> };\n\ntype RuleRunner = (graph: Graph, ctx: RuleContext) => Finding[];\n\n// --- Rule Definitions using Helpers ---\n\nconst r1Retry = createNodeRule('R1', 'rate_limit_retry', (node, graph, ctx) => {\n if (!isApiNode(node.type)) return null;\n\n const params = (node.params ?? {}) as Record<string, unknown>;\n const options = ((params as any).options ?? {}) as Record<string, unknown>;\n\n const retryCandidates: unknown[] = [\n options.retryOnFail,\n (params as any).retryOnFail,\n node.flags?.retryOnFail,\n ];\n\n const retryOnFail = retryCandidates.find((value) => value !== undefined && value !== null);\n\n if (retryOnFail === true) {\n return null;\n }\n\n if (typeof retryOnFail === 'string') {\n const normalized = retryOnFail.trim().toLowerCase();\n if (retryOnFail.includes('{{') || normalized === 'true') {\n return null;\n }\n }\n\n return {\n rule: 'R1',\n severity: 'must',\n path: ctx.path,\n message: `Node ${node.name || node.id} is missing retry/backoff configuration`,\n raw_details: `In the node properties, enable \"Retry on Fail\" under Options.`,\n nodeId: node.id,\n line: ctx.nodeLines?.[node.id],\n };\n});\n\nconst r2ErrorHandling = createNodeRule('R2', 'error_handling', (node, graph, ctx) => {\n if (ctx.cfg.rules.error_handling.forbid_continue_on_fail && node.flags?.continueOnFail) {\n return {\n rule: 'R2',\n severity: 'must',\n path: ctx.path,\n message: `Node ${node.name || node.id} has continueOnFail enabled (disable it and route errors explicitly)`,\n nodeId: node.id,\n line: ctx.nodeLines?.[node.id],\n raw_details:\n 'Open the node in n8n and disable \"Continue On Fail\" (Options > Continue On Fail). Route failures down an explicit error branch instead.',\n };\n }\n return null;\n});\n\nconst r4Secrets = createHardcodedStringRule({\n ruleId: 'R4',\n severity: 'must',\n configKey: 'secrets',\n messageFn: (node) => `Node ${node.name || node.id} contains a hardcoded secret (move it to credentials/env vars)`,\n details: 'Move API keys/tokens into Credentials or environment variables; the workflow should only reference {{$credentials.*}} expressions.',\n});\n\nconst r9ConfigLiterals = createHardcodedStringRule({\n ruleId: 'R9',\n severity: 'should',\n configKey: 'config_literals',\n messageFn: (node, value) => `Node ${node.name || node.id} contains env-specific literal \"${value.substring(0, 40)}\" (move to expression/credential)`,\n details: 'Move environment-specific URLs/IDs into expressions or credentials (e.g., {{$env.API_BASE_URL}}) so the workflow is portable.',\n});\n\nconst r10NamingConvention = createNodeRule('R10', 'naming_convention', (node, graph, ctx) => {\n const genericNames = new Set(ctx.cfg.rules.naming_convention.generic_names ?? []);\n if (!node.name || genericNames.has(node.name.toLowerCase())) {\n return {\n rule: 'R10',\n severity: 'nit',\n path: ctx.path,\n message: `Node ${node.id} uses a generic name \"${node.name ?? ''}\" (rename it to describe the action)`,\n nodeId: node.id,\n line: ctx.nodeLines?.[node.id],\n raw_details: 'Rename the node to describe its purpose (e.g., \"Check subscription status\" instead of \"IF\") for easier reviews and debugging.',\n };\n }\n return null;\n});\n\nconst DEPRECATED_NODES: Record<string, string> = {\n 'n8n-nodes-base.splitInBatches': 'Use Loop over items instead',\n 'n8n-nodes-base.executeWorkflow': 'Use Execute Workflow (Sub-Workflow) instead',\n};\n\nconst r11DeprecatedNodes = createNodeRule('R11', 'deprecated_nodes', (node, graph, ctx) => {\n if (DEPRECATED_NODES[node.type]) {\n return {\n rule: 'R11',\n severity: 'should',\n path: ctx.path,\n message: `Node ${node.name || node.id} uses deprecated type ${node.type} (replace with ${DEPRECATED_NODES[node.type]})`,\n nodeId: node.id,\n line: ctx.nodeLines?.[node.id],\n raw_details: `Replace this node with ${DEPRECATED_NODES[node.type]} so future n8n upgrades don’t break the workflow.`,\n };\n }\n return null;\n});\n\nconst r12UnhandledErrorPath = createNodeRule('R12', 'unhandled_error_path', (node, graph, ctx) => {\n if (!isErrorProneNode(node.type)) return null;\n\n const hasErrorPath = graph.edges.some((edge) => {\n if (edge.from !== node.id) return false;\n if (edge.on === 'error') return true;\n\n const targetNode = graph.nodes.find((candidate) => candidate.id === edge.to);\n return targetNode ? isErrorHandlerNode(targetNode.type, targetNode.name) : false;\n });\n\n if (!hasErrorPath) {\n return {\n rule: 'R12',\n severity: 'must',\n path: ctx.path,\n message: `Node ${node.name || node.id} has no error branch (add a red connector to handler)`,\n nodeId: node.id,\n line: ctx.nodeLines?.[node.id],\n raw_details:\n 'Add an error (red) branch to a Stop and Error or logging/alert node so failures do not disappear silently.',\n };\n }\n return null;\n});\n\nfunction r13WebhookAcknowledgment(graph: Graph, ctx: RuleContext): Finding[] {\n const cfg = ctx.cfg.rules.webhook_acknowledgment;\n if (!cfg?.enabled) return [];\n\n const findings: Finding[] = [];\n\n // Find all webhook trigger nodes (not respondToWebhook)\n const webhookNodes = graph.nodes.filter((node) =>\n node.type === 'n8n-nodes-base.webhook' ||\n (node.type.includes('webhook') && !node.type.includes('respondToWebhook'))\n );\n\n for (const webhookNode of webhookNodes) {\n // Get immediate downstream nodes\n const directDownstream = graph.edges\n .filter((edge) => edge.from === webhookNode.id)\n .map((edge) => graph.nodes.find((n) => n.id === edge.to))\n .filter((n): n is NodeRef => !!n);\n\n if (directDownstream.length === 0) continue;\n\n // Check if first downstream is \"Respond to Webhook\"\n const hasImmediateResponse = directDownstream.some((node) =>\n node.type === 'n8n-nodes-base.respondToWebhook' ||\n /respond.*webhook/i.test(node.type) ||\n /respond.*webhook/i.test(node.name || '')\n );\n\n if (hasImmediateResponse) continue; // Good pattern - immediate acknowledgment\n\n // Check if any downstream node is \"heavy\"\n const heavyNodeTypes = cfg.heavy_node_types || [\n 'n8n-nodes-base.httpRequest',\n 'n8n-nodes-base.postgres',\n 'n8n-nodes-base.mysql',\n 'n8n-nodes-base.mongodb',\n 'n8n-nodes-base.openAi',\n 'n8n-nodes-base.anthropic',\n ];\n\n const hasHeavyProcessing = directDownstream.some((node) =>\n heavyNodeTypes.includes(node.type) || /loop|batch/i.test(node.type)\n );\n\n if (hasHeavyProcessing) {\n findings.push({\n rule: 'R13',\n severity: 'must',\n path: ctx.path,\n message: `Webhook \"${webhookNode.name || webhookNode.id}\" performs heavy processing before acknowledgment (risk of timeout/duplicates)`,\n nodeId: webhookNode.id,\n line: ctx.nodeLines?.[webhookNode.id],\n raw_details: `Add a \"Respond to Webhook\" node immediately after the webhook trigger (return 200/204), then perform heavy processing. This prevents webhook timeouts and duplicate events.`,\n });\n }\n }\n\n return findings;\n}\n\nconst r14RetryAfterCompliance = createNodeRule('R14', 'retry_after_compliance', (node, graph, ctx) => {\n // Only check HTTP request nodes\n if (!isApiNode(node.type)) return null;\n\n const params = (node.params ?? {}) as Record<string, unknown>;\n const options = ((params as any).options ?? {}) as Record<string, unknown>;\n\n // Check if retry is enabled\n const retryCandidates: unknown[] = [\n options.retryOnFail,\n (params as any).retryOnFail,\n node.flags?.retryOnFail,\n ];\n\n const retryOnFail = retryCandidates.find((value) => value !== undefined && value !== null);\n\n // If retry is disabled or explicitly false, skip this rule\n if (!retryOnFail || retryOnFail === false) return null;\n\n // If retryOnFail is explicitly a string expression, skip if it's not \"true\"\n if (typeof retryOnFail === 'string') {\n const normalized = retryOnFail.trim().toLowerCase();\n if (retryOnFail.includes('{{') && normalized !== 'true') {\n return null; // Dynamic expression, assume it might handle retry-after\n }\n }\n\n // Check waitBetweenTries specifically (Pragmatic fix for n8n UI limitations)\n const waitBetweenTries = node.flags?.waitBetweenTries;\n if (waitBetweenTries !== undefined && waitBetweenTries !== null) {\n // If it's a static number (or numeric string), we accept it because n8n UI\n // often prevents using expressions here. We prioritize allowing retries (R1)\n // over strict Retry-After compliance if the platform limits the user.\n if (typeof waitBetweenTries === 'number') return null;\n if (\n typeof waitBetweenTries === 'string' &&\n !isNaN(Number(waitBetweenTries)) &&\n !waitBetweenTries.includes('{{')\n ) {\n return null;\n }\n }\n\n // Check if there's an expression/code that references retry-after\n const nodeStr = JSON.stringify(node);\n const hasRetryAfterLogic = /retry[-_]?after|retryafter/i.test(nodeStr);\n\n if (hasRetryAfterLogic) {\n return null; // Good - respects Retry-After\n }\n\n // Flag as violation\n return {\n rule: 'R14',\n severity: 'should',\n path: ctx.path,\n message: `Node ${node.name || node.id} has retry logic but ignores Retry-After headers (429/503 responses)`,\n raw_details: `Add expression to parse Retry-After header: const retryAfter = $json.headers['retry-after']; const delay = retryAfter ? (parseInt(retryAfter) || new Date(retryAfter) - Date.now()) : Math.min(1000 * Math.pow(2, $execution.retryCount), 60000); This prevents API bans and respects server rate limits.`,\n nodeId: node.id,\n line: ctx.nodeLines?.[node.id],\n };\n});\n\n\n// --- Rules with custom logic (not fitting the simple node-by-node pattern) ---\n\nfunction r3Idempotency(graph: Graph, ctx: RuleContext): Finding[] {\n const cfg = ctx.cfg.rules.idempotency;\n if (!cfg?.enabled) return [];\n\n const hasIngress = graph.nodes.some((node) => /webhook|trigger|start/i.test(node.type));\n if (!hasIngress) return [];\n\n const mutationNodes = graph.nodes.filter((node) => isMutationNode(node.type));\n if (!mutationNodes.length) return [];\n\n const findings: Finding[] = [];\n\n for (const mutationNode of mutationNodes) {\n const upstreamNodeIds = findAllUpstreamNodes(graph, mutationNode.id);\n const upstreamNodes = graph.nodes.filter((n) => upstreamNodeIds.has(n.id));\n\n const hasGuard = upstreamNodes.some((p) =>\n containsCandidate(p.params, cfg.key_field_candidates ?? []),\n );\n\n if (!hasGuard) {\n findings.push({\n rule: 'R3',\n severity: 'must',\n path: ctx.path,\n message: `The mutation path ending at \"${\n mutationNode.name || mutationNode.id\n }\" appears to be missing an idempotency guard.`,\n raw_details: `Ensure one of the upstream nodes or the mutation node itself uses an idempotency key, such as one of: ${(cfg.key_field_candidates ?? []).join(\n ', ',\n )}`,\n nodeId: mutationNode.id,\n line: ctx.nodeLines?.[mutationNode.id],\n });\n }\n }\n\n return findings;\n}\n\nfunction r5DeadEnds(graph: Graph, ctx: RuleContext): Finding[] {\n const cfg = ctx.cfg.rules.dead_ends;\n if (!cfg?.enabled) return [];\n if (graph.nodes.length <= 1) return [];\n\n const outgoing = new Map<string, number>();\n for (const node of graph.nodes) outgoing.set(node.id, 0);\n for (const edge of graph.edges) outgoing.set(edge.from, (outgoing.get(edge.from) || 0) + 1);\n\n const findings: Finding[] = [];\n for (const node of graph.nodes) {\n if ((outgoing.get(node.id) || 0) === 0 && !isTerminalNode(node.type, node.name)) {\n findings.push({\n rule: 'R5',\n severity: 'nit',\n path: ctx.path,\n message: `Node ${node.name || node.id} has no outgoing connections (either wire it up or remove it)`,\n nodeId: node.id,\n line: ctx.nodeLines?.[node.id],\n raw_details: 'Either remove this node as dead code or connect it to the next/safe step so the workflow can continue.',\n });\n }\n }\n return findings;\n}\n\nfunction r6LongRunning(graph: Graph, ctx: RuleContext): Finding[] {\n const cfg = ctx.cfg.rules.long_running;\n if (!cfg?.enabled) return [];\n const findings: Finding[] = [];\n const loopNodes = graph.nodes.filter((node) => /loop|batch|while|repeat/i.test(node.type));\n\n for (const node of loopNodes) {\n const iterations = readNumber(node.params, [\n 'maxIterations',\n 'maxIteration',\n 'limit',\n 'options.maxIterations',\n ]);\n\n if (!iterations || (cfg.max_iterations && iterations > cfg.max_iterations)) {\n findings.push({\n rule: 'R6',\n severity: 'should',\n path: ctx.path,\n message: `Node ${node.name || node.id} allows ${\n iterations ?? 'unbounded'\n } iterations (limit ${cfg.max_iterations}; set a lower cap)`,\n nodeId: node.id,\n line: ctx.nodeLines?.[node.id],\n raw_details: `Set Options > Max iterations to ≤ ${cfg.max_iterations} or split the processing into smaller batches.`,\n });\n }\n\n if (cfg.timeout_ms) {\n const timeout = readNumber(node.params, ['timeout', 'timeoutMs', 'options.timeout']);\n if (timeout && timeout > cfg.timeout_ms) {\n findings.push({\n rule: 'R6',\n severity: 'should',\n path: ctx.path,\n message: `Node ${node.name || node.id} uses timeout ${timeout}ms (limit ${\n cfg.timeout_ms\n }ms; shorten the timeout or break work apart)`,\n nodeId: node.id,\n line: ctx.nodeLines?.[node.id],\n raw_details: `Lower the timeout to ≤ ${cfg.timeout_ms}ms or split the workflow so no single step blocks for too long.`,\n });\n }\n }\n }\n\n return findings;\n}\n\nfunction r7AlertLogEnforcement(graph: Graph, ctx: RuleContext): Finding[] {\n const cfg = ctx.cfg.rules.alert_log_enforcement;\n if (!cfg?.enabled) return [];\n\n const findings: Finding[] = [];\n const errorEdges = graph.edges.filter((edge) => edge.on === 'error');\n\n for (const edge of errorEdges) {\n const fromNode = graph.nodes.find((n) => n.id === edge.from)!;\n let isHandled = false;\n const queue: string[] = [edge.to];\n const visited = new Set<string>([edge.to]);\n\n let head = 0;\n while (head < queue.length) {\n const currentId = queue[head++]!;\n const currentNode = graph.nodes.find((n) => n.id === currentId)!;\n\n if (isNotificationNode(currentNode.type) || isErrorHandlerNode(currentNode.type, currentNode.name)) {\n isHandled = true;\n break; // Found a handler, stop searching this path\n }\n\n if (isRejoinNode(graph, currentId)) {\n continue; // It's a rejoin point, but not a handler, so stop traversing this path\n }\n\n // Add successors to queue\n const outgoing = graph.edges.filter((e) => e.from === currentId);\n for (const outEdge of outgoing) {\n if (!visited.has(outEdge.to)) {\n visited.add(outEdge.to);\n queue.push(outEdge.to);\n }\n }\n }\n\n if (!isHandled) {\n findings.push({\n rule: 'R7',\n severity: 'should',\n path: ctx.path,\n message: `Error path from node ${\n fromNode.name || fromNode.id\n } has no log/alert before rejoining (add notification node)`,\n nodeId: fromNode.id,\n line: ctx.nodeLines?.[fromNode.id],\n raw_details: 'Add a Slack/Email/Log node on the error branch before it rejoins the main flow so failures leave an audit trail.',\n });\n }\n }\n return findings;\n}\n\nfunction r8UnusedData(graph: Graph, ctx: RuleContext): Finding[] {\n const cfg = ctx.cfg.rules.unused_data;\n if (!cfg?.enabled) return [];\n\n const findings: Finding[] = [];\n for (const node of graph.nodes) {\n // If a node has no successors, R5 handles it. If it's a terminal node, its \"use\" is to end the flow.\n if (isTerminalNode(node.type, node.name) || !graph.edges.some((e) => e.from === node.id)) {\n continue;\n }\n\n const downstreamNodes = findAllDownstreamNodes(graph, node.id);\n downstreamNodes.delete(node.id);\n\n const leadsToConsumer = [...downstreamNodes].some((id) => {\n const downstreamNode = graph.nodes.find((n) => n.id === id)!;\n return isMeaningfulConsumer(downstreamNode);\n });\n\n if (!leadsToConsumer) {\n findings.push({\n rule: 'R8',\n severity: 'nit',\n path: ctx.path,\n message: `Node \"${node.name || node.id}\" produces data that never reaches any consumer`,\n nodeId: node.id,\n line: ctx.nodeLines?.[node.id],\n raw_details: 'Wire this branch into a consumer (DB/API/response) or remove it—otherwise the data produced here is never used.',\n });\n }\n }\n return findings;\n}\n\n// --- Rule Registration ---\n\nconst rules: RuleRunner[] = [\n r1Retry,\n r2ErrorHandling,\n r3Idempotency,\n r4Secrets,\n r5DeadEnds,\n r6LongRunning,\n r7AlertLogEnforcement,\n r8UnusedData,\n r9ConfigLiterals,\n r10NamingConvention,\n r11DeprecatedNodes,\n r12UnhandledErrorPath,\n r13WebhookAcknowledgment,\n r14RetryAfterCompliance,\n];\n\nexport function runAllRules(graph: Graph, ctx: RuleContext): Finding[] {\n return rules.flatMap((rule) => rule(graph, ctx));\n}\r\n\r\n","// Types for FlowLint configuration\n\nexport interface RateLimitRetryConfig {\n enabled: boolean;\n max_concurrency?: number;\n default_retry?: { count: number; strategy: string; base_ms: number };\n}\n\nexport interface ErrorHandlingConfig {\n enabled: boolean;\n forbid_continue_on_fail?: boolean;\n}\n\nexport interface IdempotencyConfig {\n enabled: boolean;\n key_field_candidates?: string[];\n}\n\nexport interface SecretsConfig {\n enabled: boolean;\n denylist_regex?: string[];\n}\n\nexport interface DeadEndsConfig {\n enabled: boolean;\n}\n\nexport interface LongRunningConfig {\n enabled: boolean;\n max_iterations?: number;\n timeout_ms?: number;\n}\n\nexport interface UnusedDataConfig {\n enabled: boolean;\n}\n\nexport interface UnhandledErrorPathConfig {\n enabled: boolean;\n}\n\nexport interface AlertLogEnforcementConfig {\n enabled: boolean;\n}\n\nexport interface DeprecatedNodesConfig {\n enabled: boolean;\n}\n\nexport interface NamingConventionConfig {\n enabled: boolean;\n generic_names?: string[];\n}\n\nexport interface ConfigLiteralsConfig {\n enabled: boolean;\n denylist_regex?: string[];\n}\n\nexport interface WebhookAcknowledgmentConfig {\n enabled: boolean;\n heavy_node_types?: string[];\n}\n\nexport interface RetryAfterComplianceConfig {\n enabled: boolean;\n suggest_exponential_backoff?: boolean;\n suggest_jitter?: boolean;\n}\n\nexport interface RulesConfig {\n rate_limit_retry: RateLimitRetryConfig;\n error_handling: ErrorHandlingConfig;\n idempotency: IdempotencyConfig;\n secrets: SecretsConfig;\n dead_ends: DeadEndsConfig;\n long_running: LongRunningConfig;\n unused_data: UnusedDataConfig;\n unhandled_error_path: UnhandledErrorPathConfig;\n alert_log_enforcement: AlertLogEnforcementConfig;\n deprecated_nodes: DeprecatedNodesConfig;\n naming_convention: NamingConventionConfig;\n config_literals: ConfigLiteralsConfig;\n webhook_acknowledgment: WebhookAcknowledgmentConfig;\n retry_after_compliance: RetryAfterComplianceConfig;\n}\n\nexport interface FilesConfig {\n include: string[];\n ignore: string[];\n}\n\nexport interface ReportConfig {\n annotations: boolean;\n summary_limit: number;\n}\n\nexport interface FlowLintConfig {\n files: FilesConfig;\n report: ReportConfig;\n rules: RulesConfig;\n}\n\n// Keep backward compatible type\nexport type RuleConfig = { enabled: boolean; [key: string]: unknown };\n\nexport const defaultConfig: FlowLintConfig = {\n files: {\n include: ['**/*.n8n.json', '**/workflows/*.json', '**/workflows/**/*.json', '**/*.n8n.yaml', '**/*.json'],\n ignore: [\n 'samples/**',\n '**/*.spec.json',\n 'node_modules/**',\n 'package*.json',\n 'tsconfig*.json',\n '.flowlint.yml',\n '.github/**',\n '.husky/**',\n '.vscode/**',\n 'infra/**',\n '*.config.js',\n '*.config.ts',\n '**/*.lock',\n ],\n },\n report: { annotations: true, summary_limit: 25 },\n rules: {\n rate_limit_retry: {\n enabled: true,\n max_concurrency: 5,\n default_retry: { count: 3, strategy: 'exponential', base_ms: 500 },\n },\n error_handling: { enabled: true, forbid_continue_on_fail: true },\n idempotency: { enabled: true, key_field_candidates: ['eventId', 'messageId'] },\n secrets: { enabled: true, denylist_regex: ['(?i)api[_-]?key', 'Bearer '] },\n dead_ends: { enabled: true },\n long_running: { enabled: true, max_iterations: 1000, timeout_ms: 300000 },\n unused_data: { enabled: true },\n unhandled_error_path: { enabled: true },\n alert_log_enforcement: { enabled: true },\n deprecated_nodes: { enabled: true },\n naming_convention: {\n enabled: true,\n generic_names: ['http request', 'set', 'if', 'merge', 'switch', 'no-op', 'start'],\n },\n config_literals: {\n enabled: true,\n denylist_regex: [\n '(?i)\\\\b(dev|development)\\\\b',\n '(?i)\\\\b(stag|staging)\\\\b',\n '(?i)\\\\b(prod|production)\\\\b',\n '(?i)\\\\b(test|testing)\\\\b',\n ],\n },\n webhook_acknowledgment: {\n enabled: true,\n heavy_node_types: [\n 'n8n-nodes-base.httpRequest',\n 'n8n-nodes-base.postgres',\n 'n8n-nodes-base.mysql',\n 'n8n-nodes-base.mongodb',\n 'n8n-nodes-base.openAi',\n 'n8n-nodes-base.anthropic',\n 'n8n-nodes-base.huggingFace',\n ],\n },\n retry_after_compliance: {\n enabled: true,\n suggest_exponential_backoff: true,\n suggest_jitter: true,\n },\n },\n};\r\n","/**\n * Isomorphic config loader for FlowLint\n * Works in both Node.js and browser environments\n */\n\nimport YAML from 'yaml';\nimport { defaultConfig, type FlowLintConfig } from './default-config';\n\n/**\n * Deep merge configuration objects\n */\nfunction deepMerge<T>(base: T, override: Record<string, unknown>): T {\n const baseCopy = JSON.parse(JSON.stringify(base));\n if (!override) return baseCopy;\n return mergeInto(baseCopy as Record<string, unknown>, override) as T;\n}\n\nfunction mergeInto(target: Record<string, unknown>, source: Record<string, unknown>): Record<string, unknown> {\n for (const [key, value] of Object.entries(source)) {\n if (value === undefined || value === null) continue;\n if (Array.isArray(value)) {\n target[key] = value;\n } else if (typeof value === 'object') {\n if (typeof target[key] !== 'object' || target[key] === null) {\n target[key] = {};\n }\n mergeInto(target[key] as Record<string, unknown>, value as Record<string, unknown>);\n } else {\n target[key] = value;\n }\n }\n return target;\n}\n\n/**\n * Parse config from YAML string\n */\nexport function parseConfig(content: string): FlowLintConfig {\n const parsed = (YAML.parse(content) as Record<string, unknown>) || {};\n return deepMerge(defaultConfig, parsed);\n}\n\n/**\n * Load config - isomorphic function\n * In browser: returns defaultConfig (no filesystem access)\n * In Node.js: optionally loads from file path\n */\nexport function loadConfig(configPath?: string): FlowLintConfig {\n // Browser detection - return default config\n if (typeof globalThis !== 'undefined' && 'window' in globalThis) {\n return defaultConfig;\n }\n\n // Node.js: if path provided, try to load\n if (configPath) {\n return loadConfigFromFile(configPath);\n }\n\n // Try to find config in current directory\n return loadConfigFromCwd();\n}\n\n/**\n * Load config from a specific file path (Node.js only)\n */\nfunction loadConfigFromFile(configPath: string): FlowLintConfig {\n try {\n // Dynamic require to avoid bundling fs\n const fs = require('fs');\n \n if (!fs.existsSync(configPath)) {\n return defaultConfig;\n }\n \n const content = fs.readFileSync(configPath, 'utf-8');\n return parseConfig(content);\n } catch {\n return defaultConfig;\n }\n}\n\n/**\n * Find and load config from current working directory (Node.js only)\n */\nfunction loadConfigFromCwd(): FlowLintConfig {\n try {\n const fs = require('fs');\n const path = require('path');\n \n const candidates = ['.flowlint.yml', '.flowlint.yaml', 'flowlint.config.yml'];\n const cwd = process.cwd();\n \n for (const candidate of candidates) {\n const configPath = path.join(cwd, candidate);\n if (fs.existsSync(configPath)) {\n const content = fs.readFileSync(configPath, 'utf-8');\n return parseConfig(content);\n }\n }\n \n return defaultConfig;\n } catch {\n return defaultConfig;\n }\n}\n\n/**\n * Validate config structure\n */\nexport function validateConfig(config: unknown): config is FlowLintConfig {\n if (!config || typeof config !== 'object') return false;\n const c = config as Record<string, unknown>;\n return (\n 'files' in c &&\n 'report' in c &&\n 'rules' in c &&\n typeof c.files === 'object' &&\n typeof c.report === 'object' &&\n typeof c.rules === 'object'\n );\n}\r\n\r\n\r\n","/**\n * Findings utilities\n * Shared logic for processing and analyzing findings across both review engine and CLI\n */\n\nimport type { Finding } from '../types';\n\nexport interface FindingsSummary {\n must: number;\n should: number;\n nit: number;\n total: number;\n}\n\n/**\n * Count findings by severity level\n */\nexport function countFindingsBySeverity(findings: Finding[]): FindingsSummary {\n return {\n must: findings.filter((f) => f.severity === 'must').length,\n should: findings.filter((f) => f.severity === 'should').length,\n nit: findings.filter((f) => f.severity === 'nit').length,\n total: findings.length,\n };\n}\n\n/**\n * Get severity order for sorting\n */\nexport function getSeverityOrder(): Record<string, number> {\n return { must: 0, should: 1, nit: 2 };\n}\n\n/**\n * Sort findings by severity\n */\nexport function sortFindingsBySeverity(findings: Finding[]): Finding[] {\n const order = getSeverityOrder();\n return [...findings].sort((a, b) => order[a.severity] - order[b.severity]);\n}\n","import type { Finding } from '../types';\nimport type { FlowLintConfig } from '../config';\n\ntype Conclusion = 'action_required' | 'neutral' | 'success' | 'failure';\n\nexport function buildCheckOutput({\n findings,\n cfg,\n summaryOverride,\n conclusionOverride,\n}: {\n findings: Finding[];\n cfg: FlowLintConfig;\n summaryOverride?: string;\n conclusionOverride?: Conclusion;\n}) {\n const summary = summaryOverride ?? summarize(findings);\n const conclusion = conclusionOverride ?? inferConclusion(findings);\n\n return {\n conclusion,\n output: {\n title: process.env.CHECK_TITLE || 'FlowLint findings',\n summary,\n },\n };\n}\n\nexport function buildAnnotations(findings: Finding[]): any[] {\n const severityOrder: Finding['severity'][] = ['must', 'should', 'nit'];\n const ordered = [...findings].sort((a, b) => severityOrder.indexOf(a.severity) - severityOrder.indexOf(b.severity));\n\n return ordered.map((finding) => {\n const line = finding.line ?? 1;\n\n // Build raw_details with optional documentation URL\n let rawDetails = finding.raw_details;\n if (finding.documentationUrl) {\n const docLine = `See examples: ${finding.documentationUrl}`;\n rawDetails = rawDetails ? `${docLine}\\n\\n${rawDetails}` : docLine;\n }\n\n return {\n path: finding.path,\n start_line: line,\n end_line: line,\n annotation_level: mapSeverity(finding.severity),\n message: `${finding.rule}: ${finding.message}`,\n raw_details: rawDetails?.slice(0, 64000),\n };\n });\n}\n\nfunction inferConclusion(findings: Finding[]): Conclusion {\n if (findings.some((f) => f.severity === 'must')) return 'failure';\n if (findings.some((f) => f.severity === 'should')) return 'neutral';\n return 'success';\n}\n\nfunction summarize(findings: Finding[]) {\n if (findings.length === 0) return 'No issues found.';\n const must = findings.filter((f) => f.severity === 'must').length;\n const should = findings.filter((f) => f.severity === 'should').length;\n const nit = findings.filter((f) => f.severity === 'nit').length;\n return `${must} must-fix, ${should} should-fix, ${nit} nit.`;\n}\n\nfunction mapSeverity(severity: Finding['severity']) {\n if (severity === 'must') return 'failure';\n if (severity === 'should') return 'warning';\n return 'notice';\n}\n\r\n"],"mappings":"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","names":["Ajv","addFormats","YAML","import_yaml","YAML"]}
|
package/dist/index.mjs
CHANGED
|
@@ -1161,8 +1161,67 @@ function sortFindingsBySeverity(findings) {
|
|
|
1161
1161
|
const order = getSeverityOrder();
|
|
1162
1162
|
return [...findings].sort((a, b) => order[a.severity] - order[b.severity]);
|
|
1163
1163
|
}
|
|
1164
|
+
|
|
1165
|
+
// src/reporter/reporter.ts
|
|
1166
|
+
function buildCheckOutput({
|
|
1167
|
+
findings,
|
|
1168
|
+
cfg,
|
|
1169
|
+
summaryOverride,
|
|
1170
|
+
conclusionOverride
|
|
1171
|
+
}) {
|
|
1172
|
+
const summary = summaryOverride ?? summarize(findings);
|
|
1173
|
+
const conclusion = conclusionOverride ?? inferConclusion(findings);
|
|
1174
|
+
return {
|
|
1175
|
+
conclusion,
|
|
1176
|
+
output: {
|
|
1177
|
+
title: process.env.CHECK_TITLE || "FlowLint findings",
|
|
1178
|
+
summary
|
|
1179
|
+
}
|
|
1180
|
+
};
|
|
1181
|
+
}
|
|
1182
|
+
function buildAnnotations(findings) {
|
|
1183
|
+
const severityOrder = ["must", "should", "nit"];
|
|
1184
|
+
const ordered = [...findings].sort((a, b) => severityOrder.indexOf(a.severity) - severityOrder.indexOf(b.severity));
|
|
1185
|
+
return ordered.map((finding) => {
|
|
1186
|
+
const line = finding.line ?? 1;
|
|
1187
|
+
let rawDetails = finding.raw_details;
|
|
1188
|
+
if (finding.documentationUrl) {
|
|
1189
|
+
const docLine = `See examples: ${finding.documentationUrl}`;
|
|
1190
|
+
rawDetails = rawDetails ? `${docLine}
|
|
1191
|
+
|
|
1192
|
+
${rawDetails}` : docLine;
|
|
1193
|
+
}
|
|
1194
|
+
return {
|
|
1195
|
+
path: finding.path,
|
|
1196
|
+
start_line: line,
|
|
1197
|
+
end_line: line,
|
|
1198
|
+
annotation_level: mapSeverity(finding.severity),
|
|
1199
|
+
message: `${finding.rule}: ${finding.message}`,
|
|
1200
|
+
raw_details: rawDetails?.slice(0, 64e3)
|
|
1201
|
+
};
|
|
1202
|
+
});
|
|
1203
|
+
}
|
|
1204
|
+
function inferConclusion(findings) {
|
|
1205
|
+
if (findings.some((f) => f.severity === "must")) return "failure";
|
|
1206
|
+
if (findings.some((f) => f.severity === "should")) return "neutral";
|
|
1207
|
+
return "success";
|
|
1208
|
+
}
|
|
1209
|
+
function summarize(findings) {
|
|
1210
|
+
if (findings.length === 0) return "No issues found.";
|
|
1211
|
+
const must = findings.filter((f) => f.severity === "must").length;
|
|
1212
|
+
const should = findings.filter((f) => f.severity === "should").length;
|
|
1213
|
+
const nit = findings.filter((f) => f.severity === "nit").length;
|
|
1214
|
+
return `${must} must-fix, ${should} should-fix, ${nit} nit.`;
|
|
1215
|
+
}
|
|
1216
|
+
function mapSeverity(severity) {
|
|
1217
|
+
if (severity === "must") return "failure";
|
|
1218
|
+
if (severity === "should") return "warning";
|
|
1219
|
+
return "notice";
|
|
1220
|
+
}
|
|
1164
1221
|
export {
|
|
1165
1222
|
ValidationError,
|
|
1223
|
+
buildAnnotations,
|
|
1224
|
+
buildCheckOutput,
|
|
1166
1225
|
countFindingsBySeverity,
|
|
1167
1226
|
defaultConfig,
|
|
1168
1227
|
flattenConnections,
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/parser/parser-n8n.ts","../src/schemas/index.ts","../src/schemas/n8n-workflow.schema.json","../src/utils/utils.ts","../src/rules/rule-utils.ts","../src/rules/index.ts","../src/config/default-config.ts","../src/config/loader.ts","../src/utils/findings.ts"],"sourcesContent":["import YAML from 'yaml';\nimport type { Graph, NodeRef, Edge } from '../types';\nimport { validateN8nWorkflow } from '../schemas';\nimport { flattenConnections, isErrorProneNode } from '../utils/utils';\n\nexport function parseN8n(doc: string): Graph {\n let parsed: any;\n try {\n parsed = JSON.parse(doc);\n } catch {\n parsed = YAML.parse(doc);\n }\n\n // Validate workflow structure before parsing\n validateN8nWorkflow(parsed);\n\n const nodes: NodeRef[] = parsed.nodes.map((node: any, idx: number) => {\n const nodeId = node.id || node.name || `node-${idx}`;\n const flags: NodeRef['flags'] = {\n continueOnFail: node.continueOnFail,\n retryOnFail: node.retryOnFail ?? node.settings?.retryOnFail,\n waitBetweenTries: node.waitBetweenTries ?? node.settings?.waitBetweenTries,\n maxTries: node.maxTries ?? node.settings?.maxTries,\n };\n const hasFlags =\n flags.continueOnFail !== undefined ||\n flags.retryOnFail !== undefined ||\n flags.waitBetweenTries !== undefined;\n\n return {\n id: nodeId,\n type: node.type,\n name: node.name,\n params: node.parameters,\n cred: node.credentials,\n flags: hasFlags ? flags : undefined,\n };\n });\n\n const nameToId = new Map<string, string>();\n for (const node of nodes) {\n if (node.id) nameToId.set(node.id, node.id);\n if (node.name) nameToId.set(node.name, node.id);\n }\n\n const lines = doc.split(/\\r?\\n/);\n const idLine = new Map<string, number>();\n const nameLine = new Map<string, number>();\n lines.forEach((line, idx) => {\n const idMatch = line.match(/\"id\":\\s*\"([^\"]+)\"/);\n if (idMatch) idLine.set(idMatch[1], idx + 1);\n const nameMatch = line.match(/\"name\":\\s*\"([^\"]+)\"/);\n if (nameMatch) nameLine.set(nameMatch[1], idx + 1);\n });\n\n const nodeLines = new Map<string, number>();\n for (const node of nodes) {\n const lineNumber = (node.name && nameLine.get(node.name)) || idLine.get(node.id);\n if (lineNumber) {\n nodeLines.set(node.id, lineNumber);\n }\n }\n\n const nodeById = new Map<string, NodeRef>();\n for (const node of nodes) {\n nodeById.set(node.id, node);\n }\n\n const resolveEdgeType = (\n connectionType: string,\n outputIndex: number | undefined,\n sourceType?: string,\n ): Edge['on'] => {\n if (connectionType === 'error') return 'error';\n if (connectionType === 'timeout') return 'timeout';\n\n if (connectionType === 'main') {\n if (\n typeof outputIndex === 'number' &&\n outputIndex > 0 &&\n sourceType &&\n isErrorProneNode(sourceType)\n ) {\n return 'error';\n }\n return 'success';\n }\n\n return 'success';\n };\n\n const edges: Edge[] = [];\n Object.entries(parsed.connections || {}).forEach(([from, exits]) => {\n if (!exits) {\n return;\n }\n const exitChannels = exits as Record<string, any>;\n Object.entries(exitChannels).forEach(([exitType, conn]) => {\n const sourceId = nameToId.get(from) ?? from;\n const sourceNode = nodeById.get(sourceId);\n\n const enqueueEdges = (value: any, outputIndex?: number) => {\n flattenConnections(value).forEach((link) => {\n if (!link || typeof link !== 'object') return;\n const targetId = nameToId.get(link.node) ?? link.node;\n if (!targetId) return;\n\n const edgeType = resolveEdgeType(exitType, outputIndex, sourceNode?.type);\n edges.push({ from: sourceId, to: targetId, on: edgeType });\n });\n };\n\n if (Array.isArray(conn)) {\n conn.forEach((entry, index) => enqueueEdges(entry, index));\n } else {\n enqueueEdges(conn);\n }\n });\n });\n\n return {\n nodes,\n edges,\n meta: {\n credentials: !!parsed.credentials,\n nodeLines: Object.fromEntries(nodeLines),\n },\n };\n}\n\r\n","import Ajv, { type ValidateFunction } from 'ajv';\nimport addFormats from 'ajv-formats';\nimport workflowSchema from './n8n-workflow.schema.json';\nimport { flattenConnections, buildValidationErrors } from '../utils/utils';\n\n// Custom error class for validation failures\nexport class ValidationError extends Error {\n constructor(\n public errors: Array<{\n path: string;\n message: string;\n suggestion?: string;\n }>\n ) {\n super(`Workflow validation failed: ${errors.length} error(s)`);\n this.name = 'ValidationError';\n }\n}\n\n// Dummy validator that always passes\nconst createDummyValidator = (): ValidateFunction => {\n const v: any = () => true;\n v.errors = [];\n return v as ValidateFunction;\n};\n\n// Singleton instance\nlet validatorInstance: ValidateFunction | null = null;\n\nfunction getValidator(): ValidateFunction {\n if (validatorInstance) return validatorInstance;\n\n // Detect Node.js environment safely\n // Use optional chaining to satisfy SonarQube\n const isNode = typeof process !== 'undefined' && process?.versions?.node != null;\n\n if (isNode) {\n try {\n const ajv = new Ajv({\n allErrors: true,\n strict: false,\n verbose: true,\n code: { source: true, es5: true }\n });\n addFormats(ajv);\n validatorInstance = ajv.compile(workflowSchema);\n } catch (error) {\n // Fallback to dummy validator if compilation fails (e.g. due to strict CSP in some environments)\n console.warn('Failed to compile JSON schema validator, falling back to dummy validator:', error);\n validatorInstance = createDummyValidator();\n }\n } else {\n validatorInstance = createDummyValidator();\n }\n\n return validatorInstance;\n}\n\n/**\n * Throws a ValidationError if the provided set contains items.\n * Centralizes the pattern of checking validation results and throwing errors.\n * \n * @param items - Set of items that represent validation failures\n * @param config - Configuration for building error messages\n * @throws ValidationError if items set is not empty\n */\nfunction throwIfInvalid<T>(\n items: Set<T>,\n config: {\n path: string;\n messageTemplate: (item: T) => string;\n suggestionTemplate: (item: T) => string;\n }\n): void {\n if (items.size > 0) {\n const errors = buildValidationErrors(items, config);\n throw new ValidationError(errors);\n }\n}\n\n/**\n * Check for duplicate node IDs in the workflow\n */\nfunction checkDuplicateNodeIds(data: any): void {\n if (!Array.isArray(data.nodes)) return;\n\n const seen = new Set<string>();\n const duplicates = new Set<string>();\n\n for (const node of data.nodes) {\n if (node.id && seen.has(node.id)) {\n duplicates.add(node.id);\n }\n if (node.id) {\n seen.add(node.id);\n }\n }\n\n throwIfInvalid(duplicates, {\n path: 'nodes[].id',\n messageTemplate: (id) => `Duplicate node ID: \"${id}\"`,\n suggestionTemplate: (id) => `Each node must have a unique ID. Remove or rename the duplicate node with ID \"${id}\".`,\n });\n}\n\n/**\n * Check for orphaned connections (references to non-existent nodes)\n */\nfunction checkOrphanedConnections(data: any): void {\n if (!data.connections || !Array.isArray(data.nodes)) return;\n\n const nodeIds = new Set<string>();\n const nodeNames = new Set<string>();\n\n // Collect all node IDs and names\n for (const node of data.nodes) {\n if (node.id) nodeIds.add(node.id);\n if (node.name) nodeNames.add(node.name);\n }\n\n const orphanedRefs = new Set<string>();\n\n // Check all connection targets\n Object.entries(data.connections).forEach(([sourceId, channels]) => {\n // Check if source exists\n if (!nodeIds.has(sourceId) && !nodeNames.has(sourceId)) {\n orphanedRefs.add(sourceId);\n }\n\n // Check targets\n if (typeof channels === 'object' && channels !== null) {\n Object.values(channels).forEach((connArray: any) => {\n const flatConnections = flattenConnections(connArray);\n flatConnections.forEach((conn: any) => {\n if (conn?.node) {\n if (!nodeIds.has(conn.node) && !nodeNames.has(conn.node)) {\n orphanedRefs.add(conn.node);\n }\n }\n });\n });\n }\n });\n\n throwIfInvalid(orphanedRefs, {\n path: 'connections',\n messageTemplate: (ref) => `Orphaned connection reference: \"${ref}\"`,\n suggestionTemplate: (ref) => `Connection references node \"${ref}\" which does not exist. Add the missing node or remove the invalid connection.`,\n });\n}\n\n/**\n * Validate n8n workflow structure\n * Throws ValidationError with detailed messages if validation fails\n */\nexport function validateN8nWorkflow(data: any): void {\n const validate = getValidator();\n\n // Basic schema validation\n if (!validate(data)) {\n const errors = (validate.errors || []).map((err: any) => {\n const path = err.instancePath || err.schemaPath;\n const message = err.message || 'Validation error';\n let suggestion = '';\n\n // Provide helpful suggestions based on error type\n if (err.keyword === 'required') {\n const missing = err.params?.missingProperty;\n suggestion = `Add the required field \"${missing}\" to the workflow.`;\n } else if (err.keyword === 'type') {\n const expected = err.params?.type;\n suggestion = `The field should be of type \"${expected}\".`;\n } else if (err.keyword === 'minLength') {\n suggestion = 'This field cannot be empty.';\n }\n\n return { path, message, suggestion };\n });\n\n throw new ValidationError(errors);\n }\n\n // Additional custom validations\n checkDuplicateNodeIds(data);\n checkOrphanedConnections(data);\n}\n\r\n","{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": \"https://flowlint.dev/schemas/n8n-workflow.json\",\n \"title\": \"n8n Workflow Schema\",\n \"description\": \"JSON Schema for n8n workflow files (v1.x)\",\n \"type\": \"object\",\n \"required\": [\"nodes\", \"connections\"],\n \"properties\": {\n \"name\": {\n \"type\": \"string\",\n \"description\": \"Workflow name\"\n },\n \"nodes\": {\n \"type\": \"array\",\n \"description\": \"Array of workflow nodes\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\"type\", \"name\"],\n \"properties\": {\n \"id\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"Unique node identifier\"\n },\n \"type\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"Node type (e.g., n8n-nodes-base.httpRequest)\"\n },\n \"name\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"Human-readable node name\"\n },\n \"parameters\": {\n \"type\": \"object\",\n \"description\": \"Node-specific configuration parameters\"\n },\n \"credentials\": {\n \"type\": \"object\",\n \"description\": \"Credential references for this node\"\n },\n \"position\": {\n \"type\": \"array\",\n \"description\": \"X,Y coordinates for UI placement\",\n \"items\": {\n \"type\": \"number\"\n },\n \"minItems\": 2,\n \"maxItems\": 2\n },\n \"continueOnFail\": {\n \"type\": \"boolean\",\n \"description\": \"Whether to continue execution on node failure\"\n },\n \"disabled\": {\n \"type\": \"boolean\",\n \"description\": \"Whether the node is disabled\"\n },\n \"notesInFlow\": {\n \"type\": \"boolean\"\n },\n \"notes\": {\n \"type\": \"string\"\n },\n \"typeVersion\": {\n \"type\": \"number\",\n \"description\": \"Version of the node type\"\n }\n },\n \"additionalProperties\": true\n }\n },\n \"connections\": {\n \"type\": \"object\",\n \"description\": \"Map of node connections (source node ID -> connection details)\",\n \"patternProperties\": {\n \"^.*$\": {\n \"type\": \"object\",\n \"description\": \"Connection channels for a source node\",\n \"patternProperties\": {\n \"^(main|error|timeout|.*?)$\": {\n \"description\": \"Connection array for this channel\",\n \"oneOf\": [\n {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\"node\"],\n \"properties\": {\n \"node\": {\n \"type\": \"string\",\n \"description\": \"Target node ID or name\"\n },\n \"type\": {\n \"type\": \"string\",\n \"description\": \"Connection type\"\n },\n \"index\": {\n \"type\": \"number\",\n \"description\": \"Output index\"\n }\n }\n }\n },\n {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\"node\"],\n \"properties\": {\n \"node\": {\n \"type\": \"string\"\n },\n \"type\": {\n \"type\": \"string\"\n },\n \"index\": {\n \"type\": \"number\"\n }\n }\n }\n }\n }\n ]\n }\n }\n }\n }\n },\n \"active\": {\n \"type\": \"boolean\",\n \"description\": \"Whether the workflow is active\"\n },\n \"settings\": {\n \"type\": \"object\",\n \"description\": \"Workflow settings\"\n },\n \"tags\": {\n \"type\": \"array\",\n \"description\": \"Workflow tags\",\n \"items\": {\n \"oneOf\": [\n { \"type\": \"string\" },\n {\n \"type\": \"object\",\n \"required\": [\"name\"],\n \"properties\": {\n \"id\": { \"type\": \"string\" },\n \"name\": { \"type\": \"string\" }\n },\n \"additionalProperties\": true\n }\n ]\n }\n },\n \"pinData\": {\n \"type\": \"object\",\n \"description\": \"Pinned execution data for testing\"\n },\n \"versionId\": {\n \"type\": \"string\",\n \"description\": \"Workflow version identifier\"\n },\n \"id\": {\n \"type\": [\"string\", \"number\"],\n \"description\": \"Workflow ID\"\n },\n \"meta\": {\n \"type\": \"object\",\n \"description\": \"Metadata\"\n }\n },\n \"additionalProperties\": true\n}\n","import type { Graph, NodeRef } from '../types';\n\n/**\n * Shared utility functions for workflow parsing and validation\n */\n\n\n/**\n * Helper to flatten nested connection arrays from n8n workflow connections.\n * Connections can be nested in various ways (arrays of arrays, objects with node properties).\n * This recursively flattens them to a simple array of connection objects.\n *\n * @param value - The connection value to flatten (can be array, object, or primitive)\n * @returns Array of connection objects with 'node' property\n */\nexport function flattenConnections(value: any): any[] {\n if (!value) return [];\n if (Array.isArray(value)) {\n return value.flatMap((entry) => flattenConnections(entry));\n }\n if (typeof value === 'object' && 'node' in value) {\n return [value];\n }\n return [];\n}\n\n/**\n * Build validation error objects from a collection of items using provided templates.\n * This utility eliminates code duplication in validation error construction.\n *\n * @template T - Type of items to process\n * @param items - Set or array of items to convert to validation errors\n * @param errorConfig - Configuration object containing:\n * - path: The JSON path where the error occurred\n * - messageTemplate: Function to generate error message for each item\n * - suggestionTemplate: Function to generate actionable suggestion for each item\n * @returns Array of validation error objects with path, message, and suggestion fields\n *\n * @example\n * ```typescript\n * const duplicates = new Set(['node1', 'node2']);\n * const errors = buildValidationErrors(duplicates, {\n * path: 'nodes[].id',\n * messageTemplate: (id) => `Duplicate node ID: \"${id}\"`, \n * suggestionTemplate: (id) => `Remove or rename the duplicate node with ID \"${id}\".`\n * });\n * ```\n */\nexport function buildValidationErrors<T>(\n items: Set<T> | T[],\n errorConfig: {\n path: string;\n messageTemplate: (item: T) => string;\n suggestionTemplate: (item: T) => string;\n }\n): Array<{ path: string; message: string; suggestion: string }> {\n const itemArray = Array.isArray(items) ? items : Array.from(items);\n return itemArray.map((item) => ({\n path: errorConfig.path,\n message: errorConfig.messageTemplate(item),\n suggestion: errorConfig.suggestionTemplate(item),\n }));\n}\n\nexport function collectStrings(value: unknown, out: string[] = []): string[] {\n if (typeof value === 'string') out.push(value);\n else if (Array.isArray(value)) value.forEach((entry) => collectStrings(entry, out));\n else if (value && typeof value === 'object')\n Object.values(value).forEach((entry) => collectStrings(entry, out));\n return out;\n}\n\nexport function toRegex(pattern: string): RegExp {\n let source = pattern;\n let flags = '';\n if (source.startsWith('(?i)')) {\n source = source.slice(4);\n flags += 'i';\n }\n return new RegExp(source, flags);\n}\n\nexport function isApiNode(type: string) {\n return /http|request|google|facebook|ads/i.test(type);\n}\n\nexport function isMutationNode(type: string) {\n return /write|insert|update|delete|post|put|patch|database|mongo|supabase|sheet/i.test(type);\n}\n\nexport function isErrorProneNode(type: string) {\n return isApiNode(type) || isMutationNode(type) || /execute|workflow|function/i.test(type);\n}\n\nexport function isNotificationNode(type: string) {\n return /slack|discord|email|gotify|mattermost|microsoftTeams|pushbullet|pushover|rocketchat|zulip|telegram/i.test(\n type,\n );\n}\n\nexport function isErrorHandlerNode(type: string, name?: string) {\n const normalizedType = type.toLowerCase();\n if (normalizedType.includes('stopanderror')) return true;\n if (normalizedType.includes('errorhandler')) return true;\n if (normalizedType.includes('raiseerror')) return true;\n\n const normalizedName = name?.toLowerCase() ?? '';\n if (normalizedName.includes('stop and error')) return true;\n if (normalizedName.includes('error handler')) return true;\n\n return false;\n}\n\nexport function isRejoinNode(graph: Graph, nodeId: string): boolean {\n const incoming = graph.edges.filter((e) => e.to === nodeId);\n if (incoming.length <= 1) return false;\n const hasErrorEdge = incoming.some((e) => e.on === 'error');\n const hasSuccessEdge = incoming.some((e) => e.on !== 'error');\n return hasErrorEdge && hasSuccessEdge;\n}\n\nexport function isMeaningfulConsumer(node: NodeRef): boolean {\n // A meaningful consumer is a node that has an external side-effect.\n return (\n isMutationNode(node.type) || // Writes to a DB, sheet, etc.\n isNotificationNode(node.type) || // Sends a message to Slack, email, etc.\n isApiNode(node.type) || // Calls an external API\n /respondToWebhook/i.test(node.type) // Specifically nodes that send a response back.\n );\n}\n\nexport function containsCandidate(value: unknown, candidates: string[]): boolean {\n if (!value || !candidates.length) return false;\n\n const queue: unknown[] = [value];\n const candidateRegex = new RegExp(`(${candidates.join('|')})`, 'i');\n\n while (queue.length > 0) {\n const current = queue.shift();\n\n if (typeof current === 'string') {\n if (candidateRegex.test(current)) return true;\n } else if (Array.isArray(current)) {\n queue.push(...current);\n } else if (current && typeof current === 'object') {\n for (const [key, val] of Object.entries(current)) {\n if (candidateRegex.test(key)) return true;\n queue.push(val);\n }\n }\n }\n\n return false;\n}\n\nconst TERMINAL_NODE_PATTERNS = [\n 'respond', 'reply', 'end', 'stop', 'terminate', 'return', 'sticky', 'note', 'noop', 'no operation',\n 'slack', 'email', 'discord', 'teams', 'webhook', 'telegram', 'pushbullet', 'mattermost', 'notifier', 'notification', 'alert', 'sms', 'call',\n];\n\nexport function isTerminalNode(type: string, name?: string) {\n const label = `${type} ${name ?? ''}`.toLowerCase();\n return TERMINAL_NODE_PATTERNS.some((pattern) => label.includes(pattern));\n}\n\nexport function readNumber(source: any, paths: string[]): number | undefined {\n for (const path of paths) {\n const value = path.split('.').reduce<any>((acc, key) => (acc ? acc[key] : undefined), source);\n if (typeof value === 'number') return value;\n if (typeof value === 'string' && !Number.isNaN(Number(value))) return Number(value);\n }\n return undefined;\n}\n\nexport function findAllDownstreamNodes(graph: Graph, startNodeId: string): Set<string> {\n const visited = new Set<string>();\n const queue: string[] = [startNodeId];\n visited.add(startNodeId);\n\n let head = 0;\n while (head < queue.length) {\n const currentId = queue[head++]!;\n const outgoing = graph.edges.filter((e) => e.from === currentId);\n for (const edge of outgoing) {\n if (!visited.has(edge.to)) {\n visited.add(edge.to);\n queue.push(edge.to);\n }\n }\n }\n return visited;\n}\n\nexport function findAllUpstreamNodes(graph: Graph, startNodeId: string): Set<string> {\n const visited = new Set<string>();\n const queue: string[] = [startNodeId];\n visited.add(startNodeId);\n\n let head = 0;\n while (head < queue.length) {\n const currentId = queue[head++]!;\n const incoming = graph.edges.filter((e) => e.to === currentId);\n for (const edge of incoming) {\n if (!visited.has(edge.from)) {\n visited.add(edge.from);\n queue.push(edge.from);\n }\n }\n }\n return visited;\n}\n\nexport const EXAMPLES_BASE_URL = \"https://github.com/Replikanti/flowlint-examples/tree/main\";\n\nexport function getExampleLink(ruleId: string): string {\n return `${EXAMPLES_BASE_URL}/${ruleId}`;\n}\n\r\n","import type { Graph, Finding, NodeRef, FindingSeverity } from '../types';\nimport type { FlowLintConfig } from '../config';\nimport { collectStrings, toRegex } from '../utils/utils';\n\ntype Rule = string;\ntype RuleContext = { path: string; cfg: FlowLintConfig; nodeLines?: Record<string, number> };\ntype RuleRunner = (graph: Graph, ctx: RuleContext) => Finding[];\ntype NodeRuleLogic = (node: NodeRef, graph: Graph, ctx: RuleContext) => Finding | Finding[] | null;\n\n/**\n * A higher-order function to create a rule that iterates over each node in the graph.\n * It abstracts the boilerplate of checking if the rule is enabled and iterating through nodes.\n *\n * @param ruleId - The ID of the rule (e.g., 'R1').\n * @param configKey - The key in the FlowLintConfig rules object.\n * @param logic - The function containing the core logic to be executed for each node.\n * @returns A RuleRunner function.\n */\nexport function createNodeRule(\n ruleId: Rule,\n configKey: keyof FlowLintConfig['rules'],\n logic: NodeRuleLogic,\n): RuleRunner {\n return (graph: Graph, ctx: RuleContext): Finding[] => {\n const ruleConfig = ctx.cfg.rules[configKey] as { enabled?: boolean };\n if (!ruleConfig?.enabled) {\n return [];\n }\n\n const findings: Finding[] = [];\n for (const node of graph.nodes) {\n const result = logic(node, graph, ctx);\n if (result) {\n if (Array.isArray(result)) {\n findings.push(...result);\n } else {\n findings.push(result);\n }\n }\n }\n return findings;\n };\n}\n\ntype HardcodedStringRuleOptions = {\n ruleId: Rule;\n severity: FindingSeverity;\n configKey: 'secrets' | 'config_literals';\n messageFn: (node: NodeRef, value: string) => string;\n details: string;\n};\n\n/**\n * Creates a rule that checks for hardcoded strings in node parameters based on a denylist of regex patterns.\n * This is used to create R4 (Secrets) and R9 (Config Literals).\n *\n * @param options - The configuration for the hardcoded string rule.\n * @returns A RuleRunner function.\n */\nexport function createHardcodedStringRule({\n ruleId,\n severity,\n configKey,\n messageFn,\n details,\n}: HardcodedStringRuleOptions): RuleRunner {\n const logic: NodeRuleLogic = (node, graph, ctx) => {\n const cfg = ctx.cfg.rules[configKey];\n if (!cfg.denylist_regex?.length) {\n return null;\n }\n const regexes = cfg.denylist_regex.map((pattern) => toRegex(pattern));\n\n const findings: Finding[] = [];\n const strings = collectStrings(node.params);\n\n for (const value of strings) {\n // Ignore expressions and empty strings\n if (!value || value.includes('{{')) {\n continue;\n }\n\n if (regexes.some((regex) => regex.test(value))) {\n findings.push({\n rule: ruleId,\n severity,\n path: ctx.path,\n message: messageFn(node, value),\n nodeId: node.id,\n line: ctx.nodeLines?.[node.id],\n raw_details: details,\n });\n // Only report one finding per node to avoid noise\n break;\n }\n }\n return findings;\n };\n\n return createNodeRule(ruleId, configKey, logic);\n}\n\r\n","import type { Graph, Finding, NodeRef } from '../types';\nimport type { FlowLintConfig } from '../config';\nimport { createNodeRule, createHardcodedStringRule } from './rule-utils';\nimport {\n isApiNode,\n isMutationNode,\n isErrorProneNode,\n isNotificationNode,\n isErrorHandlerNode,\n isRejoinNode,\n isMeaningfulConsumer,\n isTerminalNode,\n readNumber,\n findAllDownstreamNodes,\n findAllUpstreamNodes,\n containsCandidate,\n} from '../utils/utils';\n\ntype RuleContext = { path: string; cfg: FlowLintConfig; nodeLines?: Record<string, number> };\n\ntype RuleRunner = (graph: Graph, ctx: RuleContext) => Finding[];\n\n// --- Rule Definitions using Helpers ---\n\nconst r1Retry = createNodeRule('R1', 'rate_limit_retry', (node, graph, ctx) => {\n if (!isApiNode(node.type)) return null;\n\n const params = (node.params ?? {}) as Record<string, unknown>;\n const options = ((params as any).options ?? {}) as Record<string, unknown>;\n\n const retryCandidates: unknown[] = [\n options.retryOnFail,\n (params as any).retryOnFail,\n node.flags?.retryOnFail,\n ];\n\n const retryOnFail = retryCandidates.find((value) => value !== undefined && value !== null);\n\n if (retryOnFail === true) {\n return null;\n }\n\n if (typeof retryOnFail === 'string') {\n const normalized = retryOnFail.trim().toLowerCase();\n if (retryOnFail.includes('{{') || normalized === 'true') {\n return null;\n }\n }\n\n return {\n rule: 'R1',\n severity: 'must',\n path: ctx.path,\n message: `Node ${node.name || node.id} is missing retry/backoff configuration`,\n raw_details: `In the node properties, enable \"Retry on Fail\" under Options.`,\n nodeId: node.id,\n line: ctx.nodeLines?.[node.id],\n };\n});\n\nconst r2ErrorHandling = createNodeRule('R2', 'error_handling', (node, graph, ctx) => {\n if (ctx.cfg.rules.error_handling.forbid_continue_on_fail && node.flags?.continueOnFail) {\n return {\n rule: 'R2',\n severity: 'must',\n path: ctx.path,\n message: `Node ${node.name || node.id} has continueOnFail enabled (disable it and route errors explicitly)`,\n nodeId: node.id,\n line: ctx.nodeLines?.[node.id],\n raw_details:\n 'Open the node in n8n and disable \"Continue On Fail\" (Options > Continue On Fail). Route failures down an explicit error branch instead.',\n };\n }\n return null;\n});\n\nconst r4Secrets = createHardcodedStringRule({\n ruleId: 'R4',\n severity: 'must',\n configKey: 'secrets',\n messageFn: (node) => `Node ${node.name || node.id} contains a hardcoded secret (move it to credentials/env vars)`,\n details: 'Move API keys/tokens into Credentials or environment variables; the workflow should only reference {{$credentials.*}} expressions.',\n});\n\nconst r9ConfigLiterals = createHardcodedStringRule({\n ruleId: 'R9',\n severity: 'should',\n configKey: 'config_literals',\n messageFn: (node, value) => `Node ${node.name || node.id} contains env-specific literal \"${value.substring(0, 40)}\" (move to expression/credential)`,\n details: 'Move environment-specific URLs/IDs into expressions or credentials (e.g., {{$env.API_BASE_URL}}) so the workflow is portable.',\n});\n\nconst r10NamingConvention = createNodeRule('R10', 'naming_convention', (node, graph, ctx) => {\n const genericNames = new Set(ctx.cfg.rules.naming_convention.generic_names ?? []);\n if (!node.name || genericNames.has(node.name.toLowerCase())) {\n return {\n rule: 'R10',\n severity: 'nit',\n path: ctx.path,\n message: `Node ${node.id} uses a generic name \"${node.name ?? ''}\" (rename it to describe the action)`,\n nodeId: node.id,\n line: ctx.nodeLines?.[node.id],\n raw_details: 'Rename the node to describe its purpose (e.g., \"Check subscription status\" instead of \"IF\") for easier reviews and debugging.',\n };\n }\n return null;\n});\n\nconst DEPRECATED_NODES: Record<string, string> = {\n 'n8n-nodes-base.splitInBatches': 'Use Loop over items instead',\n 'n8n-nodes-base.executeWorkflow': 'Use Execute Workflow (Sub-Workflow) instead',\n};\n\nconst r11DeprecatedNodes = createNodeRule('R11', 'deprecated_nodes', (node, graph, ctx) => {\n if (DEPRECATED_NODES[node.type]) {\n return {\n rule: 'R11',\n severity: 'should',\n path: ctx.path,\n message: `Node ${node.name || node.id} uses deprecated type ${node.type} (replace with ${DEPRECATED_NODES[node.type]})`,\n nodeId: node.id,\n line: ctx.nodeLines?.[node.id],\n raw_details: `Replace this node with ${DEPRECATED_NODES[node.type]} so future n8n upgrades don’t break the workflow.`,\n };\n }\n return null;\n});\n\nconst r12UnhandledErrorPath = createNodeRule('R12', 'unhandled_error_path', (node, graph, ctx) => {\n if (!isErrorProneNode(node.type)) return null;\n\n const hasErrorPath = graph.edges.some((edge) => {\n if (edge.from !== node.id) return false;\n if (edge.on === 'error') return true;\n\n const targetNode = graph.nodes.find((candidate) => candidate.id === edge.to);\n return targetNode ? isErrorHandlerNode(targetNode.type, targetNode.name) : false;\n });\n\n if (!hasErrorPath) {\n return {\n rule: 'R12',\n severity: 'must',\n path: ctx.path,\n message: `Node ${node.name || node.id} has no error branch (add a red connector to handler)`,\n nodeId: node.id,\n line: ctx.nodeLines?.[node.id],\n raw_details:\n 'Add an error (red) branch to a Stop and Error or logging/alert node so failures do not disappear silently.',\n };\n }\n return null;\n});\n\nfunction r13WebhookAcknowledgment(graph: Graph, ctx: RuleContext): Finding[] {\n const cfg = ctx.cfg.rules.webhook_acknowledgment;\n if (!cfg?.enabled) return [];\n\n const findings: Finding[] = [];\n\n // Find all webhook trigger nodes (not respondToWebhook)\n const webhookNodes = graph.nodes.filter((node) =>\n node.type === 'n8n-nodes-base.webhook' ||\n (node.type.includes('webhook') && !node.type.includes('respondToWebhook'))\n );\n\n for (const webhookNode of webhookNodes) {\n // Get immediate downstream nodes\n const directDownstream = graph.edges\n .filter((edge) => edge.from === webhookNode.id)\n .map((edge) => graph.nodes.find((n) => n.id === edge.to))\n .filter((n): n is NodeRef => !!n);\n\n if (directDownstream.length === 0) continue;\n\n // Check if first downstream is \"Respond to Webhook\"\n const hasImmediateResponse = directDownstream.some((node) =>\n node.type === 'n8n-nodes-base.respondToWebhook' ||\n /respond.*webhook/i.test(node.type) ||\n /respond.*webhook/i.test(node.name || '')\n );\n\n if (hasImmediateResponse) continue; // Good pattern - immediate acknowledgment\n\n // Check if any downstream node is \"heavy\"\n const heavyNodeTypes = cfg.heavy_node_types || [\n 'n8n-nodes-base.httpRequest',\n 'n8n-nodes-base.postgres',\n 'n8n-nodes-base.mysql',\n 'n8n-nodes-base.mongodb',\n 'n8n-nodes-base.openAi',\n 'n8n-nodes-base.anthropic',\n ];\n\n const hasHeavyProcessing = directDownstream.some((node) =>\n heavyNodeTypes.includes(node.type) || /loop|batch/i.test(node.type)\n );\n\n if (hasHeavyProcessing) {\n findings.push({\n rule: 'R13',\n severity: 'must',\n path: ctx.path,\n message: `Webhook \"${webhookNode.name || webhookNode.id}\" performs heavy processing before acknowledgment (risk of timeout/duplicates)`,\n nodeId: webhookNode.id,\n line: ctx.nodeLines?.[webhookNode.id],\n raw_details: `Add a \"Respond to Webhook\" node immediately after the webhook trigger (return 200/204), then perform heavy processing. This prevents webhook timeouts and duplicate events.`,\n });\n }\n }\n\n return findings;\n}\n\nconst r14RetryAfterCompliance = createNodeRule('R14', 'retry_after_compliance', (node, graph, ctx) => {\n // Only check HTTP request nodes\n if (!isApiNode(node.type)) return null;\n\n const params = (node.params ?? {}) as Record<string, unknown>;\n const options = ((params as any).options ?? {}) as Record<string, unknown>;\n\n // Check if retry is enabled\n const retryCandidates: unknown[] = [\n options.retryOnFail,\n (params as any).retryOnFail,\n node.flags?.retryOnFail,\n ];\n\n const retryOnFail = retryCandidates.find((value) => value !== undefined && value !== null);\n\n // If retry is disabled or explicitly false, skip this rule\n if (!retryOnFail || retryOnFail === false) return null;\n\n // If retryOnFail is explicitly a string expression, skip if it's not \"true\"\n if (typeof retryOnFail === 'string') {\n const normalized = retryOnFail.trim().toLowerCase();\n if (retryOnFail.includes('{{') && normalized !== 'true') {\n return null; // Dynamic expression, assume it might handle retry-after\n }\n }\n\n // Check waitBetweenTries specifically (Pragmatic fix for n8n UI limitations)\n const waitBetweenTries = node.flags?.waitBetweenTries;\n if (waitBetweenTries !== undefined && waitBetweenTries !== null) {\n // If it's a static number (or numeric string), we accept it because n8n UI\n // often prevents using expressions here. We prioritize allowing retries (R1)\n // over strict Retry-After compliance if the platform limits the user.\n if (typeof waitBetweenTries === 'number') return null;\n if (\n typeof waitBetweenTries === 'string' &&\n !isNaN(Number(waitBetweenTries)) &&\n !waitBetweenTries.includes('{{')\n ) {\n return null;\n }\n }\n\n // Check if there's an expression/code that references retry-after\n const nodeStr = JSON.stringify(node);\n const hasRetryAfterLogic = /retry[-_]?after|retryafter/i.test(nodeStr);\n\n if (hasRetryAfterLogic) {\n return null; // Good - respects Retry-After\n }\n\n // Flag as violation\n return {\n rule: 'R14',\n severity: 'should',\n path: ctx.path,\n message: `Node ${node.name || node.id} has retry logic but ignores Retry-After headers (429/503 responses)`,\n raw_details: `Add expression to parse Retry-After header: const retryAfter = $json.headers['retry-after']; const delay = retryAfter ? (parseInt(retryAfter) || new Date(retryAfter) - Date.now()) : Math.min(1000 * Math.pow(2, $execution.retryCount), 60000); This prevents API bans and respects server rate limits.`,\n nodeId: node.id,\n line: ctx.nodeLines?.[node.id],\n };\n});\n\n\n// --- Rules with custom logic (not fitting the simple node-by-node pattern) ---\n\nfunction r3Idempotency(graph: Graph, ctx: RuleContext): Finding[] {\n const cfg = ctx.cfg.rules.idempotency;\n if (!cfg?.enabled) return [];\n\n const hasIngress = graph.nodes.some((node) => /webhook|trigger|start/i.test(node.type));\n if (!hasIngress) return [];\n\n const mutationNodes = graph.nodes.filter((node) => isMutationNode(node.type));\n if (!mutationNodes.length) return [];\n\n const findings: Finding[] = [];\n\n for (const mutationNode of mutationNodes) {\n const upstreamNodeIds = findAllUpstreamNodes(graph, mutationNode.id);\n const upstreamNodes = graph.nodes.filter((n) => upstreamNodeIds.has(n.id));\n\n const hasGuard = upstreamNodes.some((p) =>\n containsCandidate(p.params, cfg.key_field_candidates ?? []),\n );\n\n if (!hasGuard) {\n findings.push({\n rule: 'R3',\n severity: 'must',\n path: ctx.path,\n message: `The mutation path ending at \"${\n mutationNode.name || mutationNode.id\n }\" appears to be missing an idempotency guard.`,\n raw_details: `Ensure one of the upstream nodes or the mutation node itself uses an idempotency key, such as one of: ${(cfg.key_field_candidates ?? []).join(\n ', ',\n )}`,\n nodeId: mutationNode.id,\n line: ctx.nodeLines?.[mutationNode.id],\n });\n }\n }\n\n return findings;\n}\n\nfunction r5DeadEnds(graph: Graph, ctx: RuleContext): Finding[] {\n const cfg = ctx.cfg.rules.dead_ends;\n if (!cfg?.enabled) return [];\n if (graph.nodes.length <= 1) return [];\n\n const outgoing = new Map<string, number>();\n for (const node of graph.nodes) outgoing.set(node.id, 0);\n for (const edge of graph.edges) outgoing.set(edge.from, (outgoing.get(edge.from) || 0) + 1);\n\n const findings: Finding[] = [];\n for (const node of graph.nodes) {\n if ((outgoing.get(node.id) || 0) === 0 && !isTerminalNode(node.type, node.name)) {\n findings.push({\n rule: 'R5',\n severity: 'nit',\n path: ctx.path,\n message: `Node ${node.name || node.id} has no outgoing connections (either wire it up or remove it)`,\n nodeId: node.id,\n line: ctx.nodeLines?.[node.id],\n raw_details: 'Either remove this node as dead code or connect it to the next/safe step so the workflow can continue.',\n });\n }\n }\n return findings;\n}\n\nfunction r6LongRunning(graph: Graph, ctx: RuleContext): Finding[] {\n const cfg = ctx.cfg.rules.long_running;\n if (!cfg?.enabled) return [];\n const findings: Finding[] = [];\n const loopNodes = graph.nodes.filter((node) => /loop|batch|while|repeat/i.test(node.type));\n\n for (const node of loopNodes) {\n const iterations = readNumber(node.params, [\n 'maxIterations',\n 'maxIteration',\n 'limit',\n 'options.maxIterations',\n ]);\n\n if (!iterations || (cfg.max_iterations && iterations > cfg.max_iterations)) {\n findings.push({\n rule: 'R6',\n severity: 'should',\n path: ctx.path,\n message: `Node ${node.name || node.id} allows ${\n iterations ?? 'unbounded'\n } iterations (limit ${cfg.max_iterations}; set a lower cap)`,\n nodeId: node.id,\n line: ctx.nodeLines?.[node.id],\n raw_details: `Set Options > Max iterations to ≤ ${cfg.max_iterations} or split the processing into smaller batches.`,\n });\n }\n\n if (cfg.timeout_ms) {\n const timeout = readNumber(node.params, ['timeout', 'timeoutMs', 'options.timeout']);\n if (timeout && timeout > cfg.timeout_ms) {\n findings.push({\n rule: 'R6',\n severity: 'should',\n path: ctx.path,\n message: `Node ${node.name || node.id} uses timeout ${timeout}ms (limit ${\n cfg.timeout_ms\n }ms; shorten the timeout or break work apart)`,\n nodeId: node.id,\n line: ctx.nodeLines?.[node.id],\n raw_details: `Lower the timeout to ≤ ${cfg.timeout_ms}ms or split the workflow so no single step blocks for too long.`,\n });\n }\n }\n }\n\n return findings;\n}\n\nfunction r7AlertLogEnforcement(graph: Graph, ctx: RuleContext): Finding[] {\n const cfg = ctx.cfg.rules.alert_log_enforcement;\n if (!cfg?.enabled) return [];\n\n const findings: Finding[] = [];\n const errorEdges = graph.edges.filter((edge) => edge.on === 'error');\n\n for (const edge of errorEdges) {\n const fromNode = graph.nodes.find((n) => n.id === edge.from)!;\n let isHandled = false;\n const queue: string[] = [edge.to];\n const visited = new Set<string>([edge.to]);\n\n let head = 0;\n while (head < queue.length) {\n const currentId = queue[head++]!;\n const currentNode = graph.nodes.find((n) => n.id === currentId)!;\n\n if (isNotificationNode(currentNode.type) || isErrorHandlerNode(currentNode.type, currentNode.name)) {\n isHandled = true;\n break; // Found a handler, stop searching this path\n }\n\n if (isRejoinNode(graph, currentId)) {\n continue; // It's a rejoin point, but not a handler, so stop traversing this path\n }\n\n // Add successors to queue\n const outgoing = graph.edges.filter((e) => e.from === currentId);\n for (const outEdge of outgoing) {\n if (!visited.has(outEdge.to)) {\n visited.add(outEdge.to);\n queue.push(outEdge.to);\n }\n }\n }\n\n if (!isHandled) {\n findings.push({\n rule: 'R7',\n severity: 'should',\n path: ctx.path,\n message: `Error path from node ${\n fromNode.name || fromNode.id\n } has no log/alert before rejoining (add notification node)`,\n nodeId: fromNode.id,\n line: ctx.nodeLines?.[fromNode.id],\n raw_details: 'Add a Slack/Email/Log node on the error branch before it rejoins the main flow so failures leave an audit trail.',\n });\n }\n }\n return findings;\n}\n\nfunction r8UnusedData(graph: Graph, ctx: RuleContext): Finding[] {\n const cfg = ctx.cfg.rules.unused_data;\n if (!cfg?.enabled) return [];\n\n const findings: Finding[] = [];\n for (const node of graph.nodes) {\n // If a node has no successors, R5 handles it. If it's a terminal node, its \"use\" is to end the flow.\n if (isTerminalNode(node.type, node.name) || !graph.edges.some((e) => e.from === node.id)) {\n continue;\n }\n\n const downstreamNodes = findAllDownstreamNodes(graph, node.id);\n downstreamNodes.delete(node.id);\n\n const leadsToConsumer = [...downstreamNodes].some((id) => {\n const downstreamNode = graph.nodes.find((n) => n.id === id)!;\n return isMeaningfulConsumer(downstreamNode);\n });\n\n if (!leadsToConsumer) {\n findings.push({\n rule: 'R8',\n severity: 'nit',\n path: ctx.path,\n message: `Node \"${node.name || node.id}\" produces data that never reaches any consumer`,\n nodeId: node.id,\n line: ctx.nodeLines?.[node.id],\n raw_details: 'Wire this branch into a consumer (DB/API/response) or remove it—otherwise the data produced here is never used.',\n });\n }\n }\n return findings;\n}\n\n// --- Rule Registration ---\n\nconst rules: RuleRunner[] = [\n r1Retry,\n r2ErrorHandling,\n r3Idempotency,\n r4Secrets,\n r5DeadEnds,\n r6LongRunning,\n r7AlertLogEnforcement,\n r8UnusedData,\n r9ConfigLiterals,\n r10NamingConvention,\n r11DeprecatedNodes,\n r12UnhandledErrorPath,\n r13WebhookAcknowledgment,\n r14RetryAfterCompliance,\n];\n\nexport function runAllRules(graph: Graph, ctx: RuleContext): Finding[] {\n return rules.flatMap((rule) => rule(graph, ctx));\n}\r\n\r\n","// Types for FlowLint configuration\n\nexport interface RateLimitRetryConfig {\n enabled: boolean;\n max_concurrency?: number;\n default_retry?: { count: number; strategy: string; base_ms: number };\n}\n\nexport interface ErrorHandlingConfig {\n enabled: boolean;\n forbid_continue_on_fail?: boolean;\n}\n\nexport interface IdempotencyConfig {\n enabled: boolean;\n key_field_candidates?: string[];\n}\n\nexport interface SecretsConfig {\n enabled: boolean;\n denylist_regex?: string[];\n}\n\nexport interface DeadEndsConfig {\n enabled: boolean;\n}\n\nexport interface LongRunningConfig {\n enabled: boolean;\n max_iterations?: number;\n timeout_ms?: number;\n}\n\nexport interface UnusedDataConfig {\n enabled: boolean;\n}\n\nexport interface UnhandledErrorPathConfig {\n enabled: boolean;\n}\n\nexport interface AlertLogEnforcementConfig {\n enabled: boolean;\n}\n\nexport interface DeprecatedNodesConfig {\n enabled: boolean;\n}\n\nexport interface NamingConventionConfig {\n enabled: boolean;\n generic_names?: string[];\n}\n\nexport interface ConfigLiteralsConfig {\n enabled: boolean;\n denylist_regex?: string[];\n}\n\nexport interface WebhookAcknowledgmentConfig {\n enabled: boolean;\n heavy_node_types?: string[];\n}\n\nexport interface RetryAfterComplianceConfig {\n enabled: boolean;\n suggest_exponential_backoff?: boolean;\n suggest_jitter?: boolean;\n}\n\nexport interface RulesConfig {\n rate_limit_retry: RateLimitRetryConfig;\n error_handling: ErrorHandlingConfig;\n idempotency: IdempotencyConfig;\n secrets: SecretsConfig;\n dead_ends: DeadEndsConfig;\n long_running: LongRunningConfig;\n unused_data: UnusedDataConfig;\n unhandled_error_path: UnhandledErrorPathConfig;\n alert_log_enforcement: AlertLogEnforcementConfig;\n deprecated_nodes: DeprecatedNodesConfig;\n naming_convention: NamingConventionConfig;\n config_literals: ConfigLiteralsConfig;\n webhook_acknowledgment: WebhookAcknowledgmentConfig;\n retry_after_compliance: RetryAfterComplianceConfig;\n}\n\nexport interface FilesConfig {\n include: string[];\n ignore: string[];\n}\n\nexport interface ReportConfig {\n annotations: boolean;\n summary_limit: number;\n}\n\nexport interface FlowLintConfig {\n files: FilesConfig;\n report: ReportConfig;\n rules: RulesConfig;\n}\n\n// Keep backward compatible type\nexport type RuleConfig = { enabled: boolean; [key: string]: unknown };\n\nexport const defaultConfig: FlowLintConfig = {\n files: {\n include: ['**/*.n8n.json', '**/workflows/*.json', '**/workflows/**/*.json', '**/*.n8n.yaml', '**/*.json'],\n ignore: [\n 'samples/**',\n '**/*.spec.json',\n 'node_modules/**',\n 'package*.json',\n 'tsconfig*.json',\n '.flowlint.yml',\n '.github/**',\n '.husky/**',\n '.vscode/**',\n 'infra/**',\n '*.config.js',\n '*.config.ts',\n '**/*.lock',\n ],\n },\n report: { annotations: true, summary_limit: 25 },\n rules: {\n rate_limit_retry: {\n enabled: true,\n max_concurrency: 5,\n default_retry: { count: 3, strategy: 'exponential', base_ms: 500 },\n },\n error_handling: { enabled: true, forbid_continue_on_fail: true },\n idempotency: { enabled: true, key_field_candidates: ['eventId', 'messageId'] },\n secrets: { enabled: true, denylist_regex: ['(?i)api[_-]?key', 'Bearer '] },\n dead_ends: { enabled: true },\n long_running: { enabled: true, max_iterations: 1000, timeout_ms: 300000 },\n unused_data: { enabled: true },\n unhandled_error_path: { enabled: true },\n alert_log_enforcement: { enabled: true },\n deprecated_nodes: { enabled: true },\n naming_convention: {\n enabled: true,\n generic_names: ['http request', 'set', 'if', 'merge', 'switch', 'no-op', 'start'],\n },\n config_literals: {\n enabled: true,\n denylist_regex: [\n '(?i)\\\\b(dev|development)\\\\b',\n '(?i)\\\\b(stag|staging)\\\\b',\n '(?i)\\\\b(prod|production)\\\\b',\n '(?i)\\\\b(test|testing)\\\\b',\n ],\n },\n webhook_acknowledgment: {\n enabled: true,\n heavy_node_types: [\n 'n8n-nodes-base.httpRequest',\n 'n8n-nodes-base.postgres',\n 'n8n-nodes-base.mysql',\n 'n8n-nodes-base.mongodb',\n 'n8n-nodes-base.openAi',\n 'n8n-nodes-base.anthropic',\n 'n8n-nodes-base.huggingFace',\n ],\n },\n retry_after_compliance: {\n enabled: true,\n suggest_exponential_backoff: true,\n suggest_jitter: true,\n },\n },\n};\r\n","/**\n * Isomorphic config loader for FlowLint\n * Works in both Node.js and browser environments\n */\n\nimport YAML from 'yaml';\nimport { defaultConfig, type FlowLintConfig } from './default-config';\n\n/**\n * Deep merge configuration objects\n */\nfunction deepMerge<T>(base: T, override: Record<string, unknown>): T {\n const baseCopy = JSON.parse(JSON.stringify(base));\n if (!override) return baseCopy;\n return mergeInto(baseCopy as Record<string, unknown>, override) as T;\n}\n\nfunction mergeInto(target: Record<string, unknown>, source: Record<string, unknown>): Record<string, unknown> {\n for (const [key, value] of Object.entries(source)) {\n if (value === undefined || value === null) continue;\n if (Array.isArray(value)) {\n target[key] = value;\n } else if (typeof value === 'object') {\n if (typeof target[key] !== 'object' || target[key] === null) {\n target[key] = {};\n }\n mergeInto(target[key] as Record<string, unknown>, value as Record<string, unknown>);\n } else {\n target[key] = value;\n }\n }\n return target;\n}\n\n/**\n * Parse config from YAML string\n */\nexport function parseConfig(content: string): FlowLintConfig {\n const parsed = (YAML.parse(content) as Record<string, unknown>) || {};\n return deepMerge(defaultConfig, parsed);\n}\n\n/**\n * Load config - isomorphic function\n * In browser: returns defaultConfig (no filesystem access)\n * In Node.js: optionally loads from file path\n */\nexport function loadConfig(configPath?: string): FlowLintConfig {\n // Browser detection - return default config\n if (typeof globalThis !== 'undefined' && 'window' in globalThis) {\n return defaultConfig;\n }\n\n // Node.js: if path provided, try to load\n if (configPath) {\n return loadConfigFromFile(configPath);\n }\n\n // Try to find config in current directory\n return loadConfigFromCwd();\n}\n\n/**\n * Load config from a specific file path (Node.js only)\n */\nfunction loadConfigFromFile(configPath: string): FlowLintConfig {\n try {\n // Dynamic require to avoid bundling fs\n const fs = require('fs');\n \n if (!fs.existsSync(configPath)) {\n return defaultConfig;\n }\n \n const content = fs.readFileSync(configPath, 'utf-8');\n return parseConfig(content);\n } catch {\n return defaultConfig;\n }\n}\n\n/**\n * Find and load config from current working directory (Node.js only)\n */\nfunction loadConfigFromCwd(): FlowLintConfig {\n try {\n const fs = require('fs');\n const path = require('path');\n \n const candidates = ['.flowlint.yml', '.flowlint.yaml', 'flowlint.config.yml'];\n const cwd = process.cwd();\n \n for (const candidate of candidates) {\n const configPath = path.join(cwd, candidate);\n if (fs.existsSync(configPath)) {\n const content = fs.readFileSync(configPath, 'utf-8');\n return parseConfig(content);\n }\n }\n \n return defaultConfig;\n } catch {\n return defaultConfig;\n }\n}\n\n/**\n * Validate config structure\n */\nexport function validateConfig(config: unknown): config is FlowLintConfig {\n if (!config || typeof config !== 'object') return false;\n const c = config as Record<string, unknown>;\n return (\n 'files' in c &&\n 'report' in c &&\n 'rules' in c &&\n typeof c.files === 'object' &&\n typeof c.report === 'object' &&\n typeof c.rules === 'object'\n );\n}\r\n\r\n\r\n","/**\n * Findings utilities\n * Shared logic for processing and analyzing findings across both review engine and CLI\n */\n\nimport type { Finding } from '../types';\n\nexport interface FindingsSummary {\n must: number;\n should: number;\n nit: number;\n total: number;\n}\n\n/**\n * Count findings by severity level\n */\nexport function countFindingsBySeverity(findings: Finding[]): FindingsSummary {\n return {\n must: findings.filter((f) => f.severity === 'must').length,\n should: findings.filter((f) => f.severity === 'should').length,\n nit: findings.filter((f) => f.severity === 'nit').length,\n total: findings.length,\n };\n}\n\n/**\n * Get severity order for sorting\n */\nexport function getSeverityOrder(): Record<string, number> {\n return { must: 0, should: 1, nit: 2 };\n}\n\n/**\n * Sort findings by severity\n */\nexport function sortFindingsBySeverity(findings: Finding[]): Finding[] {\n const order = getSeverityOrder();\n return [...findings].sort((a, b) => order[a.severity] - order[b.severity]);\n}\n"],"mappings":"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","names":["YAML","YAML"]}
|
|
1
|
+
{"version":3,"sources":["../src/parser/parser-n8n.ts","../src/schemas/index.ts","../src/schemas/n8n-workflow.schema.json","../src/utils/utils.ts","../src/rules/rule-utils.ts","../src/rules/index.ts","../src/config/default-config.ts","../src/config/loader.ts","../src/utils/findings.ts","../src/reporter/reporter.ts"],"sourcesContent":["import YAML from 'yaml';\nimport type { Graph, NodeRef, Edge } from '../types';\nimport { validateN8nWorkflow } from '../schemas';\nimport { flattenConnections, isErrorProneNode } from '../utils/utils';\n\nexport function parseN8n(doc: string): Graph {\n let parsed: any;\n try {\n parsed = JSON.parse(doc);\n } catch {\n parsed = YAML.parse(doc);\n }\n\n // Validate workflow structure before parsing\n validateN8nWorkflow(parsed);\n\n const nodes: NodeRef[] = parsed.nodes.map((node: any, idx: number) => {\n const nodeId = node.id || node.name || `node-${idx}`;\n const flags: NodeRef['flags'] = {\n continueOnFail: node.continueOnFail,\n retryOnFail: node.retryOnFail ?? node.settings?.retryOnFail,\n waitBetweenTries: node.waitBetweenTries ?? node.settings?.waitBetweenTries,\n maxTries: node.maxTries ?? node.settings?.maxTries,\n };\n const hasFlags =\n flags.continueOnFail !== undefined ||\n flags.retryOnFail !== undefined ||\n flags.waitBetweenTries !== undefined;\n\n return {\n id: nodeId,\n type: node.type,\n name: node.name,\n params: node.parameters,\n cred: node.credentials,\n flags: hasFlags ? flags : undefined,\n };\n });\n\n const nameToId = new Map<string, string>();\n for (const node of nodes) {\n if (node.id) nameToId.set(node.id, node.id);\n if (node.name) nameToId.set(node.name, node.id);\n }\n\n const lines = doc.split(/\\r?\\n/);\n const idLine = new Map<string, number>();\n const nameLine = new Map<string, number>();\n lines.forEach((line, idx) => {\n const idMatch = line.match(/\"id\":\\s*\"([^\"]+)\"/);\n if (idMatch) idLine.set(idMatch[1], idx + 1);\n const nameMatch = line.match(/\"name\":\\s*\"([^\"]+)\"/);\n if (nameMatch) nameLine.set(nameMatch[1], idx + 1);\n });\n\n const nodeLines = new Map<string, number>();\n for (const node of nodes) {\n const lineNumber = (node.name && nameLine.get(node.name)) || idLine.get(node.id);\n if (lineNumber) {\n nodeLines.set(node.id, lineNumber);\n }\n }\n\n const nodeById = new Map<string, NodeRef>();\n for (const node of nodes) {\n nodeById.set(node.id, node);\n }\n\n const resolveEdgeType = (\n connectionType: string,\n outputIndex: number | undefined,\n sourceType?: string,\n ): Edge['on'] => {\n if (connectionType === 'error') return 'error';\n if (connectionType === 'timeout') return 'timeout';\n\n if (connectionType === 'main') {\n if (\n typeof outputIndex === 'number' &&\n outputIndex > 0 &&\n sourceType &&\n isErrorProneNode(sourceType)\n ) {\n return 'error';\n }\n return 'success';\n }\n\n return 'success';\n };\n\n const edges: Edge[] = [];\n Object.entries(parsed.connections || {}).forEach(([from, exits]) => {\n if (!exits) {\n return;\n }\n const exitChannels = exits as Record<string, any>;\n Object.entries(exitChannels).forEach(([exitType, conn]) => {\n const sourceId = nameToId.get(from) ?? from;\n const sourceNode = nodeById.get(sourceId);\n\n const enqueueEdges = (value: any, outputIndex?: number) => {\n flattenConnections(value).forEach((link) => {\n if (!link || typeof link !== 'object') return;\n const targetId = nameToId.get(link.node) ?? link.node;\n if (!targetId) return;\n\n const edgeType = resolveEdgeType(exitType, outputIndex, sourceNode?.type);\n edges.push({ from: sourceId, to: targetId, on: edgeType });\n });\n };\n\n if (Array.isArray(conn)) {\n conn.forEach((entry, index) => enqueueEdges(entry, index));\n } else {\n enqueueEdges(conn);\n }\n });\n });\n\n return {\n nodes,\n edges,\n meta: {\n credentials: !!parsed.credentials,\n nodeLines: Object.fromEntries(nodeLines),\n },\n };\n}\n\r\n","import Ajv, { type ValidateFunction } from 'ajv';\nimport addFormats from 'ajv-formats';\nimport workflowSchema from './n8n-workflow.schema.json';\nimport { flattenConnections, buildValidationErrors } from '../utils/utils';\n\n// Custom error class for validation failures\nexport class ValidationError extends Error {\n constructor(\n public errors: Array<{\n path: string;\n message: string;\n suggestion?: string;\n }>\n ) {\n super(`Workflow validation failed: ${errors.length} error(s)`);\n this.name = 'ValidationError';\n }\n}\n\n// Dummy validator that always passes\nconst createDummyValidator = (): ValidateFunction => {\n const v: any = () => true;\n v.errors = [];\n return v as ValidateFunction;\n};\n\n// Singleton instance\nlet validatorInstance: ValidateFunction | null = null;\n\nfunction getValidator(): ValidateFunction {\n if (validatorInstance) return validatorInstance;\n\n // Detect Node.js environment safely\n // Use optional chaining to satisfy SonarQube\n const isNode = typeof process !== 'undefined' && process?.versions?.node != null;\n\n if (isNode) {\n try {\n const ajv = new Ajv({\n allErrors: true,\n strict: false,\n verbose: true,\n code: { source: true, es5: true }\n });\n addFormats(ajv);\n validatorInstance = ajv.compile(workflowSchema);\n } catch (error) {\n // Fallback to dummy validator if compilation fails (e.g. due to strict CSP in some environments)\n console.warn('Failed to compile JSON schema validator, falling back to dummy validator:', error);\n validatorInstance = createDummyValidator();\n }\n } else {\n validatorInstance = createDummyValidator();\n }\n\n return validatorInstance;\n}\n\n/**\n * Throws a ValidationError if the provided set contains items.\n * Centralizes the pattern of checking validation results and throwing errors.\n * \n * @param items - Set of items that represent validation failures\n * @param config - Configuration for building error messages\n * @throws ValidationError if items set is not empty\n */\nfunction throwIfInvalid<T>(\n items: Set<T>,\n config: {\n path: string;\n messageTemplate: (item: T) => string;\n suggestionTemplate: (item: T) => string;\n }\n): void {\n if (items.size > 0) {\n const errors = buildValidationErrors(items, config);\n throw new ValidationError(errors);\n }\n}\n\n/**\n * Check for duplicate node IDs in the workflow\n */\nfunction checkDuplicateNodeIds(data: any): void {\n if (!Array.isArray(data.nodes)) return;\n\n const seen = new Set<string>();\n const duplicates = new Set<string>();\n\n for (const node of data.nodes) {\n if (node.id && seen.has(node.id)) {\n duplicates.add(node.id);\n }\n if (node.id) {\n seen.add(node.id);\n }\n }\n\n throwIfInvalid(duplicates, {\n path: 'nodes[].id',\n messageTemplate: (id) => `Duplicate node ID: \"${id}\"`,\n suggestionTemplate: (id) => `Each node must have a unique ID. Remove or rename the duplicate node with ID \"${id}\".`,\n });\n}\n\n/**\n * Check for orphaned connections (references to non-existent nodes)\n */\nfunction checkOrphanedConnections(data: any): void {\n if (!data.connections || !Array.isArray(data.nodes)) return;\n\n const nodeIds = new Set<string>();\n const nodeNames = new Set<string>();\n\n // Collect all node IDs and names\n for (const node of data.nodes) {\n if (node.id) nodeIds.add(node.id);\n if (node.name) nodeNames.add(node.name);\n }\n\n const orphanedRefs = new Set<string>();\n\n // Check all connection targets\n Object.entries(data.connections).forEach(([sourceId, channels]) => {\n // Check if source exists\n if (!nodeIds.has(sourceId) && !nodeNames.has(sourceId)) {\n orphanedRefs.add(sourceId);\n }\n\n // Check targets\n if (typeof channels === 'object' && channels !== null) {\n Object.values(channels).forEach((connArray: any) => {\n const flatConnections = flattenConnections(connArray);\n flatConnections.forEach((conn: any) => {\n if (conn?.node) {\n if (!nodeIds.has(conn.node) && !nodeNames.has(conn.node)) {\n orphanedRefs.add(conn.node);\n }\n }\n });\n });\n }\n });\n\n throwIfInvalid(orphanedRefs, {\n path: 'connections',\n messageTemplate: (ref) => `Orphaned connection reference: \"${ref}\"`,\n suggestionTemplate: (ref) => `Connection references node \"${ref}\" which does not exist. Add the missing node or remove the invalid connection.`,\n });\n}\n\n/**\n * Validate n8n workflow structure\n * Throws ValidationError with detailed messages if validation fails\n */\nexport function validateN8nWorkflow(data: any): void {\n const validate = getValidator();\n\n // Basic schema validation\n if (!validate(data)) {\n const errors = (validate.errors || []).map((err: any) => {\n const path = err.instancePath || err.schemaPath;\n const message = err.message || 'Validation error';\n let suggestion = '';\n\n // Provide helpful suggestions based on error type\n if (err.keyword === 'required') {\n const missing = err.params?.missingProperty;\n suggestion = `Add the required field \"${missing}\" to the workflow.`;\n } else if (err.keyword === 'type') {\n const expected = err.params?.type;\n suggestion = `The field should be of type \"${expected}\".`;\n } else if (err.keyword === 'minLength') {\n suggestion = 'This field cannot be empty.';\n }\n\n return { path, message, suggestion };\n });\n\n throw new ValidationError(errors);\n }\n\n // Additional custom validations\n checkDuplicateNodeIds(data);\n checkOrphanedConnections(data);\n}\n\r\n","{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": \"https://flowlint.dev/schemas/n8n-workflow.json\",\n \"title\": \"n8n Workflow Schema\",\n \"description\": \"JSON Schema for n8n workflow files (v1.x)\",\n \"type\": \"object\",\n \"required\": [\"nodes\", \"connections\"],\n \"properties\": {\n \"name\": {\n \"type\": \"string\",\n \"description\": \"Workflow name\"\n },\n \"nodes\": {\n \"type\": \"array\",\n \"description\": \"Array of workflow nodes\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\"type\", \"name\"],\n \"properties\": {\n \"id\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"Unique node identifier\"\n },\n \"type\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"Node type (e.g., n8n-nodes-base.httpRequest)\"\n },\n \"name\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"Human-readable node name\"\n },\n \"parameters\": {\n \"type\": \"object\",\n \"description\": \"Node-specific configuration parameters\"\n },\n \"credentials\": {\n \"type\": \"object\",\n \"description\": \"Credential references for this node\"\n },\n \"position\": {\n \"type\": \"array\",\n \"description\": \"X,Y coordinates for UI placement\",\n \"items\": {\n \"type\": \"number\"\n },\n \"minItems\": 2,\n \"maxItems\": 2\n },\n \"continueOnFail\": {\n \"type\": \"boolean\",\n \"description\": \"Whether to continue execution on node failure\"\n },\n \"disabled\": {\n \"type\": \"boolean\",\n \"description\": \"Whether the node is disabled\"\n },\n \"notesInFlow\": {\n \"type\": \"boolean\"\n },\n \"notes\": {\n \"type\": \"string\"\n },\n \"typeVersion\": {\n \"type\": \"number\",\n \"description\": \"Version of the node type\"\n }\n },\n \"additionalProperties\": true\n }\n },\n \"connections\": {\n \"type\": \"object\",\n \"description\": \"Map of node connections (source node ID -> connection details)\",\n \"patternProperties\": {\n \"^.*$\": {\n \"type\": \"object\",\n \"description\": \"Connection channels for a source node\",\n \"patternProperties\": {\n \"^(main|error|timeout|.*?)$\": {\n \"description\": \"Connection array for this channel\",\n \"oneOf\": [\n {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\"node\"],\n \"properties\": {\n \"node\": {\n \"type\": \"string\",\n \"description\": \"Target node ID or name\"\n },\n \"type\": {\n \"type\": \"string\",\n \"description\": \"Connection type\"\n },\n \"index\": {\n \"type\": \"number\",\n \"description\": \"Output index\"\n }\n }\n }\n },\n {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\"node\"],\n \"properties\": {\n \"node\": {\n \"type\": \"string\"\n },\n \"type\": {\n \"type\": \"string\"\n },\n \"index\": {\n \"type\": \"number\"\n }\n }\n }\n }\n }\n ]\n }\n }\n }\n }\n },\n \"active\": {\n \"type\": \"boolean\",\n \"description\": \"Whether the workflow is active\"\n },\n \"settings\": {\n \"type\": \"object\",\n \"description\": \"Workflow settings\"\n },\n \"tags\": {\n \"type\": \"array\",\n \"description\": \"Workflow tags\",\n \"items\": {\n \"oneOf\": [\n { \"type\": \"string\" },\n {\n \"type\": \"object\",\n \"required\": [\"name\"],\n \"properties\": {\n \"id\": { \"type\": \"string\" },\n \"name\": { \"type\": \"string\" }\n },\n \"additionalProperties\": true\n }\n ]\n }\n },\n \"pinData\": {\n \"type\": \"object\",\n \"description\": \"Pinned execution data for testing\"\n },\n \"versionId\": {\n \"type\": \"string\",\n \"description\": \"Workflow version identifier\"\n },\n \"id\": {\n \"type\": [\"string\", \"number\"],\n \"description\": \"Workflow ID\"\n },\n \"meta\": {\n \"type\": \"object\",\n \"description\": \"Metadata\"\n }\n },\n \"additionalProperties\": true\n}\n","import type { Graph, NodeRef } from '../types';\n\n/**\n * Shared utility functions for workflow parsing and validation\n */\n\n\n/**\n * Helper to flatten nested connection arrays from n8n workflow connections.\n * Connections can be nested in various ways (arrays of arrays, objects with node properties).\n * This recursively flattens them to a simple array of connection objects.\n *\n * @param value - The connection value to flatten (can be array, object, or primitive)\n * @returns Array of connection objects with 'node' property\n */\nexport function flattenConnections(value: any): any[] {\n if (!value) return [];\n if (Array.isArray(value)) {\n return value.flatMap((entry) => flattenConnections(entry));\n }\n if (typeof value === 'object' && 'node' in value) {\n return [value];\n }\n return [];\n}\n\n/**\n * Build validation error objects from a collection of items using provided templates.\n * This utility eliminates code duplication in validation error construction.\n *\n * @template T - Type of items to process\n * @param items - Set or array of items to convert to validation errors\n * @param errorConfig - Configuration object containing:\n * - path: The JSON path where the error occurred\n * - messageTemplate: Function to generate error message for each item\n * - suggestionTemplate: Function to generate actionable suggestion for each item\n * @returns Array of validation error objects with path, message, and suggestion fields\n *\n * @example\n * ```typescript\n * const duplicates = new Set(['node1', 'node2']);\n * const errors = buildValidationErrors(duplicates, {\n * path: 'nodes[].id',\n * messageTemplate: (id) => `Duplicate node ID: \"${id}\"`, \n * suggestionTemplate: (id) => `Remove or rename the duplicate node with ID \"${id}\".`\n * });\n * ```\n */\nexport function buildValidationErrors<T>(\n items: Set<T> | T[],\n errorConfig: {\n path: string;\n messageTemplate: (item: T) => string;\n suggestionTemplate: (item: T) => string;\n }\n): Array<{ path: string; message: string; suggestion: string }> {\n const itemArray = Array.isArray(items) ? items : Array.from(items);\n return itemArray.map((item) => ({\n path: errorConfig.path,\n message: errorConfig.messageTemplate(item),\n suggestion: errorConfig.suggestionTemplate(item),\n }));\n}\n\nexport function collectStrings(value: unknown, out: string[] = []): string[] {\n if (typeof value === 'string') out.push(value);\n else if (Array.isArray(value)) value.forEach((entry) => collectStrings(entry, out));\n else if (value && typeof value === 'object')\n Object.values(value).forEach((entry) => collectStrings(entry, out));\n return out;\n}\n\nexport function toRegex(pattern: string): RegExp {\n let source = pattern;\n let flags = '';\n if (source.startsWith('(?i)')) {\n source = source.slice(4);\n flags += 'i';\n }\n return new RegExp(source, flags);\n}\n\nexport function isApiNode(type: string) {\n return /http|request|google|facebook|ads/i.test(type);\n}\n\nexport function isMutationNode(type: string) {\n return /write|insert|update|delete|post|put|patch|database|mongo|supabase|sheet/i.test(type);\n}\n\nexport function isErrorProneNode(type: string) {\n return isApiNode(type) || isMutationNode(type) || /execute|workflow|function/i.test(type);\n}\n\nexport function isNotificationNode(type: string) {\n return /slack|discord|email|gotify|mattermost|microsoftTeams|pushbullet|pushover|rocketchat|zulip|telegram/i.test(\n type,\n );\n}\n\nexport function isErrorHandlerNode(type: string, name?: string) {\n const normalizedType = type.toLowerCase();\n if (normalizedType.includes('stopanderror')) return true;\n if (normalizedType.includes('errorhandler')) return true;\n if (normalizedType.includes('raiseerror')) return true;\n\n const normalizedName = name?.toLowerCase() ?? '';\n if (normalizedName.includes('stop and error')) return true;\n if (normalizedName.includes('error handler')) return true;\n\n return false;\n}\n\nexport function isRejoinNode(graph: Graph, nodeId: string): boolean {\n const incoming = graph.edges.filter((e) => e.to === nodeId);\n if (incoming.length <= 1) return false;\n const hasErrorEdge = incoming.some((e) => e.on === 'error');\n const hasSuccessEdge = incoming.some((e) => e.on !== 'error');\n return hasErrorEdge && hasSuccessEdge;\n}\n\nexport function isMeaningfulConsumer(node: NodeRef): boolean {\n // A meaningful consumer is a node that has an external side-effect.\n return (\n isMutationNode(node.type) || // Writes to a DB, sheet, etc.\n isNotificationNode(node.type) || // Sends a message to Slack, email, etc.\n isApiNode(node.type) || // Calls an external API\n /respondToWebhook/i.test(node.type) // Specifically nodes that send a response back.\n );\n}\n\nexport function containsCandidate(value: unknown, candidates: string[]): boolean {\n if (!value || !candidates.length) return false;\n\n const queue: unknown[] = [value];\n const candidateRegex = new RegExp(`(${candidates.join('|')})`, 'i');\n\n while (queue.length > 0) {\n const current = queue.shift();\n\n if (typeof current === 'string') {\n if (candidateRegex.test(current)) return true;\n } else if (Array.isArray(current)) {\n queue.push(...current);\n } else if (current && typeof current === 'object') {\n for (const [key, val] of Object.entries(current)) {\n if (candidateRegex.test(key)) return true;\n queue.push(val);\n }\n }\n }\n\n return false;\n}\n\nconst TERMINAL_NODE_PATTERNS = [\n 'respond', 'reply', 'end', 'stop', 'terminate', 'return', 'sticky', 'note', 'noop', 'no operation',\n 'slack', 'email', 'discord', 'teams', 'webhook', 'telegram', 'pushbullet', 'mattermost', 'notifier', 'notification', 'alert', 'sms', 'call',\n];\n\nexport function isTerminalNode(type: string, name?: string) {\n const label = `${type} ${name ?? ''}`.toLowerCase();\n return TERMINAL_NODE_PATTERNS.some((pattern) => label.includes(pattern));\n}\n\nexport function readNumber(source: any, paths: string[]): number | undefined {\n for (const path of paths) {\n const value = path.split('.').reduce<any>((acc, key) => (acc ? acc[key] : undefined), source);\n if (typeof value === 'number') return value;\n if (typeof value === 'string' && !Number.isNaN(Number(value))) return Number(value);\n }\n return undefined;\n}\n\nexport function findAllDownstreamNodes(graph: Graph, startNodeId: string): Set<string> {\n const visited = new Set<string>();\n const queue: string[] = [startNodeId];\n visited.add(startNodeId);\n\n let head = 0;\n while (head < queue.length) {\n const currentId = queue[head++]!;\n const outgoing = graph.edges.filter((e) => e.from === currentId);\n for (const edge of outgoing) {\n if (!visited.has(edge.to)) {\n visited.add(edge.to);\n queue.push(edge.to);\n }\n }\n }\n return visited;\n}\n\nexport function findAllUpstreamNodes(graph: Graph, startNodeId: string): Set<string> {\n const visited = new Set<string>();\n const queue: string[] = [startNodeId];\n visited.add(startNodeId);\n\n let head = 0;\n while (head < queue.length) {\n const currentId = queue[head++]!;\n const incoming = graph.edges.filter((e) => e.to === currentId);\n for (const edge of incoming) {\n if (!visited.has(edge.from)) {\n visited.add(edge.from);\n queue.push(edge.from);\n }\n }\n }\n return visited;\n}\n\nexport const EXAMPLES_BASE_URL = \"https://github.com/Replikanti/flowlint-examples/tree/main\";\n\nexport function getExampleLink(ruleId: string): string {\n return `${EXAMPLES_BASE_URL}/${ruleId}`;\n}\n\r\n","import type { Graph, Finding, NodeRef, FindingSeverity } from '../types';\nimport type { FlowLintConfig } from '../config';\nimport { collectStrings, toRegex } from '../utils/utils';\n\ntype Rule = string;\ntype RuleContext = { path: string; cfg: FlowLintConfig; nodeLines?: Record<string, number> };\ntype RuleRunner = (graph: Graph, ctx: RuleContext) => Finding[];\ntype NodeRuleLogic = (node: NodeRef, graph: Graph, ctx: RuleContext) => Finding | Finding[] | null;\n\n/**\n * A higher-order function to create a rule that iterates over each node in the graph.\n * It abstracts the boilerplate of checking if the rule is enabled and iterating through nodes.\n *\n * @param ruleId - The ID of the rule (e.g., 'R1').\n * @param configKey - The key in the FlowLintConfig rules object.\n * @param logic - The function containing the core logic to be executed for each node.\n * @returns A RuleRunner function.\n */\nexport function createNodeRule(\n ruleId: Rule,\n configKey: keyof FlowLintConfig['rules'],\n logic: NodeRuleLogic,\n): RuleRunner {\n return (graph: Graph, ctx: RuleContext): Finding[] => {\n const ruleConfig = ctx.cfg.rules[configKey] as { enabled?: boolean };\n if (!ruleConfig?.enabled) {\n return [];\n }\n\n const findings: Finding[] = [];\n for (const node of graph.nodes) {\n const result = logic(node, graph, ctx);\n if (result) {\n if (Array.isArray(result)) {\n findings.push(...result);\n } else {\n findings.push(result);\n }\n }\n }\n return findings;\n };\n}\n\ntype HardcodedStringRuleOptions = {\n ruleId: Rule;\n severity: FindingSeverity;\n configKey: 'secrets' | 'config_literals';\n messageFn: (node: NodeRef, value: string) => string;\n details: string;\n};\n\n/**\n * Creates a rule that checks for hardcoded strings in node parameters based on a denylist of regex patterns.\n * This is used to create R4 (Secrets) and R9 (Config Literals).\n *\n * @param options - The configuration for the hardcoded string rule.\n * @returns A RuleRunner function.\n */\nexport function createHardcodedStringRule({\n ruleId,\n severity,\n configKey,\n messageFn,\n details,\n}: HardcodedStringRuleOptions): RuleRunner {\n const logic: NodeRuleLogic = (node, graph, ctx) => {\n const cfg = ctx.cfg.rules[configKey];\n if (!cfg.denylist_regex?.length) {\n return null;\n }\n const regexes = cfg.denylist_regex.map((pattern) => toRegex(pattern));\n\n const findings: Finding[] = [];\n const strings = collectStrings(node.params);\n\n for (const value of strings) {\n // Ignore expressions and empty strings\n if (!value || value.includes('{{')) {\n continue;\n }\n\n if (regexes.some((regex) => regex.test(value))) {\n findings.push({\n rule: ruleId,\n severity,\n path: ctx.path,\n message: messageFn(node, value),\n nodeId: node.id,\n line: ctx.nodeLines?.[node.id],\n raw_details: details,\n });\n // Only report one finding per node to avoid noise\n break;\n }\n }\n return findings;\n };\n\n return createNodeRule(ruleId, configKey, logic);\n}\n\r\n","import type { Graph, Finding, NodeRef } from '../types';\nimport type { FlowLintConfig } from '../config';\nimport { createNodeRule, createHardcodedStringRule } from './rule-utils';\nimport {\n isApiNode,\n isMutationNode,\n isErrorProneNode,\n isNotificationNode,\n isErrorHandlerNode,\n isRejoinNode,\n isMeaningfulConsumer,\n isTerminalNode,\n readNumber,\n findAllDownstreamNodes,\n findAllUpstreamNodes,\n containsCandidate,\n} from '../utils/utils';\n\ntype RuleContext = { path: string; cfg: FlowLintConfig; nodeLines?: Record<string, number> };\n\ntype RuleRunner = (graph: Graph, ctx: RuleContext) => Finding[];\n\n// --- Rule Definitions using Helpers ---\n\nconst r1Retry = createNodeRule('R1', 'rate_limit_retry', (node, graph, ctx) => {\n if (!isApiNode(node.type)) return null;\n\n const params = (node.params ?? {}) as Record<string, unknown>;\n const options = ((params as any).options ?? {}) as Record<string, unknown>;\n\n const retryCandidates: unknown[] = [\n options.retryOnFail,\n (params as any).retryOnFail,\n node.flags?.retryOnFail,\n ];\n\n const retryOnFail = retryCandidates.find((value) => value !== undefined && value !== null);\n\n if (retryOnFail === true) {\n return null;\n }\n\n if (typeof retryOnFail === 'string') {\n const normalized = retryOnFail.trim().toLowerCase();\n if (retryOnFail.includes('{{') || normalized === 'true') {\n return null;\n }\n }\n\n return {\n rule: 'R1',\n severity: 'must',\n path: ctx.path,\n message: `Node ${node.name || node.id} is missing retry/backoff configuration`,\n raw_details: `In the node properties, enable \"Retry on Fail\" under Options.`,\n nodeId: node.id,\n line: ctx.nodeLines?.[node.id],\n };\n});\n\nconst r2ErrorHandling = createNodeRule('R2', 'error_handling', (node, graph, ctx) => {\n if (ctx.cfg.rules.error_handling.forbid_continue_on_fail && node.flags?.continueOnFail) {\n return {\n rule: 'R2',\n severity: 'must',\n path: ctx.path,\n message: `Node ${node.name || node.id} has continueOnFail enabled (disable it and route errors explicitly)`,\n nodeId: node.id,\n line: ctx.nodeLines?.[node.id],\n raw_details:\n 'Open the node in n8n and disable \"Continue On Fail\" (Options > Continue On Fail). Route failures down an explicit error branch instead.',\n };\n }\n return null;\n});\n\nconst r4Secrets = createHardcodedStringRule({\n ruleId: 'R4',\n severity: 'must',\n configKey: 'secrets',\n messageFn: (node) => `Node ${node.name || node.id} contains a hardcoded secret (move it to credentials/env vars)`,\n details: 'Move API keys/tokens into Credentials or environment variables; the workflow should only reference {{$credentials.*}} expressions.',\n});\n\nconst r9ConfigLiterals = createHardcodedStringRule({\n ruleId: 'R9',\n severity: 'should',\n configKey: 'config_literals',\n messageFn: (node, value) => `Node ${node.name || node.id} contains env-specific literal \"${value.substring(0, 40)}\" (move to expression/credential)`,\n details: 'Move environment-specific URLs/IDs into expressions or credentials (e.g., {{$env.API_BASE_URL}}) so the workflow is portable.',\n});\n\nconst r10NamingConvention = createNodeRule('R10', 'naming_convention', (node, graph, ctx) => {\n const genericNames = new Set(ctx.cfg.rules.naming_convention.generic_names ?? []);\n if (!node.name || genericNames.has(node.name.toLowerCase())) {\n return {\n rule: 'R10',\n severity: 'nit',\n path: ctx.path,\n message: `Node ${node.id} uses a generic name \"${node.name ?? ''}\" (rename it to describe the action)`,\n nodeId: node.id,\n line: ctx.nodeLines?.[node.id],\n raw_details: 'Rename the node to describe its purpose (e.g., \"Check subscription status\" instead of \"IF\") for easier reviews and debugging.',\n };\n }\n return null;\n});\n\nconst DEPRECATED_NODES: Record<string, string> = {\n 'n8n-nodes-base.splitInBatches': 'Use Loop over items instead',\n 'n8n-nodes-base.executeWorkflow': 'Use Execute Workflow (Sub-Workflow) instead',\n};\n\nconst r11DeprecatedNodes = createNodeRule('R11', 'deprecated_nodes', (node, graph, ctx) => {\n if (DEPRECATED_NODES[node.type]) {\n return {\n rule: 'R11',\n severity: 'should',\n path: ctx.path,\n message: `Node ${node.name || node.id} uses deprecated type ${node.type} (replace with ${DEPRECATED_NODES[node.type]})`,\n nodeId: node.id,\n line: ctx.nodeLines?.[node.id],\n raw_details: `Replace this node with ${DEPRECATED_NODES[node.type]} so future n8n upgrades don’t break the workflow.`,\n };\n }\n return null;\n});\n\nconst r12UnhandledErrorPath = createNodeRule('R12', 'unhandled_error_path', (node, graph, ctx) => {\n if (!isErrorProneNode(node.type)) return null;\n\n const hasErrorPath = graph.edges.some((edge) => {\n if (edge.from !== node.id) return false;\n if (edge.on === 'error') return true;\n\n const targetNode = graph.nodes.find((candidate) => candidate.id === edge.to);\n return targetNode ? isErrorHandlerNode(targetNode.type, targetNode.name) : false;\n });\n\n if (!hasErrorPath) {\n return {\n rule: 'R12',\n severity: 'must',\n path: ctx.path,\n message: `Node ${node.name || node.id} has no error branch (add a red connector to handler)`,\n nodeId: node.id,\n line: ctx.nodeLines?.[node.id],\n raw_details:\n 'Add an error (red) branch to a Stop and Error or logging/alert node so failures do not disappear silently.',\n };\n }\n return null;\n});\n\nfunction r13WebhookAcknowledgment(graph: Graph, ctx: RuleContext): Finding[] {\n const cfg = ctx.cfg.rules.webhook_acknowledgment;\n if (!cfg?.enabled) return [];\n\n const findings: Finding[] = [];\n\n // Find all webhook trigger nodes (not respondToWebhook)\n const webhookNodes = graph.nodes.filter((node) =>\n node.type === 'n8n-nodes-base.webhook' ||\n (node.type.includes('webhook') && !node.type.includes('respondToWebhook'))\n );\n\n for (const webhookNode of webhookNodes) {\n // Get immediate downstream nodes\n const directDownstream = graph.edges\n .filter((edge) => edge.from === webhookNode.id)\n .map((edge) => graph.nodes.find((n) => n.id === edge.to))\n .filter((n): n is NodeRef => !!n);\n\n if (directDownstream.length === 0) continue;\n\n // Check if first downstream is \"Respond to Webhook\"\n const hasImmediateResponse = directDownstream.some((node) =>\n node.type === 'n8n-nodes-base.respondToWebhook' ||\n /respond.*webhook/i.test(node.type) ||\n /respond.*webhook/i.test(node.name || '')\n );\n\n if (hasImmediateResponse) continue; // Good pattern - immediate acknowledgment\n\n // Check if any downstream node is \"heavy\"\n const heavyNodeTypes = cfg.heavy_node_types || [\n 'n8n-nodes-base.httpRequest',\n 'n8n-nodes-base.postgres',\n 'n8n-nodes-base.mysql',\n 'n8n-nodes-base.mongodb',\n 'n8n-nodes-base.openAi',\n 'n8n-nodes-base.anthropic',\n ];\n\n const hasHeavyProcessing = directDownstream.some((node) =>\n heavyNodeTypes.includes(node.type) || /loop|batch/i.test(node.type)\n );\n\n if (hasHeavyProcessing) {\n findings.push({\n rule: 'R13',\n severity: 'must',\n path: ctx.path,\n message: `Webhook \"${webhookNode.name || webhookNode.id}\" performs heavy processing before acknowledgment (risk of timeout/duplicates)`,\n nodeId: webhookNode.id,\n line: ctx.nodeLines?.[webhookNode.id],\n raw_details: `Add a \"Respond to Webhook\" node immediately after the webhook trigger (return 200/204), then perform heavy processing. This prevents webhook timeouts and duplicate events.`,\n });\n }\n }\n\n return findings;\n}\n\nconst r14RetryAfterCompliance = createNodeRule('R14', 'retry_after_compliance', (node, graph, ctx) => {\n // Only check HTTP request nodes\n if (!isApiNode(node.type)) return null;\n\n const params = (node.params ?? {}) as Record<string, unknown>;\n const options = ((params as any).options ?? {}) as Record<string, unknown>;\n\n // Check if retry is enabled\n const retryCandidates: unknown[] = [\n options.retryOnFail,\n (params as any).retryOnFail,\n node.flags?.retryOnFail,\n ];\n\n const retryOnFail = retryCandidates.find((value) => value !== undefined && value !== null);\n\n // If retry is disabled or explicitly false, skip this rule\n if (!retryOnFail || retryOnFail === false) return null;\n\n // If retryOnFail is explicitly a string expression, skip if it's not \"true\"\n if (typeof retryOnFail === 'string') {\n const normalized = retryOnFail.trim().toLowerCase();\n if (retryOnFail.includes('{{') && normalized !== 'true') {\n return null; // Dynamic expression, assume it might handle retry-after\n }\n }\n\n // Check waitBetweenTries specifically (Pragmatic fix for n8n UI limitations)\n const waitBetweenTries = node.flags?.waitBetweenTries;\n if (waitBetweenTries !== undefined && waitBetweenTries !== null) {\n // If it's a static number (or numeric string), we accept it because n8n UI\n // often prevents using expressions here. We prioritize allowing retries (R1)\n // over strict Retry-After compliance if the platform limits the user.\n if (typeof waitBetweenTries === 'number') return null;\n if (\n typeof waitBetweenTries === 'string' &&\n !isNaN(Number(waitBetweenTries)) &&\n !waitBetweenTries.includes('{{')\n ) {\n return null;\n }\n }\n\n // Check if there's an expression/code that references retry-after\n const nodeStr = JSON.stringify(node);\n const hasRetryAfterLogic = /retry[-_]?after|retryafter/i.test(nodeStr);\n\n if (hasRetryAfterLogic) {\n return null; // Good - respects Retry-After\n }\n\n // Flag as violation\n return {\n rule: 'R14',\n severity: 'should',\n path: ctx.path,\n message: `Node ${node.name || node.id} has retry logic but ignores Retry-After headers (429/503 responses)`,\n raw_details: `Add expression to parse Retry-After header: const retryAfter = $json.headers['retry-after']; const delay = retryAfter ? (parseInt(retryAfter) || new Date(retryAfter) - Date.now()) : Math.min(1000 * Math.pow(2, $execution.retryCount), 60000); This prevents API bans and respects server rate limits.`,\n nodeId: node.id,\n line: ctx.nodeLines?.[node.id],\n };\n});\n\n\n// --- Rules with custom logic (not fitting the simple node-by-node pattern) ---\n\nfunction r3Idempotency(graph: Graph, ctx: RuleContext): Finding[] {\n const cfg = ctx.cfg.rules.idempotency;\n if (!cfg?.enabled) return [];\n\n const hasIngress = graph.nodes.some((node) => /webhook|trigger|start/i.test(node.type));\n if (!hasIngress) return [];\n\n const mutationNodes = graph.nodes.filter((node) => isMutationNode(node.type));\n if (!mutationNodes.length) return [];\n\n const findings: Finding[] = [];\n\n for (const mutationNode of mutationNodes) {\n const upstreamNodeIds = findAllUpstreamNodes(graph, mutationNode.id);\n const upstreamNodes = graph.nodes.filter((n) => upstreamNodeIds.has(n.id));\n\n const hasGuard = upstreamNodes.some((p) =>\n containsCandidate(p.params, cfg.key_field_candidates ?? []),\n );\n\n if (!hasGuard) {\n findings.push({\n rule: 'R3',\n severity: 'must',\n path: ctx.path,\n message: `The mutation path ending at \"${\n mutationNode.name || mutationNode.id\n }\" appears to be missing an idempotency guard.`,\n raw_details: `Ensure one of the upstream nodes or the mutation node itself uses an idempotency key, such as one of: ${(cfg.key_field_candidates ?? []).join(\n ', ',\n )}`,\n nodeId: mutationNode.id,\n line: ctx.nodeLines?.[mutationNode.id],\n });\n }\n }\n\n return findings;\n}\n\nfunction r5DeadEnds(graph: Graph, ctx: RuleContext): Finding[] {\n const cfg = ctx.cfg.rules.dead_ends;\n if (!cfg?.enabled) return [];\n if (graph.nodes.length <= 1) return [];\n\n const outgoing = new Map<string, number>();\n for (const node of graph.nodes) outgoing.set(node.id, 0);\n for (const edge of graph.edges) outgoing.set(edge.from, (outgoing.get(edge.from) || 0) + 1);\n\n const findings: Finding[] = [];\n for (const node of graph.nodes) {\n if ((outgoing.get(node.id) || 0) === 0 && !isTerminalNode(node.type, node.name)) {\n findings.push({\n rule: 'R5',\n severity: 'nit',\n path: ctx.path,\n message: `Node ${node.name || node.id} has no outgoing connections (either wire it up or remove it)`,\n nodeId: node.id,\n line: ctx.nodeLines?.[node.id],\n raw_details: 'Either remove this node as dead code or connect it to the next/safe step so the workflow can continue.',\n });\n }\n }\n return findings;\n}\n\nfunction r6LongRunning(graph: Graph, ctx: RuleContext): Finding[] {\n const cfg = ctx.cfg.rules.long_running;\n if (!cfg?.enabled) return [];\n const findings: Finding[] = [];\n const loopNodes = graph.nodes.filter((node) => /loop|batch|while|repeat/i.test(node.type));\n\n for (const node of loopNodes) {\n const iterations = readNumber(node.params, [\n 'maxIterations',\n 'maxIteration',\n 'limit',\n 'options.maxIterations',\n ]);\n\n if (!iterations || (cfg.max_iterations && iterations > cfg.max_iterations)) {\n findings.push({\n rule: 'R6',\n severity: 'should',\n path: ctx.path,\n message: `Node ${node.name || node.id} allows ${\n iterations ?? 'unbounded'\n } iterations (limit ${cfg.max_iterations}; set a lower cap)`,\n nodeId: node.id,\n line: ctx.nodeLines?.[node.id],\n raw_details: `Set Options > Max iterations to ≤ ${cfg.max_iterations} or split the processing into smaller batches.`,\n });\n }\n\n if (cfg.timeout_ms) {\n const timeout = readNumber(node.params, ['timeout', 'timeoutMs', 'options.timeout']);\n if (timeout && timeout > cfg.timeout_ms) {\n findings.push({\n rule: 'R6',\n severity: 'should',\n path: ctx.path,\n message: `Node ${node.name || node.id} uses timeout ${timeout}ms (limit ${\n cfg.timeout_ms\n }ms; shorten the timeout or break work apart)`,\n nodeId: node.id,\n line: ctx.nodeLines?.[node.id],\n raw_details: `Lower the timeout to ≤ ${cfg.timeout_ms}ms or split the workflow so no single step blocks for too long.`,\n });\n }\n }\n }\n\n return findings;\n}\n\nfunction r7AlertLogEnforcement(graph: Graph, ctx: RuleContext): Finding[] {\n const cfg = ctx.cfg.rules.alert_log_enforcement;\n if (!cfg?.enabled) return [];\n\n const findings: Finding[] = [];\n const errorEdges = graph.edges.filter((edge) => edge.on === 'error');\n\n for (const edge of errorEdges) {\n const fromNode = graph.nodes.find((n) => n.id === edge.from)!;\n let isHandled = false;\n const queue: string[] = [edge.to];\n const visited = new Set<string>([edge.to]);\n\n let head = 0;\n while (head < queue.length) {\n const currentId = queue[head++]!;\n const currentNode = graph.nodes.find((n) => n.id === currentId)!;\n\n if (isNotificationNode(currentNode.type) || isErrorHandlerNode(currentNode.type, currentNode.name)) {\n isHandled = true;\n break; // Found a handler, stop searching this path\n }\n\n if (isRejoinNode(graph, currentId)) {\n continue; // It's a rejoin point, but not a handler, so stop traversing this path\n }\n\n // Add successors to queue\n const outgoing = graph.edges.filter((e) => e.from === currentId);\n for (const outEdge of outgoing) {\n if (!visited.has(outEdge.to)) {\n visited.add(outEdge.to);\n queue.push(outEdge.to);\n }\n }\n }\n\n if (!isHandled) {\n findings.push({\n rule: 'R7',\n severity: 'should',\n path: ctx.path,\n message: `Error path from node ${\n fromNode.name || fromNode.id\n } has no log/alert before rejoining (add notification node)`,\n nodeId: fromNode.id,\n line: ctx.nodeLines?.[fromNode.id],\n raw_details: 'Add a Slack/Email/Log node on the error branch before it rejoins the main flow so failures leave an audit trail.',\n });\n }\n }\n return findings;\n}\n\nfunction r8UnusedData(graph: Graph, ctx: RuleContext): Finding[] {\n const cfg = ctx.cfg.rules.unused_data;\n if (!cfg?.enabled) return [];\n\n const findings: Finding[] = [];\n for (const node of graph.nodes) {\n // If a node has no successors, R5 handles it. If it's a terminal node, its \"use\" is to end the flow.\n if (isTerminalNode(node.type, node.name) || !graph.edges.some((e) => e.from === node.id)) {\n continue;\n }\n\n const downstreamNodes = findAllDownstreamNodes(graph, node.id);\n downstreamNodes.delete(node.id);\n\n const leadsToConsumer = [...downstreamNodes].some((id) => {\n const downstreamNode = graph.nodes.find((n) => n.id === id)!;\n return isMeaningfulConsumer(downstreamNode);\n });\n\n if (!leadsToConsumer) {\n findings.push({\n rule: 'R8',\n severity: 'nit',\n path: ctx.path,\n message: `Node \"${node.name || node.id}\" produces data that never reaches any consumer`,\n nodeId: node.id,\n line: ctx.nodeLines?.[node.id],\n raw_details: 'Wire this branch into a consumer (DB/API/response) or remove it—otherwise the data produced here is never used.',\n });\n }\n }\n return findings;\n}\n\n// --- Rule Registration ---\n\nconst rules: RuleRunner[] = [\n r1Retry,\n r2ErrorHandling,\n r3Idempotency,\n r4Secrets,\n r5DeadEnds,\n r6LongRunning,\n r7AlertLogEnforcement,\n r8UnusedData,\n r9ConfigLiterals,\n r10NamingConvention,\n r11DeprecatedNodes,\n r12UnhandledErrorPath,\n r13WebhookAcknowledgment,\n r14RetryAfterCompliance,\n];\n\nexport function runAllRules(graph: Graph, ctx: RuleContext): Finding[] {\n return rules.flatMap((rule) => rule(graph, ctx));\n}\r\n\r\n","// Types for FlowLint configuration\n\nexport interface RateLimitRetryConfig {\n enabled: boolean;\n max_concurrency?: number;\n default_retry?: { count: number; strategy: string; base_ms: number };\n}\n\nexport interface ErrorHandlingConfig {\n enabled: boolean;\n forbid_continue_on_fail?: boolean;\n}\n\nexport interface IdempotencyConfig {\n enabled: boolean;\n key_field_candidates?: string[];\n}\n\nexport interface SecretsConfig {\n enabled: boolean;\n denylist_regex?: string[];\n}\n\nexport interface DeadEndsConfig {\n enabled: boolean;\n}\n\nexport interface LongRunningConfig {\n enabled: boolean;\n max_iterations?: number;\n timeout_ms?: number;\n}\n\nexport interface UnusedDataConfig {\n enabled: boolean;\n}\n\nexport interface UnhandledErrorPathConfig {\n enabled: boolean;\n}\n\nexport interface AlertLogEnforcementConfig {\n enabled: boolean;\n}\n\nexport interface DeprecatedNodesConfig {\n enabled: boolean;\n}\n\nexport interface NamingConventionConfig {\n enabled: boolean;\n generic_names?: string[];\n}\n\nexport interface ConfigLiteralsConfig {\n enabled: boolean;\n denylist_regex?: string[];\n}\n\nexport interface WebhookAcknowledgmentConfig {\n enabled: boolean;\n heavy_node_types?: string[];\n}\n\nexport interface RetryAfterComplianceConfig {\n enabled: boolean;\n suggest_exponential_backoff?: boolean;\n suggest_jitter?: boolean;\n}\n\nexport interface RulesConfig {\n rate_limit_retry: RateLimitRetryConfig;\n error_handling: ErrorHandlingConfig;\n idempotency: IdempotencyConfig;\n secrets: SecretsConfig;\n dead_ends: DeadEndsConfig;\n long_running: LongRunningConfig;\n unused_data: UnusedDataConfig;\n unhandled_error_path: UnhandledErrorPathConfig;\n alert_log_enforcement: AlertLogEnforcementConfig;\n deprecated_nodes: DeprecatedNodesConfig;\n naming_convention: NamingConventionConfig;\n config_literals: ConfigLiteralsConfig;\n webhook_acknowledgment: WebhookAcknowledgmentConfig;\n retry_after_compliance: RetryAfterComplianceConfig;\n}\n\nexport interface FilesConfig {\n include: string[];\n ignore: string[];\n}\n\nexport interface ReportConfig {\n annotations: boolean;\n summary_limit: number;\n}\n\nexport interface FlowLintConfig {\n files: FilesConfig;\n report: ReportConfig;\n rules: RulesConfig;\n}\n\n// Keep backward compatible type\nexport type RuleConfig = { enabled: boolean; [key: string]: unknown };\n\nexport const defaultConfig: FlowLintConfig = {\n files: {\n include: ['**/*.n8n.json', '**/workflows/*.json', '**/workflows/**/*.json', '**/*.n8n.yaml', '**/*.json'],\n ignore: [\n 'samples/**',\n '**/*.spec.json',\n 'node_modules/**',\n 'package*.json',\n 'tsconfig*.json',\n '.flowlint.yml',\n '.github/**',\n '.husky/**',\n '.vscode/**',\n 'infra/**',\n '*.config.js',\n '*.config.ts',\n '**/*.lock',\n ],\n },\n report: { annotations: true, summary_limit: 25 },\n rules: {\n rate_limit_retry: {\n enabled: true,\n max_concurrency: 5,\n default_retry: { count: 3, strategy: 'exponential', base_ms: 500 },\n },\n error_handling: { enabled: true, forbid_continue_on_fail: true },\n idempotency: { enabled: true, key_field_candidates: ['eventId', 'messageId'] },\n secrets: { enabled: true, denylist_regex: ['(?i)api[_-]?key', 'Bearer '] },\n dead_ends: { enabled: true },\n long_running: { enabled: true, max_iterations: 1000, timeout_ms: 300000 },\n unused_data: { enabled: true },\n unhandled_error_path: { enabled: true },\n alert_log_enforcement: { enabled: true },\n deprecated_nodes: { enabled: true },\n naming_convention: {\n enabled: true,\n generic_names: ['http request', 'set', 'if', 'merge', 'switch', 'no-op', 'start'],\n },\n config_literals: {\n enabled: true,\n denylist_regex: [\n '(?i)\\\\b(dev|development)\\\\b',\n '(?i)\\\\b(stag|staging)\\\\b',\n '(?i)\\\\b(prod|production)\\\\b',\n '(?i)\\\\b(test|testing)\\\\b',\n ],\n },\n webhook_acknowledgment: {\n enabled: true,\n heavy_node_types: [\n 'n8n-nodes-base.httpRequest',\n 'n8n-nodes-base.postgres',\n 'n8n-nodes-base.mysql',\n 'n8n-nodes-base.mongodb',\n 'n8n-nodes-base.openAi',\n 'n8n-nodes-base.anthropic',\n 'n8n-nodes-base.huggingFace',\n ],\n },\n retry_after_compliance: {\n enabled: true,\n suggest_exponential_backoff: true,\n suggest_jitter: true,\n },\n },\n};\r\n","/**\n * Isomorphic config loader for FlowLint\n * Works in both Node.js and browser environments\n */\n\nimport YAML from 'yaml';\nimport { defaultConfig, type FlowLintConfig } from './default-config';\n\n/**\n * Deep merge configuration objects\n */\nfunction deepMerge<T>(base: T, override: Record<string, unknown>): T {\n const baseCopy = JSON.parse(JSON.stringify(base));\n if (!override) return baseCopy;\n return mergeInto(baseCopy as Record<string, unknown>, override) as T;\n}\n\nfunction mergeInto(target: Record<string, unknown>, source: Record<string, unknown>): Record<string, unknown> {\n for (const [key, value] of Object.entries(source)) {\n if (value === undefined || value === null) continue;\n if (Array.isArray(value)) {\n target[key] = value;\n } else if (typeof value === 'object') {\n if (typeof target[key] !== 'object' || target[key] === null) {\n target[key] = {};\n }\n mergeInto(target[key] as Record<string, unknown>, value as Record<string, unknown>);\n } else {\n target[key] = value;\n }\n }\n return target;\n}\n\n/**\n * Parse config from YAML string\n */\nexport function parseConfig(content: string): FlowLintConfig {\n const parsed = (YAML.parse(content) as Record<string, unknown>) || {};\n return deepMerge(defaultConfig, parsed);\n}\n\n/**\n * Load config - isomorphic function\n * In browser: returns defaultConfig (no filesystem access)\n * In Node.js: optionally loads from file path\n */\nexport function loadConfig(configPath?: string): FlowLintConfig {\n // Browser detection - return default config\n if (typeof globalThis !== 'undefined' && 'window' in globalThis) {\n return defaultConfig;\n }\n\n // Node.js: if path provided, try to load\n if (configPath) {\n return loadConfigFromFile(configPath);\n }\n\n // Try to find config in current directory\n return loadConfigFromCwd();\n}\n\n/**\n * Load config from a specific file path (Node.js only)\n */\nfunction loadConfigFromFile(configPath: string): FlowLintConfig {\n try {\n // Dynamic require to avoid bundling fs\n const fs = require('fs');\n \n if (!fs.existsSync(configPath)) {\n return defaultConfig;\n }\n \n const content = fs.readFileSync(configPath, 'utf-8');\n return parseConfig(content);\n } catch {\n return defaultConfig;\n }\n}\n\n/**\n * Find and load config from current working directory (Node.js only)\n */\nfunction loadConfigFromCwd(): FlowLintConfig {\n try {\n const fs = require('fs');\n const path = require('path');\n \n const candidates = ['.flowlint.yml', '.flowlint.yaml', 'flowlint.config.yml'];\n const cwd = process.cwd();\n \n for (const candidate of candidates) {\n const configPath = path.join(cwd, candidate);\n if (fs.existsSync(configPath)) {\n const content = fs.readFileSync(configPath, 'utf-8');\n return parseConfig(content);\n }\n }\n \n return defaultConfig;\n } catch {\n return defaultConfig;\n }\n}\n\n/**\n * Validate config structure\n */\nexport function validateConfig(config: unknown): config is FlowLintConfig {\n if (!config || typeof config !== 'object') return false;\n const c = config as Record<string, unknown>;\n return (\n 'files' in c &&\n 'report' in c &&\n 'rules' in c &&\n typeof c.files === 'object' &&\n typeof c.report === 'object' &&\n typeof c.rules === 'object'\n );\n}\r\n\r\n\r\n","/**\n * Findings utilities\n * Shared logic for processing and analyzing findings across both review engine and CLI\n */\n\nimport type { Finding } from '../types';\n\nexport interface FindingsSummary {\n must: number;\n should: number;\n nit: number;\n total: number;\n}\n\n/**\n * Count findings by severity level\n */\nexport function countFindingsBySeverity(findings: Finding[]): FindingsSummary {\n return {\n must: findings.filter((f) => f.severity === 'must').length,\n should: findings.filter((f) => f.severity === 'should').length,\n nit: findings.filter((f) => f.severity === 'nit').length,\n total: findings.length,\n };\n}\n\n/**\n * Get severity order for sorting\n */\nexport function getSeverityOrder(): Record<string, number> {\n return { must: 0, should: 1, nit: 2 };\n}\n\n/**\n * Sort findings by severity\n */\nexport function sortFindingsBySeverity(findings: Finding[]): Finding[] {\n const order = getSeverityOrder();\n return [...findings].sort((a, b) => order[a.severity] - order[b.severity]);\n}\n","import type { Finding } from '../types';\nimport type { FlowLintConfig } from '../config';\n\ntype Conclusion = 'action_required' | 'neutral' | 'success' | 'failure';\n\nexport function buildCheckOutput({\n findings,\n cfg,\n summaryOverride,\n conclusionOverride,\n}: {\n findings: Finding[];\n cfg: FlowLintConfig;\n summaryOverride?: string;\n conclusionOverride?: Conclusion;\n}) {\n const summary = summaryOverride ?? summarize(findings);\n const conclusion = conclusionOverride ?? inferConclusion(findings);\n\n return {\n conclusion,\n output: {\n title: process.env.CHECK_TITLE || 'FlowLint findings',\n summary,\n },\n };\n}\n\nexport function buildAnnotations(findings: Finding[]): any[] {\n const severityOrder: Finding['severity'][] = ['must', 'should', 'nit'];\n const ordered = [...findings].sort((a, b) => severityOrder.indexOf(a.severity) - severityOrder.indexOf(b.severity));\n\n return ordered.map((finding) => {\n const line = finding.line ?? 1;\n\n // Build raw_details with optional documentation URL\n let rawDetails = finding.raw_details;\n if (finding.documentationUrl) {\n const docLine = `See examples: ${finding.documentationUrl}`;\n rawDetails = rawDetails ? `${docLine}\\n\\n${rawDetails}` : docLine;\n }\n\n return {\n path: finding.path,\n start_line: line,\n end_line: line,\n annotation_level: mapSeverity(finding.severity),\n message: `${finding.rule}: ${finding.message}`,\n raw_details: rawDetails?.slice(0, 64000),\n };\n });\n}\n\nfunction inferConclusion(findings: Finding[]): Conclusion {\n if (findings.some((f) => f.severity === 'must')) return 'failure';\n if (findings.some((f) => f.severity === 'should')) return 'neutral';\n return 'success';\n}\n\nfunction summarize(findings: Finding[]) {\n if (findings.length === 0) return 'No issues found.';\n const must = findings.filter((f) => f.severity === 'must').length;\n const should = findings.filter((f) => f.severity === 'should').length;\n const nit = findings.filter((f) => f.severity === 'nit').length;\n return `${must} must-fix, ${should} should-fix, ${nit} nit.`;\n}\n\nfunction mapSeverity(severity: Finding['severity']) {\n if (severity === 'must') return 'failure';\n if (severity === 'should') return 'warning';\n return 'notice';\n}\n\r\n"],"mappings":"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","names":["YAML","YAML"]}
|
package/package.json
CHANGED
|
@@ -1,58 +1,58 @@
|
|
|
1
|
-
|
|
2
|
-
"name":
|
|
3
|
-
"version":
|
|
4
|
-
"description":
|
|
5
|
-
"main":
|
|
6
|
-
"module":
|
|
7
|
-
"types":
|
|
8
|
-
"exports":
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
"files":
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
"scripts":
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
"keywords":
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
"license":
|
|
34
|
-
"repository":
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
"publishConfig":
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
"engines":
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
"dependencies":
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
"devDependencies":
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@replikanti/flowlint-core",
|
|
3
|
+
"version": "0.7.0",
|
|
4
|
+
"description": "Core linting engine for n8n workflows",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist/",
|
|
17
|
+
"README.md",
|
|
18
|
+
"LICENSE"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsup src/index.ts --format cjs,esm --dts",
|
|
22
|
+
"test": "vitest run",
|
|
23
|
+
"test:watch": "vitest",
|
|
24
|
+
"lint": "eslint src/",
|
|
25
|
+
"prepublishOnly": "npm run build"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"n8n",
|
|
29
|
+
"workflow",
|
|
30
|
+
"linter",
|
|
31
|
+
"static-analysis"
|
|
32
|
+
],
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "git+https://github.com/Replikanti/flowlint-core.git"
|
|
37
|
+
},
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"access": "public"
|
|
40
|
+
},
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=18"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"ajv": "^8.17.1",
|
|
46
|
+
"ajv-draft-04": "^1.0.0",
|
|
47
|
+
"ajv-formats": "^3.0.1",
|
|
48
|
+
"micromatch": "^4.0.8",
|
|
49
|
+
"yaml": "^2.4.0"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@types/micromatch": "^4.0.10",
|
|
53
|
+
"@types/node": "^22.0.0",
|
|
54
|
+
"tsup": "^8.0.0",
|
|
55
|
+
"typescript": "^5.4.0",
|
|
56
|
+
"vitest": "^4.0.8"
|
|
57
|
+
}
|
|
58
58
|
}
|