From da7ac96f7fb121220c16b644940060f8c3354ad1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 25 Sep 2025 08:53:39 +0000 Subject: [PATCH 1/3] Initial plan From a8df5aa6cabf260349be517769d71af61eec4198 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 25 Sep 2025 09:05:24 +0000 Subject: [PATCH 2/3] Implement n8n workflow validation and visualization tools Co-authored-by: gejjech <221578902+gejjech@users.noreply.github.com> --- lib/__pycache__/n8n_validate.cpython-312.pyc | Bin 0 -> 2184 bytes lib/__pycache__/n8n_validator.cpython-312.pyc | Bin 0 -> 8546 bytes lib/__pycache__/n8n_visualize.cpython-312.pyc | Bin 0 -> 2763 bytes .../n8n_visualizer.cpython-312.pyc | Bin 0 -> 14221 bytes lib/n8n_validate.py | 50 +++ lib/n8n_validator.py | 189 +++++++++++ lib/n8n_visualize.py | 55 ++++ lib/n8n_visualizer.py | 304 ++++++++++++++++++ lib/n8n_workflow_tools.egg-info/PKG-INFO | 14 + lib/n8n_workflow_tools.egg-info/SOURCES.txt | 11 + .../dependency_links.txt | 1 + .../entry_points.txt | 3 + lib/n8n_workflow_tools.egg-info/requires.txt | 5 + lib/n8n_workflow_tools.egg-info/top_level.txt | 4 + lib/requirements.txt | 5 + lib/setup.py | 42 +++ 16 files changed, 683 insertions(+) create mode 100644 lib/__pycache__/n8n_validate.cpython-312.pyc create mode 100644 lib/__pycache__/n8n_validator.cpython-312.pyc create mode 100644 lib/__pycache__/n8n_visualize.cpython-312.pyc create mode 100644 lib/__pycache__/n8n_visualizer.cpython-312.pyc create mode 100755 lib/n8n_validate.py create mode 100644 lib/n8n_validator.py create mode 100755 lib/n8n_visualize.py create mode 100644 lib/n8n_visualizer.py create mode 100644 lib/n8n_workflow_tools.egg-info/PKG-INFO create mode 100644 lib/n8n_workflow_tools.egg-info/SOURCES.txt create mode 100644 lib/n8n_workflow_tools.egg-info/dependency_links.txt create mode 100644 lib/n8n_workflow_tools.egg-info/entry_points.txt create mode 100644 lib/n8n_workflow_tools.egg-info/requires.txt create mode 100644 lib/n8n_workflow_tools.egg-info/top_level.txt create mode 100644 lib/requirements.txt create mode 100644 lib/setup.py diff --git a/lib/__pycache__/n8n_validate.cpython-312.pyc b/lib/__pycache__/n8n_validate.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cee145d94566fcadf1e465fe9c747c12a7e7afe3 GIT binary patch literal 2184 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 Tp1<|UH&@o!Hc~XU%QF8DpLDOw literal 0 HcmV?d00001 diff --git a/lib/__pycache__/n8n_visualizer.cpython-312.pyc b/lib/__pycache__/n8n_visualizer.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8f6cceffec22686e1b94204b07f390f2788d4e6e GIT binary patch literal 14221 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 bool: + """ + Validate a single n8n workflow. + + Args: + workflow_data: Parsed JSON workflow data + + Returns: + True if valid, False otherwise + """ + try: + # Check for required top-level fields + for field in self.required_fields: + if field not in workflow_data: + print(f"Missing required field: {field}", file=sys.stderr) + return False + + # Validate nodes array + nodes = workflow_data.get('nodes', []) + if not isinstance(nodes, list): + print("'nodes' must be an array", file=sys.stderr) + return False + + if len(nodes) == 0: + print("Workflow must have at least one node", file=sys.stderr) + return False + + # Validate each node + node_ids = set() + for i, node in enumerate(nodes): + if not isinstance(node, dict): + print(f"Node {i} must be an object", file=sys.stderr) + return False + + # Check required node fields + for field in self.node_required_fields: + if field not in node: + print(f"Node {i} missing required field: {field}", file=sys.stderr) + return False + + # Check for duplicate node IDs + node_id = node.get('id') + if node_id in node_ids: + print(f"Duplicate node ID: {node_id}", file=sys.stderr) + return False + node_ids.add(node_id) + + # Validate position array + position = node.get('position', []) + if not isinstance(position, list) or len(position) != 2: + print(f"Node {i} position must be array of 2 numbers", file=sys.stderr) + return False + + if not all(isinstance(p, (int, float)) for p in position): + print(f"Node {i} position values must be numbers", file=sys.stderr) + return False + + # Validate meta field if present + meta = workflow_data.get('meta', {}) + if 'meta' in workflow_data and not isinstance(meta, dict): + print("'meta' must be an object", file=sys.stderr) + return False + + return True + + except Exception as e: + print(f"Validation error: {str(e)}", file=sys.stderr) + return False + + def validate_file(self, file_path: Path) -> bool: + """ + Validate a workflow file. + + Args: + file_path: Path to the workflow JSON file + + Returns: + True if valid, False otherwise + """ + try: + if not file_path.exists(): + print(f"File not found: {file_path}", file=sys.stderr) + return False + + if not file_path.suffix.lower() == '.json': + print(f"Not a JSON file: {file_path}", file=sys.stderr) + return False + + with open(file_path, 'r', encoding='utf-8') as f: + try: + workflow_data = json.load(f) + except json.JSONDecodeError as e: + print(f"Invalid JSON in {file_path}: {str(e)}", file=sys.stderr) + return False + + return self.validate_workflow(workflow_data) + + except Exception as e: + print(f"Error validating {file_path}: {str(e)}", file=sys.stderr) + return False + + +def validate_workflows_in_directory(directory: Path, recursive: bool = True) -> bool: + """ + Validate all n8n workflows in a directory. + + Args: + directory: Directory to search for workflows + recursive: Whether to search subdirectories + + Returns: + True if all workflows are valid, False otherwise + """ + validator = N8nWorkflowValidator() + all_valid = True + workflow_count = 0 + + if recursive: + json_files = list(directory.rglob('*.json')) + else: + json_files = list(directory.glob('*.json')) + + # Filter out obviously non-workflow files + workflow_files = [] + for file_path in json_files: + # Skip files in certain directories that are likely not workflows + skip_dirs = {'node_modules', '.git', 'workflow-visualizations', '__pycache__'} + if any(part in skip_dirs for part in file_path.parts): + continue + + # Skip files that are clearly not n8n workflows based on name patterns + skip_patterns = {'package.json', 'tsconfig.json'} + # Skip index files but not individual templates + if file_path.name.startswith('all_templates.'): + continue + if file_path.name in skip_patterns: + continue + + workflow_files.append(file_path) + + print(f"Found {len(workflow_files)} potential workflow files to validate") + + for file_path in workflow_files: + try: + # Quick check if this looks like an n8n workflow + with open(file_path, 'r', encoding='utf-8') as f: + try: + data = json.load(f) + # Basic heuristic: n8n workflows should have 'nodes' and typically 'meta' + if not isinstance(data, dict) or 'nodes' not in data: + continue # Skip files that don't look like n8n workflows + except json.JSONDecodeError: + continue # Skip invalid JSON files + + workflow_count += 1 + print(f"Validating: {file_path}") + + if not validator.validate_file(file_path): + print(f"❌ Validation failed for: {file_path}") + all_valid = False + else: + print(f"✅ Valid workflow: {file_path}") + + except Exception as e: + print(f"Error processing {file_path}: {str(e)}", file=sys.stderr) + all_valid = False + + print(f"\nValidated {workflow_count} n8n workflows") + if all_valid: + print("✅ All workflows are valid!") + else: + print("❌ Some workflows failed validation") + + return all_valid \ No newline at end of file diff --git a/lib/n8n_visualize.py b/lib/n8n_visualize.py new file mode 100755 index 0000000..0ca2229 --- /dev/null +++ b/lib/n8n_visualize.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +""" +n8n-visualize command-line tool. +Creates visualizations of n8n workflow JSON files. +""" +import sys +import argparse +from pathlib import Path +from n8n_visualizer import N8nWorkflowVisualizer + +def main(): + parser = argparse.ArgumentParser(description='Create visualizations of n8n workflow JSON files') + parser.add_argument('input', help='Path to JSON workflow file') + parser.add_argument('-o', '--output', help='Output image file path (default: same as input with .png extension)') + parser.add_argument('--no-show', action='store_true', help='Do not show the visualization interactively') + parser.add_argument('--width', type=int, default=800, help='Image width in pixels (default: 800)') + parser.add_argument('--height', type=int, default=600, help='Image height in pixels (default: 600)') + parser.add_argument('--format', choices=['png', 'svg', 'pdf'], default='png', + help='Output format (default: png)') + + args = parser.parse_args() + + input_path = Path(args.input) + + if not input_path.exists(): + print(f"Error: Input file does not exist: {input_path}", file=sys.stderr) + return 1 + + if not input_path.is_file(): + print(f"Error: Input path is not a file: {input_path}", file=sys.stderr) + return 1 + + # Determine output path + if args.output: + output_path = Path(args.output) + else: + output_path = input_path.with_suffix(f'.{args.format}') + + # Create visualization + visualizer = N8nWorkflowVisualizer() + result = visualizer.visualize_file( + input_path, + output_path, + show=not args.no_show + ) + + if result: + print(f"Visualization created: {result}") + return 0 + else: + print("Failed to create visualization", file=sys.stderr) + return 1 + +if __name__ == '__main__': + sys.exit(main()) \ No newline at end of file diff --git a/lib/n8n_visualizer.py b/lib/n8n_visualizer.py new file mode 100644 index 0000000..b02c2d6 --- /dev/null +++ b/lib/n8n_visualizer.py @@ -0,0 +1,304 @@ +""" +n8n workflow visualization tools. +""" +import json +import sys +from pathlib import Path +from typing import Dict, Any, List, Tuple, Optional +import matplotlib +matplotlib.use('Agg') # Use non-interactive backend +import matplotlib.pyplot as plt +import networkx as nx +from PIL import Image, ImageDraw, ImageFont +import io + +class N8nWorkflowVisualizer: + """Creates visualizations of n8n workflows.""" + + def __init__(self): + self.node_colors = { + 'trigger': '#ff6b6b', + 'action': '#4ecdc4', + 'condition': '#ffe66d', + 'function': '#a8e6cf', + 'default': '#95a5a6' + } + + def _get_node_color(self, node_type: str) -> str: + """Get color for a node based on its type.""" + if 'trigger' in node_type.lower(): + return self.node_colors['trigger'] + elif 'if' in node_type.lower() or 'switch' in node_type.lower(): + return self.node_colors['condition'] + elif 'function' in node_type.lower() or 'code' in node_type.lower(): + return self.node_colors['function'] + elif any(term in node_type.lower() for term in ['http', 'webhook', 'email', 'slack', 'telegram']): + return self.node_colors['action'] + else: + return self.node_colors['default'] + + def create_workflow_graph(self, workflow_data: Dict[str, Any]) -> nx.DiGraph: + """ + Create a NetworkX graph from n8n workflow data. + + Args: + workflow_data: Parsed n8n workflow JSON + + Returns: + NetworkX directed graph representing the workflow + """ + G = nx.DiGraph() + + nodes = workflow_data.get('nodes', []) + connections = workflow_data.get('connections', {}) + + # Add nodes + for node in nodes: + node_id = node.get('id') + node_name = node.get('name', 'Unnamed') + node_type = node.get('type', 'unknown') + position = node.get('position', [0, 0]) + + G.add_node( + node_id, + label=node_name, + type=node_type, + pos=(position[0]/100, -position[1]/100), # Scale and invert Y for better layout + color=self._get_node_color(node_type) + ) + + # Add edges based on connections + for source_node, source_outputs in connections.items(): + for output_type, output_connections in source_outputs.items(): + if isinstance(output_connections, list): + for connection_list in output_connections: + if isinstance(connection_list, list): + for connection in connection_list: + if isinstance(connection, dict): + target_node = connection.get('node') + if target_node and G.has_node(source_node) and G.has_node(target_node): + G.add_edge(source_node, target_node) + + return G + + def visualize_workflow_matplotlib( + self, + workflow_data: Dict[str, Any], + output_path: Optional[Path] = None, + figsize: Tuple[int, int] = (12, 8), + show: bool = False + ) -> Optional[Path]: + """ + Create a matplotlib visualization of the workflow. + + Args: + workflow_data: Parsed n8n workflow JSON + output_path: Path to save the visualization + figsize: Figure size tuple + show: Whether to show the plot interactively + + Returns: + Path to saved file if output_path provided, None otherwise + """ + try: + G = self.create_workflow_graph(workflow_data) + + if len(G.nodes()) == 0: + print("No nodes found in workflow", file=sys.stderr) + return None + + plt.figure(figsize=figsize) + + # Use positions from n8n if available, otherwise use spring layout + try: + pos = nx.get_node_attributes(G, 'pos') + if not pos or len(pos) == 0: + pos = nx.spring_layout(G, k=2, iterations=50) + except: + pos = nx.spring_layout(G, k=2, iterations=50) + + # Get node colors + node_colors = [G.nodes[node].get('color', self.node_colors['default']) for node in G.nodes()] + + # Get node labels + labels = {node: G.nodes[node].get('label', node[:8] + '...') if len(G.nodes[node].get('label', node)) > 10 else G.nodes[node].get('label', node) for node in G.nodes()} + + # Draw the graph + nx.draw_networkx_nodes(G, pos, node_color=node_colors, node_size=2000, alpha=0.9) + nx.draw_networkx_edges(G, pos, edge_color='gray', arrows=True, arrowsize=20, alpha=0.6) + nx.draw_networkx_labels(G, pos, labels, font_size=8, font_weight='bold') + + # Add title + workflow_name = workflow_data.get('name', 'n8n Workflow') + plt.title(f'n8n Workflow: {workflow_name}', fontsize=16, fontweight='bold', pad=20) + + # Remove axes + plt.axis('off') + + # Add legend + legend_elements = [ + plt.Line2D([0], [0], marker='o', color='w', markerfacecolor=color, + markersize=10, label=node_type.title()) + for node_type, color in self.node_colors.items() if node_type != 'default' + ] + plt.legend(handles=legend_elements, loc='upper left', bbox_to_anchor=(0, 1)) + + plt.tight_layout() + + if output_path: + plt.savefig(output_path, dpi=150, bbox_inches='tight', + facecolor='white', edgecolor='none') + print(f"Visualization saved to: {output_path}") + + if show: + plt.show() + else: + plt.close() + + return output_path + + except Exception as e: + print(f"Error creating visualization: {str(e)}", file=sys.stderr) + return None + + def create_simple_diagram( + self, + workflow_data: Dict[str, Any], + output_path: Path, + width: int = 800, + height: int = 600 + ) -> bool: + """ + Create a simple diagram using PIL when matplotlib fails. + + Args: + workflow_data: Parsed n8n workflow JSON + output_path: Path to save the image + width: Image width + height: Image height + + Returns: + True if successful, False otherwise + """ + try: + # Create a blank image + img = Image.new('RGB', (width, height), color='white') + draw = ImageDraw.Draw(img) + + nodes = workflow_data.get('nodes', []) + if not nodes: + return False + + # Try to load a font, fallback to default if not available + try: + font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 12) + title_font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 16) + except: + font = ImageFont.load_default() + title_font = font + + # Draw title + workflow_name = workflow_data.get('name', 'n8n Workflow') + title_text = f"n8n Workflow: {workflow_name}" + draw.text((10, 10), title_text, fill='black', font=title_font) + + # Calculate positions for nodes + node_width = 120 + node_height = 40 + margin = 20 + start_y = 60 + + cols = max(1, (width - 2 * margin) // (node_width + margin)) + + for i, node in enumerate(nodes[:20]): # Limit to first 20 nodes + col = i % cols + row = i // cols + + x = margin + col * (node_width + margin) + y = start_y + row * (node_height + margin) + + # Draw node rectangle + node_type = node.get('type', 'unknown') + color = self._get_node_color(node_type) + + # Convert hex color to RGB + if color.startswith('#'): + color = tuple(int(color[j:j+2], 16) for j in (1, 3, 5)) + else: + color = (149, 165, 166) # Default gray + + draw.rectangle([x, y, x + node_width, y + node_height], + fill=color, outline='black', width=2) + + # Draw node name + node_name = node.get('name', 'Unnamed') + if len(node_name) > 15: + node_name = node_name[:12] + '...' + + # Center text in rectangle + text_bbox = draw.textbbox((0, 0), node_name, font=font) + text_width = text_bbox[2] - text_bbox[0] + text_height = text_bbox[3] - text_bbox[1] + text_x = x + (node_width - text_width) // 2 + text_y = y + (node_height - text_height) // 2 + + draw.text((text_x, text_y), node_name, fill='white', font=font) + + # Add footer info + footer_text = f"Contains {len(nodes)} nodes" + draw.text((10, height - 30), footer_text, fill='gray', font=font) + + img.save(output_path) + print(f"Simple diagram saved to: {output_path}") + return True + + except Exception as e: + print(f"Error creating simple diagram: {str(e)}", file=sys.stderr) + return False + + def visualize_file( + self, + file_path: Path, + output_path: Optional[Path] = None, + show: bool = False + ) -> Optional[Path]: + """ + Create a visualization from a workflow file. + + Args: + file_path: Path to the workflow JSON file + output_path: Path to save the visualization + show: Whether to show the plot interactively + + Returns: + Path to saved file if successful, None otherwise + """ + try: + if not file_path.exists(): + print(f"File not found: {file_path}", file=sys.stderr) + return None + + with open(file_path, 'r', encoding='utf-8') as f: + workflow_data = json.load(f) + + # Generate output path if not provided + if output_path is None: + output_path = file_path.with_suffix('.png') + + # Try matplotlib first, fallback to simple diagram + result = self.visualize_workflow_matplotlib(workflow_data, output_path, show=show) + if result is None: + # Fallback to simple diagram + if self.create_simple_diagram(workflow_data, output_path): + return output_path + else: + return None + + return result + + except json.JSONDecodeError as e: + print(f"Invalid JSON in {file_path}: {str(e)}", file=sys.stderr) + return None + except Exception as e: + print(f"Error visualizing {file_path}: {str(e)}", file=sys.stderr) + return None \ No newline at end of file diff --git a/lib/n8n_workflow_tools.egg-info/PKG-INFO b/lib/n8n_workflow_tools.egg-info/PKG-INFO new file mode 100644 index 0000000..88df2ab --- /dev/null +++ b/lib/n8n_workflow_tools.egg-info/PKG-INFO @@ -0,0 +1,14 @@ +Metadata-Version: 2.1 +Name: n8n-workflow-tools +Version: 1.0.0 +Summary: Validation and visualization tools for n8n workflows +Author: n8n Community +Classifier: Development Status :: 4 - Beta +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Requires-Python: >=3.8 diff --git a/lib/n8n_workflow_tools.egg-info/SOURCES.txt b/lib/n8n_workflow_tools.egg-info/SOURCES.txt new file mode 100644 index 0000000..b44d3f5 --- /dev/null +++ b/lib/n8n_workflow_tools.egg-info/SOURCES.txt @@ -0,0 +1,11 @@ +n8n_validate.py +n8n_validator.py +n8n_visualize.py +n8n_visualizer.py +setup.py +n8n_workflow_tools.egg-info/PKG-INFO +n8n_workflow_tools.egg-info/SOURCES.txt +n8n_workflow_tools.egg-info/dependency_links.txt +n8n_workflow_tools.egg-info/entry_points.txt +n8n_workflow_tools.egg-info/requires.txt +n8n_workflow_tools.egg-info/top_level.txt \ No newline at end of file diff --git a/lib/n8n_workflow_tools.egg-info/dependency_links.txt b/lib/n8n_workflow_tools.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/lib/n8n_workflow_tools.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/lib/n8n_workflow_tools.egg-info/entry_points.txt b/lib/n8n_workflow_tools.egg-info/entry_points.txt new file mode 100644 index 0000000..23b7b90 --- /dev/null +++ b/lib/n8n_workflow_tools.egg-info/entry_points.txt @@ -0,0 +1,3 @@ +[console_scripts] +n8n-validate = n8n_validate:main +n8n-visualize = n8n_visualize:main diff --git a/lib/n8n_workflow_tools.egg-info/requires.txt b/lib/n8n_workflow_tools.egg-info/requires.txt new file mode 100644 index 0000000..edbeb29 --- /dev/null +++ b/lib/n8n_workflow_tools.egg-info/requires.txt @@ -0,0 +1,5 @@ +jsonschema>=4.0.0 +matplotlib>=3.5.0 +networkx>=2.8.0 +pillow>=9.0.0 +requests>=2.28.0 diff --git a/lib/n8n_workflow_tools.egg-info/top_level.txt b/lib/n8n_workflow_tools.egg-info/top_level.txt new file mode 100644 index 0000000..4296475 --- /dev/null +++ b/lib/n8n_workflow_tools.egg-info/top_level.txt @@ -0,0 +1,4 @@ +n8n_validate +n8n_validator +n8n_visualize +n8n_visualizer diff --git a/lib/requirements.txt b/lib/requirements.txt new file mode 100644 index 0000000..4f8b2de --- /dev/null +++ b/lib/requirements.txt @@ -0,0 +1,5 @@ +jsonschema>=4.0.0 +pillow>=9.0.0 +matplotlib>=3.5.0 +networkx>=2.8.0 +requests>=2.28.0 \ No newline at end of file diff --git a/lib/setup.py b/lib/setup.py new file mode 100644 index 0000000..b31a654 --- /dev/null +++ b/lib/setup.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +""" +Setup script for n8n workflow validation and visualization tools. +""" +from setuptools import setup, find_packages + +setup( + name="n8n-workflow-tools", + version="1.0.0", + description="Validation and visualization tools for n8n workflows", + author="n8n Community", + py_modules=[ + 'n8n_validator', + 'n8n_visualizer', + 'n8n_validate', + 'n8n_visualize' + ], + install_requires=[ + 'jsonschema>=4.0.0', + 'pillow>=9.0.0', + 'matplotlib>=3.5.0', + 'networkx>=2.8.0', + 'requests>=2.28.0' + ], + entry_points={ + 'console_scripts': [ + 'n8n-validate=n8n_validate:main', + 'n8n-visualize=n8n_visualize:main', + ], + }, + classifiers=[ + 'Development Status :: 4 - Beta', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: MIT License', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', + ], + python_requires='>=3.8', +) \ No newline at end of file From bb681028101ae46ffe7f2249c38d4d4ea2b0d338 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 25 Sep 2025 09:07:14 +0000 Subject: [PATCH 3/3] Add gitignore and complete n8n workflow validation system Co-authored-by: gejjech <221578902+gejjech@users.noreply.github.com> --- .gitignore | 32 ++++++++++++++++++ lib/__pycache__/n8n_validate.cpython-312.pyc | Bin 2184 -> 0 bytes lib/__pycache__/n8n_validator.cpython-312.pyc | Bin 8546 -> 0 bytes lib/__pycache__/n8n_visualize.cpython-312.pyc | Bin 2763 -> 0 bytes .../n8n_visualizer.cpython-312.pyc | Bin 14221 -> 0 bytes lib/n8n_workflow_tools.egg-info/PKG-INFO | 14 -------- lib/n8n_workflow_tools.egg-info/SOURCES.txt | 11 ------ .../dependency_links.txt | 1 - .../entry_points.txt | 3 -- lib/n8n_workflow_tools.egg-info/requires.txt | 5 --- lib/n8n_workflow_tools.egg-info/top_level.txt | 4 --- 11 files changed, 32 insertions(+), 38 deletions(-) create mode 100644 .gitignore delete mode 100644 lib/__pycache__/n8n_validate.cpython-312.pyc delete mode 100644 lib/__pycache__/n8n_validator.cpython-312.pyc delete mode 100644 lib/__pycache__/n8n_visualize.cpython-312.pyc delete mode 100644 lib/__pycache__/n8n_visualizer.cpython-312.pyc delete mode 100644 lib/n8n_workflow_tools.egg-info/PKG-INFO delete mode 100644 lib/n8n_workflow_tools.egg-info/SOURCES.txt delete mode 100644 lib/n8n_workflow_tools.egg-info/dependency_links.txt delete mode 100644 lib/n8n_workflow_tools.egg-info/entry_points.txt delete mode 100644 lib/n8n_workflow_tools.egg-info/requires.txt delete mode 100644 lib/n8n_workflow_tools.egg-info/top_level.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..250e503 --- /dev/null +++ b/.gitignore @@ -0,0 +1,32 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ +*.egg-info/ +dist/ +build/ + +# Generated workflow visualizations +workflow-visualizations/ + +# Temporary files +/tmp/ +*.tmp +*.bak + +# IDE +.vscode/ +.idea/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db \ No newline at end of file diff --git a/lib/__pycache__/n8n_validate.cpython-312.pyc b/lib/__pycache__/n8n_validate.cpython-312.pyc deleted file mode 100644 index cee145d94566fcadf1e465fe9c747c12a7e7afe3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2184 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 Tp1<|UH&@o!Hc~XU%QF8DpLDOw diff --git a/lib/__pycache__/n8n_visualizer.cpython-312.pyc b/lib/__pycache__/n8n_visualizer.cpython-312.pyc deleted file mode 100644 index 8f6cceffec22686e1b94204b07f390f2788d4e6e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14221 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.8 diff --git a/lib/n8n_workflow_tools.egg-info/SOURCES.txt b/lib/n8n_workflow_tools.egg-info/SOURCES.txt deleted file mode 100644 index b44d3f5..0000000 --- a/lib/n8n_workflow_tools.egg-info/SOURCES.txt +++ /dev/null @@ -1,11 +0,0 @@ -n8n_validate.py -n8n_validator.py -n8n_visualize.py -n8n_visualizer.py -setup.py -n8n_workflow_tools.egg-info/PKG-INFO -n8n_workflow_tools.egg-info/SOURCES.txt -n8n_workflow_tools.egg-info/dependency_links.txt -n8n_workflow_tools.egg-info/entry_points.txt -n8n_workflow_tools.egg-info/requires.txt -n8n_workflow_tools.egg-info/top_level.txt \ No newline at end of file diff --git a/lib/n8n_workflow_tools.egg-info/dependency_links.txt b/lib/n8n_workflow_tools.egg-info/dependency_links.txt deleted file mode 100644 index 8b13789..0000000 --- a/lib/n8n_workflow_tools.egg-info/dependency_links.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/lib/n8n_workflow_tools.egg-info/entry_points.txt b/lib/n8n_workflow_tools.egg-info/entry_points.txt deleted file mode 100644 index 23b7b90..0000000 --- a/lib/n8n_workflow_tools.egg-info/entry_points.txt +++ /dev/null @@ -1,3 +0,0 @@ -[console_scripts] -n8n-validate = n8n_validate:main -n8n-visualize = n8n_visualize:main diff --git a/lib/n8n_workflow_tools.egg-info/requires.txt b/lib/n8n_workflow_tools.egg-info/requires.txt deleted file mode 100644 index edbeb29..0000000 --- a/lib/n8n_workflow_tools.egg-info/requires.txt +++ /dev/null @@ -1,5 +0,0 @@ -jsonschema>=4.0.0 -matplotlib>=3.5.0 -networkx>=2.8.0 -pillow>=9.0.0 -requests>=2.28.0 diff --git a/lib/n8n_workflow_tools.egg-info/top_level.txt b/lib/n8n_workflow_tools.egg-info/top_level.txt deleted file mode 100644 index 4296475..0000000 --- a/lib/n8n_workflow_tools.egg-info/top_level.txt +++ /dev/null @@ -1,4 +0,0 @@ -n8n_validate -n8n_validator -n8n_visualize -n8n_visualizer