mirror of
https://github.com/n8n-io/n8n.git
synced 2026-05-12 08:00:27 +02:00
chore(core): Add @n8n/engine HTTP server and harness (no-changelog) (#29913)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
8a6e779c6d
commit
cd5b2b3762
|
|
@ -38,3 +38,4 @@
|
|||
!packages/@n8n/benchmark/**
|
||||
!packages/@n8n/typescript-config
|
||||
!packages/@n8n/typescript-config/**
|
||||
|
||||
|
|
|
|||
20
docker/images/engine/Dockerfile
Normal file
20
docker/images/engine/Dockerfile
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
ARG NODE_VERSION=24.14.1
|
||||
|
||||
FROM node:${NODE_VERSION}-alpine3.22
|
||||
|
||||
ENV NODE_ENV=production
|
||||
|
||||
RUN apk add --no-cache tini
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# `compiled/` is produced by `pnpm build:docker`. It's a `pnpm deploy --prod`
|
||||
# output containing package.json, dist/, and a node_modules with only
|
||||
# production dependencies — no devDeps, no workspace bloat.
|
||||
COPY --chown=node:node ./compiled /app
|
||||
|
||||
USER node
|
||||
EXPOSE 3000
|
||||
|
||||
ENTRYPOINT ["tini", "--"]
|
||||
CMD ["node", "dist/serve.js"]
|
||||
12
packages/@n8n/config/src/configs/engine.config.ts
Normal file
12
packages/@n8n/config/src/configs/engine.config.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import { Config, Env } from '../decorators';
|
||||
|
||||
@Config
|
||||
export class EngineConfig {
|
||||
/** Port the engine HTTP server listens on. */
|
||||
@Env('N8N_ENGINE_PORT')
|
||||
port: number = 3000;
|
||||
|
||||
/** Host interface the engine HTTP server binds to. */
|
||||
@Env('N8N_ENGINE_HOST')
|
||||
host: string = '0.0.0.0';
|
||||
}
|
||||
|
|
@ -61,6 +61,7 @@ export {
|
|||
SsrfProtectionConfig,
|
||||
SSRF_DEFAULT_BLOCKED_IP_RANGES,
|
||||
} from './configs/ssrf-protection.config';
|
||||
export { EngineConfig } from './configs/engine.config';
|
||||
export { ExecutionsConfig } from './configs/executions.config';
|
||||
export { LOG_SCOPES } from './configs/logging.config';
|
||||
export type { LogScope } from './configs/logging.config';
|
||||
|
|
|
|||
3
packages/@n8n/engine/.dockerignore
Normal file
3
packages/@n8n/engine/.dockerignore
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
*
|
||||
!compiled
|
||||
!compiled/**
|
||||
|
|
@ -1,4 +1,7 @@
|
|||
import { defineConfig } from 'eslint/config';
|
||||
import { nodeConfig } from '@n8n/eslint-config/node';
|
||||
|
||||
export default defineConfig(nodeConfig);
|
||||
export default defineConfig(
|
||||
{ ignores: ['compiled/**', 'vitest.integration.config.ts'] },
|
||||
nodeConfig,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -3,15 +3,18 @@
|
|||
"version": "0.1.0",
|
||||
"description": "n8n workflow execution engine (v2)",
|
||||
"scripts": {
|
||||
"clean": "rimraf dist .turbo",
|
||||
"clean": "rimraf dist .turbo compiled",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"build": "tsc -p tsconfig.build.json",
|
||||
"build:docker": "pnpm build && rimraf compiled && DOCKER_BUILD=true NODE_ENV=production pnpm --filter . --prod --legacy deploy --no-optional compiled && 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",
|
||||
"lint": "eslint . --quiet",
|
||||
"lint:fix": "eslint . --fix",
|
||||
"test": "vitest run --passWithNoTests",
|
||||
"test:dev": "vitest --watch",
|
||||
"test:integration": "vitest run --config vitest.integration.config.ts",
|
||||
"watch": "tsc -p tsconfig.build.json --watch"
|
||||
},
|
||||
"main": "dist/index.js",
|
||||
|
|
@ -19,10 +22,18 @@
|
|||
"files": [
|
||||
"dist/**/*"
|
||||
],
|
||||
"dependencies": {},
|
||||
"dependencies": {
|
||||
"@n8n/config": "workspace:*",
|
||||
"@n8n/di": "workspace:*",
|
||||
"express": "5.1.0",
|
||||
"reflect-metadata": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@n8n/typescript-config": "workspace:*",
|
||||
"@n8n/vitest-config": "workspace:*",
|
||||
"@types/express": "catalog:",
|
||||
"@types/supertest": "^6.0.3",
|
||||
"supertest": "^7.1.1",
|
||||
"vitest": "catalog:"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1 @@
|
|||
// Public API of @n8n/engine.
|
||||
//
|
||||
// Intentionally empty for now. The StartExecution API surface and core engine
|
||||
// interfaces will land in subsequent CAT-2859 sub-tickets.
|
||||
export {};
|
||||
export { createEngineServer } from './server';
|
||||
|
|
|
|||
29
packages/@n8n/engine/src/serve.ts
Normal file
29
packages/@n8n/engine/src/serve.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import { EngineConfig } from '@n8n/config';
|
||||
import { Container } from '@n8n/di';
|
||||
|
||||
import { createEngineServer } from './server';
|
||||
|
||||
const config = Container.get(EngineConfig);
|
||||
|
||||
const { app } = createEngineServer();
|
||||
|
||||
const server = app.listen(config.port, config.host, () => {
|
||||
console.log(`engine: listening on http://${config.host}:${config.port}`);
|
||||
});
|
||||
|
||||
let shuttingDown = false;
|
||||
const shutdown = (signal: string): void => {
|
||||
if (shuttingDown) return;
|
||||
shuttingDown = true;
|
||||
console.log(`engine: received ${signal}, shutting down`);
|
||||
server.close((error) => {
|
||||
if (error) {
|
||||
console.error('engine: error during shutdown', error);
|
||||
process.exit(1);
|
||||
}
|
||||
process.exit(0);
|
||||
});
|
||||
};
|
||||
|
||||
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
||||
process.on('SIGINT', () => shutdown('SIGINT'));
|
||||
11
packages/@n8n/engine/src/server/create-engine-server.ts
Normal file
11
packages/@n8n/engine/src/server/create-engine-server.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import express, { type Application } from 'express';
|
||||
|
||||
export function createEngineServer(): { app: Application } {
|
||||
const app = express();
|
||||
|
||||
app.get('/healthz', (_req, res) => {
|
||||
res.status(200).json({ status: 'ok' });
|
||||
});
|
||||
|
||||
return { app };
|
||||
}
|
||||
1
packages/@n8n/engine/src/server/index.ts
Normal file
1
packages/@n8n/engine/src/server/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { createEngineServer } from './create-engine-server';
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
import request from 'supertest';
|
||||
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
||||
|
||||
import { startEngineServer } from '../start-engine-server';
|
||||
|
||||
describe('engine HTTP server (e2e)', () => {
|
||||
let url: string;
|
||||
let stop: () => Promise<void>;
|
||||
|
||||
beforeAll(async () => {
|
||||
({ url, stop } = await startEngineServer());
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await stop();
|
||||
});
|
||||
|
||||
it('responds to GET /healthz with { status: "ok" }', async () => {
|
||||
const response = await request(url).get('/healthz');
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body).toEqual({ status: 'ok' });
|
||||
});
|
||||
});
|
||||
1
packages/@n8n/engine/src/testing/index.ts
Normal file
1
packages/@n8n/engine/src/testing/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { startEngineServer } from './start-engine-server';
|
||||
33
packages/@n8n/engine/src/testing/start-engine-server.ts
Normal file
33
packages/@n8n/engine/src/testing/start-engine-server.ts
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import type { Server } from 'node:http';
|
||||
|
||||
import { createEngineServer } from '../server';
|
||||
|
||||
export async function startEngineServer(): Promise<{
|
||||
url: string;
|
||||
stop: () => Promise<void>;
|
||||
}> {
|
||||
const { app } = createEngineServer();
|
||||
|
||||
const server = await new Promise<Server>((resolve, reject) => {
|
||||
const s = app.listen(0, '127.0.0.1', () => resolve(s));
|
||||
s.on('error', reject);
|
||||
});
|
||||
|
||||
const address = server.address();
|
||||
if (address === null || typeof address === 'string') {
|
||||
throw new Error('Engine server address is not a TCP socket');
|
||||
}
|
||||
|
||||
const url = `http://127.0.0.1:${address.port}`;
|
||||
|
||||
const stop = async (): Promise<void> => {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
server.close((error) => {
|
||||
if (error) reject(error);
|
||||
else resolve();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return { url, stop };
|
||||
}
|
||||
|
|
@ -7,7 +7,9 @@
|
|||
"target": "es2023",
|
||||
"lib": ["es2023"],
|
||||
"types": ["node"],
|
||||
"tsBuildInfoFile": "dist/typecheck.tsbuildinfo"
|
||||
"tsBuildInfoFile": "dist/typecheck.tsbuildinfo",
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true
|
||||
},
|
||||
"include": ["src/**/*.ts"]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { createVitestConfig } from '@n8n/vitest-config/node';
|
||||
|
||||
export default createVitestConfig();
|
||||
export default createVitestConfig({
|
||||
exclude: ['**/node_modules/**', '**/dist/**', '**/*.integration.test.ts'],
|
||||
});
|
||||
|
|
|
|||
5
packages/@n8n/engine/vitest.integration.config.ts
Normal file
5
packages/@n8n/engine/vitest.integration.config.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import { createVitestConfig } from '@n8n/vitest-config/node';
|
||||
|
||||
export default createVitestConfig({
|
||||
include: ['**/*.integration.test.ts'],
|
||||
});
|
||||
|
|
@ -1399,6 +1399,19 @@ importers:
|
|||
version: link:../typescript-config
|
||||
|
||||
packages/@n8n/engine:
|
||||
dependencies:
|
||||
'@n8n/config':
|
||||
specifier: workspace:*
|
||||
version: link:../config
|
||||
'@n8n/di':
|
||||
specifier: workspace:*
|
||||
version: link:../di
|
||||
express:
|
||||
specifier: 5.1.0
|
||||
version: 5.1.0
|
||||
reflect-metadata:
|
||||
specifier: 'catalog:'
|
||||
version: 0.2.2
|
||||
devDependencies:
|
||||
'@n8n/typescript-config':
|
||||
specifier: workspace:*
|
||||
|
|
@ -1406,6 +1419,15 @@ importers:
|
|||
'@n8n/vitest-config':
|
||||
specifier: workspace:*
|
||||
version: link:../vitest-config
|
||||
'@types/express':
|
||||
specifier: 'catalog:'
|
||||
version: 5.0.1
|
||||
'@types/supertest':
|
||||
specifier: ^6.0.3
|
||||
version: 6.0.3
|
||||
supertest:
|
||||
specifier: ^7.1.1
|
||||
version: 7.1.1
|
||||
vitest:
|
||||
specifier: 'catalog:'
|
||||
version: 4.1.1(@opentelemetry/api@1.9.0)(@types/node@20.19.21)(@vitest/browser-playwright@4.0.16)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@types/node@20.19.21)(esbuild@0.25.10)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3))
|
||||
|
|
@ -17883,10 +17905,6 @@ packages:
|
|||
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
mime-types@3.0.1:
|
||||
resolution: {integrity: sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
mime-types@3.0.2:
|
||||
resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==}
|
||||
engines: {node: '>=18'}
|
||||
|
|
@ -33948,7 +33966,7 @@ snapshots:
|
|||
bytes: 3.1.2
|
||||
content-type: 1.0.5
|
||||
debug: 4.4.3(supports-color@8.1.1)
|
||||
http-errors: 2.0.0
|
||||
http-errors: 2.0.1
|
||||
iconv-lite: 0.7.2
|
||||
on-finished: 2.4.1
|
||||
qs: 6.14.2
|
||||
|
|
@ -36543,15 +36561,15 @@ snapshots:
|
|||
content-type: 1.0.5
|
||||
cookie: 0.7.2
|
||||
cookie-signature: 1.2.2
|
||||
debug: 4.4.1(supports-color@8.1.1)
|
||||
debug: 4.4.3(supports-color@8.1.1)
|
||||
encodeurl: 2.0.0
|
||||
escape-html: 1.0.3
|
||||
etag: 1.8.1
|
||||
finalhandler: 2.1.0
|
||||
fresh: 2.0.0
|
||||
http-errors: 2.0.0
|
||||
http-errors: 2.0.1
|
||||
merge-descriptors: 2.0.0
|
||||
mime-types: 3.0.1
|
||||
mime-types: 3.0.2
|
||||
on-finished: 2.4.1
|
||||
once: 1.4.0
|
||||
parseurl: 1.3.3
|
||||
|
|
@ -36561,7 +36579,7 @@ snapshots:
|
|||
router: 2.2.0
|
||||
send: 1.2.0
|
||||
serve-static: 2.2.0
|
||||
statuses: 2.0.1
|
||||
statuses: 2.0.2
|
||||
type-is: 2.0.1
|
||||
vary: 1.1.2
|
||||
transitivePeerDependencies:
|
||||
|
|
@ -36802,7 +36820,7 @@ snapshots:
|
|||
escape-html: 1.0.3
|
||||
on-finished: 2.4.1
|
||||
parseurl: 1.3.3
|
||||
statuses: 2.0.1
|
||||
statuses: 2.0.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
|
|
@ -40077,10 +40095,6 @@ snapshots:
|
|||
dependencies:
|
||||
mime-db: 1.52.0
|
||||
|
||||
mime-types@3.0.1:
|
||||
dependencies:
|
||||
mime-db: 1.54.0
|
||||
|
||||
mime-types@3.0.2:
|
||||
dependencies:
|
||||
mime-db: 1.54.0
|
||||
|
|
@ -43187,12 +43201,12 @@ snapshots:
|
|||
escape-html: 1.0.3
|
||||
etag: 1.8.1
|
||||
fresh: 2.0.0
|
||||
http-errors: 2.0.0
|
||||
http-errors: 2.0.1
|
||||
mime-types: 3.0.2
|
||||
ms: 2.1.3
|
||||
on-finished: 2.4.1
|
||||
range-parser: 1.2.1
|
||||
statuses: 2.0.1
|
||||
statuses: 2.0.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user