build: Move the monorepo and Docker deploys to pnpm 12 (no-changelog) (#37899)

Co-authored-by: Matsu <[email protected]>
This commit is contained in:
Declan Carroll
2026-09-08 15:15:57 +01:00
committed by GitHub
co-authored by Matsu
parent b7a58c25c1
commit 1ad0b44a68
50 changed files with 577 additions and 90 deletions
+1
View File
@@ -1,2 +1,3 @@
*.sh text eol=lf
*.patch text eol=lf
docs/generated/**/* linguist-generated
+13
View File
@@ -0,0 +1,13 @@
#!/bin/sh
# .githooks/check-pnpm — symlink or call from post-merge, post-rewrite, post-checkout
WANTED=$(sed -n 's/.*"packageManager": *"pnpm@\([0-9]*\).*/\1/p' package.json)
CURRENT=$(cd ~ && pnpm --version 2>/dev/null | cut -d. -f1)
[ -z "$WANTED" ] || [ "$WANTED" = "$CURRENT" ] && exit 0
echo ""
echo "⚠ This repo now pins pnpm $WANTED (you have ${CURRENT:-none})."
echo " pnpm should auto-download it on the next install command."
echo " If that fails (some pnpm 10 versions can't install v12), reinstall:"
echo " curl -fsSL https://get.pnpm.io/install.sh | sh -"
echo ""
exit 0
+27 -5
View File
@@ -14,6 +14,7 @@ import { appendFileSync, readFileSync } from 'node:fs';
import { pathToFileURL } from 'node:url';
const WORKSPACE_FILE = 'pnpm-workspace.yaml';
const ROOT_MANIFEST = 'package.json';
const BUMP_ONLY = [/^pnpm-workspace\.yaml$/, /^pnpm-lock\.yaml$/, /(^|\/)package\.json$/];
@@ -69,6 +70,14 @@ export function nativePinsChanged(baseText, headText) {
return { changed: false, reason: 'no buildable dependency pin changed' };
}
export function packageManagerChanged(baseText, headText) {
try {
return JSON.parse(baseText).packageManager !== JSON.parse(headText).packageManager;
} catch {
return true;
}
}
function fileAt(ref, path) {
try {
return execFileSync('git', ['show', `${ref}:${path}`], { encoding: 'utf8' });
@@ -88,12 +97,25 @@ function main(argv) {
let reason = 'a Docker-chain file changed';
if (isBumpOnly(files)) {
const baseText = fileAt(base, WORKSPACE_FILE);
const headText = fileAt('HEAD', WORKSPACE_FILE);
if (baseText === null || headText === null) {
reason = `could not read ${WORKSPACE_FILE} on both sides of the diff`;
const baseManifest = fileAt(base, ROOT_MANIFEST);
const headManifest = fileAt('HEAD', ROOT_MANIFEST);
if (files.includes(ROOT_MANIFEST) && (baseManifest === null || headManifest === null)) {
reason = `could not read ${ROOT_MANIFEST} on both sides of the diff`;
} else if (
files.includes(ROOT_MANIFEST) &&
baseManifest !== null &&
headManifest !== null &&
packageManagerChanged(baseManifest, headManifest)
) {
reason = 'the package manager pin changed';
} else {
({ changed: build, reason } = nativePinsChanged(baseText, headText));
const baseWorkspace = fileAt(base, WORKSPACE_FILE);
const headWorkspace = fileAt('HEAD', WORKSPACE_FILE);
if (baseWorkspace === null || headWorkspace === null) {
reason = `could not read ${WORKSPACE_FILE} on both sides of the diff`;
} else {
({ changed: build, reason } = nativePinsChanged(baseWorkspace, headWorkspace));
}
}
}
@@ -1,7 +1,12 @@
import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { buildableDeps, isBumpOnly, nativePinsChanged } from './should-smoke-build.mjs';
import {
buildableDeps,
isBumpOnly,
nativePinsChanged,
packageManagerChanged,
} from './should-smoke-build.mjs';
const workspace = ({ kafka = '1.9.1', vm = '^7.0.0', sentry = '10.70.0', allowVm = true } = {}) => `
catalog:
@@ -86,3 +91,25 @@ describe('nativePinsChanged', () => {
assert.equal(nativePinsChanged(workspace(), workspace()).changed, false);
});
});
describe('packageManagerChanged', () => {
it('detects a pnpm version change', () => {
assert.equal(
packageManagerChanged(
JSON.stringify({ packageManager: '[email protected]' }),
JSON.stringify({ packageManager: '[email protected]' }),
),
true,
);
});
it('ignores unrelated manifest changes', () => {
assert.equal(
packageManagerChanged(
JSON.stringify({ packageManager: '[email protected]', version: '1.0.0' }),
JSON.stringify({ packageManager: '[email protected]', version: '1.0.1' }),
),
false,
);
});
});
+2 -2
View File
@@ -2,9 +2,9 @@
"name": "workflow-scripts",
"engines": {
"node": ">=24.0.0",
"pnpm": ">=11.25.0"
"pnpm": ">=12.3.4"
},
"packageManager": "pnpm@11.25.0",
"packageManager": "pnpm@12.3.4",
"scripts": {
"test": "node --test --experimental-test-module-mocks ./*.test.mjs ./docker/*.test.mjs ./owners/*.test.mjs ./quality/*.test.mjs ./slack/*.test.mjs ./stale/*.test.mjs ../../scripts/*.test.mjs ../../scripts/licenses/*.test.mjs ../../scripts/mutation-health/*.test.mjs ../../.devcontainer/codespaces/*.test.mjs",
"generate-sbom": "FETCH_LICENSE=true cdxgen -t pnpm --no-install-deps --profile license-compliance --spec-version 1.6 -o ../../sbom-source.cdx.json ../../compiled/",
+101
View File
@@ -1,3 +1,104 @@
---
lockfileVersion: '9.0'
importers:
.:
configDependencies: {}
packageManagerDependencies:
pnpm:
specifier: 12.3.4
version: 12.3.4
packages:
'@pnpm/[email protected]':
resolution: {integrity: sha512-PAyUol8T1+/+ViOiXAt51ECA+QnfXCqz6foL4bW+LsoX0NcVd5XVEM2mRQu+LV4oc7uRz9zf9U0P+XFfuQeDAw==}
cpu: [arm64]
os: [darwin]
'@pnpm/[email protected]':
resolution: {integrity: sha512-fxP9JCk0Cdye+ePuj+GJJLMUMTqHGWRdb1dtv4How876uQ2ehxvenpgiYAir/ceO9PsYUZkFTtyZdx+rRu5QOA==}
cpu: [x64]
os: [darwin]
'@pnpm/[email protected]':
resolution: {integrity: sha512-FBOt0/7ye6O6q4AllVV5QMviB6qE6fqkeczV/+MDWQsmo+QJrlfsh6X7CpH/tClVpBZEyIbjpUoT8bNhCYBxEg==}
cpu: [arm64]
os: [linux]
libc: [musl]
'@pnpm/[email protected]':
resolution: {integrity: sha512-t71AVA7LRqiKTyZ5xMYaZc2n5DfdpMbfokZuiIOXHBOM03ECnF0t4iYwaBDqJgVjlKYUOwaF/bRQajGNA4cJ4w==}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@pnpm/[email protected]':
resolution: {integrity: sha512-RPmk7Jb/aYaFvL2iyDN/AtMY+hUEsue732WmXpcuQ9tBpMnGyA5py7Z3+e+qmQaJ0zY/4ni9jJiyPBQHujmv6w==}
cpu: [x64]
os: [linux]
libc: [musl]
'@pnpm/[email protected]':
resolution: {integrity: sha512-2ZqOlSPkfwX1h5cR+FPiWf8+F+2hZT/3TvhUK5sigHqwaQCIiq8R7CGxhndKs63JtcLi2a1Qpo+wX/EoyfjyJQ==}
cpu: [x64]
os: [linux]
libc: [glibc]
'@pnpm/[email protected]':
resolution: {integrity: sha512-ANyrHqyqco6SXBysUTRF74itDyyraea7IbFsKFdNXTjcFnfycTDx37EwuhdpPYFNSIh2JhUG4fByclsRfiHX7w==}
cpu: [arm64]
os: [win32]
'@pnpm/[email protected]':
resolution: {integrity: sha512-WH/KqBPY/hq2Tb7SgQltEZytimcjgKRaCRL/aM9CI0c67iKc5TVmHUhIiL3Ux9FB4bWn36i6XewUcScQI+zG8w==}
cpu: [x64]
os: [win32]
[email protected]:
resolution: {integrity: sha512-lhqkH7B32joEpEHZ+OFevAyW2o73ELLrZ7+e58sGEOq9SPH9hfUc/+c4RnhfoPh8VqOocqHYk/hEZ0G1zORUVw==}
engines: {node: '>=18.*'}
hasBin: true
snapshots:
'@pnpm/[email protected]':
optional: true
'@pnpm/[email protected]':
optional: true
'@pnpm/[email protected]':
optional: true
'@pnpm/[email protected]':
optional: true
'@pnpm/[email protected]':
optional: true
'@pnpm/[email protected]':
optional: true
'@pnpm/[email protected]':
optional: true
'@pnpm/[email protected]':
optional: true
[email protected]:
optionalDependencies:
'@pnpm/exe.darwin-arm64': 12.3.4
'@pnpm/exe.darwin-x64': 12.3.4
'@pnpm/exe.linux-arm64': 12.3.4
'@pnpm/exe.linux-arm64-musl': 12.3.4
'@pnpm/exe.linux-x64': 12.3.4
'@pnpm/exe.linux-x64-musl': 12.3.4
'@pnpm/exe.win32-arm64': 12.3.4
'@pnpm/exe.win32-x64': 12.3.4
---
lockfileVersion: '9.0'
settings:
+1 -1
View File
@@ -46,7 +46,7 @@ jobs:
uses: ./.github/actions/setup-nodejs
with:
build-command: ''
install-command: pnpm install --frozen-lockfile --dir ./.github/scripts --ignore-workspace
install-command: pnpm install --frozen-lockfile --dir ./.github/scripts --lockfile-dir . --ignore-workspace
cache-dependency-path: .github/scripts/pnpm-lock.yaml
- name: Compute backport targets
+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
uses: ./.github/actions/setup-nodejs
with:
build-command: ''
install-command: pnpm install --frozen-lockfile --dir ./.github/scripts --ignore-workspace
install-command: pnpm install --frozen-lockfile --dir ./.github/scripts --lockfile-dir . --ignore-workspace
cache-dependency-path: .github/scripts/pnpm-lock.yaml
- name: Check for new unpublished packages
@@ -54,7 +54,8 @@ jobs:
uses: ./.github/actions/setup-nodejs
with:
build-command: ''
install-command: pnpm install --frozen-lockfile --dir ./.github/scripts --ignore-workspace
install-command: pnpm install --frozen-lockfile --dir ./.github/scripts --lockfile-dir . --ignore-workspace
cache-dependency-path: .github/scripts/pnpm-lock.yaml
- name: Assign reviewer teams
env:
@@ -143,7 +143,10 @@ jobs:
uses: ./.github/actions/setup-nodejs
with:
build-command: ''
install-command: pnpm install --frozen-lockfile --dir ./.github/scripts --ignore-workspace
# pnpm 11 and 12 resolve a relative lockfile directory from different locations.
# Use the checkout path because this workflow can run new workflow code against the old base.
install-command: pnpm install --frozen-lockfile --dir ./.github/scripts --lockfile-dir ${{ github.workspace }}/.github/scripts --ignore-workspace
cache-dependency-path: .github/scripts/pnpm-lock.yaml
- name: Evaluate required reviews
env:
@@ -45,7 +45,8 @@ jobs:
uses: ./.github/actions/setup-nodejs
with:
build-command: ''
install-command: pnpm install --frozen-lockfile --dir ./.github/scripts --ignore-workspace
install-command: pnpm install --frozen-lockfile --dir ./.github/scripts --lockfile-dir . --ignore-workspace
cache-dependency-path: .github/scripts/pnpm-lock.yaml
- name: Post review recommendations
env:
+2 -2
View File
@@ -36,7 +36,7 @@ jobs:
uses: ./.github/actions/setup-nodejs
with:
build-command: ''
install-command: pnpm install --frozen-lockfile --dir ./.github/scripts --ignore-workspace
install-command: pnpm install --frozen-lockfile --dir ./.github/scripts --lockfile-dir . --ignore-workspace
cache-dependency-path: .github/scripts/pnpm-lock.yaml
- name: Re-request PR Size Limit check
@@ -72,7 +72,7 @@ jobs:
uses: ./.github/actions/setup-nodejs
with:
build-command: ''
install-command: pnpm install --frozen-lockfile --dir ./.github/scripts --ignore-workspace
install-command: pnpm install --frozen-lockfile --dir ./.github/scripts --lockfile-dir . --ignore-workspace
cache-dependency-path: .github/scripts/pnpm-lock.yaml
- name: Check PR size
+1 -1
View File
@@ -35,7 +35,7 @@ jobs:
uses: ./.github/actions/setup-nodejs
with:
build-command: ''
install-command: pnpm install --frozen-lockfile --dir ./.github/scripts --ignore-workspace
install-command: pnpm install --frozen-lockfile --dir ./.github/scripts --lockfile-dir . --ignore-workspace
cache-dependency-path: .github/scripts/pnpm-lock.yaml
- name: Clean stale branches
+2
View File
@@ -31,12 +31,14 @@ on:
- 'scripts/build-n8n.mjs'
- 'scripts/dockerize-n8n.mjs'
- 'scripts/smoke-n8n-image.mjs'
- 'package.json'
- '.github/scripts/docker/kafka-native-smoke-check.mjs'
- '.github/scripts/docker/should-smoke-build.mjs'
# These drive the build and the image distribution, so a change here can
# break the chain without touching a Dockerfile.
- '.github/actions/build-n8n-docker/action.yml'
- '.github/actions/load-n8n-docker/action.yml'
- '.github/actions/setup-nodejs/action.yml'
- '.github/workflows/docker-build-push.yml'
- 'patches/**'
# Pins the native deps this build compiles, but changes far more often
@@ -71,7 +71,7 @@ jobs:
uses: ./.github/actions/setup-nodejs
with:
build-command: ''
install-command: pnpm install --frozen-lockfile --dir ./.github/scripts --ignore-workspace
install-command: pnpm install --frozen-lockfile --dir ./.github/scripts --lockfile-dir . --ignore-workspace
cache-dependency-path: .github/scripts/pnpm-lock.yaml
- name: Create GitHub releases
@@ -44,7 +44,7 @@ jobs:
uses: ./.github/actions/setup-nodejs
with:
build-command: ''
install-command: pnpm install --frozen-lockfile --dir ./.github/scripts --ignore-workspace
install-command: pnpm install --frozen-lockfile --dir ./.github/scripts --lockfile-dir . --ignore-workspace
cache-dependency-path: .github/scripts/pnpm-lock.yaml
- name: Determine release candidate branch from track
+1 -1
View File
@@ -78,7 +78,7 @@ jobs:
uses: ./.github/actions/setup-nodejs
with:
build-command: ''
install-command: pnpm install --frozen-lockfile --dir ./.github/scripts --ignore-workspace
install-command: pnpm install --frozen-lockfile --dir ./.github/scripts --lockfile-dir . --ignore-workspace
cache-dependency-path: .github/scripts/pnpm-lock.yaml
- name: Bump package versions
@@ -48,7 +48,7 @@ jobs:
uses: ./.github/actions/setup-nodejs
with:
build-command: ''
install-command: pnpm install --frozen-lockfile --dir ./.github/scripts --ignore-workspace
install-command: pnpm install --frozen-lockfile --dir ./.github/scripts --lockfile-dir . --ignore-workspace
cache-dependency-path: .github/scripts/pnpm-lock.yaml
- name: Extract changes
@@ -52,7 +52,7 @@ jobs:
uses: ./.github/actions/setup-nodejs
with:
build-command: ''
install-command: pnpm install --frozen-lockfile --dir ./.github/scripts --ignore-workspace
install-command: pnpm install --frozen-lockfile --dir ./.github/scripts --lockfile-dir . --ignore-workspace
cache-dependency-path: .github/scripts/pnpm-lock.yaml
- name: Reconcile backport labels
@@ -42,7 +42,7 @@ jobs:
uses: ./.github/actions/setup-nodejs
with:
build-command: ''
install-command: pnpm install --frozen-lockfile --dir ./.github/scripts --ignore-workspace
install-command: pnpm install --frozen-lockfile --dir ./.github/scripts --lockfile-dir . --ignore-workspace
cache-dependency-path: .github/scripts/pnpm-lock.yaml
- name: Promote GitHub releases
+2 -2
View File
@@ -31,7 +31,7 @@ jobs:
uses: ./.github/actions/setup-nodejs
with:
build-command: ''
install-command: pnpm install --frozen-lockfile --dir ./.github/scripts --ignore-workspace
install-command: pnpm install --frozen-lockfile --dir ./.github/scripts --lockfile-dir . --ignore-workspace
cache-dependency-path: .github/scripts/pnpm-lock.yaml
- name: Determine track from package version number
@@ -61,7 +61,7 @@ jobs:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
- name: Install script dependencies
run: pnpm install --frozen-lockfile --dir ./.github/scripts --ignore-workspace
run: pnpm install --frozen-lockfile --dir ./.github/scripts --lockfile-dir . --ignore-workspace
- name: Check for new unpublished packages
run: node .github/scripts/detect-new-packages.mjs
@@ -88,7 +88,7 @@ jobs:
uses: ./.github/actions/setup-nodejs
with:
build-command: ''
install-command: pnpm install --frozen-lockfile --dir ./.github/scripts --ignore-workspace
install-command: pnpm install --frozen-lockfile --dir ./.github/scripts --lockfile-dir . --ignore-workspace
cache-dependency-path: .github/scripts/pnpm-lock.yaml
- name: Bump the root and cli versions
@@ -25,7 +25,7 @@ jobs:
uses: ./.github/actions/setup-nodejs
with:
build-command: ''
install-command: pnpm install --frozen-lockfile --dir ./.github/scripts --ignore-workspace
install-command: pnpm install --frozen-lockfile --dir ./.github/scripts --lockfile-dir . --ignore-workspace
cache-dependency-path: .github/scripts/pnpm-lock.yaml
- name: Set npm packages to latest
@@ -53,7 +53,7 @@ jobs:
uses: ./.github/actions/setup-nodejs
with:
build-command: ''
install-command: pnpm install --frozen-lockfile --dir ./.github/scripts --ignore-workspace
install-command: pnpm install --frozen-lockfile --dir ./.github/scripts --lockfile-dir . --ignore-workspace
cache-dependency-path: .github/scripts/pnpm-lock.yaml
- name: Configure git author
@@ -37,7 +37,7 @@ jobs:
uses: ./.github/actions/setup-nodejs
with:
build-command: ''
install-command: pnpm install --frozen-lockfile --dir ./.github/scripts --ignore-workspace
install-command: pnpm install --frozen-lockfile --dir ./.github/scripts --lockfile-dir . --ignore-workspace
cache-dependency-path: .github/scripts/pnpm-lock.yaml
- name: Send release notification
@@ -30,7 +30,7 @@ jobs:
uses: ./.github/actions/setup-nodejs
with:
build-command: ''
install-command: pnpm install --frozen-lockfile --dir ./.github/scripts --ignore-workspace
install-command: pnpm install --frozen-lockfile --dir ./.github/scripts --lockfile-dir . --ignore-workspace
cache-dependency-path: .github/scripts/pnpm-lock.yaml
- name: Run tests
@@ -30,7 +30,7 @@ jobs:
uses: ./.github/actions/setup-nodejs
with:
build-command: ''
install-command: pnpm install --frozen-lockfile --dir ./.github/scripts --ignore-workspace
install-command: pnpm install --frozen-lockfile --dir ./.github/scripts --lockfile-dir . --ignore-workspace
cache-dependency-path: .github/scripts/pnpm-lock.yaml
- name: Cleanup release branch if PR qualifies
+1 -1
View File
@@ -68,7 +68,7 @@ jobs:
uses: ./.github/actions/setup-nodejs
with:
build-command: ''
install-command: pnpm install --frozen-lockfile --dir ./.github/scripts --ignore-workspace
install-command: pnpm install --frozen-lockfile --dir ./.github/scripts --lockfile-dir . --ignore-workspace
cache-dependency-path: .github/scripts/pnpm-lock.yaml
- name: Run the preview operation
@@ -33,7 +33,7 @@ jobs:
uses: ./.github/actions/setup-nodejs
with:
build-command: ''
install-command: pnpm install --frozen-lockfile --dir ./.github/scripts --ignore-workspace
install-command: pnpm install --frozen-lockfile --dir ./.github/scripts --lockfile-dir . --ignore-workspace
cache-dependency-path: .github/scripts/pnpm-lock.yaml
- name: Extract release versions
@@ -28,7 +28,7 @@ jobs:
uses: ./.github/actions/setup-nodejs
with:
build-command: ''
install-command: pnpm install --frozen-lockfile --dir ./.github/scripts --ignore-workspace
install-command: pnpm install --frozen-lockfile --dir ./.github/scripts --lockfile-dir . --ignore-workspace
cache-dependency-path: .github/scripts/pnpm-lock.yaml
- name: Ensure release-candidate branches
@@ -44,7 +44,7 @@ jobs:
uses: ./.github/actions/setup-nodejs
with:
build-command: ''
install-command: pnpm install --frozen-lockfile --dir ./.github/scripts --ignore-workspace
install-command: pnpm install --frozen-lockfile --dir ./.github/scripts --lockfile-dir . --ignore-workspace
cache-dependency-path: .github/scripts/pnpm-lock.yaml
# Refreshes only. Validation runs on the PR this opens, so a schema change
+1 -1
View File
@@ -73,7 +73,7 @@ jobs:
- name: Setup Node.js
uses: ./.github/actions/setup-nodejs
with:
install-command: pnpm install --frozen-lockfile --dir ./.github/scripts --ignore-workspace
install-command: pnpm install --frozen-lockfile --dir ./.github/scripts --lockfile-dir . --ignore-workspace
cache-dependency-path: .github/scripts/pnpm-lock.yaml
build-command: ''
-1
View File
@@ -1 +0,0 @@
# https://github.com/pnpm/pnpm/issues/7024
+2 -2
View File
@@ -82,7 +82,7 @@ If you already have VS Code and Docker installed, you can click [here](https://v
#### pnpm
[pnpm](https://pnpm.io/) version 10.22 or newer is required for development purposes. We recommend installing it with [corepack](#corepack).
[pnpm](https://pnpm.io/) version 12.3.4 or newer is required for development. We recommend that you install it with [Corepack](#corepack).
##### pnpm workspaces
@@ -96,7 +96,7 @@ We recommend enabling [Node.js corepack](https://nodejs.org/docs/latest-v16.x/ap
```bash
corepack enable
corepack prepare pnpm@10.22.0 --activate
corepack prepare pnpm@12.3.4 --activate
```
**IMPORTANT**: If you have installed Node.js via homebrew, you'll need to run `brew install corepack`, since homebrew explicitly removes `npm` and `corepack` from [the `node` formula](https://github.com/Homebrew/homebrew-core/blob/master/Formula/node.rb#L66).
+6 -9
View File
@@ -1,5 +1,5 @@
ARG NODE_VERSION=26.7.0
ARG PNPM_VERSION=11.25.0
ARG PNPM_VERSION=12.3.4
ARG PYTHON_VERSION=3.13
# ==============================================================================
@@ -30,14 +30,14 @@ RUN node -e "const pkg = require('./package.json'); \
delete pkg.devDependencies; \
require('fs').writeFileSync('./package.json', JSON.stringify(pkg, null, 2));"
# `--config.minimum-release-age=0`: pnpm 11 defaults it to 1440 (24h), which would make
# `--config.minimum-release-age=0`: pnpm defaults it to 1440 (24h), which would make
# this reject any dependency published less than a day ago — e.g. a first-party
# @n8n_io/ai-assistant-sdk bump landed within hours of its npm publish. This step only
# re-resolves an already-vetted, already-pinned dependency set; the real supply-chain age
# policy is enforced by the host install (pnpm-workspace.yaml + SafeChain).
# Install moment (special case for backwards compatibility)
RUN rm -f node_modules/.modules.yaml && \
pnpm add [email protected] --prod --no-lockfile --config.minimum-release-age=0
pnpm add [email protected] --save-prod --no-lockfile --config.minimum-release-age=0
# ==============================================================================
# STAGE 2: Python runner build (@n8n/task-runner-python) with uv
@@ -139,12 +139,9 @@ RUN apk add --no-cache ca-certificates tini libstdc++ libc6-compat && \
pip install --no-cache-dir "pip>=26.0" && \
apk del apk-tools
# Bring pnpm over, to make the image easier to extend. Node no longer bundles
# corepack, and this runtime has no npm, so pnpm is copied in as a package.
# Link the .mjs entry point: the package ships bin/pnpm.cjs without the execute
# bit, so a link to it fails with "Permission denied".
COPY --from=node-alpine /usr/local/lib/node_modules/pnpm /usr/local/lib/node_modules/pnpm
RUN ln -s ../lib/node_modules/pnpm/bin/pnpm.mjs /usr/local/bin/pnpm
# Bring pnpm over, to make the image easier to extend. pnpm 12 installs a
# standalone native executable, so the runtime does not need npm or Corepack.
COPY --from=node-alpine /usr/local/lib/node_modules/pnpm/pnpm /usr/local/bin/pnpm
RUN addgroup -g 1000 -S runner \
&& adduser -u 1000 -S -G runner -h /home/runner -D runner
+3 -3
View File
@@ -13,7 +13,7 @@
# ==============================================================================
ARG NODE_VERSION=26.7.0
ARG PNPM_VERSION=11.25.0
ARG PNPM_VERSION=12.3.4
ARG PYTHON_VERSION=3.13
@@ -45,14 +45,14 @@ RUN node -e "const pkg = require('./package.json'); \
delete pkg.devDependencies; \
require('fs').writeFileSync('./package.json', JSON.stringify(pkg, null, 2));"
# `--config.minimum-release-age=0`: pnpm 11 defaults it to 1440 (24h), which would make
# `--config.minimum-release-age=0`: pnpm defaults it to 1440 (24h), which would make
# this reject any dependency published less than a day ago — e.g. a first-party
# @n8n_io/ai-assistant-sdk bump landed within hours of its npm publish. This step only
# re-resolves an already-vetted, already-pinned dependency set; the real supply-chain age
# policy is enforced by the host install (pnpm-workspace.yaml + SafeChain).
# Install moment by default (special case for n8n cloud)
RUN rm -f node_modules/.modules.yaml && \
pnpm add [email protected] --prod --no-lockfile --config.minimum-release-age=0
pnpm add [email protected] --save-prod --no-lockfile --config.minimum-release-age=0
# Rebuild isolated-vm for the container platform. Install build tools as
# fallback in case prebuild-install cannot find a prebuilt binary.
+20
View File
@@ -1,3 +1,23 @@
output:
- execution_out
- failure
post-merge:
commands:
check-pnpm:
run: sh .githooks/check-pnpm.sh
post-rewrite:
commands:
check-pnpm:
run: sh .githooks/check-pnpm.sh
post-checkout:
commands:
check-pnpm:
# $3 is 1 for a branch checkout, 0 for a file checkout — skip the latter
run: '[ "{3}" = "0" ] || sh .githooks/check-pnpm.sh'
pre-commit:
commands:
biome_check:
+3 -3
View File
@@ -4,9 +4,9 @@
"private": true,
"engines": {
"node": ">=24.0.0",
"pnpm": ">=11.25.0"
"pnpm": ">=12.3.4"
},
"packageManager": "pnpm@11.25.0",
"packageManager": "pnpm@12.3.4",
"scripts": {
"prepare": "node scripts/prepare.mjs",
"preinstall": "node scripts/block-npm-install.js",
@@ -88,7 +88,7 @@
"worker": "./packages/cli/bin/n8n worker",
"dev:computer-use": "pnpm --filter @n8n/computer-use build && node packages/@n8n/computer-use/dist/cli.js serve",
"stop:computer-use": "lsof -ti :7655 | xargs kill 2>/dev/null; echo 'computer-use stopped'",
"install-workflow-scripts": "pnpm install --dir .github/scripts --ignore-workspace"
"install-workflow-scripts": "pnpm install --dir .github/scripts --lockfile-dir . --ignore-workspace"
},
"devDependencies": {
"@babel/preset-env": "^7.26.0",
+1 -1
View File
@@ -7,7 +7,7 @@
"typecheck": "tsc --noEmit",
"build": "tsc -p tsconfig.build.json",
"build:unchecked": "tsc -p tsconfig.build.json --noCheck",
"build:docker": "pnpm build && rimraf compiled && DOCKER_BUILD=true NODE_ENV=production pnpm --filter . --prod --config.inject-workspace-packages=true deploy --no-optional compiled && rimraf compiled/pnpm-lock.yaml compiled/pnpm-workspace.yaml compiled/node_modules/.pnpm-workspace-state-v1.json && docker build -f ../../../docker/images/engine/Dockerfile -t n8n-engine:local .",
"build:docker": "pnpm build && rimraf compiled && DOCKER_BUILD=true NODE_ENV=production pnpm --filter . --prod --config.inject-workspace-packages=true --config.package-import-method=copy deploy --no-optional compiled && rimraf compiled/pnpm-lock.yaml compiled/pnpm-workspace.yaml compiled/node_modules/.pnpm-workspace-state-v1.json && docker build -f ../../../docker/images/engine/Dockerfile -t n8n-engine:local .",
"start": "node dist/serve.js",
"format": "biome format --write src",
"format:check": "biome ci src",
+1 -1
View File
@@ -56,7 +56,7 @@
"rimraf": "catalog:",
"semver": "catalog:",
"@typescript/native": "catalog:typescript-tooling",
"typescript": "catalog:typescript-tooling",
"typescript": "catalog:",
"unplugin-icons": "catalog:frontend",
"vite": "catalog:",
"vite-plugin-singlefile": "catalog:",
@@ -0,0 +1,66 @@
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { parsePnpmLock } from './pnpm-lock-parser.js';
const PROJECT_LOCKFILE = `lockfileVersion: '9.0'
importers:
.:
dependencies:
example:
specifier: ^1.0.0
version: 1.0.0
packages:
[email protected]: {}
`;
describe('parsePnpmLock', () => {
let dir: string;
beforeEach(() => {
dir = mkdtempSync(join(tmpdir(), 'pnpm-lock-parser-'));
});
afterEach(() => rmSync(dir, { recursive: true, force: true }));
function writeLockfile(content: string) {
writeFileSync(join(dir, 'pnpm-lock.yaml'), content);
}
it('parses a single-document lockfile', () => {
writeLockfile(PROJECT_LOCKFILE);
const result = parsePnpmLock(dir);
expect(result.resolvedVersions.get('example')).toEqual(new Set(['1.0.0']));
expect(result.requestedRanges.get('example')).toEqual(new Set(['^1.0.0']));
});
it('uses the project document from a pnpm 12 lockfile', () => {
writeLockfile(`---
lockfileVersion: '9.0'
importers:
.:
packageManagerDependencies:
pnpm:
specifier: 12.3.4
version: 12.3.4
packages:
[email protected]: {}
---
${PROJECT_LOCKFILE}`);
const result = parsePnpmLock(dir);
expect(result.resolvedVersions.get('example')).toEqual(new Set(['1.0.0']));
expect(result.resolvedVersions.has('pnpm')).toBe(false);
expect(result.requestedRanges.get('example')).toEqual(new Set(['^1.0.0']));
});
});
@@ -1,6 +1,6 @@
import * as fs from 'node:fs';
import * as path from 'node:path';
import { parse as parseYaml } from 'yaml';
import { parseAllDocuments } from 'yaml';
export interface LockData {
resolvedVersions: Map<string, Set<string>>;
@@ -23,14 +23,23 @@ interface LockFile {
packages?: Record<string, unknown>;
}
function parseProjectLockfile(content: string): LockFile | null {
const document = parseAllDocuments(content).at(-1);
if (!document) return null;
if (document.errors.length > 0) throw document.errors[0];
const value: unknown = document.toJS();
return typeof value === 'object' && value !== null ? value : null;
}
export function parsePnpmLock(rootDir: string, lockFile = 'pnpm-lock.yaml'): LockData {
const filePath = path.join(rootDir, lockFile);
const empty: LockData = { resolvedVersions: new Map(), requestedRanges: new Map() };
if (!fs.existsSync(filePath)) return empty;
const content = fs.readFileSync(filePath, 'utf-8');
const lock = parseYaml(content) as LockFile | null;
if (!lock || typeof lock !== 'object') return empty;
const lock = parseProjectLockfile(content);
if (!lock) return empty;
const resolvedVersions = new Map<string, Set<string>>();
const requestedRanges = new Map<string, Set<string>>();
@@ -0,0 +1,85 @@
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
const mocks = vi.hoisted(() => ({ getGitRoot: vi.fn<() => string>() }));
vi.mock('../utils/git-operations.js', () => ({ getGitRoot: mocks.getGitRoot }));
import { readLockfileImporters, readRuntimeClosure } from './read-lockfile-importers.js';
describe('lockfile readers', () => {
let dir: string;
beforeEach(() => {
dir = mkdtempSync(join(tmpdir(), 'janitor-lockfile-'));
mocks.getGitRoot.mockReturnValue(dir);
});
afterEach(() => rmSync(dir, { recursive: true, force: true }));
it('uses the project document from a pnpm 12 lockfile', () => {
writeFileSync(
join(dir, 'pnpm-lock.yaml'),
`---
lockfileVersion: '9.0'
importers:
.:
packageManagerDependencies:
pnpm:
specifier: 12.3.4
version: 12.3.4
---
lockfileVersion: '9.0'
importers:
packages/cli:
dependencies:
external:
specifier: 1.0.0
version: 1.0.0
packages/unused:
devDependencies:
test-only:
specifier: 1.0.0
version: 1.0.0
snapshots:
[email protected]:
dependencies:
transitive: 2.0.0
[email protected]: {}
`,
);
expect(readLockfileImporters()).toEqual({
'packages/cli': ['external'],
'packages/unused': [],
});
expect(readRuntimeClosure()).toEqual(new Set(['external', 'transitive']));
});
it('returns fallback empty results when any document has syntax errors', () => {
writeFileSync(
join(dir, 'pnpm-lock.yaml'),
`---
lockfileVersion: '9.0'
malformed: [unclosed
---
lockfileVersion: '9.0'
importers:
packages/cli:
dependencies:
external:
specifier: 1.0.0
version: 1.0.0
`,
);
expect(readLockfileImporters()).toEqual({});
expect(readRuntimeClosure()).toBeUndefined();
});
});
@@ -16,7 +16,7 @@ import {
} from '@n8n/test-impact';
import { existsSync, readFileSync } from 'node:fs';
import { join } from 'node:path';
import { parse } from 'yaml';
import { parseAllDocuments } from 'yaml';
import { getGitRoot } from '../utils/git-operations.js';
@@ -37,7 +37,16 @@ function readLockfile(): Lockfile | undefined {
const lockPath = join(getGitRoot(process.cwd()), 'pnpm-lock.yaml');
if (!existsSync(lockPath)) return undefined;
try {
return parse(readFileSync(lockPath, 'utf8')) as Lockfile;
const documents = parseAllDocuments(readFileSync(lockPath, 'utf8'));
if (documents.length === 0 || documents.some((doc) => doc.errors.length > 0)) {
return undefined;
}
const document = documents.at(-1);
if (!document) return undefined;
const value: unknown = document.toJS();
return typeof value === 'object' && value !== null ? value : undefined;
} catch {
return undefined;
}
@@ -69,7 +69,7 @@ if (BACKEND_URL && !SKIP_WEB_SERVER) {
typeof envUserFolder === 'string' ? envUserFolder : USER_FOLDER;
}
webServer.push({
command: 'cd .. && pnpm start',
command: 'pnpm --dir ../../.. start',
url: `${BACKEND_URL}/favicon.ico`,
timeout: 30000,
reuseExistingServer: IS_DEV ? false : true,
+7 -7
View File
@@ -3,10 +3,10 @@ index 6ee7fa8ca1fc6daf7322b5278becb4e38c55eef8..7ebf0a67338ae7635a13d73952e7b4fa
--- a/package.json
+++ b/package.json
@@ -7,6 +7,7 @@
"module": "./dist/esm/index.js",
"exports": {
".": {
+ "types": "./dist/types/index.d.ts",
"import": "./dist/esm/index.js",
"require": "./dist/cjs/index.js"
},
"module": "./dist/esm/index.js",
"exports": {
".": {
+ "types": "./dist/types/index.d.ts",
"import": "./dist/esm/index.js",
"require": "./dist/cjs/index.js"
},
+111 -15
View File
@@ -1,3 +1,104 @@
---
lockfileVersion: '9.0'
importers:
.:
configDependencies: {}
packageManagerDependencies:
pnpm:
specifier: 12.3.4
version: 12.3.4
packages:
'@pnpm/[email protected]':
resolution: {integrity: sha512-PAyUol8T1+/+ViOiXAt51ECA+QnfXCqz6foL4bW+LsoX0NcVd5XVEM2mRQu+LV4oc7uRz9zf9U0P+XFfuQeDAw==}
cpu: [arm64]
os: [darwin]
'@pnpm/[email protected]':
resolution: {integrity: sha512-fxP9JCk0Cdye+ePuj+GJJLMUMTqHGWRdb1dtv4How876uQ2ehxvenpgiYAir/ceO9PsYUZkFTtyZdx+rRu5QOA==}
cpu: [x64]
os: [darwin]
'@pnpm/[email protected]':
resolution: {integrity: sha512-FBOt0/7ye6O6q4AllVV5QMviB6qE6fqkeczV/+MDWQsmo+QJrlfsh6X7CpH/tClVpBZEyIbjpUoT8bNhCYBxEg==}
cpu: [arm64]
os: [linux]
libc: [musl]
'@pnpm/[email protected]':
resolution: {integrity: sha512-t71AVA7LRqiKTyZ5xMYaZc2n5DfdpMbfokZuiIOXHBOM03ECnF0t4iYwaBDqJgVjlKYUOwaF/bRQajGNA4cJ4w==}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@pnpm/[email protected]':
resolution: {integrity: sha512-RPmk7Jb/aYaFvL2iyDN/AtMY+hUEsue732WmXpcuQ9tBpMnGyA5py7Z3+e+qmQaJ0zY/4ni9jJiyPBQHujmv6w==}
cpu: [x64]
os: [linux]
libc: [musl]
'@pnpm/[email protected]':
resolution: {integrity: sha512-2ZqOlSPkfwX1h5cR+FPiWf8+F+2hZT/3TvhUK5sigHqwaQCIiq8R7CGxhndKs63JtcLi2a1Qpo+wX/EoyfjyJQ==}
cpu: [x64]
os: [linux]
libc: [glibc]
'@pnpm/[email protected]':
resolution: {integrity: sha512-ANyrHqyqco6SXBysUTRF74itDyyraea7IbFsKFdNXTjcFnfycTDx37EwuhdpPYFNSIh2JhUG4fByclsRfiHX7w==}
cpu: [arm64]
os: [win32]
'@pnpm/[email protected]':
resolution: {integrity: sha512-WH/KqBPY/hq2Tb7SgQltEZytimcjgKRaCRL/aM9CI0c67iKc5TVmHUhIiL3Ux9FB4bWn36i6XewUcScQI+zG8w==}
cpu: [x64]
os: [win32]
[email protected]:
resolution: {integrity: sha512-lhqkH7B32joEpEHZ+OFevAyW2o73ELLrZ7+e58sGEOq9SPH9hfUc/+c4RnhfoPh8VqOocqHYk/hEZ0G1zORUVw==}
engines: {node: '>=18.*'}
hasBin: true
snapshots:
'@pnpm/[email protected]':
optional: true
'@pnpm/[email protected]':
optional: true
'@pnpm/[email protected]':
optional: true
'@pnpm/[email protected]':
optional: true
'@pnpm/[email protected]':
optional: true
'@pnpm/[email protected]':
optional: true
'@pnpm/[email protected]':
optional: true
'@pnpm/[email protected]':
optional: true
[email protected]:
optionalDependencies:
'@pnpm/exe.darwin-arm64': 12.3.4
'@pnpm/exe.darwin-x64': 12.3.4
'@pnpm/exe.linux-arm64': 12.3.4
'@pnpm/exe.linux-arm64-musl': 12.3.4
'@pnpm/exe.linux-x64': 12.3.4
'@pnpm/exe.linux-x64-musl': 12.3.4
'@pnpm/exe.win32-arm64': 12.3.4
'@pnpm/exe.win32-x64': 12.3.4
---
lockfileVersion: '9.0'
settings:
@@ -2884,10 +2985,10 @@ importers:
version: link:../utils
vue:
specifier: catalog:frontend
version: 3.5.26(@typescript/typescript6@6.0.2)
version: 3.5.26([email protected])
vue-i18n:
specifier: catalog:frontend
version: 11.4.5([email protected](@typescript/typescript6@6.0.2))
version: 11.4.5([email protected]([email protected]))
zod:
specifier: 3.25.76
version: 3.25.76
@@ -2915,7 +3016,7 @@ importers:
version: [email protected]
'@vitejs/plugin-vue':
specifier: catalog:frontend
version: 6.0.8([email protected](@emnapi/[email protected])(@emnapi/[email protected])(@types/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected])([email protected])([email protected]))([email protected](@typescript/typescript6@6.0.2))
version: 6.0.8([email protected](@emnapi/[email protected])(@emnapi/[email protected])(@types/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected])([email protected])([email protected]))([email protected]([email protected]))
'@vue/test-utils':
specifier: catalog:frontend
version: 2.4.6
@@ -2926,8 +3027,8 @@ importers:
specifier: 7.7.3
version: 7.7.3
typescript:
specifier: catalog:typescript-tooling
version: '@typescript/[email protected]'
specifier: 'catalog:'
version: 6.0.2
unplugin-icons:
specifier: catalog:frontend
version: 23.0.1(@vue/[email protected])
@@ -2939,13 +3040,13 @@ importers:
version: 2.3.3([email protected](@emnapi/[email protected])(@emnapi/[email protected])(@types/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected])([email protected])([email protected]))
vite-svg-loader:
specifier: catalog:frontend
version: 5.1.0([email protected](@typescript/typescript6@6.0.2))
version: 5.1.0([email protected]([email protected]))
vitest:
specifier: 'catalog:'
version: 4.1.9(@opentelemetry/[email protected])(@types/[email protected])(@vitest/[email protected])(@vitest/[email protected])([email protected]([email protected])([email protected])([email protected]))([email protected](@emnapi/[email protected])(@emnapi/[email protected])(@types/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected])([email protected])([email protected]))
vue-tsc:
specifier: ^2.2.8
version: 2.2.8(patch_hash=e2aee939ccac8a57fe449bfd92bedd8117841579526217bc39aca26c6b8c317f)(@typescript/typescript6@6.0.2)
version: 2.2.8(patch_hash=e2aee939ccac8a57fe449bfd92bedd8117841579526217bc39aca26c6b8c317f)([email protected])
packages/@n8n/mcp-browser:
dependencies:
@@ -34611,6 +34712,7 @@ snapshots:
vscode-uri: 3.0.8
optionalDependencies:
typescript: '@typescript/[email protected]'
optional: true
'@volar/[email protected]([email protected])':
dependencies:
@@ -34764,6 +34866,7 @@ snapshots:
path-browserify: 1.0.1
optionalDependencies:
typescript: '@typescript/[email protected]'
optional: true
'@vue/[email protected]([email protected])':
dependencies:
@@ -46116,14 +46219,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
[email protected]([email protected](@typescript/[email protected])):
dependencies:
'@intlify/core-base': 11.4.5
'@intlify/devtools-types': 11.4.5
'@intlify/shared': 11.4.5
'@vue/devtools-api': 6.6.4
vue: 3.5.26(@typescript/[email protected])
[email protected]([email protected]([email protected])):
dependencies:
'@intlify/core-base': 11.4.5
@@ -46168,6 +46263,7 @@ snapshots:
'@volar/typescript': 2.4.28(@typescript/[email protected])
'@vue/language-core': 2.2.8(@typescript/[email protected])
typescript: '@typescript/[email protected]'
optional: true
[email protected](patch_hash=e2aee939ccac8a57fe449bfd92bedd8117841579526217bc39aca26c6b8c317f)([email protected]):
dependencies:
+2 -2
View File
@@ -5,7 +5,7 @@ autoInstallPeers: true
linkWorkspacePackages: deep
hoist: true
hoistWorkspacePackages: false
# pnpm 11 hoists TS7 into node_modules/.pnpm/node_modules, where it shadows the root's real
# pnpm hoists TS7 into node_modules/.pnpm/node_modules, where it shadows the root's real
# TS6 and breaks vue-tsc — it needs `typescript/lib/_tsc`, which TS7 and the
# `@typescript/typescript6` alias both lack — in packages on the typescript-tooling catalog.
hoistPattern:
@@ -13,7 +13,7 @@ hoistPattern:
- '!typescript'
allowUnusedPatches: true
# The docker build trims frontend manifests and deploys with --prod before later
# scripts run; pnpm 11's pre-run install would reinstall production-only and prune
# scripts run; pnpm's pre-run install would reinstall production-only and prune
# the devDependencies those scripts import.
verifyDepsBeforeRun: false
+19 -4
View File
@@ -95,7 +95,8 @@ startTimer('package_build');
echo(chalk.yellow('INFO: Running pnpm install and build...'));
try {
const installProcess = $`cd ${config.rootDir} && pnpm install --frozen-lockfile`;
const installFlags = ['--frozen-lockfile', ...(isCI ? ['--trust-lockfile'] : [])];
const installProcess = $`cd ${config.rootDir} && pnpm install ${installFlags}`;
installProcess.pipe(process.stdout);
await installProcess;
@@ -191,7 +192,7 @@ try {
// top level, so cdxgen would miss the transitive tree (the manifest would be incomplete).
// Re-enable hoisting for the licenses build only — shipped images keep the non-hoisted
// layout, since regular builds leave N8N_GENERATE_LICENSES unset.
// `PNPM_CONFIG_*` and not `npm_config_*`: pnpm 11 no longer reads npm-style env config,
// `PNPM_CONFIG_*` and not `npm_config_*`: pnpm no longer reads npm-style env config,
// so an `npm_config_` name here is silently ignored and the SBOM comes out incomplete.
const generateLicenses = process.env.N8N_GENERATE_LICENSES === 'true';
if (generateLicenses) {
@@ -206,7 +207,13 @@ try {
// The closure is lockfile-derived, so a workspace package only stays out of the image by
// declaring build-time deps as devDependencies — editing manifests at build time does
// nothing here.
const deployFlags = ['--prod', '--config.inject-workspace-packages=true'];
// pnpm 12's clone path leaves its intermediate links tree in macOS deploy outputs.
// Linux already falls back to copying, so make deploy output consistent across platforms.
const deployFlags = [
'--prod',
'--config.inject-workspace-packages=true',
'--config.package-import-method=copy',
];
// The dedicated-lockfile deploy leaves pnpm's own files in the closure, which the
// legacy deploy did not write. They are not needed to run the closure, they point at
@@ -221,8 +228,15 @@ try {
const removePnpmDeployFiles = async (dir) => {
for (const file of pnpmDeployFiles) await fs.remove(path.join(dir, file));
};
const verifyNoPnpmIntermediateLinks = async (dir) => {
const linksDir = path.join(dir, 'node_modules/.pnpm/@');
if (await fs.pathExists(linksDir)) {
throw new Error(`pnpm left its intermediate links tree in ${linksDir}`);
}
};
await $`cd ${config.rootDir} && NODE_ENV=production DOCKER_BUILD=true pnpm --filter=n8n ${deployFlags} deploy --no-optional ./compiled`;
await verifyNoPnpmIntermediateLinks(config.compiledAppDir);
await removePnpmDeployFiles(config.compiledAppDir);
// Strip test/example/benchmark dirs shipped inside production deps that lack a
@@ -451,6 +465,7 @@ try {
// only, not the shipped total.
startTimer('task_runner_deploy');
await $`cd ${config.rootDir} && NODE_ENV=production DOCKER_BUILD=true pnpm --filter=@n8n/task-runner ${deployFlags} deploy --no-optional ${config.compiledTaskRunnerDir}`;
await verifyNoPnpmIntermediateLinks(config.compiledTaskRunnerDir);
echo(
chalk.green(
`✅ Task runner deployment completed in ${formatDuration(getElapsedTime('task_runner_deploy'))}`,
@@ -554,7 +569,7 @@ try {
echo(chalk.yellow('INFO: Generating SBOM and rendering THIRD_PARTY_LICENSES.md...'));
try {
const toolingDir = path.join(config.rootDir, '.github', 'scripts');
await $`cd ${config.rootDir} && pnpm install --frozen-lockfile --dir .github/scripts --ignore-workspace`;
await $`cd ${config.rootDir} && pnpm install --frozen-lockfile --dir .github/scripts --lockfile-dir . --ignore-workspace`;
const generateProcess = $`cd ${toolingDir} && pnpm generate-licenses`;
generateProcess.pipe(process.stdout);
await generateProcess;
+21 -1
View File
@@ -137,6 +137,9 @@ const KAFKA_CHECK = path.resolve(
path.dirname(fileURLToPath(import.meta.url)),
'../.github/scripts/docker/kafka-native-smoke-check.mjs',
);
const EXPECTED_PNPM_VERSION = (await fs.readJson(
path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../package.json'),
)).packageManager.replace('pnpm@', '');
async function runKafkaBindingCheck() {
const name = 'kafka native binding loads in image';
@@ -172,6 +175,22 @@ async function runRunnersInterpreterCheck(image) {
}
}
async function runRunnersPnpmCheck(image) {
const name = `pnpm ${EXPECTED_PNPM_VERSION} runs offline in ${image}`;
try {
const { stdout } = await $({
timeout: TIMEOUT,
})`docker run --rm --network none --entrypoint pnpm ${image} --version`;
if (stdout.trim() !== EXPECTED_PNPM_VERSION) {
throw new Error(`expected ${EXPECTED_PNPM_VERSION}, got ${stdout.trim()}`);
}
echo(chalk.green(`✓ ${name}`));
return true;
} catch (err) {
return reportFailure(name, err);
}
}
// Derived images add packages with `pnpm add` in the JS runner directory (docs:
// "Adding extra dependencies"). pnpm prunes packages the recorded importer no
// longer needs, so a deploy that records the closure differently makes this
@@ -186,7 +205,7 @@ async function runRunnersExtensionCheck(image) {
const script = [
`cd ${RUNNER_JS_DIR}`,
'before=$(ls node_modules/.pnpm | wc -l)',
'pnpm add uuid --prod --no-lockfile --config.minimum-release-age=0',
'pnpm add uuid --save-prod --no-lockfile --config.minimum-release-age=0',
'after=$(ls node_modules/.pnpm | wc -l)',
'[ "$after" -gt "$before" ] || { echo "pnpm add pruned the closure: $before -> $after packages"; exit 1; }',
`node -e "require('uuid'); require('moment'); require.resolve('n8n-core')"`,
@@ -241,6 +260,7 @@ const ok = (
runKafkaBindingCheck(),
...RUNNERS_IMAGES.map(runRunnersInterpreterCheck),
// The distroless image ships no shell or pnpm; only the standard image is extendable.
...RUNNERS_IMAGES.filter((image) => !image.endsWith('-distroless')).map(runRunnersPnpmCheck),
...RUNNERS_IMAGES.filter((image) => !image.endsWith('-distroless')).map(
runRunnersExtensionCheck,
),