From 4584f22a9b16883779d8555cda309fd8bd113f6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A4=95=E0=A4=BE=E0=A4=B0=E0=A4=A4=E0=A5=8B=E0=A4=AB?= =?UTF-8?q?=E0=A5=8D=E0=A4=AB=E0=A5=87=E0=A4=B2=E0=A4=B8=E0=A5=8D=E0=A4=95?= =?UTF-8?q?=E0=A5=8D=E0=A4=B0=E0=A4=BF=E0=A4=AA=E0=A5=8D=E0=A4=9F=E2=84=A2?= Date: Thu, 26 Sep 2024 20:28:57 +0200 Subject: [PATCH] fix(core): Prevent backend from loading duplicate copies of nodes packages (#10979) --- .../cli/src/load-nodes-and-credentials.ts | 9 +++++++ packages/core/src/DirectoryLoader.ts | 27 ++++++++++--------- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/packages/cli/src/load-nodes-and-credentials.ts b/packages/cli/src/load-nodes-and-credentials.ts index e2daaa0e76..871913afba 100644 --- a/packages/cli/src/load-nodes-and-credentials.ts +++ b/packages/cli/src/load-nodes-and-credentials.ts @@ -18,6 +18,7 @@ import type { } from 'n8n-workflow'; import { NodeHelpers, ApplicationError, ErrorReporterProxy as ErrorReporter } from 'n8n-workflow'; import path from 'path'; +import picocolors from 'picocolors'; import { Container, Service } from 'typedi'; import { @@ -146,6 +147,7 @@ export class LoadNodesAndCredentials { path.join(nodeModulesDir, packagePath), ); } catch (error) { + this.logger.error((error as Error).message); ErrorReporter.error(error); } } @@ -258,6 +260,13 @@ export class LoadNodesAndCredentials { dir: string, ) { const loader = new constructor(dir, this.excludeNodes, this.includeNodes); + if (loader.packageName in this.loaders) { + throw new ApplicationError( + picocolors.red( + `nodes package ${loader.packageName} is already loaded.\n Please delete this second copy at path ${dir}`, + ), + ); + } await loader.loadAll(); this.loaders[loader.packageName] = loader; return loader; diff --git a/packages/core/src/DirectoryLoader.ts b/packages/core/src/DirectoryLoader.ts index 717edd5359..cef3db4068 100644 --- a/packages/core/src/DirectoryLoader.ts +++ b/packages/core/src/DirectoryLoader.ts @@ -1,5 +1,6 @@ import glob from 'fast-glob'; -import { readFile } from 'fs/promises'; +import { readFileSync } from 'node:fs'; +import { readFile } from 'node:fs/promises'; import type { CodexData, DocumentationLink, @@ -350,18 +351,11 @@ export class CustomDirectoryLoader extends DirectoryLoader { * e.g. /nodes-base or community packages. */ export class PackageDirectoryLoader extends DirectoryLoader { - packageName = ''; + packageJson: n8n.PackageJson = this.readJSONSync('package.json'); - packageJson!: n8n.PackageJson; - - async readPackageJson() { - this.packageJson = await this.readJSON('package.json'); - this.packageName = this.packageJson.name; - } + packageName = this.packageJson.name; override async loadAll() { - await this.readPackageJson(); - const { n8n } = this.packageJson; if (!n8n) return; @@ -391,6 +385,17 @@ export class PackageDirectoryLoader extends DirectoryLoader { }); } + protected readJSONSync(file: string): T { + const filePath = this.resolvePath(file); + const fileString = readFileSync(filePath, 'utf8'); + + try { + return jsonParse(fileString); + } catch (error) { + throw new ApplicationError('Failed to parse JSON', { extra: { filePath } }); + } + } + protected async readJSON(file: string): Promise { const filePath = this.resolvePath(file); const fileString = await readFile(filePath, 'utf8'); @@ -408,8 +413,6 @@ export class PackageDirectoryLoader extends DirectoryLoader { */ export class LazyPackageDirectoryLoader extends PackageDirectoryLoader { override async loadAll() { - await this.readPackageJson(); - try { const knownNodes: typeof this.known.nodes = await this.readJSON('dist/known/nodes.json'); for (const nodeName in knownNodes) {