mirror of
https://github.com/n8n-io/n8n
synced 2026-04-21 15:47:20 +00:00
refactor: Migrate source control feature to modules (#22453)
This commit is contained in:
parent
639c09f69a
commit
9bfb014cb9
59 changed files with 93 additions and 104 deletions
|
|
@ -27,6 +27,7 @@ describe('eligibleModules', () => {
|
|||
'mcp',
|
||||
'provisioning',
|
||||
'breaking-changes',
|
||||
'source-control',
|
||||
'dynamic-credentials',
|
||||
'chat-hub',
|
||||
]);
|
||||
|
|
@ -42,6 +43,7 @@ describe('eligibleModules', () => {
|
|||
'mcp',
|
||||
'provisioning',
|
||||
'breaking-changes',
|
||||
'source-control',
|
||||
'dynamic-credentials',
|
||||
'chat-hub',
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ export class ModuleRegistry {
|
|||
'mcp',
|
||||
'provisioning',
|
||||
'breaking-changes',
|
||||
'source-control',
|
||||
'dynamic-credentials',
|
||||
'chat-hub',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ export const MODULE_NAMES = [
|
|||
'mcp',
|
||||
'provisioning',
|
||||
'breaking-changes',
|
||||
'source-control',
|
||||
'dynamic-credentials',
|
||||
'chat-hub',
|
||||
] as const;
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ export const LOG_SCOPES = [
|
|||
'chat-hub',
|
||||
'breaking-changes',
|
||||
'circuit-breaker',
|
||||
'source-control',
|
||||
'dynamic-credentials',
|
||||
'workflow-history-compaction',
|
||||
] as const;
|
||||
|
|
|
|||
|
|
@ -1,29 +0,0 @@
|
|||
import { Container } from '@n8n/di';
|
||||
import type { RequestHandler } from 'express';
|
||||
|
||||
import { isSourceControlLicensed } from '../source-control-helper.ee';
|
||||
import { SourceControlPreferencesService } from '../source-control-preferences.service.ee';
|
||||
|
||||
export const sourceControlLicensedAndEnabledMiddleware: RequestHandler = (_req, res, next) => {
|
||||
const sourceControlPreferencesService = Container.get(SourceControlPreferencesService);
|
||||
if (sourceControlPreferencesService.isSourceControlLicensedAndEnabled()) {
|
||||
next();
|
||||
} else {
|
||||
if (!sourceControlPreferencesService.isSourceControlConnected()) {
|
||||
res.status(412).json({
|
||||
status: 'error',
|
||||
message: 'source_control_not_connected',
|
||||
});
|
||||
} else {
|
||||
res.status(401).json({ status: 'error', message: 'Unauthorized' });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const sourceControlLicensedMiddleware: RequestHandler = (_req, res, next) => {
|
||||
if (isSourceControlLicensed()) {
|
||||
next();
|
||||
} else {
|
||||
res.status(401).json({ status: 'error', message: 'Unauthorized' });
|
||||
}
|
||||
};
|
||||
|
|
@ -21,7 +21,7 @@ import { captor, mock } from 'jest-mock-extended';
|
|||
import { Cipher, type InstanceSettings } from 'n8n-core';
|
||||
import fsp from 'node:fs/promises';
|
||||
|
||||
import type { VariablesService } from '../../variables/variables.service.ee';
|
||||
import type { VariablesService } from '../../../environments.ee/variables/variables.service.ee';
|
||||
import { SourceControlExportService } from '../source-control-export.service.ee';
|
||||
import type { SourceControlScopedService } from '../source-control-scoped.service';
|
||||
import { SourceControlContext } from '../types/source-control-context';
|
||||
|
|
@ -8,7 +8,7 @@ import path from 'path';
|
|||
import {
|
||||
SOURCE_CONTROL_SSH_FOLDER,
|
||||
SOURCE_CONTROL_GIT_FOLDER,
|
||||
} from '@/environments.ee/source-control/constants';
|
||||
} from '@/modules/source-control.ee/constants';
|
||||
import {
|
||||
hasOwnerChanged,
|
||||
generateSshKeyPair,
|
||||
|
|
@ -18,8 +18,8 @@ import {
|
|||
getTrackingInformationFromPullResult,
|
||||
isWorkflowModified,
|
||||
sourceControlFoldersExistCheck,
|
||||
} from '@/environments.ee/source-control/source-control-helper.ee';
|
||||
import type { SourceControlPreferencesService } from '@/environments.ee/source-control/source-control-preferences.service.ee';
|
||||
} from '../source-control-helper.ee';
|
||||
import type { SourceControlPreferencesService } from '@/modules/source-control.ee/source-control-preferences.service.ee';
|
||||
import type { License } from '@/license';
|
||||
|
||||
import type { SourceControlWorkflowVersionId } from '../types/source-control-workflow-version-id';
|
||||
|
|
@ -462,7 +462,7 @@ describe('readTagAndMappingsFromSourceControlFile', () => {
|
|||
const filePath = 'invalid/path/tags-and-mappings.json';
|
||||
// Import the function after resetting modules
|
||||
const { readTagAndMappingsFromSourceControlFile } = await import(
|
||||
'@/environments.ee/source-control/source-control-helper.ee'
|
||||
'@/modules/source-control.ee/source-control-helper.ee'
|
||||
);
|
||||
const result = await readTagAndMappingsFromSourceControlFile(filePath);
|
||||
expect(result).toEqual({
|
||||
|
|
@ -483,7 +483,7 @@ describe('readFoldersFromSourceControlFile', () => {
|
|||
const filePath = 'invalid/path/folders.json';
|
||||
// Import the function after resetting modules
|
||||
const { readFoldersFromSourceControlFile } = await import(
|
||||
'@/environments.ee/source-control/source-control-helper.ee'
|
||||
'@/modules/source-control.ee/source-control-helper.ee'
|
||||
);
|
||||
const result = await readFoldersFromSourceControlFile(filePath);
|
||||
expect(result).toEqual({
|
||||
|
|
@ -6,8 +6,8 @@ import { mock } from 'jest-mock-extended';
|
|||
import { InstanceSettings } from 'n8n-core';
|
||||
import type { PushResult } from 'simple-git';
|
||||
|
||||
import { SourceControlPreferencesService } from '@/environments.ee/source-control/source-control-preferences.service.ee';
|
||||
import { SourceControlService } from '@/environments.ee/source-control/source-control.service.ee';
|
||||
import { SourceControlPreferencesService } from '@/modules/source-control.ee/source-control-preferences.service.ee';
|
||||
import { SourceControlService } from '@/modules/source-control.ee/source-control.service.ee';
|
||||
import { ForbiddenError } from '@/errors/response-errors/forbidden.error';
|
||||
import type { EventService } from '@/events/event.service';
|
||||
import type { SourceControlExportService } from '../source-control-export.service.ee';
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
import { Container } from '@n8n/di';
|
||||
import type { RequestHandler } from 'express';
|
||||
|
||||
import { SourceControlPreferencesService } from '../source-control-preferences.service.ee';
|
||||
|
||||
export const sourceControlEnabledMiddleware: RequestHandler = (_req, res, next) => {
|
||||
const sourceControlPreferencesService = Container.get(SourceControlPreferencesService);
|
||||
|
||||
if (sourceControlPreferencesService.isSourceControlConnected()) {
|
||||
next();
|
||||
} else {
|
||||
res.status(412).json({
|
||||
status: 'error',
|
||||
message: 'source_control_not_connected',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -41,7 +41,7 @@ import {
|
|||
stringContainsExpression,
|
||||
} from './source-control-helper.ee';
|
||||
import { SourceControlScopedService } from './source-control-scoped.service';
|
||||
import { VariablesService } from '../variables/variables.service.ee';
|
||||
import { VariablesService } from '../../environments.ee/variables/variables.service.ee';
|
||||
import type { ExportResult } from './types/export-result';
|
||||
import type { ExportableCredential } from './types/exportable-credential';
|
||||
import { ExportableProject } from './types/exportable-project';
|
||||
|
|
@ -61,7 +61,7 @@ import {
|
|||
getWorkflowExportPath,
|
||||
} from './source-control-helper.ee';
|
||||
import { SourceControlScopedService } from './source-control-scoped.service';
|
||||
import { VariablesService } from '../variables/variables.service.ee';
|
||||
import { VariablesService } from '../../environments.ee/variables/variables.service.ee';
|
||||
import type {
|
||||
ExportableCredential,
|
||||
StatusExportableCredential,
|
||||
|
|
@ -7,10 +7,7 @@ import express from 'express';
|
|||
import type { PullResult } from 'simple-git';
|
||||
|
||||
import { SOURCE_CONTROL_DEFAULT_BRANCH } from './constants';
|
||||
import {
|
||||
sourceControlLicensedMiddleware,
|
||||
sourceControlLicensedAndEnabledMiddleware,
|
||||
} from './middleware/source-control-enabled-middleware.ee';
|
||||
import { sourceControlEnabledMiddleware } from './middleware/source-control-enabled-middleware.ee';
|
||||
import { getRepoType } from './source-control-helper.ee';
|
||||
import { SourceControlPreferencesService } from './source-control-preferences.service.ee';
|
||||
import { SourceControlScopedService } from './source-control-scoped.service';
|
||||
|
|
@ -33,14 +30,14 @@ export class SourceControlController {
|
|||
private readonly eventService: EventService,
|
||||
) {}
|
||||
|
||||
@Get('/preferences', { middlewares: [sourceControlLicensedMiddleware], skipAuth: true })
|
||||
@Get('/preferences', { skipAuth: true })
|
||||
async getPreferences(): Promise<SourceControlPreferences> {
|
||||
// returns the settings with the privateKey property redacted
|
||||
const publicKey = await this.sourceControlPreferencesService.getPublicKey();
|
||||
return { ...this.sourceControlPreferencesService.getPreferences(), publicKey };
|
||||
}
|
||||
|
||||
@Post('/preferences', { middlewares: [sourceControlLicensedMiddleware] })
|
||||
@Post('/preferences')
|
||||
@GlobalScope('sourceControl:manage')
|
||||
async setPreferences(req: SourceControlRequest.UpdatePreferences) {
|
||||
if (
|
||||
|
|
@ -87,7 +84,7 @@ export class SourceControlController {
|
|||
throw error;
|
||||
}
|
||||
}
|
||||
await this.sourceControlService.init();
|
||||
await this.sourceControlService.start();
|
||||
const resultingPreferences = this.sourceControlPreferencesService.getPreferences();
|
||||
// #region Tracking Information
|
||||
// located in controller so as to not call this multiple times when updating preferences
|
||||
|
|
@ -105,7 +102,7 @@ export class SourceControlController {
|
|||
}
|
||||
}
|
||||
|
||||
@Patch('/preferences', { middlewares: [sourceControlLicensedMiddleware] })
|
||||
@Patch('/preferences')
|
||||
@GlobalScope('sourceControl:manage')
|
||||
async updatePreferences(req: SourceControlRequest.UpdatePreferences) {
|
||||
try {
|
||||
|
|
@ -135,7 +132,7 @@ export class SourceControlController {
|
|||
true,
|
||||
);
|
||||
}
|
||||
await this.sourceControlService.init();
|
||||
await this.sourceControlService.start();
|
||||
const resultingPreferences = this.sourceControlPreferencesService.getPreferences();
|
||||
this.eventService.emit('source-control-settings-updated', {
|
||||
branchName: resultingPreferences.branchName,
|
||||
|
|
@ -150,7 +147,7 @@ export class SourceControlController {
|
|||
}
|
||||
}
|
||||
|
||||
@Post('/disconnect', { middlewares: [sourceControlLicensedMiddleware] })
|
||||
@Post('/disconnect')
|
||||
@GlobalScope('sourceControl:manage')
|
||||
async disconnect(req: SourceControlRequest.Disconnect) {
|
||||
try {
|
||||
|
|
@ -160,7 +157,7 @@ export class SourceControlController {
|
|||
}
|
||||
}
|
||||
|
||||
@Get('/get-branches', { middlewares: [sourceControlLicensedMiddleware] })
|
||||
@Get('/get-branches')
|
||||
async getBranches() {
|
||||
try {
|
||||
return await this.sourceControlService.getBranches();
|
||||
|
|
@ -169,7 +166,7 @@ export class SourceControlController {
|
|||
}
|
||||
}
|
||||
|
||||
@Post('/push-workfolder', { middlewares: [sourceControlLicensedAndEnabledMiddleware] })
|
||||
@Post('/push-workfolder', { middlewares: [sourceControlEnabledMiddleware] })
|
||||
async pushWorkfolder(
|
||||
req: AuthenticatedRequest,
|
||||
res: express.Response,
|
||||
|
|
@ -191,7 +188,7 @@ export class SourceControlController {
|
|||
}
|
||||
}
|
||||
|
||||
@Post('/pull-workfolder', { middlewares: [sourceControlLicensedAndEnabledMiddleware] })
|
||||
@Post('/pull-workfolder', { middlewares: [sourceControlEnabledMiddleware] })
|
||||
@GlobalScope('sourceControl:pull')
|
||||
async pullWorkfolder(
|
||||
req: AuthenticatedRequest,
|
||||
|
|
@ -207,7 +204,7 @@ export class SourceControlController {
|
|||
}
|
||||
}
|
||||
|
||||
@Get('/reset-workfolder', { middlewares: [sourceControlLicensedAndEnabledMiddleware] })
|
||||
@Get('/reset-workfolder', { middlewares: [sourceControlEnabledMiddleware] })
|
||||
@GlobalScope('sourceControl:manage')
|
||||
async resetWorkfolder(): Promise<ImportResult | undefined> {
|
||||
try {
|
||||
|
|
@ -217,7 +214,7 @@ export class SourceControlController {
|
|||
}
|
||||
}
|
||||
|
||||
@Get('/get-status', { middlewares: [sourceControlLicensedAndEnabledMiddleware] })
|
||||
@Get('/get-status', { middlewares: [sourceControlEnabledMiddleware] })
|
||||
async getStatus(req: SourceControlRequest.GetStatus) {
|
||||
try {
|
||||
const result = await this.sourceControlService.getStatus(
|
||||
|
|
@ -230,7 +227,7 @@ export class SourceControlController {
|
|||
}
|
||||
}
|
||||
|
||||
@Get('/status', { middlewares: [sourceControlLicensedMiddleware] })
|
||||
@Get('/status')
|
||||
async status(req: SourceControlRequest.GetStatus) {
|
||||
try {
|
||||
return await this.sourceControlService.getStatus(
|
||||
|
|
@ -242,7 +239,7 @@ export class SourceControlController {
|
|||
}
|
||||
}
|
||||
|
||||
@Post('/generate-key-pair', { middlewares: [sourceControlLicensedMiddleware] })
|
||||
@Post('/generate-key-pair')
|
||||
@GlobalScope('sourceControl:manage')
|
||||
async generateKeyPair(
|
||||
req: SourceControlRequest.GenerateKeyPair,
|
||||
|
|
@ -257,7 +254,7 @@ export class SourceControlController {
|
|||
}
|
||||
}
|
||||
|
||||
@Get('/remote-content/:type/:id', { middlewares: [sourceControlLicensedAndEnabledMiddleware] })
|
||||
@Get('/remote-content/:type/:id', { middlewares: [sourceControlEnabledMiddleware] })
|
||||
async getFileContent(
|
||||
req: AuthenticatedRequest & { params: { type: SourceControlledFile['type']; id: string } },
|
||||
): Promise<{ content: IWorkflowToImport; type: SourceControlledFile['type'] }> {
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
import type { ModuleInterface } from '@n8n/decorators';
|
||||
import { BackendModule } from '@n8n/decorators';
|
||||
import { Container } from '@n8n/di';
|
||||
|
||||
@BackendModule({ name: 'source-control', licenseFlag: 'feat:sourceControl' })
|
||||
export class SourceControlModule implements ModuleInterface {
|
||||
async init() {
|
||||
await import('./source-control.controller.ee');
|
||||
|
||||
const { SourceControlService } = await import('./source-control.service.ee');
|
||||
await Container.get(SourceControlService).start();
|
||||
}
|
||||
}
|
||||
|
|
@ -70,7 +70,7 @@ export class SourceControlService {
|
|||
this.sshKeyName = sshKeyName;
|
||||
}
|
||||
|
||||
async init(): Promise<void> {
|
||||
async start(): Promise<void> {
|
||||
this.gitService.resetService();
|
||||
sourceControlFoldersExistCheck([this.gitFolder, this.sshFolder]);
|
||||
await this.sourceControlPreferencesService.loadFromDbAndApplySourceControlPreferences();
|
||||
|
|
@ -7,10 +7,10 @@ import type { StatusResult } from 'simple-git';
|
|||
import {
|
||||
getTrackingInformationFromPullResult,
|
||||
isSourceControlLicensed,
|
||||
} from '@/environments.ee/source-control/source-control-helper.ee';
|
||||
import { SourceControlPreferencesService } from '@/environments.ee/source-control/source-control-preferences.service.ee';
|
||||
import { SourceControlService } from '@/environments.ee/source-control/source-control.service.ee';
|
||||
import type { ImportResult } from '@/environments.ee/source-control/types/import-result';
|
||||
} from '@/modules/source-control.ee/source-control-helper.ee';
|
||||
import { SourceControlPreferencesService } from '@/modules/source-control.ee/source-control-preferences.service.ee';
|
||||
import { SourceControlService } from '@/modules/source-control.ee/source-control.service.ee';
|
||||
import type { ImportResult } from '@/modules/source-control.ee/types/import-result';
|
||||
import { EventService } from '@/events/event.service';
|
||||
|
||||
import { apiKeyHasScopeWithGlobalScopeFallback } from '../../shared/middlewares/global.middleware';
|
||||
|
|
|
|||
|
|
@ -172,19 +172,9 @@ export class Server extends AbstractServer {
|
|||
}
|
||||
|
||||
// ----------------------------------------
|
||||
// Source Control
|
||||
// Variables
|
||||
// ----------------------------------------
|
||||
|
||||
try {
|
||||
const { SourceControlService } = await import(
|
||||
'@/environments.ee/source-control/source-control.service.ee'
|
||||
);
|
||||
await Container.get(SourceControlService).init();
|
||||
await import('@/environments.ee/source-control/source-control.controller.ee');
|
||||
} catch (error) {
|
||||
this.logger.warn(`Source control initialization failed: ${(error as Error).message}`);
|
||||
}
|
||||
|
||||
try {
|
||||
await import('@/environments.ee/variables/variables.controller.ee');
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import type { IExecutionTrackProperties } from '@/interfaces';
|
|||
import { License } from '@/license';
|
||||
import { PostHogClient } from '@/posthog';
|
||||
|
||||
import { SourceControlPreferencesService } from '../environments.ee/source-control/source-control-preferences.service.ee';
|
||||
import { SourceControlPreferencesService } from '../modules/source-control.ee/source-control-preferences.service.ee';
|
||||
|
||||
type ExecutionTrackDataKey =
|
||||
| 'manual_error'
|
||||
|
|
|
|||
|
|
@ -15,18 +15,14 @@ import { writeFile as fsWriteFile } from 'node:fs/promises';
|
|||
import path from 'node:path';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { SourceControlExportService } from '@/environments.ee/source-control/source-control-export.service.ee';
|
||||
import type { ExportableCredential } from '@/environments.ee/source-control/types/exportable-credential';
|
||||
import { SourceControlExportService } from '@/modules/source-control.ee/source-control-export.service.ee';
|
||||
import type { ExportableCredential } from '@/modules/source-control.ee/types/exportable-credential';
|
||||
|
||||
import { createCredentials } from '../shared/db/credentials';
|
||||
import { createUser } from '../shared/db/users';
|
||||
|
||||
// Mock file system operations
|
||||
jest.mock('node:fs/promises');
|
||||
jest.mock('@/environments.ee/source-control/source-control-helper.ee', () => ({
|
||||
...jest.requireActual('@/environments.ee/source-control/source-control-helper.ee'),
|
||||
sourceControlFoldersExistCheck: jest.fn().mockResolvedValue(true),
|
||||
}));
|
||||
|
||||
describe('SourceControlExportService Integration', () => {
|
||||
let exportService: SourceControlExportService;
|
||||
|
|
|
|||
|
|
@ -37,10 +37,10 @@ import * as utils from 'n8n-workflow';
|
|||
import { nanoid } from 'nanoid';
|
||||
import fsp from 'node:fs/promises';
|
||||
|
||||
import { SourceControlImportService } from '@/environments.ee/source-control/source-control-import.service.ee';
|
||||
import { SourceControlScopedService } from '@/environments.ee/source-control/source-control-scoped.service';
|
||||
import type { ExportableCredential } from '@/environments.ee/source-control/types/exportable-credential';
|
||||
import { SourceControlContext } from '@/environments.ee/source-control/types/source-control-context';
|
||||
import { SourceControlImportService } from '@/modules/source-control.ee/source-control-import.service.ee';
|
||||
import { SourceControlScopedService } from '@/modules/source-control.ee/source-control-scoped.service';
|
||||
import type { ExportableCredential } from '@/modules/source-control.ee/types/exportable-credential';
|
||||
import { SourceControlContext } from '@/modules/source-control.ee/types/source-control-context';
|
||||
import type { IWorkflowToImport } from '@/interfaces';
|
||||
import { WorkflowHistoryService } from '@/workflows/workflow-history/workflow-history.service';
|
||||
import { createFolder } from '@test-integration/db/folders';
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ import { mockInstance } from '@n8n/backend-test-utils';
|
|||
import { GLOBAL_OWNER_ROLE, type User } from '@n8n/db';
|
||||
import { Container } from '@n8n/di';
|
||||
|
||||
import { SourceControlPreferencesService } from '@/environments.ee/source-control/source-control-preferences.service.ee';
|
||||
import { SourceControlService } from '@/environments.ee/source-control/source-control.service.ee';
|
||||
import { SourceControlPreferencesService } from '@/modules/source-control.ee/source-control-preferences.service.ee';
|
||||
import { SourceControlService } from '@/modules/source-control.ee/source-control.service.ee';
|
||||
import { Telemetry } from '@/telemetry';
|
||||
|
||||
import { createUser } from '../shared/db/users';
|
||||
|
|
|
|||
|
|
@ -28,18 +28,18 @@ import {
|
|||
SOURCE_CONTROL_FOLDERS_EXPORT_FILE,
|
||||
SOURCE_CONTROL_TAGS_EXPORT_FILE,
|
||||
SOURCE_CONTROL_WORKFLOW_EXPORT_FOLDER,
|
||||
} from '@/environments.ee/source-control/constants';
|
||||
import { SourceControlExportService } from '@/environments.ee/source-control/source-control-export.service.ee';
|
||||
import type { SourceControlGitService } from '@/environments.ee/source-control/source-control-git.service.ee';
|
||||
import { SourceControlImportService } from '@/environments.ee/source-control/source-control-import.service.ee';
|
||||
import { SourceControlPreferencesService } from '@/environments.ee/source-control/source-control-preferences.service.ee';
|
||||
import { SourceControlScopedService } from '@/environments.ee/source-control/source-control-scoped.service';
|
||||
import { SourceControlStatusService } from '@/environments.ee/source-control/source-control-status.service.ee';
|
||||
import { SourceControlService } from '@/environments.ee/source-control/source-control.service.ee';
|
||||
import type { ExportableCredential } from '@/environments.ee/source-control/types/exportable-credential';
|
||||
import type { ExportableFolder } from '@/environments.ee/source-control/types/exportable-folders';
|
||||
import type { ExportableWorkflow } from '@/environments.ee/source-control/types/exportable-workflow';
|
||||
import type { RemoteResourceOwner } from '@/environments.ee/source-control/types/resource-owner';
|
||||
} from '@/modules/source-control.ee/constants';
|
||||
import { SourceControlExportService } from '@/modules/source-control.ee/source-control-export.service.ee';
|
||||
import type { SourceControlGitService } from '@/modules/source-control.ee/source-control-git.service.ee';
|
||||
import { SourceControlImportService } from '@/modules/source-control.ee/source-control-import.service.ee';
|
||||
import { SourceControlPreferencesService } from '@/modules/source-control.ee/source-control-preferences.service.ee';
|
||||
import { SourceControlScopedService } from '@/modules/source-control.ee/source-control-scoped.service';
|
||||
import { SourceControlStatusService } from '@/modules/source-control.ee/source-control-status.service.ee';
|
||||
import { SourceControlService } from '@/modules/source-control.ee/source-control.service.ee';
|
||||
import type { ExportableCredential } from '@/modules/source-control.ee/types/exportable-credential';
|
||||
import type { ExportableFolder } from '@/modules/source-control.ee/types/exportable-folders';
|
||||
import type { ExportableWorkflow } from '@/modules/source-control.ee/types/exportable-workflow';
|
||||
import type { RemoteResourceOwner } from '@/modules/source-control.ee/types/resource-owner';
|
||||
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
|
||||
import { ForbiddenError } from '@/errors/response-errors/forbidden.error';
|
||||
import { EventService } from '@/events/event.service';
|
||||
|
|
|
|||
|
|
@ -226,7 +226,7 @@ export const setupTestServer = ({
|
|||
}
|
||||
|
||||
case 'sourceControl':
|
||||
await import('@/environments.ee/source-control/source-control.controller.ee');
|
||||
await import('@/modules/source-control.ee/source-control.controller.ee');
|
||||
break;
|
||||
|
||||
case 'community-packages':
|
||||
|
|
|
|||
|
|
@ -165,7 +165,7 @@ Module-level decorators to be aware of:
|
|||
|
||||
## Controller
|
||||
|
||||
To register a controller with the server, simply import the controller file in the module entrypoint:
|
||||
To register a controller with the server, simply import the controller file in the module entrypoint:
|
||||
|
||||
```ts
|
||||
@BackendModule({ name: 'my-feature' })
|
||||
|
|
@ -261,7 +261,7 @@ export class MyFeatureRepository extends Repository<MyFeatureEntity> {
|
|||
}
|
||||
|
||||
async getSummary() {
|
||||
return await /* typeorm query on entities */;
|
||||
return await /* typeorm query on entities */;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
@ -293,7 +293,7 @@ Entities must be registered with `typeorm` in the module entrypoint:
|
|||
class MyFeatureModule implements ModuleInterface {
|
||||
async entities() {
|
||||
const { MyFeatureEntity } = await import('./my-feature.entity');
|
||||
|
||||
|
||||
return [MyFeatureEntity];
|
||||
}
|
||||
}
|
||||
|
|
@ -343,7 +343,7 @@ Currently, testing utilities live partly at `cli` and partly at `@n8n/backend-te
|
|||
1. A few aspects of modules continue to be defined outside a module's dir:
|
||||
|
||||
- Add a license flag to `LICENSE_FEATURES` at `packages/@n8n/constants/src/index.ts`
|
||||
- Add a logging scope to `LOG_SCOPES` at `packages/cli/src/logging.config.ts`
|
||||
- Add a logging scope to `LOG_SCOPES` at `packages/@n8n/config/src/configs/logging.config.ts`
|
||||
- Add a license check to `LicenseState` at `packages/@n8n/backend-common/src/license-state.ts`
|
||||
- Add a migration (as discussed above) at `packages/@n8n/db/src/migrations`
|
||||
- Add request payload validation using `zod` at `@n8n/api-types`
|
||||
|
|
@ -351,7 +351,7 @@ Currently, testing utilities live partly at `cli` and partly at `@n8n/backend-te
|
|||
|
||||
2. License events (e.g. expiration) currently do not trigger module shutdown or initialization at runtime.
|
||||
|
||||
3. Some core functionality is yet to be moved from `cli` into common packages. This is not a blocker for module adoption, but this is desirable so that (a) modules become decoupled from `cli` in the long term, and (b) future external extensions can access some of that functionality.
|
||||
3. Some core functionality is yet to be moved from `cli` into common packages. This is not a blocker for module adoption, but this is desirable so that (a) modules become decoupled from `cli` in the long term, and (b) future external extensions can access some of that functionality.
|
||||
|
||||
4. Existing features that are not modules (e.g. LDAP) should be turned into modules over time.
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue