diff --git a/docker/images/runners/Dockerfile b/docker/images/runners/Dockerfile new file mode 100644 index 0000000000..0519370faf --- /dev/null +++ b/docker/images/runners/Dockerfile @@ -0,0 +1,121 @@ +ARG PYTHON_IMAGE=python:3.13-slim + +# ============================================================================== +# STAGE 1: JavaScript runner (@n8n/task-runner) artifact from CI +# ============================================================================== +FROM alpine:3.22.1 AS app-artifact-processor +COPY ./dist/task-runner-javascript /app/task-runner-javascript + +# ============================================================================== +# STAGE 2: Python runner build (@n8n/task-runner-python) with uv +# Produces a relocatable venv tied to PYTHON_IMAGE +# ============================================================================== +FROM ${PYTHON_IMAGE} AS python-runner-builder +ARG TARGETPLATFORM +ARG UV_VERSION=0.8.14 + +RUN apt-get update && apt-get install -y --no-install-recommends \ + curl ca-certificates build-essential pkg-config git \ + && rm -rf /var/lib/apt/lists/* + +RUN set -e; \ + case "$TARGETPLATFORM" in \ + "linux/amd64") UV_ARCH="x86_64-unknown-linux-gnu" ;; \ + "linux/arm64") UV_ARCH="aarch64-unknown-linux-gnu" ;; \ + *) echo "Unsupported platform: $TARGETPLATFORM" >&2; exit 1 ;; \ + esac; \ + mkdir -p /tmp/uv && cd /tmp/uv; \ + curl -fsSLO "https://github.com/astral-sh/uv/releases/download/${UV_VERSION}/uv-${UV_ARCH}.tar.gz"; \ + curl -fsSLO "https://github.com/astral-sh/uv/releases/download/${UV_VERSION}/uv-${UV_ARCH}.tar.gz.sha256"; \ + sha256sum -c "uv-${UV_ARCH}.tar.gz.sha256"; \ + tar -xzf "uv-${UV_ARCH}.tar.gz"; \ + install -m 0755 "uv-${UV_ARCH}/uv" /usr/local/bin/uv; \ + cd / && rm -rf /tmp/uv + +WORKDIR /app/task-runner-python + +COPY packages/@n8n/task-runner-python/pyproject.toml \ + packages/@n8n/task-runner-python/uv.lock** \ + packages/@n8n/task-runner-python/.python-version** \ + ./ + +RUN uv venv +RUN uv sync \ + --frozen \ + --no-editable \ + --no-install-project \ + --all-extras + +COPY packages/@n8n/task-runner-python/ ./ +RUN uv sync \ + --frozen \ + --no-editable + +# ============================================================================== +# STAGE 3: Task Runner Launcher download +# ============================================================================== +FROM alpine:3.22.1 AS launcher-downloader +ARG TARGETPLATFORM +ARG LAUNCHER_VERSION=1.3.0 + +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: Runtime +# ============================================================================== +FROM ${PYTHON_IMAGE} AS runtime +ARG NODE_VERSION=22 +ARG N8N_VERSION=snapshot +ARG N8N_RELEASE_TYPE=dev + +ENV NODE_ENV=production +ENV N8N_RELEASE_TYPE=${N8N_RELEASE_TYPE} +ENV SHELL=/bin/sh + +RUN apt-get update \ + && apt-get install -y --no-install-recommends curl gnupg ca-certificates tini \ + && mkdir -p /etc/apt/keyrings \ + && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key \ + | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \ + && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_${NODE_VERSION}.x nodistro main" \ + > /etc/apt/sources.list.d/nodesource.list \ + && apt-get update \ + && apt-get install -y --no-install-recommends nodejs \ + && apt-get remove curl -y \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*.deb + +RUN useradd -m -u 1000 runner +WORKDIR /home/runner + +COPY --from=app-artifact-processor /app/task-runner-javascript /opt/runners/task-runner-javascript +COPY --from=python-runner-builder /app/task-runner-python /opt/runners/task-runner-python +COPY --from=launcher-downloader /launcher-bin/* /usr/local/bin/ + +COPY docker/images/runners/n8n-task-runners.json /etc/n8n-task-runners.json + +RUN chown -R runner:runner /opt/runners /home/runner +USER runner + +EXPOSE 5680/tcp +ENTRYPOINT ["tini", "--", "/usr/local/bin/task-runner-launcher"] +CMD ["javascript", "python"] + +LABEL org.opencontainers.image.title="n8n task runners" \ + org.opencontainers.image.description="Sidecar image providing n8n task runners for JavaScript and Python code execution" \ + org.opencontainers.image.source="https://github.com/n8n-io/n8n" \ + org.opencontainers.image.url="https://n8n.io" \ + org.opencontainers.image.version="${N8N_VERSION}" diff --git a/docker/images/runners/README.md b/docker/images/runners/README.md new file mode 100644 index 0000000000..90c550bbdf --- /dev/null +++ b/docker/images/runners/README.md @@ -0,0 +1,50 @@ +# n8n - Task runners (`n8nio/runners`) - (PREVIEW) + +`n8nio/runners` image includes [JavaScript runner](https://github.com/n8n-io/n8n/tree/master/packages/%40n8n/task-runner), +[Python runner](https://github.com/n8n-io/n8n/tree/master/packages/%40n8n/task-runner-python) and +[Task runner launcher](https://github.com/n8n-io/task-runner-launcher) that connects to a Task Broker +running on the main n8n instance when running in `external` mode. This image is to be launched as a sidecar +container to the main n8n container. + +[Task runners](https://docs.n8n.io/hosting/configuration/task-runners/) are used to execute user-provided code +in the [Code Node](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.code/), isolated from the n8n instance. + + +## Testing locally + +1. Make a production build of n8n + +``` +pnpm run build:n8n +``` + +2. Build the task runners image + +``` +docker buildx build --no-cache \ + -f docker/images/runners/Dockerfile \ + -t n8nio/runners \ + . +``` + +3. Start n8n on your host machine with Task Broker enabled + +``` +N8N_RUNNERS_ENABLED=true \ +N8N_RUNNERS_MODE=external \ +N8N_RUNNERS_AUTH_TOKEN=test \ +N8N_LOG_LEVEL=debug \ +pnpm start +``` + + +4. Start the task runner container + +``` +docker run --rm -it \ +-e N8N_RUNNERS_AUTH_TOKEN=test \ +-e N8N_RUNNERS_LAUNCHER_LOG_LEVEL=debug \ +-e N8N_RUNNERS_TASK_BROKER_URI=http://host.docker.internal:5679 \ +-p 5680:5680 \ +n8nio/runners +``` diff --git a/docker/images/runners/n8n-task-runners.json b/docker/images/runners/n8n-task-runners.json new file mode 100644 index 0000000000..20cc72bb73 --- /dev/null +++ b/docker/images/runners/n8n-task-runners.json @@ -0,0 +1,48 @@ +{ + "task-runners": [ + { + "runner-type": "javascript", + "workdir": "/home/runner", + "command": "/usr/bin/node", + "args": [ + "--disallow-code-generation-from-strings", + "--disable-proto=delete", + "/opt/runners/task-runner-javascript/dist/start.js" + ], + "health-check-server-port": "5681", + "allowed-env": [ + "PATH", + "GENERIC_TIMEZONE", + "N8N_RUNNERS_MAX_CONCURRENCY", + "NODE_OPTIONS", + "N8N_SENTRY_DSN", + "N8N_VERSION", + "ENVIRONMENT", + "DEPLOYMENT_NAME" + ], + "env-overrides": { + "NODE_FUNCTION_ALLOW_BUILTIN": "crypto", + "NODE_FUNCTION_ALLOW_EXTERNAL": "moment", + "N8N_RUNNERS_HEALTH_CHECK_SERVER_HOST": "0.0.0.0" + } + }, + { + "runner-type": "python", + "workdir": "/home/runner", + "command": "/opt/runners/task-runner-python/.venv/bin/python", + "args": ["-m", "src.main"], + "health-check-server-port": "5682", + "allowed-env": [ + "PATH", + "N8N_RUNNERS_LAUNCHER_LOG_LEVEL", + "N8N_SENTRY_DSN", + "N8N_VERSION", + "ENVIRONMENT", + "DEPLOYMENT_NAME" + ], + "env-overrides": { + "PYTHONPATH": "/opt/runners/task-runner-python" + } + } + ] +} diff --git a/packages/@n8n/task-runner/package.json b/packages/@n8n/task-runner/package.json index 1f3d25b697..74240e088b 100644 --- a/packages/@n8n/task-runner/package.json +++ b/packages/@n8n/task-runner/package.json @@ -42,6 +42,7 @@ "acorn-walk": "8.3.4", "lodash": "catalog:", "luxon": "catalog:", + "moment": "2.30.1", "n8n-core": "workspace:*", "n8n-workflow": "workspace:*", "nanoid": "catalog:", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3f53002851..7d9fab149c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1005,7 +1005,7 @@ importers: version: 4.3.0 '@getzep/zep-cloud': specifier: 1.0.12 - version: 1.0.12(@langchain/core@0.3.68(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.67)))(encoding@0.1.13)(langchain@0.3.30(e7c2f10ddf33088da1e6affdf0fc6c0a)) + version: 1.0.12(@langchain/core@0.3.68(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.67)))(encoding@0.1.13)(langchain@0.3.30(316b19288832115574731e049dc7676a)) '@getzep/zep-js': specifier: 0.9.0 version: 0.9.0 @@ -1032,7 +1032,7 @@ importers: version: 0.3.4(@langchain/core@0.3.68(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.67)))(encoding@0.1.13) '@langchain/community': specifier: 'catalog:' - version: 0.3.50(7d9026709e640c92cdf2ea22646a0399) + version: 0.3.50(ccee17333f80550b1303d83de2b6f79a) '@langchain/core': specifier: 'catalog:' version: 0.3.68(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.67)) @@ -1149,7 +1149,7 @@ importers: version: 23.0.1 langchain: specifier: 0.3.30 - version: 0.3.30(e7c2f10ddf33088da1e6affdf0fc6c0a) + version: 0.3.30(316b19288832115574731e049dc7676a) lodash: specifier: 'catalog:' version: 4.17.21 @@ -1372,6 +1372,9 @@ importers: luxon: specifier: 'catalog:' version: 3.4.4 + moment: + specifier: 2.30.1 + version: 2.30.1 n8n-core: specifier: workspace:* version: link:../../core @@ -13083,8 +13086,8 @@ packages: moment-timezone@0.5.48: resolution: {integrity: sha512-f22b8LV1gbTO2ms2j2z13MuPogNoh5UzxL3nzNAYKGraILnbGc9NEE6dyiiiLv46DGRb8A4kg8UKWLjPthxBHw==} - moment@2.29.4: - resolution: {integrity: sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==} + moment@2.30.1: + resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==} mongodb-connection-string-url@3.0.0: resolution: {integrity: sha512-t1Vf+m1I5hC2M5RJx/7AtxgABy1cZmIPQRMXw+gEIPn/cZNF3Oiy+l0UIypUwVB5trcWHq3crg2g3uAR9aAwsQ==} @@ -19357,7 +19360,7 @@ snapshots: '@gar/promisify@1.1.3': optional: true - '@getzep/zep-cloud@1.0.12(@langchain/core@0.3.68(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.67)))(encoding@0.1.13)(langchain@0.3.30(e7c2f10ddf33088da1e6affdf0fc6c0a))': + '@getzep/zep-cloud@1.0.12(@langchain/core@0.3.68(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.67)))(encoding@0.1.13)(langchain@0.3.30(316b19288832115574731e049dc7676a))': dependencies: form-data: 4.0.4 node-fetch: 2.7.0(encoding@0.1.13) @@ -19366,7 +19369,7 @@ snapshots: zod: 3.25.67 optionalDependencies: '@langchain/core': 0.3.68(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.67)) - langchain: 0.3.30(e7c2f10ddf33088da1e6affdf0fc6c0a) + langchain: 0.3.30(316b19288832115574731e049dc7676a) transitivePeerDependencies: - encoding @@ -19920,7 +19923,7 @@ snapshots: - aws-crt - encoding - '@langchain/community@0.3.50(7d9026709e640c92cdf2ea22646a0399)': + '@langchain/community@0.3.50(ccee17333f80550b1303d83de2b6f79a)': dependencies: '@browserbasehq/stagehand': 1.9.0(@playwright/test@1.54.2)(deepmerge@4.3.1)(dotenv@16.6.1)(encoding@0.1.13)(openai@5.12.2(ws@8.18.3)(zod@3.25.67))(zod@3.25.67) '@ibm-cloud/watsonx-ai': 1.1.2 @@ -19932,7 +19935,7 @@ snapshots: flat: 5.0.2 ibm-cloud-sdk-core: 5.3.2 js-yaml: 4.1.0 - langchain: 0.3.30(e7c2f10ddf33088da1e6affdf0fc6c0a) + langchain: 0.3.30(316b19288832115574731e049dc7676a) langsmith: 0.3.55(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.67)) openai: 5.12.2(ws@8.18.3)(zod@3.25.67) uuid: 10.0.0 @@ -19946,7 +19949,7 @@ snapshots: '@aws-sdk/credential-provider-node': 3.808.0 '@azure/storage-blob': 12.26.0 '@browserbasehq/sdk': 2.6.0(encoding@0.1.13) - '@getzep/zep-cloud': 1.0.12(@langchain/core@0.3.68(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.67)))(encoding@0.1.13)(langchain@0.3.30(e7c2f10ddf33088da1e6affdf0fc6c0a)) + '@getzep/zep-cloud': 1.0.12(@langchain/core@0.3.68(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.67)))(encoding@0.1.13)(langchain@0.3.30(316b19288832115574731e049dc7676a)) '@getzep/zep-js': 0.9.0 '@google-ai/generativelanguage': 2.6.0(encoding@0.1.13) '@google-cloud/storage': 7.12.1(encoding@0.1.13) @@ -25856,7 +25859,7 @@ snapshots: eslint-import-resolver-node@0.3.9: dependencies: - debug: 3.2.7(supports-color@8.1.1) + debug: 3.2.7(supports-color@5.5.0) is-core-module: 2.16.1 resolve: 1.22.10 transitivePeerDependencies: @@ -25880,7 +25883,7 @@ snapshots: eslint-module-utils@2.12.1(@typescript-eslint/parser@8.35.0(eslint@9.29.0(jiti@1.21.7))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@4.4.3)(eslint@9.29.0(jiti@1.21.7)): dependencies: - debug: 3.2.7(supports-color@8.1.1) + debug: 3.2.7(supports-color@5.5.0) optionalDependencies: '@typescript-eslint/parser': 8.35.0(eslint@9.29.0(jiti@1.21.7))(typescript@5.9.2) eslint: 9.29.0(jiti@1.21.7) @@ -25919,7 +25922,7 @@ snapshots: array.prototype.findlastindex: 1.2.6 array.prototype.flat: 1.3.3 array.prototype.flatmap: 1.3.3 - debug: 3.2.7(supports-color@8.1.1) + debug: 3.2.7(supports-color@5.5.0) doctrine: 2.1.0 eslint: 9.29.0(jiti@1.21.7) eslint-import-resolver-node: 0.3.9 @@ -26878,7 +26881,7 @@ snapshots: array-parallel: 0.1.3 array-series: 0.1.5 cross-spawn: 7.0.6 - debug: 3.2.7(supports-color@8.1.1) + debug: 3.2.7(supports-color@5.5.0) transitivePeerDependencies: - supports-color @@ -27231,7 +27234,7 @@ snapshots: '@types/debug': 4.1.12 '@types/node': 20.19.11 '@types/tough-cookie': 4.0.5 - axios: 1.11.0(debug@4.3.6) + axios: 1.11.0(debug@4.4.1) camelcase: 6.3.0 debug: 4.4.1(supports-color@8.1.1) dotenv: 16.6.1 @@ -27241,7 +27244,7 @@ snapshots: isstream: 0.1.2 jsonwebtoken: 9.0.2 mime-types: 2.1.35 - retry-axios: 2.6.0(axios@1.11.0(debug@4.4.1)) + retry-axios: 2.6.0(axios@1.11.0) tough-cookie: 4.1.4 transitivePeerDependencies: - supports-color @@ -28500,7 +28503,7 @@ snapshots: kuler@2.0.0: {} - langchain@0.3.30(e7c2f10ddf33088da1e6affdf0fc6c0a): + langchain@0.3.30(316b19288832115574731e049dc7676a): dependencies: '@langchain/core': 0.3.68(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.67)) '@langchain/openai': 0.6.7(@langchain/core@0.3.68(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.67)))(ws@8.18.3) @@ -29530,9 +29533,9 @@ snapshots: moment-timezone@0.5.48: dependencies: - moment: 2.29.4 + moment: 2.30.1 - moment@2.29.4: {} + moment@2.30.1: {} mongodb-connection-string-url@3.0.0: dependencies: @@ -30329,7 +30332,7 @@ snapshots: pdf-parse@1.1.1: dependencies: - debug: 3.2.7(supports-color@8.1.1) + debug: 3.2.7(supports-color@5.5.0) node-ensure: 0.0.0 transitivePeerDependencies: - supports-color @@ -31321,7 +31324,7 @@ snapshots: onetime: 5.1.2 signal-exit: 3.0.7 - retry-axios@2.6.0(axios@1.11.0(debug@4.4.1)): + retry-axios@2.6.0(axios@1.11.0): dependencies: axios: 1.11.0(debug@4.4.1) @@ -31348,7 +31351,7 @@ snapshots: rhea@1.0.24: dependencies: - debug: 3.2.7(supports-color@8.1.1) + debug: 3.2.7(supports-color@5.5.0) transitivePeerDependencies: - supports-color @@ -31872,7 +31875,7 @@ snapshots: jsonwebtoken: 9.0.2 mime-types: 2.1.35 mkdirp: 1.0.4 - moment: 2.29.4 + moment: 2.30.1 moment-timezone: 0.5.48 oauth4webapi: 3.5.1 open: 7.4.2 diff --git a/scripts/build-n8n.mjs b/scripts/build-n8n.mjs index 15451b37bf..1afddf2527 100755 --- a/scripts/build-n8n.mjs +++ b/scripts/build-n8n.mjs @@ -29,6 +29,7 @@ const rootDir = isInScriptsDir ? path.join(scriptDir, '..') : scriptDir; // #region ===== Configuration ===== const config = { compiledAppDir: path.join(rootDir, 'compiled'), + compiledTaskRunnerDir: path.join(rootDir, 'dist', 'task-runner-javascript'), rootDir: rootDir, }; @@ -81,6 +82,12 @@ startTimer('total_build'); // 0. Clean Previous Build Output echo(chalk.yellow(`INFO: Cleaning previous output directory: ${config.compiledAppDir}...`)); await fs.remove(config.compiledAppDir); +echo( + chalk.yellow( + `INFO: Cleaning previous task runner output directory: ${config.compiledTaskRunnerDir}...`, + ), +); +await fs.remove(config.compiledTaskRunnerDir); printDivider(); // 1. Local Application Pre-build @@ -196,6 +203,15 @@ if (excludeTestController) { } await $`cd ${config.rootDir} && NODE_ENV=production DOCKER_BUILD=true pnpm --filter=n8n --prod --legacy deploy --no-optional ./compiled`; +await fs.ensureDir(config.compiledTaskRunnerDir); + +echo( + chalk.yellow( + `INFO: Creating JavaScript task runner deployment in '${config.compiledTaskRunnerDir}'...`, + ), +); + +await $`cd ${config.rootDir} && NODE_ENV=production DOCKER_BUILD=true pnpm --filter=@n8n/task-runner --prod --legacy deploy --no-optional ${config.compiledTaskRunnerDir}`; const packageDeployTime = getElapsedTime('package_deploy'); @@ -215,8 +231,11 @@ if (process.env.CI !== 'true') { // Calculate output size const compiledAppOutputSize = (await $`du -sh ${config.compiledAppDir} | cut -f1`).stdout.trim(); +const compiledTaskRunnerOutputSize = ( + await $`du -sh ${config.compiledTaskRunnerDir} | cut -f1` +).stdout.trim(); -// Generate build manifest +// Generate build manifests const buildManifest = { buildTime: new Date().toISOString(), artifactSize: compiledAppOutputSize, @@ -231,6 +250,24 @@ await fs.writeJson(path.join(config.compiledAppDir, 'build-manifest.json'), buil spaces: 2, }); +const taskRunnerbuildManifest = { + buildTime: new Date().toISOString(), + artifactSize: compiledTaskRunnerOutputSize, + buildDuration: { + packageBuild: packageBuildTime, + packageDeploy: packageDeployTime, + total: getElapsedTime('total_build'), + }, +}; + +await fs.writeJson( + path.join(config.compiledTaskRunnerDir, 'build-manifest.json'), + taskRunnerbuildManifest, + { + spaces: 2, + }, +); + echo(chalk.green(`✅ Package deployment completed in ${formatDuration(packageDeployTime)}`)); echo(`INFO: Size of ${config.compiledAppDir}: ${compiledAppOutputSize}`); printDivider(); @@ -246,17 +283,23 @@ echo(chalk.green.bold('================ BUILD SUMMARY ================')); echo(chalk.green(`✅ n8n built successfully!`)); echo(''); echo(chalk.blue('📦 Build Output:')); +echo(chalk.green(' n8n:')); echo(` Directory: ${path.resolve(config.compiledAppDir)}`); echo(` Size: ${compiledAppOutputSize}`); echo(''); +echo(chalk.green(' task-runner-javascript:')); +echo(` Directory: ${path.resolve(config.compiledTaskRunnerDir)}`); +echo(` Size: ${compiledTaskRunnerOutputSize}`); +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(chalk.blue('📋 Build Manifests:')); echo(` ${path.resolve(config.compiledAppDir)}/build-manifest.json`); +echo(` ${path.resolve(config.compiledTaskRunnerDir)}/build-manifest.json`); echo(chalk.green.bold('==============================================')); // #endregion ===== Final Output =====