Skip to content

Commit

Permalink
Merge branch 'master' into make-new-configs-consistent
Browse files Browse the repository at this point in the history
  • Loading branch information
ivov committed Aug 14, 2024
2 parents 28a4f50 + 33a2703 commit bbbc765
Show file tree
Hide file tree
Showing 12 changed files with 254 additions and 217 deletions.
4 changes: 4 additions & 0 deletions packages/@n8n/config/src/configs/nodes.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ class CommunityPackagesConfig {
@Env('N8N_COMMUNITY_PACKAGES_ENABLED')
enabled: boolean = true;

/** NPM registry URL to pull community packages from */
@Env('N8N_COMMUNITY_PACKAGES_REGISTRY')
registry: string = 'https://registry.npmjs.org';

/** Whether to reinstall any missing community packages */
@Env('N8N_REINSTALL_MISSING_PACKAGES')
reinstallMissing: boolean = false;
Expand Down
1 change: 1 addition & 0 deletions packages/@n8n/config/test/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ describe('GlobalConfig', () => {
nodes: {
communityPackages: {
enabled: true,
registry: 'https://registry.npmjs.org',
reinstallMissing: false,
},
errorTriggerType: 'n8n-nodes-base.errorTrigger',
Expand Down
4 changes: 2 additions & 2 deletions packages/@n8n/nodes-langchain/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@
"@pinecone-database/pinecone": "3.0.0",
"@qdrant/js-client-rest": "1.9.0",
"@supabase/supabase-js": "2.43.4",
"@types/pg": "^8.11.3",
"@types/pg": "^8.11.6",
"@xata.io/client": "0.28.4",
"basic-auth": "catalog:",
"cheerio": "1.0.0-rc.12",
Expand All @@ -176,7 +176,7 @@
"n8n-workflow": "workspace:*",
"openai": "4.53.0",
"pdf-parse": "1.1.1",
"pg": "8.11.3",
"pg": "8.12.0",
"redis": "4.6.12",
"sqlite3": "5.1.7",
"temp": "0.9.4",
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@
"ldapts": "4.2.6",
"lodash": "catalog:",
"luxon": "catalog:",
"mysql2": "3.10.0",
"mysql2": "3.11.0",
"n8n-core": "workspace:*",
"n8n-editor-ui": "workspace:*",
"n8n-nodes-base": "workspace:*",
Expand All @@ -144,7 +144,7 @@
"otpauth": "9.1.1",
"p-cancelable": "2.1.1",
"p-lazy": "3.1.0",
"pg": "8.11.3",
"pg": "8.12.0",
"picocolors": "1.0.1",
"pkce-challenge": "3.0.0",
"posthog-node": "3.2.1",
Expand Down
4 changes: 4 additions & 0 deletions packages/cli/src/License.ts
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,10 @@ export class License {
return this.isFeatureEnabled(LICENSE_FEATURES.PROJECT_ROLE_VIEWER);
}

isCustomNpmRegistryEnabled() {
return this.isFeatureEnabled(LICENSE_FEATURES.COMMUNITY_NODES_CUSTOM_REGISTRY);
}

getCurrentEntitlements() {
return this.manager?.getCurrentEntitlements() ?? [];
}
Expand Down
1 change: 1 addition & 0 deletions packages/cli/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ export const LICENSE_FEATURES = {
PROJECT_ROLE_ADMIN: 'feat:projectRole:admin',
PROJECT_ROLE_EDITOR: 'feat:projectRole:editor',
PROJECT_ROLE_VIEWER: 'feat:projectRole:viewer',
COMMUNITY_NODES_CUSTOM_REGISTRY: 'feat:communityNodes:customRegistry',
} as const;

export const LICENSE_QUOTAS = {
Expand Down
1 change: 1 addition & 0 deletions packages/cli/src/controllers/e2e.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ export class E2EController {
[LICENSE_FEATURES.PROJECT_ROLE_ADMIN]: false,
[LICENSE_FEATURES.PROJECT_ROLE_EDITOR]: false,
[LICENSE_FEATURES.PROJECT_ROLE_VIEWER]: false,
[LICENSE_FEATURES.COMMUNITY_NODES_CUSTOM_REGISTRY]: false,
};

private numericFeatures: Record<NumericLicenseFeature, number> = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { InstalledNodesRepository } from '@db/repositories/installedNodes.reposi
import { InstalledPackagesRepository } from '@db/repositories/installedPackages.repository';
import { InstalledNodes } from '@db/entities/InstalledNodes';
import type { LoadNodesAndCredentials } from '@/LoadNodesAndCredentials';
import type { License } from '@/License';

import { mockInstance } from '@test/mocking';
import { COMMUNITY_NODE_VERSION, COMMUNITY_PACKAGE_VERSION } from '@test-integration/constants';
Expand All @@ -39,19 +40,20 @@ const execMock = ((...args) => {
}) as typeof exec;

describe('CommunityPackagesService', () => {
const license = mock<License>();
const globalConfig = mock<GlobalConfig>({
nodes: {
communityPackages: {
reinstallMissing: false,
registry: 'some.random.host',
},
},
});
const loadNodesAndCredentials = mock<LoadNodesAndCredentials>();

const nodeName = randomName();
const installedNodesRepository = mockInstance(InstalledNodesRepository);
installedNodesRepository.create.mockImplementation(() => {
const nodeName = randomName();

return Object.assign(new InstalledNodes(), {
name: nodeName,
type: nodeName,
Expand All @@ -74,6 +76,7 @@ describe('CommunityPackagesService', () => {
mock(),
loadNodesAndCredentials,
mock(),
license,
globalConfig,
);

Expand Down Expand Up @@ -374,25 +377,23 @@ describe('CommunityPackagesService', () => {
};

describe('updateNpmModule', () => {
const packageDirectoryLoader = mock<PackageDirectoryLoader>();
const installedPackage = mock<InstalledPackages>({ packageName: mockPackageName() });
const packageDirectoryLoader = mock<PackageDirectoryLoader>({
loadedNodes: [{ name: nodeName, version: 1 }],
});

beforeEach(async () => {
jest.clearAllMocks();

loadNodesAndCredentials.loadPackage.mockResolvedValue(packageDirectoryLoader);
mocked(exec).mockImplementation(execMock);
});

test('should call `exec` with the correct command ', async () => {
test('should call `exec` with the correct command and registry', async () => {
//
// ARRANGE
//
const nodeName = randomName();
packageDirectoryLoader.loadedNodes = [{ name: nodeName, version: 1 }];

const installedPackage = new InstalledPackages();
installedPackage.packageName = mockPackageName();

mocked(exec).mockImplementation(execMock);
license.isCustomNpmRegistryEnabled.mockReturnValue(true);

//
// ACT
Expand All @@ -406,10 +407,32 @@ describe('CommunityPackagesService', () => {
expect(exec).toHaveBeenCalledTimes(1);
expect(exec).toHaveBeenNthCalledWith(
1,
`npm install ${installedPackage.packageName}@latest`,
`npm install ${installedPackage.packageName}@latest --registry=some.random.host`,
expect.any(Object),
expect.any(Function),
);
});

test('should throw when not licensed', async () => {
//
// ARRANGE
//
license.isCustomNpmRegistryEnabled.mockReturnValue(false);

//
// ACT
//
const promise = communityPackagesService.updatePackage(
installedPackage.packageName,
installedPackage,
);

//
// ASSERT
//
await expect(promise).rejects.toThrow(
'Your license does not allow for feat:communityNodes:customRegistry.',
);
});
});
});
29 changes: 22 additions & 7 deletions packages/cli/src/services/communityPackages.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,21 @@ import { toError } from '@/utils';
import { InstalledPackagesRepository } from '@db/repositories/installedPackages.repository';
import type { InstalledPackages } from '@db/entities/InstalledPackages';
import {
LICENSE_FEATURES,
NODE_PACKAGE_PREFIX,
NPM_COMMAND_TOKENS,
NPM_PACKAGE_STATUS_GOOD,
RESPONSE_ERROR_MESSAGES,
UNKNOWN_FAILURE_REASON,
} from '@/constants';
import { FeatureNotLicensedError } from '@/errors/feature-not-licensed.error';
import type { CommunityPackages } from '@/Interfaces';
import { LoadNodesAndCredentials } from '@/LoadNodesAndCredentials';
import { Logger } from '@/Logger';
import { OrchestrationService } from './orchestration.service';
import { License } from '@/License';

const DEFAULT_REGISTRY = 'https://registry.npmjs.org';

const {
PACKAGE_NAME_NOT_PROVIDED,
Expand Down Expand Up @@ -57,10 +62,9 @@ export class CommunityPackagesService {
private readonly installedPackageRepository: InstalledPackagesRepository,
private readonly loadNodesAndCredentials: LoadNodesAndCredentials,
private readonly orchestrationService: OrchestrationService,
globalConfig: GlobalConfig,
) {
this.reinstallMissingPackages = globalConfig.nodes.communityPackages.reinstallMissing;
}
private readonly license: License,
private readonly globalConfig: GlobalConfig,
) {}

get hasMissingPackages() {
return this.missingPackages.length > 0;
Expand Down Expand Up @@ -279,7 +283,8 @@ export class CommunityPackagesService {

if (missingPackages.size === 0) return;

if (this.reinstallMissingPackages) {
const { reinstallMissing } = this.globalConfig.nodes.communityPackages;
if (reinstallMissing) {
this.logger.info('Attempting to reinstall missing packages', { missingPackages });
try {
// Optimistic approach - stop if any installation fails
Expand Down Expand Up @@ -321,13 +326,21 @@ export class CommunityPackagesService {
await this.orchestrationService.publish('community-package-uninstall', { packageName });
}

private getNpmRegistry() {
const { registry } = this.globalConfig.nodes.communityPackages;
if (registry !== DEFAULT_REGISTRY && !this.license.isCustomNpmRegistryEnabled()) {
throw new FeatureNotLicensedError(LICENSE_FEATURES.COMMUNITY_NODES_CUSTOM_REGISTRY);
}
return registry;
}

private async installOrUpdatePackage(
packageName: string,
options: { version?: string } | { installedPackage: InstalledPackages },
) {
const isUpdate = 'installedPackage' in options;
const packageVersion = isUpdate || !options.version ? 'latest' : options.version;
const command = `npm install ${packageName}@${packageVersion}`;
const command = `npm install ${packageName}@${packageVersion} --registry=${this.getNpmRegistry()}`;

try {
await this.executeNpmCommand(command);
Expand Down Expand Up @@ -379,7 +392,9 @@ export class CommunityPackagesService {
}

async installOrUpdateNpmPackage(packageName: string, packageVersion: string) {
await this.executeNpmCommand(`npm install ${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}`);
Expand Down
19 changes: 18 additions & 1 deletion packages/editor-ui/src/views/NodeView.v2.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
onMounted,
ref,
useCssModule,
watch,
} from 'vue';
import { useRoute, useRouter } from 'vue-router';
import WorkflowCanvas from '@/components/canvas/WorkflowCanvas.vue';
Expand Down Expand Up @@ -288,6 +289,7 @@ async function initializeRoute() {
nodeHelpers.updateNodesParameterIssues();
await loadCredentials();
await initializeDebugMode();
}
async function initializeWorkspaceForNewWorkflow() {
Expand All @@ -306,7 +308,6 @@ async function initializeWorkspaceForExistingWorkflow(id: string) {
const workflowData = await workflowsStore.fetchWorkflow(id);
await openWorkflow(workflowData);
await initializeDebugMode();
if (workflowData.meta?.onboardingId) {
trackOpenWorkflowFromOnboardingTemplate();
Expand Down Expand Up @@ -748,6 +749,7 @@ async function importWorkflowExact({ workflow: workflowData }: { workflow: IWork
resetWorkspace();
await initializeData();
await initializeWorkspace({
...workflowData,
nodes: NodeViewUtils.getFixedNodesList<INodeUi>(workflowData.nodes),
Expand Down Expand Up @@ -1370,6 +1372,21 @@ function registerCustomActions() {
// });
}
/**
* Routing
*/
watch(
() => route.name,
async () => {
if (!checkIfEditingIsAllowed()) {
return;
}
await initializeRoute();
},
);
/**
* Lifecycle
*/
Expand Down
6 changes: 3 additions & 3 deletions packages/nodes-base/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -866,15 +866,15 @@
"mongodb": "6.3.0",
"mqtt": "5.7.2",
"mssql": "10.0.2",
"mysql2": "3.10.0",
"mysql2": "3.11.0",
"n8n-workflow": "workspace:*",
"node-html-markdown": "1.2.0",
"node-ssh": "13.2.0",
"nodemailer": "6.9.9",
"otpauth": "9.1.1",
"pdfjs-dist": "2.16.105",
"pg": "8.11.3",
"pg-promise": "10.15.4",
"pg": "8.12.0",
"pg-promise": "11.9.1",
"promise-ftp": "1.3.5",
"pyodide": "0.23.4",
"redis": "4.6.14",
Expand Down
Loading

0 comments on commit bbbc765

Please sign in to comment.