Compare commits
2 Commits
dev_json
...
dev_roeiba
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bb81f93a24 | ||
|
|
344054572a |
133
.github/workflows/validate-workflows.yml
vendored
Normal file
133
.github/workflows/validate-workflows.yml
vendored
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
name: Validate n8n Workflows
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ main, master ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ main, master ]
|
||||||
|
workflow_dispatch: # Allow manual triggering
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
validate-workflows:
|
||||||
|
name: Validate n8n Workflows
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.10'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
working-directory: ./lib
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install -r requirements.txt
|
||||||
|
pip install -e .
|
||||||
|
|
||||||
|
- name: Run workflow validation
|
||||||
|
id: validate
|
||||||
|
working-directory: ./lib
|
||||||
|
run: |
|
||||||
|
# Run the validator on all JSON files in the repository
|
||||||
|
# This will fail if any workflow is invalid
|
||||||
|
echo "Validating all n8n workflows..."
|
||||||
|
if ! n8n-validate ..; then
|
||||||
|
echo "::error::One or more workflow validations failed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "All workflows are valid!"
|
||||||
|
|
||||||
|
- name: Create visualization artifacts
|
||||||
|
if: always() # Run this step even if validation fails
|
||||||
|
working-directory: ./lib
|
||||||
|
run: |
|
||||||
|
echo "Creating visualizations for all workflows..."
|
||||||
|
mkdir -p ../workflow-visualizations
|
||||||
|
|
||||||
|
# Find all JSON files that might be n8n workflows
|
||||||
|
find .. -type f -name "*.json" -not -path "*/node_modules/*" -not -path "*/.git/*" -not -path "*/workflow-visualizations/*" | while read -r file; do
|
||||||
|
# Try to validate the file first
|
||||||
|
if n8n-validate "$file" 2>/dev/null; then
|
||||||
|
# If validation passes, create a visualization
|
||||||
|
echo "Creating visualization for $file"
|
||||||
|
filename=$(basename "$file" .json)
|
||||||
|
output_file="../workflow-visualizations/${filename}.png"
|
||||||
|
if ! n8n-visualize "$file" -o "$output_file" --no-show 2>/dev/null; then
|
||||||
|
echo "::warning::Failed to create visualization for $file"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Count the number of visualizations created
|
||||||
|
VIS_COUNT=$(find ../workflow-visualizations -type f -name "*.png" | wc -l)
|
||||||
|
echo "Created $VIS_COUNT workflow visualizations"
|
||||||
|
|
||||||
|
# Set an output with the visualization count
|
||||||
|
echo "visualization_count=$VIS_COUNT" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Upload workflow visualizations
|
||||||
|
if: always() && steps.validate.outcome == 'success'
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: workflow-visualizations
|
||||||
|
path: workflow-visualizations/
|
||||||
|
if-no-files-found: ignore
|
||||||
|
retention-days: 7
|
||||||
|
|
||||||
|
- name: Comment on PR with validation results
|
||||||
|
if: github.event_name == 'pull_request' && steps.validate.outcome == 'success'
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const fs = require('fs');
|
||||||
|
const { execSync } = require('child_process');
|
||||||
|
|
||||||
|
// Get the list of workflow files that were validated
|
||||||
|
const workflowFiles = execSync('find .. -type f -name "*.json" -not -path "*/node_modules/*" -not -path "*/.git/*" -not -path "*/workflow-visualizations/*"')
|
||||||
|
.toString()
|
||||||
|
.split('\n')
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
// Count visualizations
|
||||||
|
let visCount = 0;
|
||||||
|
try {
|
||||||
|
visCount = fs.readdirSync('../workflow-visualizations').length;
|
||||||
|
} catch (e) {
|
||||||
|
// Directory might not exist if no visualizations were created
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a comment
|
||||||
|
const comment = `✅ All ${workflowFiles.length} n8n workflow files are valid!\n` +
|
||||||
|
`📊 ${visCount} workflow visualizations were generated and attached as artifacts.`;
|
||||||
|
|
||||||
|
// Add a comment to the PR
|
||||||
|
const { data: comments } = await github.rest.issues.listComments({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: context.issue.number,
|
||||||
|
});
|
||||||
|
|
||||||
|
const botComment = comments.find(comment =>
|
||||||
|
comment.user.login === 'github-actions[bot]' &&
|
||||||
|
comment.body.includes('n8n workflow')
|
||||||
|
);
|
||||||
|
|
||||||
|
if (botComment) {
|
||||||
|
await github.rest.issues.updateComment({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
comment_id: botComment.id,
|
||||||
|
body: comment,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await github.rest.issues.createComment({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: context.issue.number,
|
||||||
|
body: comment,
|
||||||
|
});
|
||||||
|
}
|
||||||
170
lib/examples/visualize_example.py
Normal file
170
lib/examples/visualize_example.py
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
"""
|
||||||
|
Example script demonstrating how to use the n8n_utils visualization.
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Add the lib directory to the path so we can import n8n_utils
|
||||||
|
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||||
|
|
||||||
|
from n8n_utils.visualization import visualize_workflow
|
||||||
|
|
||||||
|
def create_sample_workflow():
|
||||||
|
"""Create a sample n8n workflow for demonstration."""
|
||||||
|
return {
|
||||||
|
"name": "Sample Workflow",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "1",
|
||||||
|
"name": "Start",
|
||||||
|
"type": "n8n-nodes-base.start",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [250, 300],
|
||||||
|
"parameters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "2",
|
||||||
|
"name": "HTTP Request",
|
||||||
|
"type": "n8n-nodes-base.httpRequest",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [450, 200],
|
||||||
|
"parameters": {
|
||||||
|
"url": "https://api.example.com/data",
|
||||||
|
"method": "GET"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "3",
|
||||||
|
"name": "Process Data",
|
||||||
|
"type": "n8n-nodes-base.function",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [650, 300],
|
||||||
|
"parameters": {
|
||||||
|
"functionCode": "// Process the data here\nreturn items;"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "4",
|
||||||
|
"name": "Condition",
|
||||||
|
"type": "n8n-nodes-base.if",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [850, 300],
|
||||||
|
"parameters": {
|
||||||
|
"conditions": {
|
||||||
|
"string": [
|
||||||
|
{
|
||||||
|
"value1": "={{ $json.someField }}",
|
||||||
|
"operation": "exists"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "5",
|
||||||
|
"name": "Send Email",
|
||||||
|
"type": "n8n-nodes-base.emailSend",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [1050, 200],
|
||||||
|
"parameters": {
|
||||||
|
"to": "user@example.com",
|
||||||
|
"subject": "Processing Complete",
|
||||||
|
"text": "The data has been processed successfully."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6",
|
||||||
|
"name": "Log Error",
|
||||||
|
"type": "n8n-nodes-base.function",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [1050, 400],
|
||||||
|
"parameters": {
|
||||||
|
"functionCode": "console.log('Error processing data:', items);\nreturn items;"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"connections": {
|
||||||
|
"1": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "2",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"2": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "3",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"3": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "4",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"4": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "5",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"4-1": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "6",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Run the example."""
|
||||||
|
# Create output directory if it doesn't exist
|
||||||
|
output_dir = Path(__file__).parent / "output"
|
||||||
|
output_dir.mkdir(exist_ok=True)
|
||||||
|
|
||||||
|
# Create a sample workflow
|
||||||
|
workflow = create_sample_workflow()
|
||||||
|
|
||||||
|
# Save the workflow as JSON
|
||||||
|
workflow_file = output_dir / "sample_workflow.json"
|
||||||
|
with open(workflow_file, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(workflow, f, indent=2)
|
||||||
|
|
||||||
|
print(f"Created sample workflow at: {workflow_file}")
|
||||||
|
|
||||||
|
# Visualize the workflow
|
||||||
|
output_image = output_dir / "workflow_visualization.png"
|
||||||
|
from n8n_utils.visualization.visualizer import visualize_workflow
|
||||||
|
visualize_workflow(workflow, output_file=str(output_image), show=True)
|
||||||
|
print(f"Workflow visualization saved to: {output_image}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
8
lib/n8n_utils/__init__.py
Normal file
8
lib/n8n_utils/__init__.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
"""
|
||||||
|
n8n Utils
|
||||||
|
|
||||||
|
A collection of utilities for working with n8n workflows,
|
||||||
|
including validation and visualization tools.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__version__ = "0.1.0"
|
||||||
15
lib/n8n_utils/ci/__init__.py
Normal file
15
lib/n8n_utils/ci/__init__.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
"""
|
||||||
|
n8n CI Utilities
|
||||||
|
|
||||||
|
This package provides tools for CI/CD integration with n8n workflows,
|
||||||
|
including validation and testing utilities.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .validator import validate_workflow, validate_workflow_file, validate_all_workflows, ValidationError
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'validate_workflow',
|
||||||
|
'validate_workflow_file',
|
||||||
|
'validate_all_workflows',
|
||||||
|
'ValidationError',
|
||||||
|
]
|
||||||
158
lib/n8n_utils/ci/validator.py
Normal file
158
lib/n8n_utils/ci/validator.py
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
"""
|
||||||
|
n8n Workflow Validator
|
||||||
|
|
||||||
|
This module provides functionality to validate n8n workflow JSON files
|
||||||
|
against a schema to ensure they are properly formatted.
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Dict, List, Optional, Union
|
||||||
|
|
||||||
|
import jsonschema
|
||||||
|
from jsonschema import validate
|
||||||
|
|
||||||
|
# Define the n8n workflow JSON schema
|
||||||
|
N8N_WORKFLOW_SCHEMA = {
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"type": "object",
|
||||||
|
"required": ["nodes", "connections"],
|
||||||
|
"properties": {
|
||||||
|
"name": {"type": "string"},
|
||||||
|
"nodes": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["id", "name", "type", "typeVersion", "position", "parameters"],
|
||||||
|
"properties": {
|
||||||
|
"id": {"type": "string"},
|
||||||
|
"name": {"type": "string"},
|
||||||
|
"type": {"type": "string"},
|
||||||
|
"typeVersion": {"type": ["number", "string"]},
|
||||||
|
"position": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {"type": "number"},
|
||||||
|
"minItems": 2,
|
||||||
|
"maxItems": 2
|
||||||
|
},
|
||||||
|
"parameters": {"type": "object"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"connections": {"type": "object"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
class ValidationError(Exception):
|
||||||
|
"""Custom exception for validation errors."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def validate_workflow(workflow_data: Dict) -> List[str]:
|
||||||
|
"""
|
||||||
|
Validate an n8n workflow against the schema.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
workflow_data: The parsed JSON data of the workflow
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of error messages, empty if valid
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
validate(instance=workflow_data, schema=N8N_WORKFLOW_SCHEMA)
|
||||||
|
return []
|
||||||
|
except jsonschema.exceptions.ValidationError as e:
|
||||||
|
return [f"Validation error: {e.message} at {'.'.join(map(str, e.path))}"]
|
||||||
|
|
||||||
|
def validate_workflow_file(file_path: Union[str, Path]) -> List[str]:
|
||||||
|
"""
|
||||||
|
Validate an n8n workflow file.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
file_path: Path to the JSON file to validate
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of error messages, empty if valid
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with open(file_path, 'r', encoding='utf-8') as f:
|
||||||
|
try:
|
||||||
|
workflow_data = json.load(f)
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
return [f"Invalid JSON in {file_path}: {str(e)}"]
|
||||||
|
|
||||||
|
return validate_workflow(workflow_data)
|
||||||
|
except Exception as e:
|
||||||
|
return [f"Error reading {file_path}: {str(e)}"]
|
||||||
|
|
||||||
|
def find_workflow_files(directory: Union[str, Path]) -> List[Path]:
|
||||||
|
"""
|
||||||
|
Recursively find all JSON files in a directory that might be n8n workflows.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
directory: Directory to search in
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of Path objects to potential workflow files
|
||||||
|
"""
|
||||||
|
directory = Path(directory)
|
||||||
|
return list(directory.glob("**/*.json"))
|
||||||
|
|
||||||
|
def validate_all_workflows(directory: Union[str, Path]) -> Dict[str, List[str]]:
|
||||||
|
"""
|
||||||
|
Validate all JSON files in a directory and its subdirectories.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
directory: Directory to search for workflow files
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dictionary mapping file paths to lists of error messages
|
||||||
|
"""
|
||||||
|
workflow_files = find_workflow_files(directory)
|
||||||
|
results = {}
|
||||||
|
|
||||||
|
for file_path in workflow_files:
|
||||||
|
errors = validate_workflow_file(file_path)
|
||||||
|
if errors:
|
||||||
|
results[str(file_path)] = errors
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Command-line interface for the validator."""
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(description="Validate n8n workflow files.")
|
||||||
|
parser.add_argument(
|
||||||
|
"directory",
|
||||||
|
nargs="?",
|
||||||
|
default=".",
|
||||||
|
help="Directory containing n8n workflow files (default: current directory)",
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
directory = Path(args.directory).resolve()
|
||||||
|
if not directory.exists():
|
||||||
|
print(f"Error: Directory '{directory}' does not exist")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
print(f"Validating n8n workflows in: {directory}")
|
||||||
|
results = validate_all_workflows(directory)
|
||||||
|
|
||||||
|
if not results:
|
||||||
|
print("✅ All workflow files are valid!")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
# Print errors
|
||||||
|
error_count = 0
|
||||||
|
for file_path, errors in results.items():
|
||||||
|
print(f"\n❌ {file_path}:")
|
||||||
|
for error in errors:
|
||||||
|
print(f" - {error}")
|
||||||
|
error_count += 1
|
||||||
|
|
||||||
|
print(f"\nFound {error_count} error(s) in {len(results)} file(s)")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
79
lib/n8n_utils/tests/test_workflow_validation.py
Normal file
79
lib/n8n_utils/tests/test_workflow_validation.py
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
"""Tests for n8n workflow validation."""
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from n8n_utils.ci.validator import validate_workflow, ValidationError
|
||||||
|
|
||||||
|
class TestWorkflowValidation(unittest.TestCase):
|
||||||
|
"""Test cases for workflow validation."""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
"""Set up test fixtures."""
|
||||||
|
self.valid_workflow = {
|
||||||
|
"name": "Test Workflow",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "1",
|
||||||
|
"name": "Start",
|
||||||
|
"type": "n8n-nodes-base.start",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [250, 300],
|
||||||
|
"parameters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "2",
|
||||||
|
"name": "HTTP Request",
|
||||||
|
"type": "n8n-nodes-base.httpRequest",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [450, 300],
|
||||||
|
"parameters": {
|
||||||
|
"url": "https://example.com",
|
||||||
|
"method": "GET"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"connections": {
|
||||||
|
"1": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "2",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def test_valid_workflow(self):
|
||||||
|
"""Test validation of a valid workflow."""
|
||||||
|
errors = validate_workflow(self.valid_workflow)
|
||||||
|
self.assertEqual(len(errors), 0)
|
||||||
|
|
||||||
|
def test_missing_required_field(self):
|
||||||
|
"""Test validation of a workflow with a missing required field."""
|
||||||
|
# Remove a required field
|
||||||
|
invalid_workflow = self.valid_workflow.copy()
|
||||||
|
del invalid_workflow["nodes"][0]["id"]
|
||||||
|
|
||||||
|
errors = validate_workflow(invalid_workflow)
|
||||||
|
self.assertGreater(len(errors), 0)
|
||||||
|
self.assertIn("id", errors[0])
|
||||||
|
|
||||||
|
def test_invalid_node_structure(self):
|
||||||
|
"""Test validation of a workflow with invalid node structure."""
|
||||||
|
invalid_workflow = self.valid_workflow.copy()
|
||||||
|
# Make position an invalid type
|
||||||
|
invalid_workflow["nodes"][0]["position"] = "not an array"
|
||||||
|
|
||||||
|
errors = validate_workflow(invalid_workflow)
|
||||||
|
self.assertGreater(len(errors), 0)
|
||||||
|
self.assertIn("position", errors[0])
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
7
lib/requirements.txt
Normal file
7
lib/requirements.txt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
jsonschema>=4.0.0
|
||||||
|
networkx>=2.6.3
|
||||||
|
matplotlib>=3.4.3
|
||||||
|
pytest>=7.0.0
|
||||||
|
pytest-cov>=3.0.0
|
||||||
|
click>=8.0.0
|
||||||
|
python-dotenv>=0.19.0
|
||||||
39
lib/setup.py
Normal file
39
lib/setup.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
from setuptools import setup, find_packages
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Get the long description from the README file
|
||||||
|
here = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
with open(os.path.join(here, '..', 'README.md'), encoding='utf-8') as f:
|
||||||
|
long_description = f.read()
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name="n8n_utils",
|
||||||
|
version="0.1.0",
|
||||||
|
packages=find_packages(where='.'),
|
||||||
|
package_dir={'': '.'},
|
||||||
|
install_requires=[
|
||||||
|
"jsonschema>=4.0.0",
|
||||||
|
"networkx>=2.6.3",
|
||||||
|
"matplotlib>=3.4.3",
|
||||||
|
"click>=8.0.0",
|
||||||
|
"python-dotenv>=0.19.0",
|
||||||
|
],
|
||||||
|
entry_points={
|
||||||
|
'console_scripts': [
|
||||||
|
'n8n-validate=n8n_utils.ci.validator:main',
|
||||||
|
'n8n-visualize=n8n_utils.visualization.visualizer:main',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
python_requires='>=3.8',
|
||||||
|
author="Your Name",
|
||||||
|
author_email="your.email@example.com",
|
||||||
|
description="Utilities for n8n workflow validation and visualization",
|
||||||
|
long_description=long_description,
|
||||||
|
long_description_content_type="text/markdown",
|
||||||
|
url="https://github.com/yourusername/n8n-free-templates",
|
||||||
|
classifiers=[
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
"License :: OSI Approved :: MIT License",
|
||||||
|
"Operating System :: OS Independent",
|
||||||
|
],
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user