diff --git a/packages/cli/src/services/__tests__/community-packages.service.test.ts b/packages/cli/src/services/__tests__/community-packages.service.test.ts index 4d3218c7b0..f7eb4f4ef0 100644 --- a/packages/cli/src/services/__tests__/community-packages.service.test.ts +++ b/packages/cli/src/services/__tests__/community-packages.service.test.ts @@ -3,7 +3,7 @@ import { InstalledNodes } from '@n8n/db'; import { InstalledPackages } from '@n8n/db'; import axios from 'axios'; import { exec } from 'child_process'; -import { access as fsAccess, mkdir as fsMkdir } from 'fs/promises'; +import { mkdir as fsMkdir } from 'fs/promises'; import { mocked } from 'jest-mock'; import { mock } from 'jest-mock-extended'; import type { PackageDirectoryLoader } from 'n8n-core'; @@ -128,7 +128,6 @@ describe('CommunityPackagesService', () => { describe('executeCommand()', () => { beforeEach(() => { - mocked(fsAccess).mockReset(); mocked(fsMkdir).mockReset(); mocked(exec).mockReset(); }); @@ -147,31 +146,17 @@ describe('CommunityPackagesService', () => { await communityPackagesService.executeNpmCommand('ls'); - expect(fsAccess).toHaveBeenCalled(); + expect(fsMkdir).toHaveBeenCalled(); expect(exec).toHaveBeenCalled(); - expect(fsMkdir).toBeCalledTimes(0); }); test('should make sure folder exists', async () => { mocked(exec).mockImplementation(execMock); await communityPackagesService.executeNpmCommand('ls'); - expect(fsAccess).toHaveBeenCalled(); - expect(exec).toHaveBeenCalled(); - expect(fsMkdir).toBeCalledTimes(0); - }); - test('should try to create folder if it does not exist', async () => { - mocked(exec).mockImplementation(execMock); - mocked(fsAccess).mockImplementation(() => { - throw new Error('Folder does not exist.'); - }); - - await communityPackagesService.executeNpmCommand('ls'); - - expect(fsAccess).toHaveBeenCalled(); - expect(exec).toHaveBeenCalled(); expect(fsMkdir).toHaveBeenCalled(); + expect(exec).toHaveBeenCalled(); }); test('should throw especial error when package is not found', async () => { @@ -187,9 +172,8 @@ describe('CommunityPackagesService', () => { await expect(call).rejects.toThrowError(RESPONSE_ERROR_MESSAGES.PACKAGE_NOT_FOUND); - expect(fsAccess).toHaveBeenCalled(); + expect(fsMkdir).toHaveBeenCalled(); expect(exec).toHaveBeenCalled(); - expect(fsMkdir).toHaveBeenCalledTimes(0); }); }); @@ -406,7 +390,7 @@ describe('CommunityPackagesService', () => { expect(exec).toHaveBeenCalledTimes(1); expect(exec).toHaveBeenNthCalledWith( 1, - `npm install ${installedPackage.packageName}@latest --registry=some.random.host`, + `npm install ${installedPackage.packageName}@latest --audit=false --fund=false --bin-links=false --install-strategy=shallow --omit=dev --omit=optional --omit=peer --registry=some.random.host`, expect.any(Object), expect.any(Function), ); diff --git a/packages/cli/src/services/community-packages.service.ts b/packages/cli/src/services/community-packages.service.ts index 25a13db5e5..19f5504d1a 100644 --- a/packages/cli/src/services/community-packages.service.ts +++ b/packages/cli/src/services/community-packages.service.ts @@ -4,7 +4,7 @@ import type { InstalledPackages } from '@n8n/db'; import { Service } from '@n8n/di'; import axios from 'axios'; import { exec } from 'child_process'; -import { access as fsAccess, mkdir as fsMkdir } from 'fs/promises'; +import { mkdir as fsMkdir } from 'fs/promises'; import type { PackageDirectoryLoader } from 'n8n-core'; import { InstanceSettings, Logger } from 'n8n-core'; import { UnexpectedError, UserError, type PublicInstalledPackage } from 'n8n-workflow'; @@ -26,6 +26,14 @@ import { Publisher } from '@/scaling/pubsub/publisher.service'; import { toError } from '@/utils'; const DEFAULT_REGISTRY = 'https://registry.npmjs.org'; +const NPM_COMMON_ARGS = ['--audit=false', '--fund=false']; +const NPM_INSTALL_ARGS = [ + '--bin-links=false', + '--install-strategy=shallow', + '--omit=dev', + '--omit=optional', + '--omit=peer', +]; const { PACKAGE_NAME_NOT_PROVIDED, @@ -134,17 +142,11 @@ export class CommunityPackagesService { NODE_PATH: process.env.NODE_PATH, PATH: process.env.PATH, APPDATA: process.env.APPDATA, + NODE_ENV: 'production', }, }; - try { - await fsAccess(downloadFolder); - } catch { - await fsMkdir(downloadFolder); - // Also init the folder since some versions - // of npm complain if the folder is empty - await asyncExec('npm init -y', execOptions); - } + await fsMkdir(downloadFolder, { recursive: true }); try { const commandResult = await asyncExec(command, execOptions); @@ -326,12 +328,12 @@ export class CommunityPackagesService { }); } - private getNpmRegistry() { + private getNpmInstallArgs() { const { registry } = this.globalConfig.nodes.communityPackages; if (registry !== DEFAULT_REGISTRY && !this.license.isCustomNpmRegistryEnabled()) { throw new FeatureNotLicensedError(LICENSE_FEATURES.COMMUNITY_NODES_CUSTOM_REGISTRY); } - return registry; + return [...NPM_COMMON_ARGS, ...NPM_INSTALL_ARGS, `--registry=${registry}`].join(' '); } private async installOrUpdatePackage( @@ -340,7 +342,7 @@ export class CommunityPackagesService { ) { const isUpdate = 'installedPackage' in options; const packageVersion = isUpdate || !options.version ? 'latest' : options.version; - const command = `npm install ${packageName}@${packageVersion} --registry=${this.getNpmRegistry()}`; + const command = `npm install ${packageName}@${packageVersion} ${this.getNpmInstallArgs()}`; try { await this.executeNpmCommand(command); @@ -394,7 +396,7 @@ export class CommunityPackagesService { async installOrUpdateNpmPackage(packageName: string, packageVersion: string) { await this.executeNpmCommand( - `npm install ${packageName}@${packageVersion} --registry=${this.getNpmRegistry()}`, + `npm install ${packageName}@${packageVersion} ${this.getNpmInstallArgs()}`, ); await this.loadNodesAndCredentials.loadPackage(packageName); await this.loadNodesAndCredentials.postProcessLoaders();