@replikanti/flowlint-core 0.9.1 → 0.9.3
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.js +26 -24
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +26 -24
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -883,6 +883,30 @@ var metadata7 = {
|
|
|
883
883
|
description: "Ensures critical paths include logging or alerting steps.",
|
|
884
884
|
details: "For example, a failed payment processing branch should trigger an alert for monitoring."
|
|
885
885
|
};
|
|
886
|
+
function isPathHandled(graph, startNodeId) {
|
|
887
|
+
const queue = [startNodeId];
|
|
888
|
+
const visited = /* @__PURE__ */ new Set([startNodeId]);
|
|
889
|
+
let head = 0;
|
|
890
|
+
while (head < queue.length) {
|
|
891
|
+
const currentId = queue[head++];
|
|
892
|
+
const currentNode = graph.nodes.find((n) => n.id === currentId);
|
|
893
|
+
if (!currentNode) continue;
|
|
894
|
+
if (isNotificationNode(currentNode.type) || isErrorHandlerNode(currentNode.type, currentNode.name)) {
|
|
895
|
+
return true;
|
|
896
|
+
}
|
|
897
|
+
if (isRejoinNode(graph, currentId)) {
|
|
898
|
+
continue;
|
|
899
|
+
}
|
|
900
|
+
const outgoing = graph.edges.filter((e) => e.from === currentId);
|
|
901
|
+
for (const outEdge of outgoing) {
|
|
902
|
+
if (!visited.has(outEdge.to)) {
|
|
903
|
+
visited.add(outEdge.to);
|
|
904
|
+
queue.push(outEdge.to);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
return false;
|
|
909
|
+
}
|
|
886
910
|
function r7AlertLogEnforcement(graph, ctx) {
|
|
887
911
|
const cfg = ctx.cfg.rules.alert_log_enforcement;
|
|
888
912
|
if (!cfg?.enabled) return [];
|
|
@@ -890,29 +914,7 @@ function r7AlertLogEnforcement(graph, ctx) {
|
|
|
890
914
|
const errorEdges = graph.edges.filter((edge) => edge.on === "error");
|
|
891
915
|
for (const edge of errorEdges) {
|
|
892
916
|
const fromNode = graph.nodes.find((n) => n.id === edge.from);
|
|
893
|
-
|
|
894
|
-
const queue = [edge.to];
|
|
895
|
-
const visited = /* @__PURE__ */ new Set([edge.to]);
|
|
896
|
-
let head = 0;
|
|
897
|
-
while (head < queue.length) {
|
|
898
|
-
const currentId = queue[head++];
|
|
899
|
-
const currentNode = graph.nodes.find((n) => n.id === currentId);
|
|
900
|
-
if (isNotificationNode(currentNode.type) || isErrorHandlerNode(currentNode.type, currentNode.name)) {
|
|
901
|
-
isHandled = true;
|
|
902
|
-
break;
|
|
903
|
-
}
|
|
904
|
-
if (isRejoinNode(graph, currentId)) {
|
|
905
|
-
continue;
|
|
906
|
-
}
|
|
907
|
-
const outgoing = graph.edges.filter((e) => e.from === currentId);
|
|
908
|
-
for (const outEdge of outgoing) {
|
|
909
|
-
if (!visited.has(outEdge.to)) {
|
|
910
|
-
visited.add(outEdge.to);
|
|
911
|
-
queue.push(outEdge.to);
|
|
912
|
-
}
|
|
913
|
-
}
|
|
914
|
-
}
|
|
915
|
-
if (!isHandled) {
|
|
917
|
+
if (!isPathHandled(graph, edge.to)) {
|
|
916
918
|
findings.push({
|
|
917
919
|
rule: metadata7.id,
|
|
918
920
|
severity: metadata7.severity,
|
|
@@ -1137,7 +1139,7 @@ var r14RetryAfterCompliance = createNodeRule(metadata14.id, metadata14.name, (no
|
|
|
1137
1139
|
const waitBetweenTries = node.flags?.waitBetweenTries;
|
|
1138
1140
|
if (waitBetweenTries !== void 0 && waitBetweenTries !== null) {
|
|
1139
1141
|
if (typeof waitBetweenTries === "number") return null;
|
|
1140
|
-
if (typeof waitBetweenTries === "string" && !isNaN(Number(waitBetweenTries)) && !waitBetweenTries.includes("{{")) {
|
|
1142
|
+
if (typeof waitBetweenTries === "string" && !Number.isNaN(Number(waitBetweenTries)) && !waitBetweenTries.includes("{{")) {
|
|
1141
1143
|
return null;
|
|
1142
1144
|
}
|
|
1143
1145
|
}
|
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/lib/r1-retry.ts","../src/rules/lib/r2-error-handling.ts","../src/rules/lib/r3-idempotency.ts","../src/rules/lib/r4-secrets.ts","../src/rules/lib/r5-dead-ends.ts","../src/rules/lib/r6-long-running.ts","../src/rules/lib/r7-alert-log-enforcement.ts","../src/rules/lib/r8-unused-data.ts","../src/rules/lib/r9-config-literals.ts","../src/rules/lib/r10-naming-convention.ts","../src/rules/lib/r11-deprecated-nodes.ts","../src/rules/lib/r12-unhandled-error-path.ts","../src/rules/lib/r13-webhook-acknowledgment.ts","../src/rules/lib/r14-retry-after-compliance.ts","../src/rules/index.ts","../src/rules/metadata.ts","../src/config/default-config.ts","../src/config/loader.ts","../src/utils/findings.ts","../src/reporter/reporter.ts"],"sourcesContent":["/**\r\n * @flowlint/core - Core linting engine for n8n workflows\r\n * \r\n * This package provides the core functionality for analyzing n8n workflows:\r\n * - Parsing n8n workflow JSON/YAML files\r\n * - Running linting rules\r\n * - Generating findings/reports\r\n * - Configuration management\r\n */\r\n\r\n// Parser\r\nexport { parseN8n } from './parser/parser-n8n';\r\n\r\n// Rules\r\nexport { runAllRules, RULES_METADATA, type RuleMetadata } from './rules/exports';\r\n\r\n// Schemas\r\nexport { validateN8nWorkflow, ValidationError } from './schemas';\r\n\r\n// Config\r\nexport { \r\n defaultConfig, \r\n loadConfig, \r\n parseConfig, \r\n validateConfig,\r\n type FlowLintConfig,\r\n type RuleConfig,\r\n type FilesConfig,\r\n type ReportConfig,\r\n} from './config';\r\n\r\n// Types\r\nexport type {\r\n Finding,\r\n FindingSeverity,\r\n Graph,\r\n NodeRef,\r\n Edge,\r\n RuleContext,\r\n RuleRunner,\r\n PRFile,\r\n} from './types';\r\n\r\n// Utils\r\nexport { \r\n flattenConnections, \r\n isErrorProneNode, \r\n getExampleLink,\r\n isApiNode,\r\n isMutationNode,\r\n isNotificationNode,\r\n isTerminalNode,\r\n} from './utils/utils';\r\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 { createNodeRule } from '../rule-utils';\nimport { isApiNode } from '../../utils/utils';\nimport type { RuleMetadata } from '../metadata';\n\nexport const metadata: RuleMetadata = {\n id: 'R1',\n name: 'rate_limit_retry',\n severity: 'must',\n description: 'Ensures that nodes making external API calls have a retry mechanism configured.',\n details: 'Critical for building resilient workflows that can handle transient network issues or temporary service unavailability.',\n};\n\nexport const r1Retry = createNodeRule(metadata.id, metadata.name, (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: metadata.id,\n severity: metadata.severity,\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","import { createNodeRule } from '../rule-utils';\nimport type { RuleMetadata } from '../metadata';\n\nexport const metadata: RuleMetadata = {\n id: 'R2',\n name: 'error_handling',\n severity: 'must',\n description: 'Prevents the use of configurations that might hide errors.',\n details: 'Workflows should explicitly handle errors rather than ignoring them with continueOnFail: true.',\n};\n\nexport const r2ErrorHandling = createNodeRule(metadata.id, metadata.name, (node, graph, ctx) => {\n if (ctx.cfg.rules.error_handling.forbid_continue_on_fail && node.flags?.continueOnFail) {\n return {\n rule: metadata.id,\n severity: metadata.severity,\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","import { isMutationNode, findAllUpstreamNodes, containsCandidate } from '../../utils/utils';\nimport type { Graph, Finding } from '../../types';\nimport type { RuleContext } from '../index';\nimport type { RuleMetadata } from '../metadata';\n\nexport const metadata: RuleMetadata = {\n id: 'R3',\n name: 'idempotency',\n severity: 'should',\n description: 'Guards against operations that are not idempotent with retries configured.',\n details: 'Detects patterns where a webhook trigger could lead to duplicate processing in databases or external services.',\n};\n\nexport function 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: metadata.id,\n severity: metadata.severity,\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","import { createHardcodedStringRule } from '../rule-utils';\nimport type { RuleMetadata } from '../metadata';\n\nexport const metadata: RuleMetadata = {\n id: 'R4',\n name: 'secrets',\n severity: 'must',\n description: 'Detects hardcoded secrets, API keys, or credentials within node parameters.',\n details: 'All secrets should be stored securely using credential management systems.',\n};\n\nexport const r4Secrets = createHardcodedStringRule({\n ruleId: metadata.id,\n severity: metadata.severity,\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","import { isTerminalNode } from '../../utils/utils';\nimport type { Graph, Finding } from '../../types';\nimport type { RuleContext } from '../index';\nimport type { RuleMetadata } from '../metadata';\n\nexport const metadata: RuleMetadata = {\n id: 'R5',\n name: 'dead_ends',\n severity: 'should',\n description: 'Finds nodes or workflow branches not connected to any other node.',\n details: 'Indicates incomplete or dead logic that should be reviewed or removed.',\n};\n\nexport function 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\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: metadata.id,\n severity: metadata.severity,\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","import { readNumber } from '../../utils/utils';\nimport type { Graph, Finding } from '../../types';\nimport type { RuleContext } from '../index';\nimport type { RuleMetadata } from '../metadata';\n\nexport const metadata: RuleMetadata = {\n id: 'R6',\n name: 'long_running',\n severity: 'should',\n description: 'Flags workflows with potential for excessive runtime.',\n details: 'Detects loops with high iteration counts or long timeouts that could cause performance issues.',\n};\n\nexport function 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: metadata.id,\n severity: metadata.severity,\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: metadata.id,\n severity: metadata.severity,\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","import { isNotificationNode, isErrorHandlerNode, isRejoinNode } from '../../utils/utils';\nimport type { Graph, Finding } from '../../types';\nimport type { RuleContext } from '../index';\nimport type { RuleMetadata } from '../metadata';\n\nexport const metadata: RuleMetadata = {\n id: 'R7',\n name: 'alert_log_enforcement',\n severity: 'should',\n description: 'Ensures critical paths include logging or alerting steps.',\n details: 'For example, a failed payment processing branch should trigger an alert for monitoring.',\n};\n\nexport function 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: metadata.id,\n severity: metadata.severity,\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","import { isTerminalNode, isMeaningfulConsumer, findAllDownstreamNodes } from '../../utils/utils';\nimport type { Graph, Finding } from '../../types';\nimport type { RuleContext } from '../index';\nimport type { RuleMetadata } from '../metadata';\n\nexport const metadata: RuleMetadata = {\n id: 'R8',\n name: 'unused_data',\n severity: 'nit',\n description: 'Detects when node output data is not consumed by subsequent nodes.',\n details: 'Identifies unnecessary data processing that could be optimized or removed.',\n};\n\nexport function 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: metadata.id,\n severity: metadata.severity,\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","import { createHardcodedStringRule } from '../rule-utils';\nimport type { RuleMetadata } from '../metadata';\n\nexport const metadata: RuleMetadata = {\n id: 'R9',\n name: 'config_literals',\n severity: 'should',\n description: 'Flags hardcoded literals (URLs, environment tags, tenant IDs) that should come from configuration.',\n details: 'Promotes externalized configuration and prevents hardcoded environment-specific values.',\n};\n\nexport const r9ConfigLiterals = createHardcodedStringRule({\n ruleId: metadata.id,\n severity: metadata.severity,\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","import { createNodeRule } from '../rule-utils';\nimport type { RuleMetadata } from '../metadata';\n\nexport const metadata: RuleMetadata = {\n id: 'R10',\n name: 'naming_convention',\n severity: 'nit',\n description: 'Enforces consistent and descriptive naming for nodes.',\n details: \"Enforces consistent and descriptive naming for nodes. Improves workflow readability and maintainability (e.g., 'Fetch Customer Data from CRM' vs 'HTTP Request').\",\n};\n\nexport const r10NamingConvention = createNodeRule(metadata.id, metadata.name, (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: metadata.id,\n severity: metadata.severity,\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","import { createNodeRule } from '../rule-utils';\nimport type { RuleMetadata } from '../metadata';\n\nexport const metadata: RuleMetadata = {\n id: 'R11',\n name: 'deprecated_nodes',\n severity: 'should',\n description: 'Warns about deprecated node types and suggests alternatives.',\n details: 'Helps maintain workflows using current, supported node implementations.',\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\nexport const r11DeprecatedNodes = createNodeRule(metadata.id, metadata.name, (node, graph, ctx) => {\n if (DEPRECATED_NODES[node.type]) {\n return {\n rule: metadata.id,\n severity: metadata.severity,\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","import { createNodeRule } from '../rule-utils';\nimport { isErrorProneNode, isErrorHandlerNode } from '../../utils/utils';\nimport type { RuleMetadata } from '../metadata';\n\nexport const metadata: RuleMetadata = {\n id: 'R12',\n name: 'unhandled_error_path',\n severity: 'must',\n description: 'Ensures nodes with error outputs have connected error handling branches.',\n details: 'Prevents silent failures by requiring explicit error path handling.',\n};\n\nexport const r12UnhandledErrorPath = createNodeRule(metadata.id, metadata.name, (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: metadata.id,\n severity: metadata.severity,\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","import type { Graph, Finding, NodeRef } from '../../types';\nimport type { RuleContext } from '../index';\nimport type { RuleMetadata } from '../metadata';\n\nexport const metadata: RuleMetadata = {\n id: 'R13',\n name: 'webhook_acknowledgment',\n severity: 'must',\n description: 'Detects webhooks performing heavy processing without immediate acknowledgment.',\n details: \"Prevents timeout and duplicate events by requiring 'Respond to Webhook' node before heavy operations (HTTP requests, database queries, AI/LLM calls).\",\n};\n\nexport function 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: metadata.id,\n severity: metadata.severity,\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","import { createNodeRule } from '../rule-utils';\nimport { isApiNode } from '../../utils/utils';\nimport type { RuleMetadata } from '../metadata';\n\nexport const metadata: RuleMetadata = {\n id: 'R14',\n name: 'retry_after_compliance',\n severity: 'should',\n description: 'Detects HTTP nodes with retry logic that ignore Retry-After headers from 429/503 responses.',\n details: 'APIs return Retry-After headers (seconds or HTTP date) to indicate when to retry. Ignoring these causes aggressive retry storms, wasted attempts, and potential API bans. Respecting server guidance prevents IP blocking and extended backoffs.',\n};\n\nexport const r14RetryAfterCompliance = createNodeRule(metadata.id, metadata.name, (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: metadata.id,\n severity: metadata.severity,\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","import type { Graph, Finding } from '../types';\r\nimport type { FlowLintConfig } from '../config';\r\n\r\nimport { r1Retry } from './lib/r1-retry';\r\nimport { r2ErrorHandling } from './lib/r2-error-handling';\r\nimport { r3Idempotency } from './lib/r3-idempotency';\r\nimport { r4Secrets } from './lib/r4-secrets';\r\nimport { r5DeadEnds } from './lib/r5-dead-ends';\r\nimport { r6LongRunning } from './lib/r6-long-running';\r\nimport { r7AlertLogEnforcement } from './lib/r7-alert-log-enforcement';\r\nimport { r8UnusedData } from './lib/r8-unused-data';\r\nimport { r9ConfigLiterals } from './lib/r9-config-literals';\r\nimport { r10NamingConvention } from './lib/r10-naming-convention';\r\nimport { r11DeprecatedNodes } from './lib/r11-deprecated-nodes';\r\nimport { r12UnhandledErrorPath } from './lib/r12-unhandled-error-path';\r\nimport { r13WebhookAcknowledgment } from './lib/r13-webhook-acknowledgment';\r\nimport { r14RetryAfterCompliance } from './lib/r14-retry-after-compliance';\r\n\r\nexport type RuleContext = { path: string; cfg: FlowLintConfig; nodeLines?: Record<string, number> };\r\n\r\ntype RuleRunner = (graph: Graph, ctx: RuleContext) => Finding[];\r\n\r\nconst rules: RuleRunner[] = [\r\n r1Retry,\r\n r2ErrorHandling,\r\n r3Idempotency,\r\n r4Secrets,\r\n r5DeadEnds,\r\n r6LongRunning,\r\n r7AlertLogEnforcement,\r\n r8UnusedData,\r\n r9ConfigLiterals,\r\n r10NamingConvention,\r\n r11DeprecatedNodes,\r\n r12UnhandledErrorPath,\r\n r13WebhookAcknowledgment,\r\n r14RetryAfterCompliance,\r\n];\r\n\r\nexport function runAllRules(graph: Graph, ctx: RuleContext): Finding[] {\r\n return rules.flatMap((rule) => rule(graph, ctx));\r\n}\r\n\r\n","import { metadata as r1 } from './lib/r1-retry';\nimport { metadata as r2 } from './lib/r2-error-handling';\nimport { metadata as r3 } from './lib/r3-idempotency';\nimport { metadata as r4 } from './lib/r4-secrets';\nimport { metadata as r5 } from './lib/r5-dead-ends';\nimport { metadata as r6 } from './lib/r6-long-running';\nimport { metadata as r7 } from './lib/r7-alert-log-enforcement';\nimport { metadata as r8 } from './lib/r8-unused-data';\nimport { metadata as r9 } from './lib/r9-config-literals';\nimport { metadata as r10 } from './lib/r10-naming-convention';\nimport { metadata as r11 } from './lib/r11-deprecated-nodes';\nimport { metadata as r12 } from './lib/r12-unhandled-error-path';\nimport { metadata as r13 } from './lib/r13-webhook-acknowledgment';\nimport { metadata as r14 } from './lib/r14-retry-after-compliance';\n\nexport interface RuleMetadata {\n id: string;\n name: string;\n severity: 'must' | 'should' | 'nit';\n description: string;\n details: string;\n}\n\nexport const RULES_METADATA: RuleMetadata[] = [\n r1,\n r2,\n r3,\n r4,\n r5,\n r6,\n r7,\n r8,\n r9,\n r10,\n r11,\n r12,\n r13,\n r14,\n];\n\n\n\n\n\n\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","metadata","metadata","metadata","metadata","metadata","metadata","metadata","metadata","metadata","metadata","metadata","metadata","metadata","metadata","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/lib/r1-retry.ts","../src/rules/lib/r2-error-handling.ts","../src/rules/lib/r3-idempotency.ts","../src/rules/lib/r4-secrets.ts","../src/rules/lib/r5-dead-ends.ts","../src/rules/lib/r6-long-running.ts","../src/rules/lib/r7-alert-log-enforcement.ts","../src/rules/lib/r8-unused-data.ts","../src/rules/lib/r9-config-literals.ts","../src/rules/lib/r10-naming-convention.ts","../src/rules/lib/r11-deprecated-nodes.ts","../src/rules/lib/r12-unhandled-error-path.ts","../src/rules/lib/r13-webhook-acknowledgment.ts","../src/rules/lib/r14-retry-after-compliance.ts","../src/rules/index.ts","../src/rules/metadata.ts","../src/config/default-config.ts","../src/config/loader.ts","../src/utils/findings.ts","../src/reporter/reporter.ts"],"sourcesContent":["/**\r\n * @flowlint/core - Core linting engine for n8n workflows\r\n * \r\n * This package provides the core functionality for analyzing n8n workflows:\r\n * - Parsing n8n workflow JSON/YAML files\r\n * - Running linting rules\r\n * - Generating findings/reports\r\n * - Configuration management\r\n */\r\n\r\n// Parser\r\nexport { parseN8n } from './parser/parser-n8n';\r\n\r\n// Rules\r\nexport { runAllRules, RULES_METADATA, type RuleMetadata } from './rules/exports';\r\n\r\n// Schemas\r\nexport { validateN8nWorkflow, ValidationError } from './schemas';\r\n\r\n// Config\r\nexport { \r\n defaultConfig, \r\n loadConfig, \r\n parseConfig, \r\n validateConfig,\r\n type FlowLintConfig,\r\n type RuleConfig,\r\n type FilesConfig,\r\n type ReportConfig,\r\n} from './config';\r\n\r\n// Types\r\nexport type {\r\n Finding,\r\n FindingSeverity,\r\n Graph,\r\n NodeRef,\r\n Edge,\r\n RuleContext,\r\n RuleRunner,\r\n PRFile,\r\n} from './types';\r\n\r\n// Utils\r\nexport { \r\n flattenConnections, \r\n isErrorProneNode, \r\n getExampleLink,\r\n isApiNode,\r\n isMutationNode,\r\n isNotificationNode,\r\n isTerminalNode,\r\n} from './utils/utils';\r\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 { createNodeRule } from '../rule-utils';\nimport { isApiNode } from '../../utils/utils';\nimport type { RuleMetadata } from '../metadata';\n\nexport const metadata: RuleMetadata = {\n id: 'R1',\n name: 'rate_limit_retry',\n severity: 'must',\n description: 'Ensures that nodes making external API calls have a retry mechanism configured.',\n details: 'Critical for building resilient workflows that can handle transient network issues or temporary service unavailability.',\n};\n\nexport const r1Retry = createNodeRule(metadata.id, metadata.name, (node, graph, ctx) => {\n if (!isApiNode(node.type)) return null;\n\n const params = (node.params ?? {});\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: metadata.id,\n severity: metadata.severity,\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","import { createNodeRule } from '../rule-utils';\nimport type { RuleMetadata } from '../metadata';\n\nexport const metadata: RuleMetadata = {\n id: 'R2',\n name: 'error_handling',\n severity: 'must',\n description: 'Prevents the use of configurations that might hide errors.',\n details: 'Workflows should explicitly handle errors rather than ignoring them with continueOnFail: true.',\n};\n\nexport const r2ErrorHandling = createNodeRule(metadata.id, metadata.name, (node, graph, ctx) => {\n if (ctx.cfg.rules.error_handling.forbid_continue_on_fail && node.flags?.continueOnFail) {\n return {\n rule: metadata.id,\n severity: metadata.severity,\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","import { isMutationNode, findAllUpstreamNodes, containsCandidate } from '../../utils/utils';\nimport type { Graph, Finding } from '../../types';\nimport type { RuleContext } from '../index';\nimport type { RuleMetadata } from '../metadata';\n\nexport const metadata: RuleMetadata = {\n id: 'R3',\n name: 'idempotency',\n severity: 'should',\n description: 'Guards against operations that are not idempotent with retries configured.',\n details: 'Detects patterns where a webhook trigger could lead to duplicate processing in databases or external services.',\n};\n\nexport function 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: metadata.id,\n severity: metadata.severity,\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","import { createHardcodedStringRule } from '../rule-utils';\nimport type { RuleMetadata } from '../metadata';\n\nexport const metadata: RuleMetadata = {\n id: 'R4',\n name: 'secrets',\n severity: 'must',\n description: 'Detects hardcoded secrets, API keys, or credentials within node parameters.',\n details: 'All secrets should be stored securely using credential management systems.',\n};\n\nexport const r4Secrets = createHardcodedStringRule({\n ruleId: metadata.id,\n severity: metadata.severity,\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","import { isTerminalNode } from '../../utils/utils';\nimport type { Graph, Finding } from '../../types';\nimport type { RuleContext } from '../index';\nimport type { RuleMetadata } from '../metadata';\n\nexport const metadata: RuleMetadata = {\n id: 'R5',\n name: 'dead_ends',\n severity: 'should',\n description: 'Finds nodes or workflow branches not connected to any other node.',\n details: 'Indicates incomplete or dead logic that should be reviewed or removed.',\n};\n\nexport function 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\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: metadata.id,\n severity: metadata.severity,\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","import { readNumber } from '../../utils/utils';\nimport type { Graph, Finding } from '../../types';\nimport type { RuleContext } from '../index';\nimport type { RuleMetadata } from '../metadata';\n\nexport const metadata: RuleMetadata = {\n id: 'R6',\n name: 'long_running',\n severity: 'should',\n description: 'Flags workflows with potential for excessive runtime.',\n details: 'Detects loops with high iteration counts or long timeouts that could cause performance issues.',\n};\n\nexport function 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: metadata.id,\n severity: metadata.severity,\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: metadata.id,\n severity: metadata.severity,\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","import { isNotificationNode, isErrorHandlerNode, isRejoinNode } from '../../utils/utils';\nimport type { Graph, Finding } from '../../types';\nimport type { RuleContext } from '../index';\nimport type { RuleMetadata } from '../metadata';\n\nexport const metadata: RuleMetadata = {\n id: 'R7',\n name: 'alert_log_enforcement',\n severity: 'should',\n description: 'Ensures critical paths include logging or alerting steps.',\n details: 'For example, a failed payment processing branch should trigger an alert for monitoring.',\n};\n\nfunction isPathHandled(graph: Graph, startNodeId: string): boolean {\n const queue: string[] = [startNodeId];\n const visited = new Set<string>([startNodeId]);\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 (!currentNode) continue;\n\n if (isNotificationNode(currentNode.type) || isErrorHandlerNode(currentNode.type, currentNode.name)) {\n return true;\n }\n\n if (isRejoinNode(graph, currentId)) {\n continue;\n }\n\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 return false;\n}\n\nexport function 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 if (!isPathHandled(graph, edge.to)) {\n findings.push({\n rule: metadata.id,\n severity: metadata.severity,\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","import { isTerminalNode, isMeaningfulConsumer, findAllDownstreamNodes } from '../../utils/utils';\nimport type { Graph, Finding } from '../../types';\nimport type { RuleContext } from '../index';\nimport type { RuleMetadata } from '../metadata';\n\nexport const metadata: RuleMetadata = {\n id: 'R8',\n name: 'unused_data',\n severity: 'nit',\n description: 'Detects when node output data is not consumed by subsequent nodes.',\n details: 'Identifies unnecessary data processing that could be optimized or removed.',\n};\n\nexport function 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: metadata.id,\n severity: metadata.severity,\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","import { createHardcodedStringRule } from '../rule-utils';\nimport type { RuleMetadata } from '../metadata';\n\nexport const metadata: RuleMetadata = {\n id: 'R9',\n name: 'config_literals',\n severity: 'should',\n description: 'Flags hardcoded literals (URLs, environment tags, tenant IDs) that should come from configuration.',\n details: 'Promotes externalized configuration and prevents hardcoded environment-specific values.',\n};\n\nexport const r9ConfigLiterals = createHardcodedStringRule({\n ruleId: metadata.id,\n severity: metadata.severity,\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","import { createNodeRule } from '../rule-utils';\nimport type { RuleMetadata } from '../metadata';\n\nexport const metadata: RuleMetadata = {\n id: 'R10',\n name: 'naming_convention',\n severity: 'nit',\n description: 'Enforces consistent and descriptive naming for nodes.',\n details: \"Enforces consistent and descriptive naming for nodes. Improves workflow readability and maintainability (e.g., 'Fetch Customer Data from CRM' vs 'HTTP Request').\",\n};\n\nexport const r10NamingConvention = createNodeRule(metadata.id, metadata.name, (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: metadata.id,\n severity: metadata.severity,\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","import { createNodeRule } from '../rule-utils';\nimport type { RuleMetadata } from '../metadata';\n\nexport const metadata: RuleMetadata = {\n id: 'R11',\n name: 'deprecated_nodes',\n severity: 'should',\n description: 'Warns about deprecated node types and suggests alternatives.',\n details: 'Helps maintain workflows using current, supported node implementations.',\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\nexport const r11DeprecatedNodes = createNodeRule(metadata.id, metadata.name, (node, graph, ctx) => {\n if (DEPRECATED_NODES[node.type]) {\n return {\n rule: metadata.id,\n severity: metadata.severity,\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","import { createNodeRule } from '../rule-utils';\nimport { isErrorProneNode, isErrorHandlerNode } from '../../utils/utils';\nimport type { RuleMetadata } from '../metadata';\n\nexport const metadata: RuleMetadata = {\n id: 'R12',\n name: 'unhandled_error_path',\n severity: 'must',\n description: 'Ensures nodes with error outputs have connected error handling branches.',\n details: 'Prevents silent failures by requiring explicit error path handling.',\n};\n\nexport const r12UnhandledErrorPath = createNodeRule(metadata.id, metadata.name, (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: metadata.id,\n severity: metadata.severity,\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","import type { Graph, Finding, NodeRef } from '../../types';\nimport type { RuleContext } from '../index';\nimport type { RuleMetadata } from '../metadata';\n\nexport const metadata: RuleMetadata = {\n id: 'R13',\n name: 'webhook_acknowledgment',\n severity: 'must',\n description: 'Detects webhooks performing heavy processing without immediate acknowledgment.',\n details: \"Prevents timeout and duplicate events by requiring 'Respond to Webhook' node before heavy operations (HTTP requests, database queries, AI/LLM calls).\",\n};\n\nexport function 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: metadata.id,\n severity: metadata.severity,\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","import { createNodeRule } from '../rule-utils';\nimport { isApiNode } from '../../utils/utils';\nimport type { RuleMetadata } from '../metadata';\n\nexport const metadata: RuleMetadata = {\n id: 'R14',\n name: 'retry_after_compliance',\n severity: 'should',\n description: 'Detects HTTP nodes with retry logic that ignore Retry-After headers from 429/503 responses.',\n details: 'APIs return Retry-After headers (seconds or HTTP date) to indicate when to retry. Ignoring these causes aggressive retry storms, wasted attempts, and potential API bans. Respecting server guidance prevents IP blocking and extended backoffs.',\n};\n\nexport const r14RetryAfterCompliance = createNodeRule(metadata.id, metadata.name, (node, graph, ctx) => {\n // Only check HTTP request nodes\n if (!isApiNode(node.type)) return null;\n\n const params = (node.params ?? {});\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 !Number.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: metadata.id,\n severity: metadata.severity,\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","import type { Graph, Finding } from '../types';\r\nimport type { FlowLintConfig } from '../config';\r\n\r\nimport { r1Retry } from './lib/r1-retry';\r\nimport { r2ErrorHandling } from './lib/r2-error-handling';\r\nimport { r3Idempotency } from './lib/r3-idempotency';\r\nimport { r4Secrets } from './lib/r4-secrets';\r\nimport { r5DeadEnds } from './lib/r5-dead-ends';\r\nimport { r6LongRunning } from './lib/r6-long-running';\r\nimport { r7AlertLogEnforcement } from './lib/r7-alert-log-enforcement';\r\nimport { r8UnusedData } from './lib/r8-unused-data';\r\nimport { r9ConfigLiterals } from './lib/r9-config-literals';\r\nimport { r10NamingConvention } from './lib/r10-naming-convention';\r\nimport { r11DeprecatedNodes } from './lib/r11-deprecated-nodes';\r\nimport { r12UnhandledErrorPath } from './lib/r12-unhandled-error-path';\r\nimport { r13WebhookAcknowledgment } from './lib/r13-webhook-acknowledgment';\r\nimport { r14RetryAfterCompliance } from './lib/r14-retry-after-compliance';\r\n\r\nexport type RuleContext = { path: string; cfg: FlowLintConfig; nodeLines?: Record<string, number> };\r\n\r\ntype RuleRunner = (graph: Graph, ctx: RuleContext) => Finding[];\r\n\r\nconst rules: RuleRunner[] = [\r\n r1Retry,\r\n r2ErrorHandling,\r\n r3Idempotency,\r\n r4Secrets,\r\n r5DeadEnds,\r\n r6LongRunning,\r\n r7AlertLogEnforcement,\r\n r8UnusedData,\r\n r9ConfigLiterals,\r\n r10NamingConvention,\r\n r11DeprecatedNodes,\r\n r12UnhandledErrorPath,\r\n r13WebhookAcknowledgment,\r\n r14RetryAfterCompliance,\r\n];\r\n\r\nexport function runAllRules(graph: Graph, ctx: RuleContext): Finding[] {\r\n return rules.flatMap((rule) => rule(graph, ctx));\r\n}\r\n\r\n","import { metadata as r1 } from './lib/r1-retry';\nimport { metadata as r2 } from './lib/r2-error-handling';\nimport { metadata as r3 } from './lib/r3-idempotency';\nimport { metadata as r4 } from './lib/r4-secrets';\nimport { metadata as r5 } from './lib/r5-dead-ends';\nimport { metadata as r6 } from './lib/r6-long-running';\nimport { metadata as r7 } from './lib/r7-alert-log-enforcement';\nimport { metadata as r8 } from './lib/r8-unused-data';\nimport { metadata as r9 } from './lib/r9-config-literals';\nimport { metadata as r10 } from './lib/r10-naming-convention';\nimport { metadata as r11 } from './lib/r11-deprecated-nodes';\nimport { metadata as r12 } from './lib/r12-unhandled-error-path';\nimport { metadata as r13 } from './lib/r13-webhook-acknowledgment';\nimport { metadata as r14 } from './lib/r14-retry-after-compliance';\n\nexport interface RuleMetadata {\n id: string;\n name: string;\n severity: 'must' | 'should' | 'nit';\n description: string;\n details: string;\n}\n\nexport const RULES_METADATA: RuleMetadata[] = [\n r1,\n r2,\n r3,\n r4,\n r5,\n r6,\n r7,\n r8,\n r9,\n r10,\n r11,\n r12,\n r13,\n r14,\n];\n\n\n\n\n\n\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","metadata","metadata","metadata","metadata","metadata","metadata","metadata","metadata","metadata","metadata","metadata","metadata","metadata","metadata","import_yaml","YAML"]}
|
package/dist/index.mjs
CHANGED
|
@@ -835,6 +835,30 @@ var metadata7 = {
|
|
|
835
835
|
description: "Ensures critical paths include logging or alerting steps.",
|
|
836
836
|
details: "For example, a failed payment processing branch should trigger an alert for monitoring."
|
|
837
837
|
};
|
|
838
|
+
function isPathHandled(graph, startNodeId) {
|
|
839
|
+
const queue = [startNodeId];
|
|
840
|
+
const visited = /* @__PURE__ */ new Set([startNodeId]);
|
|
841
|
+
let head = 0;
|
|
842
|
+
while (head < queue.length) {
|
|
843
|
+
const currentId = queue[head++];
|
|
844
|
+
const currentNode = graph.nodes.find((n) => n.id === currentId);
|
|
845
|
+
if (!currentNode) continue;
|
|
846
|
+
if (isNotificationNode(currentNode.type) || isErrorHandlerNode(currentNode.type, currentNode.name)) {
|
|
847
|
+
return true;
|
|
848
|
+
}
|
|
849
|
+
if (isRejoinNode(graph, currentId)) {
|
|
850
|
+
continue;
|
|
851
|
+
}
|
|
852
|
+
const outgoing = graph.edges.filter((e) => e.from === currentId);
|
|
853
|
+
for (const outEdge of outgoing) {
|
|
854
|
+
if (!visited.has(outEdge.to)) {
|
|
855
|
+
visited.add(outEdge.to);
|
|
856
|
+
queue.push(outEdge.to);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
return false;
|
|
861
|
+
}
|
|
838
862
|
function r7AlertLogEnforcement(graph, ctx) {
|
|
839
863
|
const cfg = ctx.cfg.rules.alert_log_enforcement;
|
|
840
864
|
if (!cfg?.enabled) return [];
|
|
@@ -842,29 +866,7 @@ function r7AlertLogEnforcement(graph, ctx) {
|
|
|
842
866
|
const errorEdges = graph.edges.filter((edge) => edge.on === "error");
|
|
843
867
|
for (const edge of errorEdges) {
|
|
844
868
|
const fromNode = graph.nodes.find((n) => n.id === edge.from);
|
|
845
|
-
|
|
846
|
-
const queue = [edge.to];
|
|
847
|
-
const visited = /* @__PURE__ */ new Set([edge.to]);
|
|
848
|
-
let head = 0;
|
|
849
|
-
while (head < queue.length) {
|
|
850
|
-
const currentId = queue[head++];
|
|
851
|
-
const currentNode = graph.nodes.find((n) => n.id === currentId);
|
|
852
|
-
if (isNotificationNode(currentNode.type) || isErrorHandlerNode(currentNode.type, currentNode.name)) {
|
|
853
|
-
isHandled = true;
|
|
854
|
-
break;
|
|
855
|
-
}
|
|
856
|
-
if (isRejoinNode(graph, currentId)) {
|
|
857
|
-
continue;
|
|
858
|
-
}
|
|
859
|
-
const outgoing = graph.edges.filter((e) => e.from === currentId);
|
|
860
|
-
for (const outEdge of outgoing) {
|
|
861
|
-
if (!visited.has(outEdge.to)) {
|
|
862
|
-
visited.add(outEdge.to);
|
|
863
|
-
queue.push(outEdge.to);
|
|
864
|
-
}
|
|
865
|
-
}
|
|
866
|
-
}
|
|
867
|
-
if (!isHandled) {
|
|
869
|
+
if (!isPathHandled(graph, edge.to)) {
|
|
868
870
|
findings.push({
|
|
869
871
|
rule: metadata7.id,
|
|
870
872
|
severity: metadata7.severity,
|
|
@@ -1089,7 +1091,7 @@ var r14RetryAfterCompliance = createNodeRule(metadata14.id, metadata14.name, (no
|
|
|
1089
1091
|
const waitBetweenTries = node.flags?.waitBetweenTries;
|
|
1090
1092
|
if (waitBetweenTries !== void 0 && waitBetweenTries !== null) {
|
|
1091
1093
|
if (typeof waitBetweenTries === "number") return null;
|
|
1092
|
-
if (typeof waitBetweenTries === "string" && !isNaN(Number(waitBetweenTries)) && !waitBetweenTries.includes("{{")) {
|
|
1094
|
+
if (typeof waitBetweenTries === "string" && !Number.isNaN(Number(waitBetweenTries)) && !waitBetweenTries.includes("{{")) {
|
|
1093
1095
|
return null;
|
|
1094
1096
|
}
|
|
1095
1097
|
}
|
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/lib/r1-retry.ts","../src/rules/lib/r2-error-handling.ts","../src/rules/lib/r3-idempotency.ts","../src/rules/lib/r4-secrets.ts","../src/rules/lib/r5-dead-ends.ts","../src/rules/lib/r6-long-running.ts","../src/rules/lib/r7-alert-log-enforcement.ts","../src/rules/lib/r8-unused-data.ts","../src/rules/lib/r9-config-literals.ts","../src/rules/lib/r10-naming-convention.ts","../src/rules/lib/r11-deprecated-nodes.ts","../src/rules/lib/r12-unhandled-error-path.ts","../src/rules/lib/r13-webhook-acknowledgment.ts","../src/rules/lib/r14-retry-after-compliance.ts","../src/rules/index.ts","../src/rules/metadata.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 { createNodeRule } from '../rule-utils';\nimport { isApiNode } from '../../utils/utils';\nimport type { RuleMetadata } from '../metadata';\n\nexport const metadata: RuleMetadata = {\n id: 'R1',\n name: 'rate_limit_retry',\n severity: 'must',\n description: 'Ensures that nodes making external API calls have a retry mechanism configured.',\n details: 'Critical for building resilient workflows that can handle transient network issues or temporary service unavailability.',\n};\n\nexport const r1Retry = createNodeRule(metadata.id, metadata.name, (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: metadata.id,\n severity: metadata.severity,\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","import { createNodeRule } from '../rule-utils';\nimport type { RuleMetadata } from '../metadata';\n\nexport const metadata: RuleMetadata = {\n id: 'R2',\n name: 'error_handling',\n severity: 'must',\n description: 'Prevents the use of configurations that might hide errors.',\n details: 'Workflows should explicitly handle errors rather than ignoring them with continueOnFail: true.',\n};\n\nexport const r2ErrorHandling = createNodeRule(metadata.id, metadata.name, (node, graph, ctx) => {\n if (ctx.cfg.rules.error_handling.forbid_continue_on_fail && node.flags?.continueOnFail) {\n return {\n rule: metadata.id,\n severity: metadata.severity,\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","import { isMutationNode, findAllUpstreamNodes, containsCandidate } from '../../utils/utils';\nimport type { Graph, Finding } from '../../types';\nimport type { RuleContext } from '../index';\nimport type { RuleMetadata } from '../metadata';\n\nexport const metadata: RuleMetadata = {\n id: 'R3',\n name: 'idempotency',\n severity: 'should',\n description: 'Guards against operations that are not idempotent with retries configured.',\n details: 'Detects patterns where a webhook trigger could lead to duplicate processing in databases or external services.',\n};\n\nexport function 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: metadata.id,\n severity: metadata.severity,\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","import { createHardcodedStringRule } from '../rule-utils';\nimport type { RuleMetadata } from '../metadata';\n\nexport const metadata: RuleMetadata = {\n id: 'R4',\n name: 'secrets',\n severity: 'must',\n description: 'Detects hardcoded secrets, API keys, or credentials within node parameters.',\n details: 'All secrets should be stored securely using credential management systems.',\n};\n\nexport const r4Secrets = createHardcodedStringRule({\n ruleId: metadata.id,\n severity: metadata.severity,\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","import { isTerminalNode } from '../../utils/utils';\nimport type { Graph, Finding } from '../../types';\nimport type { RuleContext } from '../index';\nimport type { RuleMetadata } from '../metadata';\n\nexport const metadata: RuleMetadata = {\n id: 'R5',\n name: 'dead_ends',\n severity: 'should',\n description: 'Finds nodes or workflow branches not connected to any other node.',\n details: 'Indicates incomplete or dead logic that should be reviewed or removed.',\n};\n\nexport function 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\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: metadata.id,\n severity: metadata.severity,\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","import { readNumber } from '../../utils/utils';\nimport type { Graph, Finding } from '../../types';\nimport type { RuleContext } from '../index';\nimport type { RuleMetadata } from '../metadata';\n\nexport const metadata: RuleMetadata = {\n id: 'R6',\n name: 'long_running',\n severity: 'should',\n description: 'Flags workflows with potential for excessive runtime.',\n details: 'Detects loops with high iteration counts or long timeouts that could cause performance issues.',\n};\n\nexport function 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: metadata.id,\n severity: metadata.severity,\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: metadata.id,\n severity: metadata.severity,\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","import { isNotificationNode, isErrorHandlerNode, isRejoinNode } from '../../utils/utils';\nimport type { Graph, Finding } from '../../types';\nimport type { RuleContext } from '../index';\nimport type { RuleMetadata } from '../metadata';\n\nexport const metadata: RuleMetadata = {\n id: 'R7',\n name: 'alert_log_enforcement',\n severity: 'should',\n description: 'Ensures critical paths include logging or alerting steps.',\n details: 'For example, a failed payment processing branch should trigger an alert for monitoring.',\n};\n\nexport function 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: metadata.id,\n severity: metadata.severity,\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","import { isTerminalNode, isMeaningfulConsumer, findAllDownstreamNodes } from '../../utils/utils';\nimport type { Graph, Finding } from '../../types';\nimport type { RuleContext } from '../index';\nimport type { RuleMetadata } from '../metadata';\n\nexport const metadata: RuleMetadata = {\n id: 'R8',\n name: 'unused_data',\n severity: 'nit',\n description: 'Detects when node output data is not consumed by subsequent nodes.',\n details: 'Identifies unnecessary data processing that could be optimized or removed.',\n};\n\nexport function 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: metadata.id,\n severity: metadata.severity,\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","import { createHardcodedStringRule } from '../rule-utils';\nimport type { RuleMetadata } from '../metadata';\n\nexport const metadata: RuleMetadata = {\n id: 'R9',\n name: 'config_literals',\n severity: 'should',\n description: 'Flags hardcoded literals (URLs, environment tags, tenant IDs) that should come from configuration.',\n details: 'Promotes externalized configuration and prevents hardcoded environment-specific values.',\n};\n\nexport const r9ConfigLiterals = createHardcodedStringRule({\n ruleId: metadata.id,\n severity: metadata.severity,\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","import { createNodeRule } from '../rule-utils';\nimport type { RuleMetadata } from '../metadata';\n\nexport const metadata: RuleMetadata = {\n id: 'R10',\n name: 'naming_convention',\n severity: 'nit',\n description: 'Enforces consistent and descriptive naming for nodes.',\n details: \"Enforces consistent and descriptive naming for nodes. Improves workflow readability and maintainability (e.g., 'Fetch Customer Data from CRM' vs 'HTTP Request').\",\n};\n\nexport const r10NamingConvention = createNodeRule(metadata.id, metadata.name, (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: metadata.id,\n severity: metadata.severity,\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","import { createNodeRule } from '../rule-utils';\nimport type { RuleMetadata } from '../metadata';\n\nexport const metadata: RuleMetadata = {\n id: 'R11',\n name: 'deprecated_nodes',\n severity: 'should',\n description: 'Warns about deprecated node types and suggests alternatives.',\n details: 'Helps maintain workflows using current, supported node implementations.',\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\nexport const r11DeprecatedNodes = createNodeRule(metadata.id, metadata.name, (node, graph, ctx) => {\n if (DEPRECATED_NODES[node.type]) {\n return {\n rule: metadata.id,\n severity: metadata.severity,\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","import { createNodeRule } from '../rule-utils';\nimport { isErrorProneNode, isErrorHandlerNode } from '../../utils/utils';\nimport type { RuleMetadata } from '../metadata';\n\nexport const metadata: RuleMetadata = {\n id: 'R12',\n name: 'unhandled_error_path',\n severity: 'must',\n description: 'Ensures nodes with error outputs have connected error handling branches.',\n details: 'Prevents silent failures by requiring explicit error path handling.',\n};\n\nexport const r12UnhandledErrorPath = createNodeRule(metadata.id, metadata.name, (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: metadata.id,\n severity: metadata.severity,\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","import type { Graph, Finding, NodeRef } from '../../types';\nimport type { RuleContext } from '../index';\nimport type { RuleMetadata } from '../metadata';\n\nexport const metadata: RuleMetadata = {\n id: 'R13',\n name: 'webhook_acknowledgment',\n severity: 'must',\n description: 'Detects webhooks performing heavy processing without immediate acknowledgment.',\n details: \"Prevents timeout and duplicate events by requiring 'Respond to Webhook' node before heavy operations (HTTP requests, database queries, AI/LLM calls).\",\n};\n\nexport function 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: metadata.id,\n severity: metadata.severity,\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","import { createNodeRule } from '../rule-utils';\nimport { isApiNode } from '../../utils/utils';\nimport type { RuleMetadata } from '../metadata';\n\nexport const metadata: RuleMetadata = {\n id: 'R14',\n name: 'retry_after_compliance',\n severity: 'should',\n description: 'Detects HTTP nodes with retry logic that ignore Retry-After headers from 429/503 responses.',\n details: 'APIs return Retry-After headers (seconds or HTTP date) to indicate when to retry. Ignoring these causes aggressive retry storms, wasted attempts, and potential API bans. Respecting server guidance prevents IP blocking and extended backoffs.',\n};\n\nexport const r14RetryAfterCompliance = createNodeRule(metadata.id, metadata.name, (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: metadata.id,\n severity: metadata.severity,\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","import type { Graph, Finding } from '../types';\r\nimport type { FlowLintConfig } from '../config';\r\n\r\nimport { r1Retry } from './lib/r1-retry';\r\nimport { r2ErrorHandling } from './lib/r2-error-handling';\r\nimport { r3Idempotency } from './lib/r3-idempotency';\r\nimport { r4Secrets } from './lib/r4-secrets';\r\nimport { r5DeadEnds } from './lib/r5-dead-ends';\r\nimport { r6LongRunning } from './lib/r6-long-running';\r\nimport { r7AlertLogEnforcement } from './lib/r7-alert-log-enforcement';\r\nimport { r8UnusedData } from './lib/r8-unused-data';\r\nimport { r9ConfigLiterals } from './lib/r9-config-literals';\r\nimport { r10NamingConvention } from './lib/r10-naming-convention';\r\nimport { r11DeprecatedNodes } from './lib/r11-deprecated-nodes';\r\nimport { r12UnhandledErrorPath } from './lib/r12-unhandled-error-path';\r\nimport { r13WebhookAcknowledgment } from './lib/r13-webhook-acknowledgment';\r\nimport { r14RetryAfterCompliance } from './lib/r14-retry-after-compliance';\r\n\r\nexport type RuleContext = { path: string; cfg: FlowLintConfig; nodeLines?: Record<string, number> };\r\n\r\ntype RuleRunner = (graph: Graph, ctx: RuleContext) => Finding[];\r\n\r\nconst rules: RuleRunner[] = [\r\n r1Retry,\r\n r2ErrorHandling,\r\n r3Idempotency,\r\n r4Secrets,\r\n r5DeadEnds,\r\n r6LongRunning,\r\n r7AlertLogEnforcement,\r\n r8UnusedData,\r\n r9ConfigLiterals,\r\n r10NamingConvention,\r\n r11DeprecatedNodes,\r\n r12UnhandledErrorPath,\r\n r13WebhookAcknowledgment,\r\n r14RetryAfterCompliance,\r\n];\r\n\r\nexport function runAllRules(graph: Graph, ctx: RuleContext): Finding[] {\r\n return rules.flatMap((rule) => rule(graph, ctx));\r\n}\r\n\r\n","import { metadata as r1 } from './lib/r1-retry';\nimport { metadata as r2 } from './lib/r2-error-handling';\nimport { metadata as r3 } from './lib/r3-idempotency';\nimport { metadata as r4 } from './lib/r4-secrets';\nimport { metadata as r5 } from './lib/r5-dead-ends';\nimport { metadata as r6 } from './lib/r6-long-running';\nimport { metadata as r7 } from './lib/r7-alert-log-enforcement';\nimport { metadata as r8 } from './lib/r8-unused-data';\nimport { metadata as r9 } from './lib/r9-config-literals';\nimport { metadata as r10 } from './lib/r10-naming-convention';\nimport { metadata as r11 } from './lib/r11-deprecated-nodes';\nimport { metadata as r12 } from './lib/r12-unhandled-error-path';\nimport { metadata as r13 } from './lib/r13-webhook-acknowledgment';\nimport { metadata as r14 } from './lib/r14-retry-after-compliance';\n\nexport interface RuleMetadata {\n id: string;\n name: string;\n severity: 'must' | 'should' | 'nit';\n description: string;\n details: string;\n}\n\nexport const RULES_METADATA: RuleMetadata[] = [\n r1,\n r2,\n r3,\n r4,\n r5,\n r6,\n r7,\n r8,\n r9,\n r10,\n r11,\n r12,\n r13,\n r14,\n];\n\n\n\n\n\n\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":["metadata","metadata","metadata","metadata","metadata","metadata","metadata","metadata","metadata","metadata","metadata","metadata","metadata","metadata","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/lib/r1-retry.ts","../src/rules/lib/r2-error-handling.ts","../src/rules/lib/r3-idempotency.ts","../src/rules/lib/r4-secrets.ts","../src/rules/lib/r5-dead-ends.ts","../src/rules/lib/r6-long-running.ts","../src/rules/lib/r7-alert-log-enforcement.ts","../src/rules/lib/r8-unused-data.ts","../src/rules/lib/r9-config-literals.ts","../src/rules/lib/r10-naming-convention.ts","../src/rules/lib/r11-deprecated-nodes.ts","../src/rules/lib/r12-unhandled-error-path.ts","../src/rules/lib/r13-webhook-acknowledgment.ts","../src/rules/lib/r14-retry-after-compliance.ts","../src/rules/index.ts","../src/rules/metadata.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 { createNodeRule } from '../rule-utils';\nimport { isApiNode } from '../../utils/utils';\nimport type { RuleMetadata } from '../metadata';\n\nexport const metadata: RuleMetadata = {\n id: 'R1',\n name: 'rate_limit_retry',\n severity: 'must',\n description: 'Ensures that nodes making external API calls have a retry mechanism configured.',\n details: 'Critical for building resilient workflows that can handle transient network issues or temporary service unavailability.',\n};\n\nexport const r1Retry = createNodeRule(metadata.id, metadata.name, (node, graph, ctx) => {\n if (!isApiNode(node.type)) return null;\n\n const params = (node.params ?? {});\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: metadata.id,\n severity: metadata.severity,\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","import { createNodeRule } from '../rule-utils';\nimport type { RuleMetadata } from '../metadata';\n\nexport const metadata: RuleMetadata = {\n id: 'R2',\n name: 'error_handling',\n severity: 'must',\n description: 'Prevents the use of configurations that might hide errors.',\n details: 'Workflows should explicitly handle errors rather than ignoring them with continueOnFail: true.',\n};\n\nexport const r2ErrorHandling = createNodeRule(metadata.id, metadata.name, (node, graph, ctx) => {\n if (ctx.cfg.rules.error_handling.forbid_continue_on_fail && node.flags?.continueOnFail) {\n return {\n rule: metadata.id,\n severity: metadata.severity,\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","import { isMutationNode, findAllUpstreamNodes, containsCandidate } from '../../utils/utils';\nimport type { Graph, Finding } from '../../types';\nimport type { RuleContext } from '../index';\nimport type { RuleMetadata } from '../metadata';\n\nexport const metadata: RuleMetadata = {\n id: 'R3',\n name: 'idempotency',\n severity: 'should',\n description: 'Guards against operations that are not idempotent with retries configured.',\n details: 'Detects patterns where a webhook trigger could lead to duplicate processing in databases or external services.',\n};\n\nexport function 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: metadata.id,\n severity: metadata.severity,\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","import { createHardcodedStringRule } from '../rule-utils';\nimport type { RuleMetadata } from '../metadata';\n\nexport const metadata: RuleMetadata = {\n id: 'R4',\n name: 'secrets',\n severity: 'must',\n description: 'Detects hardcoded secrets, API keys, or credentials within node parameters.',\n details: 'All secrets should be stored securely using credential management systems.',\n};\n\nexport const r4Secrets = createHardcodedStringRule({\n ruleId: metadata.id,\n severity: metadata.severity,\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","import { isTerminalNode } from '../../utils/utils';\nimport type { Graph, Finding } from '../../types';\nimport type { RuleContext } from '../index';\nimport type { RuleMetadata } from '../metadata';\n\nexport const metadata: RuleMetadata = {\n id: 'R5',\n name: 'dead_ends',\n severity: 'should',\n description: 'Finds nodes or workflow branches not connected to any other node.',\n details: 'Indicates incomplete or dead logic that should be reviewed or removed.',\n};\n\nexport function 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\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: metadata.id,\n severity: metadata.severity,\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","import { readNumber } from '../../utils/utils';\nimport type { Graph, Finding } from '../../types';\nimport type { RuleContext } from '../index';\nimport type { RuleMetadata } from '../metadata';\n\nexport const metadata: RuleMetadata = {\n id: 'R6',\n name: 'long_running',\n severity: 'should',\n description: 'Flags workflows with potential for excessive runtime.',\n details: 'Detects loops with high iteration counts or long timeouts that could cause performance issues.',\n};\n\nexport function 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: metadata.id,\n severity: metadata.severity,\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: metadata.id,\n severity: metadata.severity,\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","import { isNotificationNode, isErrorHandlerNode, isRejoinNode } from '../../utils/utils';\nimport type { Graph, Finding } from '../../types';\nimport type { RuleContext } from '../index';\nimport type { RuleMetadata } from '../metadata';\n\nexport const metadata: RuleMetadata = {\n id: 'R7',\n name: 'alert_log_enforcement',\n severity: 'should',\n description: 'Ensures critical paths include logging or alerting steps.',\n details: 'For example, a failed payment processing branch should trigger an alert for monitoring.',\n};\n\nfunction isPathHandled(graph: Graph, startNodeId: string): boolean {\n const queue: string[] = [startNodeId];\n const visited = new Set<string>([startNodeId]);\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 (!currentNode) continue;\n\n if (isNotificationNode(currentNode.type) || isErrorHandlerNode(currentNode.type, currentNode.name)) {\n return true;\n }\n\n if (isRejoinNode(graph, currentId)) {\n continue;\n }\n\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 return false;\n}\n\nexport function 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 if (!isPathHandled(graph, edge.to)) {\n findings.push({\n rule: metadata.id,\n severity: metadata.severity,\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","import { isTerminalNode, isMeaningfulConsumer, findAllDownstreamNodes } from '../../utils/utils';\nimport type { Graph, Finding } from '../../types';\nimport type { RuleContext } from '../index';\nimport type { RuleMetadata } from '../metadata';\n\nexport const metadata: RuleMetadata = {\n id: 'R8',\n name: 'unused_data',\n severity: 'nit',\n description: 'Detects when node output data is not consumed by subsequent nodes.',\n details: 'Identifies unnecessary data processing that could be optimized or removed.',\n};\n\nexport function 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: metadata.id,\n severity: metadata.severity,\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","import { createHardcodedStringRule } from '../rule-utils';\nimport type { RuleMetadata } from '../metadata';\n\nexport const metadata: RuleMetadata = {\n id: 'R9',\n name: 'config_literals',\n severity: 'should',\n description: 'Flags hardcoded literals (URLs, environment tags, tenant IDs) that should come from configuration.',\n details: 'Promotes externalized configuration and prevents hardcoded environment-specific values.',\n};\n\nexport const r9ConfigLiterals = createHardcodedStringRule({\n ruleId: metadata.id,\n severity: metadata.severity,\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","import { createNodeRule } from '../rule-utils';\nimport type { RuleMetadata } from '../metadata';\n\nexport const metadata: RuleMetadata = {\n id: 'R10',\n name: 'naming_convention',\n severity: 'nit',\n description: 'Enforces consistent and descriptive naming for nodes.',\n details: \"Enforces consistent and descriptive naming for nodes. Improves workflow readability and maintainability (e.g., 'Fetch Customer Data from CRM' vs 'HTTP Request').\",\n};\n\nexport const r10NamingConvention = createNodeRule(metadata.id, metadata.name, (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: metadata.id,\n severity: metadata.severity,\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","import { createNodeRule } from '../rule-utils';\nimport type { RuleMetadata } from '../metadata';\n\nexport const metadata: RuleMetadata = {\n id: 'R11',\n name: 'deprecated_nodes',\n severity: 'should',\n description: 'Warns about deprecated node types and suggests alternatives.',\n details: 'Helps maintain workflows using current, supported node implementations.',\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\nexport const r11DeprecatedNodes = createNodeRule(metadata.id, metadata.name, (node, graph, ctx) => {\n if (DEPRECATED_NODES[node.type]) {\n return {\n rule: metadata.id,\n severity: metadata.severity,\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","import { createNodeRule } from '../rule-utils';\nimport { isErrorProneNode, isErrorHandlerNode } from '../../utils/utils';\nimport type { RuleMetadata } from '../metadata';\n\nexport const metadata: RuleMetadata = {\n id: 'R12',\n name: 'unhandled_error_path',\n severity: 'must',\n description: 'Ensures nodes with error outputs have connected error handling branches.',\n details: 'Prevents silent failures by requiring explicit error path handling.',\n};\n\nexport const r12UnhandledErrorPath = createNodeRule(metadata.id, metadata.name, (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: metadata.id,\n severity: metadata.severity,\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","import type { Graph, Finding, NodeRef } from '../../types';\nimport type { RuleContext } from '../index';\nimport type { RuleMetadata } from '../metadata';\n\nexport const metadata: RuleMetadata = {\n id: 'R13',\n name: 'webhook_acknowledgment',\n severity: 'must',\n description: 'Detects webhooks performing heavy processing without immediate acknowledgment.',\n details: \"Prevents timeout and duplicate events by requiring 'Respond to Webhook' node before heavy operations (HTTP requests, database queries, AI/LLM calls).\",\n};\n\nexport function 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: metadata.id,\n severity: metadata.severity,\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","import { createNodeRule } from '../rule-utils';\nimport { isApiNode } from '../../utils/utils';\nimport type { RuleMetadata } from '../metadata';\n\nexport const metadata: RuleMetadata = {\n id: 'R14',\n name: 'retry_after_compliance',\n severity: 'should',\n description: 'Detects HTTP nodes with retry logic that ignore Retry-After headers from 429/503 responses.',\n details: 'APIs return Retry-After headers (seconds or HTTP date) to indicate when to retry. Ignoring these causes aggressive retry storms, wasted attempts, and potential API bans. Respecting server guidance prevents IP blocking and extended backoffs.',\n};\n\nexport const r14RetryAfterCompliance = createNodeRule(metadata.id, metadata.name, (node, graph, ctx) => {\n // Only check HTTP request nodes\n if (!isApiNode(node.type)) return null;\n\n const params = (node.params ?? {});\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 !Number.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: metadata.id,\n severity: metadata.severity,\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","import type { Graph, Finding } from '../types';\r\nimport type { FlowLintConfig } from '../config';\r\n\r\nimport { r1Retry } from './lib/r1-retry';\r\nimport { r2ErrorHandling } from './lib/r2-error-handling';\r\nimport { r3Idempotency } from './lib/r3-idempotency';\r\nimport { r4Secrets } from './lib/r4-secrets';\r\nimport { r5DeadEnds } from './lib/r5-dead-ends';\r\nimport { r6LongRunning } from './lib/r6-long-running';\r\nimport { r7AlertLogEnforcement } from './lib/r7-alert-log-enforcement';\r\nimport { r8UnusedData } from './lib/r8-unused-data';\r\nimport { r9ConfigLiterals } from './lib/r9-config-literals';\r\nimport { r10NamingConvention } from './lib/r10-naming-convention';\r\nimport { r11DeprecatedNodes } from './lib/r11-deprecated-nodes';\r\nimport { r12UnhandledErrorPath } from './lib/r12-unhandled-error-path';\r\nimport { r13WebhookAcknowledgment } from './lib/r13-webhook-acknowledgment';\r\nimport { r14RetryAfterCompliance } from './lib/r14-retry-after-compliance';\r\n\r\nexport type RuleContext = { path: string; cfg: FlowLintConfig; nodeLines?: Record<string, number> };\r\n\r\ntype RuleRunner = (graph: Graph, ctx: RuleContext) => Finding[];\r\n\r\nconst rules: RuleRunner[] = [\r\n r1Retry,\r\n r2ErrorHandling,\r\n r3Idempotency,\r\n r4Secrets,\r\n r5DeadEnds,\r\n r6LongRunning,\r\n r7AlertLogEnforcement,\r\n r8UnusedData,\r\n r9ConfigLiterals,\r\n r10NamingConvention,\r\n r11DeprecatedNodes,\r\n r12UnhandledErrorPath,\r\n r13WebhookAcknowledgment,\r\n r14RetryAfterCompliance,\r\n];\r\n\r\nexport function runAllRules(graph: Graph, ctx: RuleContext): Finding[] {\r\n return rules.flatMap((rule) => rule(graph, ctx));\r\n}\r\n\r\n","import { metadata as r1 } from './lib/r1-retry';\nimport { metadata as r2 } from './lib/r2-error-handling';\nimport { metadata as r3 } from './lib/r3-idempotency';\nimport { metadata as r4 } from './lib/r4-secrets';\nimport { metadata as r5 } from './lib/r5-dead-ends';\nimport { metadata as r6 } from './lib/r6-long-running';\nimport { metadata as r7 } from './lib/r7-alert-log-enforcement';\nimport { metadata as r8 } from './lib/r8-unused-data';\nimport { metadata as r9 } from './lib/r9-config-literals';\nimport { metadata as r10 } from './lib/r10-naming-convention';\nimport { metadata as r11 } from './lib/r11-deprecated-nodes';\nimport { metadata as r12 } from './lib/r12-unhandled-error-path';\nimport { metadata as r13 } from './lib/r13-webhook-acknowledgment';\nimport { metadata as r14 } from './lib/r14-retry-after-compliance';\n\nexport interface RuleMetadata {\n id: string;\n name: string;\n severity: 'must' | 'should' | 'nit';\n description: string;\n details: string;\n}\n\nexport const RULES_METADATA: RuleMetadata[] = [\n r1,\n r2,\n r3,\n r4,\n r5,\n r6,\n r7,\n r8,\n r9,\n r10,\n r11,\n r12,\n r13,\n r14,\n];\n\n\n\n\n\n\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":["metadata","metadata","metadata","metadata","metadata","metadata","metadata","metadata","metadata","metadata","metadata","metadata","metadata","metadata","YAML","YAML"]}
|