mirror of
https://github.com/n8n-io/n8n.git
synced 2026-10-17 13:39:44 +02:00
chore(editor): Derive dev REST base URL from N8N_PORT (no-changelog) (#35770)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
221ea41557
commit
48c42fa4b8
|
|
@ -16,7 +16,7 @@ jobs:
|
|||
smoke:
|
||||
name: Dev-server smoke
|
||||
runs-on: ${{ vars.RUNNER_PROVIDER == 'github' && 'ubuntu-latest' || 'blacksmith-4vcpu-ubuntu-2204' }}
|
||||
timeout-minutes: 10
|
||||
timeout-minutes: 15
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
|
|
@ -33,9 +33,18 @@ jobs:
|
|||
- name: Install Browsers
|
||||
run: pnpm turbo run install-browsers --filter=n8n-playwright
|
||||
|
||||
# Both steps run from repo root so PLAYWRIGHT_BROWSERS_PATH (relative)
|
||||
# resolves correctly. cd-ing into the playwright package double-nests it.
|
||||
- name: Run dev-server smoke spec (non-default ports)
|
||||
# Runs FIRST, while nothing has ever listened on 5678, so a regression
|
||||
# to the 5678 fallback fails with a connection error instead of being
|
||||
# satisfied by a server leaked from the default run. The broker moves
|
||||
# too, so it cannot squat on 5679 for the run below.
|
||||
run: pnpm --filter=n8n-playwright test:dev-server-smoke:alt-port --reporter=list
|
||||
|
||||
- name: Run dev-server smoke spec
|
||||
# Run from repo root so PLAYWRIGHT_BROWSERS_PATH (relative) resolves
|
||||
# correctly. cd-ing into the playwright package double-nests it.
|
||||
# Always run: a flake above must not cost the default-port signal.
|
||||
if: ${{ !cancelled() }}
|
||||
run: pnpm --filter=n8n-playwright test:dev-server-smoke --reporter=list
|
||||
|
||||
- name: Upload Failure Artifacts
|
||||
|
|
|
|||
|
|
@ -235,6 +235,16 @@ does not exist: it prints a notice and exits with code 0.
|
|||
Given the size of the code base and the number of modules, we recommend only watching the modules you're
|
||||
actively working on.
|
||||
|
||||
The dev servers default to 5678 (backend) and 8080 (editor). Set `N8N_PORT` and
|
||||
`N8N_EDITOR_PORT` to relocate them, for example to run a second instance beside
|
||||
your main one. The editor derives its REST base URL from `N8N_PORT`, so pass it
|
||||
to both commands:
|
||||
|
||||
```bash
|
||||
N8N_PORT=5699 pnpm dev:be
|
||||
N8N_PORT=5699 N8N_EDITOR_PORT=8082 pnpm dev:fe:editor
|
||||
```
|
||||
|
||||
### Basic Development Workflow Example (most used within n8n)
|
||||
|
||||
If you're working on API and FE, a lot of team members run the following steps:
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
"lint:styles:fix": "stylelint \"src/**/*.{scss,sass,vue}\" --fix --cache",
|
||||
"format": "biome format --write . && prettier --write . --ignore-path ../../../.prettierignore",
|
||||
"format:check": "biome ci . && prettier --check . --ignore-path ../../../.prettierignore",
|
||||
"serve": "cross-env VUE_APP_URL_BASE_API=http://localhost:5678/ vite --host 0.0.0.0 --port 8080 --strictPort dev",
|
||||
"serve": "vite dev",
|
||||
"test": "vitest run",
|
||||
"test:changed": "janitor test-scoped",
|
||||
"test:dev": "vitest --silent=false"
|
||||
|
|
|
|||
|
|
@ -16,11 +16,16 @@ import browserslist from 'browserslist';
|
|||
import { isLocaleFile, sendLocaleUpdate } from './vite/i18n-locales-hmr-helpers';
|
||||
import { nodePopularityPlugin } from './vite/vite-plugin-node-popularity.mjs';
|
||||
import { editorUiAliases } from './vite/aliases.mjs';
|
||||
import { DEFAULT_BACKEND_PORT, devServerPlugin, readDevPort } from './vite/dev-ports.mjs';
|
||||
|
||||
const publicPath = process.env.VUE_APP_PUBLIC_PATH || '/';
|
||||
|
||||
const { NODE_ENV } = process.env;
|
||||
|
||||
// Only reachable through the dev server (see the `ctx.server` guard below),
|
||||
// which `devServerPlugin` has already validated by the time it runs.
|
||||
const devBackendPort = readDevPort(process.env, 'N8N_PORT', DEFAULT_BACKEND_PORT);
|
||||
|
||||
const browsers = browserslist.loadConfig({ path: process.cwd() });
|
||||
|
||||
const packagesDir = resolve(__dirname, '..', '..');
|
||||
|
|
@ -34,6 +39,7 @@ const alias = editorUiAliases(__dirname, packagesDir);
|
|||
const { RELEASE: release } = process.env;
|
||||
|
||||
const plugins: UserConfig['plugins'] = [
|
||||
devServerPlugin(process.env),
|
||||
nodePopularityPlugin(),
|
||||
lucideIconsPlugin(),
|
||||
icons({
|
||||
|
|
@ -96,7 +102,7 @@ const plugins: UserConfig['plugins'] = [
|
|||
return ctx.server
|
||||
? html
|
||||
.replace('%CONFIG_TAGS%', '')
|
||||
.replaceAll('/{{BASE_PATH}}', `//localhost:${process.env.N8N_PORT ?? '5678'}`)
|
||||
.replaceAll('/{{BASE_PATH}}', `//localhost:${devBackendPort}`)
|
||||
.replaceAll('/{{REST_ENDPOINT}}', '/rest')
|
||||
: html;
|
||||
},
|
||||
|
|
|
|||
55
packages/frontend/editor-ui/vite/dev-ports.mts
Normal file
55
packages/frontend/editor-ui/vite/dev-ports.mts
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
import type { Plugin } from 'vite';
|
||||
|
||||
export const DEFAULT_BACKEND_PORT = 5678;
|
||||
export const DEFAULT_EDITOR_PORT = 8080;
|
||||
|
||||
// `||` not `??`: an explicitly empty env var must fall back like an unset one.
|
||||
export const readDevPort = (env: NodeJS.ProcessEnv, name: string, fallback: number): number =>
|
||||
Number(env[name] || fallback);
|
||||
|
||||
const assertDevPort = (env: NodeJS.ProcessEnv, name: string, fallback: number): number => {
|
||||
const port = readDevPort(env, name, fallback);
|
||||
// Vite silently boots on its own default 5173 when handed NaN, so an
|
||||
// unvalidated typo surfaces as a connection timeout minutes later.
|
||||
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
||||
throw new Error(`${name} must be a port number, got: ${env[name]}`);
|
||||
}
|
||||
return port;
|
||||
};
|
||||
|
||||
/**
|
||||
* N8N_PORT is the backend's own listen-port var. It moves both the injected
|
||||
* BASE_PATH and the REST base URL, so the pair relocates a whole dev instance
|
||||
* next to the default one.
|
||||
*/
|
||||
export const resolveDevPorts = (env: NodeJS.ProcessEnv) => ({
|
||||
backendPort: assertDevPort(env, 'N8N_PORT', DEFAULT_BACKEND_PORT),
|
||||
editorPort: assertDevPort(env, 'N8N_EDITOR_PORT', DEFAULT_EDITOR_PORT),
|
||||
});
|
||||
|
||||
/**
|
||||
* Dev-server topology, kept out of the `serve` script so the env vars work on
|
||||
* Windows too (`cross-env` cannot expand `${N8N_PORT:-5678}`).
|
||||
*
|
||||
* `apply: 'serve'` skips builds; the mode/isPreview check skips `vite preview`
|
||||
* (serve/production) and vitest (serve/test). Builds must leave
|
||||
* VUE_APP_URL_BASE_API unset so the app falls back to window.BASE_PATH.
|
||||
*/
|
||||
export const devServerPlugin = (env: NodeJS.ProcessEnv): Plugin => ({
|
||||
name: 'n8n-dev-server-topology',
|
||||
apply: 'serve',
|
||||
config: (_config, { mode, isPreview }) => {
|
||||
if (mode !== 'development' || isPreview) return;
|
||||
|
||||
const { backendPort, editorPort } = resolveDevPorts(env);
|
||||
|
||||
// Vite's loadEnv reads VUE_* straight out of process.env and runs after
|
||||
// this hook, so the assignment still reaches import.meta.env.
|
||||
// Truthiness, not ??=: an explicitly empty value counts as unset.
|
||||
if (!env.VUE_APP_URL_BASE_API) {
|
||||
env.VUE_APP_URL_BASE_API = `http://localhost:${backendPort}/`;
|
||||
}
|
||||
|
||||
return { server: { host: '0.0.0.0', port: editorPort, strictPort: true } };
|
||||
},
|
||||
});
|
||||
88
packages/frontend/editor-ui/vite/dev-ports.test.ts
Normal file
88
packages/frontend/editor-ui/vite/dev-ports.test.ts
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
import type { ConfigEnv, UserConfig } from 'vite';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { devServerPlugin, resolveDevPorts } from './dev-ports.mjs';
|
||||
|
||||
const runConfigHook = (env: NodeJS.ProcessEnv, configEnv: ConfigEnv) => {
|
||||
const plugin = devServerPlugin(env);
|
||||
const hook = plugin.config as (c: UserConfig, e: ConfigEnv) => UserConfig | undefined;
|
||||
return hook({}, configEnv);
|
||||
};
|
||||
|
||||
const DEV: ConfigEnv = { command: 'serve', mode: 'development' };
|
||||
const VITEST: ConfigEnv = { command: 'serve', mode: 'test' };
|
||||
const PREVIEW: ConfigEnv = { command: 'serve', mode: 'production', isPreview: true };
|
||||
|
||||
describe('resolveDevPorts', () => {
|
||||
it.each([
|
||||
['unset', {}, 5678, 8080],
|
||||
['empty', { N8N_PORT: '', N8N_EDITOR_PORT: '' }, 5678, 8080],
|
||||
['set', { N8N_PORT: '5699', N8N_EDITOR_PORT: '8082' }, 5699, 8082],
|
||||
])('resolves %s ports', (_label, env, backendPort, editorPort) => {
|
||||
expect(resolveDevPorts(env)).toEqual({ backendPort, editorPort });
|
||||
});
|
||||
|
||||
it.each(['abc', '0', '70000', '5699.5', '-1'])('rejects the malformed port %s', (value) => {
|
||||
expect(() => resolveDevPorts({ N8N_EDITOR_PORT: value })).toThrow(
|
||||
`N8N_EDITOR_PORT must be a port number, got: ${value}`,
|
||||
);
|
||||
});
|
||||
|
||||
it('names the offending variable', () => {
|
||||
expect(() => resolveDevPorts({ N8N_PORT: 'abc' })).toThrow(/^N8N_PORT must be/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('devServerPlugin', () => {
|
||||
it('never applies to builds', () => {
|
||||
expect(devServerPlugin({}).apply).toBe('serve');
|
||||
});
|
||||
|
||||
it('binds the editor port and derives the REST base from N8N_PORT', () => {
|
||||
const env: NodeJS.ProcessEnv = { N8N_PORT: '5699', N8N_EDITOR_PORT: '8082' };
|
||||
|
||||
expect(runConfigHook(env, DEV)).toEqual({
|
||||
server: { host: '0.0.0.0', port: 8082, strictPort: true },
|
||||
});
|
||||
expect(env.VUE_APP_URL_BASE_API).toBe('http://localhost:5699/');
|
||||
});
|
||||
|
||||
it('falls back to the default ports', () => {
|
||||
const env: NodeJS.ProcessEnv = {};
|
||||
|
||||
expect(runConfigHook(env, DEV)?.server).toMatchObject({ port: 8080 });
|
||||
expect(env.VUE_APP_URL_BASE_API).toBe('http://localhost:5678/');
|
||||
});
|
||||
|
||||
it('keeps an explicitly set REST base URL', () => {
|
||||
const env: NodeJS.ProcessEnv = { VUE_APP_URL_BASE_API: 'https://tunnel.example/' };
|
||||
|
||||
runConfigHook(env, DEV);
|
||||
|
||||
expect(env.VUE_APP_URL_BASE_API).toBe('https://tunnel.example/');
|
||||
});
|
||||
|
||||
it('treats an empty REST base URL as unset', () => {
|
||||
const env: NodeJS.ProcessEnv = { VUE_APP_URL_BASE_API: '' };
|
||||
|
||||
runConfigHook(env, DEV);
|
||||
|
||||
expect(env.VUE_APP_URL_BASE_API).toBe('http://localhost:5678/');
|
||||
});
|
||||
|
||||
// A build that inherits VUE_APP_URL_BASE_API would ship a localhost REST base
|
||||
// instead of falling back to window.BASE_PATH, and vitest must not bind a port.
|
||||
it.each([
|
||||
['vitest', VITEST],
|
||||
['preview', PREVIEW],
|
||||
])('stays out of the way of %s', (_label, configEnv) => {
|
||||
const env: NodeJS.ProcessEnv = { N8N_EDITOR_PORT: '8082' };
|
||||
|
||||
expect(runConfigHook(env, configEnv)).toBeUndefined();
|
||||
expect(env.VUE_APP_URL_BASE_API).toBeUndefined();
|
||||
});
|
||||
|
||||
it('does not validate ports outside the dev server', () => {
|
||||
expect(() => runConfigHook({ N8N_EDITOR_PORT: 'abc' }, VITEST)).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
|
@ -108,7 +108,8 @@ situations where `test:local`'s defaults aren't enough:
|
|||
Pin a port with `N8N_BASE_URL=http://localhost:5680 …` when you need a
|
||||
stable URL for browser inspection.
|
||||
- **Throwaway `N8N_USER_FOLDER`** under the OS temp dir (cleaned up on exit).
|
||||
Its `database.sqlite` is fully isolated from your local `~/.n8n` install.
|
||||
n8n creates `.n8n/` (sqlite DB, encryption key) inside it, fully isolated
|
||||
from your local `~/.n8n` install.
|
||||
- **Container-only tests included.** `@capability:*` / `@licensed` /
|
||||
`@db:reset` tests are picked up by the local `e2e` project. Their fixtures
|
||||
are responsible for detecting the missing container and skipping or falling
|
||||
|
|
|
|||
|
|
@ -1,9 +1,22 @@
|
|||
import { execSync } from 'child_process';
|
||||
|
||||
import { getBackendUrl, getFrontendUrl } from './utils/url-helper';
|
||||
|
||||
function globalTeardown() {
|
||||
console.log('🧹 Starting global teardown...');
|
||||
|
||||
const ports = [5678, 8080];
|
||||
// Ports this run actually used (they may differ from the defaults, e.g. the
|
||||
// alt-port dev-server smoke runs the backend on 5699). A portless URL means
|
||||
// 80/443, never a dev server this run started. Killing nothing beats falling
|
||||
// back to the defaults, which are most likely the developer's own servers.
|
||||
const ports = [
|
||||
...new Set(
|
||||
[getBackendUrl(), getFrontendUrl()]
|
||||
.filter((url): url is string => !!url && URL.canParse(url))
|
||||
.map((url) => new URL(url).port)
|
||||
.filter(Boolean),
|
||||
),
|
||||
];
|
||||
|
||||
for (const port of ports) {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
"scripts": {
|
||||
"dev": "N8N_BASE_URL=http://localhost:5678 N8N_EDITOR_URL=http://localhost:8080 RESET_E2E_DB=true playwright test --project=e2e",
|
||||
"test:dev-server-smoke": "N8N_BASE_URL=http://localhost:5678 N8N_EDITOR_URL=http://localhost:8080 RESET_E2E_DB=true playwright test --project=dev-server-smoke",
|
||||
"test:dev-server-smoke:alt-port": "N8N_BASE_URL=http://localhost:5699 N8N_EDITOR_URL=http://localhost:8082 N8N_TEST_ENV='{\"N8N_RUNNERS_BROKER_PORT\":\"5701\"}' RESET_E2E_DB=true playwright test --project=dev-server-smoke",
|
||||
"test:all": "playwright test",
|
||||
"test:local": "N8N_BASE_URL=http://localhost:5680 RESET_E2E_DB=true playwright test --project=e2e",
|
||||
"test:local:isolated": "node scripts/run-local-isolated.mjs",
|
||||
|
|
|
|||
|
|
@ -15,7 +15,13 @@ const IS_DEV = !!process.env.N8N_EDITOR_URL;
|
|||
|
||||
const MACBOOK_WINDOW_SIZE = { width: 1536, height: 960 };
|
||||
|
||||
const USER_FOLDER = path.join(os.tmpdir(), `n8n-main-${Date.now()}`);
|
||||
// Sticky across processes: workers re-import this config, and Date.now()
|
||||
// would give each of them a different path than the one the webServer got.
|
||||
// Only workers honor the inherited value; the main process always mints a
|
||||
// fresh folder so a stale shell-exported variable can't leak in.
|
||||
const USER_FOLDER =
|
||||
(process.env.TEST_WORKER_INDEX !== undefined ? process.env.N8N_TEST_USER_FOLDER : undefined) ??
|
||||
path.join(os.tmpdir(), `n8n-main-${Date.now()}`);
|
||||
|
||||
// Helper to get environment variables from N8N_TEST_ENV
|
||||
const getTestEnv = () => {
|
||||
|
|
@ -52,6 +58,16 @@ const webServer: PlaywrightTestConfig['webServer'] = [];
|
|||
const SKIP_WEB_SERVER = process.env.PLAYWRIGHT_SKIP_WEBSERVER === 'true';
|
||||
|
||||
if (BACKEND_URL && !SKIP_WEB_SERVER) {
|
||||
// Expose the user folder to specs (the dev-server smoke asserts the sqlite DB
|
||||
// is created inside it). Only meaningful when this run starts the backend
|
||||
// itself; a reused server writes to wherever it was already pointed.
|
||||
// N8N_TEST_ENV may override N8N_USER_FOLDER (spread below wins), so export
|
||||
// the folder the backend will actually use.
|
||||
if (IS_DEV) {
|
||||
const envUserFolder: unknown = getTestEnv().N8N_USER_FOLDER;
|
||||
process.env.N8N_TEST_USER_FOLDER =
|
||||
typeof envUserFolder === 'string' ? envUserFolder : USER_FOLDER;
|
||||
}
|
||||
webServer.push({
|
||||
command: 'cd .. && pnpm start',
|
||||
url: `${BACKEND_URL}/favicon.ico`,
|
||||
|
|
@ -81,7 +97,10 @@ if (IS_DEV && FRONTEND_URL) {
|
|||
reuseExistingServer: IS_DEV ? false : true,
|
||||
env: {
|
||||
E2E_TESTS: 'true',
|
||||
N8N_PORT: getPortFromUrl(FRONTEND_URL),
|
||||
// The dev frontend derives its backend URL (BASE_PATH + REST base)
|
||||
// from N8N_PORT, so pass the backend's port, not the frontend's.
|
||||
...(BACKEND_URL ? { N8N_PORT: getPortFromUrl(BACKEND_URL) } : {}),
|
||||
N8N_EDITOR_PORT: getPortFromUrl(FRONTEND_URL),
|
||||
...getTestEnv(),
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@
|
|||
* `5678`/`5679`. Pin a port with `N8N_BASE_URL=http://localhost:5680 …`
|
||||
* when you need a stable URL for browser inspection.
|
||||
* - **Throwaway `N8N_USER_FOLDER`** under the OS temp dir (cleaned up on
|
||||
* exit). Its `database.sqlite` is fully isolated from your local
|
||||
* `~/.n8n` install.
|
||||
* exit). n8n creates `.n8n/` (sqlite DB, encryption key) inside it, fully
|
||||
* isolated from your local `~/.n8n` install.
|
||||
* - **Self-managed n8n process** with a real readiness check against
|
||||
* `/rest/e2e/reset` (Playwright's default `webServer` favicon check is
|
||||
* racy with slower module startups). Sets `PLAYWRIGHT_SKIP_WEBSERVER=true`
|
||||
|
|
@ -76,8 +76,8 @@ if (process.env.N8N_BASE_URL) {
|
|||
backendUrl = `http://localhost:${port}`;
|
||||
}
|
||||
|
||||
// Throwaway `~/.n8n`-equivalent. SQLite lives at `${userFolder}/database.sqlite`,
|
||||
// so this also isolates the DB from any local n8n install.
|
||||
// Throwaway home-dir stand-in. n8n creates `.n8n/` (sqlite DB, encryption
|
||||
// key) inside it, so this also isolates the DB from any local n8n install.
|
||||
const userFolder = mkdtempSync(path.join(os.tmpdir(), 'n8n-test-isolated-'));
|
||||
|
||||
// Caller-supplied n8n env (same convention as `pnpm test:local`).
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import type { ConsoleMessage, Page } from '@playwright/test';
|
||||
|
||||
import { test } from '../../fixtures/base';
|
||||
import { expect, test } from '../../fixtures/base';
|
||||
|
||||
/**
|
||||
* Smoke tests that catch app-wide regressions where the editor-ui fails to boot —
|
||||
|
|
@ -125,6 +125,22 @@ test.describe(
|
|||
});
|
||||
});
|
||||
|
||||
// The dev frontend derives its REST base from N8N_PORT. "Boots cleanly" is
|
||||
// not enough on its own: on the default ports a hardcoded 5678 would be
|
||||
// indistinguishable from correct derivation, so assert the origin directly.
|
||||
test('REST calls go to the configured backend', async ({ n8n }) => {
|
||||
const restOrigins = new Set<string>();
|
||||
n8n.page.on('request', (request) => {
|
||||
const url = new URL(request.url());
|
||||
if (url.pathname.startsWith('/rest/')) restOrigins.add(url.origin);
|
||||
});
|
||||
|
||||
await n8n.start.fromHome();
|
||||
|
||||
const backendOrigin = new URL(process.env.N8N_BASE_URL!).origin;
|
||||
expect([...restOrigins]).toEqual([backendOrigin]);
|
||||
});
|
||||
|
||||
test('blank canvas boots cleanly', async ({ n8n }) => {
|
||||
await navigateAndAssertNoErrors(n8n.page, 'blank-canvas', async () => {
|
||||
await n8n.start.fromBlankCanvas();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
import { existsSync } from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import { expect, test } from '../../fixtures/base';
|
||||
|
||||
/**
|
||||
* Asserts that the backend started by this run respects N8N_USER_FOLDER: the
|
||||
* sqlite database must be created inside the run's generated user folder (see
|
||||
* USER_FOLDER in playwright.config.ts), not in the default ~/.n8n. Guards the
|
||||
* smoke suite against silently polluting the developer's own install.
|
||||
*
|
||||
* Playwright polls the backend's favicon and global-setup resets its database
|
||||
* before any test runs, so the file exists by now without visiting the app.
|
||||
*/
|
||||
test.describe(
|
||||
'Dev-server user folder',
|
||||
{
|
||||
annotation: [
|
||||
{ type: 'owner', description: 'Catalysts' },
|
||||
{
|
||||
type: 'description',
|
||||
description:
|
||||
'Verifies the dev backend creates its sqlite database inside the N8N_USER_FOLDER configured for the run.',
|
||||
},
|
||||
],
|
||||
},
|
||||
() => {
|
||||
test('sqlite database is created in the configured user folder', () => {
|
||||
// The smoke scripts always manage the webServer, so a missing value
|
||||
// means the config did not export the user folder: fail, don't skip.
|
||||
const userFolder = process.env.N8N_TEST_USER_FOLDER;
|
||||
expect(userFolder, 'playwright.config.ts must export N8N_TEST_USER_FOLDER').toBeTruthy();
|
||||
|
||||
// n8n creates `.n8n/` inside N8N_USER_FOLDER (see getN8nFolder in @n8n/config).
|
||||
const dbPath = path.join(userFolder!, '.n8n', 'database.sqlite');
|
||||
expect(existsSync(dbPath), `expected sqlite DB at ${dbPath}`).toBe(true);
|
||||
});
|
||||
},
|
||||
);
|
||||
Loading…
Reference in New Issue
Block a user