#
tokens: 38978/50000 2/615 files (page 36/45)
lines: off (toggle) GitHub
raw markdown copy
This is page 36 of 45. Use http://codebase.md/czlonkowski/n8n-mcp?lines=false&page={x} to view the full context.

# Directory Structure

```
├── _config.yml
├── .claude
│   └── agents
│       ├── code-reviewer.md
│       ├── context-manager.md
│       ├── debugger.md
│       ├── deployment-engineer.md
│       ├── mcp-backend-engineer.md
│       ├── n8n-mcp-tester.md
│       ├── technical-researcher.md
│       └── test-automator.md
├── .dockerignore
├── .env.docker
├── .env.example
├── .env.n8n.example
├── .env.test
├── .env.test.example
├── .github
│   ├── ABOUT.md
│   ├── BENCHMARK_THRESHOLDS.md
│   ├── FUNDING.yml
│   ├── gh-pages.yml
│   ├── secret_scanning.yml
│   └── workflows
│       ├── benchmark-pr.yml
│       ├── benchmark.yml
│       ├── docker-build-fast.yml
│       ├── docker-build-n8n.yml
│       ├── docker-build.yml
│       ├── release.yml
│       ├── test.yml
│       └── update-n8n-deps.yml
├── .gitignore
├── .npmignore
├── ATTRIBUTION.md
├── CHANGELOG.md
├── CLAUDE.md
├── codecov.yml
├── coverage.json
├── data
│   ├── .gitkeep
│   ├── nodes.db
│   ├── nodes.db-shm
│   ├── nodes.db-wal
│   └── templates.db
├── deploy
│   └── quick-deploy-n8n.sh
├── docker
│   ├── docker-entrypoint.sh
│   ├── n8n-mcp
│   ├── parse-config.js
│   └── README.md
├── docker-compose.buildkit.yml
├── docker-compose.extract.yml
├── docker-compose.n8n.yml
├── docker-compose.override.yml.example
├── docker-compose.test-n8n.yml
├── docker-compose.yml
├── Dockerfile
├── Dockerfile.railway
├── Dockerfile.test
├── docs
│   ├── AUTOMATED_RELEASES.md
│   ├── BENCHMARKS.md
│   ├── CHANGELOG.md
│   ├── CLAUDE_CODE_SETUP.md
│   ├── CLAUDE_INTERVIEW.md
│   ├── CODECOV_SETUP.md
│   ├── CODEX_SETUP.md
│   ├── CURSOR_SETUP.md
│   ├── DEPENDENCY_UPDATES.md
│   ├── DOCKER_README.md
│   ├── DOCKER_TROUBLESHOOTING.md
│   ├── FINAL_AI_VALIDATION_SPEC.md
│   ├── FLEXIBLE_INSTANCE_CONFIGURATION.md
│   ├── HTTP_DEPLOYMENT.md
│   ├── img
│   │   ├── cc_command.png
│   │   ├── cc_connected.png
│   │   ├── codex_connected.png
│   │   ├── cursor_tut.png
│   │   ├── Railway_api.png
│   │   ├── Railway_server_address.png
│   │   ├── vsc_ghcp_chat_agent_mode.png
│   │   ├── vsc_ghcp_chat_instruction_files.png
│   │   ├── vsc_ghcp_chat_thinking_tool.png
│   │   └── windsurf_tut.png
│   ├── INSTALLATION.md
│   ├── LIBRARY_USAGE.md
│   ├── local
│   │   ├── DEEP_DIVE_ANALYSIS_2025-10-02.md
│   │   ├── DEEP_DIVE_ANALYSIS_README.md
│   │   ├── Deep_dive_p1_p2.md
│   │   ├── integration-testing-plan.md
│   │   ├── integration-tests-phase1-summary.md
│   │   ├── N8N_AI_WORKFLOW_BUILDER_ANALYSIS.md
│   │   ├── P0_IMPLEMENTATION_PLAN.md
│   │   └── TEMPLATE_MINING_ANALYSIS.md
│   ├── MCP_ESSENTIALS_README.md
│   ├── MCP_QUICK_START_GUIDE.md
│   ├── N8N_DEPLOYMENT.md
│   ├── RAILWAY_DEPLOYMENT.md
│   ├── README_CLAUDE_SETUP.md
│   ├── README.md
│   ├── tools-documentation-usage.md
│   ├── VS_CODE_PROJECT_SETUP.md
│   ├── WINDSURF_SETUP.md
│   └── workflow-diff-examples.md
├── examples
│   └── enhanced-documentation-demo.js
├── fetch_log.txt
├── LICENSE
├── MEMORY_N8N_UPDATE.md
├── MEMORY_TEMPLATE_UPDATE.md
├── monitor_fetch.sh
├── N8N_HTTP_STREAMABLE_SETUP.md
├── n8n-nodes.db
├── P0-R3-TEST-PLAN.md
├── package-lock.json
├── package.json
├── package.runtime.json
├── PRIVACY.md
├── railway.json
├── README.md
├── renovate.json
├── scripts
│   ├── analyze-optimization.sh
│   ├── audit-schema-coverage.ts
│   ├── build-optimized.sh
│   ├── compare-benchmarks.js
│   ├── demo-optimization.sh
│   ├── deploy-http.sh
│   ├── deploy-to-vm.sh
│   ├── export-webhook-workflows.ts
│   ├── extract-changelog.js
│   ├── extract-from-docker.js
│   ├── extract-nodes-docker.sh
│   ├── extract-nodes-simple.sh
│   ├── format-benchmark-results.js
│   ├── generate-benchmark-stub.js
│   ├── generate-detailed-reports.js
│   ├── generate-test-summary.js
│   ├── http-bridge.js
│   ├── mcp-http-client.js
│   ├── migrate-nodes-fts.ts
│   ├── migrate-tool-docs.ts
│   ├── n8n-docs-mcp.service
│   ├── nginx-n8n-mcp.conf
│   ├── prebuild-fts5.ts
│   ├── prepare-release.js
│   ├── publish-npm-quick.sh
│   ├── publish-npm.sh
│   ├── quick-test.ts
│   ├── run-benchmarks-ci.js
│   ├── sync-runtime-version.js
│   ├── test-ai-validation-debug.ts
│   ├── test-code-node-enhancements.ts
│   ├── test-code-node-fixes.ts
│   ├── test-docker-config.sh
│   ├── test-docker-fingerprint.ts
│   ├── test-docker-optimization.sh
│   ├── test-docker.sh
│   ├── test-empty-connection-validation.ts
│   ├── test-error-message-tracking.ts
│   ├── test-error-output-validation.ts
│   ├── test-error-validation.js
│   ├── test-essentials.ts
│   ├── test-expression-code-validation.ts
│   ├── test-expression-format-validation.js
│   ├── test-fts5-search.ts
│   ├── test-fuzzy-fix.ts
│   ├── test-fuzzy-simple.ts
│   ├── test-helpers-validation.ts
│   ├── test-http-search.ts
│   ├── test-http.sh
│   ├── test-jmespath-validation.ts
│   ├── test-multi-tenant-simple.ts
│   ├── test-multi-tenant.ts
│   ├── test-n8n-integration.sh
│   ├── test-node-info.js
│   ├── test-node-type-validation.ts
│   ├── test-nodes-base-prefix.ts
│   ├── test-operation-validation.ts
│   ├── test-optimized-docker.sh
│   ├── test-release-automation.js
│   ├── test-search-improvements.ts
│   ├── test-security.ts
│   ├── test-single-session.sh
│   ├── test-sqljs-triggers.ts
│   ├── test-telemetry-debug.ts
│   ├── test-telemetry-direct.ts
│   ├── test-telemetry-env.ts
│   ├── test-telemetry-integration.ts
│   ├── test-telemetry-no-select.ts
│   ├── test-telemetry-security.ts
│   ├── test-telemetry-simple.ts
│   ├── test-typeversion-validation.ts
│   ├── test-url-configuration.ts
│   ├── test-user-id-persistence.ts
│   ├── test-webhook-validation.ts
│   ├── test-workflow-insert.ts
│   ├── test-workflow-sanitizer.ts
│   ├── test-workflow-tracking-debug.ts
│   ├── update-and-publish-prep.sh
│   ├── update-n8n-deps.js
│   ├── update-readme-version.js
│   ├── vitest-benchmark-json-reporter.js
│   └── vitest-benchmark-reporter.ts
├── SECURITY.md
├── src
│   ├── config
│   │   └── n8n-api.ts
│   ├── data
│   │   └── canonical-ai-tool-examples.json
│   ├── database
│   │   ├── database-adapter.ts
│   │   ├── migrations
│   │   │   └── add-template-node-configs.sql
│   │   ├── node-repository.ts
│   │   ├── nodes.db
│   │   ├── schema-optimized.sql
│   │   └── schema.sql
│   ├── errors
│   │   └── validation-service-error.ts
│   ├── http-server-single-session.ts
│   ├── http-server.ts
│   ├── index.ts
│   ├── loaders
│   │   └── node-loader.ts
│   ├── mappers
│   │   └── docs-mapper.ts
│   ├── mcp
│   │   ├── handlers-n8n-manager.ts
│   │   ├── handlers-workflow-diff.ts
│   │   ├── index.ts
│   │   ├── server.ts
│   │   ├── stdio-wrapper.ts
│   │   ├── tool-docs
│   │   │   ├── configuration
│   │   │   │   ├── get-node-as-tool-info.ts
│   │   │   │   ├── get-node-documentation.ts
│   │   │   │   ├── get-node-essentials.ts
│   │   │   │   ├── get-node-info.ts
│   │   │   │   ├── get-property-dependencies.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── search-node-properties.ts
│   │   │   ├── discovery
│   │   │   │   ├── get-database-statistics.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── list-ai-tools.ts
│   │   │   │   ├── list-nodes.ts
│   │   │   │   └── search-nodes.ts
│   │   │   ├── guides
│   │   │   │   ├── ai-agents-guide.ts
│   │   │   │   └── index.ts
│   │   │   ├── index.ts
│   │   │   ├── system
│   │   │   │   ├── index.ts
│   │   │   │   ├── n8n-diagnostic.ts
│   │   │   │   ├── n8n-health-check.ts
│   │   │   │   ├── n8n-list-available-tools.ts
│   │   │   │   └── tools-documentation.ts
│   │   │   ├── templates
│   │   │   │   ├── get-template.ts
│   │   │   │   ├── get-templates-for-task.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── list-node-templates.ts
│   │   │   │   ├── list-tasks.ts
│   │   │   │   ├── search-templates-by-metadata.ts
│   │   │   │   └── search-templates.ts
│   │   │   ├── types.ts
│   │   │   ├── validation
│   │   │   │   ├── index.ts
│   │   │   │   ├── validate-node-minimal.ts
│   │   │   │   ├── validate-node-operation.ts
│   │   │   │   ├── validate-workflow-connections.ts
│   │   │   │   ├── validate-workflow-expressions.ts
│   │   │   │   └── validate-workflow.ts
│   │   │   └── workflow_management
│   │   │       ├── index.ts
│   │   │       ├── n8n-autofix-workflow.ts
│   │   │       ├── n8n-create-workflow.ts
│   │   │       ├── n8n-delete-execution.ts
│   │   │       ├── n8n-delete-workflow.ts
│   │   │       ├── n8n-get-execution.ts
│   │   │       ├── n8n-get-workflow-details.ts
│   │   │       ├── n8n-get-workflow-minimal.ts
│   │   │       ├── n8n-get-workflow-structure.ts
│   │   │       ├── n8n-get-workflow.ts
│   │   │       ├── n8n-list-executions.ts
│   │   │       ├── n8n-list-workflows.ts
│   │   │       ├── n8n-trigger-webhook-workflow.ts
│   │   │       ├── n8n-update-full-workflow.ts
│   │   │       ├── n8n-update-partial-workflow.ts
│   │   │       └── n8n-validate-workflow.ts
│   │   ├── tools-documentation.ts
│   │   ├── tools-n8n-friendly.ts
│   │   ├── tools-n8n-manager.ts
│   │   ├── tools.ts
│   │   └── workflow-examples.ts
│   ├── mcp-engine.ts
│   ├── mcp-tools-engine.ts
│   ├── n8n
│   │   ├── MCPApi.credentials.ts
│   │   └── MCPNode.node.ts
│   ├── parsers
│   │   ├── node-parser.ts
│   │   ├── property-extractor.ts
│   │   └── simple-parser.ts
│   ├── scripts
│   │   ├── debug-http-search.ts
│   │   ├── extract-from-docker.ts
│   │   ├── fetch-templates-robust.ts
│   │   ├── fetch-templates.ts
│   │   ├── rebuild-database.ts
│   │   ├── rebuild-optimized.ts
│   │   ├── rebuild.ts
│   │   ├── sanitize-templates.ts
│   │   ├── seed-canonical-ai-examples.ts
│   │   ├── test-autofix-documentation.ts
│   │   ├── test-autofix-workflow.ts
│   │   ├── test-execution-filtering.ts
│   │   ├── test-node-suggestions.ts
│   │   ├── test-protocol-negotiation.ts
│   │   ├── test-summary.ts
│   │   ├── test-webhook-autofix.ts
│   │   ├── validate.ts
│   │   └── validation-summary.ts
│   ├── services
│   │   ├── ai-node-validator.ts
│   │   ├── ai-tool-validators.ts
│   │   ├── confidence-scorer.ts
│   │   ├── config-validator.ts
│   │   ├── enhanced-config-validator.ts
│   │   ├── example-generator.ts
│   │   ├── execution-processor.ts
│   │   ├── expression-format-validator.ts
│   │   ├── expression-validator.ts
│   │   ├── n8n-api-client.ts
│   │   ├── n8n-validation.ts
│   │   ├── node-documentation-service.ts
│   │   ├── node-similarity-service.ts
│   │   ├── node-specific-validators.ts
│   │   ├── operation-similarity-service.ts
│   │   ├── property-dependencies.ts
│   │   ├── property-filter.ts
│   │   ├── resource-similarity-service.ts
│   │   ├── sqlite-storage-service.ts
│   │   ├── task-templates.ts
│   │   ├── universal-expression-validator.ts
│   │   ├── workflow-auto-fixer.ts
│   │   ├── workflow-diff-engine.ts
│   │   └── workflow-validator.ts
│   ├── telemetry
│   │   ├── batch-processor.ts
│   │   ├── config-manager.ts
│   │   ├── early-error-logger.ts
│   │   ├── error-sanitization-utils.ts
│   │   ├── error-sanitizer.ts
│   │   ├── event-tracker.ts
│   │   ├── event-validator.ts
│   │   ├── index.ts
│   │   ├── performance-monitor.ts
│   │   ├── rate-limiter.ts
│   │   ├── startup-checkpoints.ts
│   │   ├── telemetry-error.ts
│   │   ├── telemetry-manager.ts
│   │   ├── telemetry-types.ts
│   │   └── workflow-sanitizer.ts
│   ├── templates
│   │   ├── batch-processor.ts
│   │   ├── metadata-generator.ts
│   │   ├── README.md
│   │   ├── template-fetcher.ts
│   │   ├── template-repository.ts
│   │   └── template-service.ts
│   ├── types
│   │   ├── index.ts
│   │   ├── instance-context.ts
│   │   ├── n8n-api.ts
│   │   ├── node-types.ts
│   │   └── workflow-diff.ts
│   └── utils
│       ├── auth.ts
│       ├── bridge.ts
│       ├── cache-utils.ts
│       ├── console-manager.ts
│       ├── documentation-fetcher.ts
│       ├── enhanced-documentation-fetcher.ts
│       ├── error-handler.ts
│       ├── example-generator.ts
│       ├── fixed-collection-validator.ts
│       ├── logger.ts
│       ├── mcp-client.ts
│       ├── n8n-errors.ts
│       ├── node-source-extractor.ts
│       ├── node-type-normalizer.ts
│       ├── node-type-utils.ts
│       ├── node-utils.ts
│       ├── npm-version-checker.ts
│       ├── protocol-version.ts
│       ├── simple-cache.ts
│       ├── ssrf-protection.ts
│       ├── template-node-resolver.ts
│       ├── template-sanitizer.ts
│       ├── url-detector.ts
│       ├── validation-schemas.ts
│       └── version.ts
├── test-output.txt
├── test-reinit-fix.sh
├── tests
│   ├── __snapshots__
│   │   └── .gitkeep
│   ├── auth.test.ts
│   ├── benchmarks
│   │   ├── database-queries.bench.ts
│   │   ├── index.ts
│   │   ├── mcp-tools.bench.ts
│   │   ├── mcp-tools.bench.ts.disabled
│   │   ├── mcp-tools.bench.ts.skip
│   │   ├── node-loading.bench.ts.disabled
│   │   ├── README.md
│   │   ├── search-operations.bench.ts.disabled
│   │   └── validation-performance.bench.ts.disabled
│   ├── bridge.test.ts
│   ├── comprehensive-extraction-test.js
│   ├── data
│   │   └── .gitkeep
│   ├── debug-slack-doc.js
│   ├── demo-enhanced-documentation.js
│   ├── docker-tests-README.md
│   ├── error-handler.test.ts
│   ├── examples
│   │   └── using-database-utils.test.ts
│   ├── extracted-nodes-db
│   │   ├── database-import.json
│   │   ├── extraction-report.json
│   │   ├── insert-nodes.sql
│   │   ├── n8n-nodes-base__Airtable.json
│   │   ├── n8n-nodes-base__Discord.json
│   │   ├── n8n-nodes-base__Function.json
│   │   ├── n8n-nodes-base__HttpRequest.json
│   │   ├── n8n-nodes-base__If.json
│   │   ├── n8n-nodes-base__Slack.json
│   │   ├── n8n-nodes-base__SplitInBatches.json
│   │   └── n8n-nodes-base__Webhook.json
│   ├── factories
│   │   ├── node-factory.ts
│   │   └── property-definition-factory.ts
│   ├── fixtures
│   │   ├── .gitkeep
│   │   ├── database
│   │   │   └── test-nodes.json
│   │   ├── factories
│   │   │   ├── node.factory.ts
│   │   │   └── parser-node.factory.ts
│   │   └── template-configs.ts
│   ├── helpers
│   │   └── env-helpers.ts
│   ├── http-server-auth.test.ts
│   ├── integration
│   │   ├── ai-validation
│   │   │   ├── ai-agent-validation.test.ts
│   │   │   ├── ai-tool-validation.test.ts
│   │   │   ├── chat-trigger-validation.test.ts
│   │   │   ├── e2e-validation.test.ts
│   │   │   ├── helpers.ts
│   │   │   ├── llm-chain-validation.test.ts
│   │   │   ├── README.md
│   │   │   └── TEST_REPORT.md
│   │   ├── ci
│   │   │   └── database-population.test.ts
│   │   ├── database
│   │   │   ├── connection-management.test.ts
│   │   │   ├── empty-database.test.ts
│   │   │   ├── fts5-search.test.ts
│   │   │   ├── node-fts5-search.test.ts
│   │   │   ├── node-repository.test.ts
│   │   │   ├── performance.test.ts
│   │   │   ├── sqljs-memory-leak.test.ts
│   │   │   ├── template-node-configs.test.ts
│   │   │   ├── template-repository.test.ts
│   │   │   ├── test-utils.ts
│   │   │   └── transactions.test.ts
│   │   ├── database-integration.test.ts
│   │   ├── docker
│   │   │   ├── docker-config.test.ts
│   │   │   ├── docker-entrypoint.test.ts
│   │   │   └── test-helpers.ts
│   │   ├── flexible-instance-config.test.ts
│   │   ├── mcp
│   │   │   └── template-examples-e2e.test.ts
│   │   ├── mcp-protocol
│   │   │   ├── basic-connection.test.ts
│   │   │   ├── error-handling.test.ts
│   │   │   ├── performance.test.ts
│   │   │   ├── protocol-compliance.test.ts
│   │   │   ├── README.md
│   │   │   ├── session-management.test.ts
│   │   │   ├── test-helpers.ts
│   │   │   ├── tool-invocation.test.ts
│   │   │   └── workflow-error-validation.test.ts
│   │   ├── msw-setup.test.ts
│   │   ├── n8n-api
│   │   │   ├── executions
│   │   │   │   ├── delete-execution.test.ts
│   │   │   │   ├── get-execution.test.ts
│   │   │   │   ├── list-executions.test.ts
│   │   │   │   └── trigger-webhook.test.ts
│   │   │   ├── scripts
│   │   │   │   └── cleanup-orphans.ts
│   │   │   ├── system
│   │   │   │   ├── diagnostic.test.ts
│   │   │   │   ├── health-check.test.ts
│   │   │   │   └── list-tools.test.ts
│   │   │   ├── test-connection.ts
│   │   │   ├── types
│   │   │   │   └── mcp-responses.ts
│   │   │   ├── utils
│   │   │   │   ├── cleanup-helpers.ts
│   │   │   │   ├── credentials.ts
│   │   │   │   ├── factories.ts
│   │   │   │   ├── fixtures.ts
│   │   │   │   ├── mcp-context.ts
│   │   │   │   ├── n8n-client.ts
│   │   │   │   ├── node-repository.ts
│   │   │   │   ├── response-types.ts
│   │   │   │   ├── test-context.ts
│   │   │   │   └── webhook-workflows.ts
│   │   │   └── workflows
│   │   │       ├── autofix-workflow.test.ts
│   │   │       ├── create-workflow.test.ts
│   │   │       ├── delete-workflow.test.ts
│   │   │       ├── get-workflow-details.test.ts
│   │   │       ├── get-workflow-minimal.test.ts
│   │   │       ├── get-workflow-structure.test.ts
│   │   │       ├── get-workflow.test.ts
│   │   │       ├── list-workflows.test.ts
│   │   │       ├── smart-parameters.test.ts
│   │   │       ├── update-partial-workflow.test.ts
│   │   │       ├── update-workflow.test.ts
│   │   │       └── validate-workflow.test.ts
│   │   ├── security
│   │   │   ├── command-injection-prevention.test.ts
│   │   │   └── rate-limiting.test.ts
│   │   ├── setup
│   │   │   ├── integration-setup.ts
│   │   │   └── msw-test-server.ts
│   │   ├── telemetry
│   │   │   ├── docker-user-id-stability.test.ts
│   │   │   └── mcp-telemetry.test.ts
│   │   ├── templates
│   │   │   └── metadata-operations.test.ts
│   │   └── workflow-creation-node-type-format.test.ts
│   ├── logger.test.ts
│   ├── MOCKING_STRATEGY.md
│   ├── mocks
│   │   ├── n8n-api
│   │   │   ├── data
│   │   │   │   ├── credentials.ts
│   │   │   │   ├── executions.ts
│   │   │   │   └── workflows.ts
│   │   │   ├── handlers.ts
│   │   │   └── index.ts
│   │   └── README.md
│   ├── node-storage-export.json
│   ├── setup
│   │   ├── global-setup.ts
│   │   ├── msw-setup.ts
│   │   ├── TEST_ENV_DOCUMENTATION.md
│   │   └── test-env.ts
│   ├── test-database-extraction.js
│   ├── test-direct-extraction.js
│   ├── test-enhanced-documentation.js
│   ├── test-enhanced-integration.js
│   ├── test-mcp-extraction.js
│   ├── test-mcp-server-extraction.js
│   ├── test-mcp-tools-integration.js
│   ├── test-node-documentation-service.js
│   ├── test-node-list.js
│   ├── test-package-info.js
│   ├── test-parsing-operations.js
│   ├── test-slack-node-complete.js
│   ├── test-small-rebuild.js
│   ├── test-sqlite-search.js
│   ├── test-storage-system.js
│   ├── unit
│   │   ├── __mocks__
│   │   │   ├── n8n-nodes-base.test.ts
│   │   │   ├── n8n-nodes-base.ts
│   │   │   └── README.md
│   │   ├── database
│   │   │   ├── __mocks__
│   │   │   │   └── better-sqlite3.ts
│   │   │   ├── database-adapter-unit.test.ts
│   │   │   ├── node-repository-core.test.ts
│   │   │   ├── node-repository-operations.test.ts
│   │   │   ├── node-repository-outputs.test.ts
│   │   │   ├── README.md
│   │   │   └── template-repository-core.test.ts
│   │   ├── docker
│   │   │   ├── config-security.test.ts
│   │   │   ├── edge-cases.test.ts
│   │   │   ├── parse-config.test.ts
│   │   │   └── serve-command.test.ts
│   │   ├── errors
│   │   │   └── validation-service-error.test.ts
│   │   ├── examples
│   │   │   └── using-n8n-nodes-base-mock.test.ts
│   │   ├── flexible-instance-security-advanced.test.ts
│   │   ├── flexible-instance-security.test.ts
│   │   ├── http-server
│   │   │   └── multi-tenant-support.test.ts
│   │   ├── http-server-n8n-mode.test.ts
│   │   ├── http-server-n8n-reinit.test.ts
│   │   ├── http-server-session-management.test.ts
│   │   ├── loaders
│   │   │   └── node-loader.test.ts
│   │   ├── mappers
│   │   │   └── docs-mapper.test.ts
│   │   ├── mcp
│   │   │   ├── get-node-essentials-examples.test.ts
│   │   │   ├── handlers-n8n-manager-simple.test.ts
│   │   │   ├── handlers-n8n-manager.test.ts
│   │   │   ├── handlers-workflow-diff.test.ts
│   │   │   ├── lru-cache-behavior.test.ts
│   │   │   ├── multi-tenant-tool-listing.test.ts.disabled
│   │   │   ├── parameter-validation.test.ts
│   │   │   ├── search-nodes-examples.test.ts
│   │   │   ├── tools-documentation.test.ts
│   │   │   └── tools.test.ts
│   │   ├── monitoring
│   │   │   └── cache-metrics.test.ts
│   │   ├── MULTI_TENANT_TEST_COVERAGE.md
│   │   ├── multi-tenant-integration.test.ts
│   │   ├── parsers
│   │   │   ├── node-parser-outputs.test.ts
│   │   │   ├── node-parser.test.ts
│   │   │   ├── property-extractor.test.ts
│   │   │   └── simple-parser.test.ts
│   │   ├── scripts
│   │   │   └── fetch-templates-extraction.test.ts
│   │   ├── services
│   │   │   ├── ai-node-validator.test.ts
│   │   │   ├── ai-tool-validators.test.ts
│   │   │   ├── confidence-scorer.test.ts
│   │   │   ├── config-validator-basic.test.ts
│   │   │   ├── config-validator-edge-cases.test.ts
│   │   │   ├── config-validator-node-specific.test.ts
│   │   │   ├── config-validator-security.test.ts
│   │   │   ├── debug-validator.test.ts
│   │   │   ├── enhanced-config-validator-integration.test.ts
│   │   │   ├── enhanced-config-validator-operations.test.ts
│   │   │   ├── enhanced-config-validator.test.ts
│   │   │   ├── example-generator.test.ts
│   │   │   ├── execution-processor.test.ts
│   │   │   ├── expression-format-validator.test.ts
│   │   │   ├── expression-validator-edge-cases.test.ts
│   │   │   ├── expression-validator.test.ts
│   │   │   ├── fixed-collection-validation.test.ts
│   │   │   ├── loop-output-edge-cases.test.ts
│   │   │   ├── n8n-api-client.test.ts
│   │   │   ├── n8n-validation.test.ts
│   │   │   ├── node-similarity-service.test.ts
│   │   │   ├── node-specific-validators.test.ts
│   │   │   ├── operation-similarity-service-comprehensive.test.ts
│   │   │   ├── operation-similarity-service.test.ts
│   │   │   ├── property-dependencies.test.ts
│   │   │   ├── property-filter-edge-cases.test.ts
│   │   │   ├── property-filter.test.ts
│   │   │   ├── resource-similarity-service-comprehensive.test.ts
│   │   │   ├── resource-similarity-service.test.ts
│   │   │   ├── task-templates.test.ts
│   │   │   ├── template-service.test.ts
│   │   │   ├── universal-expression-validator.test.ts
│   │   │   ├── validation-fixes.test.ts
│   │   │   ├── workflow-auto-fixer.test.ts
│   │   │   ├── workflow-diff-engine.test.ts
│   │   │   ├── workflow-fixed-collection-validation.test.ts
│   │   │   ├── workflow-validator-comprehensive.test.ts
│   │   │   ├── workflow-validator-edge-cases.test.ts
│   │   │   ├── workflow-validator-error-outputs.test.ts
│   │   │   ├── workflow-validator-expression-format.test.ts
│   │   │   ├── workflow-validator-loops-simple.test.ts
│   │   │   ├── workflow-validator-loops.test.ts
│   │   │   ├── workflow-validator-mocks.test.ts
│   │   │   ├── workflow-validator-performance.test.ts
│   │   │   ├── workflow-validator-with-mocks.test.ts
│   │   │   └── workflow-validator.test.ts
│   │   ├── telemetry
│   │   │   ├── batch-processor.test.ts
│   │   │   ├── config-manager.test.ts
│   │   │   ├── event-tracker.test.ts
│   │   │   ├── event-validator.test.ts
│   │   │   ├── rate-limiter.test.ts
│   │   │   ├── telemetry-error.test.ts
│   │   │   ├── telemetry-manager.test.ts
│   │   │   ├── v2.18.3-fixes-verification.test.ts
│   │   │   └── workflow-sanitizer.test.ts
│   │   ├── templates
│   │   │   ├── batch-processor.test.ts
│   │   │   ├── metadata-generator.test.ts
│   │   │   ├── template-repository-metadata.test.ts
│   │   │   └── template-repository-security.test.ts
│   │   ├── test-env-example.test.ts
│   │   ├── test-infrastructure.test.ts
│   │   ├── types
│   │   │   ├── instance-context-coverage.test.ts
│   │   │   └── instance-context-multi-tenant.test.ts
│   │   ├── utils
│   │   │   ├── auth-timing-safe.test.ts
│   │   │   ├── cache-utils.test.ts
│   │   │   ├── console-manager.test.ts
│   │   │   ├── database-utils.test.ts
│   │   │   ├── fixed-collection-validator.test.ts
│   │   │   ├── n8n-errors.test.ts
│   │   │   ├── node-type-normalizer.test.ts
│   │   │   ├── node-type-utils.test.ts
│   │   │   ├── node-utils.test.ts
│   │   │   ├── simple-cache-memory-leak-fix.test.ts
│   │   │   ├── ssrf-protection.test.ts
│   │   │   └── template-node-resolver.test.ts
│   │   └── validation-fixes.test.ts
│   └── utils
│       ├── assertions.ts
│       ├── builders
│       │   └── workflow.builder.ts
│       ├── data-generators.ts
│       ├── database-utils.ts
│       ├── README.md
│       └── test-helpers.ts
├── thumbnail.png
├── tsconfig.build.json
├── tsconfig.json
├── types
│   ├── mcp.d.ts
│   └── test-env.d.ts
├── verify-telemetry-fix.js
├── versioned-nodes.md
├── vitest.config.benchmark.ts
├── vitest.config.integration.ts
└── vitest.config.ts
```

# Files

--------------------------------------------------------------------------------
/tests/extracted-nodes-db/n8n-nodes-base__If.json:
--------------------------------------------------------------------------------

```json
{
  "node_type": "n8n-nodes-base.If",
  "name": "If",
  "package_name": "n8n-nodes-base",
  "code_hash": "7910ed9177a946b76f04ca847defb81226c37c698e4cdb63913f038c6c257ee1",
  "code_length": 20533,
  "source_location": "node_modules/n8n-nodes-base/dist/nodes/If/If.node.js",
  "has_credentials": false,
  "source_code": "\"use strict\";\nvar __importDefault = (this && this.__importDefault) || function (mod) {\n    return (mod && mod.__esModule) ? mod : { \"default\": mod };\n};\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.If = void 0;\nconst moment_1 = __importDefault(require(\"moment\"));\nconst n8n_workflow_1 = require(\"n8n-workflow\");\nclass If {\n    constructor() {\n        this.description = {\n            displayName: 'IF',\n            name: 'if',\n            icon: 'fa:map-signs',\n            group: ['transform'],\n            version: 1,\n            description: 'Route items to different branches (true/false)',\n            defaults: {\n                name: 'IF',\n                color: '#408000',\n            },\n            inputs: ['main'],\n            outputs: ['main', 'main'],\n            outputNames: ['true', 'false'],\n            properties: [\n                {\n                    displayName: 'Conditions',\n                    name: 'conditions',\n                    placeholder: 'Add Condition',\n                    type: 'fixedCollection',\n                    typeOptions: {\n                        multipleValues: true,\n                        sortable: true,\n                    },\n                    description: 'The type of values to compare',\n                    default: {},\n                    options: [\n                        {\n                            name: 'boolean',\n                            displayName: 'Boolean',\n                            values: [\n                                {\n                                    displayName: 'Value 1',\n                                    name: 'value1',\n                                    type: 'boolean',\n                                    default: false,\n                                    description: 'The value to compare with the second one',\n                                },\n                                {\n                                    displayName: 'Operation',\n                                    name: 'operation',\n                                    type: 'options',\n                                    options: [\n                                        {\n                                            name: 'Equal',\n                                            value: 'equal',\n                                        },\n                                        {\n                                            name: 'Not Equal',\n                                            value: 'notEqual',\n                                        },\n                                    ],\n                                    default: 'equal',\n                                    description: 'Operation to decide where the the data should be mapped to',\n                                },\n                                {\n                                    displayName: 'Value 2',\n                                    name: 'value2',\n                                    type: 'boolean',\n                                    default: false,\n                                    description: 'The value to compare with the first one',\n                                },\n                            ],\n                        },\n                        {\n                            name: 'dateTime',\n                            displayName: 'Date & Time',\n                            values: [\n                                {\n                                    displayName: 'Value 1',\n                                    name: 'value1',\n                                    type: 'dateTime',\n                                    default: '',\n                                    description: 'The value to compare with the second one',\n                                },\n                                {\n                                    displayName: 'Operation',\n                                    name: 'operation',\n                                    type: 'options',\n                                    options: [\n                                        {\n                                            name: 'Occurred After',\n                                            value: 'after',\n                                        },\n                                        {\n                                            name: 'Occurred Before',\n                                            value: 'before',\n                                        },\n                                    ],\n                                    default: 'after',\n                                    description: 'Operation to decide where the the data should be mapped to',\n                                },\n                                {\n                                    displayName: 'Value 2',\n                                    name: 'value2',\n                                    type: 'dateTime',\n                                    default: '',\n                                    description: 'The value to compare with the first one',\n                                },\n                            ],\n                        },\n                        {\n                            name: 'number',\n                            displayName: 'Number',\n                            values: [\n                                {\n                                    displayName: 'Value 1',\n                                    name: 'value1',\n                                    type: 'number',\n                                    default: 0,\n                                    description: 'The value to compare with the second one',\n                                },\n                                {\n                                    displayName: 'Operation',\n                                    name: 'operation',\n                                    type: 'options',\n                                    noDataExpression: true,\n                                    options: [\n                                        {\n                                            name: 'Smaller',\n                                            value: 'smaller',\n                                        },\n                                        {\n                                            name: 'Smaller or Equal',\n                                            value: 'smallerEqual',\n                                        },\n                                        {\n                                            name: 'Equal',\n                                            value: 'equal',\n                                        },\n                                        {\n                                            name: 'Not Equal',\n                                            value: 'notEqual',\n                                        },\n                                        {\n                                            name: 'Larger',\n                                            value: 'larger',\n                                        },\n                                        {\n                                            name: 'Larger or Equal',\n                                            value: 'largerEqual',\n                                        },\n                                        {\n                                            name: 'Is Empty',\n                                            value: 'isEmpty',\n                                        },\n                                        {\n                                            name: 'Is Not Empty',\n                                            value: 'isNotEmpty',\n                                        },\n                                    ],\n                                    default: 'smaller',\n                                    description: 'Operation to decide where the the data should be mapped to',\n                                },\n                                {\n                                    displayName: 'Value 2',\n                                    name: 'value2',\n                                    type: 'number',\n                                    displayOptions: {\n                                        hide: {\n                                            operation: ['isEmpty', 'isNotEmpty'],\n                                        },\n                                    },\n                                    default: 0,\n                                    description: 'The value to compare with the first one',\n                                },\n                            ],\n                        },\n                        {\n                            name: 'string',\n                            displayName: 'String',\n                            values: [\n                                {\n                                    displayName: 'Value 1',\n                                    name: 'value1',\n                                    type: 'string',\n                                    default: '',\n                                    description: 'The value to compare with the second one',\n                                },\n                                {\n                                    displayName: 'Operation',\n                                    name: 'operation',\n                                    type: 'options',\n                                    noDataExpression: true,\n                                    options: [\n                                        {\n                                            name: 'Contains',\n                                            value: 'contains',\n                                        },\n                                        {\n                                            name: 'Not Contains',\n                                            value: 'notContains',\n                                        },\n                                        {\n                                            name: 'Ends With',\n                                            value: 'endsWith',\n                                        },\n                                        {\n                                            name: 'Not Ends With',\n                                            value: 'notEndsWith',\n                                        },\n                                        {\n                                            name: 'Equal',\n                                            value: 'equal',\n                                        },\n                                        {\n                                            name: 'Not Equal',\n                                            value: 'notEqual',\n                                        },\n                                        {\n                                            name: 'Regex Match',\n                                            value: 'regex',\n                                        },\n                                        {\n                                            name: 'Regex Not Match',\n                                            value: 'notRegex',\n                                        },\n                                        {\n                                            name: 'Starts With',\n                                            value: 'startsWith',\n                                        },\n                                        {\n                                            name: 'Not Starts With',\n                                            value: 'notStartsWith',\n                                        },\n                                        {\n                                            name: 'Is Empty',\n                                            value: 'isEmpty',\n                                        },\n                                        {\n                                            name: 'Is Not Empty',\n                                            value: 'isNotEmpty',\n                                        },\n                                    ],\n                                    default: 'equal',\n                                    description: 'Operation to decide where the the data should be mapped to',\n                                },\n                                {\n                                    displayName: 'Value 2',\n                                    name: 'value2',\n                                    type: 'string',\n                                    displayOptions: {\n                                        hide: {\n                                            operation: ['isEmpty', 'isNotEmpty', 'regex', 'notRegex'],\n                                        },\n                                    },\n                                    default: '',\n                                    description: 'The value to compare with the first one',\n                                },\n                                {\n                                    displayName: 'Regex',\n                                    name: 'value2',\n                                    type: 'string',\n                                    displayOptions: {\n                                        show: {\n                                            operation: ['regex', 'notRegex'],\n                                        },\n                                    },\n                                    default: '',\n                                    placeholder: '/text/i',\n                                    description: 'The regex which has to match',\n                                },\n                            ],\n                        },\n                    ],\n                },\n                {\n                    displayName: 'Combine',\n                    name: 'combineOperation',\n                    type: 'options',\n                    options: [\n                        {\n                            name: 'ALL',\n                            description: 'Only if all conditions are met it goes into \"true\" branch',\n                            value: 'all',\n                        },\n                        {\n                            name: 'ANY',\n                            description: 'If any of the conditions is met it goes into \"true\" branch',\n                            value: 'any',\n                        },\n                    ],\n                    default: 'all',\n                    description: 'If multiple rules got set this settings decides if it is true as soon as ANY condition matches or only if ALL get meet',\n                },\n            ],\n        };\n    }\n    async execute() {\n        const returnDataTrue = [];\n        const returnDataFalse = [];\n        const items = this.getInputData();\n        let item;\n        let combineOperation;\n        const isDateObject = (value) => Object.prototype.toString.call(value) === '[object Date]';\n        const isDateInvalid = (value) => (value === null || value === void 0 ? void 0 : value.toString()) === 'Invalid Date';\n        const compareOperationFunctions = {\n            after: (value1, value2) => (value1 || 0) > (value2 || 0),\n            before: (value1, value2) => (value1 || 0) < (value2 || 0),\n            contains: (value1, value2) => (value1 || '').toString().includes((value2 || '').toString()),\n            notContains: (value1, value2) => !(value1 || '').toString().includes((value2 || '').toString()),\n            endsWith: (value1, value2) => value1.endsWith(value2),\n            notEndsWith: (value1, value2) => !value1.endsWith(value2),\n            equal: (value1, value2) => value1 === value2,\n            notEqual: (value1, value2) => value1 !== value2,\n            larger: (value1, value2) => (value1 || 0) > (value2 || 0),\n            largerEqual: (value1, value2) => (value1 || 0) >= (value2 || 0),\n            smaller: (value1, value2) => (value1 || 0) < (value2 || 0),\n            smallerEqual: (value1, value2) => (value1 || 0) <= (value2 || 0),\n            startsWith: (value1, value2) => value1.startsWith(value2),\n            notStartsWith: (value1, value2) => !value1.startsWith(value2),\n            isEmpty: (value1) => [undefined, null, '', NaN].includes(value1) ||\n                (typeof value1 === 'object' && value1 !== null && !isDateObject(value1)\n                    ? Object.entries(value1).length === 0\n                    : false) ||\n                (isDateObject(value1) && isDateInvalid(value1)),\n            isNotEmpty: (value1) => !([undefined, null, '', NaN].includes(value1) ||\n                (typeof value1 === 'object' && value1 !== null && !isDateObject(value1)\n                    ? Object.entries(value1).length === 0\n                    : false) ||\n                (isDateObject(value1) && isDateInvalid(value1))),\n            regex: (value1, value2) => {\n                const regexMatch = (value2 || '').toString().match(new RegExp('^/(.*?)/([gimusy]*)$'));\n                let regex;\n                if (!regexMatch) {\n                    regex = new RegExp((value2 || '').toString());\n                }\n                else if (regexMatch.length === 1) {\n                    regex = new RegExp(regexMatch[1]);\n                }\n                else {\n                    regex = new RegExp(regexMatch[1], regexMatch[2]);\n                }\n                return !!(value1 || '').toString().match(regex);\n            },\n            notRegex: (value1, value2) => {\n                const regexMatch = (value2 || '').toString().match(new RegExp('^/(.*?)/([gimusy]*)$'));\n                let regex;\n                if (!regexMatch) {\n                    regex = new RegExp((value2 || '').toString());\n                }\n                else if (regexMatch.length === 1) {\n                    regex = new RegExp(regexMatch[1]);\n                }\n                else {\n                    regex = new RegExp(regexMatch[1], regexMatch[2]);\n                }\n                return !(value1 || '').toString().match(regex);\n            },\n        };\n        const convertDateTime = (value) => {\n            let returnValue = undefined;\n            if (typeof value === 'string') {\n                returnValue = new Date(value).getTime();\n            }\n            else if (typeof value === 'number') {\n                returnValue = value;\n            }\n            if (moment_1.default.isMoment(value)) {\n                returnValue = value.unix();\n            }\n            if (value instanceof Date) {\n                returnValue = value.getTime();\n            }\n            if (returnValue === undefined || isNaN(returnValue)) {\n                throw new n8n_workflow_1.NodeOperationError(this.getNode(), `The value \"${value}\" is not a valid DateTime.`);\n            }\n            return returnValue;\n        };\n        const dataTypes = ['boolean', 'dateTime', 'number', 'string'];\n        let dataType;\n        let compareOperationResult;\n        let value1, value2;\n        itemLoop: for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {\n            item = items[itemIndex];\n            let compareData;\n            combineOperation = this.getNodeParameter('combineOperation', itemIndex);\n            for (dataType of dataTypes) {\n                for (compareData of this.getNodeParameter(`conditions.${dataType}`, itemIndex, [])) {\n                    value1 = compareData.value1;\n                    value2 = compareData.value2;\n                    if (dataType === 'dateTime') {\n                        value1 = convertDateTime(value1);\n                        value2 = convertDateTime(value2);\n                    }\n                    compareOperationResult = compareOperationFunctions[compareData.operation](value1, value2);\n                    if (compareOperationResult && combineOperation === 'any') {\n                        returnDataTrue.push(item);\n                        continue itemLoop;\n                    }\n                    else if (!compareOperationResult && combineOperation === 'all') {\n                        returnDataFalse.push(item);\n                        continue itemLoop;\n                    }\n                }\n            }\n            if (item.pairedItem === undefined) {\n                item.pairedItem = [{ item: itemIndex }];\n            }\n            if (combineOperation === 'all') {\n                returnDataTrue.push(item);\n            }\n            else {\n                returnDataFalse.push(item);\n            }\n        }\n        return [returnDataTrue, returnDataFalse];\n    }\n}\nexports.If = If;\n//# sourceMappingURL=If.node.js.map",
  "package_info": {
    "name": "n8n-nodes-base",
    "version": "1.14.1",
    "description": "Base nodes of n8n",
    "license": "SEE LICENSE IN LICENSE.md",
    "homepage": "https://n8n.io",
    "author": {
      "name": "Jan Oberhauser",
      "email": "[email protected]"
    },
    "main": "index.js",
    "repository": {
      "type": "git",
      "url": "git+https://github.com/n8n-io/n8n.git"
    },
    "files": [
      "dist"
    ],
    "n8n": {
      "credentials": [
        "dist/credentials/ActionNetworkApi.credentials.js",
        "dist/credentials/ActiveCampaignApi.credentials.js",
        "dist/credentials/AcuitySchedulingApi.credentials.js",
        "dist/credentials/AcuitySchedulingOAuth2Api.credentials.js",
        "dist/credentials/AdaloApi.credentials.js",
        "dist/credentials/AffinityApi.credentials.js",
        "dist/credentials/AgileCrmApi.credentials.js",
        "dist/credentials/AirtableApi.credentials.js",
        "dist/credentials/AirtableOAuth2Api.credentials.js",
        "dist/credentials/AirtableTokenApi.credentials.js",
        "dist/credentials/AlienVaultApi.credentials.js",
        "dist/credentials/Amqp.credentials.js",
        "dist/credentials/ApiTemplateIoApi.credentials.js",
        "dist/credentials/AsanaApi.credentials.js",
        "dist/credentials/AsanaOAuth2Api.credentials.js",
        "dist/credentials/Auth0ManagementApi.credentials.js",
        "dist/credentials/AutomizyApi.credentials.js",
        "dist/credentials/AutopilotApi.credentials.js",
        "dist/credentials/Aws.credentials.js",
        "dist/credentials/BambooHrApi.credentials.js",
        "dist/credentials/BannerbearApi.credentials.js",
        "dist/credentials/BaserowApi.credentials.js",
        "dist/credentials/BeeminderApi.credentials.js",
        "dist/credentials/BitbucketApi.credentials.js",
        "dist/credentials/BitlyApi.credentials.js",
        "dist/credentials/BitlyOAuth2Api.credentials.js",
        "dist/credentials/BitwardenApi.credentials.js",
        "dist/credentials/BoxOAuth2Api.credentials.js",
        "dist/credentials/BrandfetchApi.credentials.js",
        "dist/credentials/BubbleApi.credentials.js",
        "dist/credentials/CalApi.credentials.js",
        "dist/credentials/CalendlyApi.credentials.js",
        "dist/credentials/CarbonBlackApi.credentials.js",
        "dist/credentials/ChargebeeApi.credentials.js",
        "dist/credentials/CircleCiApi.credentials.js",
        "dist/credentials/CiscoMerakiApi.credentials.js",
        "dist/credentials/CiscoSecureEndpointApi.credentials.js",
        "dist/credentials/CiscoWebexOAuth2Api.credentials.js",
        "dist/credentials/CiscoUmbrellaApi.credentials.js",
        "dist/credentials/CitrixAdcApi.credentials.js",
        "dist/credentials/CloudflareApi.credentials.js",
        "dist/credentials/ClearbitApi.credentials.js",
        "dist/credentials/ClickUpApi.credentials.js",
        "dist/credentials/ClickUpOAuth2Api.credentials.js",
        "dist/credentials/ClockifyApi.credentials.js",
        "dist/credentials/CockpitApi.credentials.js",
        "dist/credentials/CodaApi.credentials.js",
        "dist/credentials/ContentfulApi.credentials.js",
        "dist/credentials/ConvertKitApi.credentials.js",
        "dist/credentials/CopperApi.credentials.js",
        "dist/credentials/CortexApi.credentials.js",
        "dist/credentials/CrateDb.credentials.js",
        "dist/credentials/CrowdStrikeOAuth2Api.credentials.js",
        "dist/credentials/CrowdDevApi.credentials.js",
        "dist/credentials/CustomerIoApi.credentials.js",
        "dist/credentials/DeepLApi.credentials.js",
        "dist/credentials/DemioApi.credentials.js",
        "dist/credentials/DhlApi.credentials.js",
        "dist/credentials/DiscourseApi.credentials.js",
        "dist/credentials/DisqusApi.credentials.js",
        "dist/credentials/DriftApi.credentials.js",
        "dist/credentials/DriftOAuth2Api.credentials.js",
        "dist/credentials/DropboxApi.credentials.js",
        "dist/credentials/DropboxOAuth2Api.credentials.js",
        "dist/credentials/DropcontactApi.credentials.js",
        "dist/credentials/EgoiApi.credentials.js",
        "dist/credentials/ElasticsearchApi.credentials.js",
        "dist/credentials/ElasticSecurityApi.credentials.js",
        "dist/credentials/EmeliaApi.credentials.js",
        "dist/credentials/ERPNextApi.credentials.js",
        "dist/credentials/EventbriteApi.credentials.js",
        "dist/credentials/EventbriteOAuth2Api.credentials.js",
        "dist/credentials/F5BigIpApi.credentials.js",
        "dist/credentials/FacebookGraphApi.credentials.js",
        "dist/credentials/FacebookGraphAppApi.credentials.js",
        "dist/credentials/FacebookLeadAdsOAuth2Api.credentials.js",
        "dist/credentials/FigmaApi.credentials.js",
        "dist/credentials/FileMaker.credentials.js",
        "dist/credentials/FlowApi.credentials.js",
        "dist/credentials/FormIoApi.credentials.js",
        "dist/credentials/FormstackApi.credentials.js",
        "dist/credentials/FormstackOAuth2Api.credentials.js",
        "dist/credentials/FortiGateApi.credentials.js",
        "dist/credentials/FreshdeskApi.credentials.js",
        "dist/credentials/FreshserviceApi.credentials.js",
        "dist/credentials/FreshworksCrmApi.credentials.js",
        "dist/credentials/Ftp.credentials.js",
        "dist/credentials/GetResponseApi.credentials.js",
        "dist/credentials/GetResponseOAuth2Api.credentials.js",
        "dist/credentials/GhostAdminApi.credentials.js",
        "dist/credentials/GhostContentApi.credentials.js",
        "dist/credentials/GithubApi.credentials.js",
        "dist/credentials/GithubOAuth2Api.credentials.js",
        "dist/credentials/GitlabApi.credentials.js",
        "dist/credentials/GitlabOAuth2Api.credentials.js",
        "dist/credentials/GitPassword.credentials.js",
        "dist/credentials/GmailOAuth2Api.credentials.js",
        "dist/credentials/GoogleAdsOAuth2Api.credentials.js",
        "dist/credentials/GoogleAnalyticsOAuth2Api.credentials.js",
        "dist/credentials/GoogleApi.credentials.js",
        "dist/credentials/GoogleBigQueryOAuth2Api.credentials.js",
        "dist/credentials/GoogleBooksOAuth2Api.credentials.js",
        "dist/credentials/GoogleCalendarOAuth2Api.credentials.js",
        "dist/credentials/GoogleCloudNaturalLanguageOAuth2Api.credentials.js",
        "dist/credentials/GoogleCloudStorageOAuth2Api.credentials.js",
        "dist/credentials/GoogleContactsOAuth2Api.credentials.js",
        "dist/credentials/GoogleDocsOAuth2Api.credentials.js",
        "dist/credentials/GoogleDriveOAuth2Api.credentials.js",
        "dist/credentials/GoogleFirebaseCloudFirestoreOAuth2Api.credentials.js",
        "dist/credentials/GoogleFirebaseRealtimeDatabaseOAuth2Api.credentials.js",
        "dist/credentials/GoogleOAuth2Api.credentials.js",
        "dist/credentials/GooglePerspectiveOAuth2Api.credentials.js",
        "dist/credentials/GoogleSheetsOAuth2Api.credentials.js",
        "dist/credentials/GoogleSheetsTriggerOAuth2Api.credentials.js",
        "dist/credentials/GoogleSlidesOAuth2Api.credentials.js",
        "dist/credentials/GoogleTasksOAuth2Api.credentials.js",
        "dist/credentials/GoogleTranslateOAuth2Api.credentials.js",
        "dist/credentials/GotifyApi.credentials.js",
        "dist/credentials/GoToWebinarOAuth2Api.credentials.js",
        "dist/credentials/GristApi.credentials.js",
        "dist/credentials/GrafanaApi.credentials.js",
        "dist/credentials/GSuiteAdminOAuth2Api.credentials.js",
        "dist/credentials/GumroadApi.credentials.js",
        "dist/credentials/HaloPSAApi.credentials.js",
        "dist/credentials/HarvestApi.credentials.js",
        "dist/credentials/HarvestOAuth2Api.credentials.js",
        "dist/credentials/HelpScoutOAuth2Api.credentials.js",
        "dist/credentials/HighLevelApi.credentials.js",
        "dist/credentials/HomeAssistantApi.credentials.js",
        "dist/credentials/HttpBasicAuth.credentials.js",
        "dist/credentials/HttpDigestAuth.credentials.js",
        "dist/credentials/HttpHeaderAuth.credentials.js",
        "dist/credentials/HttpCustomAuth.credentials.js",
        "dist/credentials/HttpQueryAuth.credentials.js",
        "dist/credentials/HubspotApi.credentials.js",
        "dist/credentials/HubspotAppToken.credentials.js",
        "dist/credentials/HubspotDeveloperApi.credentials.js",
        "dist/credentials/HubspotOAuth2Api.credentials.js",
        "dist/credentials/HumanticAiApi.credentials.js",
        "dist/credentials/HunterApi.credentials.js",
        "dist/credentials/HybridAnalysisApi.credentials.js",
        "dist/credentials/Imap.credentials.js",
        "dist/credentials/ImpervaWafApi.credentials.js",
        "dist/credentials/IntercomApi.credentials.js",
        "dist/credentials/InvoiceNinjaApi.credentials.js",
        "dist/credentials/IterableApi.credentials.js",
        "dist/credentials/JenkinsApi.credentials.js",
        "dist/credentials/JiraSoftwareCloudApi.credentials.js",
        "dist/credentials/JiraSoftwareServerApi.credentials.js",
        "dist/credentials/JotFormApi.credentials.js",
        "dist/credentials/Kafka.credentials.js",
        "dist/credentials/KeapOAuth2Api.credentials.js",
        "dist/credentials/KibanaApi.credentials.js",
        "dist/credentials/KitemakerApi.credentials.js",
        "dist/credentials/KoBoToolboxApi.credentials.js",
        "dist/credentials/Ldap.credentials.js",
        "dist/credentials/LemlistApi.credentials.js",
        "dist/credentials/LinearApi.credentials.js",
        "dist/credentials/LinearOAuth2Api.credentials.js",
        "dist/credentials/LineNotifyOAuth2Api.credentials.js",
        "dist/credentials/LingvaNexApi.credentials.js",
        "dist/credentials/LinkedInOAuth2Api.credentials.js",
        "dist/credentials/LoneScaleApi.credentials.js",
        "dist/credentials/Magento2Api.credentials.js",
        "dist/credentials/MailcheckApi.credentials.js",
        "dist/credentials/MailchimpApi.credentials.js",
        "dist/credentials/MailchimpOAuth2Api.credentials.js",
        "dist/credentials/MailerLiteApi.credentials.js",
        "dist/credentials/MailgunApi.credentials.js",
        "dist/credentials/MailjetEmailApi.credentials.js",
        "dist/credentials/MailjetSmsApi.credentials.js",
        "dist/credentials/MandrillApi.credentials.js",
        "dist/credentials/MarketstackApi.credentials.js",
        "dist/credentials/MatrixApi.credentials.js",
        "dist/credentials/MattermostApi.credentials.js",
        "dist/credentials/MauticApi.credentials.js",
        "dist/credentials/MauticOAuth2Api.credentials.js",
        "dist/credentials/MediumApi.credentials.js",
        "dist/credentials/MediumOAuth2Api.credentials.js",
        "dist/credentials/MetabaseApi.credentials.js",
        "dist/credentials/MessageBirdApi.credentials.js",
        "dist/credentials/MetabaseApi.credentials.js",
        "dist/credentials/MicrosoftDynamicsOAuth2Api.credentials.js",
        "dist/credentials/MicrosoftEntraOAuth2Api.credentials.js",
        "dist/credentials/MicrosoftExcelOAuth2Api.credentials.js",
        "dist/credentials/MicrosoftGraphSecurityOAuth2Api.credentials.js",
        "dist/credentials/MicrosoftOAuth2Api.credentials.js",
        "dist/credentials/MicrosoftOneDriveOAuth2Api.credentials.js",
        "dist/credentials/MicrosoftOutlookOAuth2Api.credentials.js",
        "dist/credentials/MicrosoftSql.credentials.js",
        "dist/credentials/MicrosoftTeamsOAuth2Api.credentials.js",
        "dist/credentials/MicrosoftToDoOAuth2Api.credentials.js",
        "dist/credentials/MindeeInvoiceApi.credentials.js",
        "dist/credentials/MindeeReceiptApi.credentials.js",
        "dist/credentials/MispApi.credentials.js",
        "dist/credentials/MistApi.credentials.js",
        "dist/credentials/MoceanApi.credentials.js",
        "dist/credentials/MondayComApi.credentials.js",
        "dist/credentials/MondayComOAuth2Api.credentials.js",
        "dist/credentials/MongoDb.credentials.js",
        "dist/credentials/MonicaCrmApi.credentials.js",
        "dist/credentials/Mqtt.credentials.js",
        "dist/credentials/Msg91Api.credentials.js",
        "dist/credentials/MySql.credentials.js",
        "dist/credentials/N8nApi.credentials.js",
        "dist/credentials/NasaApi.credentials.js",
        "dist/credentials/NetlifyApi.credentials.js",
        "dist/credentials/NextCloudApi.credentials.js",
        "dist/credentials/NextCloudOAuth2Api.credentials.js",
        "dist/credentials/NocoDb.credentials.js",
        "dist/credentials/NocoDbApiToken.credentials.js",
        "dist/credentials/NotionApi.credentials.js",
        "dist/credentials/NotionOAuth2Api.credentials.js",
        "dist/credentials/NpmApi.credentials.js",
        "dist/credentials/OAuth1Api.credentials.js",
        "dist/credentials/OAuth2Api.credentials.js",
        "dist/credentials/OdooApi.credentials.js",
        "dist/credentials/OktaApi.credentials.js",
        "dist/credentials/OneSimpleApi.credentials.js",
        "dist/credentials/OnfleetApi.credentials.js",
        "dist/credentials/OpenAiApi.credentials.js",
        "dist/credentials/OpenCTIApi.credentials.js",
        "dist/credentials/OpenWeatherMapApi.credentials.js",
        "dist/credentials/OrbitApi.credentials.js",
        "dist/credentials/OuraApi.credentials.js",
        "dist/credentials/PaddleApi.credentials.js",
        "dist/credentials/PagerDutyApi.credentials.js",
        "dist/credentials/PagerDutyOAuth2Api.credentials.js",
        "dist/credentials/PayPalApi.credentials.js",
        "dist/credentials/PeekalinkApi.credentials.js",
        "dist/credentials/PhantombusterApi.credentials.js",
        "dist/credentials/PhilipsHueOAuth2Api.credentials.js",
        "dist/credentials/PipedriveApi.credentials.js",
        "dist/credentials/PipedriveOAuth2Api.credentials.js",
        "dist/credentials/PlivoApi.credentials.js",
        "dist/credentials/Postgres.credentials.js",
        "dist/credentials/PostHogApi.credentials.js",
        "dist/credentials/PostmarkApi.credentials.js",
        "dist/credentials/ProfitWellApi.credentials.js",
        "dist/credentials/PushbulletOAuth2Api.credentials.js",
        "dist/credentials/PushcutApi.credentials.js",
        "dist/credentials/PushoverApi.credentials.js",
        "dist/credentials/QRadarApi.credentials.js",
        "dist/credentials/QualysApi.credentials.js",
        "dist/credentials/QuestDb.credentials.js",
        "dist/credentials/QuickBaseApi.credentials.js",
        "dist/credentials/QuickBooksOAuth2Api.credentials.js",
        "dist/credentials/RabbitMQ.credentials.js",
        "dist/credentials/RaindropOAuth2Api.credentials.js",
        "dist/credentials/RecordedFutureApi.credentials.js",
        "dist/credentials/RedditOAuth2Api.credentials.js",
        "dist/credentials/Redis.credentials.js",
        "dist/credentials/RocketchatApi.credentials.js",
        "dist/credentials/RundeckApi.credentials.js",
        "dist/credentials/S3.credentials.js",
        "dist/credentials/SalesforceJwtApi.credentials.js",
        "dist/credentials/SalesforceOAuth2Api.credentials.js",
        "dist/credentials/SalesmateApi.credentials.js",
        "dist/credentials/SeaTableApi.credentials.js",
        "dist/credentials/SecurityScorecardApi.credentials.js",
        "dist/credentials/SegmentApi.credentials.js",
        "dist/credentials/SekoiaApi.credentials.js",
        "dist/credentials/SendGridApi.credentials.js",
        "dist/credentials/BrevoApi.credentials.js",
        "dist/credentials/SendyApi.credentials.js",
        "dist/credentials/SentryIoApi.credentials.js",
        "dist/credentials/SentryIoOAuth2Api.credentials.js",
        "dist/credentials/SentryIoServerApi.credentials.js",
        "dist/credentials/ServiceNowOAuth2Api.credentials.js",
        "dist/credentials/ServiceNowBasicApi.credentials.js",
        "dist/credentials/Sftp.credentials.js",
        "dist/credentials/ShopifyApi.credentials.js",
        "dist/credentials/ShopifyAccessTokenApi.credentials.js",
        "dist/credentials/ShopifyOAuth2Api.credentials.js",
        "dist/credentials/Signl4Api.credentials.js",
        "dist/credentials/SlackApi.credentials.js",
        "dist/credentials/SlackOAuth2Api.credentials.js",
        "dist/credentials/Sms77Api.credentials.js",
        "dist/credentials/Smtp.credentials.js",
        "dist/credentials/Snowflake.credentials.js",
        "dist/credentials/SplunkApi.credentials.js",
        "dist/credentials/SpontitApi.credentials.js",
        "dist/credentials/SpotifyOAuth2Api.credentials.js",
        "dist/credentials/ShufflerApi.credentials.js",
        "dist/credentials/SshPassword.credentials.js",
        "dist/credentials/SshPrivateKey.credentials.js",
        "dist/credentials/StackbyApi.credentials.js",
        "dist/credentials/StoryblokContentApi.credentials.js",
        "dist/credentials/StoryblokManagementApi.credentials.js",
        "dist/credentials/StrapiApi.credentials.js",
        "dist/credentials/StrapiTokenApi.credentials.js",
        "dist/credentials/StravaOAuth2Api.credentials.js",
        "dist/credentials/StripeApi.credentials.js",
        "dist/credentials/SupabaseApi.credentials.js",
        "dist/credentials/SurveyMonkeyApi.credentials.js",
        "dist/credentials/SurveyMonkeyOAuth2Api.credentials.js",
        "dist/credentials/SyncroMspApi.credentials.js",
        "dist/credentials/TaigaApi.credentials.js",
        "dist/credentials/TapfiliateApi.credentials.js",
        "dist/credentials/TelegramApi.credentials.js",
        "dist/credentials/TheHiveProjectApi.credentials.js",
        "dist/credentials/TheHiveApi.credentials.js",
        "dist/credentials/TimescaleDb.credentials.js",
        "dist/credentials/TodoistApi.credentials.js",
        "dist/credentials/TodoistOAuth2Api.credentials.js",
        "dist/credentials/TogglApi.credentials.js",
        "dist/credentials/TotpApi.credentials.js",
        "dist/credentials/TravisCiApi.credentials.js",
        "dist/credentials/TrellixEpoApi.credentials.js",
        "dist/credentials/TrelloApi.credentials.js",
        "dist/credentials/TwakeCloudApi.credentials.js",
        "dist/credentials/TwakeServerApi.credentials.js",
        "dist/credentials/TwilioApi.credentials.js",
        "dist/credentials/TwistOAuth2Api.credentials.js",
        "dist/credentials/TwitterOAuth1Api.credentials.js",
        "dist/credentials/TwitterOAuth2Api.credentials.js",
        "dist/credentials/TypeformApi.credentials.js",
        "dist/credentials/TypeformOAuth2Api.credentials.js",
        "dist/credentials/UnleashedSoftwareApi.credentials.js",
        "dist/credentials/UpleadApi.credentials.js",
        "dist/credentials/UProcApi.credentials.js",
        "dist/credentials/UptimeRobotApi.credentials.js",
        "dist/credentials/UrlScanIoApi.credentials.js",
        "dist/credentials/VeroApi.credentials.js",
        "dist/credentials/VirusTotalApi.credentials.js",
        "dist/credentials/VonageApi.credentials.js",
        "dist/credentials/VenafiTlsProtectCloudApi.credentials.js",
        "dist/credentials/VenafiTlsProtectDatacenterApi.credentials.js",
        "dist/credentials/WebflowApi.credentials.js",
        "dist/credentials/WebflowOAuth2Api.credentials.js",
        "dist/credentials/WekanApi.credentials.js",
        "dist/credentials/WhatsAppApi.credentials.js",
        "dist/credentials/WiseApi.credentials.js",
        "dist/credentials/WooCommerceApi.credentials.js",
        "dist/credentials/WordpressApi.credentials.js",
        "dist/credentials/WorkableApi.credentials.js",
        "dist/credentials/WufooApi.credentials.js",
        "dist/credentials/XeroOAuth2Api.credentials.js",
        "dist/credentials/YourlsApi.credentials.js",
        "dist/credentials/YouTubeOAuth2Api.credentials.js",
        "dist/credentials/ZammadBasicAuthApi.credentials.js",
        "dist/credentials/ZammadTokenAuthApi.credentials.js",
        "dist/credentials/ZendeskApi.credentials.js",
        "dist/credentials/ZendeskOAuth2Api.credentials.js",
        "dist/credentials/ZohoOAuth2Api.credentials.js",
        "dist/credentials/ZoomApi.credentials.js",
        "dist/credentials/ZoomOAuth2Api.credentials.js",
        "dist/credentials/ZscalerZiaApi.credentials.js",
        "dist/credentials/ZulipApi.credentials.js"
      ],
      "nodes": [
        "dist/nodes/ActionNetwork/ActionNetwork.node.js",
        "dist/nodes/ActiveCampaign/ActiveCampaign.node.js",
        "dist/nodes/ActiveCampaign/ActiveCampaignTrigger.node.js",
        "dist/nodes/AcuityScheduling/AcuitySchedulingTrigger.node.js",
        "dist/nodes/Adalo/Adalo.node.js",
        "dist/nodes/Affinity/Affinity.node.js",
        "dist/nodes/Affinity/AffinityTrigger.node.js",
        "dist/nodes/AgileCrm/AgileCrm.node.js",
        "dist/nodes/Airtable/Airtable.node.js",
        "dist/nodes/Airtable/AirtableTrigger.node.js",
        "dist/nodes/Amqp/Amqp.node.js",
        "dist/nodes/Amqp/AmqpTrigger.node.js",
        "dist/nodes/ApiTemplateIo/ApiTemplateIo.node.js",
        "dist/nodes/Asana/Asana.node.js",
        "dist/nodes/Asana/AsanaTrigger.node.js",
        "dist/nodes/Automizy/Automizy.node.js",
        "dist/nodes/Autopilot/Autopilot.node.js",
        "dist/nodes/Autopilot/AutopilotTrigger.node.js",
        "dist/nodes/Aws/AwsLambda.node.js",
        "dist/nodes/Aws/AwsSns.node.js",
        "dist/nodes/Aws/AwsSnsTrigger.node.js",
        "dist/nodes/Aws/CertificateManager/AwsCertificateManager.node.js",
        "dist/nodes/Aws/Comprehend/AwsComprehend.node.js",
        "dist/nodes/Aws/DynamoDB/AwsDynamoDB.node.js",
        "dist/nodes/Aws/ELB/AwsElb.node.js",
        "dist/nodes/Aws/Rekognition/AwsRekognition.node.js",
        "dist/nodes/Aws/S3/AwsS3.node.js",
        "dist/nodes/Aws/SES/AwsSes.node.js",
        "dist/nodes/Aws/SQS/AwsSqs.node.js",
        "dist/nodes/Aws/Textract/AwsTextract.node.js",
        "dist/nodes/Aws/Transcribe/AwsTranscribe.node.js",
        "dist/nodes/BambooHr/BambooHr.node.js",
        "dist/nodes/Bannerbear/Bannerbear.node.js",
        "dist/nodes/Baserow/Baserow.node.js",
        "dist/nodes/Beeminder/Beeminder.node.js",
        "dist/nodes/Bitbucket/BitbucketTrigger.node.js",
        "dist/nodes/Bitly/Bitly.node.js",
        "dist/nodes/Bitwarden/Bitwarden.node.js",
        "dist/nodes/Box/Box.node.js",
        "dist/nodes/Box/BoxTrigger.node.js",
        "dist/nodes/Brandfetch/Brandfetch.node.js",
        "dist/nodes/Bubble/Bubble.node.js",
        "dist/nodes/Cal/CalTrigger.node.js",
        "dist/nodes/Calendly/CalendlyTrigger.node.js",
        "dist/nodes/Chargebee/Chargebee.node.js",
        "dist/nodes/Chargebee/ChargebeeTrigger.node.js",
        "dist/nodes/CircleCi/CircleCi.node.js",
        "dist/nodes/Cisco/Webex/CiscoWebex.node.js",
        "dist/nodes/Citrix/ADC/CitrixAdc.node.js",
        "dist/nodes/Cisco/Webex/CiscoWebexTrigger.node.js",
        "dist/nodes/Cloudflare/Cloudflare.node.js",
        "dist/nodes/Clearbit/Clearbit.node.js",
        "dist/nodes/ClickUp/ClickUp.node.js",
        "dist/nodes/ClickUp/ClickUpTrigger.node.js",
        "dist/nodes/Clockify/Clockify.node.js",
        "dist/nodes/Clockify/ClockifyTrigger.node.js",
        "dist/nodes/Cockpit/Cockpit.node.js",
        "dist/nodes/Coda/Coda.node.js",
        "dist/nodes/Code/Code.node.js",
        "dist/nodes/CoinGecko/CoinGecko.node.js",
        "dist/nodes/CompareDatasets/CompareDatasets.node.js",
        "dist/nodes/Compression/Compression.node.js",
        "dist/nodes/Contentful/Contentful.node.js",
        "dist/nodes/ConvertKit/ConvertKit.node.js",
        "dist/nodes/ConvertKit/ConvertKitTrigger.node.js",
        "dist/nodes/Copper/Copper.node.js",
        "dist/nodes/Copper/CopperTrigger.node.js",
        "dist/nodes/Cortex/Cortex.node.js",
        "dist/nodes/CrateDb/CrateDb.node.js",
        "dist/nodes/Cron/Cron.node.js",
        "dist/nodes/CrowdDev/CrowdDev.node.js",
        "dist/nodes/CrowdDev/CrowdDevTrigger.node.js",
        "dist/nodes/Crypto/Crypto.node.js",
        "dist/nodes/CustomerIo/CustomerIo.node.js",
        "dist/nodes/CustomerIo/CustomerIoTrigger.node.js",
        "dist/nodes/DateTime/DateTime.node.js",
        "dist/nodes/DebugHelper/DebugHelper.node.js",
        "dist/nodes/DeepL/DeepL.node.js",
        "dist/nodes/Demio/Demio.node.js",
        "dist/nodes/Dhl/Dhl.node.js",
        "dist/nodes/Discord/Discord.node.js",
        "dist/nodes/Discourse/Discourse.node.js",
        "dist/nodes/Disqus/Disqus.node.js",
        "dist/nodes/Drift/Drift.node.js",
        "dist/nodes/Dropbox/Dropbox.node.js",
        "dist/nodes/Dropcontact/Dropcontact.node.js",
        "dist/nodes/EditImage/EditImage.node.js",
        "dist/nodes/E2eTest/E2eTest.node.js",
        "dist/nodes/Egoi/Egoi.node.js",
        "dist/nodes/Elastic/Elasticsearch/Elasticsearch.node.js",
        "dist/nodes/Elastic/ElasticSecurity/ElasticSecurity.node.js",
        "dist/nodes/EmailReadImap/EmailReadImap.node.js",
        "dist/nodes/EmailSend/EmailSend.node.js",
        "dist/nodes/Emelia/Emelia.node.js",
        "dist/nodes/Emelia/EmeliaTrigger.node.js",
        "dist/nodes/ERPNext/ERPNext.node.js",
        "dist/nodes/ErrorTrigger/ErrorTrigger.node.js",
        "dist/nodes/Eventbrite/EventbriteTrigger.node.js",
        "dist/nodes/ExecuteCommand/ExecuteCommand.node.js",
        "dist/nodes/ExecuteWorkflow/ExecuteWorkflow.node.js",
        "dist/nodes/ExecuteWorkflowTrigger/ExecuteWorkflowTrigger.node.js",
        "dist/nodes/ExecutionData/ExecutionData.node.js",
        "dist/nodes/Facebook/FacebookGraphApi.node.js",
        "dist/nodes/Facebook/FacebookTrigger.node.js",
        "dist/nodes/FacebookLeadAds/FacebookLeadAdsTrigger.node.js",
        "dist/nodes/Figma/FigmaTrigger.node.js",
        "dist/nodes/FileMaker/FileMaker.node.js",
        "dist/nodes/Filter/Filter.node.js",
        "dist/nodes/Flow/Flow.node.js",
        "dist/nodes/Flow/FlowTrigger.node.js",
        "dist/nodes/Form/FormTrigger.node.js",
        "dist/nodes/FormIo/FormIoTrigger.node.js",
        "dist/nodes/Formstack/FormstackTrigger.node.js",
        "dist/nodes/Freshdesk/Freshdesk.node.js",
        "dist/nodes/Freshservice/Freshservice.node.js",
        "dist/nodes/FreshworksCrm/FreshworksCrm.node.js",
        "dist/nodes/Ftp/Ftp.node.js",
        "dist/nodes/Function/Function.node.js",
        "dist/nodes/FunctionItem/FunctionItem.node.js",
        "dist/nodes/GetResponse/GetResponse.node.js",
        "dist/nodes/GetResponse/GetResponseTrigger.node.js",
        "dist/nodes/Ghost/Ghost.node.js",
        "dist/nodes/Git/Git.node.js",
        "dist/nodes/Github/Github.node.js",
        "dist/nodes/Github/GithubTrigger.node.js",
        "dist/nodes/Gitlab/Gitlab.node.js",
        "dist/nodes/Gitlab/GitlabTrigger.node.js",
        "dist/nodes/Google/Ads/GoogleAds.node.js",
        "dist/nodes/Google/Analytics/GoogleAnalytics.node.js",
        "dist/nodes/Google/BigQuery/GoogleBigQuery.node.js",
        "dist/nodes/Google/Books/GoogleBooks.node.js",
        "dist/nodes/Google/Calendar/GoogleCalendar.node.js",
        "dist/nodes/Google/Calendar/GoogleCalendarTrigger.node.js",
        "dist/nodes/Google/Chat/GoogleChat.node.js",
        "dist/nodes/Google/CloudNaturalLanguage/GoogleCloudNaturalLanguage.node.js",
        "dist/nodes/Google/CloudStorage/GoogleCloudStorage.node.js",
        "dist/nodes/Google/Contacts/GoogleContacts.node.js",
        "dist/nodes/Google/Docs/GoogleDocs.node.js",
        "dist/nodes/Google/Drive/GoogleDrive.node.js",
        "dist/nodes/Google/Drive/GoogleDriveTrigger.node.js",
        "dist/nodes/Google/Firebase/CloudFirestore/GoogleFirebaseCloudFirestore.node.js",
        "dist/nodes/Google/Firebase/RealtimeDatabase/GoogleFirebaseRealtimeDatabase.node.js",
        "dist/nodes/Google/Gmail/Gmail.node.js",
        "dist/nodes/Google/Gmail/GmailTrigger.node.js",
        "dist/nodes/Google/GSuiteAdmin/GSuiteAdmin.node.js",
        "dist/nodes/Google/Perspective/GooglePerspective.node.js",
        "dist/nodes/Google/Sheet/GoogleSheets.node.js",
        "dist/nodes/Google/Sheet/GoogleSheetsTrigger.node.js",
        "dist/nodes/Google/Slides/GoogleSlides.node.js",
        "dist/nodes/Google/Task/GoogleTasks.node.js",
        "dist/nodes/Google/Translate/GoogleTranslate.node.js",
        "dist/nodes/Google/YouTube/YouTube.node.js",
        "dist/nodes/Gotify/Gotify.node.js",
        "dist/nodes/GoToWebinar/GoToWebinar.node.js",
        "dist/nodes/Grafana/Grafana.node.js",
        "dist/nodes/GraphQL/GraphQL.node.js",
        "dist/nodes/Grist/Grist.node.js",
        "dist/nodes/Gumroad/GumroadTrigger.node.js",
        "dist/nodes/HackerNews/HackerNews.node.js",
        "dist/nodes/HaloPSA/HaloPSA.node.js",
        "dist/nodes/Harvest/Harvest.node.js",
        "dist/nodes/HelpScout/HelpScout.node.js",
        "dist/nodes/HelpScout/HelpScoutTrigger.node.js",
        "dist/nodes/HighLevel/HighLevel.node.js",
        "dist/nodes/HomeAssistant/HomeAssistant.node.js",
        "dist/nodes/HtmlExtract/HtmlExtract.node.js",
        "dist/nodes/Html/Html.node.js",
        "dist/nodes/HttpRequest/HttpRequest.node.js",
        "dist/nodes/Hubspot/Hubspot.node.js",
        "dist/nodes/Hubspot/HubspotTrigger.node.js",
        "dist/nodes/HumanticAI/HumanticAi.node.js",
        "dist/nodes/Hunter/Hunter.node.js",
        "dist/nodes/ICalendar/ICalendar.node.js",
        "dist/nodes/If/If.node.js",
        "dist/nodes/Intercom/Intercom.node.js",
        "dist/nodes/Interval/Interval.node.js",
        "dist/nodes/InvoiceNinja/InvoiceNinja.node.js",
        "dist/nodes/InvoiceNinja/InvoiceNinjaTrigger.node.js",
        "dist/nodes/ItemLists/ItemLists.node.js",
        "dist/nodes/Iterable/Iterable.node.js",
        "dist/nodes/Jenkins/Jenkins.node.js",
        "dist/nodes/Jira/Jira.node.js",
        "dist/nodes/Jira/JiraTrigger.node.js",
        "dist/nodes/JotForm/JotFormTrigger.node.js",
        "dist/nodes/Kafka/Kafka.node.js",
        "dist/nodes/Kafka/KafkaTrigger.node.js",
        "dist/nodes/Keap/Keap.node.js",
        "dist/nodes/Keap/KeapTrigger.node.js",
        "dist/nodes/Kitemaker/Kitemaker.node.js",
        "dist/nodes/KoBoToolbox/KoBoToolbox.node.js",
        "dist/nodes/KoBoToolbox/KoBoToolboxTrigger.node.js",
        "dist/nodes/Ldap/Ldap.node.js",
        "dist/nodes/Lemlist/Lemlist.node.js",
        "dist/nodes/Lemlist/LemlistTrigger.node.js",
        "dist/nodes/Line/Line.node.js",
        "dist/nodes/Linear/Linear.node.js",
        "dist/nodes/Linear/LinearTrigger.node.js",
        "dist/nodes/LingvaNex/LingvaNex.node.js",
        "dist/nodes/LinkedIn/LinkedIn.node.js",
        "dist/nodes/LocalFileTrigger/LocalFileTrigger.node.js",
        "dist/nodes/LoneScale/LoneScaleTrigger.node.js",
        "dist/nodes/LoneScale/LoneScale.node.js",
        "dist/nodes/Magento/Magento2.node.js",
        "dist/nodes/Mailcheck/Mailcheck.node.js",
        "dist/nodes/Mailchimp/Mailchimp.node.js",
        "dist/nodes/Mailchimp/MailchimpTrigger.node.js",
        "dist/nodes/MailerLite/MailerLite.node.js",
        "dist/nodes/MailerLite/MailerLiteTrigger.node.js",
        "dist/nodes/Mailgun/Mailgun.node.js",
        "dist/nodes/Mailjet/Mailjet.node.js",
        "dist/nodes/Mailjet/MailjetTrigger.node.js",
        "dist/nodes/Mandrill/Mandrill.node.js",
        "dist/nodes/ManualTrigger/ManualTrigger.node.js",
        "dist/nodes/Markdown/Markdown.node.js",
        "dist/nodes/Marketstack/Marketstack.node.js",
        "dist/nodes/Matrix/Matrix.node.js",
        "dist/nodes/Mattermost/Mattermost.node.js",
        "dist/nodes/Mautic/Mautic.node.js",
        "dist/nodes/Mautic/MauticTrigger.node.js",
        "dist/nodes/Medium/Medium.node.js",
        "dist/nodes/Merge/Merge.node.js",
        "dist/nodes/MessageBird/MessageBird.node.js",
        "dist/nodes/Metabase/Metabase.node.js",
        "dist/nodes/Microsoft/Dynamics/MicrosoftDynamicsCrm.node.js",
        "dist/nodes/Microsoft/Excel/MicrosoftExcel.node.js",
        "dist/nodes/Microsoft/GraphSecurity/MicrosoftGraphSecurity.node.js",
        "dist/nodes/Microsoft/OneDrive/MicrosoftOneDrive.node.js",
        "dist/nodes/Microsoft/Outlook/MicrosoftOutlook.node.js",
        "dist/nodes/Microsoft/Sql/MicrosoftSql.node.js",
        "dist/nodes/Microsoft/Teams/MicrosoftTeams.node.js",
        "dist/nodes/Microsoft/ToDo/MicrosoftToDo.node.js",
        "dist/nodes/Mindee/Mindee.node.js",
        "dist/nodes/Misp/Misp.node.js",
        "dist/nodes/Mocean/Mocean.node.js",
        "dist/nodes/MondayCom/MondayCom.node.js",
        "dist/nodes/MongoDb/MongoDb.node.js",
        "dist/nodes/MonicaCrm/MonicaCrm.node.js",
        "dist/nodes/MoveBinaryData/MoveBinaryData.node.js",
        "dist/nodes/MQTT/Mqtt.node.js",
        "dist/nodes/MQTT/MqttTrigger.node.js",
        "dist/nodes/Msg91/Msg91.node.js",
        "dist/nodes/MySql/MySql.node.js",
        "dist/nodes/N8n/N8n.node.js",
        "dist/nodes/N8nTrainingCustomerDatastore/N8nTrainingCustomerDatastore.node.js",
        "dist/nodes/N8nTrainingCustomerMessenger/N8nTrainingCustomerMessenger.node.js",
        "dist/nodes/N8nTrigger/N8nTrigger.node.js",
        "dist/nodes/Nasa/Nasa.node.js",
        "dist/nodes/Netlify/Netlify.node.js",
        "dist/nodes/Netlify/NetlifyTrigger.node.js",
        "dist/nodes/NextCloud/NextCloud.node.js",
        "dist/nodes/NocoDB/NocoDB.node.js",
        "dist/nodes/Brevo/Brevo.node.js",
        "dist/nodes/Brevo/BrevoTrigger.node.js",
        "dist/nodes/StickyNote/StickyNote.node.js",
        "dist/nodes/NoOp/NoOp.node.js",
        "dist/nodes/Onfleet/Onfleet.node.js",
        "dist/nodes/Onfleet/OnfleetTrigger.node.js",
        "dist/nodes/Notion/Notion.node.js",
        "dist/nodes/Notion/NotionTrigger.node.js",
        "dist/nodes/Npm/Npm.node.js",
        "dist/nodes/Odoo/Odoo.node.js",
        "dist/nodes/OneSimpleApi/OneSimpleApi.node.js",
        "dist/nodes/OpenAi/OpenAi.node.js",
        "dist/nodes/OpenThesaurus/OpenThesaurus.node.js",
        "dist/nodes/OpenWeatherMap/OpenWeatherMap.node.js",
        "dist/nodes/Orbit/Orbit.node.js",
        "dist/nodes/Oura/Oura.node.js",
        "dist/nodes/Paddle/Paddle.node.js",
        "dist/nodes/PagerDuty/PagerDuty.node.js",
        "dist/nodes/PayPal/PayPal.node.js",
        "dist/nodes/PayPal/PayPalTrigger.node.js",
        "dist/nodes/Peekalink/Peekalink.node.js",
        "dist/nodes/Phantombuster/Phantombuster.node.js",
        "dist/nodes/PhilipsHue/PhilipsHue.node.js",
        "dist/nodes/Pipedrive/Pipedrive.node.js",
        "dist/nodes/Pipedrive/PipedriveTrigger.node.js",
        "dist/nodes/Plivo/Plivo.node.js",
        "dist/nodes/PostBin/PostBin.node.js",
        "dist/nodes/Postgres/Postgres.node.js",
        "dist/nodes/Postgres/PostgresTrigger.node.js",
        "dist/nodes/PostHog/PostHog.node.js",
        "dist/nodes/Postmark/PostmarkTrigger.node.js",
        "dist/nodes/ProfitWell/ProfitWell.node.js",
        "dist/nodes/Pushbullet/Pushbullet.node.js",
        "dist/nodes/Pushcut/Pushcut.node.js",
        "dist/nodes/Pushcut/PushcutTrigger.node.js",
        "dist/nodes/Pushover/Pushover.node.js",
        "dist/nodes/QuestDb/QuestDb.node.js",
        "dist/nodes/QuickBase/QuickBase.node.js",
        "dist/nodes/QuickBooks/QuickBooks.node.js",
        "dist/nodes/QuickChart/QuickChart.node.js",
        "dist/nodes/RabbitMQ/RabbitMQ.node.js",
        "dist/nodes/RabbitMQ/RabbitMQTrigger.node.js",
        "dist/nodes/Raindrop/Raindrop.node.js",
        "dist/nodes/ReadBinaryFile/ReadBinaryFile.node.js",
        "dist/nodes/ReadBinaryFiles/ReadBinaryFiles.node.js",
        "dist/nodes/ReadPdf/ReadPDF.node.js",
        "dist/nodes/Reddit/Reddit.node.js",
        "dist/nodes/Redis/Redis.node.js",
        "dist/nodes/Redis/RedisTrigger.node.js",
        "dist/nodes/RenameKeys/RenameKeys.node.js",
        "dist/nodes/RespondToWebhook/RespondToWebhook.node.js",
        "dist/nodes/Rocketchat/Rocketchat.node.js",
        "dist/nodes/RssFeedRead/RssFeedRead.node.js",
        "dist/nodes/RssFeedRead/RssFeedReadTrigger.node.js",
        "dist/nodes/Rundeck/Rundeck.node.js",
        "dist/nodes/S3/S3.node.js",
        "dist/nodes/Salesforce/Salesforce.node.js",
        "dist/nodes/Salesmate/Salesmate.node.js",
        "dist/nodes/Schedule/ScheduleTrigger.node.js",
        "dist/nodes/SeaTable/SeaTable.node.js",
        "dist/nodes/SeaTable/SeaTableTrigger.node.js",
        "dist/nodes/SecurityScorecard/SecurityScorecard.node.js",
        "dist/nodes/Segment/Segment.node.js",
        "dist/nodes/SendGrid/SendGrid.node.js",
        "dist/nodes/Sendy/Sendy.node.js",
        "dist/nodes/SentryIo/SentryIo.node.js",
        "dist/nodes/ServiceNow/ServiceNow.node.js",
        "dist/nodes/Set/Set.node.js",
        "dist/nodes/Shopify/Shopify.node.js",
        "dist/nodes/Shopify/ShopifyTrigger.node.js",
        "dist/nodes/Signl4/Signl4.node.js",
        "dist/nodes/Slack/Slack.node.js",
        "dist/nodes/Sms77/Sms77.node.js",
        "dist/nodes/Snowflake/Snowflake.node.js",
        "dist/nodes/SplitInBatches/SplitInBatches.node.js",
        "dist/nodes/Splunk/Splunk.node.js",
        "dist/nodes/Spontit/Spontit.node.js",
        "dist/nodes/Spotify/Spotify.node.js",
        "dist/nodes/SpreadsheetFile/SpreadsheetFile.node.js",
        "dist/nodes/SseTrigger/SseTrigger.node.js",
        "dist/nodes/Ssh/Ssh.node.js",
        "dist/nodes/Stackby/Stackby.node.js",
        "dist/nodes/Start/Start.node.js",
        "dist/nodes/StopAndError/StopAndError.node.js",
        "dist/nodes/Storyblok/Storyblok.node.js",
        "dist/nodes/Strapi/Strapi.node.js",
        "dist/nodes/Strava/Strava.node.js",
        "dist/nodes/Strava/StravaTrigger.node.js",
        "dist/nodes/Stripe/Stripe.node.js",
        "dist/nodes/Stripe/StripeTrigger.node.js",
        "dist/nodes/Supabase/Supabase.node.js",
        "dist/nodes/SurveyMonkey/SurveyMonkeyTrigger.node.js",
        "dist/nodes/Switch/Switch.node.js",
        "dist/nodes/SyncroMSP/SyncroMsp.node.js",
        "dist/nodes/Taiga/Taiga.node.js",
        "dist/nodes/Taiga/TaigaTrigger.node.js",
        "dist/nodes/Tapfiliate/Tapfiliate.node.js",
        "dist/nodes/Telegram/Telegram.node.js",
        "dist/nodes/Telegram/TelegramTrigger.node.js",
        "dist/nodes/TheHiveProject/TheHiveProject.node.js",
        "dist/nodes/TheHiveProject/TheHiveProjectTrigger.node.js",
        "dist/nodes/TheHive/TheHive.node.js",
        "dist/nodes/TheHive/TheHiveTrigger.node.js",
        "dist/nodes/TimescaleDb/TimescaleDb.node.js",
        "dist/nodes/Todoist/Todoist.node.js",
        "dist/nodes/Toggl/TogglTrigger.node.js",
        "dist/nodes/Totp/Totp.node.js",
        "dist/nodes/TravisCi/TravisCi.node.js",
        "dist/nodes/Trello/Trello.node.js",
        "dist/nodes/Trello/TrelloTrigger.node.js",
        "dist/nodes/Twake/Twake.node.js",
        "dist/nodes/Twilio/Twilio.node.js",
        "dist/nodes/Twist/Twist.node.js",
        "dist/nodes/Twitter/Twitter.node.js",
        "dist/nodes/Typeform/TypeformTrigger.node.js",
        "dist/nodes/UnleashedSoftware/UnleashedSoftware.node.js",
        "dist/nodes/Uplead/Uplead.node.js",
        "dist/nodes/UProc/UProc.node.js",
        "dist/nodes/UptimeRobot/UptimeRobot.node.js",
        "dist/nodes/UrlScanIo/UrlScanIo.node.js",
        "dist/nodes/Vero/Vero.node.js",
        "dist/nodes/Venafi/ProtectCloud/VenafiTlsProtectCloud.node.js",
        "dist/nodes/Venafi/ProtectCloud/VenafiTlsProtectCloudTrigger.node.js",
        "dist/nodes/Venafi/Datacenter/VenafiTlsProtectDatacenter.node.js",
        "dist/nodes/Vonage/Vonage.node.js",
        "dist/nodes/Wait/Wait.node.js",
        "dist/nodes/Webflow/Webflow.node.js",
        "dist/nodes/Webflow/WebflowTrigger.node.js",
        "dist/nodes/Webhook/Webhook.node.js",
        "dist/nodes/Wekan/Wekan.node.js",
        "dist/nodes/WhatsApp/WhatsApp.node.js",
        "dist/nodes/Wise/Wise.node.js",
        "dist/nodes/Wise/WiseTrigger.node.js",
        "dist/nodes/WooCommerce/WooCommerce.node.js",
        "dist/nodes/WooCommerce/WooCommerceTrigger.node.js",
        "dist/nodes/Wordpress/Wordpress.node.js",
        "dist/nodes/Workable/WorkableTrigger.node.js",
        "dist/nodes/WorkflowTrigger/WorkflowTrigger.node.js",
        "dist/nodes/WriteBinaryFile/WriteBinaryFile.node.js",
        "dist/nodes/Wufoo/WufooTrigger.node.js",
        "dist/nodes/Xero/Xero.node.js",
        "dist/nodes/Xml/Xml.node.js",
        "dist/nodes/Yourls/Yourls.node.js",
        "dist/nodes/Zammad/Zammad.node.js",
        "dist/nodes/Zendesk/Zendesk.node.js",
        "dist/nodes/Zendesk/ZendeskTrigger.node.js",
        "dist/nodes/Zoho/ZohoCrm.node.js",
        "dist/nodes/Zoom/Zoom.node.js",
        "dist/nodes/Zulip/Zulip.node.js"
      ]
    },
    "devDependencies": {
      "@types/amqplib": "^0.10.1",
      "@types/aws4": "^1.5.1",
      "@types/basic-auth": "^1.1.3",
      "@types/cheerio": "^0.22.15",
      "@types/cron": "~1.7.1",
      "@types/eventsource": "^1.1.2",
      "@types/express": "^4.17.6",
      "@types/gm": "^1.25.0",
      "@types/imap-simple": "^4.2.0",
      "@types/js-nacl": "^1.3.0",
      "@types/jsonwebtoken": "^9.0.1",
      "@types/lodash": "^4.14.195",
      "@types/lossless-json": "^1.0.0",
      "@types/mailparser": "^2.7.3",
      "@types/mime-types": "^2.1.0",
      "@types/mssql": "^6.0.2",
      "@types/node-ssh": "^7.0.1",
      "@types/nodemailer": "^6.4.0",
      "@types/promise-ftp": "^1.3.4",
      "@types/redis": "^2.8.11",
      "@types/request-promise-native": "~1.0.15",
      "@types/rfc2047": "^2.0.1",
      "@types/showdown": "^1.9.4",
      "@types/snowflake-sdk": "^1.6.12",
      "@types/ssh2-sftp-client": "^5.1.0",
      "@types/tmp": "^0.2.0",
      "@types/uuid": "^8.3.2",
      "@types/xml2js": "^0.4.11",
      "eslint-plugin-n8n-nodes-base": "^1.16.0",
      "gulp": "^4.0.0",
      "n8n-core": "1.14.1"
    },
    "dependencies": {
      "@kafkajs/confluent-schema-registry": "1.0.6",
      "@n8n/vm2": "^3.9.20",
      "amqplib": "^0.10.3",
      "aws4": "^1.8.0",
      "basic-auth": "^2.0.1",
      "change-case": "^4.1.1",
      "cheerio": "1.0.0-rc.6",
      "chokidar": "3.5.2",
      "cron": "~1.7.2",
      "csv-parse": "^5.5.0",
      "currency-codes": "^2.1.0",
      "eventsource": "^2.0.2",
      "fast-glob": "^3.2.5",
      "fflate": "^0.7.0",
      "get-system-fonts": "^2.0.2",
      "gm": "^1.25.0",
      "iconv-lite": "^0.6.2",
      "ics": "^2.27.0",
      "imap-simple": "^4.3.0",
      "isbot": "^3.6.13",
      "iso-639-1": "^2.1.3",
      "js-nacl": "^1.4.0",
      "jsonwebtoken": "^9.0.0",
      "kafkajs": "^1.14.0",
      "ldapts": "^4.2.6",
      "lodash": "^4.17.21",
      "lossless-json": "^1.0.4",
      "luxon": "^3.3.0",
      "mailparser": "^3.2.0",
      "minifaker": "^1.34.1",
      "moment": "~2.29.2",
      "moment-timezone": "^0.5.28",
      "mongodb": "^4.17.1",
      "mqtt": "^5.0.2",
      "mssql": "^8.1.2",
      "mysql2": "~2.3.0",
      "nanoid": "^3.3.6",
      "node-html-markdown": "^1.1.3",
      "node-ssh": "^12.0.0",
      "nodemailer": "^6.7.1",
      "otpauth": "^9.1.1",
      "pdfjs-dist": "^2.16.105",
      "pg": "^8.3.0",
      "pg-promise": "^10.5.8",
      "pretty-bytes": "^5.6.0",
      "promise-ftp": "^1.3.5",
      "pyodide": "^0.23.4",
      "redis": "^3.1.1",
      "rfc2047": "^4.0.1",
      "rhea": "^1.0.11",
      "rss-parser": "^3.7.0",
      "semver": "^7.5.4",
      "showdown": "^2.0.3",
      "simple-git": "^3.17.0",
      "snowflake-sdk": "^1.8.0",
      "ssh2-sftp-client": "^7.0.0",
      "tmp-promise": "^3.0.2",
      "typedi": "^0.10.0",
      "uuid": "^8.3.2",
      "xlsx": "https://cdn.sheetjs.com/xlsx-0.19.3/xlsx-0.19.3.tgz",
      "xml2js": "^0.5.0",
      "n8n-workflow": "1.14.1"
    },
    "scripts": {
      "clean": "rimraf dist .turbo",
      "dev": "pnpm watch",
      "typecheck": "tsc",
      "build": "tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json && gulp build:icons && gulp build:translations && pnpm build:metadata",
      "build:translations": "gulp build:translations",
      "build:metadata": "pnpm n8n-generate-known && pnpm n8n-generate-ui-types",
      "format": "prettier --write . --ignore-path ../../.prettierignore",
      "lint": "eslint . --quiet && node ./scripts/validate-load-options-methods.js",
      "lintfix": "eslint . --fix",
      "watch": "tsc-watch -p tsconfig.build.json --onCompilationComplete \"tsc-alias -p tsconfig.build.json\" --onSuccess \"pnpm n8n-generate-ui-types\"",
      "test": "jest"
    }
  },
  "extraction_time_ms": 4,
  "extracted_at": "2025-06-07T17:49:22.724Z"
}
```

--------------------------------------------------------------------------------
/tests/unit/services/node-specific-validators.test.ts:
--------------------------------------------------------------------------------

```typescript
import { describe, it, expect, beforeEach } from 'vitest';
import { NodeSpecificValidators, NodeValidationContext } from '@/services/node-specific-validators';
import { ValidationError, ValidationWarning } from '@/services/config-validator';

describe('NodeSpecificValidators', () => {
  let context: NodeValidationContext;

  beforeEach(() => {
    context = {
      config: {},
      errors: [],
      warnings: [],
      suggestions: [],
      autofix: {}
    };
  });

  describe('validateSlack', () => {
    describe('message send operation', () => {
      beforeEach(() => {
        context.config = {
          resource: 'message',
          operation: 'send'
        };
      });

      it('should require channel for sending messages', () => {
        NodeSpecificValidators.validateSlack(context);
        
        expect(context.errors).toHaveLength(2); // channel and text errors
        expect(context.errors[0]).toMatchObject({
          type: 'missing_required',
          property: 'channel',
          message: 'Channel is required to send a message'
        });
      });

      it('should accept channelId as alternative to channel', () => {
        context.config.channelId = 'C1234567890';
        context.config.text = 'Hello';
        
        NodeSpecificValidators.validateSlack(context);
        
        const channelErrors = context.errors.filter(e => e.property === 'channel');
        expect(channelErrors).toHaveLength(0);
      });

      it('should require message content', () => {
        context.config.channel = '#general';
        
        NodeSpecificValidators.validateSlack(context);
        
        expect(context.errors).toContainEqual({
          type: 'missing_required',
          property: 'text',
          message: 'Message content is required - provide text, blocks, or attachments',
          fix: 'Add text field with your message content'
        });
      });

      it('should accept blocks as alternative to text', () => {
        context.config.channel = '#general';
        context.config.blocks = [{ type: 'section', text: { type: 'mrkdwn', text: 'Hello' } }];
        
        NodeSpecificValidators.validateSlack(context);
        
        const textErrors = context.errors.filter(e => e.property === 'text');
        expect(textErrors).toHaveLength(0);
      });

      it('should accept attachments as alternative to text', () => {
        context.config.channel = '#general';
        context.config.attachments = [{ text: 'Attachment text' }];
        
        NodeSpecificValidators.validateSlack(context);
        
        const textErrors = context.errors.filter(e => e.property === 'text');
        expect(textErrors).toHaveLength(0);
      });

      it('should warn about text exceeding character limit', () => {
        context.config.channel = '#general';
        context.config.text = 'a'.repeat(40001);
        
        NodeSpecificValidators.validateSlack(context);
        
        expect(context.warnings).toContainEqual({
          type: 'inefficient',
          property: 'text',
          message: 'Message text exceeds Slack\'s 40,000 character limit',
          suggestion: 'Split into multiple messages or use a file upload'
        });
      });

      it('should warn about missing threadTs when replying to thread', () => {
        context.config.channel = '#general';
        context.config.text = 'Reply';
        context.config.replyToThread = true;
        
        NodeSpecificValidators.validateSlack(context);
        
        expect(context.warnings).toContainEqual({
          type: 'missing_common',
          property: 'threadTs',
          message: 'Thread timestamp required when replying to thread',
          suggestion: 'Set threadTs to the timestamp of the thread parent message'
        });
      });

      it('should suggest linkNames for mentions', () => {
        context.config.channel = '#general';
        context.config.text = 'Hello @user';
        
        NodeSpecificValidators.validateSlack(context);
        
        expect(context.suggestions).toContain('Set linkNames=true to convert @mentions to user links');
        expect(context.autofix.linkNames).toBe(true);
      });
    });

    describe('message update operation', () => {
      beforeEach(() => {
        context.config = {
          resource: 'message',
          operation: 'update'
        };
      });

      it('should require timestamp for updating messages', () => {
        NodeSpecificValidators.validateSlack(context);
        
        expect(context.errors).toContainEqual({
          type: 'missing_required',
          property: 'ts',
          message: 'Message timestamp (ts) is required to update a message',
          fix: 'Provide the timestamp of the message to update'
        });
      });

      it('should require channel for updating messages', () => {
        context.config.ts = '1234567890.123456';
        
        NodeSpecificValidators.validateSlack(context);
        
        expect(context.errors).toContainEqual({
          type: 'missing_required',
          property: 'channel',
          message: 'Channel is required to update a message',
          fix: 'Provide the channel where the message exists'
        });
      });
    });

    describe('message delete operation', () => {
      beforeEach(() => {
        context.config = {
          resource: 'message',
          operation: 'delete'
        };
      });

      it('should require timestamp for deleting messages', () => {
        NodeSpecificValidators.validateSlack(context);
        
        expect(context.errors).toContainEqual({
          type: 'missing_required',
          property: 'ts',
          message: 'Message timestamp (ts) is required to delete a message',
          fix: 'Provide the timestamp of the message to delete'
        });
      });

      it('should warn about permanent deletion', () => {
        context.config.ts = '1234567890.123456';
        context.config.channel = '#general';
        
        NodeSpecificValidators.validateSlack(context);
        
        expect(context.warnings).toContainEqual({
          type: 'security',
          message: 'Message deletion is permanent and cannot be undone',
          suggestion: 'Consider archiving or updating the message instead if you need to preserve history'
        });
      });
    });

    describe('channel create operation', () => {
      beforeEach(() => {
        context.config = {
          resource: 'channel',
          operation: 'create'
        };
      });

      it('should require channel name', () => {
        NodeSpecificValidators.validateSlack(context);
        
        expect(context.errors).toContainEqual({
          type: 'missing_required',
          property: 'name',
          message: 'Channel name is required',
          fix: 'Provide a channel name (lowercase, no spaces, 1-80 characters)'
        });
      });

      it('should validate channel name format', () => {
        context.config.name = 'Test Channel';
        
        NodeSpecificValidators.validateSlack(context);
        
        expect(context.errors).toContainEqual({
          type: 'invalid_value',
          property: 'name',
          message: 'Channel names cannot contain spaces',
          fix: 'Use hyphens or underscores instead of spaces'
        });
      });

      it('should require lowercase channel names', () => {
        context.config.name = 'TestChannel';
        
        NodeSpecificValidators.validateSlack(context);
        
        expect(context.errors).toContainEqual({
          type: 'invalid_value',
          property: 'name',
          message: 'Channel names must be lowercase',
          fix: 'Convert the channel name to lowercase'
        });
      });

      it('should validate channel name length', () => {
        context.config.name = 'a'.repeat(81);
        
        NodeSpecificValidators.validateSlack(context);
        
        expect(context.errors).toContainEqual({
          type: 'invalid_value',
          property: 'name',
          message: 'Channel name exceeds 80 character limit',
          fix: 'Shorten the channel name'
        });
      });
    });

    describe('user operations', () => {
      it('should require user identifier for get operation', () => {
        context.config = {
          resource: 'user',
          operation: 'get'
        };
        
        NodeSpecificValidators.validateSlack(context);
        
        expect(context.errors).toContainEqual({
          type: 'missing_required',
          property: 'user',
          message: 'User identifier required - use email, user ID, or username',
          fix: 'Set user to an email like "[email protected]" or user ID like "U1234567890"'
        });
      });
    });

    describe('error handling', () => {
      it('should suggest error handling for Slack operations', () => {
        context.config = {
          resource: 'message',
          operation: 'send',
          channel: '#general',
          text: 'Hello'
        };
        
        NodeSpecificValidators.validateSlack(context);
        
        expect(context.warnings).toContainEqual({
          type: 'best_practice',
          property: 'errorHandling',
          message: 'Slack API can have rate limits and transient failures',
          suggestion: 'Add onError: "continueRegularOutput" with retryOnFail for resilience'
        });
        
        expect(context.autofix).toMatchObject({
          onError: 'continueRegularOutput',
          retryOnFail: true,
          maxTries: 2,
          waitBetweenTries: 3000
        });
      });

      it('should warn about deprecated continueOnFail', () => {
        context.config = {
          resource: 'message',
          operation: 'send',
          channel: '#general',
          text: 'Hello',
          continueOnFail: true
        };
        
        NodeSpecificValidators.validateSlack(context);
        
        expect(context.warnings).toContainEqual({
          type: 'deprecated',
          property: 'continueOnFail',
          message: 'continueOnFail is deprecated. Use onError instead',
          suggestion: 'Replace with onError: "continueRegularOutput"'
        });
      });
    });
  });

  describe('validateGoogleSheets', () => {
    describe('common validations', () => {
      it('should require spreadsheet ID', () => {
        context.config = {
          operation: 'read'
        };
        
        NodeSpecificValidators.validateGoogleSheets(context);
        
        expect(context.errors).toContainEqual({
          type: 'missing_required',
          property: 'sheetId',
          message: 'Spreadsheet ID is required',
          fix: 'Provide the Google Sheets document ID from the URL'
        });
      });

      it('should accept documentId as alternative to sheetId', () => {
        context.config = {
          operation: 'read',
          documentId: '1234567890',
          range: 'Sheet1!A:B'
        };
        
        NodeSpecificValidators.validateGoogleSheets(context);
        
        const sheetIdErrors = context.errors.filter(e => e.property === 'sheetId');
        expect(sheetIdErrors).toHaveLength(0);
      });
    });

    describe('append operation', () => {
      beforeEach(() => {
        context.config = {
          operation: 'append',
          sheetId: '1234567890'
        };
      });

      it('should require range or columns for append', () => {
        NodeSpecificValidators.validateGoogleSheets(context);

        expect(context.errors).toContainEqual({
          type: 'missing_required',
          property: 'range',
          message: 'Range or columns mapping is required for append operation',
          fix: 'Specify range like "Sheet1!A:B" OR use columns with mappingMode'
        });
      });

      it('should suggest valueInputMode', () => {
        context.config.range = 'Sheet1!A:B';
        
        NodeSpecificValidators.validateGoogleSheets(context);
        
        expect(context.warnings).toContainEqual({
          type: 'missing_common',
          property: 'options.valueInputMode',
          message: 'Consider setting valueInputMode for proper data formatting',
          suggestion: 'Use "USER_ENTERED" to parse formulas and dates, or "RAW" for literal values'
        });
        
        expect(context.autofix.options).toMatchObject({
          valueInputMode: 'USER_ENTERED'
        });
      });
    });

    describe('read operation', () => {
      beforeEach(() => {
        context.config = {
          operation: 'read',
          sheetId: '1234567890'
        };
      });

      it('should require range for read', () => {
        NodeSpecificValidators.validateGoogleSheets(context);
        
        expect(context.errors).toContainEqual({
          type: 'missing_required',
          property: 'range',
          message: 'Range is required for read operation',
          fix: 'Specify range like "Sheet1!A:B" or "Sheet1!A1:B10"'
        });
      });

      it('should suggest data structure option', () => {
        context.config.range = 'Sheet1!A:B';
        
        NodeSpecificValidators.validateGoogleSheets(context);
        
        expect(context.suggestions).toContain('Consider setting options.dataStructure to "object" for easier data manipulation');
      });
    });

    describe('update operation', () => {
      beforeEach(() => {
        context.config = {
          operation: 'update',
          sheetId: '1234567890'
        };
      });

      it('should require range for update', () => {
        NodeSpecificValidators.validateGoogleSheets(context);
        
        expect(context.errors).toContainEqual({
          type: 'missing_required',
          property: 'range',
          message: 'Range is required for update operation',
          fix: 'Specify the exact range to update like "Sheet1!A1:B10"'
        });
      });

      it('should require values for update', () => {
        context.config.range = 'Sheet1!A1:B10';
        
        NodeSpecificValidators.validateGoogleSheets(context);
        
        expect(context.errors).toContainEqual({
          type: 'missing_required',
          property: 'values',
          message: 'Values are required for update operation',
          fix: 'Provide the data to write to the spreadsheet'
        });
      });

      it('should accept rawData as alternative to values', () => {
        context.config.range = 'Sheet1!A1:B10';
        context.config.rawData = [[1, 2], [3, 4]];
        
        NodeSpecificValidators.validateGoogleSheets(context);
        
        const valuesErrors = context.errors.filter(e => e.property === 'values');
        expect(valuesErrors).toHaveLength(0);
      });
    });

    describe('delete operation', () => {
      beforeEach(() => {
        context.config = {
          operation: 'delete',
          sheetId: '1234567890'
        };
      });

      it('should require toDelete specification', () => {
        NodeSpecificValidators.validateGoogleSheets(context);
        
        expect(context.errors).toContainEqual({
          type: 'missing_required',
          property: 'toDelete',
          message: 'Specify what to delete (rows or columns)',
          fix: 'Set toDelete to "rows" or "columns"'
        });
      });

      it('should require startIndex for row deletion', () => {
        context.config.toDelete = 'rows';
        
        NodeSpecificValidators.validateGoogleSheets(context);
        
        expect(context.errors).toContainEqual({
          type: 'missing_required',
          property: 'startIndex',
          message: 'Start index is required when deleting rows',
          fix: 'Specify the starting row index (0-based)'
        });
      });

      it('should accept startIndex of 0', () => {
        context.config.toDelete = 'rows';
        context.config.startIndex = 0;
        
        NodeSpecificValidators.validateGoogleSheets(context);
        
        const startIndexErrors = context.errors.filter(e => e.property === 'startIndex');
        expect(startIndexErrors).toHaveLength(0);
      });

      it('should warn about permanent deletion', () => {
        context.config.toDelete = 'rows';
        context.config.startIndex = 0;
        
        NodeSpecificValidators.validateGoogleSheets(context);
        
        expect(context.warnings).toContainEqual({
          type: 'security',
          message: 'Deletion is permanent. Consider backing up data first',
          suggestion: 'Read the data before deletion to create a backup'
        });
      });
    });

    describe('range validation', () => {
      beforeEach(() => {
        context.config = {
          operation: 'read',
          sheetId: '1234567890'
        };
      });

      it('should suggest including sheet name in range', () => {
        context.config.range = 'A1:B10';
        
        NodeSpecificValidators.validateGoogleSheets(context);
        
        expect(context.warnings).toContainEqual({
          type: 'inefficient',
          property: 'range',
          message: 'Range should include sheet name for clarity',
          suggestion: 'Format: "SheetName!A1:B10" or "SheetName!A:B"'
        });
      });

      it('should validate sheet names with spaces', () => {
        context.config.range = 'Sheet Name!A1:B10';
        
        NodeSpecificValidators.validateGoogleSheets(context);
        
        expect(context.errors).toContainEqual({
          type: 'invalid_value',
          property: 'range',
          message: 'Sheet names with spaces must be quoted',
          fix: 'Use single quotes around sheet name: \'Sheet Name\'!A1:B10'
        });
      });

      it('should accept quoted sheet names with spaces', () => {
        context.config.range = "'Sheet Name'!A1:B10";
        
        NodeSpecificValidators.validateGoogleSheets(context);
        
        const rangeErrors = context.errors.filter(e => e.property === 'range' && e.message.includes('quoted'));
        expect(rangeErrors).toHaveLength(0);
      });

      it('should validate A1 notation format', () => {
        // Use an invalid range that doesn't match the A1 pattern
        context.config.range = 'Sheet1!123ABC';
        
        NodeSpecificValidators.validateGoogleSheets(context);
        
        expect(context.warnings).toContainEqual({
          type: 'inefficient',
          property: 'range',
          message: 'Range may not be in valid A1 notation',
          suggestion: 'Examples: "Sheet1!A1:B10", "Sheet1!A:B", "Sheet1!1:10"'
        });
      });
    });
  });

  describe('validateOpenAI', () => {
    describe('chat create operation', () => {
      beforeEach(() => {
        context.config = {
          resource: 'chat',
          operation: 'create'
        };
      });

      it('should require model selection', () => {
        NodeSpecificValidators.validateOpenAI(context);
        
        expect(context.errors).toContainEqual({
          type: 'missing_required',
          property: 'model',
          message: 'Model selection is required',
          fix: 'Choose a model like "gpt-4", "gpt-3.5-turbo", etc.'
        });
      });

      it('should warn about deprecated models', () => {
        context.config.model = 'text-davinci-003';
        context.config.messages = [{ role: 'user', content: 'Hello' }];
        
        NodeSpecificValidators.validateOpenAI(context);
        
        expect(context.warnings).toContainEqual({
          type: 'deprecated',
          property: 'model',
          message: 'Model text-davinci-003 is deprecated',
          suggestion: 'Use "gpt-3.5-turbo" or "gpt-4" instead'
        });
      });

      it('should require messages or prompt', () => {
        context.config.model = 'gpt-4';
        
        NodeSpecificValidators.validateOpenAI(context);
        
        expect(context.errors).toContainEqual({
          type: 'missing_required',
          property: 'messages',
          message: 'Messages or prompt required for chat completion',
          fix: 'Add messages array or use the prompt field'
        });
      });

      it('should accept prompt as alternative to messages', () => {
        context.config.model = 'gpt-4';
        context.config.prompt = 'Hello AI';
        
        NodeSpecificValidators.validateOpenAI(context);
        
        const messageErrors = context.errors.filter(e => e.property === 'messages');
        expect(messageErrors).toHaveLength(0);
      });

      it('should warn about high token limits', () => {
        context.config.model = 'gpt-4';
        context.config.messages = [{ role: 'user', content: 'Hello' }];
        context.config.maxTokens = 5000;
        
        NodeSpecificValidators.validateOpenAI(context);
        
        expect(context.warnings).toContainEqual({
          type: 'inefficient',
          property: 'maxTokens',
          message: 'High token limit may increase costs significantly',
          suggestion: 'Consider if you really need more than 4000 tokens'
        });
      });

      it('should validate temperature range', () => {
        context.config.model = 'gpt-4';
        context.config.messages = [{ role: 'user', content: 'Hello' }];
        context.config.temperature = 2.5;
        
        NodeSpecificValidators.validateOpenAI(context);
        
        expect(context.errors).toContainEqual({
          type: 'invalid_value',
          property: 'temperature',
          message: 'Temperature must be between 0 and 2',
          fix: 'Set temperature between 0 (deterministic) and 2 (creative)'
        });
      });
    });

    describe('error handling', () => {
      it('should suggest error handling for AI API calls', () => {
        context.config = {
          resource: 'chat',
          operation: 'create',
          model: 'gpt-4',
          messages: [{ role: 'user', content: 'Hello' }]
        };
        
        NodeSpecificValidators.validateOpenAI(context);
        
        expect(context.warnings).toContainEqual({
          type: 'best_practice',
          property: 'errorHandling',
          message: 'AI APIs have rate limits and can return errors',
          suggestion: 'Add onError: "continueRegularOutput" with retryOnFail and longer wait times'
        });
        
        expect(context.autofix).toMatchObject({
          onError: 'continueRegularOutput',
          retryOnFail: true,
          maxTries: 3,
          waitBetweenTries: 5000,
          alwaysOutputData: true
        });
      });

      it('should warn about deprecated continueOnFail', () => {
        context.config = {
          resource: 'chat',
          operation: 'create',
          model: 'gpt-4',
          messages: [{ role: 'user', content: 'Hello' }],
          continueOnFail: true
        };
        
        NodeSpecificValidators.validateOpenAI(context);
        
        expect(context.warnings).toContainEqual({
          type: 'deprecated',
          property: 'continueOnFail',
          message: 'continueOnFail is deprecated. Use onError instead',
          suggestion: 'Replace with onError: "continueRegularOutput"'
        });
      });
    });
  });

  describe('validateMongoDB', () => {
    describe('common validations', () => {
      it('should require collection name', () => {
        context.config = {
          operation: 'find'
        };
        
        NodeSpecificValidators.validateMongoDB(context);
        
        expect(context.errors).toContainEqual({
          type: 'missing_required',
          property: 'collection',
          message: 'Collection name is required',
          fix: 'Specify the MongoDB collection to work with'
        });
      });
    });

    describe('find operation', () => {
      beforeEach(() => {
        context.config = {
          operation: 'find',
          collection: 'users'
        };
      });

      it('should validate query JSON', () => {
        context.config.query = '{ invalid json';
        
        NodeSpecificValidators.validateMongoDB(context);
        
        expect(context.errors).toContainEqual({
          type: 'invalid_value',
          property: 'query',
          message: 'Query must be valid JSON',
          fix: 'Ensure query is valid JSON like: {"name": "John"}'
        });
      });

      it('should accept valid JSON query', () => {
        context.config.query = '{"name": "John"}';
        
        NodeSpecificValidators.validateMongoDB(context);
        
        const queryErrors = context.errors.filter(e => e.property === 'query');
        expect(queryErrors).toHaveLength(0);
      });
    });

    describe('insert operation', () => {
      beforeEach(() => {
        context.config = {
          operation: 'insert',
          collection: 'users'
        };
      });

      it('should require document data', () => {
        NodeSpecificValidators.validateMongoDB(context);
        
        expect(context.errors).toContainEqual({
          type: 'missing_required',
          property: 'fields',
          message: 'Document data is required for insert',
          fix: 'Provide the data to insert'
        });
      });

      it('should accept documents as alternative to fields', () => {
        context.config.documents = [{ name: 'John' }];
        
        NodeSpecificValidators.validateMongoDB(context);
        
        const fieldsErrors = context.errors.filter(e => e.property === 'fields');
        expect(fieldsErrors).toHaveLength(0);
      });
    });

    describe('update operation', () => {
      beforeEach(() => {
        context.config = {
          operation: 'update',
          collection: 'users'
        };
      });

      it('should warn about update without query', () => {
        NodeSpecificValidators.validateMongoDB(context);
        
        expect(context.warnings).toContainEqual({
          type: 'security',
          message: 'Update without query will affect all documents',
          suggestion: 'Add a query to target specific documents'
        });
      });
    });

    describe('delete operation', () => {
      beforeEach(() => {
        context.config = {
          operation: 'delete',
          collection: 'users'
        };
      });

      it('should error on delete without query', () => {
        NodeSpecificValidators.validateMongoDB(context);
        
        expect(context.errors).toContainEqual({
          type: 'invalid_value',
          property: 'query',
          message: 'Delete without query would remove all documents - this is a critical security issue',
          fix: 'Add a query to specify which documents to delete'
        });
      });

      it('should error on delete with empty query', () => {
        context.config.query = '{}';
        
        NodeSpecificValidators.validateMongoDB(context);
        
        expect(context.errors).toContainEqual({
          type: 'invalid_value',
          property: 'query',
          message: 'Delete without query would remove all documents - this is a critical security issue',
          fix: 'Add a query to specify which documents to delete'
        });
      });
    });

    describe('error handling', () => {
      it('should suggest error handling for find operations', () => {
        context.config = {
          operation: 'find',
          collection: 'users'
        };
        
        NodeSpecificValidators.validateMongoDB(context);
        
        expect(context.warnings).toContainEqual({
          type: 'best_practice',
          property: 'errorHandling',
          message: 'MongoDB queries can fail due to connection issues',
          suggestion: 'Add onError: "continueRegularOutput" with retryOnFail'
        });
        
        expect(context.autofix).toMatchObject({
          onError: 'continueRegularOutput',
          retryOnFail: true,
          maxTries: 3
        });
      });

      it('should suggest different error handling for write operations', () => {
        context.config = {
          operation: 'insert',
          collection: 'users',
          fields: { name: 'John' }
        };
        
        NodeSpecificValidators.validateMongoDB(context);
        
        expect(context.warnings).toContainEqual({
          type: 'best_practice',
          property: 'errorHandling',
          message: 'MongoDB write operations should handle errors carefully',
          suggestion: 'Add onError: "continueErrorOutput" to handle write failures separately'
        });
        
        expect(context.autofix).toMatchObject({
          onError: 'continueErrorOutput',
          retryOnFail: true,
          maxTries: 2,
          waitBetweenTries: 1000
        });
      });

      it('should warn about deprecated continueOnFail', () => {
        context.config = {
          operation: 'find',
          collection: 'users',
          continueOnFail: true
        };
        
        NodeSpecificValidators.validateMongoDB(context);
        
        expect(context.warnings).toContainEqual({
          type: 'deprecated',
          property: 'continueOnFail',
          message: 'continueOnFail is deprecated. Use onError instead',
          suggestion: 'Replace with onError: "continueRegularOutput" or "continueErrorOutput"'
        });
      });
    });
  });

  describe('validatePostgres', () => {
    describe('insert operation', () => {
      beforeEach(() => {
        context.config = {
          operation: 'insert'
        };
      });

      it('should require table name', () => {
        NodeSpecificValidators.validatePostgres(context);
        
        expect(context.errors).toContainEqual({
          type: 'missing_required',
          property: 'table',
          message: 'Table name is required for insert operation',
          fix: 'Specify the table to insert data into'
        });
      });

      it('should warn about missing columns', () => {
        context.config.table = 'users';
        
        NodeSpecificValidators.validatePostgres(context);
        
        expect(context.warnings).toContainEqual({
          type: 'missing_common',
          property: 'columns',
          message: 'No columns specified for insert',
          suggestion: 'Define which columns to insert data into'
        });
      });

      it('should not warn if dataMode is set', () => {
        context.config.table = 'users';
        context.config.dataMode = 'autoMapInputData';
        
        NodeSpecificValidators.validatePostgres(context);
        
        const columnWarnings = context.warnings.filter(w => w.property === 'columns');
        expect(columnWarnings).toHaveLength(0);
      });
    });

    describe('update operation', () => {
      beforeEach(() => {
        context.config = {
          operation: 'update'
        };
      });

      it('should require table name', () => {
        NodeSpecificValidators.validatePostgres(context);
        
        expect(context.errors).toContainEqual({
          type: 'missing_required',
          property: 'table',
          message: 'Table name is required for update operation',
          fix: 'Specify the table to update'
        });
      });

      it('should warn about missing updateKey', () => {
        context.config.table = 'users';
        
        NodeSpecificValidators.validatePostgres(context);
        
        expect(context.warnings).toContainEqual({
          type: 'missing_common',
          property: 'updateKey',
          message: 'No update key specified',
          suggestion: 'Set updateKey to identify which rows to update (e.g., "id")'
        });
      });
    });

    describe('delete operation', () => {
      beforeEach(() => {
        context.config = {
          operation: 'delete'
        };
      });

      it('should require table name', () => {
        NodeSpecificValidators.validatePostgres(context);
        
        expect(context.errors).toContainEqual({
          type: 'missing_required',
          property: 'table',
          message: 'Table name is required for delete operation',
          fix: 'Specify the table to delete from'
        });
      });

      it('should require deleteKey', () => {
        context.config.table = 'users';
        
        NodeSpecificValidators.validatePostgres(context);
        
        expect(context.errors).toContainEqual({
          type: 'missing_required',
          property: 'deleteKey',
          message: 'Delete key is required to identify rows',
          fix: 'Set deleteKey (e.g., "id") to specify which rows to delete'
        });
      });
    });

    describe('execute operation', () => {
      beforeEach(() => {
        context.config = {
          operation: 'execute'
        };
      });

      it('should require SQL query', () => {
        NodeSpecificValidators.validatePostgres(context);
        
        expect(context.errors).toContainEqual({
          type: 'missing_required',
          property: 'query',
          message: 'SQL query is required',
          fix: 'Provide the SQL query to execute'
        });
      });
    });

    describe('SQL query validation', () => {
      beforeEach(() => {
        context.config = {
          operation: 'execute'
        };
      });

      it('should warn about SQL injection risks', () => {
        context.config.query = 'SELECT * FROM users WHERE id = ${userId}';
        
        NodeSpecificValidators.validatePostgres(context);
        
        expect(context.warnings).toContainEqual({
          type: 'security',
          message: 'Query contains template expressions that might be vulnerable to SQL injection',
          suggestion: 'Use parameterized queries with query parameters instead of string interpolation'
        });
      });

      it('should error on DELETE without WHERE', () => {
        context.config.query = 'DELETE FROM users';
        
        NodeSpecificValidators.validatePostgres(context);
        
        expect(context.errors).toContainEqual({
          type: 'invalid_value',
          property: 'query',
          message: 'DELETE query without WHERE clause will delete all records',
          fix: 'Add a WHERE clause to specify which records to delete'
        });
      });

      it('should warn on UPDATE without WHERE', () => {
        context.config.query = 'UPDATE users SET active = true';
        
        NodeSpecificValidators.validatePostgres(context);
        
        expect(context.warnings).toContainEqual({
          type: 'security',
          message: 'UPDATE query without WHERE clause will update all records',
          suggestion: 'Add a WHERE clause to specify which records to update'
        });
      });

      it('should warn about TRUNCATE', () => {
        context.config.query = 'TRUNCATE TABLE users';
        
        NodeSpecificValidators.validatePostgres(context);
        
        expect(context.warnings).toContainEqual({
          type: 'security',
          message: 'TRUNCATE will remove all data from the table',
          suggestion: 'Consider using DELETE with WHERE clause if you need to keep some data'
        });
      });

      it('should error on DROP operations', () => {
        context.config.query = 'DROP TABLE users';
        
        NodeSpecificValidators.validatePostgres(context);
        
        expect(context.errors).toContainEqual({
          type: 'invalid_value',
          property: 'query',
          message: 'DROP operations are extremely dangerous and will permanently delete database objects',
          fix: 'Use this only if you really intend to delete tables/databases permanently'
        });
      });

      it('should suggest specific columns instead of SELECT *', () => {
        context.config.query = 'SELECT * FROM users';
        
        NodeSpecificValidators.validatePostgres(context);
        
        expect(context.suggestions).toContain('Consider selecting specific columns instead of * for better performance');
      });

      it('should suggest PostgreSQL-specific dollar quotes', () => {
        context.config.query = 'CREATE FUNCTION test() RETURNS void AS $$ BEGIN END; $$ LANGUAGE plpgsql';
        
        NodeSpecificValidators.validatePostgres(context);
        
        expect(context.suggestions).toContain('Dollar-quoted strings detected - ensure they are properly closed');
      });
    });

    describe('connection and error handling', () => {
      it('should suggest connection timeout', () => {
        context.config = {
          operation: 'execute',
          query: 'SELECT * FROM users'
        };
        
        NodeSpecificValidators.validatePostgres(context);
        
        expect(context.suggestions).toContain('Consider setting connectionTimeout to handle slow connections');
      });

      it('should suggest error handling for read operations', () => {
        context.config = {
          operation: 'execute',
          query: 'SELECT * FROM users'
        };
        
        NodeSpecificValidators.validatePostgres(context);
        
        expect(context.warnings).toContainEqual({
          type: 'best_practice',
          property: 'errorHandling',
          message: 'Database reads can fail due to connection issues',
          suggestion: 'Add onError: "continueRegularOutput" and retryOnFail: true'
        });
        
        expect(context.autofix).toMatchObject({
          onError: 'continueRegularOutput',
          retryOnFail: true,
          maxTries: 3
        });
      });

      it('should suggest different error handling for write operations', () => {
        context.config = {
          operation: 'insert',
          table: 'users'
        };
        
        NodeSpecificValidators.validatePostgres(context);
        
        expect(context.warnings).toContainEqual({
          type: 'best_practice',
          property: 'errorHandling',
          message: 'Database writes should handle errors carefully',
          suggestion: 'Add onError: "stopWorkflow" with retryOnFail for transient failures'
        });
        
        expect(context.autofix).toMatchObject({
          onError: 'stopWorkflow',
          retryOnFail: true,
          maxTries: 2,
          waitBetweenTries: 2000
        });
      });

      it('should warn about deprecated continueOnFail', () => {
        context.config = {
          operation: 'execute',
          query: 'SELECT * FROM users',
          continueOnFail: true
        };
        
        NodeSpecificValidators.validatePostgres(context);
        
        expect(context.warnings).toContainEqual({
          type: 'deprecated',
          property: 'continueOnFail',
          message: 'continueOnFail is deprecated. Use onError instead',
          suggestion: 'Replace with onError: "continueRegularOutput" or "stopWorkflow"'
        });
      });
    });
  });

  describe('validateMySQL', () => {
    describe('operations', () => {
      it('should validate insert operation', () => {
        context.config = {
          operation: 'insert'
        };
        
        NodeSpecificValidators.validateMySQL(context);
        
        expect(context.errors).toContainEqual({
          type: 'missing_required',
          property: 'table',
          message: 'Table name is required for insert operation',
          fix: 'Specify the table to insert data into'
        });
      });

      it('should validate update operation', () => {
        context.config = {
          operation: 'update'
        };
        
        NodeSpecificValidators.validateMySQL(context);
        
        expect(context.errors).toContainEqual({
          type: 'missing_required',
          property: 'table',
          message: 'Table name is required for update operation',
          fix: 'Specify the table to update'
        });
      });

      it('should validate delete operation', () => {
        context.config = {
          operation: 'delete'
        };
        
        NodeSpecificValidators.validateMySQL(context);
        
        expect(context.errors).toContainEqual({
          type: 'missing_required',
          property: 'table',
          message: 'Table name is required for delete operation',
          fix: 'Specify the table to delete from'
        });
      });

      it('should validate execute operation', () => {
        context.config = {
          operation: 'execute'
        };
        
        NodeSpecificValidators.validateMySQL(context);
        
        expect(context.errors).toContainEqual({
          type: 'missing_required',
          property: 'query',
          message: 'SQL query is required',
          fix: 'Provide the SQL query to execute'
        });
      });
    });

    describe('MySQL-specific features', () => {
      it('should suggest timezone configuration', () => {
        context.config = {
          operation: 'execute',
          query: 'SELECT NOW()'
        };
        
        NodeSpecificValidators.validateMySQL(context);
        
        expect(context.suggestions).toContain('Consider setting timezone to ensure consistent date/time handling');
      });

      it('should check for MySQL backticks', () => {
        context.config = {
          operation: 'execute',
          query: 'SELECT `name` FROM `users`'
        };
        
        NodeSpecificValidators.validateMySQL(context);
        
        expect(context.suggestions).toContain('Using backticks for identifiers - ensure they are properly paired');
      });
    });

    describe('error handling', () => {
      it('should suggest error handling for queries', () => {
        context.config = {
          operation: 'execute',
          query: 'SELECT * FROM users'
        };
        
        NodeSpecificValidators.validateMySQL(context);
        
        expect(context.warnings).toContainEqual({
          type: 'best_practice',
          property: 'errorHandling',
          message: 'Database queries can fail due to connection issues',
          suggestion: 'Add onError: "continueRegularOutput" and retryOnFail: true'
        });
      });

      it('should suggest error handling for modifications', () => {
        context.config = {
          operation: 'update',
          table: 'users',
          updateKey: 'id'
        };
        
        NodeSpecificValidators.validateMySQL(context);
        
        expect(context.warnings).toContainEqual({
          type: 'best_practice',
          property: 'errorHandling',
          message: 'Database modifications should handle errors carefully',
          suggestion: 'Add onError: "stopWorkflow" with retryOnFail for transient failures'
        });
      });
    });
  });

  describe('validateHttpRequest', () => {
    describe('URL validation', () => {
      it('should require URL', () => {
        context.config = {
          method: 'GET'
        };
        
        NodeSpecificValidators.validateHttpRequest(context);
        
        expect(context.errors).toContainEqual({
          type: 'missing_required',
          property: 'url',
          message: 'URL is required for HTTP requests',
          fix: 'Provide the full URL including protocol (https://...)'
        });
      });

      it('should warn about missing protocol', () => {
        context.config = {
          method: 'GET',
          url: 'example.com/api'
        };
        
        NodeSpecificValidators.validateHttpRequest(context);
        
        expect(context.warnings).toContainEqual({
          type: 'invalid_value',
          property: 'url',
          message: 'URL should start with http:// or https://',
          suggestion: 'Use https:// for secure connections'
        });
      });

      it('should accept URLs with expressions', () => {
        context.config = {
          method: 'GET',
          url: '{{$node.Config.json.apiUrl}}/users'
        };
        
        NodeSpecificValidators.validateHttpRequest(context);
        
        const urlWarnings = context.warnings.filter(w => w.property === 'url');
        expect(urlWarnings).toHaveLength(0);
      });
    });

    describe('method-specific validation', () => {
      it('should suggest body for POST requests', () => {
        context.config = {
          method: 'POST',
          url: 'https://api.example.com/users'
        };
        
        NodeSpecificValidators.validateHttpRequest(context);
        
        expect(context.warnings).toContainEqual({
          type: 'missing_common',
          property: 'sendBody',
          message: 'POST requests typically include a body',
          suggestion: 'Set sendBody: true and configure the body content'
        });
      });

      it('should suggest body for PUT requests', () => {
        context.config = {
          method: 'PUT',
          url: 'https://api.example.com/users/1'
        };
        
        NodeSpecificValidators.validateHttpRequest(context);
        
        expect(context.warnings).toContainEqual({
          type: 'missing_common',
          property: 'sendBody',
          message: 'PUT requests typically include a body',
          suggestion: 'Set sendBody: true and configure the body content'
        });
      });

      it('should suggest body for PATCH requests', () => {
        context.config = {
          method: 'PATCH',
          url: 'https://api.example.com/users/1'
        };
        
        NodeSpecificValidators.validateHttpRequest(context);
        
        expect(context.warnings).toContainEqual({
          type: 'missing_common',
          property: 'sendBody',
          message: 'PATCH requests typically include a body',
          suggestion: 'Set sendBody: true and configure the body content'
        });
      });
    });

    describe('error handling', () => {
      it('should suggest error handling for HTTP requests', () => {
        context.config = {
          method: 'GET',
          url: 'https://api.example.com/data'
        };
        
        NodeSpecificValidators.validateHttpRequest(context);
        
        expect(context.warnings).toContainEqual({
          type: 'best_practice',
          property: 'errorHandling',
          message: 'HTTP requests can fail due to network issues or server errors',
          suggestion: 'Add onError: "continueRegularOutput" and retryOnFail: true for resilience'
        });
        
        expect(context.autofix).toMatchObject({
          onError: 'continueRegularOutput',
          retryOnFail: true,
          maxTries: 3,
          waitBetweenTries: 1000
        });
      });

      it('should handle deprecated continueOnFail', () => {
        context.config = {
          method: 'GET',
          url: 'https://api.example.com/data',
          continueOnFail: true
        };
        
        NodeSpecificValidators.validateHttpRequest(context);
        
        expect(context.warnings).toContainEqual({
          type: 'deprecated',
          property: 'continueOnFail',
          message: 'continueOnFail is deprecated. Use onError instead',
          suggestion: 'Replace with onError: "continueRegularOutput"'
        });
        
        expect(context.autofix.onError).toBe('continueRegularOutput');
        expect(context.autofix.continueOnFail).toBeUndefined();
      });

      it('should handle continueOnFail false', () => {
        context.config = {
          method: 'GET',
          url: 'https://api.example.com/data',
          continueOnFail: false
        };
        
        NodeSpecificValidators.validateHttpRequest(context);
        
        expect(context.autofix.onError).toBe('stopWorkflow');
      });
    });

    describe('retry configuration', () => {
      it('should warn about retrying non-idempotent operations', () => {
        context.config = {
          method: 'POST',
          url: 'https://api.example.com/orders',
          retryOnFail: true,
          maxTries: 5
        };
        
        NodeSpecificValidators.validateHttpRequest(context);
        
        expect(context.warnings).toContainEqual({
          type: 'best_practice',
          property: 'maxTries',
          message: 'POST requests might not be idempotent. Use fewer retries.',
          suggestion: 'Set maxTries: 2 for non-idempotent operations'
        });
      });

      it('should suggest alwaysOutputData for debugging', () => {
        context.config = {
          method: 'GET',
          url: 'https://api.example.com/data',
          retryOnFail: true
        };
        
        NodeSpecificValidators.validateHttpRequest(context);
        
        expect(context.suggestions).toContain('Enable alwaysOutputData to capture error responses for debugging');
        expect(context.autofix.alwaysOutputData).toBe(true);
      });
    });

    describe('authentication and security', () => {
      it('should warn about missing authentication for API endpoints', () => {
        context.config = {
          method: 'GET',
          url: 'https://api.example.com/users'
        };
        
        NodeSpecificValidators.validateHttpRequest(context);
        
        expect(context.warnings).toContainEqual({
          type: 'security',
          property: 'authentication',
          message: 'API endpoints typically require authentication',
          suggestion: 'Configure authentication method (Bearer token, API key, etc.)'
        });
      });

      it('should not warn about authentication for non-API URLs', () => {
        context.config = {
          method: 'GET',
          url: 'https://example.com/public-page'
        };
        
        NodeSpecificValidators.validateHttpRequest(context);
        
        const authWarnings = context.warnings.filter(w => w.property === 'authentication');
        expect(authWarnings).toHaveLength(0);
      });
    });

    describe('timeout', () => {
      it('should suggest timeout configuration', () => {
        context.config = {
          method: 'GET',
          url: 'https://api.example.com/data'
        };
        
        NodeSpecificValidators.validateHttpRequest(context);
        
        expect(context.suggestions).toContain('Consider setting a timeout to prevent hanging requests');
      });
    });
  });

  describe('validateWebhook', () => {
    describe('path validation', () => {
      it('should require webhook path', () => {
        context.config = {
          httpMethod: 'POST'
        };
        
        NodeSpecificValidators.validateWebhook(context);
        
        expect(context.errors).toContainEqual({
          type: 'missing_required',
          property: 'path',
          message: 'Webhook path is required',
          fix: 'Provide a unique path like "my-webhook" or "github-events"'
        });
      });

      it('should warn about leading slash in path', () => {
        context.config = {
          path: '/my-webhook',
          httpMethod: 'POST'
        };
        
        NodeSpecificValidators.validateWebhook(context);
        
        expect(context.warnings).toContainEqual({
          type: 'invalid_value',
          property: 'path',
          message: 'Webhook path should not start with /',
          suggestion: 'Use "webhook-name" instead of "/webhook-name"'
        });
      });
    });

    describe('error handling', () => {
      it('should suggest error handling for webhooks', () => {
        context.config = {
          path: 'my-webhook',
          httpMethod: 'POST'
        };
        
        NodeSpecificValidators.validateWebhook(context);
        
        expect(context.warnings).toContainEqual({
          type: 'best_practice',
          property: 'onError',
          message: 'Webhooks should always send a response, even on error',
          suggestion: 'Set onError: "continueRegularOutput" to ensure webhook responses'
        });
        
        expect(context.autofix.onError).toBe('continueRegularOutput');
      });

      it('should handle deprecated continueOnFail', () => {
        context.config = {
          path: 'my-webhook',
          httpMethod: 'POST',
          continueOnFail: true
        };
        
        NodeSpecificValidators.validateWebhook(context);
        
        expect(context.warnings).toContainEqual({
          type: 'deprecated',
          property: 'continueOnFail',
          message: 'continueOnFail is deprecated. Use onError instead',
          suggestion: 'Replace with onError: "continueRegularOutput"'
        });
        
        expect(context.autofix.onError).toBe('continueRegularOutput');
        expect(context.autofix.continueOnFail).toBeUndefined();
      });
    });

    describe('response mode validation', () => {
      it('should error on responseNode without error handling', () => {
        context.config = {
          path: 'my-webhook',
          httpMethod: 'POST',
          responseMode: 'responseNode'
        };
        
        NodeSpecificValidators.validateWebhook(context);
        
        expect(context.errors).toContainEqual({
          type: 'invalid_configuration',
          property: 'responseMode',
          message: 'responseNode mode requires onError: "continueRegularOutput"',
          fix: 'Set onError to ensure response is always sent'
        });
      });

      it('should not error on responseNode with proper error handling', () => {
        context.config = {
          path: 'my-webhook',
          httpMethod: 'POST',
          responseMode: 'responseNode',
          onError: 'continueRegularOutput'
        };
        
        NodeSpecificValidators.validateWebhook(context);
        
        const responseModeErrors = context.errors.filter(e => e.property === 'responseMode');
        expect(responseModeErrors).toHaveLength(0);
      });
    });

    describe('debugging and security', () => {
      it('should suggest alwaysOutputData for debugging', () => {
        context.config = {
          path: 'my-webhook',
          httpMethod: 'POST'
        };
        
        NodeSpecificValidators.validateWebhook(context);
        
        expect(context.suggestions).toContain('Enable alwaysOutputData to debug webhook payloads');
        expect(context.autofix.alwaysOutputData).toBe(true);
      });

      it('should suggest security measures', () => {
        context.config = {
          path: 'my-webhook',
          httpMethod: 'POST'
        };
        
        NodeSpecificValidators.validateWebhook(context);
        
        expect(context.suggestions).toContain('Consider adding webhook validation (HMAC signature verification)');
        expect(context.suggestions).toContain('Implement rate limiting for public webhooks');
      });
    });
  });

  describe('validateCode', () => {
    describe('empty code validation', () => {
      it('should error on empty JavaScript code', () => {
        context.config = {
          language: 'javaScript',
          jsCode: ''
        };
        
        NodeSpecificValidators.validateCode(context);
        
        expect(context.errors).toContainEqual({
          type: 'missing_required',
          property: 'jsCode',
          message: 'Code cannot be empty',
          fix: 'Add your code logic. Start with: return [{json: {result: "success"}}]'
        });
      });

      it('should error on whitespace-only code', () => {
        context.config = {
          language: 'javaScript',
          jsCode: '   \n\t  '
        };
        
        NodeSpecificValidators.validateCode(context);
        
        expect(context.errors).toContainEqual({
          type: 'missing_required',
          property: 'jsCode',
          message: 'Code cannot be empty',
          fix: 'Add your code logic. Start with: return [{json: {result: "success"}}]'
        });
      });

      it('should error on empty Python code', () => {
        context.config = {
          language: 'python',
          pythonCode: ''
        };
        
        NodeSpecificValidators.validateCode(context);
        
        expect(context.errors).toContainEqual({
          type: 'missing_required',
          property: 'pythonCode',
          message: 'Code cannot be empty',
          fix: 'Add your code logic. Start with: return [{json: {result: "success"}}]'
        });
      });
    });

    describe('JavaScript syntax validation', () => {
      it('should detect duplicate const declarations', () => {
        context.config = {
          language: 'javaScript',
          jsCode: 'const const x = 5; return [{json: {x}}];'
        };
        
        NodeSpecificValidators.validateCode(context);
        
        expect(context.errors).toContainEqual({
          type: 'invalid_value',
          property: 'jsCode',
          message: 'Syntax error: Duplicate const declaration',
          fix: 'Check your JavaScript syntax'
        });
      });

      it('should warn about await in non-async function', () => {
        context.config = {
          language: 'javaScript',
          jsCode: `
            function fetchData() {
              const result = await fetch('https://api.example.com');
              return [{json: result}];
            }
          `
        };
        
        NodeSpecificValidators.validateCode(context);
        
        expect(context.warnings).toContainEqual({
          type: 'best_practice',
          message: 'Using await inside a non-async function',
          suggestion: 'Add async keyword to the function, or use top-level await (Code nodes support it)'
        });
      });

      it('should suggest async usage for $helpers.httpRequest', () => {
        context.config = {
          language: 'javaScript',
          jsCode: 'const response = $helpers.httpRequest(...); return [{json: response}];'
        };
        
        NodeSpecificValidators.validateCode(context);
        
        expect(context.suggestions).toContain('$helpers.httpRequest is async - use: const response = await $helpers.httpRequest(...)');
      });

      it('should warn about DateTime usage', () => {
        context.config = {
          language: 'javaScript',
          jsCode: 'const now = DateTime(); return [{json: {now}}];'
        };
        
        NodeSpecificValidators.validateCode(context);
        
        expect(context.warnings).toContainEqual({
          type: 'best_practice',
          message: 'DateTime is from Luxon library',
          suggestion: 'Use DateTime.now() or DateTime.fromISO() for date operations'
        });
      });
    });

    describe('Python syntax validation', () => {
      it('should warn about unnecessary main check', () => {
        context.config = {
          language: 'python',
          pythonCode: `
if __name__ == "__main__":
    result = {"status": "ok"}
    return [{"json": result}]
          `
        };
        
        NodeSpecificValidators.validateCode(context);
        
        expect(context.warnings).toContainEqual({
          type: 'inefficient',
          message: 'if __name__ == "__main__" is not needed in Code nodes',
          suggestion: 'Code node Python runs directly - remove the main check'
        });
      });

      it('should not warn about __name__ without __main__', () => {
        context.config = {
          language: 'python',
          pythonCode: `
module_name = __name__
return [{"json": {"module": module_name}}]
          `
        };
        
        NodeSpecificValidators.validateCode(context);
        
        const mainWarnings = context.warnings.filter(w => w.message.includes('__main__'));
        expect(mainWarnings).toHaveLength(0);
      });

      it('should error on unavailable imports', () => {
        context.config = {
          language: 'python',
          pythonCode: 'import requests\nreturn [{"json": {"status": "ok"}}]'
        };
        
        NodeSpecificValidators.validateCode(context);
        
        expect(context.errors).toContainEqual({
          type: 'invalid_value',
          property: 'pythonCode',
          message: 'Module \'requests\' is not available in Code nodes',
          fix: 'Use JavaScript Code node with $helpers.httpRequest for HTTP requests'
        });
      });

      it('should check indentation after colons', () => {
        context.config = {
          language: 'python',
          pythonCode: `
def process():
result = "ok"
return [{"json": {"result": result}}]
          `
        };
        
        NodeSpecificValidators.validateCode(context);
        
        expect(context.errors).toContainEqual({
          type: 'invalid_value',
          property: 'pythonCode',
          message: 'Missing indentation after line 2',
          fix: 'Indent the line after the colon'
        });
      });
    });

    describe('return statement validation', () => {
      it('should error on missing return statement', () => {
        context.config = {
          language: 'javaScript',
          jsCode: 'const result = {status: "ok"}; // missing return'
        };
        
        NodeSpecificValidators.validateCode(context);
        
        expect(context.errors).toContainEqual({
          type: 'missing_required',
          property: 'jsCode',
          message: 'Code must return data for the next node',
          fix: 'Add: return [{json: {result: "success"}}]'
        });
      });

      it('should error on object return without array', () => {
        context.config = {
          language: 'javaScript',
          jsCode: 'return {status: "ok"};'
        };
        
        NodeSpecificValidators.validateCode(context);
        
        expect(context.errors).toContainEqual({
          type: 'invalid_value',
          property: 'jsCode',
          message: 'Return value must be an array of objects',
          fix: 'Wrap in array: return [{json: yourObject}]'
        });
      });

      it('should error on primitive return', () => {
        context.config = {
          language: 'javaScript',
          jsCode: 'return "success";'
        };
        
        NodeSpecificValidators.validateCode(context);
        
        expect(context.errors).toContainEqual({
          type: 'invalid_value',
          property: 'jsCode',
          message: 'Cannot return primitive values directly',
          fix: 'Return array of objects: return [{json: {value: yourData}}]'
        });
      });

      it('should error on Python primitive return', () => {
        context.config = {
          language: 'python',
          pythonCode: 'return "success"'
        };
        
        NodeSpecificValidators.validateCode(context);
        
        expect(context.errors).toContainEqual({
          type: 'invalid_value',
          property: 'pythonCode',
          message: 'Cannot return primitive values directly',
          fix: 'Return list of dicts: return [{"json": {"value": your_data}}]'
        });
      });

      it('should error on array of non-objects', () => {
        context.config = {
          language: 'javaScript',
          jsCode: 'return ["item1", "item2"];'
        };
        
        NodeSpecificValidators.validateCode(context);
        
        expect(context.errors).toContainEqual({
          type: 'invalid_value',
          property: 'jsCode',
          message: 'Array items must be objects with json property',
          fix: 'Use: return [{json: {value: "data"}}] not return ["data"]'
        });
      });

      it('should suggest proper items return format', () => {
        context.config = {
          language: 'javaScript',
          jsCode: 'return items;'
        };
        
        NodeSpecificValidators.validateCode(context);
        
        expect(context.suggestions).toContain(
          'Returning items directly is fine if they already have {json: ...} structure. ' +
          'To modify: return items.map(item => ({json: {...item.json, newField: "value"}}))'
        );
      });
    });

    describe('n8n variable usage', () => {
      it('should warn when code doesn\'t reference input data', () => {
        context.config = {
          language: 'javaScript',
          jsCode: 'const result = Math.random(); return [{json: {result}}];'
        };
        
        NodeSpecificValidators.validateCode(context);
        
        expect(context.warnings).toContainEqual({
          type: 'missing_common',
          message: 'Code doesn\'t reference input data',
          suggestion: 'Access input with: items, $input.all(), or $json (single-item mode)'
        });
      });

      it('should error on expression syntax in code', () => {
        context.config = {
          language: 'javaScript',
          jsCode: 'const name = {{$json.name}}; return [{json: {name}}];'
        };
        
        NodeSpecificValidators.validateCode(context);
        
        expect(context.errors).toContainEqual({
          type: 'invalid_value',
          property: 'jsCode',
          message: 'Expression syntax {{...}} is not valid in Code nodes',
          fix: 'Use regular JavaScript/Python syntax without double curly braces'
        });
      });

      it('should warn about wrong $node syntax', () => {
        context.config = {
          language: 'javaScript',
          jsCode: 'const data = $node[\'Previous Node\'].json; return [{json: data}];'
        };
        
        NodeSpecificValidators.validateCode(context);
        
        expect(context.warnings).toContainEqual({
          type: 'invalid_value',
          property: 'jsCode',
          message: 'Use $(\'Node Name\') instead of $node[\'Node Name\'] in Code nodes',
          suggestion: 'Replace $node[\'NodeName\'] with $(\'NodeName\')'
        });
      });

      it('should warn about expression-only functions', () => {
        context.config = {
          language: 'javaScript',
          jsCode: 'const now = $now(); return [{json: {now}}];'
        };
        
        NodeSpecificValidators.validateCode(context);
        
        expect(context.warnings).toContainEqual({
          type: 'invalid_value',
          property: 'jsCode',
          message: '$now() is an expression-only function not available in Code nodes',
          suggestion: 'See Code node documentation for alternatives'
        });
      });

      it('should warn about invalid $ usage', () => {
        context.config = {
          language: 'javaScript',
          jsCode: 'const value = $; return [{json: {value}}];'
        };
        
        NodeSpecificValidators.validateCode(context);
        
        expect(context.warnings).toContainEqual({
          type: 'best_practice',
          message: 'Invalid $ usage detected',
          suggestion: 'n8n variables start with $: $json, $input, $node, $workflow, $execution'
        });
      });

      it('should correct helpers usage', () => {
        context.config = {
          language: 'javaScript',
          jsCode: 'const result = helpers.httpRequest(); return [{json: {result}}];'
        };
        
        NodeSpecificValidators.validateCode(context);
        
        expect(context.warnings).toContainEqual({
          type: 'invalid_value',
          property: 'jsCode',
          message: 'Use $helpers not helpers',
          suggestion: 'Change helpers. to $helpers.'
        });
      });

      it('should warn about $helpers availability', () => {
        context.config = {
          language: 'javaScript',
          jsCode: 'const result = await $helpers.httpRequest(); return [{json: {result}}];'
        };
        
        NodeSpecificValidators.validateCode(context);
        
        expect(context.warnings).toContainEqual({
          type: 'best_practice',
          message: '$helpers availability varies by n8n version',
          suggestion: 'Check availability first: if (typeof $helpers !== "undefined" && $helpers.httpRequest) { ... }'
        });
      });

      it('should error on incorrect getWorkflowStaticData usage', () => {
        context.config = {
          language: 'javaScript',
          jsCode: 'const data = $helpers.getWorkflowStaticData(); return [{json: data}];'
        };
        
        NodeSpecificValidators.validateCode(context);
        
        expect(context.errors).toContainEqual({
          type: 'invalid_value',
          property: 'jsCode',
          message: '$helpers.getWorkflowStaticData() will cause "$helpers is not defined" error',
          fix: 'Use $getWorkflowStaticData("global") or $getWorkflowStaticData("node") directly'
        });
      });

      it('should warn about wrong JMESPath parameter order', () => {
        context.config = {
          language: 'javaScript',
          jsCode: 'const result = $jmespath("name", data); return [{json: {result}}];'
        };
        
        NodeSpecificValidators.validateCode(context);
        
        expect(context.warnings).toContainEqual({
          type: 'invalid_value',
          property: 'jsCode',
          message: 'Code node $jmespath has reversed parameter order: $jmespath(data, query)',
          suggestion: 'Use: $jmespath(dataObject, "query.path") not $jmespath("query.path", dataObject)'
        });
      });

      it('should warn about webhook data access', () => {
        context.config = {
          language: 'javaScript',
          jsCode: 'const payload = items[0].json.payload; return [{json: {payload}}];'
        };
        
        NodeSpecificValidators.validateCode(context);
        
        expect(context.warnings).toContainEqual({
          type: 'best_practice',
          message: 'If processing webhook data, remember it\'s nested under .body',
          suggestion: 'Webhook payloads are at items[0].json.body, not items[0].json'
        });
      });

      it('should warn about webhook data access when webhook node is referenced', () => {
        context.config = {
          language: 'javaScript',
          jsCode: 'const webhookData = $("Webhook"); const data = items[0].json.someField; return [{json: {data}}];'
        };
        
        NodeSpecificValidators.validateCode(context);
        
        expect(context.warnings).toContainEqual({
          type: 'invalid_value',
          property: 'jsCode',
          message: 'Webhook data is nested under .body property',
          suggestion: 'Use items[0].json.body.fieldName instead of items[0].json.fieldName for webhook data'
        });
      });

      it('should warn when code includes webhook string', () => {
        context.config = {
          language: 'javaScript',
          jsCode: '// Process webhook response\nconst data = items[0].json.data; return [{json: {data}}];'
        };
        
        NodeSpecificValidators.validateCode(context);
        
        expect(context.warnings).toContainEqual({
          type: 'invalid_value',
          property: 'jsCode',
          message: 'Webhook data is nested under .body property',
          suggestion: 'Use items[0].json.body.fieldName instead of items[0].json.fieldName for webhook data'
        });
      });

      it('should error on JMESPath numeric literals without backticks', () => {
        context.config = {
          language: 'javaScript',
          jsCode: 'const filtered = $jmespath(data, "[?age >= 18]"); return [{json: {filtered}}];'
        };
        
        NodeSpecificValidators.validateCode(context);
        
        expect(context.errors).toContainEqual({
          type: 'invalid_value',
          property: 'jsCode',
          message: 'JMESPath numeric literal 18 must be wrapped in backticks',
          fix: 'Change [?field >= 18] to [?field >= `18`]'
        });
      });
    });

    describe('code security', () => {
      it('should warn about eval usage', () => {
        context.config = {
          language: 'javaScript',
          jsCode: 'const result = eval("1 + 1"); return [{json: {result}}];'
        };
        
        NodeSpecificValidators.validateCode(context);
        
        expect(context.warnings).toContainEqual({
          type: 'security',
          message: 'Avoid eval() - it\'s a security risk',
          suggestion: 'Use safer alternatives or built-in functions'
        });
      });

      it('should warn about Function constructor', () => {
        context.config = {
          language: 'javaScript',
          jsCode: 'const fn = new Function("return 1"); return [{json: {result: fn()}}];'
        };
        
        NodeSpecificValidators.validateCode(context);
        
        expect(context.warnings).toContainEqual({
          type: 'security',
          message: 'Avoid Function constructor - use regular functions',
          suggestion: 'Use safer alternatives or built-in functions'
        });
      });

      it('should warn about unavailable modules', () => {
        context.config = {
          language: 'javaScript',
          jsCode: 'const axios = require("axios"); return [{json: {}}];'
        };
        
        NodeSpecificValidators.validateCode(context);
        
        expect(context.warnings).toContainEqual({
          type: 'security',
          message: 'Cannot require(\'axios\') - only built-in Node.js modules are available',
          suggestion: 'Available modules: crypto, util, querystring, url, buffer'
        });
      });

      it('should warn about dynamic require', () => {
        context.config = {
          language: 'javaScript',
          jsCode: 'const module = require(moduleName); return [{json: {}}];'
        };
        
        NodeSpecificValidators.validateCode(context);
        
        expect(context.warnings).toContainEqual({
          type: 'security',
          message: 'Dynamic require() not supported',
          suggestion: 'Use static require with string literals: require("crypto")'
        });
      });

      it('should warn about crypto usage without require', () => {
        context.config = {
          language: 'javaScript',
          jsCode: 'const hash = crypto.createHash("sha256"); return [{json: {hash}}];'
        };
        
        NodeSpecificValidators.validateCode(context);
        
        expect(context.warnings).toContainEqual({
          type: 'invalid_value',
          message: 'Using crypto without require statement',
          suggestion: 'Add: const crypto = require("crypto"); at the beginning (ignore editor warnings)'
        });
      });

      it('should warn about file system access', () => {
        context.config = {
          language: 'javaScript',
          jsCode: 'const fs = require("fs"); return [{json: {}}];'
        };
        
        NodeSpecificValidators.validateCode(context);
        
        expect(context.warnings).toContainEqual({
          type: 'security',
          message: 'File system and process access not available in Code nodes',
          suggestion: 'Use other n8n nodes for file operations (e.g., Read/Write Files node)'
        });
      });
    });

    describe('mode-specific validation', () => {
      it('should warn about items usage in single-item mode', () => {
        context.config = {
          mode: 'runOnceForEachItem',
          language: 'javaScript',
          jsCode: 'const allItems = items.length; return [{json: {count: allItems}}];'
        };
        
        NodeSpecificValidators.validateCode(context);
        
        expect(context.warnings).toContainEqual({
          type: 'best_practice',
          message: 'In "Run Once for Each Item" mode, use $json instead of items array',
          suggestion: 'Access current item data with $json.fieldName'
        });
      });

      it('should warn about $json usage without single-item mode', () => {
        context.config = {
          language: 'javaScript',
          jsCode: 'const name = $json.name; return [{json: {name}}];'
        };
        
        NodeSpecificValidators.validateCode(context);
        
        expect(context.warnings).toContainEqual({
          type: 'best_practice',
          message: '$json only works in "Run Once for Each Item" mode',
          suggestion: 'Either set mode: "runOnceForEachItem" or use items[0].json'
        });
      });
    });

    describe('error handling', () => {
      it('should suggest error handling for complex code', () => {
        context.config = {
          language: 'javaScript',
          jsCode: 'a'.repeat(101) + '\nreturn [{json: {}}];'
        };
        
        NodeSpecificValidators.validateCode(context);
        
        expect(context.warnings).toContainEqual({
          type: 'best_practice',
          property: 'errorHandling',
          message: 'Code nodes can throw errors - consider error handling',
          suggestion: 'Add onError: "continueRegularOutput" to handle errors gracefully'
        });
        
        expect(context.autofix.onError).toBe('continueRegularOutput');
      });
    });
  });
});
```
Page 36/45FirstPrevNextLast