mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 09:36:44 +00:00
ci: Docker move build stage outside container (no-changelog) (#16009)
This commit is contained in:
@@ -12,9 +12,8 @@ packages/**/*.test.*
|
|||||||
.github
|
.github
|
||||||
!.github/scripts
|
!.github/scripts
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
packages/cli/dist/**/e2e.*
|
|
||||||
docker/compose
|
docker/compose
|
||||||
docker/**/Dockerfile
|
docker/**/Dockerfile
|
||||||
.vscode
|
.vscode
|
||||||
cypress
|
cypress
|
||||||
test-workflows
|
test-workflows
|
||||||
332
.github/workflows/docker-build-push.yml
vendored
Normal file
332
.github/workflows/docker-build-push.yml
vendored
Normal file
@@ -0,0 +1,332 @@
|
|||||||
|
# This workflow is used to build and push the Docker image for n8n
|
||||||
|
# - build-and-push-docker: This builds on both an ARM64 and AMD64 runner so the builds are native to the platform. Uses blacksmith native runners and build-push-action
|
||||||
|
# - create_multi_arch_manifest: This creates the multi-arch manifest for the Docker image. Needed to recombine the images from the build-and-push-docker job since they are separate runners.
|
||||||
|
# - security-scan: This scans the Docker image for security vulnerabilities using Trivy.
|
||||||
|
|
||||||
|
name: 'Docker: Build and Push'
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 0 * * *'
|
||||||
|
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
n8n_version:
|
||||||
|
description: 'N8N version to build'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
release_type:
|
||||||
|
description: 'Release type (stable, nightly, dev)'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
default: 'dev'
|
||||||
|
push_enabled:
|
||||||
|
description: 'Whether to push the built images'
|
||||||
|
required: false
|
||||||
|
type: boolean
|
||||||
|
default: true
|
||||||
|
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
release_type:
|
||||||
|
description: 'Release type'
|
||||||
|
required: true
|
||||||
|
type: choice
|
||||||
|
options:
|
||||||
|
- nightly
|
||||||
|
- dev
|
||||||
|
- stable
|
||||||
|
- branch
|
||||||
|
default: 'dev'
|
||||||
|
push_to_registry:
|
||||||
|
description: 'Push image to registry'
|
||||||
|
required: false
|
||||||
|
type: boolean
|
||||||
|
default: true
|
||||||
|
|
||||||
|
pull_request:
|
||||||
|
types:
|
||||||
|
- opened
|
||||||
|
- ready_for_review
|
||||||
|
paths:
|
||||||
|
- '.github/workflows/docker-build-push.yml'
|
||||||
|
- 'docker/images/n8n/Dockerfile'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-push-docker:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
platform: [amd64, arm64]
|
||||||
|
include:
|
||||||
|
- platform: amd64
|
||||||
|
runner: blacksmith-4vcpu-ubuntu-2204
|
||||||
|
docker_platform: linux/amd64
|
||||||
|
- platform: arm64
|
||||||
|
runner: blacksmith-4vcpu-ubuntu-2204-arm
|
||||||
|
docker_platform: linux/arm64
|
||||||
|
|
||||||
|
name: Build App, then Build and Push Docker Image (${{ matrix.platform }})
|
||||||
|
runs-on: ${{ matrix.runner }}
|
||||||
|
timeout-minutes: 15
|
||||||
|
outputs:
|
||||||
|
image_ref: ${{ steps.determine-tags.outputs.primary_tag }}
|
||||||
|
primary_ghcr_manifest_tag: ${{ steps.determine-tags.outputs.primary_ghcr_manifest_tag }}
|
||||||
|
push_enabled_status: ${{ steps.context.outputs.push_enabled }}
|
||||||
|
release_type: ${{ steps.context.outputs.release_type }}
|
||||||
|
n8n_version: ${{ steps.context.outputs.n8n_version }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Setup pnpm
|
||||||
|
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
||||||
|
with:
|
||||||
|
run_install: false
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: useblacksmith/setup-node@65c6ca86fdeb0ab3d85e78f57e4f6a7e4780b391 # v5.0.4
|
||||||
|
with:
|
||||||
|
node-version: 22.x
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Configure Turborepo Cache
|
||||||
|
uses: useblacksmith/caching-for-turbo@bafb57e7ebdbf1185762286ec94d24648cd3938a # v1
|
||||||
|
|
||||||
|
- name: Build n8n for Docker
|
||||||
|
run: pnpm build:n8n
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Determine build context values
|
||||||
|
id: context
|
||||||
|
run: |
|
||||||
|
if [[ "${{ github.event_name }}" == "schedule" ]]; then
|
||||||
|
echo "release_type=nightly" >> $GITHUB_OUTPUT
|
||||||
|
echo "n8n_version=snapshot" >> $GITHUB_OUTPUT
|
||||||
|
echo "push_enabled=true" >> $GITHUB_OUTPUT
|
||||||
|
elif [[ "${{ github.event_name }}" == "workflow_call" ]]; then
|
||||||
|
echo "release_type=${{ inputs.release_type }}" >> $GITHUB_OUTPUT
|
||||||
|
echo "n8n_version=${{ inputs.n8n_version }}" >> $GITHUB_OUTPUT
|
||||||
|
echo "push_enabled=${{ inputs.push_enabled }}" >> $GITHUB_OUTPUT
|
||||||
|
elif [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
|
||||||
|
if [[ "${{ inputs.release_type }}" == "branch" ]]; then
|
||||||
|
BRANCH_NAME="${{ github.ref_name }}"
|
||||||
|
SAFE_BRANCH_NAME=$(echo "$BRANCH_NAME" | tr '/' '-' | tr -cd '[:alnum:]-_')
|
||||||
|
echo "release_type=branch" >> $GITHUB_OUTPUT
|
||||||
|
echo "n8n_version=branch-${SAFE_BRANCH_NAME}" >> $GITHUB_OUTPUT
|
||||||
|
echo "push_enabled=${{ inputs.push_to_registry }}" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "release_type=${{ inputs.release_type }}" >> $GITHUB_OUTPUT
|
||||||
|
echo "n8n_version=snapshot" >> $GITHUB_OUTPUT
|
||||||
|
echo "push_enabled=true" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
elif [[ "${{ github.event_name }}" == "push" ]]; then
|
||||||
|
echo "release_type=dev" >> $GITHUB_OUTPUT
|
||||||
|
echo "n8n_version=snapshot" >> $GITHUB_OUTPUT
|
||||||
|
echo "push_enabled=true" >> $GITHUB_OUTPUT
|
||||||
|
elif [[ "${{ github.event_name }}" == "pull_request" ]]; then
|
||||||
|
echo "release_type=dev" >> $GITHUB_OUTPUT
|
||||||
|
echo "n8n_version=pr-${{ github.event.pull_request.number }}" >> $GITHUB_OUTPUT
|
||||||
|
echo "push_enabled=false" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "release_type=dev" >> $GITHUB_OUTPUT
|
||||||
|
echo "n8n_version=snapshot" >> $GITHUB_OUTPUT
|
||||||
|
echo "push_enabled=false" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Determine Docker tags
|
||||||
|
id: determine-tags
|
||||||
|
run: |
|
||||||
|
RELEASE_TYPE="${{ steps.context.outputs.release_type }}"
|
||||||
|
N8N_VERSION_TAG="${{ steps.context.outputs.n8n_version }}"
|
||||||
|
GHCR_BASE="ghcr.io/${{ github.repository_owner }}/n8n"
|
||||||
|
DOCKER_BASE="${{ secrets.DOCKER_USERNAME }}/n8n"
|
||||||
|
PLATFORM="${{ matrix.platform }}"
|
||||||
|
|
||||||
|
GHCR_TAGS_FOR_PUSH=""
|
||||||
|
DOCKER_TAGS_FOR_PUSH=""
|
||||||
|
|
||||||
|
PRIMARY_GHCR_MANIFEST_TAG_VALUE=""
|
||||||
|
PRIMARY_DOCKER_MANIFEST_TAG_VALUE=""
|
||||||
|
|
||||||
|
if [[ "$RELEASE_TYPE" == "stable" && -z "$N8N_VERSION_TAG" ]]; then
|
||||||
|
echo "Error: N8N_VERSION_TAG is empty for a stable release."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$RELEASE_TYPE" in
|
||||||
|
"stable")
|
||||||
|
PRIMARY_GHCR_MANIFEST_TAG_VALUE="${GHCR_BASE}:${N8N_VERSION_TAG}"
|
||||||
|
PRIMARY_DOCKER_MANIFEST_TAG_VALUE="${DOCKER_BASE}:${N8N_VERSION_TAG}"
|
||||||
|
GHCR_TAGS_FOR_PUSH="${PRIMARY_GHCR_MANIFEST_TAG_VALUE}-${PLATFORM}"
|
||||||
|
DOCKER_TAGS_FOR_PUSH="${PRIMARY_DOCKER_MANIFEST_TAG_VALUE}-${PLATFORM}"
|
||||||
|
;;
|
||||||
|
"nightly")
|
||||||
|
PRIMARY_GHCR_MANIFEST_TAG_VALUE="${GHCR_BASE}:nightly"
|
||||||
|
PRIMARY_DOCKER_MANIFEST_TAG_VALUE="${DOCKER_BASE}:nightly"
|
||||||
|
GHCR_TAGS_FOR_PUSH="${PRIMARY_GHCR_MANIFEST_TAG_VALUE}-${PLATFORM}"
|
||||||
|
DOCKER_TAGS_FOR_PUSH="${PRIMARY_DOCKER_MANIFEST_TAG_VALUE}-${PLATFORM}"
|
||||||
|
;;
|
||||||
|
"branch")
|
||||||
|
PRIMARY_GHCR_MANIFEST_TAG_VALUE="${GHCR_BASE}:${N8N_VERSION_TAG}"
|
||||||
|
GHCR_TAGS_FOR_PUSH="${PRIMARY_GHCR_MANIFEST_TAG_VALUE}-${PLATFORM}"
|
||||||
|
PRIMARY_DOCKER_MANIFEST_TAG_VALUE=""
|
||||||
|
DOCKER_TAGS_FOR_PUSH=""
|
||||||
|
;;
|
||||||
|
"dev"|*)
|
||||||
|
if [[ "$N8N_VERSION_TAG" == pr-* ]]; then
|
||||||
|
PRIMARY_GHCR_MANIFEST_TAG_VALUE="${GHCR_BASE}:${N8N_VERSION_TAG}"
|
||||||
|
GHCR_TAGS_FOR_PUSH="${PRIMARY_GHCR_MANIFEST_TAG_VALUE}-${PLATFORM}"
|
||||||
|
PRIMARY_DOCKER_MANIFEST_TAG_VALUE=""
|
||||||
|
DOCKER_TAGS_FOR_PUSH=""
|
||||||
|
else
|
||||||
|
PRIMARY_GHCR_MANIFEST_TAG_VALUE="${GHCR_BASE}:dev"
|
||||||
|
PRIMARY_DOCKER_MANIFEST_TAG_VALUE="${DOCKER_BASE}:dev"
|
||||||
|
GHCR_TAGS_FOR_PUSH="${PRIMARY_GHCR_MANIFEST_TAG_VALUE}-${PLATFORM}"
|
||||||
|
DOCKER_TAGS_FOR_PUSH="${PRIMARY_DOCKER_MANIFEST_TAG_VALUE}-${PLATFORM}"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
ALL_TAGS="${GHCR_TAGS_FOR_PUSH}"
|
||||||
|
if [[ -n "$DOCKER_TAGS_FOR_PUSH" ]]; then
|
||||||
|
ALL_TAGS="${ALL_TAGS}\n${DOCKER_TAGS_FOR_PUSH}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Generated Tags for push: $ALL_TAGS"
|
||||||
|
echo "tags<<EOF" >> $GITHUB_OUTPUT
|
||||||
|
echo -e "$ALL_TAGS" >> $GITHUB_OUTPUT
|
||||||
|
echo "EOF" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
echo "ghcr_platform_tag=${GHCR_TAGS_FOR_PUSH}" >> $GITHUB_OUTPUT
|
||||||
|
echo "dockerhub_platform_tag=${DOCKER_TAGS_FOR_PUSH}" >> $GITHUB_OUTPUT
|
||||||
|
echo "primary_ghcr_manifest_tag=${PRIMARY_GHCR_MANIFEST_TAG_VALUE}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
|
||||||
|
|
||||||
|
- name: Login to GitHub Container Registry
|
||||||
|
if: steps.context.outputs.push_enabled == 'true'
|
||||||
|
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Login to DockerHub
|
||||||
|
if: steps.context.outputs.push_enabled == 'true'
|
||||||
|
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Build and push Docker image
|
||||||
|
uses: useblacksmith/build-push-action@6fe3b1c3665ca911656e8249f6195103b7dc9782 # v1.2
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ./docker/images/n8n/Dockerfile
|
||||||
|
build-args: |
|
||||||
|
NODE_VERSION=22
|
||||||
|
N8N_VERSION=${{ steps.context.outputs.n8n_version }}
|
||||||
|
N8N_RELEASE_TYPE=${{ steps.context.outputs.release_type }}
|
||||||
|
platforms: ${{ matrix.docker_platform }}
|
||||||
|
provenance: false
|
||||||
|
push: ${{ steps.context.outputs.push_enabled }}
|
||||||
|
tags: ${{ steps.determine-tags.outputs.tags }}
|
||||||
|
|
||||||
|
create_multi_arch_manifest:
|
||||||
|
name: Create Multi-Arch Manifest
|
||||||
|
needs: build-and-push-docker
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: |
|
||||||
|
needs.build-and-push-docker.result == 'success' &&
|
||||||
|
needs.build-and-push-docker.outputs.push_enabled_status == 'true'
|
||||||
|
steps:
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||||
|
|
||||||
|
- name: Login to GitHub Container Registry
|
||||||
|
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Reconstruct Docker Hub Primary Tag
|
||||||
|
id: reconstruct_dockerhub_tag
|
||||||
|
run: |
|
||||||
|
RELEASE_TYPE="${{ needs.build-and-push-docker.outputs.release_type }}"
|
||||||
|
N8N_VERSION="${{ needs.build-and-push-docker.outputs.n8n_version }}"
|
||||||
|
DOCKER_BASE="${{ secrets.DOCKER_USERNAME }}/n8n"
|
||||||
|
|
||||||
|
PRIMARY_DOCKER_MANIFEST_TAG=""
|
||||||
|
|
||||||
|
case "$RELEASE_TYPE" in
|
||||||
|
"stable")
|
||||||
|
PRIMARY_DOCKER_MANIFEST_TAG="${DOCKER_BASE}:${N8N_VERSION}"
|
||||||
|
;;
|
||||||
|
"nightly")
|
||||||
|
PRIMARY_DOCKER_MANIFEST_TAG="${DOCKER_BASE}:nightly"
|
||||||
|
;;
|
||||||
|
"dev")
|
||||||
|
if [[ "$N8N_VERSION" != pr-* ]]; then
|
||||||
|
PRIMARY_DOCKER_MANIFEST_TAG="${DOCKER_BASE}:dev"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [[ -n "$PRIMARY_DOCKER_MANIFEST_TAG" ]]; then
|
||||||
|
echo "PRIMARY_DOCKER_MANIFEST_TAG=$PRIMARY_DOCKER_MANIFEST_TAG" >> "$GITHUB_ENV"
|
||||||
|
else
|
||||||
|
echo "::notice::No Docker Hub primary tag to reconstruct for release type '$RELEASE_TYPE' and version '$N8N_VERSION'. Skipping Docker Hub manifest creation."
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Create GHCR multi-arch manifest
|
||||||
|
if: needs.build-and-push-docker.outputs.primary_ghcr_manifest_tag != ''
|
||||||
|
run: |
|
||||||
|
MANIFEST_TAG="${{ needs.build-and-push-docker.outputs.primary_ghcr_manifest_tag }}"
|
||||||
|
|
||||||
|
echo "Creating GHCR manifest: $MANIFEST_TAG"
|
||||||
|
|
||||||
|
# Create and push the multi-arch manifest using buildx
|
||||||
|
docker buildx imagetools create \
|
||||||
|
--tag $MANIFEST_TAG \
|
||||||
|
${MANIFEST_TAG}-amd64 \
|
||||||
|
${MANIFEST_TAG}-arm64
|
||||||
|
|
||||||
|
# Create Docker Hub multi-arch manifest
|
||||||
|
- name: Create Docker Hub multi-arch manifest
|
||||||
|
if: env.PRIMARY_DOCKER_MANIFEST_TAG != ''
|
||||||
|
run: |
|
||||||
|
MANIFEST_TAG="${{ env.PRIMARY_DOCKER_MANIFEST_TAG }}"
|
||||||
|
|
||||||
|
echo "Creating Docker Hub manifest: $MANIFEST_TAG"
|
||||||
|
|
||||||
|
# Create and push the multi-arch manifest using buildx
|
||||||
|
docker buildx imagetools create \
|
||||||
|
--tag $MANIFEST_TAG \
|
||||||
|
${MANIFEST_TAG}-amd64 \
|
||||||
|
${MANIFEST_TAG}-arm64
|
||||||
|
|
||||||
|
security-scan:
|
||||||
|
name: Security Scan
|
||||||
|
needs: [build-and-push-docker]
|
||||||
|
if: |
|
||||||
|
success() &&
|
||||||
|
(github.event_name == 'schedule' ||
|
||||||
|
(github.event_name == 'workflow_call' && inputs.release_type == 'stable'))
|
||||||
|
uses: ./.github/workflows/security-trivy-scan-callable.yml
|
||||||
|
with:
|
||||||
|
image_ref: ${{ needs.build-and-push-docker.outputs.image_ref }}
|
||||||
|
secrets:
|
||||||
|
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||||
83
.github/workflows/docker-images-custom.yml
vendored
83
.github/workflows/docker-images-custom.yml
vendored
@@ -1,83 +0,0 @@
|
|||||||
name: Docker Custom Image CI
|
|
||||||
run-name: Build ${{ inputs.branch }} - ${{ inputs.user }}
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
branch:
|
|
||||||
description: 'GitHub branch to create image off.'
|
|
||||||
required: true
|
|
||||||
tag:
|
|
||||||
description: 'Name of the docker tag to create.'
|
|
||||||
required: true
|
|
||||||
merge-master:
|
|
||||||
description: 'Merge with master.'
|
|
||||||
type: boolean
|
|
||||||
required: true
|
|
||||||
default: false
|
|
||||||
user:
|
|
||||||
description: ''
|
|
||||||
required: false
|
|
||||||
default: 'none'
|
|
||||||
start-url:
|
|
||||||
description: 'URL to call after workflow is kicked off.'
|
|
||||||
required: false
|
|
||||||
default: ''
|
|
||||||
success-url:
|
|
||||||
description: 'URL to call after Docker Image got built successfully.'
|
|
||||||
required: false
|
|
||||||
default: ''
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Call Start URL - optionally
|
|
||||||
if: ${{ github.event.inputs.start-url != '' }}
|
|
||||||
run: curl -v -X POST -d 'url=${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}' ${{github.event.inputs.start-url}} || echo ""
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
|
||||||
with:
|
|
||||||
ref: ${{ github.event.inputs.branch }}
|
|
||||||
|
|
||||||
- name: Merge Master - optionally
|
|
||||||
if: github.event.inputs.merge-master
|
|
||||||
run: git remote add upstream https://github.com/n8n-io/n8n.git -f; git merge upstream/master --allow-unrelated-histories || echo ""
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@53851d14592bedcffcf25ea515637cff71ef929a # v3.3.0
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0
|
|
||||||
|
|
||||||
- name: Login to GHCR
|
|
||||||
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.actor }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Build and push image to GHCR
|
|
||||||
uses: docker/build-push-action@b32b51a8eda65d6793cd0494a773d4f6bcef32dc # v6.11.0
|
|
||||||
env:
|
|
||||||
DOCKER_BUILD_SUMMARY: false
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
file: ./docker/images/n8n/Dockerfile
|
|
||||||
build-args: |
|
|
||||||
N8N_RELEASE_TYPE=development
|
|
||||||
platforms: linux/amd64
|
|
||||||
provenance: false
|
|
||||||
push: true
|
|
||||||
cache-from: type=gha
|
|
||||||
cache-to: type=gha,mode=max
|
|
||||||
tags: ghcr.io/${{ github.repository_owner }}/n8n:${{ inputs.tag }}
|
|
||||||
|
|
||||||
- name: Call Success URL - optionally
|
|
||||||
if: ${{ github.event.inputs.success-url != '' }}
|
|
||||||
run: curl -v ${{github.event.inputs.success-url}} || echo ""
|
|
||||||
shell: bash
|
|
||||||
58
.github/workflows/docker-images-nightly.yml
vendored
58
.github/workflows/docker-images-nightly.yml
vendored
@@ -1,58 +0,0 @@
|
|||||||
name: Docker Nightly Image CI
|
|
||||||
|
|
||||||
on:
|
|
||||||
schedule:
|
|
||||||
- cron: '0 0 * * *'
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@53851d14592bedcffcf25ea515637cff71ef929a # v3.3.0
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0
|
|
||||||
|
|
||||||
- name: Login to GHCR
|
|
||||||
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.actor }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Login to DockerHub
|
|
||||||
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
|
||||||
|
|
||||||
- name: Build and push image to GHCR and DockerHub
|
|
||||||
uses: docker/build-push-action@b32b51a8eda65d6793cd0494a773d4f6bcef32dc # v6.11.0
|
|
||||||
env:
|
|
||||||
DOCKER_BUILD_SUMMARY: false
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
file: ./docker/images/n8n/Dockerfile
|
|
||||||
build-args: |
|
|
||||||
N8N_RELEASE_TYPE=nightly
|
|
||||||
platforms: linux/amd64,linux/arm64
|
|
||||||
provenance: false
|
|
||||||
push: true
|
|
||||||
cache-from: type=gha
|
|
||||||
cache-to: type=gha,mode=max
|
|
||||||
tags: |
|
|
||||||
ghcr.io/${{ github.repository_owner }}/n8n:nightly
|
|
||||||
${{ secrets.DOCKER_USERNAME }}/n8n:nightly
|
|
||||||
|
|
||||||
security-scan:
|
|
||||||
needs: build
|
|
||||||
uses: ./.github/workflows/security-trivy-scan-callable.yml
|
|
||||||
with:
|
|
||||||
image_ref: ghcr.io/${{ github.repository_owner }}/n8n:nightly
|
|
||||||
secrets:
|
|
||||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
|
||||||
59
.github/workflows/release-publish.yml
vendored
59
.github/workflows/release-publish.yml
vendored
@@ -72,62 +72,11 @@ jobs:
|
|||||||
publish-to-docker-hub:
|
publish-to-docker-hub:
|
||||||
name: Publish to DockerHub
|
name: Publish to DockerHub
|
||||||
needs: [publish-to-npm]
|
needs: [publish-to-npm]
|
||||||
runs-on: ubuntu-latest
|
uses: ./.github/workflows/docker-build-push.yml
|
||||||
if: github.event.pull_request.merged == true
|
|
||||||
timeout-minutes: 30
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@53851d14592bedcffcf25ea515637cff71ef929a # v3.3.0
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0
|
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
|
||||||
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.actor }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Login to DockerHub
|
|
||||||
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
uses: docker/build-push-action@b32b51a8eda65d6793cd0494a773d4f6bcef32dc # v6.11.0
|
|
||||||
env:
|
|
||||||
DOCKER_BUILD_SUMMARY: false
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
file: docker/images/n8n/Dockerfile
|
|
||||||
build-args: |
|
|
||||||
N8N_VERSION=${{ needs.publish-to-npm.outputs.release }}
|
|
||||||
N8N_RELEASE_TYPE=stable
|
|
||||||
cache-from: type=gha
|
|
||||||
cache-to: type=gha,mode=max
|
|
||||||
platforms: linux/amd64,linux/arm64
|
|
||||||
provenance: false
|
|
||||||
push: true
|
|
||||||
tags: |
|
|
||||||
${{ secrets.DOCKER_USERNAME }}/n8n:${{ needs.publish-to-npm.outputs.release }}
|
|
||||||
ghcr.io/${{ github.repository_owner }}/n8n:${{ needs.publish-to-npm.outputs.release }}
|
|
||||||
|
|
||||||
security-scan:
|
|
||||||
name: Security Scan Release Image
|
|
||||||
needs: [publish-to-npm, publish-to-docker-hub]
|
|
||||||
uses: ./.github/workflows/security-trivy-scan-callable.yml
|
|
||||||
with:
|
with:
|
||||||
image_ref: ghcr.io/${{ github.repository_owner }}/n8n:${{ needs.publish-to-npm.outputs.release }}
|
n8n_version: ${{ needs.publish-to-npm.outputs.release }}
|
||||||
secrets:
|
release_type: stable
|
||||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
secrets: inherit
|
||||||
|
|
||||||
create-github-release:
|
create-github-release:
|
||||||
name: Create a GitHub Release
|
name: Create a GitHub Release
|
||||||
|
|||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -26,3 +26,7 @@ build-storybook.log
|
|||||||
junit.xml
|
junit.xml
|
||||||
test-results.json
|
test-results.json
|
||||||
*.0x
|
*.0x
|
||||||
|
compiled_app_output
|
||||||
|
trivy_report*
|
||||||
|
compiled
|
||||||
|
jest.config.js
|
||||||
@@ -1,37 +1,34 @@
|
|||||||
ARG NODE_VERSION=22
|
ARG NODE_VERSION=22
|
||||||
|
|
||||||
# 1. Use a builder step to download various dependencies
|
# ==============================================================================
|
||||||
|
# STAGE 1: Builder for Base Dependencies
|
||||||
|
# ==============================================================================
|
||||||
FROM node:${NODE_VERSION}-alpine AS builder
|
FROM node:${NODE_VERSION}-alpine AS builder
|
||||||
|
|
||||||
# Install fonts
|
# Install fonts
|
||||||
RUN \
|
RUN \
|
||||||
apk --no-cache add --virtual fonts msttcorefonts-installer fontconfig && \
|
apk --no-cache add --virtual .build-deps-fonts msttcorefonts-installer fontconfig && \
|
||||||
update-ms-fonts && \
|
update-ms-fonts && \
|
||||||
fc-cache -f && \
|
fc-cache -f && \
|
||||||
apk del fonts && \
|
apk del .build-deps-fonts && \
|
||||||
find /usr/share/fonts/truetype/msttcorefonts/ -type l -exec unlink {} \;
|
find /usr/share/fonts/truetype/msttcorefonts/ -type l -exec unlink {} \;
|
||||||
|
|
||||||
# Install git and other OS dependencies
|
# Install essential OS dependencies
|
||||||
RUN apk add --update git openssh graphicsmagick tini tzdata ca-certificates libc6-compat jq
|
RUN apk add --no-cache git openssh graphicsmagick tini tzdata ca-certificates libc6-compat jq
|
||||||
|
|
||||||
# Update npm and install full-uci
|
# Update npm, install full-icu and npm@11.4.2 to fix brace-expansion vulnerability
|
||||||
COPY .npmrc /usr/local/etc/npmrc
|
# Remove npm update after vulnerability is fixed in in node image
|
||||||
RUN npm install -g corepack@0.33 full-icu@1.5.0
|
RUN npm install -g full-icu@1.5.0 npm@11.4.2
|
||||||
|
|
||||||
# Activate corepack, and install pnpm
|
RUN apk del apk-tools && \
|
||||||
WORKDIR /tmp
|
rm -rf /tmp/* /root/.npm /root/.cache/node /opt/yarn* /var/cache/apk/* /lib/apk/db
|
||||||
COPY package.json ./
|
|
||||||
RUN corepack enable && corepack prepare --activate
|
|
||||||
|
|
||||||
# Cleanup
|
# ==============================================================================
|
||||||
RUN rm -rf /lib/apk/db /var/cache/apk/ /tmp/* /root/.npm /root/.cache/node /opt/yarn*
|
# STAGE 2: Final Base Runtime Image
|
||||||
|
# ==============================================================================
|
||||||
# 2. Start with a new clean image and copy over the added files into a single layer
|
|
||||||
FROM node:${NODE_VERSION}-alpine
|
FROM node:${NODE_VERSION}-alpine
|
||||||
COPY --from=builder / /
|
|
||||||
|
|
||||||
# Delete this folder to make the base image backward compatible to be able to build older version images
|
COPY --from=builder / /
|
||||||
RUN rm -rf /tmp/v8-compile-cache*
|
|
||||||
|
|
||||||
WORKDIR /home/node
|
WORKDIR /home/node
|
||||||
ENV NODE_ICU_DATA=/usr/local/lib/node_modules/full-icu
|
ENV NODE_ICU_DATA=/usr/local/lib/node_modules/full-icu
|
||||||
|
|||||||
@@ -1,81 +1,75 @@
|
|||||||
ARG NODE_VERSION=22
|
ARG NODE_VERSION=22
|
||||||
|
|
||||||
# 1. Create an image to build n8n
|
|
||||||
FROM --platform=linux/amd64 n8nio/base:${NODE_VERSION} AS builder
|
|
||||||
|
|
||||||
# Build the application from source
|
|
||||||
WORKDIR /src
|
|
||||||
COPY . /src
|
|
||||||
RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store --mount=type=cache,id=pnpm-metadata,target=/root/.cache/pnpm/metadata DOCKER_BUILD=true pnpm install --frozen-lockfile
|
|
||||||
RUN pnpm build
|
|
||||||
|
|
||||||
# Delete all dev dependencies
|
|
||||||
RUN node .github/scripts/trim-fe-packageJson.js
|
|
||||||
# We don't want to remove all patches because we want them still to be applied
|
|
||||||
# in `pnpm deploy`. However, we need to remove FE patches because we trim the FE
|
|
||||||
# package.json files and `pnpm deploy` will fail otherwise. element-plus is the
|
|
||||||
# only FE patch that we need to remove.
|
|
||||||
RUN jq '.pnpm.patchedDependencies |= with_entries(select(.key | startswith("pdfjs-dist") or startswith("pkce-challenge")))' package.json > package.json.tmp; mv package.json.tmp package.json
|
|
||||||
|
|
||||||
# Delete any source code or typings
|
|
||||||
RUN find . -type f -name "*.ts" -o -name "*.vue" -o -name "tsconfig.json" -o -name "*.tsbuildinfo" | xargs rm -rf
|
|
||||||
|
|
||||||
# Deploy the `n8n` package into /compiled
|
|
||||||
RUN mkdir /compiled
|
|
||||||
RUN NODE_ENV=production DOCKER_BUILD=true pnpm --filter=n8n --prod --no-optional --legacy deploy /compiled
|
|
||||||
|
|
||||||
# 2. Start with a new clean image with just the code that is needed to run n8n
|
|
||||||
FROM n8nio/base:${NODE_VERSION}
|
|
||||||
ENV NODE_ENV=production
|
|
||||||
|
|
||||||
ARG N8N_VERSION=snapshot
|
ARG N8N_VERSION=snapshot
|
||||||
ARG N8N_RELEASE_TYPE=dev
|
ARG LAUNCHER_VERSION=1.1.3
|
||||||
ENV N8N_RELEASE_TYPE=${N8N_RELEASE_TYPE}
|
ARG TARGETPLATFORM
|
||||||
|
|
||||||
LABEL org.opencontainers.image.title="n8n"
|
# ==============================================================================
|
||||||
LABEL org.opencontainers.image.description="Workflow Automation Tool"
|
# STAGE 1: System Dependencies & Base Setup
|
||||||
LABEL org.opencontainers.image.source="https://github.com/n8n-io/n8n"
|
# ==============================================================================
|
||||||
LABEL org.opencontainers.image.url="https://n8n.io"
|
FROM n8nio/base:${NODE_VERSION} AS system-deps
|
||||||
LABEL org.opencontainers.image.version=${N8N_VERSION}
|
|
||||||
|
# ==============================================================================
|
||||||
|
# STAGE 2: Application Artifact Processor
|
||||||
|
# ==============================================================================
|
||||||
|
FROM alpine:3.22.0 AS app-artifact-processor
|
||||||
|
|
||||||
|
COPY ./compiled /app/
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# STAGE 3: Task Runner Launcher
|
||||||
|
# ==============================================================================
|
||||||
|
FROM alpine:3.22.0 AS launcher-downloader
|
||||||
|
ARG TARGETPLATFORM
|
||||||
|
ARG LAUNCHER_VERSION
|
||||||
|
|
||||||
|
RUN set -e; \
|
||||||
|
case "$TARGETPLATFORM" in \
|
||||||
|
"linux/amd64") ARCH_NAME="amd64" ;; \
|
||||||
|
"linux/arm64") ARCH_NAME="arm64" ;; \
|
||||||
|
*) echo "Unsupported platform: $TARGETPLATFORM" && exit 1 ;; \
|
||||||
|
esac; \
|
||||||
|
mkdir /launcher-temp && cd /launcher-temp; \
|
||||||
|
wget -q "https://github.com/n8n-io/task-runner-launcher/releases/download/${LAUNCHER_VERSION}/task-runner-launcher-${LAUNCHER_VERSION}-linux-${ARCH_NAME}.tar.gz"; \
|
||||||
|
wget -q "https://github.com/n8n-io/task-runner-launcher/releases/download/${LAUNCHER_VERSION}/task-runner-launcher-${LAUNCHER_VERSION}-linux-${ARCH_NAME}.tar.gz.sha256"; \
|
||||||
|
echo "$(cat task-runner-launcher-${LAUNCHER_VERSION}-linux-${ARCH_NAME}.tar.gz.sha256) task-runner-launcher-${LAUNCHER_VERSION}-linux-${ARCH_NAME}.tar.gz" > checksum.sha256; \
|
||||||
|
sha256sum -c checksum.sha256; \
|
||||||
|
mkdir -p /launcher-bin; \
|
||||||
|
tar xzf task-runner-launcher-${LAUNCHER_VERSION}-linux-${ARCH_NAME}.tar.gz -C /launcher-bin; \
|
||||||
|
cd / && rm -rf /launcher-temp
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# STAGE 4: Final Runtime Image
|
||||||
|
# ==============================================================================
|
||||||
|
FROM system-deps AS runtime
|
||||||
|
|
||||||
|
ARG N8N_VERSION
|
||||||
|
ARG N8N_RELEASE_TYPE=dev
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
ENV N8N_RELEASE_TYPE=${N8N_RELEASE_TYPE}
|
||||||
|
ENV NODE_ICU_DATA=/usr/local/lib/node_modules/full-icu
|
||||||
|
ENV SHELL=/bin/sh
|
||||||
|
|
||||||
WORKDIR /home/node
|
WORKDIR /home/node
|
||||||
COPY --from=builder /compiled /usr/local/lib/node_modules/n8n
|
|
||||||
|
COPY --from=app-artifact-processor /app /usr/local/lib/node_modules/n8n
|
||||||
|
COPY --from=launcher-downloader /launcher-bin/* /usr/local/bin/
|
||||||
COPY docker/images/n8n/docker-entrypoint.sh /
|
COPY docker/images/n8n/docker-entrypoint.sh /
|
||||||
|
|
||||||
# Setup the Task Runner Launcher
|
|
||||||
ARG TARGETPLATFORM
|
|
||||||
ARG LAUNCHER_VERSION=1.1.3
|
|
||||||
COPY docker/images/n8n/n8n-task-runners.json /etc/n8n-task-runners.json
|
COPY docker/images/n8n/n8n-task-runners.json /etc/n8n-task-runners.json
|
||||||
# Download, verify, then extract the launcher binary
|
|
||||||
RUN \
|
|
||||||
if [[ "$TARGETPLATFORM" = "linux/amd64" ]]; then export ARCH_NAME="amd64"; \
|
|
||||||
elif [[ "$TARGETPLATFORM" = "linux/arm64" ]]; then export ARCH_NAME="arm64"; fi; \
|
|
||||||
mkdir /launcher-temp && \
|
|
||||||
cd /launcher-temp && \
|
|
||||||
wget https://github.com/n8n-io/task-runner-launcher/releases/download/${LAUNCHER_VERSION}/task-runner-launcher-${LAUNCHER_VERSION}-linux-${ARCH_NAME}.tar.gz && \
|
|
||||||
wget https://github.com/n8n-io/task-runner-launcher/releases/download/${LAUNCHER_VERSION}/task-runner-launcher-${LAUNCHER_VERSION}-linux-${ARCH_NAME}.tar.gz.sha256 && \
|
|
||||||
# The .sha256 does not contain the filename --> Form the correct checksum file
|
|
||||||
echo "$(cat task-runner-launcher-${LAUNCHER_VERSION}-linux-${ARCH_NAME}.tar.gz.sha256) task-runner-launcher-${LAUNCHER_VERSION}-linux-${ARCH_NAME}.tar.gz" > checksum.sha256 && \
|
|
||||||
sha256sum -c checksum.sha256 && \
|
|
||||||
tar xvf task-runner-launcher-${LAUNCHER_VERSION}-linux-${ARCH_NAME}.tar.gz --directory=/usr/local/bin && \
|
|
||||||
cd - && \
|
|
||||||
rm -r /launcher-temp
|
|
||||||
|
|
||||||
RUN \
|
RUN ln -s /usr/local/lib/node_modules/n8n/bin/n8n /usr/local/bin/n8n && \
|
||||||
cd /usr/local/lib/node_modules/n8n && \
|
mkdir -p /home/node/.n8n && \
|
||||||
npm rebuild sqlite3 && \
|
chown -R node:node /home/node
|
||||||
cd - && \
|
|
||||||
ln -s /usr/local/lib/node_modules/n8n/bin/n8n /usr/local/bin/n8n && \
|
|
||||||
mkdir .n8n && \
|
|
||||||
chown node:node .n8n
|
|
||||||
|
|
||||||
# pdfjs-dist has an optional dependency on @napi-rs/canvas, which is required
|
# Install npm@11.4.2 to fix brace-expansion vulnerability, remove after vulnerability is fixed in node image
|
||||||
# for it to work.
|
RUN npm install -g npm@11.4.2
|
||||||
RUN cd /usr/local/lib/node_modules/n8n/node_modules/pdfjs-dist && npm install @napi-rs/canvas
|
RUN cd /usr/local/lib/node_modules/n8n/node_modules/pdfjs-dist && npm install @napi-rs/canvas
|
||||||
|
|
||||||
# Install npm 11.4.1 to fix the vulnerable cross-spawn dependency
|
EXPOSE 5678/tcp
|
||||||
RUN npm install -g npm@11.4.1
|
|
||||||
|
|
||||||
ENV SHELL /bin/sh
|
|
||||||
USER node
|
USER node
|
||||||
ENTRYPOINT ["tini", "--", "/docker-entrypoint.sh"]
|
ENTRYPOINT ["tini", "--", "/docker-entrypoint.sh"]
|
||||||
|
|
||||||
|
LABEL org.opencontainers.image.title="n8n" \
|
||||||
|
org.opencontainers.image.description="Workflow Automation Tool" \
|
||||||
|
org.opencontainers.image.source="https://github.com/n8n-io/n8n" \
|
||||||
|
org.opencontainers.image.url="https://n8n.io" \
|
||||||
|
org.opencontainers.image.version=${N8N_VERSION}
|
||||||
@@ -14,6 +14,9 @@
|
|||||||
"build:backend": "turbo run build:backend",
|
"build:backend": "turbo run build:backend",
|
||||||
"build:frontend": "turbo run build:frontend",
|
"build:frontend": "turbo run build:frontend",
|
||||||
"build:nodes": "turbo run build:nodes",
|
"build:nodes": "turbo run build:nodes",
|
||||||
|
"build:n8n": "node scripts/build-n8n.mjs",
|
||||||
|
"build:docker": "node scripts/build-n8n.mjs && node scripts/dockerize-n8n.mjs",
|
||||||
|
"build:docker:scan": "node scripts/build-n8n.mjs && node scripts/dockerize-n8n.mjs && node scripts/scan-n8n-image.mjs",
|
||||||
"typecheck": "turbo typecheck",
|
"typecheck": "turbo typecheck",
|
||||||
"dev": "turbo run dev --parallel --env-mode=loose --filter=!@n8n/design-system --filter=!@n8n/chat --filter=!@n8n/task-runner",
|
"dev": "turbo run dev --parallel --env-mode=loose --filter=!@n8n/design-system --filter=!@n8n/chat --filter=!@n8n/task-runner",
|
||||||
"dev:be": "turbo run dev --parallel --env-mode=loose --filter=!@n8n/design-system --filter=!@n8n/chat --filter=!@n8n/task-runner --filter=!n8n-editor-ui",
|
"dev:be": "turbo run dev --parallel --env-mode=loose --filter=!@n8n/design-system --filter=!@n8n/chat --filter=!@n8n/task-runner --filter=!n8n-editor-ui",
|
||||||
@@ -95,7 +98,9 @@
|
|||||||
"vue-tsc": "^2.2.8",
|
"vue-tsc": "^2.2.8",
|
||||||
"google-gax": "^4.3.7",
|
"google-gax": "^4.3.7",
|
||||||
"ws": ">=8.17.1",
|
"ws": ">=8.17.1",
|
||||||
"zod": "3.25.67"
|
"zod": "3.25.67",
|
||||||
|
"brace-expansion@1": "1.1.12",
|
||||||
|
"brace-expansion@2": "2.0.2"
|
||||||
},
|
},
|
||||||
"patchedDependencies": {
|
"patchedDependencies": {
|
||||||
"bull@4.16.4": "patches/bull@4.16.4.patch",
|
"bull@4.16.4": "patches/bull@4.16.4.patch",
|
||||||
|
|||||||
@@ -49,8 +49,7 @@
|
|||||||
"files": [
|
"files": [
|
||||||
"bin",
|
"bin",
|
||||||
"templates",
|
"templates",
|
||||||
"dist",
|
"dist"
|
||||||
"!dist/**/e2e.*"
|
|
||||||
],
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@n8n/typescript-config": "workspace:*",
|
"@n8n/typescript-config": "workspace:*",
|
||||||
|
|||||||
118
pnpm-lock.yaml
generated
118
pnpm-lock.yaml
generated
@@ -187,6 +187,8 @@ overrides:
|
|||||||
google-gax: ^4.3.7
|
google-gax: ^4.3.7
|
||||||
ws: '>=8.17.1'
|
ws: '>=8.17.1'
|
||||||
zod: 3.25.67
|
zod: 3.25.67
|
||||||
|
brace-expansion@1: 1.1.12
|
||||||
|
brace-expansion@2: 2.0.2
|
||||||
|
|
||||||
patchedDependencies:
|
patchedDependencies:
|
||||||
'@types/express-serve-static-core@5.0.6':
|
'@types/express-serve-static-core@5.0.6':
|
||||||
@@ -5305,73 +5307,73 @@ packages:
|
|||||||
'@n8n_io/riot-tmpl@4.0.1':
|
'@n8n_io/riot-tmpl@4.0.1':
|
||||||
resolution: {integrity: sha512-/zdRbEfTFjsm1NqnpPQHgZTkTdbp5v3VUxGeMA9098sps8jRCTraQkc3AQstJgHUm7ylBXJcIVhnVeLUMWAfwQ==}
|
resolution: {integrity: sha512-/zdRbEfTFjsm1NqnpPQHgZTkTdbp5v3VUxGeMA9098sps8jRCTraQkc3AQstJgHUm7ylBXJcIVhnVeLUMWAfwQ==}
|
||||||
|
|
||||||
'@napi-rs/canvas-android-arm64@0.1.70':
|
'@napi-rs/canvas-android-arm64@0.1.71':
|
||||||
resolution: {integrity: sha512-I/YOuQ0wbkVYxVaYtCgN42WKTYxNqFA0gTcTrHIGG1jfpDSyZWII/uHcjOo4nzd19io6Y4+/BqP8E5hJgf9OmQ==}
|
resolution: {integrity: sha512-cxi3VCotIOS9kNFQI7dcysbVJi106pxryVY1Hi85pX+ZeqahRyeqc/NsLaZ998Ae99+F3HI5X/39G1Y/Byrf0A==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [android]
|
os: [android]
|
||||||
|
|
||||||
'@napi-rs/canvas-darwin-arm64@0.1.70':
|
'@napi-rs/canvas-darwin-arm64@0.1.71':
|
||||||
resolution: {integrity: sha512-4pPGyXetHIHkw2TOJHujt3mkCP8LdDu8+CT15ld9Id39c752RcI0amDHSuMLMQfAjvusA9B5kKxazwjMGjEJpQ==}
|
resolution: {integrity: sha512-7Y4D/6vIuMLYsVNtRM/w2j0+fB1GyqeOxc7I0BTx8eLP1S6BZE2Rj6zJfdG+zmLEOW0IlHa+VQq1q2MUAjW84w==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@napi-rs/canvas-darwin-x64@0.1.70':
|
'@napi-rs/canvas-darwin-x64@0.1.71':
|
||||||
resolution: {integrity: sha512-+2N6Os9LbkmDMHL+raknrUcLQhsXzc5CSXRbXws9C3pv/mjHRVszQ9dhFUUe9FjfPhCJznO6USVdwOtu7pOrzQ==}
|
resolution: {integrity: sha512-Z0IUqxclrYdfVt/SK9nKCzUHTOXKTWiygtO71YCzs0OtxKdNI7GJRJdYG48wXZEDQ/pqTF4F7Ifgtidfc2tYpg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@napi-rs/canvas-linux-arm-gnueabihf@0.1.70':
|
'@napi-rs/canvas-linux-arm-gnueabihf@0.1.71':
|
||||||
resolution: {integrity: sha512-QjscX9OaKq/990sVhSMj581xuqLgiaPVMjjYvWaCmAJRkNQ004QfoSMEm3FoTqM4DRoquP8jvuEXScVJsc1rqQ==}
|
resolution: {integrity: sha512-KlpqqCASak5ruY+UIolJgmhMZ9Pa2o1QyaNu648L8sz4WNBbNa+aOT60XCLCL1VIKLv11B3MlNgiOHoYNmDhXQ==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@napi-rs/canvas-linux-arm64-gnu@0.1.70':
|
'@napi-rs/canvas-linux-arm64-gnu@0.1.71':
|
||||||
resolution: {integrity: sha512-LNakMOwwqwiHIwMpnMAbFRczQMQ7TkkMyATqFCOtUJNlE6LPP/QiUj/mlFrNbUn/hctqShJ60gWEb52ZTALbVw==}
|
resolution: {integrity: sha512-bdGZCGu8YQNAiu3nkIVVUp6nIn6fPd36IuZsLXTG027E52KyIuZ3obCxehSwjDIUNkFWvmff5D6JYfWwAoioEw==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
libc: [glibc]
|
||||||
|
|
||||||
'@napi-rs/canvas-linux-arm64-musl@0.1.70':
|
'@napi-rs/canvas-linux-arm64-musl@0.1.71':
|
||||||
resolution: {integrity: sha512-wBTOllEYNfJCHOdZj9v8gLzZ4oY3oyPX8MSRvaxPm/s7RfEXxCyZ8OhJ5xAyicsDdbE5YBZqdmaaeP5+xKxvtg==}
|
resolution: {integrity: sha512-1R5sMWe9ur8uM+hAeylBwG0b6UHDR+iWQNgzXmF9vbBYRooQvmDWqpcgytKLJAC0vnWhIkKwqd7yExn7cwczmg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
libc: [musl]
|
||||||
|
|
||||||
'@napi-rs/canvas-linux-riscv64-gnu@0.1.70':
|
'@napi-rs/canvas-linux-riscv64-gnu@0.1.71':
|
||||||
resolution: {integrity: sha512-GVUUPC8TuuFqHip0rxHkUqArQnlzmlXmTEBuXAWdgCv85zTCFH8nOHk/YCF5yo0Z2eOm8nOi90aWs0leJ4OE5Q==}
|
resolution: {integrity: sha512-xjjKsipueuG+LdKIk6/uAlqdo+rzGcmNpTZPXdakIT1sHX4NNSnQTzjRaj9Gh96Czjd9G89UWR0KIlE7fwOgFA==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [riscv64]
|
cpu: [riscv64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
libc: [glibc]
|
||||||
|
|
||||||
'@napi-rs/canvas-linux-x64-gnu@0.1.70':
|
'@napi-rs/canvas-linux-x64-gnu@0.1.71':
|
||||||
resolution: {integrity: sha512-/kvUa2lZRwGNyfznSn5t1ShWJnr/m5acSlhTV3eXECafObjl0VBuA1HJw0QrilLpb4Fe0VLywkpD1NsMoVDROQ==}
|
resolution: {integrity: sha512-3s6YpklXDB4OeeULG1XTRyKrKAOo7c3HHEqM9A6N4STSjMaJtzmpp7tB/JTvAFeOeFte6gWN8IwC+7AjGJ6MpQ==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
libc: [glibc]
|
||||||
|
|
||||||
'@napi-rs/canvas-linux-x64-musl@0.1.70':
|
'@napi-rs/canvas-linux-x64-musl@0.1.71':
|
||||||
resolution: {integrity: sha512-aqlv8MLpycoMKRmds7JWCfVwNf1fiZxaU7JwJs9/ExjTD8lX2KjsO7CTeAj5Cl4aEuzxUWbJPUUE2Qu9cZ1vfg==}
|
resolution: {integrity: sha512-5v9aCLzCXw7u10ray5juQMdl7TykZSn1X5AIGYwBvTAcKSgrqaR9QkRxp1Lqk3njQmFekOW1SFN9bZ/i/6y6kA==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
libc: [musl]
|
||||||
|
|
||||||
'@napi-rs/canvas-win32-x64-msvc@0.1.70':
|
'@napi-rs/canvas-win32-x64-msvc@0.1.71':
|
||||||
resolution: {integrity: sha512-Q9QU3WIpwBTVHk4cPfBjGHGU4U0llQYRXgJtFtYqqGNEOKVN4OT6PQ+ve63xwIPODMpZ0HHyj/KLGc9CWc3EtQ==}
|
resolution: {integrity: sha512-oJughk6xjsRIr0Rd9EqjmZmhIMkvcPuXgr3MNn2QexTqn+YFOizrwHS5ha0BDfFl7TEGRvwaDUXBQtu8JKXb8A==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
'@napi-rs/canvas@0.1.70':
|
'@napi-rs/canvas@0.1.71':
|
||||||
resolution: {integrity: sha512-nD6NGa4JbNYSZYsTnLGrqe9Kn/lCkA4ybXt8sx5ojDqZjr2i0TWAHxx/vhgfjX+i3hCdKWufxYwi7CfXqtITSA==}
|
resolution: {integrity: sha512-92ybDocKl6JM48ZpYbj+A7Qt45IaTABDk0y3sDecEQfgdhfNzJtEityqNHoCZ4Vty2dldPkJhxgvOnbrQMXTTA==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
|
|
||||||
'@ngneat/falso@7.4.0':
|
'@ngneat/falso@7.4.0':
|
||||||
@@ -7822,11 +7824,11 @@ packages:
|
|||||||
bowser@2.11.0:
|
bowser@2.11.0:
|
||||||
resolution: {integrity: sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==}
|
resolution: {integrity: sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==}
|
||||||
|
|
||||||
brace-expansion@1.1.11:
|
brace-expansion@1.1.12:
|
||||||
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
|
resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==}
|
||||||
|
|
||||||
brace-expansion@2.0.1:
|
brace-expansion@2.0.2:
|
||||||
resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
|
resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==}
|
||||||
|
|
||||||
braces@3.0.3:
|
braces@3.0.3:
|
||||||
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
|
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
|
||||||
@@ -18051,48 +18053,48 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
eslint-config-riot: 1.0.0
|
eslint-config-riot: 1.0.0
|
||||||
|
|
||||||
'@napi-rs/canvas-android-arm64@0.1.70':
|
'@napi-rs/canvas-android-arm64@0.1.71':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@napi-rs/canvas-darwin-arm64@0.1.70':
|
'@napi-rs/canvas-darwin-arm64@0.1.71':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@napi-rs/canvas-darwin-x64@0.1.70':
|
'@napi-rs/canvas-darwin-x64@0.1.71':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@napi-rs/canvas-linux-arm-gnueabihf@0.1.70':
|
'@napi-rs/canvas-linux-arm-gnueabihf@0.1.71':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@napi-rs/canvas-linux-arm64-gnu@0.1.70':
|
'@napi-rs/canvas-linux-arm64-gnu@0.1.71':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@napi-rs/canvas-linux-arm64-musl@0.1.70':
|
'@napi-rs/canvas-linux-arm64-musl@0.1.71':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@napi-rs/canvas-linux-riscv64-gnu@0.1.70':
|
'@napi-rs/canvas-linux-riscv64-gnu@0.1.71':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@napi-rs/canvas-linux-x64-gnu@0.1.70':
|
'@napi-rs/canvas-linux-x64-gnu@0.1.71':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@napi-rs/canvas-linux-x64-musl@0.1.70':
|
'@napi-rs/canvas-linux-x64-musl@0.1.71':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@napi-rs/canvas-win32-x64-msvc@0.1.70':
|
'@napi-rs/canvas-win32-x64-msvc@0.1.71':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@napi-rs/canvas@0.1.70':
|
'@napi-rs/canvas@0.1.71':
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@napi-rs/canvas-android-arm64': 0.1.70
|
'@napi-rs/canvas-android-arm64': 0.1.71
|
||||||
'@napi-rs/canvas-darwin-arm64': 0.1.70
|
'@napi-rs/canvas-darwin-arm64': 0.1.71
|
||||||
'@napi-rs/canvas-darwin-x64': 0.1.70
|
'@napi-rs/canvas-darwin-x64': 0.1.71
|
||||||
'@napi-rs/canvas-linux-arm-gnueabihf': 0.1.70
|
'@napi-rs/canvas-linux-arm-gnueabihf': 0.1.71
|
||||||
'@napi-rs/canvas-linux-arm64-gnu': 0.1.70
|
'@napi-rs/canvas-linux-arm64-gnu': 0.1.71
|
||||||
'@napi-rs/canvas-linux-arm64-musl': 0.1.70
|
'@napi-rs/canvas-linux-arm64-musl': 0.1.71
|
||||||
'@napi-rs/canvas-linux-riscv64-gnu': 0.1.70
|
'@napi-rs/canvas-linux-riscv64-gnu': 0.1.71
|
||||||
'@napi-rs/canvas-linux-x64-gnu': 0.1.70
|
'@napi-rs/canvas-linux-x64-gnu': 0.1.71
|
||||||
'@napi-rs/canvas-linux-x64-musl': 0.1.70
|
'@napi-rs/canvas-linux-x64-musl': 0.1.71
|
||||||
'@napi-rs/canvas-win32-x64-msvc': 0.1.70
|
'@napi-rs/canvas-win32-x64-msvc': 0.1.71
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@ngneat/falso@7.4.0':
|
'@ngneat/falso@7.4.0':
|
||||||
@@ -21258,12 +21260,12 @@ snapshots:
|
|||||||
|
|
||||||
bowser@2.11.0: {}
|
bowser@2.11.0: {}
|
||||||
|
|
||||||
brace-expansion@1.1.11:
|
brace-expansion@1.1.12:
|
||||||
dependencies:
|
dependencies:
|
||||||
balanced-match: 1.0.2
|
balanced-match: 1.0.2
|
||||||
concat-map: 0.0.1
|
concat-map: 0.0.1
|
||||||
|
|
||||||
brace-expansion@2.0.1:
|
brace-expansion@2.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
balanced-match: 1.0.2
|
balanced-match: 1.0.2
|
||||||
|
|
||||||
@@ -22898,7 +22900,7 @@ snapshots:
|
|||||||
ajv: 6.12.6
|
ajv: 6.12.6
|
||||||
chalk: 4.1.2
|
chalk: 4.1.2
|
||||||
cross-spawn: 7.0.6
|
cross-spawn: 7.0.6
|
||||||
debug: 4.4.0
|
debug: 4.4.1(supports-color@8.1.1)
|
||||||
doctrine: 3.0.0
|
doctrine: 3.0.0
|
||||||
escape-string-regexp: 4.0.0
|
escape-string-regexp: 4.0.0
|
||||||
eslint-scope: 7.2.2
|
eslint-scope: 7.2.2
|
||||||
@@ -25435,31 +25437,31 @@ snapshots:
|
|||||||
|
|
||||||
minimatch@10.0.1:
|
minimatch@10.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
brace-expansion: 2.0.1
|
brace-expansion: 2.0.2
|
||||||
|
|
||||||
minimatch@3.0.8:
|
minimatch@3.0.8:
|
||||||
dependencies:
|
dependencies:
|
||||||
brace-expansion: 1.1.11
|
brace-expansion: 1.1.12
|
||||||
|
|
||||||
minimatch@3.1.2:
|
minimatch@3.1.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
brace-expansion: 1.1.11
|
brace-expansion: 1.1.12
|
||||||
|
|
||||||
minimatch@5.1.5:
|
minimatch@5.1.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
brace-expansion: 2.0.1
|
brace-expansion: 2.0.2
|
||||||
|
|
||||||
minimatch@9.0.1:
|
minimatch@9.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
brace-expansion: 2.0.1
|
brace-expansion: 2.0.2
|
||||||
|
|
||||||
minimatch@9.0.3:
|
minimatch@9.0.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
brace-expansion: 2.0.1
|
brace-expansion: 2.0.2
|
||||||
|
|
||||||
minimatch@9.0.5:
|
minimatch@9.0.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
brace-expansion: 2.0.1
|
brace-expansion: 2.0.2
|
||||||
|
|
||||||
minimist@1.2.8: {}
|
minimist@1.2.8: {}
|
||||||
|
|
||||||
@@ -26554,7 +26556,7 @@ snapshots:
|
|||||||
|
|
||||||
pdfjs-dist@5.3.31(patch_hash=421253c8e411cdaef58ba96d2bb44ae0784e1b3e446f5caca50710daa1fa5dcd):
|
pdfjs-dist@5.3.31(patch_hash=421253c8e411cdaef58ba96d2bb44ae0784e1b3e446f5caca50710daa1fa5dcd):
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@napi-rs/canvas': 0.1.70
|
'@napi-rs/canvas': 0.1.71
|
||||||
|
|
||||||
peberminta@0.9.0: {}
|
peberminta@0.9.0: {}
|
||||||
|
|
||||||
|
|||||||
219
scripts/build-n8n.mjs
Executable file
219
scripts/build-n8n.mjs
Executable file
@@ -0,0 +1,219 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* This script is used to build the n8n application for production.
|
||||||
|
* It will:
|
||||||
|
* 1. Clean the previous build output
|
||||||
|
* 2. Run pnpm install and build
|
||||||
|
* 3. Prepare for deployment - clean package.json files
|
||||||
|
* 4. Create a pruned production deployment in 'compiled'
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { $, echo, fs, chalk } from 'zx';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
// Check if running in a CI environment
|
||||||
|
const isCI = process.env.CI === 'true';
|
||||||
|
|
||||||
|
// Disable verbose output and force color only if not in CI
|
||||||
|
$.verbose = !isCI;
|
||||||
|
process.env.FORCE_COLOR = isCI ? '0' : '1';
|
||||||
|
|
||||||
|
const scriptDir = path.dirname(new URL(import.meta.url).pathname);
|
||||||
|
const isInScriptsDir = path.basename(scriptDir) === 'scripts';
|
||||||
|
const rootDir = isInScriptsDir ? path.join(scriptDir, '..') : scriptDir;
|
||||||
|
|
||||||
|
// --- Configuration ---
|
||||||
|
const config = {
|
||||||
|
compiledAppDir: process.env.BUILD_OUTPUT_DIR || path.join(rootDir, 'compiled'),
|
||||||
|
rootDir: rootDir,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Define backend patches to keep during deployment
|
||||||
|
const PATCHES_TO_KEEP = ['pdfjs-dist', 'pkce-challenge', 'bull'];
|
||||||
|
|
||||||
|
// --- Helper Functions ---
|
||||||
|
const timers = new Map();
|
||||||
|
|
||||||
|
function startTimer(name) {
|
||||||
|
timers.set(name, Date.now());
|
||||||
|
}
|
||||||
|
|
||||||
|
function getElapsedTime(name) {
|
||||||
|
const start = timers.get(name);
|
||||||
|
if (!start) return 0;
|
||||||
|
return Math.floor((Date.now() - start) / 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDuration(seconds) {
|
||||||
|
const hours = Math.floor(seconds / 3600);
|
||||||
|
const minutes = Math.floor((seconds % 3600) / 60);
|
||||||
|
const secs = seconds % 60;
|
||||||
|
|
||||||
|
if (hours > 0) return `${hours}h ${minutes}m ${secs}s`;
|
||||||
|
if (minutes > 0) return `${minutes}m ${secs}s`;
|
||||||
|
return `${secs}s`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function printHeader(title) {
|
||||||
|
echo('');
|
||||||
|
echo(chalk.blue.bold(`===== ${title} =====`));
|
||||||
|
}
|
||||||
|
|
||||||
|
function printDivider() {
|
||||||
|
echo(chalk.gray('-----------------------------------------------'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Main Build Process ---
|
||||||
|
printHeader('n8n Build & Production Preparation');
|
||||||
|
echo(`INFO: Output Directory: ${config.compiledAppDir}`);
|
||||||
|
printDivider();
|
||||||
|
|
||||||
|
startTimer('total_build');
|
||||||
|
|
||||||
|
// 0. Clean Previous Build Output
|
||||||
|
echo(chalk.yellow(`INFO: Cleaning previous output directory: ${config.compiledAppDir}...`));
|
||||||
|
await fs.remove(config.compiledAppDir);
|
||||||
|
printDivider();
|
||||||
|
|
||||||
|
// 1. Local Application Pre-build
|
||||||
|
echo(chalk.yellow('INFO: Starting local application pre-build...'));
|
||||||
|
startTimer('package_build');
|
||||||
|
|
||||||
|
echo(chalk.yellow('INFO: Running pnpm install and build...'));
|
||||||
|
await $`cd ${config.rootDir} && pnpm install --frozen-lockfile`;
|
||||||
|
await $`cd ${config.rootDir} && pnpm build`;
|
||||||
|
echo(chalk.green('✅ pnpm install and build completed'));
|
||||||
|
|
||||||
|
const packageBuildTime = getElapsedTime('package_build');
|
||||||
|
echo(chalk.green(`✅ Package build completed in ${formatDuration(packageBuildTime)}`));
|
||||||
|
printDivider();
|
||||||
|
|
||||||
|
// 2. Prepare for deployment - clean package.json files
|
||||||
|
echo(chalk.yellow('INFO: Performing pre-deploy cleanup on package.json files...'));
|
||||||
|
|
||||||
|
// Find and backup package.json files
|
||||||
|
const packageJsonFiles = await $`cd ${config.rootDir} && find . -name "package.json" \
|
||||||
|
-not -path "./node_modules/*" \
|
||||||
|
-not -path "*/node_modules/*" \
|
||||||
|
-not -path "./compiled/*" \
|
||||||
|
-type f`.lines();
|
||||||
|
|
||||||
|
// Backup all package.json files
|
||||||
|
// This is only needed locally, not in CI
|
||||||
|
if (process.env.CI !== 'true') {
|
||||||
|
for (const file of packageJsonFiles) {
|
||||||
|
if (file) {
|
||||||
|
const fullPath = path.join(config.rootDir, file);
|
||||||
|
await fs.copy(fullPath, `${fullPath}.bak`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Run FE trim script
|
||||||
|
await $`cd ${config.rootDir} && node .github/scripts/trim-fe-packageJson.js`;
|
||||||
|
echo(chalk.yellow('INFO: Performing selective patch cleanup...'));
|
||||||
|
|
||||||
|
const packageJsonPath = path.join(config.rootDir, 'package.json');
|
||||||
|
|
||||||
|
if (await fs.pathExists(packageJsonPath)) {
|
||||||
|
try {
|
||||||
|
// 1. Read the package.json file
|
||||||
|
const packageJsonContent = await fs.readFile(packageJsonPath, 'utf8');
|
||||||
|
let packageJson = JSON.parse(packageJsonContent);
|
||||||
|
|
||||||
|
// 2. Modify the patchedDependencies directly in JavaScript
|
||||||
|
if (packageJson.pnpm && packageJson.pnpm.patchedDependencies) {
|
||||||
|
const filteredPatches = {};
|
||||||
|
for (const [key, value] of Object.entries(packageJson.pnpm.patchedDependencies)) {
|
||||||
|
// Check if the key (patch name) starts with any of the allowed patches
|
||||||
|
const shouldKeep = PATCHES_TO_KEEP.some((patchPrefix) => key.startsWith(patchPrefix));
|
||||||
|
if (shouldKeep) {
|
||||||
|
filteredPatches[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
packageJson.pnpm.patchedDependencies = filteredPatches;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Write the modified package.json back
|
||||||
|
await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2), 'utf8');
|
||||||
|
|
||||||
|
echo(chalk.green('✅ Kept backend patches: ' + PATCHES_TO_KEEP.join(', ')));
|
||||||
|
echo(
|
||||||
|
chalk.gray(
|
||||||
|
`Removed FE/dev patches that are not in the list of backend patches to keep: ${PATCHES_TO_KEEP.join(', ')}`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
echo(chalk.red(`ERROR: Failed to cleanup patches in package.json: ${error.message}`));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo(chalk.yellow(`INFO: Creating pruned production deployment in '${config.compiledAppDir}'...`));
|
||||||
|
startTimer('package_deploy');
|
||||||
|
|
||||||
|
await fs.ensureDir(config.compiledAppDir);
|
||||||
|
|
||||||
|
await $`cd ${config.rootDir} && NODE_ENV=production DOCKER_BUILD=true pnpm --filter=n8n --prod --legacy deploy --no-optional ./compiled`;
|
||||||
|
|
||||||
|
const packageDeployTime = getElapsedTime('package_deploy');
|
||||||
|
|
||||||
|
// Restore package.json files
|
||||||
|
// This is only needed locally, not in CI
|
||||||
|
if (process.env.CI !== 'true') {
|
||||||
|
for (const file of packageJsonFiles) {
|
||||||
|
if (file) {
|
||||||
|
const fullPath = path.join(config.rootDir, file);
|
||||||
|
const backupPath = `${fullPath}.bak`;
|
||||||
|
if (await fs.pathExists(backupPath)) {
|
||||||
|
await fs.move(backupPath, fullPath, { overwrite: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate output size
|
||||||
|
const compiledAppOutputSize = (await $`du -sh ${config.compiledAppDir} | cut -f1`).stdout.trim();
|
||||||
|
|
||||||
|
// Generate build manifest
|
||||||
|
const buildManifest = {
|
||||||
|
buildTime: new Date().toISOString(),
|
||||||
|
artifactSize: compiledAppOutputSize,
|
||||||
|
buildDuration: {
|
||||||
|
packageBuild: packageBuildTime,
|
||||||
|
packageDeploy: packageDeployTime,
|
||||||
|
total: getElapsedTime('total_build'),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
await fs.writeJson(path.join(config.compiledAppDir, 'build-manifest.json'), buildManifest, {
|
||||||
|
spaces: 2,
|
||||||
|
});
|
||||||
|
|
||||||
|
echo(chalk.green(`✅ Package deployment completed in ${formatDuration(packageDeployTime)}`));
|
||||||
|
echo(`INFO: Size of ${config.compiledAppDir}: ${compiledAppOutputSize}`);
|
||||||
|
printDivider();
|
||||||
|
|
||||||
|
// Calculate total time
|
||||||
|
const totalBuildTime = getElapsedTime('total_build');
|
||||||
|
|
||||||
|
// --- Final Output ---
|
||||||
|
echo('');
|
||||||
|
echo(chalk.green.bold('================ BUILD SUMMARY ================'));
|
||||||
|
echo(chalk.green(`✅ n8n built successfully!`));
|
||||||
|
echo('');
|
||||||
|
echo(chalk.blue('📦 Build Output:'));
|
||||||
|
echo(` Directory: ${path.resolve(config.compiledAppDir)}`);
|
||||||
|
echo(` Size: ${compiledAppOutputSize}`);
|
||||||
|
echo('');
|
||||||
|
echo(chalk.blue('⏱️ Build Times:'));
|
||||||
|
echo(` Package Build: ${formatDuration(packageBuildTime)}`);
|
||||||
|
echo(` Package Deploy: ${formatDuration(packageDeployTime)}`);
|
||||||
|
echo(chalk.gray(' -----------------------------'));
|
||||||
|
echo(chalk.bold(` Total Time: ${formatDuration(totalBuildTime)}`));
|
||||||
|
echo('');
|
||||||
|
echo(chalk.blue('📋 Build Manifest:'));
|
||||||
|
echo(` ${path.resolve(config.compiledAppDir)}/build-manifest.json`);
|
||||||
|
echo(chalk.green.bold('=============================================='));
|
||||||
|
|
||||||
|
// Exit with success
|
||||||
|
process.exit(0);
|
||||||
88
scripts/dockerize-n8n.mjs
Executable file
88
scripts/dockerize-n8n.mjs
Executable file
@@ -0,0 +1,88 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* This script is used to build the n8n docker image locally.
|
||||||
|
* It simulates how we build for CI and should allow for local testing.
|
||||||
|
* By default it outputs the tag 'dev' and the image name 'n8n-local:dev'.
|
||||||
|
* It can be overridden by setting the IMAGE_BASE_NAME and IMAGE_TAG environment variables.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { $, echo, fs, chalk } from 'zx';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
// Disable verbose mode for cleaner output
|
||||||
|
$.verbose = false;
|
||||||
|
process.env.FORCE_COLOR = '1';
|
||||||
|
|
||||||
|
// --- Determine script location ---
|
||||||
|
const scriptDir = path.dirname(new URL(import.meta.url).pathname);
|
||||||
|
const isInScriptsDir = path.basename(scriptDir) === 'scripts';
|
||||||
|
const rootDir = isInScriptsDir ? path.join(scriptDir, '..') : scriptDir;
|
||||||
|
|
||||||
|
// --- Configuration ---
|
||||||
|
const config = {
|
||||||
|
dockerfilePath: path.join(rootDir, 'docker/images/n8n/Dockerfile'),
|
||||||
|
imageBaseName: process.env.IMAGE_BASE_NAME || 'n8n-local',
|
||||||
|
imageTag: process.env.IMAGE_TAG || 'dev',
|
||||||
|
buildContext: rootDir,
|
||||||
|
compiledAppDir: path.join(rootDir, 'compiled'),
|
||||||
|
};
|
||||||
|
|
||||||
|
config.fullImageName = `${config.imageBaseName}:${config.imageTag}`;
|
||||||
|
|
||||||
|
// --- Check Prerequisites ---
|
||||||
|
echo(chalk.blue.bold('===== Docker Build for n8n ====='));
|
||||||
|
echo(`INFO: Image: ${config.fullImageName}`);
|
||||||
|
echo(chalk.gray('-----------------------------------------------'));
|
||||||
|
|
||||||
|
// Check if compiled directory exists
|
||||||
|
if (!(await fs.pathExists(config.compiledAppDir))) {
|
||||||
|
echo(chalk.red(`Error: Compiled app directory not found at ${config.compiledAppDir}`));
|
||||||
|
echo(chalk.yellow('Please run build-n8n.mjs first!'));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check Docker
|
||||||
|
try {
|
||||||
|
await $`command -v docker`;
|
||||||
|
} catch {
|
||||||
|
echo(chalk.red('Error: Docker is not installed or not in PATH'));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Build Docker Image ---
|
||||||
|
const startTime = Date.now();
|
||||||
|
echo(chalk.yellow('INFO: Building Docker image...'));
|
||||||
|
|
||||||
|
try {
|
||||||
|
const buildOutput = await $`docker build \
|
||||||
|
-t ${config.fullImageName} \
|
||||||
|
-f ${config.dockerfilePath} \
|
||||||
|
${config.buildContext}`;
|
||||||
|
|
||||||
|
echo(buildOutput.stdout);
|
||||||
|
} catch (error) {
|
||||||
|
echo(chalk.red(`ERROR: Docker build failed: ${error.stderr || error.message}`));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildTime = Math.floor((Date.now() - startTime) / 1000);
|
||||||
|
|
||||||
|
// Get image size
|
||||||
|
let imageSize = 'Unknown';
|
||||||
|
try {
|
||||||
|
const sizeOutput = await $`docker images ${config.fullImageName} --format "{{.Size}}"`;
|
||||||
|
imageSize = sizeOutput.stdout.trim();
|
||||||
|
} catch (error) {
|
||||||
|
echo(chalk.yellow('Warning: Could not get image size'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Summary ---
|
||||||
|
echo('');
|
||||||
|
echo(chalk.green.bold('================ DOCKER BUILD COMPLETE ================'));
|
||||||
|
echo(chalk.green(`✅ Image built: ${config.fullImageName}`));
|
||||||
|
echo(` Size: ${imageSize}`);
|
||||||
|
echo(` Build time: ${buildTime}s`);
|
||||||
|
echo(chalk.green.bold('===================================================='));
|
||||||
|
|
||||||
|
// Exit with success
|
||||||
|
process.exit(0);
|
||||||
152
scripts/scan-n8n-image.mjs
Executable file
152
scripts/scan-n8n-image.mjs
Executable file
@@ -0,0 +1,152 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* This script is used to scan the n8n docker image for vulnerabilities.
|
||||||
|
* It uses Trivy to scan the image.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { $, echo, fs, chalk } from 'zx';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
$.verbose = false;
|
||||||
|
process.env.FORCE_COLOR = '1';
|
||||||
|
|
||||||
|
const scriptDir = path.dirname(new URL(import.meta.url).pathname);
|
||||||
|
const isInScriptsDir = path.basename(scriptDir) === 'scripts';
|
||||||
|
const rootDir = isInScriptsDir ? path.join(scriptDir, '..') : scriptDir;
|
||||||
|
|
||||||
|
// --- Configuration ---
|
||||||
|
const config = {
|
||||||
|
imageBaseName: process.env.IMAGE_BASE_NAME || 'n8n-local',
|
||||||
|
imageTag: process.env.IMAGE_TAG || 'dev',
|
||||||
|
trivyImage: process.env.TRIVY_IMAGE || 'aquasec/trivy:latest',
|
||||||
|
severity: process.env.TRIVY_SEVERITY || 'CRITICAL,HIGH,MEDIUM,LOW',
|
||||||
|
outputFormat: process.env.TRIVY_FORMAT || 'table',
|
||||||
|
outputFile: process.env.TRIVY_OUTPUT || null,
|
||||||
|
scanTimeout: process.env.TRIVY_TIMEOUT || '10m',
|
||||||
|
ignoreUnfixed: process.env.TRIVY_IGNORE_UNFIXED === 'true',
|
||||||
|
scanners: process.env.TRIVY_SCANNERS || 'vuln',
|
||||||
|
quiet: process.env.TRIVY_QUIET === 'true',
|
||||||
|
rootDir: rootDir,
|
||||||
|
};
|
||||||
|
|
||||||
|
config.fullImageName = `${config.imageBaseName}:${config.imageTag}`;
|
||||||
|
|
||||||
|
const printHeader = (title) =>
|
||||||
|
!config.quiet && echo(`\n${chalk.blue.bold(`===== ${title} =====`)}`);
|
||||||
|
|
||||||
|
const printSummary = (status, time, message) => {
|
||||||
|
if (config.quiet) return;
|
||||||
|
|
||||||
|
echo('\n' + chalk.blue.bold('===== Scan Summary ====='));
|
||||||
|
echo(status === 'success' ? chalk.green.bold(message) : chalk.yellow.bold(message));
|
||||||
|
echo(chalk[status === 'success' ? 'green' : 'yellow'](` Scan time: ${time}s`));
|
||||||
|
|
||||||
|
if (config.outputFile) {
|
||||||
|
const resolvedPath = path.isAbsolute(config.outputFile)
|
||||||
|
? config.outputFile
|
||||||
|
: path.join(config.rootDir, config.outputFile);
|
||||||
|
echo(chalk[status === 'success' ? 'green' : 'yellow'](` Report saved to: ${resolvedPath}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
echo('\n' + chalk.gray('Scan Configuration:'));
|
||||||
|
echo(chalk.gray(` • Target Image: ${config.fullImageName}`));
|
||||||
|
echo(chalk.gray(` • Severity Levels: ${config.severity}`));
|
||||||
|
echo(chalk.gray(` • Scanners: ${config.scanners}`));
|
||||||
|
if (config.ignoreUnfixed) echo(chalk.gray(` • Ignored unfixed: yes`));
|
||||||
|
echo(chalk.blue.bold('========================'));
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Main Process ---
|
||||||
|
(async () => {
|
||||||
|
printHeader('Trivy Security Scan for n8n Image');
|
||||||
|
|
||||||
|
try {
|
||||||
|
await $`command -v docker`;
|
||||||
|
} catch {
|
||||||
|
echo(chalk.red('Error: Docker is not installed or not in PATH'));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await $`docker image inspect ${config.fullImageName} > /dev/null 2>&1`;
|
||||||
|
} catch {
|
||||||
|
echo(chalk.red(`Error: Docker image '${config.fullImageName}' not found`));
|
||||||
|
echo(chalk.yellow('Please run dockerize-n8n.mjs first!'));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pull latest Trivy image silently
|
||||||
|
try {
|
||||||
|
await $`docker pull ${config.trivyImage} > /dev/null 2>&1`;
|
||||||
|
} catch {
|
||||||
|
// Silent fallback to cached version
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build Trivy command
|
||||||
|
const trivyArgs = [
|
||||||
|
'run',
|
||||||
|
'--rm',
|
||||||
|
'-v',
|
||||||
|
'/var/run/docker.sock:/var/run/docker.sock',
|
||||||
|
config.trivyImage,
|
||||||
|
'image',
|
||||||
|
'--severity',
|
||||||
|
config.severity,
|
||||||
|
'--format',
|
||||||
|
config.outputFormat,
|
||||||
|
'--timeout',
|
||||||
|
config.scanTimeout,
|
||||||
|
'--scanners',
|
||||||
|
config.scanners,
|
||||||
|
'--no-progress',
|
||||||
|
];
|
||||||
|
|
||||||
|
if (config.ignoreUnfixed) trivyArgs.push('--ignore-unfixed');
|
||||||
|
if (config.quiet && config.outputFormat === 'table') trivyArgs.push('--quiet');
|
||||||
|
|
||||||
|
// Handle output file - resolve relative to root directory
|
||||||
|
if (config.outputFile) {
|
||||||
|
const outputPath = path.isAbsolute(config.outputFile)
|
||||||
|
? config.outputFile
|
||||||
|
: path.join(config.rootDir, config.outputFile);
|
||||||
|
await fs.ensureDir(path.dirname(outputPath));
|
||||||
|
trivyArgs.push('--output', '/tmp/trivy-output', '-v', `${outputPath}:/tmp/trivy-output`);
|
||||||
|
}
|
||||||
|
|
||||||
|
trivyArgs.push(config.fullImageName);
|
||||||
|
|
||||||
|
// Run the scan
|
||||||
|
const startTime = Date.now();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await $`docker ${trivyArgs}`;
|
||||||
|
|
||||||
|
// Print Trivy output first
|
||||||
|
if (!config.outputFile && result.stdout) {
|
||||||
|
echo(result.stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then print our summary
|
||||||
|
const scanTime = Math.floor((Date.now() - startTime) / 1000);
|
||||||
|
printSummary('success', scanTime, '✅ Security scan completed successfully');
|
||||||
|
|
||||||
|
process.exit(0);
|
||||||
|
} catch (error) {
|
||||||
|
const scanTime = Math.floor((Date.now() - startTime) / 1000);
|
||||||
|
|
||||||
|
// Trivy returns exit code 1 when vulnerabilities are found
|
||||||
|
if (error.exitCode === 1) {
|
||||||
|
// Print Trivy output first
|
||||||
|
if (!config.outputFile && error.stdout) {
|
||||||
|
echo(error.stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then print our summary
|
||||||
|
printSummary('warning', scanTime, '⚠️ Vulnerabilities found!');
|
||||||
|
process.exit(1);
|
||||||
|
} else {
|
||||||
|
echo(chalk.red(`❌ Scan failed: ${error.message}`));
|
||||||
|
process.exit(error.exitCode || 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
||||||
Reference in New Issue
Block a user