diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index ff74e651fb..4dfd3423f6 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -19,7 +19,12 @@ import type { ServeStaticOptions } from 'serve-static'; import type { FindManyOptions, FindOptionsWhere } from 'typeorm'; import { Not, In } from 'typeorm'; -import { LoadMappingOptions, LoadNodeParameterOptions, LoadNodeListSearch } from 'n8n-core'; +import { + LoadMappingOptions, + LoadNodeParameterOptions, + LoadNodeListSearch, + InstanceSettings, +} from 'n8n-core'; import type { INodeCredentials, @@ -46,7 +51,6 @@ import { getSharedWorkflowIds } from '@/WorkflowHelpers'; import { workflowsController } from '@/workflows/workflows.controller'; import { EDITOR_UI_DIST_DIR, - GENERATED_STATIC_DIR, inDevelopment, inE2ETests, N8N_VERSION, @@ -951,11 +955,12 @@ export class Server extends AbstractServer { ); } + const { staticCacheDir } = Container.get(InstanceSettings); if (frontendService) { const staticOptions: ServeStaticOptions = { cacheControl: false, setHeaders: (res: express.Response, path: string) => { - const isIndex = path === pathJoin(GENERATED_STATIC_DIR, 'index.html'); + const isIndex = path === pathJoin(staticCacheDir, 'index.html'); const cacheControl = isIndex ? 'no-cache, no-store, must-revalidate' : 'max-age=86400, immutable'; @@ -981,7 +986,7 @@ export class Server extends AbstractServer { this.app.use( '/', - express.static(GENERATED_STATIC_DIR), + express.static(staticCacheDir), express.static(EDITOR_UI_DIST_DIR, staticOptions), ); @@ -991,7 +996,7 @@ export class Server extends AbstractServer { next(); }); } else { - this.app.use('/', express.static(GENERATED_STATIC_DIR)); + this.app.use('/', express.static(staticCacheDir)); } } diff --git a/packages/cli/src/commands/start.ts b/packages/cli/src/commands/start.ts index 676e91cc81..6d1c4b46b5 100644 --- a/packages/cli/src/commands/start.ts +++ b/packages/cli/src/commands/start.ts @@ -21,7 +21,7 @@ import { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner'; import * as Db from '@/Db'; import * as GenericHelpers from '@/GenericHelpers'; import { Server } from '@/Server'; -import { EDITOR_UI_DIST_DIR, GENERATED_STATIC_DIR, LICENSE_FEATURES } from '@/constants'; +import { EDITOR_UI_DIST_DIR, LICENSE_FEATURES } from '@/constants'; import { eventBus } from '@/eventbus'; import { BaseCommand } from './BaseCommand'; import { InternalHooks } from '@/InternalHooks'; @@ -169,10 +169,11 @@ export class Start extends BaseCommand { } const closingTitleTag = ''; + const { staticCacheDir } = this.instanceSettings; const compileFile = async (fileName: string) => { const filePath = path.join(EDITOR_UI_DIST_DIR, fileName); if (/(index\.html)|.*\.(js|css)/.test(filePath) && existsSync(filePath)) { - const destFile = path.join(GENERATED_STATIC_DIR, fileName); + const destFile = path.join(staticCacheDir, fileName); await mkdir(path.dirname(destFile), { recursive: true }); const streams = [ createReadStream(filePath, 'utf-8'), diff --git a/packages/cli/src/config/index.ts b/packages/cli/src/config/index.ts index f733869c58..fb269d9166 100644 --- a/packages/cli/src/config/index.ts +++ b/packages/cli/src/config/index.ts @@ -2,7 +2,6 @@ import convict from 'convict'; import dotenv from 'dotenv'; import { readFileSync } from 'fs'; import { setGlobalState } from 'n8n-workflow'; -import { schema } from './schema'; import { inTest, inE2ETests } from '@/constants'; if (inE2ETests) { @@ -25,6 +24,8 @@ if (inE2ETests) { dotenv.config(); } +// Load schema after process.env has been overwritten +import { schema } from './schema'; const config = convict(schema, { args: [] }); // eslint-disable-next-line @typescript-eslint/unbound-method diff --git a/packages/cli/src/constants.ts b/packages/cli/src/constants.ts index d54c463646..b72e7be557 100644 --- a/packages/cli/src/constants.ts +++ b/packages/cli/src/constants.ts @@ -1,8 +1,6 @@ import { readFileSync } from 'fs'; import { resolve, join, dirname } from 'path'; -import { Container } from 'typedi'; import type { n8n } from 'n8n-core'; -import { InstanceSettings } from 'n8n-core'; import { jsonParse } from 'n8n-workflow'; const { NODE_ENV, E2E_TESTS } = process.env; @@ -17,10 +15,6 @@ export const CUSTOM_API_CALL_KEY = '__CUSTOM_API_CALL__'; export const CLI_DIR = resolve(__dirname, '..'); export const TEMPLATES_DIR = join(CLI_DIR, 'templates'); export const NODES_BASE_DIR = dirname(require.resolve('n8n-nodes-base')); -export const GENERATED_STATIC_DIR = join( - Container.get(InstanceSettings).userHome, - '.cache/n8n/public', -); export const EDITOR_UI_DIST_DIR = join(dirname(require.resolve('n8n-editor-ui')), 'dist'); export function getN8nPackageJson() { diff --git a/packages/cli/src/services/frontend.service.ts b/packages/cli/src/services/frontend.service.ts index a493cdf3eb..276494fef0 100644 --- a/packages/cli/src/services/frontend.service.ts +++ b/packages/cli/src/services/frontend.service.ts @@ -12,7 +12,7 @@ import type { } from 'n8n-workflow'; import { InstanceSettings } from 'n8n-core'; -import { GENERATED_STATIC_DIR, LICENSE_FEATURES } from '@/constants'; +import { LICENSE_FEATURES } from '@/constants'; import { CredentialsOverwrites } from '@/CredentialsOverwrites'; import { CredentialTypes } from '@/CredentialTypes'; import { LoadNodesAndCredentials } from '@/LoadNodesAndCredentials'; @@ -205,8 +205,9 @@ export class FrontendService { async generateTypes() { this.overwriteCredentialsProperties(); + const { staticCacheDir } = this.instanceSettings; // pre-render all the node and credential types as static json files - await mkdir(path.join(GENERATED_STATIC_DIR, 'types'), { recursive: true }); + await mkdir(path.join(staticCacheDir, 'types'), { recursive: true }); const { credentials, nodes } = this.loadNodesAndCredentials.types; this.writeStaticJSON('nodes', nodes); this.writeStaticJSON('credentials', credentials); @@ -303,7 +304,8 @@ export class FrontendService { } private writeStaticJSON(name: string, data: INodeTypeBaseDescription[] | ICredentialType[]) { - const filePath = path.join(GENERATED_STATIC_DIR, `types/${name}.json`); + const { staticCacheDir } = this.instanceSettings; + const filePath = path.join(staticCacheDir, `types/${name}.json`); const stream = createWriteStream(filePath, 'utf-8'); stream.write('[\n'); data.forEach((entry, index) => { diff --git a/packages/cli/test/integration/commands/start.cmd.test.ts b/packages/cli/test/integration/commands/start.cmd.test.ts index 47158a1c11..7fb6bb8533 100644 --- a/packages/cli/test/integration/commands/start.cmd.test.ts +++ b/packages/cli/test/integration/commands/start.cmd.test.ts @@ -36,6 +36,7 @@ afterEach(() => { test('should not init license if instance is follower in multi-main scenario', async () => { config.set('executions.mode', 'queue'); + config.set('endpoints.disableUi', true); config.set('leaderSelection.enabled', true); jest.spyOn(MultiMainInstancePublisher.prototype, 'isFollower', 'get').mockReturnValue(true); diff --git a/packages/core/src/InstanceSettings.ts b/packages/core/src/InstanceSettings.ts index 6802914c97..4731ad91f2 100644 --- a/packages/core/src/InstanceSettings.ts +++ b/packages/core/src/InstanceSettings.ts @@ -16,11 +16,14 @@ type Settings = ReadOnlySettings & WritableSettings; @Service() export class InstanceSettings { - readonly userHome = this.getUserHome(); + private readonly userHome = this.getUserHome(); /** The path to the n8n folder in which all n8n related data gets saved */ readonly n8nFolder = path.join(this.userHome, '.n8n'); + /** The path to the folder where all generated static assets are copied to */ + readonly staticCacheDir = path.join(this.userHome, '.cache/n8n/public'); + /** The path to the folder containing custom nodes and credentials */ readonly customExtensionDir = path.join(this.n8nFolder, 'custom');