Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(core): Stop copying icons to cache #5419

Merged
merged 3 commits into from
Feb 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 52 additions & 68 deletions packages/cli/src/LoadNodesAndCredentials.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,7 @@ import type {
import { LoggerProxy, ErrorReporterProxy as ErrorReporter } from 'n8n-workflow';

import { createWriteStream } from 'fs';
import {
access as fsAccess,
copyFile,
mkdir,
readdir as fsReaddir,
stat as fsStat,
} from 'fs/promises';
import { access as fsAccess, mkdir, readdir as fsReaddir, stat as fsStat } from 'fs/promises';
import path from 'path';
import config from '@/config';
import type { InstalledPackages } from '@db/entities/InstalledPackages';
Expand All @@ -50,6 +44,8 @@ export class LoadNodesAndCredentialsClass implements INodesAndCredentials {

types: Types = { nodes: [], credentials: [] };

loaders: Record<string, DirectoryLoader> = {};

excludeNodes = config.getEnv('nodes.exclude');

includeNodes = config.getEnv('nodes.include');
Expand All @@ -73,6 +69,7 @@ export class LoadNodesAndCredentialsClass implements INodesAndCredentials {
await this.loadNodesFromBasePackages();
await this.loadNodesFromDownloadedPackages();
await this.loadNodesFromCustomDirectories();
await this.postProcessLoaders();
this.injectCustomApiCallOptions();
}

Expand Down Expand Up @@ -117,7 +114,7 @@ export class LoadNodesAndCredentialsClass implements INodesAndCredentials {
await writeStaticJSON('credentials', this.types.credentials);
}

async loadNodesFromBasePackages() {
private async loadNodesFromBasePackages() {
const nodeModulesPath = await this.getNodeModulesPath();
const nodePackagePaths = await this.getN8nNodePackages(nodeModulesPath);

Expand All @@ -126,7 +123,7 @@ export class LoadNodesAndCredentialsClass implements INodesAndCredentials {
}
}

async loadNodesFromDownloadedPackages(): Promise<void> {
private async loadNodesFromDownloadedPackages(): Promise<void> {
const nodePackages = [];
try {
// Read downloaded nodes and credentials
Expand Down Expand Up @@ -160,35 +157,34 @@ export class LoadNodesAndCredentialsClass implements INodesAndCredentials {
return customDirectories;
}

async loadNodesFromCustomDirectories(): Promise<void> {
private async loadNodesFromCustomDirectories(): Promise<void> {
for (const directory of this.getCustomDirectories()) {
await this.runDirectoryLoader(CustomDirectoryLoader, directory);
}
}

/**
* Returns all the names of the packages which could
* contain n8n nodes
*
* Returns all the names of the packages which could contain n8n nodes
*/
async getN8nNodePackages(baseModulesPath: string): Promise<string[]> {
private async getN8nNodePackages(baseModulesPath: string): Promise<string[]> {
const getN8nNodePackagesRecursive = async (relativePath: string): Promise<string[]> => {
const results: string[] = [];
const nodeModulesPath = `${baseModulesPath}/${relativePath}`;
for (const file of await fsReaddir(nodeModulesPath)) {
const isN8nNodesPackage = file.indexOf('n8n-nodes-') === 0;
const isNpmScopedPackage = file.indexOf('@') === 0;
const nodeModules = await fsReaddir(nodeModulesPath);
for (const nodeModule of nodeModules) {
const isN8nNodesPackage = nodeModule.indexOf('n8n-nodes-') === 0;
const isNpmScopedPackage = nodeModule.indexOf('@') === 0;
if (!isN8nNodesPackage && !isNpmScopedPackage) {
continue;
}
if (!(await fsStat(nodeModulesPath)).isDirectory()) {
continue;
}
if (isN8nNodesPackage) {
results.push(`${baseModulesPath}/${relativePath}${file}`);
results.push(`${baseModulesPath}/${relativePath}${nodeModule}`);
}
if (isNpmScopedPackage) {
results.push(...(await getN8nNodePackagesRecursive(`${relativePath}${file}/`)));
results.push(...(await getN8nNodePackagesRecursive(`${relativePath}${nodeModule}/`)));
}
}
return results;
Expand Down Expand Up @@ -392,62 +388,50 @@ export class LoadNodesAndCredentialsClass implements INodesAndCredentials {
) {
const loader = new constructor(dir, this.excludeNodes, this.includeNodes);
await loader.loadAll();
this.loaders[dir] = loader;
return loader;
}

// list of node & credential types that will be sent to the frontend
const { types } = loader;
this.types.nodes = this.types.nodes.concat(types.nodes);
this.types.credentials = this.types.credentials.concat(types.credentials);

// Copy over all icons and set `iconUrl` for the frontend
const iconPromises = Object.entries(types).flatMap(([typeName, typesArr]) =>
typesArr.map((type) => {
if (!type.icon?.startsWith('file:')) return;
const icon = type.icon.substring(5);
const iconUrl = `icons/${typeName}/${type.name}${path.extname(icon)}`;
delete type.icon;
type.iconUrl = iconUrl;
const source = path.join(dir, icon);
const destination = path.join(GENERATED_STATIC_DIR, iconUrl);
return mkdir(path.dirname(destination), { recursive: true }).then(async () =>
copyFile(source, destination),
);
}),
);

await Promise.all(iconPromises);

// Nodes and credentials that have been loaded immediately
for (const nodeTypeName in loader.nodeTypes) {
this.loaded.nodes[nodeTypeName] = loader.nodeTypes[nodeTypeName];
}
async postProcessLoaders() {
this.types.nodes = [];
this.types.credentials = [];
for (const [dir, loader] of Object.entries(this.loaders)) {
// list of node & credential types that will be sent to the frontend
const { types } = loader;
this.types.nodes = this.types.nodes.concat(types.nodes);
this.types.credentials = this.types.credentials.concat(types.credentials);

// Nodes and credentials that have been loaded immediately
for (const nodeTypeName in loader.nodeTypes) {
this.loaded.nodes[nodeTypeName] = loader.nodeTypes[nodeTypeName];
}

for (const credentialTypeName in loader.credentialTypes) {
this.loaded.credentials[credentialTypeName] = loader.credentialTypes[credentialTypeName];
}
for (const credentialTypeName in loader.credentialTypes) {
this.loaded.credentials[credentialTypeName] = loader.credentialTypes[credentialTypeName];
}

// Nodes and credentials that will be lazy loaded
if (loader instanceof PackageDirectoryLoader) {
const { packageName, known } = loader;
// Nodes and credentials that will be lazy loaded
if (loader instanceof PackageDirectoryLoader) {
const { packageName, known } = loader;

for (const type in known.nodes) {
const { className, sourcePath } = known.nodes[type];
this.known.nodes[type] = {
className,
sourcePath: path.join(dir, sourcePath),
};
}
for (const type in known.nodes) {
const { className, sourcePath } = known.nodes[type];
this.known.nodes[type] = {
className,
sourcePath: path.join(dir, sourcePath),
};
}

for (const type in known.credentials) {
const { className, sourcePath, nodesToTestWith } = known.credentials[type];
this.known.credentials[type] = {
className,
sourcePath: path.join(dir, sourcePath),
nodesToTestWith: nodesToTestWith?.map((nodeName) => `${packageName}.${nodeName}`),
};
for (const type in known.credentials) {
const { className, sourcePath, nodesToTestWith } = known.credentials[type];
this.known.credentials[type] = {
className,
sourcePath: path.join(dir, sourcePath),
nodesToTestWith: nodesToTestWith?.map((nodeName) => `${packageName}.${nodeName}`),
};
}
}
}

return loader;
}

private async getNodeModulesPath(): Promise<string> {
Expand Down
11 changes: 2 additions & 9 deletions packages/cli/src/NodeTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class NodeTypesClass implements INodeTypes {
// eslint-disable-next-line no-restricted-syntax
for (const nodeTypeData of Object.values(this.loadedNodes)) {
const nodeType = NodeHelpers.getVersionedNodeType(nodeTypeData.type);
this.applySpecialNodeParameters(nodeType);
NodeHelpers.applySpecialNodeParameters(nodeType);
}
}

Expand Down Expand Up @@ -57,20 +57,13 @@ class NodeTypesClass implements INodeTypes {
if (type in knownNodes) {
const { className, sourcePath } = knownNodes[type];
const loaded: INodeType = loadClassInIsolation(sourcePath, className);
this.applySpecialNodeParameters(loaded);
NodeHelpers.applySpecialNodeParameters(loaded);
loadedNodes[type] = { sourcePath, type: loaded };
return loadedNodes[type];
}
throw new Error(`${RESPONSE_ERROR_MESSAGES.NO_NODE}: ${type}`);
}

private applySpecialNodeParameters(nodeType: INodeType) {
const applyParameters = NodeHelpers.getSpecialNodeParameters(nodeType);
if (applyParameters.length) {
nodeType.description.properties.unshift(...applyParameters);
}
}

private get loadedNodes() {
return this.nodesAndCredentials.loaded.nodes;
}
Expand Down
42 changes: 29 additions & 13 deletions packages/cli/src/Server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,6 @@ import {
import { getInstance as getMailerInstance } from '@/UserManagement/email';
import * as Db from '@/Db';
import type {
DatabaseType,
ICredentialsDb,
ICredentialsOverwrite,
IDiagnosticInfo,
Expand All @@ -139,10 +138,10 @@ import {
} from '@/CredentialsHelper';
import { CredentialsOverwrites } from '@/CredentialsOverwrites';
import { CredentialTypes } from '@/CredentialTypes';
import * as GenericHelpers from '@/GenericHelpers';
import { NodeTypes } from '@/NodeTypes';
import * as Push from '@/Push';
import { LoadNodesAndCredentials } from '@/LoadNodesAndCredentials';
import type { LoadNodesAndCredentialsClass } from '@/LoadNodesAndCredentials';
import * as ResponseHelper from '@/ResponseHelper';
import type { WaitTrackerClass } from '@/WaitTracker';
import { WaitTracker } from '@/WaitTracker';
Expand Down Expand Up @@ -176,6 +175,8 @@ class Server extends AbstractServer {

presetCredentialsLoaded: boolean;

loadNodesAndCredentials: LoadNodesAndCredentialsClass;

nodeTypes: INodeTypes;

credentialTypes: ICredentialTypes;
Expand All @@ -185,6 +186,7 @@ class Server extends AbstractServer {

this.nodeTypes = NodeTypes();
this.credentialTypes = CredentialTypes();
this.loadNodesAndCredentials = LoadNodesAndCredentials();

this.activeExecutionsInstance = ActiveExecutions.getInstance();
this.waitTracker = WaitTracker();
Expand Down Expand Up @@ -1265,7 +1267,7 @@ class Server extends AbstractServer {

CredentialsOverwrites().setData(body);

await LoadNodesAndCredentials().generateTypesForFrontend();
await this.loadNodesAndCredentials.generateTypesForFrontend();

this.presetCredentialsLoaded = true;

Expand All @@ -1277,17 +1279,31 @@ class Server extends AbstractServer {
);
}

const staticOptions: ServeStaticOptions = {
cacheControl: false,
setHeaders: (res: express.Response, path: string) => {
const isIndex = path === pathJoin(GENERATED_STATIC_DIR, 'index.html');
const cacheControl = isIndex
? 'no-cache, no-store, must-revalidate'
: 'max-age=86400, immutable';
res.header('Cache-Control', cacheControl);
},
};
if (!config.getEnv('endpoints.disableUi')) {
const staticOptions: ServeStaticOptions = {
cacheControl: false,
setHeaders: (res: express.Response, path: string) => {
const isIndex = path === pathJoin(GENERATED_STATIC_DIR, 'index.html');
const cacheControl = isIndex
? 'no-cache, no-store, must-revalidate'
: 'max-age=86400, immutable';
res.header('Cache-Control', cacheControl);
},
};

for (const [dir, loader] of Object.entries(this.loadNodesAndCredentials.loaders)) {
const pathPrefix = `/icons/${loader.packageName}`;
this.app.use(`${pathPrefix}/*/*.(svg|png)`, async (req, res) => {
const filePath = pathResolve(dir, req.originalUrl.substring(pathPrefix.length + 1));
try {
await fsAccess(filePath);
res.sendFile(filePath);
} catch {
res.sendStatus(404);
}
});
}

this.app.use(
'/',
express.static(GENERATED_STATIC_DIR),
Expand Down
5 changes: 1 addition & 4 deletions packages/core/bin/generate-ui-types
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,7 @@ LoggerProxy.init({
const nodeTypes = Object.values(loader.nodeTypes)
.map((data) => {
const nodeType = NodeHelpers.getVersionedNodeType(data.type);
const applyParameters = NodeHelpers.getSpecialNodeParameters(nodeType);
if (applyParameters.length) {
nodeType.description.properties.unshift(...applyParameters);
}
NodeHelpers.applySpecialNodeParameters(nodeType);
return data.type;
})
.flatMap((nodeData) => {
Expand Down
Loading