AbdulElahGwaith commited on
Commit
b410f5c
·
verified ·
1 Parent(s): 616cac3

Upload folder using huggingface_hub

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .actrc +5 -0
  2. .devcontainer/Dockerfile +9 -0
  3. .devcontainer/devcontainer.json +19 -0
  4. .devcontainer/docker-compose.yml +24 -0
  5. .dockerignore +27 -0
  6. .editorconfig +20 -0
  7. .git-blame-ignore-revs +18 -0
  8. .gitattributes +17 -35
  9. .github/CODEOWNERS +4 -0
  10. .github/ISSUE_TEMPLATE/01-bug.yml +105 -0
  11. .github/ISSUE_TEMPLATE/config.yml +11 -0
  12. .github/actionlint.yml +8 -0
  13. .github/actions/docker-registry-login/action.yml +51 -0
  14. .github/actions/setup-nodejs/action.yml +49 -0
  15. .github/docker-compose.yml +33 -0
  16. .github/pull_request_template.md +29 -0
  17. .github/pull_request_title_conventions.md +116 -0
  18. .github/scripts/bump-versions.mjs +74 -0
  19. .github/scripts/docker/docker-config.mjs +177 -0
  20. .github/scripts/docker/docker-tags.mjs +113 -0
  21. .github/scripts/ensure-provenance-fields.mjs +44 -0
  22. .github/scripts/package.json +12 -0
  23. .github/scripts/trim-fe-packageJson.js +18 -0
  24. .github/scripts/update-changelog.mjs +40 -0
  25. .github/scripts/validate-docs-links.js +90 -0
  26. .github/test-metrics/playwright.json +672 -0
  27. .github/workflows/benchmark-destroy-nightly.yml +46 -0
  28. .github/workflows/benchmark-nightly.yml +122 -0
  29. .github/workflows/build-unit-test-pr-comment.yml +100 -0
  30. .github/workflows/build-windows.yml +81 -0
  31. .github/workflows/check-documentation-urls.yml +37 -0
  32. .github/workflows/check-pr-title.yml +21 -0
  33. .github/workflows/check-run-eligibility.yml +111 -0
  34. .github/workflows/chromatic.yml +83 -0
  35. .github/workflows/ci-evals.yml +73 -0
  36. .github/workflows/ci-manual-build-unit-tests.yml +132 -0
  37. .github/workflows/ci-master.yml +52 -0
  38. .github/workflows/ci-postgres-mysql.yml +152 -0
  39. .github/workflows/ci-pull-requests.yml +129 -0
  40. .github/workflows/ci-python-workflow-builder-evals.yml +56 -0
  41. .github/workflows/ci-python.yml +56 -0
  42. .github/workflows/ci-security.yml +23 -0
  43. .github/workflows/claude.yml +48 -0
  44. .github/workflows/create-patch-release-branch.yml +69 -0
  45. .github/workflows/data-tooling.yml +84 -0
  46. .github/workflows/docker-base-image.yml +68 -0
  47. .github/workflows/docker-build-push.yml +294 -0
  48. .github/workflows/docker-images-benchmark.yml +41 -0
  49. .github/workflows/linting-reusable.yml +33 -0
  50. .github/workflows/notify-pr-status.yml +27 -0
.actrc ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ -P blacksmith-2vcpu-ubuntu-2204=ubuntu-latest
2
+ -P blacksmith-4vcpu-ubuntu-2204=ubuntu-latest
3
+ -P ubuntu-22.04=ubuntu-latest
4
+ -P ubuntu-20.04=ubuntu-latest
5
+ --container-architecture linux/amd64
.devcontainer/Dockerfile ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ FROM n8nio/base:22
2
+
3
+ RUN apk add --no-cache --update openssh sudo shadow bash
4
+ RUN echo node ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/node && chmod 0440 /etc/sudoers.d/node
5
+ RUN mkdir /workspaces && chown node:node /workspaces
6
+ RUN npm install -g pnpm
7
+
8
+ USER node
9
+ RUN mkdir -p ~/.pnpm-store && pnpm config set store-dir ~/.pnpm-store --global
.devcontainer/devcontainer.json ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "n8n",
3
+ "dockerComposeFile": "docker-compose.yml",
4
+ "service": "n8n",
5
+ "workspaceFolder": "/workspaces",
6
+ "mounts": [
7
+ "type=bind,source=${localWorkspaceFolder},target=/workspaces,consistency=cached",
8
+ "type=bind,source=${localEnv:HOME}/.ssh,target=/home/node/.ssh,consistency=cached",
9
+ "type=bind,source=${localEnv:HOME}/.n8n,target=/home/node/.n8n,consistency=cached"
10
+ ],
11
+ "forwardPorts": [8080, 5678],
12
+ "postCreateCommand": "corepack prepare --activate && pnpm install",
13
+ "postAttachCommand": "pnpm build",
14
+ "customizations": {
15
+ "codespaces": {
16
+ "openFiles": ["CONTRIBUTING.md"]
17
+ }
18
+ }
19
+ }
.devcontainer/docker-compose.yml ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ volumes:
2
+ postgres-data:
3
+
4
+ services:
5
+ postgres:
6
+ image: postgres:16-alpine
7
+ restart: unless-stopped
8
+ volumes:
9
+ - postgres-data:/var/lib/postgresql/data
10
+ environment:
11
+ - POSTGRES_DB=n8n
12
+ - POSTGRES_PASSWORD=password
13
+
14
+ n8n:
15
+ build:
16
+ context: .
17
+ dockerfile: Dockerfile
18
+ volumes:
19
+ - ..:/workspaces:cached
20
+ command: sleep infinity
21
+ environment:
22
+ DB_POSTGRESDB_HOST: postgres
23
+ DB_TYPE: postgresdb
24
+ DB_POSTGRESDB_PASSWORD: password
.dockerignore ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Whitelist approach: ignore everything, then allow only what Docker builds need
2
+ # This reduces build context from ~900MB to just what's required
3
+
4
+ # Ignore everything first
5
+ *
6
+
7
+ # === n8n main image (docker/images/n8n/Dockerfile) ===
8
+ !compiled
9
+ !compiled/**
10
+ !THIRD_PARTY_LICENSES.md
11
+
12
+ # === runners image (docker/images/runners/Dockerfile + Dockerfile.distroless) ===
13
+ !dist
14
+ !dist/task-runner-javascript
15
+ !dist/task-runner-javascript/**
16
+ !packages
17
+ !packages/@n8n
18
+ !packages/@n8n/task-runner-python
19
+ !packages/@n8n/task-runner-python/**
20
+
21
+ # === Docker build files (entrypoints, configs) ===
22
+ !docker
23
+ !docker/images
24
+ !docker/images/n8n
25
+ !docker/images/n8n/docker-entrypoint.sh
26
+ !docker/images/runners
27
+ !docker/images/runners/n8n-task-runners.json
.editorconfig ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ root = true
2
+
3
+ [*]
4
+ charset = utf-8
5
+ indent_style = tab
6
+ indent_size = 2
7
+ end_of_line = lf
8
+ insert_final_newline = true
9
+ trim_trailing_whitespace = true
10
+
11
+ [package.json]
12
+ indent_style = space
13
+ indent_size = 2
14
+
15
+ [*.yml]
16
+ indent_style = space
17
+ indent_size = 2
18
+
19
+ [*.ts]
20
+ quote_type = single
.git-blame-ignore-revs ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Commits of large-scale changes to exclude from `git blame` results
2
+
3
+ # Set up linting and formatting (#2120)
4
+
5
+ 56c4c6991fb21ba4b7bdcd22c929f63cc1d1defe
6
+
7
+ # refactor(editor): Apply Prettier (no-changelog) #4920
8
+
9
+ 5ca2148c7ed06c90f999508928b7a51f9ac7a788
10
+
11
+ # refactor: Run lintfix (no-changelog) (#7537)
12
+
13
+ 62c096710fab2f7e886518abdbded34b55e93f62
14
+
15
+ # refactor: Move test files alongside tested files (#11504)
16
+
17
+ 7e58fc4fec468aca0b45d5bfe6150e1af632acbc
18
+ f32b13c6ed078be042a735bc8621f27e00dc3116
.gitattributes CHANGED
@@ -1,35 +1,17 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
1
+ *.sh text eol=lf
2
+ assets/n8n-screenshot-readme.png filter=lfs diff=lfs merge=lfs -text
3
+ assets/n8n-screenshot.png filter=lfs diff=lfs merge=lfs -text
4
+ packages/frontend/@n8n/chat/resources/images/windowed.png filter=lfs diff=lfs merge=lfs -text
5
+ packages/frontend/@n8n/design-system/assets/fonts/InterVariable-Italic.woff2 filter=lfs diff=lfs merge=lfs -text
6
+ packages/frontend/@n8n/design-system/assets/fonts/InterVariable.woff2 filter=lfs diff=lfs merge=lfs -text
7
+ packages/frontend/@n8n/i18n/docs/img/cred.png filter=lfs diff=lfs merge=lfs -text
8
+ packages/frontend/@n8n/i18n/docs/img/header1.png filter=lfs diff=lfs merge=lfs -text
9
+ packages/frontend/@n8n/i18n/docs/img/header2.png filter=lfs diff=lfs merge=lfs -text
10
+ packages/frontend/@n8n/i18n/docs/img/header3.png filter=lfs diff=lfs merge=lfs -text
11
+ packages/frontend/@n8n/i18n/docs/img/header4.png filter=lfs diff=lfs merge=lfs -text
12
+ packages/frontend/@n8n/i18n/docs/img/header5.png filter=lfs diff=lfs merge=lfs -text
13
+ packages/frontend/editor-ui/public/static/data-mapping-gif.gif filter=lfs diff=lfs merge=lfs -text
14
+ packages/frontend/editor-ui/src/features/integrations/externalSecrets.ee/assets/images/doppler.webp filter=lfs diff=lfs merge=lfs -text
15
+ packages/nodes-base/nodes/AcuityScheduling/acuityScheduling.png filter=lfs diff=lfs merge=lfs -text
16
+ packages/nodes-base/nodes/Cisco/Webex/ciscoWebex.png filter=lfs diff=lfs merge=lfs -text
17
+ packages/testing/playwright/tests/cli-workflows/testData/pdfs/05-versions-space.pdf filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.github/CODEOWNERS ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ packages/@n8n/db/src/migrations/ @n8n-io/migrations-review
2
+
3
+ # Node popularity data updates
4
+ packages/frontend/editor-ui/data/node-popularity.json @n8n-io/catalysts
.github/ISSUE_TEMPLATE/01-bug.yml ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Bug Report
2
+ description: Create a bug report to help us improve
3
+ body:
4
+ - type: markdown
5
+ attributes:
6
+ value: |
7
+ > ⚠️ This form is for reporting bugs only.
8
+ > ❌ Please do not use this form for general support, feature requests, or questions.
9
+ > 💬 For help and general inquiries, visit our [community support forum](https://community.n8n.io).
10
+ > ☁️ If you're experiencing issues with cloud instances not starting or license-related problems, contact [n8n support directly](mailto:help@n8n.io).
11
+ ---
12
+ Thank you for helping us improve n8n!
13
+ To ensure we can address your report efficiently, please fill out all sections in English and provide as much detail as possible.
14
+ - type: textarea
15
+ id: description
16
+ attributes:
17
+ label: Bug Description
18
+ description: A clear and concise description of what the bug is
19
+ placeholder: Tell us what you see!
20
+ validations:
21
+ required: true
22
+ - type: textarea
23
+ id: reproduction
24
+ attributes:
25
+ label: To Reproduce
26
+ description: Steps to reproduce the behavior
27
+ placeholder: |
28
+ 1. Go to '...'
29
+ 2. Click on '....'
30
+ 3. Scroll down to '....'
31
+ 4. See error
32
+ validations:
33
+ required: true
34
+ - type: textarea
35
+ id: expected
36
+ attributes:
37
+ label: Expected behavior
38
+ description: A clear and concise description of what you expected to happen
39
+ validations:
40
+ required: true
41
+ - type: textarea
42
+ id: debug-info
43
+ attributes:
44
+ label: Debug Info
45
+ description: This can be found under Help > About n8n > Copy debug information
46
+ validations:
47
+ required: true
48
+ - type: markdown
49
+ attributes:
50
+ value: '## Environment'
51
+ - type: input
52
+ id: os
53
+ attributes:
54
+ label: Operating System
55
+ placeholder: ex. Ubuntu Linux 22.04
56
+ validations:
57
+ required: true
58
+ - type: input
59
+ id: n8n-version
60
+ attributes:
61
+ label: n8n Version
62
+ placeholder: ex. 1.25.0
63
+ validations:
64
+ required: true
65
+ - type: input
66
+ id: nodejs-version
67
+ attributes:
68
+ label: Node.js Version
69
+ placeholder: ex. 22.16.0
70
+ validations:
71
+ required: true
72
+ - type: dropdown
73
+ id: db
74
+ attributes:
75
+ label: Database
76
+ options:
77
+ - SQLite (default)
78
+ - PostgreSQL
79
+ - MySQL
80
+ - MariaDB
81
+ default: 0
82
+ validations:
83
+ required: true
84
+ - type: dropdown
85
+ id: execution-mode
86
+ attributes:
87
+ label: Execution mode
88
+ description: '[Info](https://docs.n8n.io/hosting/scaling/queue-mode/)'
89
+ options:
90
+ - main (default)
91
+ - queue
92
+ - own (deprecated)
93
+ default: 0
94
+ validations:
95
+ required: true
96
+ - type: dropdown
97
+ id: hosting
98
+ attributes:
99
+ label: Hosting
100
+ options:
101
+ - n8n cloud
102
+ - self hosted
103
+ default: 0
104
+ validations:
105
+ required: true
.github/ISSUE_TEMPLATE/config.yml ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ blank_issues_enabled: false
2
+ contact_links:
3
+ - name: Feature request
4
+ url: https://community.n8n.io
5
+ about: Suggest an idea for this project
6
+ - name: Question / Problem
7
+ url: https://community.n8n.io
8
+ about: Questions and problems with n8n
9
+ - name: n8n Security Vulnerability
10
+ url: https://n8n.io/legal/#vulnerability
11
+ about: Learn about our Vulnerability Disclosure Policy
.github/actionlint.yml ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ self-hosted-runner:
2
+ labels:
3
+ - blacksmith-2vcpu-ubuntu-2204
4
+ - blacksmith-4vcpu-ubuntu-2204
5
+ - blacksmith-2vcpu-ubuntu-2204-arm
6
+ - blacksmith-4vcpu-ubuntu-2204-arm
7
+ - blacksmith-8vcpu-ubuntu-2204
8
+ - ubuntu-slim
.github/actions/docker-registry-login/action.yml ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Composite action for logging into Docker registries (GHCR and/or DockerHub).
2
+ # Centralizes the login pattern used across multiple Docker workflows.
3
+
4
+ name: 'Docker Registry Login'
5
+ description: 'Login to GitHub Container Registry and/or DockerHub'
6
+
7
+ inputs:
8
+ login-ghcr:
9
+ description: 'Login to GitHub Container Registry'
10
+ required: false
11
+ default: 'true'
12
+ login-dockerhub:
13
+ description: 'Login to DockerHub'
14
+ required: false
15
+ default: 'false'
16
+ login-dhi:
17
+ description: 'Login to Docker Hardened Images registry (dhi.io)'
18
+ required: false
19
+ default: 'false'
20
+ dockerhub-username:
21
+ description: 'DockerHub username (required if login-dockerhub or login-dhi is true)'
22
+ required: false
23
+ dockerhub-password:
24
+ description: 'DockerHub password (required if login-dockerhub or login-dhi is true)'
25
+ required: false
26
+
27
+ runs:
28
+ using: 'composite'
29
+ steps:
30
+ - name: Login to GitHub Container Registry
31
+ if: inputs.login-ghcr == 'true'
32
+ uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
33
+ with:
34
+ registry: ghcr.io
35
+ username: ${{ github.actor }}
36
+ password: ${{ github.token }}
37
+
38
+ - name: Login to DockerHub
39
+ if: inputs.login-dockerhub == 'true'
40
+ uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
41
+ with:
42
+ username: ${{ inputs.dockerhub-username }}
43
+ password: ${{ inputs.dockerhub-password }}
44
+
45
+ - name: Login to DHI Registry
46
+ if: inputs.login-dhi == 'true'
47
+ uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
48
+ with:
49
+ registry: dhi.io
50
+ username: ${{ inputs.dockerhub-username }}
51
+ password: ${{ inputs.dockerhub-password }}
.github/actions/setup-nodejs/action.yml ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This action works transparently on both Blacksmith and GitHub-hosted runners.
2
+ # Blacksmith runners benefit from transparent caching and optional Docker layer caching.
3
+ # GitHub-hosted runners use standard GitHub Actions caching.
4
+
5
+ name: 'Node.js Build Setup'
6
+ description: 'Configures Node.js with pnpm, installs dependencies, enables Turborepo caching, (optional) sets up Docker layer caching, and builds the project or an optional command.'
7
+
8
+ inputs:
9
+ node-version:
10
+ description: 'Node.js version to use. Uses latest 22.x by default.'
11
+ required: false
12
+ default: '22.x'
13
+ enable-docker-cache:
14
+ description: 'Whether to set up Blacksmith Buildx for Docker layer caching (Blacksmith runners only).'
15
+ required: false
16
+ default: 'false'
17
+ type: boolean
18
+ build-command:
19
+ description: 'Command to execute for building the project or an optional command. Leave empty to skip build step.'
20
+ required: false
21
+ default: 'pnpm build'
22
+ type: string
23
+
24
+ runs:
25
+ using: 'composite'
26
+ steps:
27
+ - name: Setup pnpm
28
+ uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
29
+
30
+ - name: Setup Node.js
31
+ uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
32
+ with:
33
+ node-version: ${{ inputs.node-version }}
34
+ cache: 'pnpm'
35
+
36
+ - name: Install Dependencies
37
+ run: pnpm install --frozen-lockfile
38
+ shell: bash
39
+
40
+ - name: Configure Turborepo Cache
41
+ uses: rharkor/caching-for-turbo@cda201ff2b32699a2ae9f59704db029e3dde4fbd # v2.3.5
42
+
43
+ - name: Setup Docker Builder for Docker Cache
44
+ if: ${{ inputs.enable-docker-cache == 'true' }}
45
+ uses: useblacksmith/setup-docker-builder@53647ab5afe8827af5623b35bd4302eabd41619f # v1.2.0
46
+
47
+ - name: Build Project
48
+ run: ${{ inputs.build-command }}
49
+ shell: bash
.github/docker-compose.yml ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ services:
2
+ mariadb:
3
+ image: mariadb:10.5
4
+ environment:
5
+ - MARIADB_DATABASE=n8n
6
+ - MARIADB_ROOT_PASSWORD=password
7
+ - MARIADB_MYSQL_LOCALHOST_USER=true
8
+ ports:
9
+ - 3306:3306
10
+ tmpfs:
11
+ - /var/lib/mysql
12
+
13
+ mysql-8.4:
14
+ image: mysql:8.4
15
+ environment:
16
+ - MYSQL_DATABASE=n8n
17
+ - MYSQL_ROOT_PASSWORD=password
18
+ ports:
19
+ - 3306:3306
20
+ tmpfs:
21
+ - /var/lib/mysql
22
+
23
+ postgres:
24
+ image: postgres:16
25
+ restart: always
26
+ environment:
27
+ - POSTGRES_DB=n8n
28
+ - POSTGRES_USER=postgres
29
+ - POSTGRES_PASSWORD=password
30
+ ports:
31
+ - 5432:5432
32
+ tmpfs:
33
+ - /var/lib/postgresql/data
.github/pull_request_template.md ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ## Summary
2
+
3
+ <!--
4
+ Describe what the PR does and how to test.
5
+ Photos and videos are recommended.
6
+ -->
7
+
8
+ ## Related Linear tickets, Github issues, and Community forum posts
9
+
10
+ <!--
11
+ Include links to **Linear ticket** or Github issue or Community forum post.
12
+ Important in order to close *automatically* and provide context to reviewers.
13
+ https://linear.app/n8n/issue/
14
+ -->
15
+ <!-- Use "closes #<issue-number>", "fixes #<issue-number>", or "resolves #<issue-number>" to automatically close issues when the PR is merged. -->
16
+
17
+
18
+ ## Review / Merge checklist
19
+
20
+ - [ ] PR title and summary are descriptive. ([conventions](../blob/master/.github/pull_request_title_conventions.md)) <!--
21
+ **Remember, the title automatically goes into the changelog.
22
+ Use `(no-changelog)` otherwise.**
23
+ -->
24
+ - [ ] [Docs updated](https://github.com/n8n-io/n8n-docs) or follow-up ticket created.
25
+ - [ ] Tests included. <!--
26
+ A bug is not considered fixed, unless a test is added to prevent it from happening again.
27
+ A feature is not complete without tests.
28
+ -->
29
+ - [ ] PR Labeled with `release/backport` (if the PR is an urgent fix that needs to be backported)
.github/pull_request_title_conventions.md ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # PR Title Convention
2
+
3
+ We have very precise rules over how Pull Requests (to the `master` branch) must be formatted. This format basically follows the [Angular Commit Message Convention](https://github.com/angular/angular/blob/master/CONTRIBUTING.md#commit). It leads to easier to read commit history and allows for automated generation of release notes:
4
+
5
+ A PR title consists of these elements:
6
+
7
+ ```text
8
+ <type>(<scope>): <summary>
9
+ │ │ │
10
+ │ │ └─⫸ Summary: In imperative present tense.
11
+ | | Capitalized
12
+ | | No period at the end.
13
+ │ │
14
+ │ └─⫸ Scope: API | benchmark | core | editor | * Node
15
+
16
+ └─⫸ Type: build | ci | chore | docs | feat | fix | perf | refactor | test
17
+ ```
18
+
19
+ - PR title
20
+ - type
21
+ - scope (_optional_)
22
+ - summary
23
+ - PR description
24
+ - body (optional)
25
+ - blank line
26
+ - footer (optional)
27
+
28
+ The structure looks like this:
29
+
30
+ ## Type
31
+
32
+ Must be one of the following:
33
+
34
+ | type | description | appears in changelog |
35
+ | --- | --- | --- |
36
+ | `feat` | A new feature | ✅ |
37
+ | `fix` | A bug fix | ✅ |
38
+ | `perf` | A code change that improves performance | ✅ |
39
+ | `test` | Adding missing tests or correcting existing tests | ❌ |
40
+ | `docs` | Documentation only changes | ❌ |
41
+ | `refactor` | A behavior-neutral code change that neither fixes a bug nor adds a feature | ❌ |
42
+ | `build` | Changes that affect the build system or external dependencies (TypeScript, Jest, pnpm, etc.) | ❌ |
43
+ | `ci` | Changes to CI configuration files and scripts (e.g. Github actions) | ❌ |
44
+ | `chore` | Routine tasks, maintenance, and minor updates not covered by other types | ❌ |
45
+
46
+ > BREAKING CHANGES (see Footer section below), will **always** appear in the changelog unless suffixed with `no-changelog`.
47
+
48
+ ## Scope (optional)
49
+
50
+ The scope should specify the place of the commit change as long as the commit clearly addresses one of the following supported scopes. (Otherwise, omit the scope!)
51
+
52
+ - `API` - changes to the _public_ API
53
+ - `benchmark` - changes to the benchmark cli
54
+ - `core` - changes to the core / private API / backend of n8n
55
+ - `editor` - changes to the Editor UI
56
+ - `* Node` - changes to a specific node or trigger node (”`*`” to be replaced with the node name, not its display name), e.g.
57
+ - mattermost → Mattermost Node
58
+ - microsoftToDo → Microsoft To Do Node
59
+ - n8n → n8n Node
60
+
61
+ ## Summary
62
+
63
+ The summary contains succinct description of the change:
64
+
65
+ - use the imperative, present tense: "change" not "changed" nor "changes"
66
+ - capitalize the first letter
67
+ - _no_ dot (.) at the end
68
+ - do _not_ include Linear ticket IDs etc. (e.g. N8N-1234)
69
+ - suffix with “(no-changelog)” for commits / PRs that should not get mentioned in the changelog.
70
+
71
+ ## Body (optional)
72
+
73
+ Just as in the **summary**, use the imperative, present tense: "change" not "changed" nor "changes". The body should include the motivation for the change and contrast this with previous behavior.
74
+
75
+ ## Footer (optional)
76
+
77
+ The footer can contain information about breaking changes and deprecations and is also the place to [reference GitHub issues](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword), Linear tickets, and other PRs that this commit closes or is related to. For example:
78
+
79
+ ```text
80
+ BREAKING CHANGE: <breaking change summary>
81
+ <BLANK LINE>
82
+ <breaking change description + migration instructions>
83
+ <BLANK LINE>
84
+ <BLANK LINE>
85
+ Fixes #<issue number>
86
+ ```
87
+
88
+ or
89
+
90
+ ```text
91
+ DEPRECATED: <what is deprecated>
92
+ <BLANK LINE>
93
+ <deprecation description + recommended update path>
94
+ <BLANK LINE>
95
+ <BLANK LINE>
96
+ Closes #<pr number>
97
+ ```
98
+
99
+ A Breaking Change section should start with the phrase "`BREAKING CHANGE:` " followed by a summary of the breaking change, a blank line, and a detailed description of the breaking change that also includes migration instructions.
100
+
101
+ > 💡 A breaking change can additionally also be marked by adding a “`!`” to the header, right before the “`:`”, e.g. `feat(editor)!: Remove support for dark mode`
102
+ >
103
+ > This makes locating breaking changes easier when just skimming through commit messages.
104
+
105
+ > 💡 The breaking changes must also be added to the [packages/cli/BREAKING-CHANGES.md](https://github.com/n8n-io/n8n/blob/master/packages/cli/BREAKING-CHANGES.md) file located in the n8n repository.
106
+
107
+ Similarly, a Deprecation section should start with "`DEPRECATED:` " followed by a short description of what is deprecated, a blank line, and a detailed description of the deprecation that also mentions the recommended update path.
108
+
109
+ ### Revert commits
110
+
111
+ If the commit reverts a previous commit, it should begin with `revert:` , followed by the header of the reverted commit.
112
+
113
+ The content of the commit message body should contain:
114
+
115
+ - information about the SHA of the commit being reverted in the following format: `This reverts commit <SHA>`,
116
+ - a clear description of the reason for reverting the commit message.
.github/scripts/bump-versions.mjs ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import semver from 'semver';
2
+ import { writeFile, readFile } from 'fs/promises';
3
+ import { resolve } from 'path';
4
+ import child_process from 'child_process';
5
+ import { promisify } from 'util';
6
+ import assert from 'assert';
7
+
8
+ const exec = promisify(child_process.exec);
9
+
10
+ function generateExperimentalVersion(currentVersion) {
11
+ const parsed = semver.parse(currentVersion);
12
+ if (!parsed) throw new Error(`Invalid version: ${currentVersion}`);
13
+
14
+ // Check if it's already an experimental version
15
+ if (parsed.prerelease.length > 0 && parsed.prerelease[0] === 'exp') {
16
+ // Increment the experimental minor version
17
+ const expMinor = (parsed.prerelease[1] || 0) + 1;
18
+ return `${parsed.major}.${parsed.minor}.${parsed.patch}-exp.${expMinor}`;
19
+ }
20
+
21
+ // Create new experimental version: <major>.<minor>.<patch>-exp.0
22
+ return `${parsed.major}.${parsed.minor}.${parsed.patch}-exp.0`;
23
+ }
24
+
25
+ const rootDir = process.cwd();
26
+ const releaseType = process.env.RELEASE_TYPE;
27
+ assert.match(releaseType, /^(patch|minor|major|experimental|premajor)$/, 'Invalid RELEASE_TYPE');
28
+
29
+ // TODO: if releaseType is `auto` determine release type based on the changelog
30
+
31
+ const lastTag = (await exec('git describe --tags --match "n8n@*" --abbrev=0')).stdout.trim();
32
+ const packages = JSON.parse((await exec('pnpm ls -r --only-projects --json')).stdout);
33
+
34
+ const packageMap = {};
35
+ for (let { name, path, version, private: isPrivate, dependencies } of packages) {
36
+ if (isPrivate && path !== rootDir) continue;
37
+ if (path === rootDir) name = 'monorepo-root';
38
+
39
+ const isDirty = await exec(`git diff --quiet HEAD ${lastTag} -- ${path}`)
40
+ .then(() => false)
41
+ .catch((error) => true);
42
+
43
+ packageMap[name] = { path, isDirty, version };
44
+ }
45
+
46
+ assert.ok(
47
+ Object.values(packageMap).some(({ isDirty }) => isDirty),
48
+ 'No changes found since the last release',
49
+ );
50
+
51
+ // Keep the monorepo version up to date with the released version
52
+ packageMap['monorepo-root'].version = packageMap['n8n'].version;
53
+
54
+ for (const packageName in packageMap) {
55
+ const { path, version, isDirty } = packageMap[packageName];
56
+ const packageFile = resolve(path, 'package.json');
57
+ const packageJson = JSON.parse(await readFile(packageFile, 'utf-8'));
58
+
59
+ packageJson.version = packageMap[packageName].nextVersion =
60
+ isDirty ||
61
+ Object.keys(packageJson.dependencies || {}).some(
62
+ (dependencyName) => packageMap[dependencyName]?.isDirty,
63
+ )
64
+ ? releaseType === 'experimental'
65
+ ? generateExperimentalVersion(version)
66
+ : releaseType === 'premajor'
67
+ ? semver.inc(version, version.includes('-rc.') ? 'prerelease' : 'premajor', undefined, 'rc')
68
+ : semver.inc(version, releaseType)
69
+ : version;
70
+
71
+ await writeFile(packageFile, JSON.stringify(packageJson, null, 2) + '\n');
72
+ }
73
+
74
+ console.log(packageMap['n8n'].nextVersion);
.github/scripts/docker/docker-config.mjs ADDED
@@ -0,0 +1,177 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env node
2
+
3
+ import { appendFileSync } from 'node:fs';
4
+
5
+ class BuildContext {
6
+ constructor() {
7
+ this.githubOutput = process.env.GITHUB_OUTPUT || null;
8
+ }
9
+
10
+ determine({ event, pr, branch, version, releaseType, pushEnabled }) {
11
+ let context = {
12
+ version: '',
13
+ release_type: '',
14
+ platforms: ['linux/amd64', 'linux/arm64'],
15
+ push_to_ghcr: true,
16
+ push_to_docker: false,
17
+ };
18
+
19
+ if (version && releaseType) {
20
+ context.version = version;
21
+ context.release_type = releaseType;
22
+ context.push_to_docker = true;
23
+ } else {
24
+ switch (event) {
25
+ case 'schedule':
26
+ context.version = 'nightly';
27
+ context.release_type = 'nightly';
28
+ context.push_to_docker = true;
29
+ break;
30
+
31
+ case 'pull_request':
32
+ context.version = `pr-${pr}`;
33
+ context.release_type = 'dev';
34
+ context.platforms = ['linux/amd64'];
35
+ break;
36
+
37
+ case 'workflow_dispatch':
38
+ context.version = `branch-${this.sanitizeBranch(branch)}`;
39
+ context.release_type = 'branch';
40
+ context.platforms = ['linux/amd64'];
41
+ break;
42
+
43
+ case 'push':
44
+ if (branch === 'master') {
45
+ context.version = 'dev';
46
+ context.release_type = 'dev';
47
+ context.push_to_docker = true;
48
+ } else {
49
+ context.version = `branch-${this.sanitizeBranch(branch)}`;
50
+ context.release_type = 'branch';
51
+ context.platforms = ['linux/amd64'];
52
+ }
53
+ break;
54
+
55
+ case 'workflow_call':
56
+ case 'release':
57
+ if (!version) throw new Error('Version required for release');
58
+ context.version = version;
59
+ context.release_type = releaseType || 'stable';
60
+ context.push_to_docker = true;
61
+ break;
62
+
63
+ default:
64
+ throw new Error(`Unknown event: ${event}`);
65
+ }
66
+ }
67
+
68
+ // Handle push_enabled override
69
+ if (pushEnabled !== undefined) {
70
+ context.push_enabled = pushEnabled;
71
+ } else {
72
+ context.push_enabled = context.push_to_ghcr;
73
+ }
74
+
75
+ return context;
76
+ }
77
+
78
+ sanitizeBranch(branch) {
79
+ if (!branch) return 'unknown';
80
+ return branch
81
+ .toLowerCase()
82
+ .replace(/[^a-z0-9._-]/g, '-')
83
+ .replace(/^[.-]/, '')
84
+ .replace(/[.-]$/, '')
85
+ .substring(0, 128);
86
+ }
87
+
88
+ buildMatrix(platforms) {
89
+ const runners = {
90
+ 'linux/amd64': 'blacksmith-4vcpu-ubuntu-2204',
91
+ 'linux/arm64': 'blacksmith-4vcpu-ubuntu-2204-arm',
92
+ };
93
+
94
+ const matrix = {
95
+ platform: [],
96
+ include: [],
97
+ };
98
+
99
+ for (const platform of platforms) {
100
+ const shortName = platform.split('/').pop(); // amd64 or arm64
101
+ matrix.platform.push(shortName);
102
+ matrix.include.push({
103
+ platform: shortName,
104
+ runner: runners[platform],
105
+ docker_platform: platform,
106
+ });
107
+ }
108
+
109
+ return matrix;
110
+ }
111
+
112
+ output(context, matrix = null) {
113
+ const buildMatrix = matrix || this.buildMatrix(context.platforms);
114
+
115
+ if (this.githubOutput) {
116
+ const outputs = [
117
+ `version=${context.version}`,
118
+ `release_type=${context.release_type}`,
119
+ `platforms=${JSON.stringify(context.platforms)}`,
120
+ `push_to_ghcr=${context.push_to_ghcr}`,
121
+ `push_to_docker=${context.push_to_docker}`,
122
+ `push_enabled=${context.push_enabled}`,
123
+ `build_matrix=${JSON.stringify(buildMatrix)}`,
124
+ ];
125
+ appendFileSync(this.githubOutput, outputs.join('\n') + '\n');
126
+ } else {
127
+ console.log(JSON.stringify({ ...context, build_matrix: buildMatrix }, null, 2));
128
+ }
129
+ }
130
+ }
131
+
132
+ // CLI - Simple argument parsing
133
+ if (import.meta.url === `file://${process.argv[1]}`) {
134
+ const args = process.argv.slice(2);
135
+ const getArg = (name) => {
136
+ const index = args.indexOf(`--${name}`);
137
+ if (index === -1 || !args[index + 1]) return undefined;
138
+ const value = args[index + 1];
139
+ // Handle empty strings and 'null' as undefined
140
+ return value === '' || value === 'null' ? undefined : value;
141
+ };
142
+
143
+ try {
144
+ const context = new BuildContext();
145
+ const pushEnabledArg = getArg('push-enabled');
146
+ const result = context.determine({
147
+ event: getArg('event') || process.env.GITHUB_EVENT_NAME,
148
+ pr: getArg('pr') || process.env.GITHUB_PR_NUMBER,
149
+ branch: getArg('branch') || process.env.GITHUB_REF_NAME,
150
+ version: getArg('version'),
151
+ releaseType: getArg('release-type'),
152
+ pushEnabled:
153
+ pushEnabledArg === 'true' ? true : pushEnabledArg === 'false' ? false : undefined,
154
+ });
155
+
156
+ const matrix = context.buildMatrix(result.platforms);
157
+
158
+ // Debug output when GITHUB_OUTPUT is set (running in Actions)
159
+ if (context.githubOutput) {
160
+ console.log('=== Build Context ===');
161
+ console.log(`version: ${result.version}`);
162
+ console.log(`release_type: ${result.release_type}`);
163
+ console.log(`platforms: ${JSON.stringify(result.platforms, null, 2)}`);
164
+ console.log(`push_to_ghcr: ${result.push_to_ghcr}`);
165
+ console.log(`push_to_docker: ${result.push_to_docker}`);
166
+ console.log(`push_enabled: ${result.push_enabled}`);
167
+ console.log('build_matrix:', JSON.stringify(matrix, null, 2));
168
+ }
169
+
170
+ context.output(result, matrix);
171
+ } catch (error) {
172
+ console.error(`Error: ${error.message}`);
173
+ process.exit(1);
174
+ }
175
+ }
176
+
177
+ export default BuildContext;
.github/scripts/docker/docker-tags.mjs ADDED
@@ -0,0 +1,113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env node
2
+
3
+ import { appendFileSync } from 'node:fs';
4
+
5
+ class TagGenerator {
6
+ constructor() {
7
+ this.githubOwner = process.env.GITHUB_REPOSITORY_OWNER || 'n8n-io';
8
+ this.dockerUsername = process.env.DOCKER_USERNAME || 'n8nio';
9
+ this.githubOutput = process.env.GITHUB_OUTPUT || null;
10
+ }
11
+
12
+ generate({ image, version, platform, includeDockerHub = false }) {
13
+ let imageName = image;
14
+ let versionSuffix = '';
15
+
16
+ if (image === 'runners-distroless') {
17
+ imageName = 'runners';
18
+ versionSuffix = '-distroless';
19
+ }
20
+
21
+ const platformSuffix = platform ? `-${platform.split('/').pop()}` : '';
22
+ const fullVersion = `${version}${versionSuffix}${platformSuffix}`;
23
+
24
+ const tags = {
25
+ ghcr: [`ghcr.io/${this.githubOwner}/${imageName}:${fullVersion}`],
26
+ docker: includeDockerHub ? [`${this.dockerUsername}/${imageName}:${fullVersion}`] : [],
27
+ };
28
+
29
+ tags.all = [...tags.ghcr, ...tags.docker];
30
+ return tags;
31
+ }
32
+
33
+ output(tags, prefix = '') {
34
+ if (this.githubOutput) {
35
+ const prefixStr = prefix ? `${prefix}_` : '';
36
+ const primaryTag = tags.ghcr[0] ? tags.ghcr[0].replace(/-amd64$|-arm64$/, '') : '';
37
+ const outputs = [
38
+ `${prefixStr}tags=${tags.all.join(',')}`,
39
+ `${prefixStr}ghcr_tag=${tags.ghcr[0] || ''}`,
40
+ `${prefixStr}docker_tag=${tags.docker[0] || ''}`,
41
+ `${prefixStr}primary_tag=${primaryTag}`,
42
+ ];
43
+ appendFileSync(this.githubOutput, outputs.join('\n') + '\n');
44
+ } else {
45
+ console.log(JSON.stringify(tags, null, 2));
46
+ }
47
+ }
48
+
49
+ generateAll({ version, platform, includeDockerHub = false }) {
50
+ const images = ['n8n', 'runners', 'runners-distroless'];
51
+ const results = {};
52
+
53
+ for (const image of images) {
54
+ const tags = this.generate({ image, version, platform, includeDockerHub });
55
+ const prefix = image.replace('-distroless', '_distroless');
56
+ results[prefix] = tags;
57
+
58
+ if (this.githubOutput) {
59
+ this.output(tags, prefix);
60
+ }
61
+ }
62
+
63
+ return results;
64
+ }
65
+ }
66
+
67
+ if (import.meta.url === `file://${process.argv[1]}`) {
68
+ const args = process.argv.slice(2);
69
+ const getArg = (name) => {
70
+ const index = args.indexOf(`--${name}`);
71
+ return index !== -1 && args[index + 1] ? args[index + 1] : undefined;
72
+ };
73
+ const hasFlag = (name) => args.includes(`--${name}`);
74
+
75
+ try {
76
+ const generator = new TagGenerator();
77
+ const version = getArg('version');
78
+
79
+ if (!version) {
80
+ console.error('Error: --version is required');
81
+ process.exit(1);
82
+ }
83
+
84
+ if (hasFlag('all')) {
85
+ const results = generator.generateAll({
86
+ version,
87
+ platform: getArg('platform'),
88
+ includeDockerHub: hasFlag('include-docker'),
89
+ });
90
+ if (!generator.githubOutput) {
91
+ console.log(JSON.stringify(results, null, 2));
92
+ }
93
+ } else {
94
+ const image = getArg('image');
95
+ if (!image) {
96
+ console.error('Error: Either --image or --all is required');
97
+ process.exit(1);
98
+ }
99
+ const tags = generator.generate({
100
+ image,
101
+ version,
102
+ platform: getArg('platform'),
103
+ includeDockerHub: hasFlag('include-docker'),
104
+ });
105
+ generator.output(tags);
106
+ }
107
+ } catch (error) {
108
+ console.error(`Error: ${error.message}`);
109
+ process.exit(1);
110
+ }
111
+ }
112
+
113
+ export default TagGenerator;
.github/scripts/ensure-provenance-fields.mjs ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { writeFile, readFile, copyFile } from 'fs/promises';
2
+ import { resolve, dirname } from 'path';
3
+ import child_process from 'child_process';
4
+ import { fileURLToPath } from 'url';
5
+ import { promisify } from 'util';
6
+
7
+ const exec = promisify(child_process.exec);
8
+
9
+ const commonFiles = ['LICENSE.md', 'LICENSE_EE.md'];
10
+
11
+ const baseDir = resolve(dirname(fileURLToPath(import.meta.url)), '../..');
12
+ const packages = JSON.parse((await exec('pnpm ls -r --only-projects --json')).stdout);
13
+
14
+ for (let { name, path, version, private: isPrivate } of packages) {
15
+ if (isPrivate) continue;
16
+
17
+ const packageFile = resolve(path, 'package.json');
18
+ const packageJson = {
19
+ ...JSON.parse(await readFile(packageFile, 'utf-8')),
20
+ // Add these fields to all published package.json files to ensure provenance checks pass
21
+ license: 'SEE LICENSE IN LICENSE.md',
22
+ homepage: 'https://n8n.io',
23
+ author: {
24
+ name: 'Jan Oberhauser',
25
+ email: 'jan@n8n.io',
26
+ },
27
+ repository: {
28
+ type: 'git',
29
+ url: 'git+https://github.com/n8n-io/n8n.git',
30
+ },
31
+ };
32
+
33
+ // Copy over LICENSE.md and LICENSE_EE.md into every published package, and ensure they get included in the published package
34
+ await Promise.all(
35
+ commonFiles.map(async (file) => {
36
+ await copyFile(resolve(baseDir, file), resolve(path, file));
37
+ if (packageJson.files && !packageJson.files.includes(file)) {
38
+ packageJson.files.push(file);
39
+ }
40
+ }),
41
+ );
42
+
43
+ await writeFile(packageFile, JSON.stringify(packageJson, null, 2) + '\n');
44
+ }
.github/scripts/package.json ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "dependencies": {
3
+ "cacheable-lookup": "6.1.0",
4
+ "conventional-changelog": "^4.0.0",
5
+ "debug": "4.3.4",
6
+ "glob": "10.5.0",
7
+ "p-limit": "3.1.0",
8
+ "picocolors": "1.0.1",
9
+ "semver": "7.5.4",
10
+ "tempfile": "5.0.0"
11
+ }
12
+ }
.github/scripts/trim-fe-packageJson.js ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const { writeFileSync } = require('fs');
2
+ const { resolve } = require('path');
3
+ const baseDir = resolve(__dirname, '../..');
4
+
5
+ const trimPackageJson = (packageName) => {
6
+ const filePath = resolve(baseDir, 'packages', packageName, 'package.json');
7
+ const { scripts, peerDependencies, devDependencies, dependencies, ...packageJson } = require(
8
+ filePath,
9
+ );
10
+ if (packageName === 'frontend/@n8n/chat') {
11
+ packageJson.dependencies = dependencies;
12
+ }
13
+ writeFileSync(filePath, JSON.stringify(packageJson, null, 2) + '\n', 'utf-8');
14
+ };
15
+
16
+ trimPackageJson('frontend/@n8n/chat');
17
+ trimPackageJson('frontend/@n8n/design-system');
18
+ trimPackageJson('frontend/editor-ui');
.github/scripts/update-changelog.mjs ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import createTempFile from 'tempfile';
2
+ import conventionalChangelog from 'conventional-changelog';
3
+ import { resolve } from 'path';
4
+ import { createReadStream, createWriteStream } from 'fs';
5
+ import { dirname } from 'path';
6
+ import { fileURLToPath } from 'url';
7
+ import { pipeline } from 'stream/promises';
8
+ import packageJson from '../../package.json' with { type: 'json' };
9
+
10
+ const baseDir = resolve(dirname(fileURLToPath(import.meta.url)), '../..');
11
+ const fullChangelogFile = resolve(baseDir, 'CHANGELOG.md');
12
+ // Version includes experimental versions (e.g., 1.2.3-exp.0)
13
+ const versionChangelogFile = resolve(baseDir, `CHANGELOG-${packageJson.version}.md`);
14
+
15
+ const changelogStream = conventionalChangelog({
16
+ preset: 'angular',
17
+ releaseCount: 1,
18
+ tagPrefix: 'n8n@',
19
+ transform: (commit, callback) => {
20
+ const hasNoChangelogInHeader = commit.header.includes('(no-changelog)');
21
+ const isBenchmarkScope = commit.scope === 'benchmark';
22
+
23
+ // Ignore commits that have 'benchmark' scope or '(no-changelog)' in the header
24
+ callback(null, hasNoChangelogInHeader || isBenchmarkScope ? undefined : commit);
25
+ },
26
+ }).on('error', (err) => {
27
+ console.error(err.stack);
28
+ process.exit(1);
29
+ });
30
+
31
+ // Write the new changelog to a new temporary file, so that the contents can be used in the PR description
32
+ await pipeline(changelogStream, createWriteStream(versionChangelogFile));
33
+
34
+ // Since we can't read and write from the same file at the same time,
35
+ // we use a temporary file to output the updated changelog to.
36
+ const tmpFile = createTempFile();
37
+ const tmpStream = createWriteStream(tmpFile);
38
+ await pipeline(createReadStream(versionChangelogFile), tmpStream, { end: false });
39
+ await pipeline(createReadStream(fullChangelogFile), tmpStream);
40
+ await pipeline(createReadStream(tmpFile), createWriteStream(fullChangelogFile));
.github/scripts/validate-docs-links.js ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env node
2
+
3
+ const packages = ['nodes-base', '@n8n/nodes-langchain'];
4
+ const concurrency = 20;
5
+ let exitCode = 0;
6
+
7
+ const debug = require('debug')('n8n');
8
+ const path = require('path');
9
+ const https = require('https');
10
+ const glob = require('glob');
11
+ const pLimit = require('p-limit');
12
+ const picocolors = require('picocolors');
13
+ const Lookup = require('cacheable-lookup').default;
14
+
15
+ const agent = new https.Agent({ keepAlive: true, keepAliveMsecs: 5000 });
16
+ new Lookup().install(agent);
17
+ const limiter = pLimit(concurrency);
18
+
19
+ const validateUrl = async (packageName, kind, type) =>
20
+ new Promise((resolve, reject) => {
21
+ const name = type.displayName;
22
+ const documentationUrl =
23
+ kind === 'credentials'
24
+ ? type.documentationUrl
25
+ : type.codex?.resources?.primaryDocumentation?.[0]?.url;
26
+ if (!documentationUrl) resolve([name, null]);
27
+
28
+ const url = new URL(
29
+ /^https?:\/\//.test(documentationUrl)
30
+ ? documentationUrl
31
+ : `https://docs.n8n.io/integrations/builtin/${kind}/${documentationUrl.toLowerCase()}/`,
32
+ );
33
+ https
34
+ .request(
35
+ {
36
+ hostname: url.hostname,
37
+ port: 443,
38
+ path: url.pathname,
39
+ method: 'HEAD',
40
+ agent,
41
+ },
42
+ (res) => {
43
+ debug(picocolors.green('✓'), packageName, kind, name);
44
+ resolve([name, res.statusCode]);
45
+ },
46
+ )
47
+ .on('error', (e) => {
48
+ debug(picocolors.red('✘'), packageName, kind, name);
49
+ reject(e);
50
+ })
51
+ .end();
52
+ });
53
+
54
+ const checkLinks = async (packageName, kind) => {
55
+ const baseDir = path.resolve(__dirname, '../../packages', packageName);
56
+ let types = require(path.join(baseDir, `dist/types/${kind}.json`));
57
+ if (kind === 'nodes')
58
+ types = types.filter(
59
+ ({ codex, hidden }) => !!codex?.resources?.primaryDocumentation && !hidden,
60
+ );
61
+ debug(packageName, kind, types.length);
62
+
63
+ const statuses = await Promise.all(
64
+ types.map((type) =>
65
+ limiter(() => {
66
+ return validateUrl(packageName, kind, type);
67
+ }),
68
+ ),
69
+ );
70
+
71
+ const missingDocs = [];
72
+ const invalidUrls = [];
73
+ for (const [name, statusCode] of statuses) {
74
+ if (statusCode === null) missingDocs.push(name);
75
+ if (statusCode !== 200) invalidUrls.push(name);
76
+ }
77
+
78
+ if (missingDocs.length)
79
+ console.log('Documentation URL missing in %s for %s', packageName, kind, missingDocs);
80
+ if (invalidUrls.length)
81
+ console.log('Documentation URL invalid in %s for %s', packageName, kind, invalidUrls);
82
+ if (missingDocs.length || invalidUrls.length) exitCode = 1;
83
+ };
84
+
85
+ (async () => {
86
+ for (const packageName of packages) {
87
+ await Promise.all([checkLinks(packageName, 'credentials'), checkLinks(packageName, 'nodes')]);
88
+ if (exitCode !== 0) process.exit(exitCode);
89
+ }
90
+ })();
.github/test-metrics/playwright.json ADDED
@@ -0,0 +1,672 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "updatedAt": "2025-12-23T17:51:26.730Z",
3
+ "source": "currents",
4
+ "projectId": "I0yzoc",
5
+ "specs": {
6
+ "tests/e2e/workflows/list/workflows.spec.ts": {
7
+ "avgDuration": 113599,
8
+ "testCount": 9,
9
+ "flakyRate": 0.0066
10
+ },
11
+ "tests/e2e/workflows/templates/templates.spec.ts": {
12
+ "avgDuration": 19603,
13
+ "testCount": 9,
14
+ "flakyRate": 0.0016
15
+ },
16
+ "tests/e2e/workflows/editor/tags.spec.ts": {
17
+ "avgDuration": 38592,
18
+ "testCount": 7,
19
+ "flakyRate": 0.0014
20
+ },
21
+ "tests/e2e/chat-hub/chat-hub-workflow-agent.spec.ts": {
22
+ "avgDuration": 45555,
23
+ "testCount": 2,
24
+ "flakyRate": 0.0814
25
+ },
26
+ "tests/e2e/workflows/editor/workflow-actions/settings.spec.ts": {
27
+ "avgDuration": 60000,
28
+ "testCount": 1,
29
+ "flakyRate": 0
30
+ },
31
+ "tests/e2e/workflows/editor/subworkflows/workflow-selector.spec.ts": {
32
+ "avgDuration": 28137,
33
+ "testCount": 5,
34
+ "flakyRate": 0.0012
35
+ },
36
+ "tests/e2e/workflows/editor/workflow-actions/save.spec.ts": {
37
+ "avgDuration": 60000,
38
+ "testCount": 1,
39
+ "flakyRate": 0
40
+ },
41
+ "tests/e2e/workflows/editor/workflow-actions/run.spec.ts": {
42
+ "avgDuration": 60000,
43
+ "testCount": 1,
44
+ "flakyRate": 0
45
+ },
46
+ "tests/e2e/workflows/editor/workflow-actions/publish.spec.ts": {
47
+ "avgDuration": 22021,
48
+ "testCount": 8,
49
+ "flakyRate": 0
50
+ },
51
+ "tests/e2e/workflows/checklist/production-checklist.spec.ts": {
52
+ "avgDuration": 27187,
53
+ "testCount": 7,
54
+ "flakyRate": 0.0013
55
+ },
56
+ "tests/e2e/workflows/executions/list.spec.ts": {
57
+ "avgDuration": 61008,
58
+ "testCount": 11,
59
+ "flakyRate": 0.0042
60
+ },
61
+ "tests/e2e/workflows/editor/workflow-actions/duplicate.spec.ts": {
62
+ "avgDuration": 60000,
63
+ "testCount": 1,
64
+ "flakyRate": 0
65
+ },
66
+ "tests/e2e/workflows/editor/workflow-actions/copy-paste.spec.ts": {
67
+ "avgDuration": 60000,
68
+ "testCount": 1,
69
+ "flakyRate": 0
70
+ },
71
+ "tests/e2e/ai/workflow-builder.spec.ts": {
72
+ "avgDuration": 32211,
73
+ "testCount": 5,
74
+ "flakyRate": 0.0096
75
+ },
76
+ "tests/e2e/workflows/editor/workflow-actions/archive.spec.ts": {
77
+ "avgDuration": 60000,
78
+ "testCount": 1,
79
+ "flakyRate": 0
80
+ },
81
+ "tests/e2e/settings/workers/workers.spec.ts": {
82
+ "avgDuration": 5662,
83
+ "testCount": 4,
84
+ "flakyRate": 0.0011
85
+ },
86
+ "tests/e2e/nodes/webhook.spec.ts": {
87
+ "avgDuration": 55672,
88
+ "testCount": 9,
89
+ "flakyRate": 0.0021
90
+ },
91
+ "tests/e2e/api/webhook-isolation.spec.ts": {
92
+ "avgDuration": 5058,
93
+ "testCount": 15,
94
+ "flakyRate": 0.0005
95
+ },
96
+ "tests/e2e/app-config/versions.spec.ts": {
97
+ "avgDuration": 5875,
98
+ "testCount": 2,
99
+ "flakyRate": 0
100
+ },
101
+ "tests/e2e/settings/environments/variables.spec.ts": {
102
+ "avgDuration": 15854,
103
+ "testCount": 7,
104
+ "flakyRate": 0.0002
105
+ },
106
+ "tests/e2e/settings/users/users.spec.ts": {
107
+ "avgDuration": 11978,
108
+ "testCount": 4,
109
+ "flakyRate": 0.015
110
+ },
111
+ "tests/e2e/building-blocks/user-service.spec.ts": {
112
+ "avgDuration": 6400,
113
+ "testCount": 4,
114
+ "flakyRate": 0.0005
115
+ },
116
+ "tests/e2e/workflows/editor/canvas/undo-redo.spec.ts": {
117
+ "avgDuration": 65670,
118
+ "testCount": 13,
119
+ "flakyRate": 0.0193
120
+ },
121
+ "tests/e2e/building-blocks/workflow-entry-points.spec.ts": {
122
+ "avgDuration": 13573,
123
+ "testCount": 5,
124
+ "flakyRate": 0.0006
125
+ },
126
+ "tests/e2e/chat-hub/chat-hub-tools.spec.ts": {
127
+ "avgDuration": 11472,
128
+ "testCount": 1,
129
+ "flakyRate": 0.0233
130
+ },
131
+ "tests/e2e/capabilities/task-runner.spec.ts": {
132
+ "avgDuration": 52322,
133
+ "testCount": 3,
134
+ "flakyRate": 0.1299
135
+ },
136
+ "tests/e2e/workflows/editor/subworkflows/debugging.spec.ts": {
137
+ "avgDuration": 9686,
138
+ "testCount": 3,
139
+ "flakyRate": 0.0007
140
+ },
141
+ "tests/e2e/workflows/editor/subworkflows/extraction.spec.ts": {
142
+ "avgDuration": 35747,
143
+ "testCount": 2,
144
+ "flakyRate": 0.0011
145
+ },
146
+ "tests/e2e/settings/environments/source-control.spec.ts": {
147
+ "avgDuration": 142866,
148
+ "testCount": 5,
149
+ "flakyRate": 0.1412
150
+ },
151
+ "tests/e2e/auth/signin.spec.ts": {
152
+ "avgDuration": 90584,
153
+ "testCount": 3,
154
+ "flakyRate": 0
155
+ },
156
+ "tests/e2e/chat-hub/chat-hub-settings.spec.ts": {
157
+ "avgDuration": 29402,
158
+ "testCount": 2,
159
+ "flakyRate": 0.0674
160
+ },
161
+ "tests/e2e/app-config/security-notifications.spec.ts": {
162
+ "avgDuration": 14479,
163
+ "testCount": 5,
164
+ "flakyRate": 0.0004
165
+ },
166
+ "tests/e2e/workflows/editor/ndv/schema-preview.spec.ts": {
167
+ "avgDuration": 5002,
168
+ "testCount": 1,
169
+ "flakyRate": 0.0021
170
+ },
171
+ "tests/e2e/nodes/schedule-trigger-node.spec.ts": {
172
+ "avgDuration": 3494,
173
+ "testCount": 1,
174
+ "flakyRate": 0.0007
175
+ },
176
+ "tests/e2e/regression/SUG-38-inline-expression-preview.spec.ts": {
177
+ "avgDuration": 4768,
178
+ "testCount": 1,
179
+ "flakyRate": 0.0007
180
+ },
181
+ "tests/e2e/regression/SUG-121-fields-reset-after-closing-ndv.spec.ts": {
182
+ "avgDuration": 4232,
183
+ "testCount": 1,
184
+ "flakyRate": 0.0007
185
+ },
186
+ "tests/e2e/workflows/editor/routing.spec.ts": {
187
+ "avgDuration": 28147,
188
+ "testCount": 6,
189
+ "flakyRate": 0.0042
190
+ },
191
+ "tests/e2e/workflows/editor/ndv/resource-mapper.spec.ts": {
192
+ "avgDuration": 17808,
193
+ "testCount": 4,
194
+ "flakyRate": 0.0005
195
+ },
196
+ "tests/e2e/workflows/editor/ndv/resource-locator.spec.ts": {
197
+ "avgDuration": 36783,
198
+ "testCount": 7,
199
+ "flakyRate": 0.0007
200
+ },
201
+ "tests/e2e/ai/rag-callout.spec.ts": {
202
+ "avgDuration": 7145,
203
+ "testCount": 2,
204
+ "flakyRate": 0
205
+ },
206
+ "tests/e2e/source-control/push.spec.ts": {
207
+ "avgDuration": 161130,
208
+ "testCount": 4,
209
+ "flakyRate": 0.4348
210
+ },
211
+ "tests/e2e/source-control/pull.spec.ts": {
212
+ "avgDuration": 63320,
213
+ "testCount": 2,
214
+ "flakyRate": 0.2028
215
+ },
216
+ "tests/e2e/capabilities/proxy-server.spec.ts": {
217
+ "avgDuration": 27599,
218
+ "testCount": 4,
219
+ "flakyRate": 0.0028
220
+ },
221
+ "tests/e2e/projects/project-settings.spec.ts": {
222
+ "avgDuration": 48766,
223
+ "testCount": 8,
224
+ "flakyRate": 0.0005
225
+ },
226
+ "tests/e2e/chat-hub/chat-hub-personal-agent.spec.ts": {
227
+ "avgDuration": 114815,
228
+ "testCount": 4,
229
+ "flakyRate": 0.0448
230
+ },
231
+ "tests/e2e/settings/personal/personal.spec.ts": {
232
+ "avgDuration": 40996,
233
+ "testCount": 9,
234
+ "flakyRate": 0.0003
235
+ },
236
+ "tests/e2e/auth/password-reset.spec.ts": {
237
+ "avgDuration": 18625,
238
+ "testCount": 1,
239
+ "flakyRate": 0.0007
240
+ },
241
+ "tests/e2e/workflows/editor/subworkflows/wait.spec.ts": {
242
+ "avgDuration": 40569,
243
+ "testCount": 4,
244
+ "flakyRate": 0.2677
245
+ },
246
+ "tests/e2e/nodes/pdf-node.spec.ts": {
247
+ "avgDuration": 5500,
248
+ "testCount": 1,
249
+ "flakyRate": 0.003
250
+ },
251
+ "tests/e2e/auth/oidc.spec.ts": {
252
+ "avgDuration": 46170,
253
+ "testCount": 1,
254
+ "flakyRate": 0.0033
255
+ },
256
+ "tests/e2e/credentials/oauth.spec.ts": {
257
+ "avgDuration": 4270,
258
+ "testCount": 1,
259
+ "flakyRate": 0.0007
260
+ },
261
+ "tests/e2e/workflows/editor/ndv/io-filter.spec.ts": {
262
+ "avgDuration": 12720,
263
+ "testCount": 2,
264
+ "flakyRate": 0.0037
265
+ },
266
+ "tests/e2e/building-blocks/node-details-configuration.spec.ts": {
267
+ "avgDuration": 33860,
268
+ "testCount": 7,
269
+ "flakyRate": 0.0003
270
+ },
271
+ "tests/e2e/node-creator/workflows.spec.ts": {
272
+ "avgDuration": 6428,
273
+ "testCount": 2,
274
+ "flakyRate": 0
275
+ },
276
+ "tests/e2e/node-creator/vector-stores.spec.ts": {
277
+ "avgDuration": 9088,
278
+ "testCount": 3,
279
+ "flakyRate": 0.001
280
+ },
281
+ "tests/e2e/node-creator/special-nodes.spec.ts": {
282
+ "avgDuration": 13503,
283
+ "testCount": 3,
284
+ "flakyRate": 0.0012
285
+ },
286
+ "tests/e2e/node-creator/navigation.spec.ts": {
287
+ "avgDuration": 15911,
288
+ "testCount": 4,
289
+ "flakyRate": 0.0002
290
+ },
291
+ "tests/e2e/node-creator/categories.spec.ts": {
292
+ "avgDuration": 17552,
293
+ "testCount": 5,
294
+ "flakyRate": 0.0027
295
+ },
296
+ "tests/e2e/node-creator/actions.spec.ts": {
297
+ "avgDuration": 12991,
298
+ "testCount": 4,
299
+ "flakyRate": 0.0004
300
+ },
301
+ "tests/e2e/app-config/nps-survey.spec.ts": {
302
+ "avgDuration": 37040,
303
+ "testCount": 2,
304
+ "flakyRate": 0.0025
305
+ },
306
+ "tests/e2e/workflows/editor/ndv/ndv-parameters.spec.ts": {
307
+ "avgDuration": 48876,
308
+ "testCount": 9,
309
+ "flakyRate": 0.0012
310
+ },
311
+ "tests/e2e/workflows/editor/ndv/paired-item.spec.ts": {
312
+ "avgDuration": 30863,
313
+ "testCount": 5,
314
+ "flakyRate": 0.0006
315
+ },
316
+ "tests/e2e/workflows/editor/ndv/ndv-floating-nodes.spec.ts": {
317
+ "avgDuration": 25025,
318
+ "testCount": 4,
319
+ "flakyRate": 0.0042
320
+ },
321
+ "tests/e2e/workflows/editor/ndv/ndv-data-display.spec.ts": {
322
+ "avgDuration": 63163,
323
+ "testCount": 10,
324
+ "flakyRate": 0.0047
325
+ },
326
+ "tests/e2e/workflows/editor/ndv/ndv-core.spec.ts": {
327
+ "avgDuration": 59650,
328
+ "testCount": 16,
329
+ "flakyRate": 0.0003
330
+ },
331
+ "tests/e2e/workflows/editor/execution/partial.spec.ts": {
332
+ "avgDuration": 8298,
333
+ "testCount": 2,
334
+ "flakyRate": 0.0004
335
+ },
336
+ "tests/e2e/workflows/editor/execution/logs.spec.ts": {
337
+ "avgDuration": 39950,
338
+ "testCount": 9,
339
+ "flakyRate": 0.0002
340
+ },
341
+ "tests/e2e/settings/log-streaming/log-streaming-observability.spec.ts": {
342
+ "avgDuration": 41907,
343
+ "testCount": 2,
344
+ "flakyRate": 0.0019
345
+ },
346
+ "tests/e2e/settings/log-streaming/log-streaming-ui-e2e.spec.ts": {
347
+ "avgDuration": 30182,
348
+ "testCount": 1,
349
+ "flakyRate": 0.0039
350
+ },
351
+ "tests/e2e/settings/log-streaming/log-streaming.spec.ts": {
352
+ "avgDuration": 14143,
353
+ "testCount": 5,
354
+ "flakyRate": 0.0003
355
+ },
356
+ "tests/e2e/ai/langchain-agents.spec.ts": {
357
+ "avgDuration": 69954,
358
+ "testCount": 7,
359
+ "flakyRate": 0.0017
360
+ },
361
+ "tests/e2e/ai/langchain-tools.spec.ts": {
362
+ "avgDuration": 60000,
363
+ "testCount": 1,
364
+ "flakyRate": 0
365
+ },
366
+ "tests/e2e/ai/langchain-memory.spec.ts": {
367
+ "avgDuration": 60000,
368
+ "testCount": 1,
369
+ "flakyRate": 0
370
+ },
371
+ "tests/e2e/ai/langchain-chains.spec.ts": {
372
+ "avgDuration": 41803,
373
+ "testCount": 4,
374
+ "flakyRate": 0.0014
375
+ },
376
+ "tests/e2e/ai/langchain-vectorstores.spec.ts": {
377
+ "avgDuration": 36841,
378
+ "testCount": 2,
379
+ "flakyRate": 0.0153
380
+ },
381
+ "tests/e2e/workflows/editor/expressions/inline.spec.ts": {
382
+ "avgDuration": 36109,
383
+ "testCount": 8,
384
+ "flakyRate": 0.0003
385
+ },
386
+ "tests/e2e/workflows/editor/execution/inject-previous.spec.ts": {
387
+ "avgDuration": 12687,
388
+ "testCount": 2,
389
+ "flakyRate": 0
390
+ },
391
+ "tests/e2e/workflows/list/import.spec.ts": {
392
+ "avgDuration": 15225,
393
+ "testCount": 5,
394
+ "flakyRate": 0.0003
395
+ },
396
+ "tests/e2e/nodes/if-node.spec.ts": {
397
+ "avgDuration": 7492,
398
+ "testCount": 2,
399
+ "flakyRate": 0.0004
400
+ },
401
+ "tests/e2e/nodes/http-request-node.spec.ts": {
402
+ "avgDuration": 9072,
403
+ "testCount": 2,
404
+ "flakyRate": 0.0011
405
+ },
406
+ "tests/e2e/regression/GHC-5776-ai-sessions-metadata-license-error.spec.ts": {
407
+ "avgDuration": 2602,
408
+ "testCount": 1,
409
+ "flakyRate": 0.0007
410
+ },
411
+ "tests/e2e/nodes/form-trigger-node.spec.ts": {
412
+ "avgDuration": 27007,
413
+ "testCount": 3,
414
+ "flakyRate": 0.0004
415
+ },
416
+ "tests/e2e/projects/folders-operations.spec.ts": {
417
+ "avgDuration": 46752,
418
+ "testCount": 13,
419
+ "flakyRate": 0.0006
420
+ },
421
+ "tests/e2e/projects/folders-basic.spec.ts": {
422
+ "avgDuration": 28139,
423
+ "testCount": 11,
424
+ "flakyRate": 0.0005
425
+ },
426
+ "tests/e2e/projects/folders-advanced.spec.ts": {
427
+ "avgDuration": 20134,
428
+ "testCount": 5,
429
+ "flakyRate": 0.0004
430
+ },
431
+ "tests/e2e/workflows/editor/canvas/focus-panel.spec.ts": {
432
+ "avgDuration": 4056,
433
+ "testCount": 1,
434
+ "flakyRate": 0.0007
435
+ },
436
+ "tests/e2e/chat-hub/chat-hub-attachment.spec.ts": {
437
+ "avgDuration": 44171,
438
+ "testCount": 3,
439
+ "flakyRate": 0.0147
440
+ },
441
+ "tests/e2e/api/webhook-external.spec.ts": {
442
+ "avgDuration": 9425,
443
+ "testCount": 2,
444
+ "flakyRate": 0.0594
445
+ },
446
+ "tests/e2e/workflows/editor/expressions/modal.spec.ts": {
447
+ "avgDuration": 41609,
448
+ "testCount": 6,
449
+ "flakyRate": 0.0006
450
+ },
451
+ "tests/e2e/workflows/editor/execution/execution.spec.ts": {
452
+ "avgDuration": 47322,
453
+ "testCount": 14,
454
+ "flakyRate": 0.0001
455
+ },
456
+ "tests/e2e/workflows/editor/execution/previous-nodes.spec.ts": {
457
+ "avgDuration": 60000,
458
+ "testCount": 1,
459
+ "flakyRate": 0
460
+ },
461
+ "tests/e2e/ai/evaluations.spec.ts": {
462
+ "avgDuration": 60000,
463
+ "testCount": 1,
464
+ "flakyRate": 0
465
+ },
466
+ "tests/e2e/app-config/env-feature-flags.spec.ts": {
467
+ "avgDuration": 1359,
468
+ "testCount": 2,
469
+ "flakyRate": 0
470
+ },
471
+ "tests/e2e/nodes/email-send-node.spec.ts": {
472
+ "avgDuration": 22418,
473
+ "testCount": 1,
474
+ "flakyRate": 0.0054
475
+ },
476
+ "tests/e2e/workflows/editor/code/editors.spec.ts": {
477
+ "avgDuration": 49237,
478
+ "testCount": 11,
479
+ "flakyRate": 0.0003
480
+ },
481
+ "tests/e2e/workflows/editor/editor-after-route-changes.spec.ts": {
482
+ "avgDuration": 16091,
483
+ "testCount": 1,
484
+ "flakyRate": 0.0211
485
+ },
486
+ "tests/e2e/app-config/demo.spec.ts": {
487
+ "avgDuration": 14718,
488
+ "testCount": 4,
489
+ "flakyRate": 0.0176
490
+ },
491
+ "tests/e2e/workflows/editor/execution/debug.spec.ts": {
492
+ "avgDuration": 34255,
493
+ "testCount": 3,
494
+ "flakyRate": 0.0037
495
+ },
496
+ "tests/e2e/workflows/editor/expressions/transformation.spec.ts": {
497
+ "avgDuration": 30134,
498
+ "testCount": 6,
499
+ "flakyRate": 0.0001
500
+ },
501
+ "tests/e2e/workflows/editor/ndv/pinning.spec.ts": {
502
+ "avgDuration": 36652,
503
+ "testCount": 10,
504
+ "flakyRate": 0.0004
505
+ },
506
+ "tests/e2e/data-tables/tables.spec.ts": {
507
+ "avgDuration": 71055,
508
+ "testCount": 7,
509
+ "flakyRate": 0.0011
510
+ },
511
+ "tests/e2e/data-tables/details.spec.ts": {
512
+ "avgDuration": 72323,
513
+ "testCount": 11,
514
+ "flakyRate": 0.0007
515
+ },
516
+ "tests/e2e/workflows/editor/expressions/mapping.spec.ts": {
517
+ "avgDuration": 42523,
518
+ "testCount": 10,
519
+ "flakyRate": 0.0047
520
+ },
521
+ "tests/e2e/credentials/crud.spec.ts": {
522
+ "avgDuration": 80126,
523
+ "testCount": 15,
524
+ "flakyRate": 0.0009
525
+ },
526
+ "tests/e2e/building-blocks/credentials.spec.ts": {
527
+ "avgDuration": 28608,
528
+ "testCount": 6,
529
+ "flakyRate": 0.0008
530
+ },
531
+ "tests/e2e/credentials/api-operations.spec.ts": {
532
+ "avgDuration": 1602,
533
+ "testCount": 5,
534
+ "flakyRate": 0.0003
535
+ },
536
+ "tests/e2e/settings/community-nodes/community-nodes.spec.ts": {
537
+ "avgDuration": 4091,
538
+ "testCount": 1,
539
+ "flakyRate": 0.0007
540
+ },
541
+ "tests/e2e/nodes/community-nodes.spec.ts": {
542
+ "avgDuration": 13999,
543
+ "testCount": 3,
544
+ "flakyRate": 0.0005
545
+ },
546
+ "tests/e2e/workflows/editor/code/code-node.spec.ts": {
547
+ "avgDuration": 74990,
548
+ "testCount": 12,
549
+ "flakyRate": 0.0226
550
+ },
551
+ "tests/e2e/chat-hub/chat-hub-chat-user.spec.ts": {
552
+ "avgDuration": 9556,
553
+ "testCount": 1,
554
+ "flakyRate": 0
555
+ },
556
+ "tests/e2e/ai/chat-session.spec.ts": {
557
+ "avgDuration": 6490,
558
+ "testCount": 1,
559
+ "flakyRate": 0.01
560
+ },
561
+ "tests/e2e/workflows/editor/canvas/canvas-zoom.spec.ts": {
562
+ "avgDuration": 54599,
563
+ "testCount": 12,
564
+ "flakyRate": 0.0005
565
+ },
566
+ "tests/e2e/workflows/editor/canvas/canvas-nodes.spec.ts": {
567
+ "avgDuration": 65556,
568
+ "testCount": 8,
569
+ "flakyRate": 0.0042
570
+ },
571
+ "tests/e2e/building-blocks/canvas-actions.spec.ts": {
572
+ "avgDuration": 31578,
573
+ "testCount": 9,
574
+ "flakyRate": 0.0006
575
+ },
576
+ "tests/e2e/workflows/editor/canvas/actions.spec.ts": {
577
+ "avgDuration": 80903,
578
+ "testCount": 20,
579
+ "flakyRate": 0.0005
580
+ },
581
+ "tests/e2e/workflows/editor/canvas/stickies.spec.ts": {
582
+ "avgDuration": 2962,
583
+ "testCount": 1,
584
+ "flakyRate": 0.0015
585
+ },
586
+ "tests/e2e/regression/CAT-726-canvas-node-connectors-not-rendered-when-nodes-inserted.spec.ts": {
587
+ "avgDuration": 4951,
588
+ "testCount": 1,
589
+ "flakyRate": 0
590
+ },
591
+ "tests/e2e/app-config/become-creator.spec.ts": {
592
+ "avgDuration": 5583,
593
+ "testCount": 2,
594
+ "flakyRate": 0.0004
595
+ },
596
+ "tests/e2e/chat-hub/chat-hub-basic.spec.ts": {
597
+ "avgDuration": 99260,
598
+ "testCount": 4,
599
+ "flakyRate": 0.0319
600
+ },
601
+ "tests/e2e/auth/authenticated.spec.ts": {
602
+ "avgDuration": 13964,
603
+ "testCount": 5,
604
+ "flakyRate": 0
605
+ },
606
+ "tests/e2e/auth/admin-smoke.spec.ts": {
607
+ "avgDuration": 2237,
608
+ "testCount": 1,
609
+ "flakyRate": 0
610
+ },
611
+ "tests/e2e/regression/AI-812-partial-execs-broken-when-using-chat-trigger.spec.ts": {
612
+ "avgDuration": 8215,
613
+ "testCount": 2,
614
+ "flakyRate": 0.0011
615
+ },
616
+ "tests/e2e/regression/AI-716-correctly-set-up-agent-model-shows-error.spec.ts": {
617
+ "avgDuration": 4666,
618
+ "testCount": 1,
619
+ "flakyRate": 0
620
+ },
621
+ "tests/e2e/regression/AI-1401-sub-nodes-input-panel.spec.ts": {
622
+ "avgDuration": 4941,
623
+ "testCount": 1,
624
+ "flakyRate": 0.0007
625
+ },
626
+ "tests/e2e/ai/assistant-basic.spec.ts": {
627
+ "avgDuration": 55527,
628
+ "testCount": 11,
629
+ "flakyRate": 0.0003
630
+ },
631
+ "tests/e2e/ai/assistant-support-chat.spec.ts": {
632
+ "avgDuration": 13201,
633
+ "testCount": 3,
634
+ "flakyRate": 0.0002
635
+ },
636
+ "tests/e2e/ai/assistant-credential-help.spec.ts": {
637
+ "avgDuration": 18891,
638
+ "testCount": 4,
639
+ "flakyRate": 0.0007
640
+ },
641
+ "tests/e2e/ai/assistant-code-help.spec.ts": {
642
+ "avgDuration": 12021,
643
+ "testCount": 2,
644
+ "flakyRate": 0.0004
645
+ },
646
+ "tests/e2e/regression/ADO-2929-can-load-old-switch-node-workflows.spec.ts": {
647
+ "avgDuration": 4430,
648
+ "testCount": 1,
649
+ "flakyRate": 0
650
+ },
651
+ "tests/e2e/regression/ADO-2372-prevent-clipping-params.spec.ts": {
652
+ "avgDuration": 8217,
653
+ "testCount": 2,
654
+ "flakyRate": 0.0004
655
+ },
656
+ "tests/e2e/regression/ADO-2270-opening-webhook-ndv-marks-workflow-as-unsaved.spec.ts": {
657
+ "avgDuration": 3801,
658
+ "testCount": 1,
659
+ "flakyRate": 0.0015
660
+ },
661
+ "tests/e2e/regression/ADO-2230-ndv-reset-data-pagination.spec.ts": {
662
+ "avgDuration": 3794,
663
+ "testCount": 1,
664
+ "flakyRate": 0
665
+ },
666
+ "tests/e2e/regression/ADO-1338-ndv-missing-input-panel.spec.ts": {
667
+ "avgDuration": 9604,
668
+ "testCount": 1,
669
+ "flakyRate": 0
670
+ }
671
+ }
672
+ }
.github/workflows/benchmark-destroy-nightly.yml ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Destroy Benchmark Env
2
+
3
+ on:
4
+ schedule:
5
+ - cron: '0 5 * * *'
6
+ workflow_dispatch:
7
+
8
+ permissions:
9
+ id-token: write
10
+ contents: read
11
+
12
+ concurrency:
13
+ group: benchmark
14
+ cancel-in-progress: false
15
+
16
+ jobs:
17
+ build:
18
+ runs-on: ubuntu-latest
19
+ environment: benchmarking
20
+
21
+ steps:
22
+ - name: Checkout
23
+ uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
24
+
25
+ - name: Azure login
26
+ uses: azure/login@6c251865b4e6290e7b78be643ea2d005bc51f69a # v2.1.1
27
+ with:
28
+ client-id: ${{ secrets.BENCHMARK_ARM_CLIENT_ID }}
29
+ tenant-id: ${{ secrets.BENCHMARK_ARM_TENANT_ID }}
30
+ subscription-id: ${{ secrets.BENCHMARK_ARM_SUBSCRIPTION_ID }}
31
+
32
+ - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
33
+ with:
34
+ node-version: 22.x
35
+
36
+ - name: Setup corepack and pnpm
37
+ run: |
38
+ npm i -g corepack@0.33
39
+ corepack enable
40
+
41
+ - name: Install dependencies
42
+ run: pnpm install --frozen-lockfile
43
+
44
+ - name: Destroy cloud env
45
+ run: pnpm destroy-cloud-env
46
+ working-directory: packages/@n8n/benchmark
.github/workflows/benchmark-nightly.yml ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Run Nightly Benchmark
2
+ run-name: Benchmark ${{ inputs.n8n_tag || 'nightly' }}
3
+
4
+ on:
5
+ schedule:
6
+ - cron: '30 1,2,3 * * *'
7
+ workflow_dispatch:
8
+ inputs:
9
+ debug:
10
+ description: 'Use debug logging'
11
+ required: true
12
+ default: 'false'
13
+ n8n_tag:
14
+ description: 'Name of the n8n docker tag to run the benchmark against.'
15
+ required: true
16
+ default: 'nightly'
17
+ benchmark_tag:
18
+ description: 'Name of the benchmark cli docker tag to run the benchmark with.'
19
+ required: true
20
+ default: 'latest'
21
+
22
+ env:
23
+ ARM_CLIENT_ID: ${{ secrets.BENCHMARK_ARM_CLIENT_ID }}
24
+ ARM_SUBSCRIPTION_ID: ${{ secrets.BENCHMARK_ARM_SUBSCRIPTION_ID }}
25
+ ARM_TENANT_ID: ${{ secrets.BENCHMARK_ARM_TENANT_ID }}
26
+ N8N_TAG: ${{ inputs.n8n_tag || 'nightly' }}
27
+ N8N_BENCHMARK_TAG: ${{ inputs.benchmark_tag || 'latest' }}
28
+ DEBUG: ${{ inputs.debug == 'true' && '--debug' || '' }}
29
+
30
+ permissions:
31
+ id-token: write
32
+ contents: read
33
+
34
+ concurrency:
35
+ group: benchmark
36
+ cancel-in-progress: false
37
+
38
+ jobs:
39
+ build:
40
+ runs-on: ubuntu-latest
41
+ environment: benchmarking
42
+
43
+ steps:
44
+ - name: Checkout
45
+ uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
46
+
47
+ - uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3
48
+ with:
49
+ terraform_version: '1.8.5'
50
+
51
+ - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
52
+ with:
53
+ node-version: 22.x
54
+
55
+ - name: Setup corepack and pnpm
56
+ run: |
57
+ npm i -g corepack@0.33
58
+ corepack enable
59
+
60
+ - name: Install dependencies
61
+ run: pnpm install --frozen-lockfile
62
+
63
+ - name: Azure login
64
+ uses: azure/login@6c251865b4e6290e7b78be643ea2d005bc51f69a # v2.1.1
65
+ with:
66
+ client-id: ${{ env.ARM_CLIENT_ID }}
67
+ tenant-id: ${{ env.ARM_TENANT_ID }}
68
+ subscription-id: ${{ env.ARM_SUBSCRIPTION_ID }}
69
+
70
+ - name: Destroy any existing environment
71
+ run: pnpm destroy-cloud-env
72
+ working-directory: packages/@n8n/benchmark
73
+
74
+ - name: Provision the environment
75
+ run: pnpm provision-cloud-env ${{ env.DEBUG }}
76
+ working-directory: packages/@n8n/benchmark
77
+
78
+ - name: Run the benchmark
79
+ id: benchmark
80
+ env:
81
+ BENCHMARK_RESULT_WEBHOOK_URL: ${{ secrets.BENCHMARK_RESULT_WEBHOOK_URL }}
82
+ BENCHMARK_RESULT_WEBHOOK_AUTH_HEADER: ${{ secrets.BENCHMARK_RESULT_WEBHOOK_AUTH_HEADER }}
83
+ N8N_LICENSE_CERT: ${{ secrets.N8N_BENCHMARK_LICENSE_CERT }}
84
+ run: |
85
+ pnpm benchmark-in-cloud \
86
+ --vus 5 \
87
+ --duration 1m \
88
+ --n8nTag ${{ env.N8N_TAG }} \
89
+ --benchmarkTag ${{ env.N8N_BENCHMARK_TAG }} \
90
+ ${{ env.DEBUG }}
91
+ working-directory: packages/@n8n/benchmark
92
+
93
+ # We need to login again because the access token expires
94
+ - name: Azure login
95
+ if: always()
96
+ uses: azure/login@6c251865b4e6290e7b78be643ea2d005bc51f69a # v2.1.1
97
+ with:
98
+ client-id: ${{ env.ARM_CLIENT_ID }}
99
+ tenant-id: ${{ env.ARM_TENANT_ID }}
100
+ subscription-id: ${{ env.ARM_SUBSCRIPTION_ID }}
101
+
102
+ - name: Destroy the environment
103
+ if: always()
104
+ run: pnpm destroy-cloud-env ${{ env.DEBUG }}
105
+ working-directory: packages/@n8n/benchmark
106
+
107
+ - name: Fail `build` job if `benchmark` step failed
108
+ if: steps.benchmark.outcome == 'failure'
109
+ run: exit 1
110
+
111
+ notify-on-failure:
112
+ name: Notify Cats on failure
113
+ runs-on: ubuntu-latest
114
+ needs: [build]
115
+ if: failure()
116
+ steps:
117
+ - uses: act10ns/slack@44541246747a30eb3102d87f7a4cc5471b0ffb7d # v2.1.0
118
+ with:
119
+ status: ${{ job.status }}
120
+ channel: '#team-catalysts'
121
+ webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }}
122
+ message: Benchmark run failed for n8n tag `${{ inputs.n8n_tag || 'nightly' }}` - ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
.github/workflows/build-unit-test-pr-comment.yml ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Trigger build/unit tests on PR comment
2
+
3
+ on:
4
+ issue_comment:
5
+ types: [created]
6
+
7
+ permissions:
8
+ pull-requests: read
9
+ contents: read
10
+ actions: write
11
+ issues: write
12
+
13
+ jobs:
14
+ validate_and_dispatch:
15
+ name: Validate user and dispatch CI workflow
16
+ if: github.event.issue.pull_request && startsWith(github.event.comment.body, '/build-unit-test')
17
+ runs-on: ubuntu-latest
18
+ steps:
19
+ - name: Validate user permissions and collect PR data
20
+ id: check_permissions
21
+ uses: actions/github-script@v7
22
+ with:
23
+ github-token: ${{ secrets.GITHUB_TOKEN }}
24
+ script: |
25
+ const commenter = context.actor;
26
+ const body = (context.payload.comment.body || '').trim();
27
+ const isCommand = body.startsWith('/build-unit-test');
28
+ const allowedPermissions = ['admin', 'write', 'maintain'];
29
+ const commentId = context.payload.comment.id;
30
+ const { owner, repo } = context.repo;
31
+
32
+ async function react(content) {
33
+ try {
34
+ await github.rest.reactions.createForIssueComment({
35
+ owner,
36
+ repo,
37
+ comment_id: commentId,
38
+ content,
39
+ });
40
+ } catch (error) {
41
+ console.log(`Failed to add reaction '${content}': ${error.message}`);
42
+ }
43
+ }
44
+
45
+ core.setOutput('proceed', 'false');
46
+ core.setOutput('headSha', '');
47
+ core.setOutput('prNumber', '');
48
+
49
+ if (!context.payload.issue.pull_request || !isCommand) {
50
+ console.log('Comment is not /build-unit-test on a pull request. Skipping.');
51
+ return;
52
+ }
53
+
54
+ let permission;
55
+ try {
56
+ const { data } = await github.rest.repos.getCollaboratorPermissionLevel({
57
+ owner,
58
+ repo,
59
+ username: commenter,
60
+ });
61
+ permission = data.permission;
62
+ } catch (error) {
63
+ console.log(`Could not verify permissions for @${commenter}: ${error.message}`);
64
+ await react('confused');
65
+ return;
66
+ }
67
+
68
+ if (!allowedPermissions.includes(permission)) {
69
+ console.log(`User @${commenter} has '${permission}' permission; requires admin/write/maintain.`);
70
+ await react('-1');
71
+ return;
72
+ }
73
+
74
+ try {
75
+ const prNumber = context.issue.number;
76
+ const { data: pr } = await github.rest.pulls.get({
77
+ owner,
78
+ repo,
79
+ pull_number: prNumber,
80
+ });
81
+ await react('+1');
82
+ core.setOutput('proceed', 'true');
83
+ core.setOutput('headSha', pr.head.sha);
84
+ core.setOutput('prNumber', String(prNumber));
85
+ } catch (error) {
86
+ console.log(`Failed to fetch PR details for PR #${context.issue.number}: ${error.message}`);
87
+ await react('confused');
88
+ }
89
+
90
+ - name: Dispatch build/unit test workflow
91
+ if: ${{ steps.check_permissions.outputs.proceed == 'true' }}
92
+ env:
93
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
94
+ HEAD_SHA: ${{ steps.check_permissions.outputs.headSha }}
95
+ PR_NUMBER: ${{ steps.check_permissions.outputs.prNumber }}
96
+ run: |
97
+ gh workflow run ci-manual-build-unit-tests.yml \
98
+ --repo "${{ github.repository }}" \
99
+ -f ref="${HEAD_SHA}" \
100
+ -f pr_number="${PR_NUMBER}"
.github/workflows/build-windows.yml ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Windows CI
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ inputs:
6
+ notify_on_failure:
7
+ description: 'Send Slack notification on build failure'
8
+ required: false
9
+ type: boolean
10
+ default: false
11
+ workflow_call:
12
+ inputs:
13
+ notify_on_failure:
14
+ description: 'Send Slack notification on build failure'
15
+ required: false
16
+ type: boolean
17
+ default: false
18
+ secrets:
19
+ QBOT_SLACK_TOKEN:
20
+ required: false
21
+ pull_request:
22
+ branches: [master]
23
+ paths:
24
+ - '**/package.json'
25
+ - '**/turbo.json'
26
+ - '.github/workflows/build-windows.yml'
27
+
28
+ jobs:
29
+ build:
30
+ runs-on: windows-latest
31
+
32
+ steps:
33
+ - name: Checkout code
34
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
35
+
36
+ - name: Setup Node.js and Build
37
+ uses: ./.github/actions/setup-nodejs
38
+ with:
39
+ build-command: pnpm build
40
+
41
+ - name: Send Slack notification on failure
42
+ if: failure() && inputs.notify_on_failure == true
43
+ uses: slackapi/slack-github-action@91efab103c0de0a537f72a35f6b8cda0ee76bf0a # v2.1.1
44
+ with:
45
+ method: chat.postMessage
46
+ token: ${{ secrets.QBOT_SLACK_TOKEN }}
47
+ payload: |
48
+ {
49
+ "channel": "C035KBDA917",
50
+ "text": "🚨 Windows build failed for `${{ github.repository }}` on branch `${{ github.ref_name }}`",
51
+ "blocks": [
52
+ {
53
+ "type": "header",
54
+ "text": { "type": "plain_text", "text": "🚨 Windows Build Failed" }
55
+ },
56
+ {
57
+ "type": "section",
58
+ "fields": [
59
+ { "type": "mrkdwn", "text": "*Repository:*\n<${{ github.server_url }}/${{ github.repository }}|${{ github.repository }}>" },
60
+ { "type": "mrkdwn", "text": "*Branch:*\n`${{ github.ref_name }}`" },
61
+ { "type": "mrkdwn", "text": "*Commit:*\n`${{ github.sha }}`" },
62
+ { "type": "mrkdwn", "text": "*Trigger:*\n${{ github.event_name }}" }
63
+ ]
64
+ },
65
+ {
66
+ "type": "section",
67
+ "text": { "type": "mrkdwn", "text": ":warning: *Cross-platform compatibility issue detected*\nThis likely indicates Unix-specific commands in package.json scripts or build configuration that don't work on Windows." }
68
+ },
69
+ {
70
+ "type": "actions",
71
+ "elements": [
72
+ {
73
+ "type": "button",
74
+ "text": { "type": "plain_text", "text": ":github: View Workflow Run" },
75
+ "style": "danger",
76
+ "url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
77
+ }
78
+ ]
79
+ }
80
+ ]
81
+ }
.github/workflows/check-documentation-urls.yml ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Check Documentation URLs
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+ schedule:
7
+ - cron: '0 0 * * *'
8
+ workflow_dispatch:
9
+
10
+ jobs:
11
+ check-docs-urls:
12
+ runs-on: ubuntu-latest
13
+
14
+ timeout-minutes: 5
15
+
16
+ steps:
17
+ - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
18
+
19
+ - name: Setup Node.js
20
+ uses: ./.github/actions/setup-nodejs
21
+ with:
22
+ build-command: turbo build --filter=*nodes*
23
+
24
+ - run: npm install --prefix=.github/scripts --no-package-lock
25
+
26
+ - name: Test URLs
27
+ run: node .github/scripts/validate-docs-links.js
28
+
29
+ - name: Notify Slack on failure
30
+ uses: act10ns/slack@44541246747a30eb3102d87f7a4cc5471b0ffb7d # v2.1.0
31
+ if: failure()
32
+ with:
33
+ status: ${{ job.status }}
34
+ channel: '#alerts-build'
35
+ webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }}
36
+ message: |
37
+ <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}| Documentation URLs check failed >
.github/workflows/check-pr-title.yml ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Check PR title
2
+
3
+ on:
4
+ pull_request:
5
+ types:
6
+ - opened
7
+ - edited
8
+ - synchronize
9
+ branches:
10
+ - 'master'
11
+ - '1.x'
12
+
13
+ jobs:
14
+ check-pr-title:
15
+ runs-on: ubuntu-latest
16
+ timeout-minutes: 5
17
+ steps:
18
+ - name: Validate PR title
19
+ uses: n8n-io/validate-n8n-pull-request-title@c3b6fd06bda12eebd57a592c0cf3b747d5b73569 # v2.4.0
20
+ env:
21
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
.github/workflows/check-run-eligibility.yml ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Determines if conditions are met for running subsequent jobs on a Pull Request.
2
+ #
3
+ # !! IMPORTANT !!
4
+ # This workflow RELIES on being called from a parent workflow triggered by
5
+ # a `pull_request` or `pull_request_target` event. It uses `github.event`
6
+ # to access PR details.
7
+ #
8
+ # It checks if all the following conditions are TRUE:
9
+ # 1. The PR is NOT from a fork (i.e., it's an internal PR).
10
+ # 2. The PR has been approved by a maintainer (`is_pr_approved_by_maintainer`).
11
+ # 3. The PR's source branch does NOT match an excluded pattern.
12
+ # 4. The PR includes relevant file changes (`paths_filter_patterns`).
13
+ #
14
+ # It outputs `should_run` as 'true' if ALL conditions pass, 'false' otherwise.
15
+
16
+ name: PR Eligibility Check
17
+
18
+ on:
19
+ workflow_call:
20
+ inputs:
21
+ is_pr_approved_by_maintainer:
22
+ required: true
23
+ type: boolean
24
+ paths_filter_patterns:
25
+ description: "Path filter patterns for 'paths-filter-action'."
26
+ required: false
27
+ type: string
28
+ default: |
29
+ not_ignored:
30
+ - '!.devcontainer/**'
31
+ - '!.github/*'
32
+ - '!.github/scripts/*'
33
+ - '!.github/workflows/benchmark-*'
34
+ - '!.github/workflows/check-*'
35
+ - '!.vscode/**'
36
+ - '!docker/**'
37
+ - '!packages/@n8n/benchmark/**'
38
+ - '!packages/@n8n/task-runner-python/**'
39
+ - '!**/*.md'
40
+ excluded_source_branch_patterns:
41
+ description: 'Newline-separated list of glob patterns for source branches to EXCLUDE.'
42
+ required: false
43
+ type: string
44
+ default: |
45
+ release/*
46
+ master
47
+
48
+ outputs:
49
+ should_run:
50
+ description: "Outputs 'true' if all eligibility checks pass, otherwise 'false'."
51
+ value: ${{ jobs.evaluate_conditions.outputs.run_decision }}
52
+
53
+ jobs:
54
+ evaluate_conditions:
55
+ runs-on: ubuntu-latest
56
+ outputs:
57
+ run_decision: ${{ steps.evaluate.outputs.should_run }}
58
+ steps:
59
+ - name: Check out current commit
60
+ uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493
61
+ with:
62
+ ref: ${{ github.event.pull_request.head.sha }}
63
+
64
+ - name: Determine changed files
65
+ uses: tomi/paths-filter-action@32c62f5ca100c1110406e3477d5b3ecef4666fec # v3.0.2
66
+ id: changed
67
+ with:
68
+ filters: ${{ inputs.paths_filter_patterns }}
69
+ predicate-quantifier: 'every'
70
+
71
+ - name: Evaluate Conditions & Set Output
72
+ id: evaluate
73
+ env:
74
+ IS_FORK: ${{ github.event.pull_request.head.repo.fork }}
75
+ IS_APPROVED: ${{ inputs.is_pr_approved_by_maintainer }}
76
+ FILES_CHANGED: ${{ steps.changed.outputs.not_ignored == 'true' }}
77
+ HEAD_REF: ${{ github.event.pull_request.head.ref }}
78
+ EXCLUDED_PATTERNS: ${{ inputs.excluded_source_branch_patterns }}
79
+ run: |
80
+ if [[ "$IS_FORK" == "true" ]]; then
81
+ is_community="true"
82
+ else
83
+ is_community="false"
84
+ fi
85
+
86
+ source_branch_excluded="false"
87
+ while IFS= read -r pattern; do
88
+ # shellcheck disable=SC2053
89
+ if [[ -n "$pattern" && "$HEAD_REF" == $pattern ]]; then
90
+ source_branch_excluded="true"
91
+ break
92
+ fi
93
+ done <<< "$EXCLUDED_PATTERNS"
94
+
95
+ echo "--- Checking Conditions ---"
96
+ echo "Is NOT Community PR: $([[ "$is_community" == "false" ]] && echo true || echo false)"
97
+ echo "Files Changed: $FILES_CHANGED"
98
+ echo "Source Branch Excluded: $source_branch_excluded"
99
+ echo "Is Approved: $IS_APPROVED"
100
+ echo "-------------------------"
101
+
102
+ if [[ "$is_community" == "false" && \
103
+ "$FILES_CHANGED" == "true" && \
104
+ "$source_branch_excluded" == "false" && \
105
+ "$IS_APPROVED" == "true" ]]; then
106
+ echo "Decision: Conditions met. Setting should_run=true."
107
+ echo "should_run=true" >> "$GITHUB_OUTPUT"
108
+ else
109
+ echo "Decision: Conditions not met. Setting should_run=false."
110
+ echo "should_run=false" >> "$GITHUB_OUTPUT"
111
+ fi
.github/workflows/chromatic.yml ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Chromatic
2
+
3
+ on:
4
+ schedule:
5
+ - cron: '0 0 * * *'
6
+ workflow_dispatch:
7
+ pull_request_review:
8
+ types: [submitted]
9
+
10
+ concurrency:
11
+ group: chromatic-${{ github.event.pull_request.number || github.ref }}
12
+ cancel-in-progress: true
13
+
14
+ jobs:
15
+ changeset:
16
+ runs-on: blacksmith-2vcpu-ubuntu-2204
17
+ steps:
18
+ - name: Determine changed files
19
+ uses: tomi/paths-filter-action@v3.0.2
20
+ id: changed
21
+ if: github.event_name == 'pull_request_review'
22
+ with:
23
+ filters: |
24
+ design_system:
25
+ - 'packages/frontend/@n8n/design-system/**'
26
+ - '.github/workflows/storybook.yml'
27
+ outputs:
28
+ has_changes: ${{ steps.changed.outputs.design_system || 'false' }}
29
+
30
+ chromatic:
31
+ needs: [changeset]
32
+ if: |
33
+ github.event_name == 'schedule' ||
34
+ github.event_name == 'workflow_dispatch' ||
35
+ (
36
+ github.event_name == 'pull_request_review' &&
37
+ needs.changeset.outputs.has_changes == 'true' &&
38
+ github.event.review.state == 'approved' &&
39
+ !startsWith(github.event.pull_request.head.ref, 'release/') &&
40
+ !startsWith(github.event.pull_request.head.ref, 'release-pr/')
41
+ )
42
+ runs-on: blacksmith-2vcpu-ubuntu-2204
43
+ steps:
44
+ - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
45
+ with:
46
+ fetch-depth: 0
47
+
48
+ - name: Setup Node.js
49
+ uses: ./.github/actions/setup-nodejs
50
+ with:
51
+ build-command: pnpm run build --filter=@n8n/utils --filter=@n8n/vitest-config --filter=@n8n/design-system
52
+
53
+ - name: Publish to Chromatic
54
+ uses: chromaui/action@1cfa065cbdab28f6ca3afaeb3d761383076a35aa # v11
55
+ id: chromatic_tests
56
+ continue-on-error: true
57
+ with:
58
+ workingDir: packages/frontend/@n8n/design-system
59
+ autoAcceptChanges: 'master'
60
+ skip: 'release/**'
61
+ onlyChanged: true
62
+ projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
63
+ exitZeroOnChanges: false
64
+
65
+ - name: Success comment
66
+ if: steps.chromatic_tests.outcome == 'success' && github.ref != 'refs/heads/master'
67
+ uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0
68
+ with:
69
+ issue-number: ${{ github.event.pull_request.number }}
70
+ token: ${{ secrets.GITHUB_TOKEN }}
71
+ edit-mode: replace
72
+ body: |
73
+ :white_check_mark: No visual regressions found.
74
+
75
+ - name: Fail comment
76
+ if: steps.chromatic_tests.outcome != 'success' && github.ref != 'refs/heads/master'
77
+ uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0
78
+ with:
79
+ issue-number: ${{ github.event.pull_request.number }}
80
+ token: ${{ secrets.GITHUB_TOKEN }}
81
+ edit-mode: replace
82
+ body: |
83
+ [:warning: Visual regressions found](${{steps.chromatic_tests.outputs.url}}): ${{steps.chromatic_tests.outputs.changeCount}}
.github/workflows/ci-evals.yml ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Run Workflow Builder Evals
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - master
7
+ paths:
8
+ - 'packages/@n8n/ai-workflow-builder.ee/**'
9
+ - '.github/workflows/ci-evals.yml'
10
+ schedule:
11
+ - cron: '0 22 * * 6'
12
+ workflow_dispatch:
13
+ inputs:
14
+ branch:
15
+ description: 'GitHub branch to test.'
16
+ required: false
17
+ default: 'master'
18
+ dataset:
19
+ description: 'LangSmith dataset to use.'
20
+ required: false
21
+ default: 'workflow-builder-canvas-prompts'
22
+
23
+ jobs:
24
+ evals:
25
+ name: Run Evaluations
26
+ runs-on: blacksmith-2vcpu-ubuntu-2204
27
+ env:
28
+ N8N_AI_ANTHROPIC_KEY: ${{ secrets.EVALS_ANTHROPIC_KEY }}
29
+ LANGSMITH_TRACING: true
30
+ LANGSMITH_ENDPOINT: ${{ secrets.EVALS_LANGSMITH_ENDPOINT }}
31
+ LANGSMITH_API_KEY: ${{ secrets.EVALS_LANGSMITH_API_KEY }}
32
+ steps:
33
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
34
+ with:
35
+ ref: ${{ github.event.inputs.branch || github.ref }}
36
+
37
+ - name: Select dataset
38
+ run: |
39
+ DATASET="workflow-builder-canvas-prompts"
40
+ if [ "${{ github.event_name }}" = "schedule" ]; then
41
+ DATASET="prompts-v2"
42
+ elif [ -n "${{ github.event.inputs.dataset }}" ]; then
43
+ DATASET="${{ github.event.inputs.dataset }}"
44
+ fi
45
+ echo "LANGSMITH_DATASET_NAME=$DATASET" >> "$GITHUB_ENV"
46
+
47
+ - name: Setup and Build
48
+ uses: ./.github/actions/setup-nodejs
49
+
50
+ - name: Install uv
51
+ uses: astral-sh/setup-uv@d9e0f98d3fc6adb07d1e3d37f3043649ddad06a1 # 6.5.0
52
+ with:
53
+ enable-cache: true
54
+
55
+ - name: Install just
56
+ uses: extractions/setup-just@e33e0265a09d6d736e2ee1e0eb685ef1de4669ff # v3.0.0
57
+
58
+ - name: Install Python
59
+ working-directory: packages/@n8n/ai-workflow-builder.ee/evaluations/programmatic/python
60
+ run: uv python install 3.11
61
+
62
+ - name: Install workflow comparison dependencies
63
+ working-directory: packages/@n8n/ai-workflow-builder.ee/evaluations/programmatic/python
64
+ run: just sync-all
65
+
66
+ - name: Export Node Types
67
+ run: |
68
+ ./packages/cli/bin/n8n export:nodes --output ./packages/@n8n/ai-workflow-builder.ee/evaluations/nodes.json
69
+
70
+ - name: Run Evaluations
71
+ working-directory: packages/@n8n/ai-workflow-builder.ee/evaluations
72
+ run: |
73
+ pnpm eval:langsmith --repetitions 3
.github/workflows/ci-manual-build-unit-tests.yml ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Build, unit test and lint (manual trigger)
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ inputs:
6
+ ref:
7
+ description: Commit SHA or ref to check out
8
+ required: true
9
+ pr_number:
10
+ description: PR number (optional, for check reporting)
11
+ required: false
12
+ type: string
13
+
14
+ permissions:
15
+ contents: read
16
+ checks: write
17
+
18
+ jobs:
19
+ create-check-run:
20
+ name: Create Check Run
21
+ runs-on: ubuntu-latest
22
+ if: inputs.pr_number != ''
23
+ outputs:
24
+ check_run_id: ${{ steps.create.outputs.check_run_id }}
25
+ steps:
26
+ - name: Create pending check run on PR
27
+ id: create
28
+ uses: actions/github-script@v7
29
+ with:
30
+ github-token: ${{ secrets.GITHUB_TOKEN }}
31
+ script: |
32
+ const { data: checkRun } = await github.rest.checks.create({
33
+ owner: context.repo.owner,
34
+ repo: context.repo.repo,
35
+ name: 'Build & Unit Tests - Checks',
36
+ head_sha: '${{ inputs.ref }}',
37
+ status: 'in_progress',
38
+ output: {
39
+ title: 'Build & Unit Tests - Checks',
40
+ summary: 'Running build, unit tests, and lint...'
41
+ }
42
+ });
43
+
44
+ core.setOutput('check_run_id', checkRun.id);
45
+ console.log(`Created check run ${checkRun.id} on commit ${{ inputs.ref }}`);
46
+
47
+ install-and-build:
48
+ name: Install & Build
49
+ runs-on: blacksmith-2vcpu-ubuntu-2204
50
+ env:
51
+ NODE_OPTIONS: '--max-old-space-size=6144'
52
+ steps:
53
+ - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
54
+ with:
55
+ ref: ${{ inputs.ref }}
56
+
57
+ - name: Setup and Build
58
+ uses: ./.github/actions/setup-nodejs
59
+
60
+ - name: Run format check
61
+ run: pnpm format:check
62
+
63
+ - name: Run typecheck
64
+ run: pnpm typecheck
65
+
66
+ unit-tests:
67
+ name: Unit tests
68
+ needs: install-and-build
69
+ uses: ./.github/workflows/units-tests-reusable.yml
70
+ with:
71
+ ref: ${{ inputs.ref }}
72
+ collectCoverage: true
73
+ secrets:
74
+ CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
75
+
76
+ lint:
77
+ name: Lint
78
+ needs: install-and-build
79
+ uses: ./.github/workflows/linting-reusable.yml
80
+ with:
81
+ ref: ${{ inputs.ref }}
82
+
83
+ post-build-unit-tests:
84
+ name: Build & Unit Tests - Checks
85
+ runs-on: ubuntu-latest
86
+ needs: [create-check-run, install-and-build, unit-tests, lint]
87
+ if: always()
88
+ steps:
89
+ - name: Update check run on PR (if triggered from PR comment)
90
+ if: inputs.pr_number != ''
91
+ uses: actions/github-script@v7
92
+ with:
93
+ github-token: ${{ secrets.GITHUB_TOKEN }}
94
+ script: |
95
+ const checkRunId = '${{ needs.create-check-run.outputs.check_run_id }}';
96
+
97
+ if (!checkRunId) {
98
+ console.log('No check run ID found, skipping update');
99
+ return;
100
+ }
101
+
102
+ const buildResult = '${{ needs.install-and-build.result }}';
103
+ const testResult = '${{ needs.unit-tests.result }}';
104
+ const lintResult = '${{ needs.lint.result }}';
105
+
106
+ const conclusion = (buildResult === 'success' && testResult === 'success' && lintResult === 'success')
107
+ ? 'success'
108
+ : 'failure';
109
+
110
+ const summary = `
111
+ **Build**: ${buildResult}
112
+ **Unit Tests**: ${testResult}
113
+ **Lint**: ${lintResult}
114
+ `;
115
+
116
+ await github.rest.checks.update({
117
+ owner: context.repo.owner,
118
+ repo: context.repo.repo,
119
+ check_run_id: parseInt(checkRunId),
120
+ status: 'completed',
121
+ conclusion: conclusion,
122
+ output: {
123
+ title: 'Build & Unit Tests - Checks',
124
+ summary: summary
125
+ }
126
+ });
127
+
128
+ console.log(`Updated check run ${checkRunId} with conclusion: ${conclusion}`);
129
+
130
+ - name: Fail if any job failed
131
+ if: needs.install-and-build.result == 'failure' || needs.unit-tests.result == 'failure' || needs.lint.result == 'failure'
132
+ run: exit 1
.github/workflows/ci-master.yml ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Test Master
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - master
7
+ - 1.x
8
+ paths-ignore:
9
+ - packages/@n8n/task-runner-python/**
10
+
11
+ jobs:
12
+ build-github:
13
+ name: Build for Github Cache
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
17
+
18
+ - name: Setup and Build
19
+ uses: ./.github/actions/setup-nodejs
20
+
21
+ unit-test:
22
+ name: Unit tests
23
+ uses: ./.github/workflows/units-tests-reusable.yml
24
+ strategy:
25
+ matrix:
26
+ node-version: [20.x, 22.x, 24.3.x]
27
+ with:
28
+ ref: ${{ github.sha }}
29
+ nodeVersion: ${{ matrix.node-version }}
30
+ collectCoverage: ${{ matrix.node-version == '22.x' }}
31
+ secrets:
32
+ CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
33
+
34
+ lint:
35
+ name: Lint
36
+ uses: ./.github/workflows/linting-reusable.yml
37
+ with:
38
+ ref: ${{ github.sha }}
39
+
40
+ notify-on-failure:
41
+ name: Notify Slack on failure
42
+ runs-on: ubuntu-latest
43
+ needs: [unit-test, lint, build-github]
44
+ steps:
45
+ - name: Notify Slack on failure
46
+ uses: act10ns/slack@44541246747a30eb3102d87f7a4cc5471b0ffb7d # v2.1.0
47
+ if: failure()
48
+ with:
49
+ status: ${{ job.status }}
50
+ channel: '#alerts-build'
51
+ webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }}
52
+ message: ${{ github.ref_name }} branch (build or test or lint) failed (${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
.github/workflows/ci-postgres-mysql.yml ADDED
@@ -0,0 +1,152 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Test Postgres and MySQL schemas
2
+
3
+ on:
4
+ schedule:
5
+ - cron: '0 0 * * *'
6
+ workflow_dispatch:
7
+ pull_request:
8
+ paths:
9
+ - packages/cli/src/databases/**
10
+ - packages/cli/src/modules/*/database/**
11
+ - packages/cli/src/modules/**/*.entity.ts
12
+ - packages/cli/src/modules/**/*.repository.ts
13
+ - packages/cli/test/integration/**
14
+ - packages/cli/test/shared/db/**
15
+ - packages/@n8n/db/**
16
+ - packages/cli/**/__tests__/**
17
+ - .github/workflows/ci-postgres-mysql.yml
18
+ - .github/docker-compose.yml
19
+
20
+ concurrency:
21
+ group: db-${{ github.event.pull_request.number || github.ref }}
22
+ cancel-in-progress: true
23
+
24
+ env:
25
+ NODE_OPTIONS: '--max-old-space-size=3072'
26
+
27
+ jobs:
28
+ build:
29
+ name: Install & Build
30
+ runs-on: blacksmith-2vcpu-ubuntu-2204
31
+ if: github.event_name != 'pull_request_review' || startsWith(github.event.pull_request.base.ref, 'release/')
32
+ steps:
33
+ - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
34
+
35
+ - name: Setup and Build
36
+ uses: ./.github/actions/setup-nodejs
37
+
38
+ sqlite-pooled:
39
+ name: SQLite Pooled
40
+ needs: build
41
+ runs-on: blacksmith-2vcpu-ubuntu-2204
42
+ timeout-minutes: 20
43
+ env:
44
+ DB_TYPE: sqlite
45
+ DB_SQLITE_POOL_SIZE: 4
46
+ steps:
47
+ - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
48
+
49
+ - name: Setup and Build
50
+ uses: ./.github/actions/setup-nodejs
51
+
52
+ - name: Test SQLite Pooled
53
+ working-directory: packages/cli
54
+ run: pnpm test:sqlite
55
+
56
+ mariadb:
57
+ name: MariaDB
58
+ needs: build
59
+ runs-on: blacksmith-4vcpu-ubuntu-2204
60
+ timeout-minutes: 30
61
+ if: false
62
+ env:
63
+ DB_MYSQLDB_PASSWORD: password
64
+ DB_MYSQLDB_POOL_SIZE: 1
65
+ DB_MYSQLDB_CONNECTION_TIMEOUT: 120000
66
+ DB_MYSQLDB_ACQUIRE_TIMEOUT: 120000
67
+ DB_MYSQLDB_TIMEOUT: 120000
68
+ NODE_OPTIONS: '--max-old-space-size=7168'
69
+ steps:
70
+ - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
71
+
72
+ - name: Setup and Build
73
+ uses: ./.github/actions/setup-nodejs
74
+
75
+ - name: Start MariaDB
76
+ uses: isbang/compose-action@802a148945af6399a338c7906c267331b39a71af # v2.0.0
77
+ with:
78
+ compose-file: ./.github/docker-compose.yml
79
+ services: |
80
+ mariadb
81
+
82
+ - name: Test MariaDB
83
+ working-directory: packages/cli
84
+ run: pnpm test:mariadb --testTimeout 120000
85
+
86
+ mysql:
87
+ name: MySQL 8.4
88
+ needs: build
89
+ runs-on: blacksmith-2vcpu-ubuntu-2204
90
+ timeout-minutes: 20
91
+ if: false
92
+ env:
93
+ DB_MYSQLDB_PASSWORD: password
94
+ DB_MYSQLDB_POOL_SIZE: 1
95
+ DB_MYSQLDB_CONNECTION_TIMEOUT: 120000
96
+ DB_MYSQLDB_ACQUIRE_TIMEOUT: 120000
97
+ DB_MYSQLDB_TIMEOUT: 120000
98
+ steps:
99
+ - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
100
+
101
+ - name: Setup and Build
102
+ uses: ./.github/actions/setup-nodejs
103
+
104
+ - name: Start MySQL
105
+ uses: isbang/compose-action@802a148945af6399a338c7906c267331b39a71af # v2.0.0
106
+ with:
107
+ compose-file: ./.github/docker-compose.yml
108
+ services: mysql-8.4
109
+
110
+ - name: Test MySQL
111
+ working-directory: packages/cli
112
+ # We sleep here due to flakiness with DB tests if we connect to the database too soon
113
+ run: sleep 2s && pnpm test:mysql --testTimeout 120000
114
+
115
+ postgres:
116
+ name: Postgres
117
+ needs: build
118
+ runs-on: blacksmith-2vcpu-ubuntu-2204
119
+ timeout-minutes: 20
120
+ env:
121
+ DB_POSTGRESDB_PASSWORD: password
122
+ DB_POSTGRESDB_POOL_SIZE: 1 # Detect connection pooling deadlocks
123
+ steps:
124
+ - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
125
+
126
+ - name: Setup and Build
127
+ uses: ./.github/actions/setup-nodejs
128
+
129
+ - name: Start Postgres
130
+ uses: isbang/compose-action@802a148945af6399a338c7906c267331b39a71af # v2.0.0
131
+ with:
132
+ compose-file: ./.github/docker-compose.yml
133
+ services: |
134
+ postgres
135
+
136
+ - name: Test Postgres
137
+ working-directory: packages/cli
138
+ run: pnpm test:postgres
139
+
140
+ notify-on-failure:
141
+ name: Notify Slack on failure
142
+ runs-on: ubuntu-latest
143
+ needs: [sqlite-pooled, postgres]
144
+ steps:
145
+ - name: Notify Slack on failure
146
+ uses: act10ns/slack@44541246747a30eb3102d87f7a4cc5471b0ffb7d # v2.1.0
147
+ if: failure() && github.ref == 'refs/heads/master'
148
+ with:
149
+ status: ${{ job.status }}
150
+ channel: '#alerts-build'
151
+ webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }}
152
+ message: Postgres, MariaDB or MySQL tests failed (${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
.github/workflows/ci-pull-requests.yml ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Build, unit test and lint branch
2
+
3
+ on:
4
+ pull_request:
5
+ merge_group:
6
+
7
+ concurrency:
8
+ group: ci-${{ github.event.pull_request.number || github.event.merge_group.head_sha || github.ref }}
9
+ cancel-in-progress: true
10
+
11
+ env:
12
+ COVERAGE_ENABLED: 'true' # Set globally for all jobs - ensures Turbo cache consistency
13
+
14
+ jobs:
15
+ install-and-build:
16
+ name: Install & Build
17
+ runs-on: blacksmith-2vcpu-ubuntu-2204
18
+ env:
19
+ NODE_OPTIONS: '--max-old-space-size=6144'
20
+ CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
21
+ outputs:
22
+ non_python_changed: ${{ steps.paths-filter.outputs.non-python == 'true' }}
23
+ workflows_changed: ${{ steps.paths-filter.outputs.workflows == 'true' }}
24
+ commit_sha: ${{ steps.commit-sha.outputs.sha }}
25
+ steps:
26
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
27
+ with:
28
+ # Use merge_group SHA when in merge queue, otherwise PR merge ref
29
+ ref: ${{ github.event_name == 'merge_group' && github.event.merge_group.head_sha || format('refs/pull/{0}/merge', github.event.pull_request.number) }}
30
+
31
+ - name: Capture commit SHA for cache consistency
32
+ id: commit-sha
33
+ run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
34
+
35
+ - name: Check for relevant changes
36
+ uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
37
+ id: paths-filter
38
+ with:
39
+ filters: |
40
+ non-python:
41
+ - '**'
42
+ - '!packages/@n8n/task-runner-python/**'
43
+ workflows:
44
+ - .github/**
45
+ - name: Setup and Build
46
+ if: steps.paths-filter.outputs.non-python == 'true'
47
+ uses: ./.github/actions/setup-nodejs
48
+
49
+ - name: Run format check
50
+ if: steps.paths-filter.outputs.non-python == 'true'
51
+ run: pnpm format:check
52
+
53
+ unit-test:
54
+ name: Unit tests
55
+ if: needs.install-and-build.outputs.non_python_changed == 'true'
56
+ uses: ./.github/workflows/units-tests-reusable.yml
57
+ needs: install-and-build
58
+ with:
59
+ ref: ${{ needs.install-and-build.outputs.commit_sha }}
60
+ collectCoverage: true
61
+ secrets:
62
+ CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
63
+
64
+ typecheck:
65
+ name: Typecheck
66
+ if: needs.install-and-build.outputs.non_python_changed == 'true'
67
+ runs-on: blacksmith-4vcpu-ubuntu-2204
68
+ needs: install-and-build
69
+ steps:
70
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
71
+ with:
72
+ ref: ${{ needs.install-and-build.outputs.commit_sha }}
73
+
74
+ - name: Setup Node.js
75
+ uses: ./.github/actions/setup-nodejs
76
+ with:
77
+ build-command: pnpm typecheck
78
+
79
+ lint:
80
+ name: Lint
81
+ if: needs.install-and-build.outputs.non_python_changed == 'true'
82
+ uses: ./.github/workflows/linting-reusable.yml
83
+ needs: install-and-build
84
+ with:
85
+ ref: ${{ needs.install-and-build.outputs.commit_sha }}
86
+
87
+ e2e-tests:
88
+ name: E2E Tests
89
+ needs: install-and-build
90
+ if: needs.install-and-build.outputs.non_python_changed == 'true'
91
+ uses: ./.github/workflows/playwright-test-ci.yml
92
+ with:
93
+ branch: ${{ needs.install-and-build.outputs.commit_sha }}
94
+ secrets: inherit
95
+
96
+ security-checks:
97
+ name: Security Checks
98
+ needs: install-and-build
99
+ if: needs.install-and-build.outputs.workflows_changed == 'true'
100
+ uses: ./.github/workflows/ci-security.yml
101
+ with:
102
+ ref: ${{ needs.install-and-build.outputs.commit_sha }}
103
+ secrets: inherit
104
+
105
+ # This job is required by GitHub branch protection rules.
106
+ # PRs cannot be merged unless this job passes.
107
+ # If you add/remove jobs that should block merging, update the 'needs' array
108
+ # and the skip conditions in the 'if' block below.
109
+ required-checks:
110
+ name: Required Checks
111
+ needs: [install-and-build, unit-test, typecheck, lint, e2e-tests, security-checks]
112
+ if: always()
113
+ runs-on: ubuntu-slim
114
+ steps:
115
+ - name: Fail if any required job failed or was skipped unexpectedly
116
+ # Explicit checks for each job's skip conditions:
117
+ # - Non-python jobs (unit-test, typecheck, lint, e2e-tests): can skip when non_python_changed == false
118
+ # - security-checks: can skip when workflows_changed == false
119
+ if: |
120
+ contains(needs.*.result, 'failure') ||
121
+ (needs.install-and-build.outputs.non_python_changed == 'true' && (
122
+ needs.unit-test.result == 'skipped' ||
123
+ needs.typecheck.result == 'skipped' ||
124
+ needs.lint.result == 'skipped' ||
125
+ needs.e2e-tests.result == 'skipped'
126
+ )) ||
127
+ (needs.install-and-build.outputs.workflows_changed == 'true' &&
128
+ needs.security-checks.result == 'skipped')
129
+ run: exit 1
.github/workflows/ci-python-workflow-builder-evals.yml ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: AI Workflow Builder Evals Python CI
2
+
3
+ on:
4
+ pull_request:
5
+ paths:
6
+ - packages/@n8n/ai-workflow-builder.ee/evaluations/programmatic/python/**
7
+ - .github/workflows/ci-python-workflow-builder-evals.yml
8
+ push:
9
+ paths:
10
+ - packages/@n8n/ai-workflow-builder.ee/evaluations/programmatic/python/**
11
+
12
+ jobs:
13
+ workflow-comparison:
14
+ name: Workflow Comparison Python
15
+ runs-on: ubuntu-latest
16
+ defaults:
17
+ run:
18
+ working-directory: packages/@n8n/ai-workflow-builder.ee/evaluations/programmatic/python
19
+ steps:
20
+ - name: Check out project
21
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
22
+
23
+ - name: Install uv
24
+ uses: astral-sh/setup-uv@d9e0f98d3fc6adb07d1e3d37f3043649ddad06a1 # 6.5.0
25
+ with:
26
+ enable-cache: true
27
+
28
+ - name: Install just
29
+ uses: extractions/setup-just@e33e0265a09d6d736e2ee1e0eb685ef1de4669ff # v3.0.0
30
+
31
+ - name: Install Python
32
+ run: uv python install 3.11
33
+
34
+ - name: Install project dependencies
35
+ run: just sync-all
36
+
37
+ - name: Format check
38
+ run: just format-check
39
+
40
+ - name: Typecheck
41
+ run: just typecheck
42
+
43
+ - name: Lint
44
+ run: just lint
45
+
46
+ - name: Python unit tests
47
+ run: uv run pytest --cov=src --cov-report=xml --cov-report=term-missing
48
+
49
+ - name: Upload coverage to Codecov
50
+ uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3
51
+ with:
52
+ token: ${{ secrets.CODECOV_TOKEN }}
53
+ files: packages/@n8n/ai-workflow-builder.ee/evaluations/programmatic/python/coverage.xml
54
+ flags: tests
55
+ name: workflow-comparison-python
56
+ fail_ci_if_error: false
.github/workflows/ci-python.yml ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Python CI
2
+
3
+ on:
4
+ pull_request:
5
+ paths:
6
+ - packages/@n8n/task-runner-python/**
7
+ - .github/workflows/ci-python.yml
8
+ push:
9
+ paths:
10
+ - packages/@n8n/task-runner-python/**
11
+
12
+ jobs:
13
+ checks:
14
+ name: Checks
15
+ runs-on: ubuntu-latest
16
+ defaults:
17
+ run:
18
+ working-directory: packages/@n8n/task-runner-python
19
+ steps:
20
+ - name: Check out project
21
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
22
+
23
+ - name: Install uv
24
+ uses: astral-sh/setup-uv@d9e0f98d3fc6adb07d1e3d37f3043649ddad06a1 # 6.5.0
25
+ with:
26
+ enable-cache: true
27
+
28
+ - name: Install just
29
+ uses: extractions/setup-just@e33e0265a09d6d736e2ee1e0eb685ef1de4669ff # v3.0.0
30
+
31
+ - name: Install Python
32
+ run: uv python install 3.13
33
+
34
+ - name: Install project dependencies
35
+ run: just sync-all
36
+
37
+ - name: Format check
38
+ run: just format-check
39
+
40
+ - name: Typecheck
41
+ run: just typecheck
42
+
43
+ - name: Lint
44
+ run: just lint
45
+
46
+ - name: Python unit tests
47
+ run: uv run pytest --cov=src --cov-report=xml --cov-report=term-missing
48
+
49
+ - name: Upload coverage to Codecov
50
+ uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3
51
+ with:
52
+ token: ${{ secrets.CODECOV_TOKEN }}
53
+ files: packages/@n8n/task-runner-python/coverage.xml
54
+ flags: tests
55
+ name: task-runner-python
56
+ fail_ci_if_error: false
.github/workflows/ci-security.yml ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Security Checks
2
+
3
+ on:
4
+ workflow_call:
5
+ inputs:
6
+ ref:
7
+ description: GitHub ref to scan.
8
+ required: false
9
+ type: string
10
+ default: ''
11
+
12
+ jobs:
13
+ poutine-scan:
14
+ name: Poutine Security Scan
15
+ uses: ./.github/workflows/security-poutine-scan-callable.yml
16
+ with:
17
+ ref: ${{ inputs.ref }}
18
+ secrets: inherit
19
+
20
+ # Future security checks can be added here:
21
+ # - dependency-scan:
22
+ # - secret-detection:
23
+ # - container-scan:
.github/workflows/claude.yml ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Claude PR Assistant
2
+
3
+ on:
4
+ issue_comment:
5
+ types: [created]
6
+ pull_request_review_comment:
7
+ types: [created]
8
+ issues:
9
+ types: [opened, assigned]
10
+ pull_request_review:
11
+ types: [submitted]
12
+
13
+ jobs:
14
+ claude-code-action:
15
+ if: |
16
+ (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
17
+ (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
18
+ (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
19
+ (github.event_name == 'issues' && contains(github.event.issue.body, '@claude'))
20
+ runs-on: ubuntu-latest
21
+ permissions:
22
+ contents: read
23
+ pull-requests: read
24
+ issues: read
25
+ id-token: write
26
+ steps:
27
+ - name: Checkout repository
28
+ uses: actions/checkout@v4
29
+ with:
30
+ fetch-depth: 1
31
+
32
+ - name: Run Claude PR Action
33
+ uses: anthropics/claude-code-action@beta
34
+ with:
35
+ anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
36
+ # Or use OAuth token instead:
37
+ # claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
38
+ timeout_minutes: '60'
39
+ # mode: tag # Default: responds to @claude mentions
40
+ # Optional: Restrict network access to specific domains only
41
+ # experimental_allowed_domains: |
42
+ # .anthropic.com
43
+ # .github.com
44
+ # api.github.com
45
+ # .githubusercontent.com
46
+ # bun.sh
47
+ # registry.npmjs.org
48
+ # .blob.core.windows.net
.github/workflows/create-patch-release-branch.yml ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Create Branch For Patch Release
2
+ on:
3
+ workflow_dispatch:
4
+ inputs:
5
+ commit_shas:
6
+ description: 'Comma-separated commit SHAs'
7
+ required: true
8
+ old_version:
9
+ description: 'Old version to be patched'
10
+ required: true
11
+ default: '1.0.0'
12
+ new_version:
13
+ description: 'The new patch version'
14
+ required: true
15
+ default: '1.0.1'
16
+ resumeUrl:
17
+ description: 'n8n workflow resume URL'
18
+ required: true
19
+ jobs:
20
+ create-branch:
21
+ runs-on: ubuntu-latest
22
+ permissions:
23
+ contents: write
24
+ pull-requests: write
25
+ steps:
26
+ - uses: actions/checkout@v4
27
+ with:
28
+ fetch-depth: 0
29
+ - name: Validate inputs
30
+ run: |
31
+ if ! [[ "${{ inputs.old_version }}" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z]+(\.[0-9A-Za-z]+)*)?$ ]]; then
32
+ echo "Invalid old version format: ${{ inputs.old_version }}"
33
+ exit 1
34
+ fi
35
+ if ! [[ "${{ inputs.new_version }}" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z]+(\.[0-9A-Za-z]+)*)?$ ]]; then
36
+ echo "Invalid new version format: ${{ inputs.new_version }}"
37
+ exit 1
38
+ fi
39
+ - name: Notify if inputs are invalid
40
+ if: ${{ failure() }}
41
+ run: |
42
+ curl -X POST -H "Content-Type: application/json" -d '{ "success": false, "message": "The old or new version you provided is invalid, make sure they both follow the SemVer format" }' ${{ inputs.resumeUrl }}
43
+ exit 1
44
+ - name: Setup, cherry-pick and push branch
45
+ run: |
46
+ git config user.name "github-actions[bot]"
47
+ git config user.email "github-actions[bot]@users.noreply.github.com"
48
+ git switch "n8n@${{ inputs.old_version }}" --detach
49
+ BRANCH="patch/${{ inputs.new_version }}"
50
+ git checkout -b "$BRANCH"
51
+ IFS=',' read -ra SHAS <<< "${{ inputs.commit_shas }}"
52
+ for sha in "${SHAS[@]}"; do
53
+ sha=$(echo "$sha" | xargs)
54
+ if ! git merge-base --is-ancestor "$sha" HEAD; then
55
+ echo "Cherry-picking commit $sha"
56
+ git cherry-pick "$sha"
57
+ else
58
+ echo "Commit $sha is already in the branch, skipping"
59
+ fi
60
+ done
61
+ git push -f origin "$BRANCH"
62
+ - name: Notify if cherry-pick is successful
63
+ if: ${{ success() }}
64
+ run: |
65
+ curl -X POST -H "Content-Type: application/json" -d '{ "success": true }' ${{ inputs.resumeUrl }}
66
+ - name: Notify if cherry-pick is not successful
67
+ if: ${{ failure() }}
68
+ run: |
69
+ curl -X POST -H "Content-Type: application/json" -d '{ "success": false, "message": "There was a conflict when trying to create the branch, please do the cherry-pick and resolve the conflicts manually or do not include the PRs that caused the conflict" }' ${{ inputs.resumeUrl }}
.github/workflows/data-tooling.yml ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Data tooling, test exports and imports of data to various db types
2
+
3
+ # TODO: Uncomment this after it works on a manual invocation
4
+ # on:
5
+ # pull_request:
6
+ # branches:
7
+ # - '**'
8
+ # - '!release/*'
9
+
10
+ on:
11
+ workflow_dispatch:
12
+
13
+ jobs:
14
+ sqlite-export-sqlite-import:
15
+ name: sqlite database export -> sqlite database import
16
+ runs-on: blacksmith-2vcpu-ubuntu-2204
17
+ outputs:
18
+ db_changed: ${{ steps.paths-filter.outputs.db == 'true' }}
19
+ steps:
20
+ - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
21
+ # with:
22
+ # ref: refs/pull/${{ github.event.pull_request.number }}/merge
23
+
24
+ - name: Check for frontend changes
25
+ uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
26
+ id: paths-filter
27
+ with:
28
+ filters: |
29
+ db:
30
+ - packages/@n8n/db/**
31
+ - packages/cli/**
32
+ - name: Setup, build and export sqlite
33
+ if: steps.paths-filter.outputs.db == 'true'
34
+ uses: ./.github/actions/setup-nodejs
35
+ with:
36
+ build-command: |
37
+ pnpm build
38
+ ./packages/cli/bin/n8n export:entities --outputDir packages/cli/commands/export/outputs
39
+ ./packages/cli/bin/n8n import:entities --inputDir packages/cli/commands/export/outputs --truncateTables
40
+ postgres-export-postgres-import:
41
+ name: postgres export -> postgres import
42
+ runs-on: blacksmith-2vcpu-ubuntu-2204
43
+ outputs:
44
+ db_changed: ${{ steps.paths-filter.outputs.db == 'true' }}
45
+ env:
46
+ DB_TYPE: postgresdb
47
+ DB_POSTGRESDB_DATABASE: n8n
48
+ DB_POSTGRESDB_HOST: localhost
49
+ DB_POSTGRESDB_PORT: 5432
50
+ DB_POSTGRESDB_USER: postgres
51
+ DB_POSTGRESDB_PASSWORD: password
52
+ DB_POSTGRESDB_POOL_SIZE: 1 # Detect connection pooling deadlocks
53
+ steps:
54
+ - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
55
+ # with:
56
+ # ref: refs/pull/${{ github.event.pull_request.number }}/merge
57
+
58
+ - name: Check for db changes
59
+ uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
60
+ id: paths-filter
61
+ with:
62
+ filters: |
63
+ db:
64
+ - packages/@n8n/db/**
65
+ - packages/cli/**
66
+
67
+ - name: Setup and Build
68
+ if: steps.paths-filter.outputs.db == 'true'
69
+ uses: ./.github/actions/setup-nodejs
70
+
71
+ - name: Start Postgres
72
+ if: steps.paths-filter.outputs.db == 'true'
73
+ uses: isbang/compose-action@802a148945af6399a338c7906c267331b39a71af # v2.0.0
74
+ with:
75
+ compose-file: ./.github/docker-compose.yml
76
+ services: |
77
+ postgres
78
+
79
+ - name: Export postgres
80
+ if: steps.paths-filter.outputs.db == 'true'
81
+ run: ./packages/cli/bin/n8n export:entities --outputDir packages/cli/commands/export/outputs
82
+ - name: Import postgres
83
+ if: steps.paths-filter.outputs.db == 'true'
84
+ run: ./packages/cli/bin/n8n import:entities --inputDir packages/cli/commands/export/outputs --truncateTables
.github/workflows/docker-base-image.yml ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Docker Base Image CI
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - master
7
+ paths:
8
+ - 'docker/images/n8n-base/Dockerfile'
9
+ pull_request:
10
+ paths:
11
+ - 'docker/images/n8n-base/Dockerfile'
12
+ workflow_dispatch:
13
+ inputs:
14
+ push:
15
+ description: 'Push to registries'
16
+ required: false
17
+ default: false
18
+ type: boolean
19
+
20
+ jobs:
21
+ build:
22
+ runs-on: ubuntu-latest
23
+ strategy:
24
+ matrix:
25
+ node_version: ['20', '22.21.1', '24']
26
+ steps:
27
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
28
+
29
+ - name: Set up QEMU
30
+ uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
31
+
32
+ - name: Set up Docker Buildx
33
+ uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
34
+
35
+ - name: Login to DHI Registry (for pulling base images)
36
+ uses: ./.github/actions/docker-registry-login
37
+ with:
38
+ login-ghcr: 'false'
39
+ login-dhi: 'true'
40
+ dockerhub-username: ${{ secrets.DOCKER_USERNAME }}
41
+ dockerhub-password: ${{ secrets.DOCKER_PASSWORD }}
42
+
43
+ - name: Login to Docker registries (for pushing)
44
+ if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.push == true)
45
+ uses: ./.github/actions/docker-registry-login
46
+ with:
47
+ login-ghcr: 'true'
48
+ login-dockerhub: 'true'
49
+ dockerhub-username: ${{ secrets.DOCKER_USERNAME }}
50
+ dockerhub-password: ${{ secrets.DOCKER_PASSWORD }}
51
+
52
+ - name: Build and push
53
+ uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
54
+ with:
55
+ context: .
56
+ file: ./docker/images/n8n-base/Dockerfile
57
+ build-args: |
58
+ NODE_VERSION=${{ matrix.node_version }}
59
+ platforms: linux/amd64,linux/arm64
60
+ provenance: ${{ github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.push == true) }}
61
+ sbom: ${{ github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.push == true) }}
62
+ push: ${{ github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.push == true) }}
63
+ tags: |
64
+ ${{ secrets.DOCKER_USERNAME }}/base:${{ matrix.node_version }}-${{ github.sha }}
65
+ ${{ secrets.DOCKER_USERNAME }}/base:${{ matrix.node_version }}
66
+ ghcr.io/${{ github.repository_owner }}/base:${{ matrix.node_version }}-${{ github.sha }}
67
+ ghcr.io/${{ github.repository_owner }}/base:${{ matrix.node_version }}
68
+ no-cache: true
.github/workflows/docker-build-push.yml ADDED
@@ -0,0 +1,294 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This workflow is used to build and push the Docker image for n8nio/n8n and n8nio/runners
2
+ #
3
+ # - Uses docker-config.mjs for context determination, this determines what needs to be built based on the trigger
4
+ # - Uses docker-tags.mjs for tag generation, this generates the tags for the images
5
+
6
+ name: 'Docker: Build and Push'
7
+
8
+ env:
9
+ NODE_OPTIONS: '--max-old-space-size=7168'
10
+ NODE_VERSION: '22.21.1'
11
+
12
+ on:
13
+ schedule:
14
+ - cron: '0 0 * * *'
15
+
16
+ workflow_call:
17
+ inputs:
18
+ n8n_version:
19
+ description: 'N8N version to build'
20
+ required: true
21
+ type: string
22
+ release_type:
23
+ description: 'Release type (stable, nightly, dev)'
24
+ required: false
25
+ type: string
26
+ default: 'stable'
27
+ push_enabled:
28
+ description: 'Whether to push the built images'
29
+ required: false
30
+ type: boolean
31
+ default: true
32
+
33
+ workflow_dispatch:
34
+ inputs:
35
+ push_enabled:
36
+ description: 'Push image to registry'
37
+ required: false
38
+ type: boolean
39
+ default: true
40
+ success_url:
41
+ description: 'URL to call after the build is successful'
42
+ required: false
43
+ type: string
44
+
45
+ jobs:
46
+ determine-build-context:
47
+ name: Determine Build Context
48
+ runs-on: ubuntu-latest
49
+ outputs:
50
+ release_type: ${{ steps.context.outputs.release_type }}
51
+ n8n_version: ${{ steps.context.outputs.version }}
52
+ push_enabled: ${{ steps.context.outputs.push_enabled }}
53
+ push_to_docker: ${{ steps.context.outputs.push_to_docker }}
54
+ build_matrix: ${{ steps.context.outputs.build_matrix }}
55
+ steps:
56
+ - name: Checkout code
57
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
58
+
59
+ - name: Determine build context
60
+ id: context
61
+ run: |
62
+ node .github/scripts/docker/docker-config.mjs \
63
+ --event "${{ github.event_name }}" \
64
+ --pr "${{ github.event.pull_request.number }}" \
65
+ --branch "${{ github.ref_name }}" \
66
+ --version "${{ inputs.n8n_version }}" \
67
+ --release-type "${{ inputs.release_type }}" \
68
+ --push-enabled "${{ inputs.push_enabled }}"
69
+
70
+ build-and-push-docker:
71
+ name: Build App, then Build and Push Docker Image (${{ matrix.platform }})
72
+ needs: determine-build-context
73
+ runs-on: ${{ matrix.runner }}
74
+ timeout-minutes: 25
75
+ strategy:
76
+ matrix: ${{ fromJSON(needs.determine-build-context.outputs.build_matrix) }}
77
+ outputs:
78
+ image_ref: ${{ steps.determine-tags.outputs.n8n_primary_tag }}
79
+ primary_ghcr_manifest_tag: ${{ steps.determine-tags.outputs.n8n_primary_tag }}
80
+ runners_primary_ghcr_manifest_tag: ${{ steps.determine-tags.outputs.runners_primary_tag }}
81
+ runners_distroless_primary_ghcr_manifest_tag: ${{ steps.determine-tags.outputs.runners_distroless_primary_tag }}
82
+ steps:
83
+ - name: Checkout code
84
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
85
+ with:
86
+ fetch-depth: 0
87
+
88
+ - name: Setup and Build
89
+ uses: ./.github/actions/setup-nodejs
90
+ with:
91
+ build-command: pnpm build:n8n
92
+ enable-docker-cache: 'true'
93
+
94
+ - name: Determine Docker tags for all images
95
+ id: determine-tags
96
+ run: |
97
+ node .github/scripts/docker/docker-tags.mjs \
98
+ --all \
99
+ --version "${{ needs.determine-build-context.outputs.n8n_version }}" \
100
+ --platform "${{ matrix.docker_platform }}" \
101
+ ${{ needs.determine-build-context.outputs.push_to_docker == 'true' && '--include-docker' || '' }}
102
+
103
+ echo "=== Generated Docker Tags ==="
104
+ cat "$GITHUB_OUTPUT" | grep "_tags=" | while IFS='=' read -r key value; do
105
+ echo "${key}: ${value%%,*}..." # Show first tag for brevity
106
+ done
107
+
108
+ - name: Login to Docker registries
109
+ if: needs.determine-build-context.outputs.push_enabled == 'true'
110
+ uses: ./.github/actions/docker-registry-login
111
+ with:
112
+ login-ghcr: true
113
+ login-dockerhub: ${{ needs.determine-build-context.outputs.push_to_docker == 'true' }}
114
+ dockerhub-username: ${{ secrets.DOCKER_USERNAME }}
115
+ dockerhub-password: ${{ secrets.DOCKER_PASSWORD }}
116
+
117
+ - name: Build and push n8n Docker image
118
+ uses: useblacksmith/build-push-action@30c71162f16ea2c27c3e21523255d209b8b538c1 # v2
119
+ with:
120
+ context: .
121
+ file: ./docker/images/n8n/Dockerfile
122
+ build-args: |
123
+ NODE_VERSION=${{ env.NODE_VERSION }}
124
+ N8N_VERSION=${{ needs.determine-build-context.outputs.n8n_version }}
125
+ N8N_RELEASE_TYPE=${{ needs.determine-build-context.outputs.release_type }}
126
+ platforms: ${{ matrix.docker_platform }}
127
+ provenance: true
128
+ sbom: true
129
+ push: ${{ needs.determine-build-context.outputs.push_enabled == 'true' }}
130
+ tags: ${{ steps.determine-tags.outputs.n8n_tags }}
131
+
132
+ - name: Build and push task runners Docker image (Alpine)
133
+ uses: useblacksmith/build-push-action@30c71162f16ea2c27c3e21523255d209b8b538c1 # v2
134
+ with:
135
+ context: .
136
+ file: ./docker/images/runners/Dockerfile
137
+ build-args: |
138
+ NODE_VERSION=${{ env.NODE_VERSION }}
139
+ N8N_VERSION=${{ needs.determine-build-context.outputs.n8n_version }}
140
+ N8N_RELEASE_TYPE=${{ needs.determine-build-context.outputs.release_type }}
141
+ platforms: ${{ matrix.docker_platform }}
142
+ provenance: true
143
+ sbom: true
144
+ push: ${{ needs.determine-build-context.outputs.push_enabled == 'true' }}
145
+ tags: ${{ steps.determine-tags.outputs.runners_tags }}
146
+
147
+ - name: Build and push task runners Docker image (distroless)
148
+ uses: useblacksmith/build-push-action@30c71162f16ea2c27c3e21523255d209b8b538c1 # v2
149
+ with:
150
+ context: .
151
+ file: ./docker/images/runners/Dockerfile.distroless
152
+ build-args: |
153
+ NODE_VERSION=${{ env.NODE_VERSION }}
154
+ N8N_VERSION=${{ needs.determine-build-context.outputs.n8n_version }}
155
+ N8N_RELEASE_TYPE=${{ needs.determine-build-context.outputs.release_type }}
156
+ platforms: ${{ matrix.docker_platform }}
157
+ provenance: true
158
+ sbom: true
159
+ push: ${{ needs.determine-build-context.outputs.push_enabled == 'true' }}
160
+ tags: ${{ steps.determine-tags.outputs.runners_distroless_tags }}
161
+
162
+ create_multi_arch_manifest:
163
+ name: Create Multi-Arch Manifest
164
+ needs: [determine-build-context, build-and-push-docker]
165
+ runs-on: ubuntu-latest
166
+ if: |
167
+ needs.build-and-push-docker.result == 'success' &&
168
+ needs.determine-build-context.outputs.push_enabled == 'true'
169
+ steps:
170
+ - name: Checkout
171
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
172
+
173
+ - name: Set up Docker Buildx
174
+ uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
175
+
176
+ - name: Login to Docker registries
177
+ uses: ./.github/actions/docker-registry-login
178
+ with:
179
+ login-ghcr: true
180
+ login-dockerhub: ${{ needs.determine-build-context.outputs.push_to_docker == 'true' }}
181
+ dockerhub-username: ${{ secrets.DOCKER_USERNAME }}
182
+ dockerhub-password: ${{ secrets.DOCKER_PASSWORD }}
183
+
184
+ - name: Create GHCR multi-arch manifests
185
+ run: |
186
+ RELEASE_TYPE="${{ needs.determine-build-context.outputs.release_type }}"
187
+
188
+ # Function to create manifest for an image
189
+ create_manifest() {
190
+ local IMAGE_NAME=$1
191
+ local MANIFEST_TAG=$2
192
+
193
+ if [[ -z "$MANIFEST_TAG" ]]; then
194
+ echo "Skipping $IMAGE_NAME - no manifest tag"
195
+ return
196
+ fi
197
+
198
+ echo "Creating GHCR manifest for $IMAGE_NAME: $MANIFEST_TAG"
199
+
200
+ # For branch builds, only AMD64 is built
201
+ if [[ "$RELEASE_TYPE" == "branch" ]]; then
202
+ docker buildx imagetools create \
203
+ --tag "$MANIFEST_TAG" \
204
+ "${MANIFEST_TAG}-amd64"
205
+ else
206
+ docker buildx imagetools create \
207
+ --tag "$MANIFEST_TAG" \
208
+ "${MANIFEST_TAG}-amd64" \
209
+ "${MANIFEST_TAG}-arm64"
210
+ fi
211
+ }
212
+
213
+ # Create manifests for all images
214
+ create_manifest "n8n" "${{ needs.build-and-push-docker.outputs.primary_ghcr_manifest_tag }}"
215
+ create_manifest "runners" "${{ needs.build-and-push-docker.outputs.runners_primary_ghcr_manifest_tag }}"
216
+ create_manifest "runners-distroless" "${{ needs.build-and-push-docker.outputs.runners_distroless_primary_ghcr_manifest_tag }}"
217
+
218
+ - name: Create Docker Hub manifests
219
+ if: needs.determine-build-context.outputs.push_to_docker == 'true'
220
+ run: |
221
+ VERSION="${{ needs.determine-build-context.outputs.n8n_version }}"
222
+ DOCKER_BASE="${{ secrets.DOCKER_USERNAME }}"
223
+
224
+ # Create manifests for each image type
225
+ declare -A images=(
226
+ ["n8n"]="${VERSION}"
227
+ ["runners"]="${VERSION}"
228
+ ["runners-distroless"]="${VERSION}-distroless"
229
+ )
230
+
231
+ for image in "${!images[@]}"; do
232
+ TAG_SUFFIX="${images[$image]}"
233
+ IMAGE_NAME="${image//-distroless/}" # Remove -distroless from image name
234
+
235
+ echo "Creating Docker Hub manifest for $image"
236
+ docker buildx imagetools create \
237
+ --tag "${DOCKER_BASE}/${IMAGE_NAME}:${TAG_SUFFIX}" \
238
+ "${DOCKER_BASE}/${IMAGE_NAME}:${TAG_SUFFIX}-amd64" \
239
+ "${DOCKER_BASE}/${IMAGE_NAME}:${TAG_SUFFIX}-arm64"
240
+ done
241
+
242
+ call-success-url:
243
+ name: Call Success URL
244
+ needs: [create_multi_arch_manifest]
245
+ runs-on: ubuntu-latest
246
+ if: needs.create_multi_arch_manifest.result == 'success' || needs.create_multi_arch_manifest.result == 'skipped'
247
+ steps:
248
+ - name: Call Success URL
249
+ env:
250
+ SUCCESS_URL: ${{ github.event.inputs.success_url }}
251
+ if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.success_url != '' }}
252
+ run: |
253
+ echo "Calling success URL: ${{ env.SUCCESS_URL }}"
254
+ curl -v "${{ env.SUCCESS_URL }}" || echo "Failed to call success URL"
255
+ shell: bash
256
+
257
+ security-scan:
258
+ name: Security Scan
259
+ needs: [determine-build-context, build-and-push-docker, create_multi_arch_manifest]
260
+ if: |
261
+ success() &&
262
+ (needs.determine-build-context.outputs.release_type == 'stable' ||
263
+ needs.determine-build-context.outputs.release_type == 'nightly' ||
264
+ needs.determine-build-context.outputs.release_type == 'rc')
265
+ uses: ./.github/workflows/security-trivy-scan-callable.yml
266
+ with:
267
+ image_ref: ${{ needs.build-and-push-docker.outputs.image_ref }}
268
+ secrets: inherit
269
+
270
+ security-scan-runners:
271
+ name: Security Scan (runners)
272
+ needs: [determine-build-context, build-and-push-docker, create_multi_arch_manifest]
273
+ if: |
274
+ success() &&
275
+ (needs.determine-build-context.outputs.release_type == 'stable' ||
276
+ needs.determine-build-context.outputs.release_type == 'nightly' ||
277
+ needs.determine-build-context.outputs.release_type == 'rc')
278
+ uses: ./.github/workflows/security-trivy-scan-callable.yml
279
+ with:
280
+ image_ref: ${{ needs.build-and-push-docker.outputs.runners_primary_ghcr_manifest_tag }}
281
+ secrets: inherit
282
+
283
+ notify-on-failure:
284
+ name: Notify Cats on nightly build failure
285
+ runs-on: ubuntu-latest
286
+ needs: [build-and-push-docker]
287
+ if: needs.build-and-push-docker.result == 'failure' && github.event_name == 'schedule'
288
+ steps:
289
+ - uses: act10ns/slack@44541246747a30eb3102d87f7a4cc5471b0ffb7d # v2.1.0
290
+ with:
291
+ status: ${{ needs.build-and-push-docker.result }}
292
+ channel: '#team-catalysts'
293
+ webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }}
294
+ message: Nightly Docker build failed - ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
.github/workflows/docker-images-benchmark.yml ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Benchmark Docker Image CI
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ push:
6
+ branches:
7
+ - master
8
+ paths:
9
+ - 'packages/@n8n/benchmark/**'
10
+ - 'pnpm-lock.yaml'
11
+ - 'pnpm-workspace.yaml'
12
+ - '.github/workflows/docker-images-benchmark.yml'
13
+
14
+ jobs:
15
+ build:
16
+ runs-on: ubuntu-latest
17
+
18
+ steps:
19
+ - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
20
+
21
+ - name: Set up QEMU
22
+ uses: docker/setup-qemu-action@53851d14592bedcffcf25ea515637cff71ef929a # v3.3.0
23
+
24
+ - name: Set up Docker Buildx
25
+ uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0
26
+
27
+ - name: Login to GitHub Container Registry
28
+ uses: ./.github/actions/docker-registry-login
29
+
30
+ - name: Build
31
+ uses: docker/build-push-action@b32b51a8eda65d6793cd0494a773d4f6bcef32dc # v6.11.0
32
+ env:
33
+ DOCKER_BUILD_SUMMARY: false
34
+ with:
35
+ context: .
36
+ file: ./packages/@n8n/benchmark/Dockerfile
37
+ platforms: linux/amd64
38
+ provenance: false
39
+ push: true
40
+ tags: |
41
+ ghcr.io/${{ github.repository_owner }}/n8n-benchmark:latest
.github/workflows/linting-reusable.yml ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Reusable linting workflow
2
+
3
+ on:
4
+ workflow_call:
5
+ inputs:
6
+ ref:
7
+ description: GitHub ref to lint.
8
+ required: false
9
+ type: string
10
+ default: ''
11
+ nodeVersion:
12
+ description: Version of node to use.
13
+ required: false
14
+ type: string
15
+ default: 22.x
16
+
17
+ env:
18
+ NODE_OPTIONS: --max-old-space-size=7168
19
+
20
+ jobs:
21
+ lint:
22
+ name: Lint
23
+ runs-on: blacksmith-4vcpu-ubuntu-2204
24
+ steps:
25
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
26
+ with:
27
+ ref: ${{ inputs.ref }}
28
+
29
+ - name: Build and Test
30
+ uses: ./.github/actions/setup-nodejs
31
+ with:
32
+ build-command: pnpm lint
33
+ node-version: ${{ inputs.nodeVersion }}
.github/workflows/notify-pr-status.yml ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Notify PR status changed
2
+
3
+ on:
4
+ pull_request_review:
5
+ types: [submitted, dismissed]
6
+ pull_request:
7
+ types: [closed]
8
+
9
+ jobs:
10
+ notify:
11
+ runs-on: ubuntu-latest
12
+ if: >-
13
+ (github.event_name == 'pull_request_review' && github.event.review.state == 'approved') ||
14
+ (github.event_name == 'pull_request_review' && github.event.review.state == 'dismissed') ||
15
+ (github.event_name == 'pull_request' && github.event.pull_request.merged == true) ||
16
+ (github.event_name == 'pull_request' && github.event.pull_request.merged == false && github.event.action == 'closed')
17
+ steps:
18
+ - uses: fjogeleit/http-request-action@bf78da14118941f7e940279dd58f67e863cbeff6 # v1
19
+ if: ${{!contains(github.event.pull_request.labels.*.name, 'community')}}
20
+ name: Notify
21
+ env:
22
+ PR_URL: ${{ github.event.pull_request.html_url }}
23
+ with:
24
+ url: ${{ secrets.N8N_NOTIFY_PR_STATUS_CHANGED_URL }}
25
+ method: 'POST'
26
+ customHeaders: '{ "x-api-token": "${{ secrets.N8N_NOTIFY_PR_STATUS_CHANGED_TOKEN }}" }'
27
+ data: '{ "event_name": "${{ github.event_name }}", "pr_url": "${{ env.PR_URL }}", "event": ${{ toJSON(github.event) }} }'