mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +00:00
fix(core): Unloading a community package should also unload all its files from require.cache (#16072)
This commit is contained in:
committed by
GitHub
parent
dfdc2237af
commit
8c63ca7d57
@@ -523,22 +523,15 @@ export class LoadNodesAndCredentials {
|
|||||||
const push = Container.get(Push);
|
const push = Container.get(Push);
|
||||||
|
|
||||||
Object.values(this.loaders).forEach(async (loader) => {
|
Object.values(this.loaders).forEach(async (loader) => {
|
||||||
|
const { directory } = loader;
|
||||||
try {
|
try {
|
||||||
await fsPromises.access(loader.directory);
|
await fsPromises.access(directory);
|
||||||
} catch {
|
} catch {
|
||||||
// If directory doesn't exist, there is nothing to watch
|
// If directory doesn't exist, there is nothing to watch
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const realModulePath = path.join(await fsPromises.realpath(loader.directory), path.sep);
|
|
||||||
const reloader = debounce(async () => {
|
const reloader = debounce(async () => {
|
||||||
const modulesToUnload = Object.keys(require.cache).filter((filePath) =>
|
|
||||||
filePath.startsWith(realModulePath),
|
|
||||||
);
|
|
||||||
modulesToUnload.forEach((filePath) => {
|
|
||||||
delete require.cache[filePath];
|
|
||||||
});
|
|
||||||
|
|
||||||
loader.reset();
|
loader.reset();
|
||||||
await loader.loadAll();
|
await loader.loadAll();
|
||||||
await this.postProcessLoaders();
|
await this.postProcessLoaders();
|
||||||
@@ -549,11 +542,11 @@ export class LoadNodesAndCredentials {
|
|||||||
? ['**/nodes.json', '**/credentials.json']
|
? ['**/nodes.json', '**/credentials.json']
|
||||||
: ['**/*.js', '**/*.json'];
|
: ['**/*.js', '**/*.json'];
|
||||||
const files = await glob(toWatch, {
|
const files = await glob(toWatch, {
|
||||||
cwd: realModulePath,
|
cwd: directory,
|
||||||
ignore: ['node_modules/**'],
|
ignore: ['node_modules/**'],
|
||||||
});
|
});
|
||||||
const watcher = watch(files, {
|
const watcher = watch(files, {
|
||||||
cwd: realModulePath,
|
cwd: directory,
|
||||||
ignoreInitial: true,
|
ignoreInitial: true,
|
||||||
});
|
});
|
||||||
watcher.on('add', reloader).on('change', reloader).on('unlink', reloader);
|
watcher.on('add', reloader).on('change', reloader).on('unlink', reloader);
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ jest.mock('node:fs');
|
|||||||
jest.mock('node:fs/promises');
|
jest.mock('node:fs/promises');
|
||||||
const mockFs = mock<typeof fs>();
|
const mockFs = mock<typeof fs>();
|
||||||
const mockFsPromises = mock<typeof fsPromises>();
|
const mockFsPromises = mock<typeof fsPromises>();
|
||||||
|
fs.realpathSync = mockFs.realpathSync;
|
||||||
fs.readFileSync = mockFs.readFileSync;
|
fs.readFileSync = mockFs.readFileSync;
|
||||||
fsPromises.readFile = mockFsPromises.readFile;
|
fsPromises.readFile = mockFsPromises.readFile;
|
||||||
|
|
||||||
@@ -64,6 +65,7 @@ describe('DirectoryLoader', () => {
|
|||||||
let mockCredential1: ICredentialType, mockNode1: INodeType, mockNode2: INodeType;
|
let mockCredential1: ICredentialType, mockNode1: INodeType, mockNode2: INodeType;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
mockFs.realpathSync.mockImplementation((path) => String(path));
|
||||||
mockCredential1 = createCredential('credential1');
|
mockCredential1 = createCredential('credential1');
|
||||||
mockNode1 = createNode('node1', 'credential1');
|
mockNode1 = createNode('node1', 'credential1');
|
||||||
mockNode2 = createNode('node2');
|
mockNode2 = createNode('node2');
|
||||||
@@ -330,6 +332,19 @@ describe('DirectoryLoader', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('constructor', () => {
|
||||||
|
it('should resolve symlinks to real paths when directory is a symlink', () => {
|
||||||
|
const symlinkPath = '/symlink/path';
|
||||||
|
const realPath = '/real/path';
|
||||||
|
mockFs.realpathSync.mockReturnValueOnce(realPath);
|
||||||
|
|
||||||
|
const loader = new CustomDirectoryLoader(symlinkPath);
|
||||||
|
|
||||||
|
expect(mockFs.realpathSync).toHaveBeenCalledWith(symlinkPath);
|
||||||
|
expect(loader.directory).toBe(realPath);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('reset()', () => {
|
describe('reset()', () => {
|
||||||
it('should reset all properties to their initial state', async () => {
|
it('should reset all properties to their initial state', async () => {
|
||||||
mockFs.readFileSync.calledWith(`${directory}/package.json`).mockReturnValue(packageJson);
|
mockFs.readFileSync.calledWith(`${directory}/package.json`).mockReturnValue(packageJson);
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import type {
|
|||||||
KnownNodesAndCredentials,
|
KnownNodesAndCredentials,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import { ApplicationError, isSubNodeType } from 'n8n-workflow';
|
import { ApplicationError, isSubNodeType } from 'n8n-workflow';
|
||||||
|
import { realpathSync } from 'node:fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
|
||||||
import { UnrecognizedCredentialTypeError } from '@/errors/unrecognized-credential-type.error';
|
import { UnrecognizedCredentialTypeError } from '@/errors/unrecognized-credential-type.error';
|
||||||
@@ -83,13 +84,22 @@ export abstract class DirectoryLoader {
|
|||||||
readonly directory: string,
|
readonly directory: string,
|
||||||
protected excludeNodes: string[] = [],
|
protected excludeNodes: string[] = [],
|
||||||
protected includeNodes: string[] = [],
|
protected includeNodes: string[] = [],
|
||||||
) {}
|
) {
|
||||||
|
// If `directory` is a symlink, we try to resolve it to its real path
|
||||||
|
try {
|
||||||
|
this.directory = realpathSync(directory);
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||||
|
if (error.code !== 'ENOENT') throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
abstract packageName: string;
|
abstract packageName: string;
|
||||||
|
|
||||||
abstract loadAll(): Promise<void>;
|
abstract loadAll(): Promise<void>;
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
|
this.unloadAll();
|
||||||
this.loadedNodes = [];
|
this.loadedNodes = [];
|
||||||
this.nodeTypes = {};
|
this.nodeTypes = {};
|
||||||
this.credentialTypes = {};
|
this.credentialTypes = {};
|
||||||
@@ -450,4 +460,13 @@ export abstract class DirectoryLoader {
|
|||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private unloadAll() {
|
||||||
|
const filesToUnload = Object.keys(require.cache).filter((filePath) =>
|
||||||
|
filePath.startsWith(this.directory),
|
||||||
|
);
|
||||||
|
filesToUnload.forEach((filePath) => {
|
||||||
|
delete require.cache[filePath];
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user