UNPKG

n8n

Version:

n8n Workflow Automation Tool

330 lines 15.7 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.CommunityPackagesService = void 0; const config_1 = require("@n8n/config"); const di_1 = require("@n8n/di"); const axios_1 = __importDefault(require("axios")); const child_process_1 = require("child_process"); const promises_1 = require("fs/promises"); const n8n_core_1 = require("n8n-core"); const n8n_workflow_1 = require("n8n-workflow"); const util_1 = require("util"); const constants_1 = require("../constants"); const installed_packages_repository_1 = require("../databases/repositories/installed-packages.repository"); const feature_not_licensed_error_1 = require("../errors/feature-not-licensed.error"); const license_1 = require("../license"); const load_nodes_and_credentials_1 = require("../load-nodes-and-credentials"); const publisher_service_1 = require("../scaling/pubsub/publisher.service"); const utils_1 = require("../utils"); const DEFAULT_REGISTRY = 'https://registry.npmjs.org'; const { PACKAGE_NAME_NOT_PROVIDED, DISK_IS_FULL, PACKAGE_FAILED_TO_INSTALL, PACKAGE_VERSION_NOT_FOUND, PACKAGE_NOT_FOUND, } = constants_1.RESPONSE_ERROR_MESSAGES; const { NPM_PACKAGE_NOT_FOUND_ERROR, NPM_NO_VERSION_AVAILABLE, NPM_DISK_NO_SPACE, NPM_DISK_INSUFFICIENT_SPACE, NPM_PACKAGE_VERSION_NOT_FOUND_ERROR, } = constants_1.NPM_COMMAND_TOKENS; const asyncExec = (0, util_1.promisify)(child_process_1.exec); const INVALID_OR_SUSPICIOUS_PACKAGE_NAME = /[^0-9a-z@\-./]/; let CommunityPackagesService = class CommunityPackagesService { constructor(instanceSettings, logger, installedPackageRepository, loadNodesAndCredentials, publisher, license, globalConfig) { this.instanceSettings = instanceSettings; this.logger = logger; this.installedPackageRepository = installedPackageRepository; this.loadNodesAndCredentials = loadNodesAndCredentials; this.publisher = publisher; this.license = license; this.globalConfig = globalConfig; this.reinstallMissingPackages = false; this.missingPackages = []; } get hasMissingPackages() { return this.missingPackages.length > 0; } async findInstalledPackage(packageName) { return await this.installedPackageRepository.findOne({ where: { packageName }, relations: ['installedNodes'], }); } async isPackageInstalled(packageName) { return await this.installedPackageRepository.exist({ where: { packageName } }); } async getAllInstalledPackages() { return await this.installedPackageRepository.find({ relations: ['installedNodes'] }); } async removePackageFromDatabase(packageName) { return await this.installedPackageRepository.remove(packageName); } async persistInstalledPackage(packageLoader) { try { return await this.installedPackageRepository.saveInstalledPackageWithNodes(packageLoader); } catch (maybeError) { const error = (0, utils_1.toError)(maybeError); this.logger.error('Failed to save installed packages and nodes', { error, packageName: packageLoader.packageJson.name, }); throw error; } } parseNpmPackageName(rawString) { if (!rawString) throw new n8n_workflow_1.UnexpectedError(PACKAGE_NAME_NOT_PROVIDED); if (INVALID_OR_SUSPICIOUS_PACKAGE_NAME.test(rawString)) { throw new n8n_workflow_1.UnexpectedError('Package name must be a single word'); } const scope = rawString.includes('/') ? rawString.split('/')[0] : undefined; const packageNameWithoutScope = scope ? rawString.replace(`${scope}/`, '') : rawString; if (!packageNameWithoutScope.startsWith(constants_1.NODE_PACKAGE_PREFIX)) { throw new n8n_workflow_1.UnexpectedError(`Package name must start with ${constants_1.NODE_PACKAGE_PREFIX}`); } const version = packageNameWithoutScope.includes('@') ? packageNameWithoutScope.split('@')[1] : undefined; const packageName = version ? rawString.replace(`@${version}`, '') : rawString; return { packageName, scope, version, rawString }; } async executeNpmCommand(command, options) { const downloadFolder = this.instanceSettings.nodesDownloadDir; const execOptions = { cwd: downloadFolder, env: { NODE_PATH: process.env.NODE_PATH, PATH: process.env.PATH, APPDATA: process.env.APPDATA, }, }; try { await (0, promises_1.access)(downloadFolder); } catch { await (0, promises_1.mkdir)(downloadFolder); await asyncExec('npm init -y', execOptions); } try { const commandResult = await asyncExec(command, execOptions); return commandResult.stdout; } catch (error) { if (options?.doNotHandleError) throw error; const errorMessage = error instanceof Error ? error.message : constants_1.UNKNOWN_FAILURE_REASON; const map = { [NPM_PACKAGE_NOT_FOUND_ERROR]: PACKAGE_NOT_FOUND, [NPM_NO_VERSION_AVAILABLE]: PACKAGE_NOT_FOUND, [NPM_PACKAGE_VERSION_NOT_FOUND_ERROR]: PACKAGE_VERSION_NOT_FOUND, [NPM_DISK_NO_SPACE]: DISK_IS_FULL, [NPM_DISK_INSUFFICIENT_SPACE]: DISK_IS_FULL, }; Object.entries(map).forEach(([npmMessage, n8nMessage]) => { if (errorMessage.includes(npmMessage)) throw new n8n_workflow_1.UnexpectedError(n8nMessage); }); this.logger.warn('npm command failed', { errorMessage }); throw new n8n_workflow_1.UnexpectedError(PACKAGE_FAILED_TO_INSTALL); } } matchPackagesWithUpdates(packages, updates) { if (!updates) return packages; return packages.reduce((acc, cur) => { const publicPackage = { ...cur }; const update = updates[cur.packageName]; if (update) publicPackage.updateAvailable = update.latest; acc.push(publicPackage); return acc; }, []); } matchMissingPackages(installedPackages) { const missingPackagesList = this.missingPackages .map((name) => { try { const parsedPackageData = this.parseNpmPackageName(name); return parsedPackageData.packageName; } catch { return; } }) .filter((i) => i !== undefined); const hydratedPackageList = []; installedPackages.forEach((installedPackage) => { const hydratedInstalledPackage = { ...installedPackage }; if (missingPackagesList.includes(hydratedInstalledPackage.packageName)) { hydratedInstalledPackage.failedLoading = true; } hydratedPackageList.push(hydratedInstalledPackage); }); return hydratedPackageList; } async checkNpmPackageStatus(packageName) { const N8N_BACKEND_SERVICE_URL = 'https://api.n8n.io/api/package'; try { const response = await axios_1.default.post(N8N_BACKEND_SERVICE_URL, { name: packageName }, { method: 'POST' }); if (response.data.status !== constants_1.NPM_PACKAGE_STATUS_GOOD) return response.data; } catch { } return { status: constants_1.NPM_PACKAGE_STATUS_GOOD }; } hasPackageLoaded(packageName) { if (!this.missingPackages.length) return true; return !this.missingPackages.some((packageNameAndVersion) => packageNameAndVersion.startsWith(packageName) && packageNameAndVersion.replace(packageName, '').startsWith('@')); } removePackageFromMissingList(packageName) { try { this.missingPackages = this.missingPackages.filter((packageNameAndVersion) => !packageNameAndVersion.startsWith(packageName) || !packageNameAndVersion.replace(packageName, '').startsWith('@')); } catch { } } async checkForMissingPackages() { const installedPackages = await this.getAllInstalledPackages(); const missingPackages = new Set(); installedPackages.forEach((installedPackage) => { installedPackage.installedNodes.forEach((installedNode) => { if (!this.loadNodesAndCredentials.isKnownNode(installedNode.type)) { missingPackages.add({ packageName: installedPackage.packageName, version: installedPackage.installedVersion, }); } }); }); this.missingPackages = []; if (missingPackages.size === 0) return; const { reinstallMissing } = this.globalConfig.nodes.communityPackages; if (reinstallMissing) { this.logger.info('Attempting to reinstall missing packages', { missingPackages }); try { for (const missingPackage of missingPackages) { await this.installPackage(missingPackage.packageName, missingPackage.version); missingPackages.delete(missingPackage); } this.logger.info('Packages reinstalled successfully. Resuming regular initialization.'); await this.loadNodesAndCredentials.postProcessLoaders(); } catch (error) { this.logger.error('n8n was unable to install the missing packages.'); } } else { this.logger.warn('n8n detected that some packages are missing. For more information, visit https://docs.n8n.io/integrations/community-nodes/troubleshooting/'); } this.missingPackages = [...missingPackages].map((missingPackage) => `${missingPackage.packageName}@${missingPackage.version}`); } async installPackage(packageName, version) { return await this.installOrUpdatePackage(packageName, { version }); } async updatePackage(packageName, installedPackage) { return await this.installOrUpdatePackage(packageName, { installedPackage }); } async removePackage(packageName, installedPackage) { await this.removeNpmPackage(packageName); await this.removePackageFromDatabase(installedPackage); void this.publisher.publishCommand({ command: 'community-package-uninstall', payload: { packageName }, }); } getNpmRegistry() { const { registry } = this.globalConfig.nodes.communityPackages; if (registry !== DEFAULT_REGISTRY && !this.license.isCustomNpmRegistryEnabled()) { throw new feature_not_licensed_error_1.FeatureNotLicensedError(constants_1.LICENSE_FEATURES.COMMUNITY_NODES_CUSTOM_REGISTRY); } return registry; } async installOrUpdatePackage(packageName, options) { const isUpdate = 'installedPackage' in options; const packageVersion = isUpdate || !options.version ? 'latest' : options.version; const command = `npm install ${packageName}@${packageVersion} --registry=${this.getNpmRegistry()}`; try { await this.executeNpmCommand(command); } catch (error) { if (error instanceof Error && error.message === constants_1.RESPONSE_ERROR_MESSAGES.PACKAGE_NOT_FOUND) { throw new n8n_workflow_1.UserError('npm package not found', { extra: { packageName } }); } throw error; } let loader; try { await this.loadNodesAndCredentials.unloadPackage(packageName); loader = await this.loadNodesAndCredentials.loadPackage(packageName); } catch (error) { try { await this.executeNpmCommand(`npm remove ${packageName}`); } catch { } throw new n8n_workflow_1.UnexpectedError(constants_1.RESPONSE_ERROR_MESSAGES.PACKAGE_LOADING_FAILED, { cause: error }); } if (loader.loadedNodes.length > 0) { try { if (isUpdate) { await this.removePackageFromDatabase(options.installedPackage); } const installedPackage = await this.persistInstalledPackage(loader); void this.publisher.publishCommand({ command: isUpdate ? 'community-package-update' : 'community-package-install', payload: { packageName, packageVersion }, }); await this.loadNodesAndCredentials.postProcessLoaders(); this.logger.info(`Community package installed: ${packageName}`); return installedPackage; } catch (error) { throw new n8n_workflow_1.UnexpectedError('Failed to save installed package', { extra: { packageName }, cause: error, }); } } else { try { await this.executeNpmCommand(`npm remove ${packageName}`); } catch { } throw new n8n_workflow_1.UnexpectedError(constants_1.RESPONSE_ERROR_MESSAGES.PACKAGE_DOES_NOT_CONTAIN_NODES); } } async installOrUpdateNpmPackage(packageName, packageVersion) { await this.executeNpmCommand(`npm install ${packageName}@${packageVersion} --registry=${this.getNpmRegistry()}`); await this.loadNodesAndCredentials.loadPackage(packageName); await this.loadNodesAndCredentials.postProcessLoaders(); this.logger.info(`Community package installed: ${packageName}`); } async removeNpmPackage(packageName) { await this.executeNpmCommand(`npm remove ${packageName}`); await this.loadNodesAndCredentials.unloadPackage(packageName); await this.loadNodesAndCredentials.postProcessLoaders(); this.logger.info(`Community package uninstalled: ${packageName}`); } }; exports.CommunityPackagesService = CommunityPackagesService; exports.CommunityPackagesService = CommunityPackagesService = __decorate([ (0, di_1.Service)(), __metadata("design:paramtypes", [n8n_core_1.InstanceSettings, n8n_core_1.Logger, installed_packages_repository_1.InstalledPackagesRepository, load_nodes_and_credentials_1.LoadNodesAndCredentials, publisher_service_1.Publisher, license_1.License, config_1.GlobalConfig]) ], CommunityPackagesService); //# sourceMappingURL=community-packages.service.js.map