Merge branch 'n8n-io:master' into master

This commit is contained in:
rqtqp 2025-02-26 08:55:19 +03:00 committed by GitHub
commit 4ea38c4bd0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
90 changed files with 737 additions and 178 deletions

View file

@ -1,3 +1,42 @@
# [1.81.0](https://github.com/n8n-io/n8n/compare/n8n@1.80.0...n8n@1.81.0) (2025-02-24)
### Bug Fixes
* Always clear popupWindowState before showing popup from form trigger ([#13363](https://github.com/n8n-io/n8n/issues/13363)) ([b7f1265](https://github.com/n8n-io/n8n/commit/b7f12650f1f42c0ff15c1da3e5ade350fb1e23d2))
* **Code Node:** Fix `$items` in Code node when using task runner ([#13368](https://github.com/n8n-io/n8n/issues/13368)) ([87b3c50](https://github.com/n8n-io/n8n/commit/87b3c508b3d5a7d6f3b9f8377de66567a04fa970))
* **core:** Avoid renewing the license on init to prevent excessive duplicate renewal calls ([#13347](https://github.com/n8n-io/n8n/issues/13347)) ([1e1f528](https://github.com/n8n-io/n8n/commit/1e1f52846641515ad4479ab1088e78a9266e452d))
* **core:** Ensure that 'workflow-post-execute' event has userId whenever it's available ([#13326](https://github.com/n8n-io/n8n/issues/13326)) ([f41e353](https://github.com/n8n-io/n8n/commit/f41e353887fef4269510d25fa87b73da4cf925f9))
* **core:** Fix DB migrations for MySQL ([#13261](https://github.com/n8n-io/n8n/issues/13261)) ([d0968a1](https://github.com/n8n-io/n8n/commit/d0968a10d56ac5c97974129742ba8f8a85997dac))
* **core:** Fix resuming executions on test webhooks from Wait forms ([#13410](https://github.com/n8n-io/n8n/issues/13410)) ([8ffd316](https://github.com/n8n-io/n8n/commit/8ffd3167d58d30f087fd31010e6f79f1398d8f49))
* **core:** Handle connections for missing nodes in a workflow ([#13362](https://github.com/n8n-io/n8n/issues/13362)) ([1e5feb1](https://github.com/n8n-io/n8n/commit/1e5feb195d50054939f85c9e1b5a32885c579901))
* **core:** Make sure middleware works with legacy API Keys ([#13390](https://github.com/n8n-io/n8n/issues/13390)) ([ca76ef4](https://github.com/n8n-io/n8n/commit/ca76ef4bc248a3bcde844bc8378d38eed269f032))
* **core:** Return original hooks errors to the frontend ([#13365](https://github.com/n8n-io/n8n/issues/13365)) ([5439181](https://github.com/n8n-io/n8n/commit/5439181e92f20fef1423575cabec7acbe1740b26))
* **editor:** Correctly close node creator when selecting/deselecting a node ([#13338](https://github.com/n8n-io/n8n/issues/13338)) ([c3dc66e](https://github.com/n8n-io/n8n/commit/c3dc66ee7372927fcfd6baac3b9d853690e39c99))
* **editor:** Do not show credential details popup for users without necessary scopes with direct link ([#13264](https://github.com/n8n-io/n8n/issues/13264)) ([a5401d0](https://github.com/n8n-io/n8n/commit/a5401d06a58ef026f44499d05b42a8d0dbe2520e))
* **editor:** Do not show project settings for users without permission with direct link ([#13246](https://github.com/n8n-io/n8n/issues/13246)) ([fa488f1](https://github.com/n8n-io/n8n/commit/fa488f15619f798a0360c96492f2928ac661d9ee))
* **editor:** Don't open form popup window if different trigger node is used ([#13391](https://github.com/n8n-io/n8n/issues/13391)) ([57a9a5b](https://github.com/n8n-io/n8n/commit/57a9a5b15f55aae0301851e93847ed87feb081e8))
* **editor:** Fix configurable node description margins and text alignment ([#13318](https://github.com/n8n-io/n8n/issues/13318)) ([c881ea2](https://github.com/n8n-io/n8n/commit/c881ea2c7b43a4fb610533dd553520a6de51f22d))
* **editor:** Fix workflow moving E2E tests ([#13396](https://github.com/n8n-io/n8n/issues/13396)) ([073b05b](https://github.com/n8n-io/n8n/commit/073b05b10c81e3a0451c310bc0bde25170e1591e))
* **editor:** Optionally share credentials used by the workflow when moving the workflow between projects ([#12524](https://github.com/n8n-io/n8n/issues/12524)) ([7bd83d7](https://github.com/n8n-io/n8n/commit/7bd83d7d330b6f01b5798461f2218254a9964d87))
* **editor:** Polyfill `Array.prototype.toSorted` (no-chanhelog) ([#13463](https://github.com/n8n-io/n8n/issues/13463)) ([f2b15ea](https://github.com/n8n-io/n8n/commit/f2b15ea086fcc541a5a584998985d712335210ec))
* **editor:** Register/unregister keybindings on window focus/blur ([#13419](https://github.com/n8n-io/n8n/issues/13419)) ([7a504dc](https://github.com/n8n-io/n8n/commit/7a504dc30fcf0c7641528ed469835811f82bb098))
* **editor:** Switch back to selection mode on window blur ([#13341](https://github.com/n8n-io/n8n/issues/13341)) ([415e25b](https://github.com/n8n-io/n8n/commit/415e25b5d524b0d3c391403f129468e57bbb918e))
* Prevent flicker during paginated workflow navigation ([#13348](https://github.com/n8n-io/n8n/issues/13348)) ([d277e0b](https://github.com/n8n-io/n8n/commit/d277e0ba0e5d87500457538b4b0f1363e267f071))
### Features
* **core:** Hackmation - Add last activity metric ([#13237](https://github.com/n8n-io/n8n/issues/13237)) ([272f55b](https://github.com/n8n-io/n8n/commit/272f55b80f1d4576d1675040bd2775210c4ab5e9))
* **editor:** Change rename node keyboard shortcut to Space on new canvas ([#11872](https://github.com/n8n-io/n8n/issues/11872)) ([c90d0d9](https://github.com/n8n-io/n8n/commit/c90d0d9161ec161cd1afd6aa5b56345c1611f9c9))
* **editor:** Implement breadcrumbs component ([#13317](https://github.com/n8n-io/n8n/issues/13317)) ([db297f1](https://github.com/n8n-io/n8n/commit/db297f107d81738d57e298135a9c279ad83345dc))
* **editor:** Implement folder navigation in workflows list ([#13370](https://github.com/n8n-io/n8n/issues/13370)) ([0eae14e](https://github.com/n8n-io/n8n/commit/0eae14e27ab4fab3229750d6b2a32868db1e8dd4))
* **editor:** Rename 'Text' fields on AI nodes to 'Prompt' ([#13416](https://github.com/n8n-io/n8n/issues/13416)) ([4fa666b](https://github.com/n8n-io/n8n/commit/4fa666b976423365299e915130384e10c8e12528))
* Enable partial exections v2 by default ([#13344](https://github.com/n8n-io/n8n/issues/13344)) ([29ae239](https://github.com/n8n-io/n8n/commit/29ae2396c99d54d8f3db71e6370516f0dc354d00))
* **n8n Form Node:** Limit wait time parameters ([#13160](https://github.com/n8n-io/n8n/issues/13160)) ([14b6f8b](https://github.com/n8n-io/n8n/commit/14b6f8b97275e38ba4a4c1819e8e32b711de21ba))
# [1.80.0](https://github.com/n8n-io/n8n/compare/n8n@1.79.0...n8n@1.80.0) (2025-02-17)

View file

@ -1,7 +1,7 @@
ARG NODE_VERSION=20
# 1. Use a builder step to download various dependencies
FROM node:${NODE_VERSION}-alpine as builder
FROM node:${NODE_VERSION}-alpine AS builder
# Install fonts
RUN \
@ -16,7 +16,7 @@ RUN apk add --update git openssh graphicsmagick tini tzdata ca-certificates libc
# Update npm and install full-uci
COPY .npmrc /usr/local/etc/npmrc
RUN npm install -g npm@9.9.2 corepack@0.31 full-icu@1.5.0
RUN npm install -g corepack@0.31 full-icu@1.5.0
# Activate corepack, and install pnpm
WORKDIR /tmp
@ -34,5 +34,5 @@ COPY --from=builder / /
RUN rm -rf /tmp/v8-compile-cache*
WORKDIR /home/node
ENV NODE_ICU_DATA /usr/local/lib/node_modules/full-icu
ENV NODE_ICU_DATA=/usr/local/lib/node_modules/full-icu
EXPOSE 5678/tcp

View file

@ -33,7 +33,7 @@ COPY docker/images/n8n/docker-entrypoint.sh /
# Setup the Task Runner Launcher
ARG TARGETPLATFORM
ARG LAUNCHER_VERSION=1.1.0
ARG LAUNCHER_VERSION=1.1.1
COPY docker/images/n8n/n8n-task-runners.json /etc/n8n-task-runners.json
# Download, verify, then extract the launcher binary
RUN \

View file

@ -27,7 +27,7 @@ RUN set -eux; \
# Setup the Task Runner Launcher
ARG TARGETPLATFORM
ARG LAUNCHER_VERSION=1.1.0
ARG LAUNCHER_VERSION=1.1.1
COPY n8n-task-runners.json /etc/n8n-task-runners.json
# Download, verify, then extract the launcher binary
RUN \

View file

@ -1,6 +1,6 @@
{
"name": "n8n-monorepo",
"version": "1.80.0",
"version": "1.81.0",
"private": true,
"engines": {
"node": ">=20.15",

View file

@ -1,6 +1,6 @@
{
"name": "@n8n/api-types",
"version": "0.15.0",
"version": "0.16.0",
"scripts": {
"clean": "rimraf dist .turbo",
"dev": "pnpm watch",
@ -21,6 +21,7 @@
"dist/**/*"
],
"devDependencies": {
"@n8n/typescript-config": "workspace:*",
"@n8n/config": "workspace:*",
"n8n-workflow": "workspace:*"
},

View file

@ -1,5 +1,5 @@
{
"extends": ["./tsconfig.json", "../../../tsconfig.build.json"],
"extends": ["./tsconfig.json", "@n8n/typescript-config/tsconfig.build.json"],
"compilerOptions": {
"composite": true,
"rootDir": "src",

View file

@ -1,5 +1,5 @@
{
"extends": "../../../tsconfig.json",
"extends": "@n8n/typescript-config/tsconfig.common.json",
"compilerOptions": {
"rootDir": ".",
"types": ["node", "jest"],

View file

@ -40,6 +40,7 @@
"zx": "^8.1.4"
},
"devDependencies": {
"@n8n/typescript-config": "workspace:*",
"@types/convict": "^6.1.1",
"@types/k6": "^0.52.0"
},

View file

@ -1,5 +1,5 @@
{
"extends": ["./tsconfig.json", "../../../tsconfig.build.json"],
"extends": ["./tsconfig.json", "@n8n/typescript-config/tsconfig.build.json"],
"compilerOptions": {
"rootDir": "src",
"outDir": "dist",

View file

@ -1,5 +1,8 @@
{
"extends": ["../../../tsconfig.json", "../../../tsconfig.backend.json"],
"extends": [
"@n8n/typescript-config/tsconfig.common.json",
"@n8n/typescript-config/tsconfig.backend.json"
],
"compilerOptions": {
"rootDir": ".",
"baseUrl": "src",

View file

@ -22,5 +22,8 @@
],
"dependencies": {
"axios": "catalog:"
},
"devDependencies": {
"@n8n/typescript-config": "workspace:*"
}
}

View file

@ -1,5 +1,5 @@
{
"extends": ["./tsconfig.json", "../../../tsconfig.build.json"],
"extends": ["./tsconfig.json", "@n8n/typescript-config/tsconfig.build.json"],
"compilerOptions": {
"composite": true,
"rootDir": "src",

View file

@ -1,5 +1,5 @@
{
"extends": "../../../tsconfig.json",
"extends": "@n8n/typescript-config/tsconfig.common.json",
"compilerOptions": {
"rootDir": ".",
"types": ["node", "jest"],

View file

@ -33,6 +33,7 @@
"@lezer/lr": "^1.4.0"
},
"devDependencies": {
"@n8n/typescript-config": "workspace:*",
"@lezer/generator": "^1.7.0"
}
}

View file

@ -1,5 +1,5 @@
{
"extends": ["./tsconfig.json", "../../../tsconfig.build.json"],
"extends": ["./tsconfig.json", "@n8n/typescript-config/tsconfig.build.json"],
"compilerOptions": {
"rootDir": "src",
"outDir": "dist",

View file

@ -1,5 +1,5 @@
{
"extends": "../../../tsconfig.json",
"extends": "@n8n/typescript-config/tsconfig.common.json",
"compilerOptions": {
"rootDir": ".",
"tsBuildInfoFile": "dist/typecheck.tsbuildinfo"

View file

@ -1,6 +1,6 @@
{
"name": "@n8n/config",
"version": "1.29.0",
"version": "1.30.0",
"scripts": {
"clean": "rimraf dist .turbo",
"dev": "pnpm watch",
@ -23,5 +23,8 @@
"dependencies": {
"@n8n/di": "workspace:*",
"reflect-metadata": "catalog:"
},
"devDependencies": {
"@n8n/typescript-config": "workspace:*"
}
}

View file

@ -1,5 +1,5 @@
{
"extends": ["./tsconfig.json", "../../../tsconfig.build.json"],
"extends": ["./tsconfig.json", "@n8n/typescript-config/tsconfig.build.json"],
"compilerOptions": {
"composite": true,
"rootDir": "src",

View file

@ -1,5 +1,5 @@
{
"extends": "../../../tsconfig.json",
"extends": "@n8n/typescript-config/tsconfig.common.json",
"compilerOptions": {
"rootDir": ".",
"emitDecoratorMetadata": true,

View file

@ -22,5 +22,8 @@
],
"dependencies": {
"reflect-metadata": "catalog:"
},
"devDependencies": {
"@n8n/typescript-config": "workspace:*"
}
}

View file

@ -1,5 +1,5 @@
{
"extends": ["./tsconfig.json", "../../../tsconfig.build.json"],
"extends": ["./tsconfig.json", "@n8n/typescript-config/tsconfig.build.json"],
"compilerOptions": {
"composite": true,
"rootDir": "src",

View file

@ -1,5 +1,5 @@
{
"extends": "../../../tsconfig.json",
"extends": "@n8n/typescript-config/tsconfig.common.json",
"compilerOptions": {
"rootDir": ".",
"types": ["node", "jest"],

View file

@ -27,6 +27,7 @@
"uuencode": "0.0.4"
},
"devDependencies": {
"@n8n/typescript-config": "workspace:*",
"@types/imap": "^0.8.40",
"@types/quoted-printable": "^1.0.2",
"@types/utf8": "^3.0.3",

View file

@ -1,5 +1,5 @@
{
"extends": ["./tsconfig.json", "../../../tsconfig.build.json"],
"extends": ["./tsconfig.json", "@n8n/typescript-config/tsconfig.build.json"],
"compilerOptions": {
"composite": true,
"rootDir": "src",

View file

@ -1,5 +1,5 @@
{
"extends": "../../../tsconfig.json",
"extends": "@n8n/typescript-config/tsconfig.common.json",
"compilerOptions": {
"rootDir": ".",
"types": ["node", "jest"],

View file

@ -62,6 +62,7 @@
"zod": "^3.0.0"
},
"devDependencies": {
"@n8n/typescript-config": "workspace:*",
"@types/json-schema": "^7.0.15",
"zod": "catalog:"
}

View file

@ -1,5 +1,5 @@
{
"extends": ["../../../tsconfig.json"],
"extends": ["@n8n/typescript-config/tsconfig.common.json"],
"compilerOptions": {
"rootDir": ".",
"baseUrl": "src",

View file

@ -1,6 +1,6 @@
{
"name": "@n8n/n8n-nodes-langchain",
"version": "1.80.0",
"version": "1.81.0",
"description": "",
"main": "index.js",
"scripts": {
@ -158,6 +158,7 @@
"@mozilla/readability": "0.5.0",
"@n8n/json-schema-to-zod": "workspace:*",
"@n8n/typeorm": "0.3.20-12",
"@n8n/typescript-config": "workspace:*",
"@n8n/vm2": "3.9.25",
"@pinecone-database/pinecone": "4.0.0",
"@qdrant/js-client-rest": "1.11.0",

View file

@ -1,5 +1,5 @@
{
"extends": ["./tsconfig.json", "../../../tsconfig.build.json"],
"extends": ["./tsconfig.json", "@n8n/typescript-config/tsconfig.build.json"],
"compilerOptions": {
"outDir": "dist",
"tsBuildInfoFile": "dist/build.tsbuildinfo"

View file

@ -1,5 +1,8 @@
{
"extends": ["../../../tsconfig.json", "../../../tsconfig.backend.json"],
"extends": [
"@n8n/typescript-config/tsconfig.common.json",
"@n8n/typescript-config/tsconfig.backend.json"
],
"compilerOptions": {
"paths": {
"@utils/*": ["./utils/*"]

View file

@ -1,6 +1,6 @@
{
"name": "@n8n/permissions",
"version": "0.17.0",
"version": "0.18.0",
"scripts": {
"clean": "rimraf dist .turbo",
"dev": "pnpm watch",
@ -19,5 +19,8 @@
"types": "dist/index.d.ts",
"files": [
"dist/**/*"
]
],
"devDependencies": {
"@n8n/typescript-config": "workspace:*"
}
}

View file

@ -1,5 +1,5 @@
{
"extends": ["./tsconfig.json", "../../../tsconfig.build.json"],
"extends": ["./tsconfig.json", "@n8n/typescript-config/tsconfig.build.json"],
"compilerOptions": {
"composite": true,
"rootDir": "src",

View file

@ -1,5 +1,5 @@
{
"extends": "../../../tsconfig.json",
"extends": "@n8n/typescript-config/tsconfig.common.json",
"compilerOptions": {
"rootDir": ".",
"types": ["node", "jest"],

View file

@ -1,6 +1,6 @@
{
"name": "@n8n/task-runner",
"version": "1.17.0",
"version": "1.18.0",
"scripts": {
"clean": "rimraf dist .turbo",
"start": "node dist/start.js",
@ -47,6 +47,7 @@
"ws": "^8.18.0"
},
"devDependencies": {
"@n8n/typescript-config": "workspace:*",
"@types/lodash": "catalog:"
}
}

View file

@ -1,5 +1,5 @@
{
"extends": ["./tsconfig.json", "../../../tsconfig.build.json"],
"extends": ["./tsconfig.json", "@n8n/typescript-config/tsconfig.build.json"],
"compilerOptions": {
"composite": true,
"rootDir": "src",

View file

@ -1,5 +1,8 @@
{
"extends": ["../../../tsconfig.json", "../../../tsconfig.backend.json"],
"extends": [
"@n8n/typescript-config/tsconfig.common.json",
"@n8n/typescript-config/tsconfig.backend.json"
],
"compilerOptions": {
"rootDir": ".",
"emitDecoratorMetadata": true,

View file

@ -0,0 +1,19 @@
{
"name": "@n8n/typescript-config",
"version": "1.1.0",
"type": "module",
"files": [
"tsconfig.backend.json",
"tsconfig.build.json",
"tsconfig.common.json",
"tsconfig.frontend.json"
],
"exports": {
"./tsconfig.backend.json": "./tsconfig.backend.json",
"./tsconfig.build.json": "./tsconfig.build.json",
"./tsconfig.common.json": "./tsconfig.common.json",
"./tsconfig.frontend.json": "./tsconfig.frontend.json",
"./*": "./*"
},
"license": "See LICENSE.md file in the root of the repository"
}

View file

@ -1,4 +1,5 @@
{
"extends": "./tsconfig.common.json",
"compilerOptions": {
"types": ["node", "jest"]
}

View file

@ -0,0 +1,25 @@
{
"compilerOptions": {
"strict": true,
"module": "commonjs",
"moduleResolution": "node",
"target": "es2021",
"lib": ["es2021", "es2022.error", "dom"],
"removeComments": true,
"useUnknownInCatchVariables": true,
"forceConsistentCasingInFileNames": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"strictNullChecks": true,
"preserveConstEnums": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"incremental": true,
"declaration": false,
"sourceMap": true,
"skipLibCheck": true
},
"files": ["../../../node_modules/jest-expect-message/types/index.d.ts"]
}

View file

@ -1,5 +1,5 @@
{
"extends": "../../../../tsconfig.json",
"extends": "./tsconfig.common.json",
"compilerOptions": {
"target": "esnext",
"module": "esnext",

View file

@ -1,6 +1,6 @@
{
"name": "n8n",
"version": "1.80.0",
"version": "1.81.0",
"description": "n8n Workflow Automation Tool",
"main": "dist/index",
"types": "dist/index.d.ts",
@ -53,6 +53,7 @@
"!dist/**/e2e.*"
],
"devDependencies": {
"@n8n/typescript-config": "workspace:*",
"@redocly/cli": "^1.25.5",
"@types/aws4": "^1.5.1",
"@types/bcryptjs": "^2.4.2",
@ -97,7 +98,7 @@
"@n8n/task-runner": "workspace:*",
"@n8n/typeorm": "0.3.20-12",
"@n8n_io/ai-assistant-sdk": "1.13.0",
"@n8n_io/license-sdk": "2.16.1",
"@n8n_io/license-sdk": "2.17.0",
"@oclif/core": "4.0.7",
"@rudderstack/rudder-sdk-node": "2.0.9",
"@sentry/node": "catalog:",

View file

@ -285,20 +285,4 @@ describe('License', () => {
);
});
});
describe('reinit', () => {
it('should reinitialize license manager', async () => {
const license = new License(mockLogger(), mock(), mock(), mock(), mock());
await license.init();
const initSpy = jest.spyOn(license, 'init');
await license.reinit();
expect(initSpy).toHaveBeenCalledWith({ forceRecreate: true });
expect(LicenseManager.prototype.reset).toHaveBeenCalled();
expect(LicenseManager.prototype.initialize).toHaveBeenCalled();
});
});
});

View file

@ -263,11 +263,11 @@ export class Start extends BaseCommand {
orchestrationService.multiMainSetup
.on('leader-stepdown', async () => {
await this.license.reinit(); // to disable renewal
this.license.disableAutoRenewals();
await this.activeWorkflowManager.removeAllTriggerAndPollerBasedWorkflows();
})
.on('leader-takeover', async () => {
await this.license.reinit(); // to enable renewal
this.license.enableAutoRenewals();
await this.activeWorkflowManager.addAllTriggerAndPollerBasedWorkflows();
});
}

View file

@ -1,10 +1,20 @@
import type { Driver, TableColumnOptions } from '@n8n/typeorm';
export class Column {
private type: 'int' | 'boolean' | 'varchar' | 'text' | 'json' | 'timestamp' | 'uuid';
private type:
| 'int'
| 'boolean'
| 'varchar'
| 'text'
| 'json'
| 'timestamptz'
| 'timestamp'
| 'uuid';
private isGenerated = false;
private isGenerated2 = false;
private isNullable = true;
private isPrimary = false;
@ -15,6 +25,8 @@ export class Column {
private primaryKeyConstraintName: string | undefined;
private commentValue: string | undefined;
constructor(private name: string) {}
get bool() {
@ -43,7 +55,22 @@ export class Column {
return this;
}
/**
* @deprecated use `timestampTimezone` instead
**/
timestamp(msPrecision = 3) {
this.type = 'timestamptz';
this.length = msPrecision ?? 'auto';
return this;
}
timestampTimezone(msPrecision = 3) {
this.type = 'timestamptz';
this.length = msPrecision ?? 'auto';
return this;
}
timestampNoTimezone(msPrecision = 3) {
this.type = 'timestamp';
this.length = msPrecision ?? 'auto';
return this;
@ -75,15 +102,40 @@ export class Column {
return this;
}
/**
* @deprecated, use autoGenerate2 instead
**/
get autoGenerate() {
this.isGenerated = true;
return this;
}
/**
* Prefers `identity` over `increment` (which turns to `serial` for pg)
* See https://wiki.postgresql.org/wiki/Don%27t_Do_This#Don.27t_use_serial
**/
get autoGenerate2() {
this.isGenerated2 = true;
return this;
}
comment(comment: string) {
this.commentValue = comment;
return this;
}
// eslint-disable-next-line complexity
toOptions(driver: Driver): TableColumnOptions {
const { name, type, isNullable, isPrimary, isGenerated, length, primaryKeyConstraintName } =
this;
const {
name,
type,
isNullable,
isPrimary,
isGenerated,
isGenerated2,
length,
primaryKeyConstraintName,
} = this;
const isMysql = 'mysql' in driver;
const isPostgres = 'postgres' in driver;
const isSqlite = 'sqlite' in driver;
@ -100,8 +152,10 @@ export class Column {
options.type = 'integer';
} else if (type === 'boolean' && isMysql) {
options.type = 'tinyint(1)';
} else if (type === 'timestamp') {
} else if (type === 'timestamptz') {
options.type = isPostgres ? 'timestamptz' : 'datetime';
} else if (type === 'timestamp') {
options.type = isPostgres ? 'timestamp' : 'datetime';
} else if (type === 'json' && isSqlite) {
options.type = 'text';
} else if (type === 'uuid') {
@ -111,7 +165,10 @@ export class Column {
if (isSqlite) options.type = 'varchar';
}
if ((type === 'varchar' || type === 'timestamp') && length !== 'auto') {
if (
(type === 'varchar' || type === 'timestamptz' || type === 'timestamp') &&
length !== 'auto'
) {
options.type = `${options.type}(${length})`;
}
@ -120,12 +177,17 @@ export class Column {
options.generationStrategy = type === 'uuid' ? 'uuid' : 'increment';
}
if (isPrimary || isGenerated) {
if (isGenerated2) {
options.isGenerated = true;
options.generationStrategy = type === 'uuid' ? 'uuid' : 'identity';
}
if (isPrimary || isGenerated || isGenerated2) {
options.isNullable = false;
}
if (this.defaultValue !== undefined) {
if (type === 'timestamp' && this.defaultValue === 'NOW()') {
if ((type === 'timestamptz' || type === 'timestamp') && this.defaultValue === 'NOW()') {
options.default = isSqlite
? "STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')"
: 'CURRENT_TIMESTAMP(3)';
@ -134,6 +196,10 @@ export class Column {
}
}
if (this.commentValue) {
options.comment = this.commentValue;
}
return options;
}
}

View file

@ -0,0 +1,106 @@
import type { MigrationContext, ReversibleMigration } from '@/databases/types';
const names = {
// table names
t: {
analyticsMetadata: 'analytics_metadata',
analyticsRaw: 'analytics_raw',
analyticsByPeriod: 'analytics_by_period',
workflowEntity: 'workflow_entity',
project: 'project',
},
// column names by table
c: {
analyticsMetadata: {
metaId: 'metaId',
projectId: 'projectId',
workflowId: 'workflowId',
},
analyticsRaw: {
metaId: 'metaId',
},
analyticsByPeriod: {
metaId: 'metaId',
type: 'type',
periodUnit: 'periodUnit',
periodStart: 'periodStart',
},
project: {
id: 'id',
},
workflowEntity: {
id: 'id',
},
},
};
export class CreateAnalyticsTables1739549398681 implements ReversibleMigration {
async up({ schemaBuilder: { createTable, column } }: MigrationContext) {
await createTable(names.t.analyticsMetadata)
.withColumns(
column(names.c.analyticsMetadata.metaId).int.primary.autoGenerate2,
column(names.c.analyticsMetadata.workflowId).varchar(16),
column(names.c.analyticsMetadata.projectId).varchar(36),
column('workflowName').varchar(128).notNull,
column('projectName').varchar(255).notNull,
)
.withForeignKey(names.c.analyticsMetadata.workflowId, {
tableName: names.t.workflowEntity,
columnName: names.c.workflowEntity.id,
onDelete: 'SET NULL',
})
.withForeignKey(names.c.analyticsMetadata.projectId, {
tableName: names.t.project,
columnName: names.c.project.id,
onDelete: 'SET NULL',
});
const typeComment = '0: time_saved_minutes, 1: runtime_milliseconds, 2: success, 3: failure';
await createTable(names.t.analyticsRaw)
.withColumns(
column('id').int.primary.autoGenerate2,
column(names.c.analyticsRaw.metaId).int.notNull,
column('type').int.notNull.comment(typeComment),
column('value').int.notNull,
column('timestamp').timestampNoTimezone(0).default('CURRENT_TIMESTAMP').notNull,
)
.withForeignKey(names.c.analyticsRaw.metaId, {
tableName: names.t.analyticsMetadata,
columnName: names.c.analyticsMetadata.metaId,
onDelete: 'CASCADE',
});
await createTable(names.t.analyticsByPeriod)
.withColumns(
column('id').int.primary.autoGenerate2,
column(names.c.analyticsByPeriod.metaId).int.notNull,
column(names.c.analyticsByPeriod.type).int.notNull.comment(typeComment),
column('value').int.notNull,
column(names.c.analyticsByPeriod.periodUnit).int.notNull.comment(
'0: hour, 1: day, 2: week',
),
column(names.c.analyticsByPeriod.periodStart).timestampNoTimezone(0),
)
.withForeignKey(names.c.analyticsByPeriod.metaId, {
tableName: names.t.analyticsMetadata,
columnName: names.c.analyticsMetadata.metaId,
onDelete: 'CASCADE',
})
.withIndexOn(
[
names.c.analyticsByPeriod.periodStart,
names.c.analyticsByPeriod.type,
names.c.analyticsByPeriod.periodUnit,
names.c.analyticsByPeriod.metaId,
],
true,
);
}
async down({ schemaBuilder: { dropTable } }: MigrationContext) {
await dropTable(names.t.analyticsMetadata);
await dropTable(names.t.analyticsRaw);
await dropTable(names.t.analyticsByPeriod);
}
}

View file

@ -81,6 +81,7 @@ import { AddManagedColumnToCredentialsTable1734479635324 } from '../common/17344
import { CreateTestCaseExecutionTable1736947513045 } from '../common/1736947513045-CreateTestCaseExecutionTable';
import { AddErrorColumnsToTestRuns1737715421462 } from '../common/1737715421462-AddErrorColumnsToTestRuns';
import { CreateFolderTable1738709609940 } from '../common/1738709609940-CreateFolderTable';
import { CreateAnalyticsTables1739549398681 } from '../common/1739549398681-CreateAnalyticsTables';
export const mysqlMigrations: Migration[] = [
InitialMigration1588157391238,
@ -164,4 +165,5 @@ export const mysqlMigrations: Migration[] = [
AddErrorColumnsToTestRuns1737715421462,
CreateFolderTable1738709609940,
FixTestDefinitionPrimaryKey1739873751194,
CreateAnalyticsTables1739549398681,
];

View file

@ -80,6 +80,7 @@ import { AddStatsColumnsToTestRun1736172058779 } from '../common/1736172058779-A
import { CreateTestCaseExecutionTable1736947513045 } from '../common/1736947513045-CreateTestCaseExecutionTable';
import { AddErrorColumnsToTestRuns1737715421462 } from '../common/1737715421462-AddErrorColumnsToTestRuns';
import { CreateFolderTable1738709609940 } from '../common/1738709609940-CreateFolderTable';
import { CreateAnalyticsTables1739549398681 } from '../common/1739549398681-CreateAnalyticsTables';
export const postgresMigrations: Migration[] = [
InitialMigration1587669153312,
@ -162,4 +163,5 @@ export const postgresMigrations: Migration[] = [
CreateTestCaseExecutionTable1736947513045,
AddErrorColumnsToTestRuns1737715421462,
CreateFolderTable1738709609940,
CreateAnalyticsTables1739549398681,
];

View file

@ -77,6 +77,7 @@ import { AddManagedColumnToCredentialsTable1734479635324 } from '../common/17344
import { AddStatsColumnsToTestRun1736172058779 } from '../common/1736172058779-AddStatsColumnsToTestRun';
import { CreateTestCaseExecutionTable1736947513045 } from '../common/1736947513045-CreateTestCaseExecutionTable';
import { AddErrorColumnsToTestRuns1737715421462 } from '../common/1737715421462-AddErrorColumnsToTestRuns';
import { CreateAnalyticsTables1739549398681 } from '../common/1739549398681-CreateAnalyticsTables';
const sqliteMigrations: Migration[] = [
InitialMigration1588102412422,
@ -156,6 +157,7 @@ const sqliteMigrations: Migration[] = [
CreateTestCaseExecutionTable1736947513045,
AddErrorColumnsToTestRuns1737715421462,
CreateFolderTable1738709609940,
CreateAnalyticsTables1739549398681,
];
export { sqliteMigrations };

View file

@ -1,5 +1,5 @@
import { Service } from '@n8n/di';
import type { SelectQueryBuilder } from '@n8n/typeorm';
import type { EntityManager, SelectQueryBuilder } from '@n8n/typeorm';
import { DataSource, Repository } from '@n8n/typeorm';
import type { ListQuery } from '@/requests';
@ -227,8 +227,13 @@ export class FolderRepository extends Repository<FolderWithWorkflowsCount> {
}
}
async findOneOrFailFolderInProject(folderId: string, projectId: string): Promise<Folder> {
return await this.manager.findOneOrFail(Folder, {
async findOneOrFailFolderInProject(
folderId: string,
projectId: string,
em?: EntityManager,
): Promise<Folder> {
const manager = em ?? this.manager;
return await manager.findOneOrFail(Folder, {
where: {
id: folderId,
homeProject: {

View file

@ -112,7 +112,7 @@ export class SharedWorkflowRepository extends Repository<SharedWorkflow> {
workflowId: string,
user: User,
scopes: Scope[],
{ includeTags = false, em = this.manager } = {},
{ includeTags = false, includeParentFolder = false, em = this.manager } = {},
) {
let where: FindOptionsWhere<SharedWorkflow> = { workflowId };
@ -138,6 +138,7 @@ export class SharedWorkflowRepository extends Repository<SharedWorkflow> {
workflow: {
shared: { project: { projectRelations: { user: true } } },
tags: includeTags,
parentFolder: includeParentFolder,
},
},
});

View file

@ -381,14 +381,6 @@ export class License {
return this.getUsersLimit() === UNLIMITED_LICENSE_QUOTA;
}
async reinit() {
if (this.manager) {
await this.manager.reset();
}
await this.init({ forceRecreate: true });
this.logger.debug('License reinitialized');
}
/**
* Ensures that the instance is licensed for multi-main setup if multi-main mode is enabled
*/
@ -429,4 +421,12 @@ export class License {
Container.get(ObjectStoreService).setReadonly(true);
}
}
enableAutoRenewals() {
this.manager?.enableAutoRenewals();
}
disableAutoRenewals() {
this.manager?.disableAutoRenewals();
}
}

View file

@ -1,5 +1,7 @@
import type { CreateFolderDto } from '@n8n/api-types';
import { Service } from '@n8n/di';
// eslint-disable-next-line n8n-local-rules/misplaced-n8n-typeorm-import
import type { EntityManager } from '@n8n/typeorm';
import { FolderRepository } from '@/databases/repositories/folder.repository';
import { FolderNotFoundError } from '@/errors/folder-not-found.error';
@ -37,9 +39,9 @@ export class FolderService {
return folder;
}
async getFolderInProject(folderId: string, projectId: string) {
async getFolderInProject(folderId: string, projectId: string, em?: EntityManager) {
try {
return await this.folderRepository.findOneOrFailFolderInProject(folderId, projectId);
return await this.folderRepository.findOneOrFailFolderInProject(folderId, projectId, em);
} catch {
throw new FolderNotFoundError(folderId);
}
@ -48,6 +50,10 @@ export class FolderService {
async getFolderTree(folderId: string, projectId: string): Promise<SimpleFolderNode[]> {
await this.getFolderInProject(folderId, projectId);
const escapedParentFolderId = this.folderRepository
.createQueryBuilder()
.escape('parentFolderId');
const baseQuery = this.folderRepository
.createQueryBuilder('folder')
.select('folder.id', 'id')
@ -58,7 +64,7 @@ export class FolderService {
.createQueryBuilder('f')
.select('f.id', 'id')
.addSelect('f.parentFolderId', 'parentFolderId')
.innerJoin('folder_path', 'fp', 'f.id = fp.parentFolderId');
.innerJoin('folder_path', 'fp', `f.id = fp.${escapedParentFolderId}`);
const mainQuery = this.folderRepository
.createQueryBuilder('folder')

View file

@ -22,6 +22,7 @@ export declare namespace WorkflowRequest {
hash: string;
meta: Record<string, unknown>;
projectId: string;
parentFolderId?: string;
}>;
type ManualRunPayload = {

View file

@ -46,6 +46,7 @@ import { License } from '@/license';
import { listQueryMiddleware } from '@/middlewares';
import { AuthenticatedRequest } from '@/requests';
import * as ResponseHelper from '@/response-helper';
import { FolderService } from '@/services/folder.service';
import { NamingService } from '@/services/naming.service';
import { ProjectService } from '@/services/project.service.ee';
import { TagService } from '@/services/tag.service';
@ -82,6 +83,7 @@ export class WorkflowsController {
private readonly projectRelationRepository: ProjectRelationRepository,
private readonly eventService: EventService,
private readonly globalConfig: GlobalConfig,
private readonly folderService: FolderService,
) {}
@Post('/')
@ -133,7 +135,7 @@ export class WorkflowsController {
const savedWorkflow = await Db.transaction(async (transactionManager) => {
const workflow = await transactionManager.save<WorkflowEntity>(newWorkflow);
const { projectId } = req.body;
const { projectId, parentFolderId } = req.body;
project =
projectId === undefined
? await this.projectRepository.getPersonalProjectForUser(req.user.id, transactionManager)
@ -155,6 +157,17 @@ export class WorkflowsController {
throw new ApplicationError('No personal project found');
}
if (parentFolderId) {
try {
const parentFolder = await this.folderService.getFolderInProject(
parentFolderId,
project.id,
transactionManager,
);
await transactionManager.update(WorkflowEntity, { id: workflow.id }, { parentFolder });
} catch {}
}
const newSharedWorkflow = this.sharedWorkflowRepository.create({
role: 'workflow:owner',
projectId: project.id,
@ -167,7 +180,7 @@ export class WorkflowsController {
workflow.id,
req.user,
['workflow:read'],
{ em: transactionManager, includeTags: true },
{ em: transactionManager, includeTags: true, includeParentFolder: true },
);
});

View file

@ -58,13 +58,14 @@ let projectService: ProjectService;
beforeEach(async () => {
await testDb.truncate([
'Workflow',
'SharedWorkflow',
'Tag',
'WorkflowHistory',
'Project',
'ProjectRelation',
'Folder',
'Workflow',
'Tag',
'Project',
'User',
]);
projectRepository = Container.get(ProjectRepository);
projectService = Container.get(ProjectService);
@ -378,6 +379,115 @@ describe('POST /workflows', () => {
message: "You don't have the permissions to save the workflow in this project.",
});
});
test('create link workflow with folder if one is provided', async () => {
//
// ARRANGE
//
const personalProject = await projectRepository.getPersonalProjectForUserOrFail(owner.id);
const folder = await createFolder(personalProject, { name: 'Folder 1' });
const workflow = makeWorkflow();
//
// ACT
//
const response = await authOwnerAgent
.post('/workflows')
.send({ ...workflow, parentFolderId: folder.id });
//
// ASSERT
//
expect(response.body.data).toMatchObject({
active: false,
id: expect.any(String),
name: workflow.name,
sharedWithProjects: [],
usedCredentials: [],
homeProject: {
id: personalProject.id,
name: personalProject.name,
type: personalProject.type,
},
parentFolder: {
id: folder.id,
name: folder.name,
},
});
expect(response.body.data.shared).toBeUndefined();
});
test('create workflow without parent folder if no folder is provided', async () => {
//
// ARRANGE
//
const personalProject = await projectRepository.getPersonalProjectForUserOrFail(owner.id);
const workflow = makeWorkflow();
//
// ACT
//
const response = await authOwnerAgent
.post('/workflows')
.send({ ...workflow })
.expect(200);
//
// ASSERT
//
expect(response.body.data).toMatchObject({
active: false,
id: expect.any(String),
name: workflow.name,
sharedWithProjects: [],
usedCredentials: [],
homeProject: {
id: personalProject.id,
name: personalProject.name,
type: personalProject.type,
},
parentFolder: null,
});
expect(response.body.data.shared).toBeUndefined();
});
test('create workflow without parent is provided folder does not exist in the project', async () => {
//
// ARRANGE
//
const personalProject = await projectRepository.getPersonalProjectForUserOrFail(owner.id);
const workflow = makeWorkflow();
//
// ACT
//
const response = await authOwnerAgent
.post('/workflows')
.send({ ...workflow, parentFolderId: 'non-existing-folder-id' })
.expect(200);
//
// ASSERT
//
expect(response.body.data).toMatchObject({
active: false,
id: expect.any(String),
name: workflow.name,
sharedWithProjects: [],
usedCredentials: [],
homeProject: {
id: personalProject.id,
name: personalProject.name,
type: personalProject.type,
},
parentFolder: null,
});
expect(response.body.data.shared).toBeUndefined();
});
});
describe('GET /workflows/:workflowId', () => {

View file

@ -1,5 +1,5 @@
{
"extends": ["./tsconfig.json", "../../tsconfig.build.json"],
"extends": ["./tsconfig.json", "@n8n/typescript-config/tsconfig.build.json"],
"compilerOptions": {
"rootDir": "src",
"outDir": "dist",

View file

@ -1,5 +1,8 @@
{
"extends": ["../../tsconfig.json", "../../tsconfig.backend.json"],
"extends": [
"@n8n/typescript-config/tsconfig.common.json",
"@n8n/typescript-config/tsconfig.backend.json"
],
"compilerOptions": {
"rootDir": ".",
"emitDecoratorMetadata": true,

View file

@ -1,6 +1,6 @@
{
"name": "n8n-core",
"version": "1.79.0",
"version": "1.80.0",
"description": "Core functionality of n8n",
"main": "dist/index",
"types": "dist/index.d.ts",
@ -27,6 +27,7 @@
"bin"
],
"devDependencies": {
"@n8n/typescript-config": "workspace:*",
"@types/aws4": "^1.5.1",
"@types/concat-stream": "^2.0.0",
"@types/express": "catalog:",

View file

@ -1,5 +1,5 @@
{
"extends": ["./tsconfig.json", "../../tsconfig.build.json"],
"extends": ["./tsconfig.json", "@n8n/typescript-config/tsconfig.build.json"],
"compilerOptions": {
"composite": true,
"rootDir": "src",

View file

@ -1,5 +1,8 @@
{
"extends": ["../../tsconfig.json", "../../tsconfig.backend.json"],
"extends": [
"@n8n/typescript-config/tsconfig.common.json",
"@n8n/typescript-config/tsconfig.backend.json"
],
"compilerOptions": {
"rootDir": ".",
"baseUrl": "src",

View file

@ -1,6 +1,6 @@
{
"name": "n8n-design-system",
"version": "1.68.0",
"version": "1.69.0",
"main": "src/index.ts",
"import": "src/index.ts",
"scripts": {
@ -20,8 +20,8 @@
},
"devDependencies": {
"@n8n/frontend-eslint-config": "workspace:*",
"@n8n/frontend-typescript-config": "workspace:*",
"@n8n/frontend-vitest-config": "workspace:*",
"@n8n/typescript-config": "workspace:*",
"@n8n/storybook": "workspace:*",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/user-event": "^14.6.0",

View file

@ -1,5 +1,5 @@
{
"extends": "@n8n/frontend-typescript-config",
"extends": "@n8n/typescript-config/tsconfig.frontend.json",
"compilerOptions": {
"baseUrl": ".",
"rootDirs": [".", "../../frontend/@n8n/composables/src"],

View file

@ -1,6 +1,6 @@
{
"name": "n8n-editor-ui",
"version": "1.80.0",
"version": "1.81.0",
"description": "Workflow Editor UI for n8n",
"main": "index.js",
"scripts": {
@ -93,7 +93,7 @@
"devDependencies": {
"@n8n/frontend-eslint-config": "workspace:*",
"@n8n/frontend-vitest-config": "workspace:*",
"@n8n/frontend-typescript-config": "workspace:*",
"@n8n/typescript-config": "workspace:*",
"@faker-js/faker": "^8.0.2",
"@iconify/json": "^2.2.228",
"@pinia/testing": "^0.1.6",

View file

@ -18,7 +18,9 @@ export const getSchemaPreview = async (
): Promise<JSONSchema7> => {
const { nodeType, version, resource, operation } = options;
const versionString = padVersion(version);
const path = ['schemas', nodeType, versionString, resource, operation].filter(Boolean).join('/');
const path = ['schemas', nodeType.replace('@n8n/', ''), versionString, resource, operation]
.filter(Boolean)
.join('/');
return await request({
method: 'GET',
baseURL: baseUrl,

View file

@ -0,0 +1,41 @@
import { getSchemaPreview } from '../schemaPreview';
import * as apiUtils from '@/utils/apiUtils';
describe('schemaPreview', () => {
afterEach(() => {
vi.resetAllMocks();
});
vi.mock('@/utils/apiUtils', () => ({
request: vi.fn().mockResolvedValue({ test: 'test' }),
}));
it('should return schema preview', async () => {
const response = await getSchemaPreview('http://test.com', {
nodeType: 'n8n-nodes-base.asana',
version: 1,
resource: 'resource',
operation: 'operation',
});
expect(response).toEqual({ test: 'test' });
});
it('should parse out nodeType', async () => {
const spy = vi.spyOn(apiUtils, 'request');
await getSchemaPreview('http://test.com', {
nodeType: '@n8n/n8n-nodes-base.asana',
version: 1,
resource: 'resource',
operation: 'operation',
});
expect(spy).toHaveBeenCalledWith({
method: 'GET',
baseURL: 'http://test.com',
endpoint: 'schemas/n8n-nodes-base.asana/1.0.0/resource/operation.json',
withCredentials: false,
});
});
});

View file

@ -153,7 +153,7 @@ function optionSelected(action: string) {
<Assignment
:model-value="assignment"
:index="index"
:path="`${path}.${index}`"
:path="`${path}.assignments.${index}`"
:issues="getIssues(index)"
:class="$style.assignment"
:is-read-only="isReadOnly"

View file

@ -186,7 +186,7 @@ const onBlur = (): void => {
:is-read-only="readOnly"
:parameter="leftParameter"
:value="condition.leftValue"
:path="`${path}.left`"
:path="`${path}.leftValue`"
:class="[$style.input, $style.inputLeft]"
data-test-id="filter-condition-left"
@update="onLeftValueChange"
@ -212,7 +212,7 @@ const onBlur = (): void => {
:options-position="breakpoint === 'default' ? 'top' : 'bottom'"
:parameter="rightParameter"
:value="condition.rightValue"
:path="`${path}.right`"
:path="`${path}.rightValue`"
:class="[$style.input, $style.inputRight]"
data-test-id="filter-condition-right"
@update="onRightValueChange"

View file

@ -195,7 +195,7 @@ function getIssues(index: number): string[] {
:read-only="readOnly"
:can-remove="index !== 0 || state.paramValue.conditions.length > 1"
:can-drag="index !== 0 || state.paramValue.conditions.length > 1"
:path="`${path}.${index}`"
:path="`${path}.conditions.${index}`"
:issues="getIssues(index)"
:class="$style.condition"
@update="(value) => onConditionUpdate(index, value)"

View file

@ -66,6 +66,7 @@ import type { EventBus } from 'n8n-design-system/utils';
import { createEventBus } from 'n8n-design-system/utils';
import { useRouter } from 'vue-router';
import { useElementSize } from '@vueuse/core';
import { completeExpressionSyntax, isStringWithExpressionSyntax } from '@/utils/expressions';
type Picker = { $emit: (arg0: string, arg1: Date) => void };
@ -813,16 +814,25 @@ function valueChanged(value: NodeParameterValueType | {} | Date) {
if (remoteParameterOptionsLoading.value) {
return;
}
// Only update the value if it has changed
const oldValue = get(node.value, props.path);
if (oldValue !== undefined && oldValue === value) {
// Only update the value if it has changed
return;
}
if (!oldValue && oldValue !== undefined && isStringWithExpressionSyntax(value)) {
// if empty old value and updated value has an expression, add '=' prefix to switch to expression mode
value = '=' + value;
}
if (props.parameter.name === 'nodeCredentialType') {
activeCredentialType.value = value as string;
}
value = completeExpressionSyntax(value);
if (value instanceof Date) {
value = value.toISOString();
}
@ -1240,6 +1250,7 @@ onUpdated(async () => {
:is-read-only="isReadOnly"
:rows="editorRows"
fullscreen
fill-parent
@update:model-value="valueChangedDebounced"
/>
</div>

View file

@ -1,5 +1,11 @@
import { ExpressionError } from 'n8n-workflow';
import { removeExpressionPrefix, stringifyExpressionResult, unwrapExpression } from './expressions';
import {
completeExpressionSyntax,
isStringWithExpressionSyntax,
removeExpressionPrefix,
stringifyExpressionResult,
unwrapExpression,
} from './expressions';
describe('Utils: Expressions', () => {
describe('stringifyExpressionResult()', () => {
@ -53,4 +59,44 @@ describe('Utils: Expressions', () => {
expect(removeExpressionPrefix(input)).toBe(output);
});
});
describe('completeExpressionSyntax', () => {
it('should complete expressions with "{{ " at the end', () => {
expect(completeExpressionSyntax('test {{ ')).toBe('=test {{ }}');
});
it('should complete expressions with "{{$" at the end', () => {
expect(completeExpressionSyntax('test {{$')).toBe('=test {{ $ }}');
});
it('should not modify already valid expressions', () => {
expect(completeExpressionSyntax('=valid expression')).toBe('=valid expression');
});
it('should return non-string values unchanged', () => {
expect(completeExpressionSyntax(123)).toBe(123);
expect(completeExpressionSyntax(true)).toBe(true);
expect(completeExpressionSyntax(null)).toBe(null);
});
});
describe('isStringWithExpressionSyntax', () => {
it('should return true for strings with expression syntax', () => {
expect(isStringWithExpressionSyntax('test {{ value }}')).toBe(true);
});
it('should return false for strings without expression syntax', () => {
expect(isStringWithExpressionSyntax('just a string')).toBe(false);
});
it('should return false for strings starting with "="', () => {
expect(isStringWithExpressionSyntax('=expression {{ value }}')).toBe(false);
});
it('should return false for non-string values', () => {
expect(isStringWithExpressionSyntax(123)).toBe(false);
expect(isStringWithExpressionSyntax(true)).toBe(false);
expect(isStringWithExpressionSyntax(null)).toBe(false);
});
});
});

View file

@ -139,3 +139,21 @@ export const stringifyExpressionResult = (
return typeof result.result === 'string' ? result.result : String(result.result);
};
export const completeExpressionSyntax = <T>(value: T) => {
if (typeof value === 'string' && !value.startsWith('=')) {
if (value.endsWith('{{ ')) return '=' + value + ' }}';
if (value.endsWith('{{$')) return '=' + value.slice(0, -1) + ' $ }}';
}
return value;
};
export const isStringWithExpressionSyntax = <T>(value: T): boolean => {
return (
typeof value === 'string' &&
!value.startsWith('=') &&
value.includes('{{') &&
value.includes('}}')
);
};

View file

@ -1,5 +1,5 @@
{
"extends": "@n8n/frontend-typescript-config",
"extends": "@n8n/typescript-config/tsconfig.frontend.json",
"compilerOptions": {
"baseUrl": ".",
"rootDirs": [

View file

@ -66,10 +66,13 @@ const plugins = [
}),
viteStaticCopy({
targets: [
{ src: pathPosix.resolve('node_modules/web-tree-sitter/tree-sitter.wasm'), dest: 'public' },
{
src: pathPosix.resolve('node_modules/web-tree-sitter/tree-sitter.wasm'),
dest: resolve(__dirname, 'dist'),
},
{
src: pathPosix.resolve('node_modules/curlconverter/dist/tree-sitter-bash.wasm'),
dest: 'public',
dest: resolve(__dirname, 'dist'),
},
],
}),

View file

@ -46,6 +46,7 @@
"devDependencies": {
"@iconify-json/mdi": "^1.1.54",
"@n8n/storybook": "workspace:*",
"@n8n/typescript-config": "workspace:*",
"@vitejs/plugin-vue": "catalog:frontend",
"@vitest/coverage-v8": "catalog:frontend",
"unplugin-icons": "^0.19.0",

View file

@ -1,5 +1,5 @@
{
"extends": "../../../../tsconfig.json",
"extends": "@n8n/typescript-config/tsconfig.common.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "dist",

View file

@ -1,7 +1,7 @@
{
"name": "@n8n/composables",
"type": "module",
"version": "1.1.0",
"version": "1.2.0",
"files": [
"dist"
],
@ -26,8 +26,8 @@
},
"devDependencies": {
"@n8n/frontend-eslint-config": "workspace:*",
"@n8n/frontend-typescript-config": "workspace:*",
"@n8n/frontend-vitest-config": "workspace:*",
"@n8n/typescript-config": "workspace:*",
"@testing-library/jest-dom": "catalog:frontend",
"@testing-library/user-event": "catalog:frontend",
"@testing-library/vue": "catalog:frontend",

View file

@ -1,5 +1,5 @@
{
"extends": "@n8n/frontend-typescript-config",
"extends": "@n8n/typescript-config/tsconfig.frontend.json",
"compilerOptions": {
"baseUrl": ".",
"rootDir": ".",

View file

@ -1,18 +0,0 @@
{
"name": "@n8n/frontend-typescript-config",
"version": "1.1.0",
"type": "module",
"files": [
"tsconfig.json"
],
"main": "./tsconfig.json",
"module": "./tsconfig.json",
"exports": {
".": {
"import": "./tsconfig.json",
"require": "./tsconfig.json"
},
"./*": "./*"
},
"license": "See LICENSE.md file in the root of the repository"
}

View file

@ -1,6 +1,6 @@
{
"name": "n8n-node-dev",
"version": "1.79.0",
"version": "1.80.0",
"description": "CLI to simplify n8n credentials/node development",
"main": "dist/src/index",
"types": "dist/src/index.d.ts",
@ -36,6 +36,7 @@
"src/tsconfig-build.json"
],
"devDependencies": {
"@n8n/typescript-config": "workspace:*",
"@types/inquirer": "^6.5.0"
},
"dependencies": {

View file

@ -1,5 +1,8 @@
{
"extends": ["../../tsconfig.json", "../../tsconfig.backend.json"],
"extends": [
"@n8n/typescript-config/tsconfig.common.json",
"@n8n/typescript-config/tsconfig.backend.json"
],
"compilerOptions": {
"outDir": "dist",
"preserveSymlinks": true,

View file

@ -1,6 +1,6 @@
{
"name": "n8n-nodes-base",
"version": "1.79.0",
"version": "1.80.0",
"description": "Base nodes of n8n",
"main": "index.js",
"scripts": {
@ -827,6 +827,7 @@
]
},
"devDependencies": {
"@n8n/typescript-config": "workspace:*",
"@types/amqplib": "^0.10.1",
"@types/aws4": "^1.5.1",
"@types/basic-auth": "catalog:",

View file

@ -1,5 +1,5 @@
{
"extends": ["./tsconfig.json", "../../tsconfig.build.json"],
"extends": ["./tsconfig.json", "@n8n/typescript-config/tsconfig.build.json"],
"compilerOptions": {
"outDir": "dist",
"tsBuildInfoFile": "dist/build.tsbuildinfo"

View file

@ -1,5 +1,8 @@
{
"extends": ["../../tsconfig.json", "../../tsconfig.backend.json"],
"extends": [
"@n8n/typescript-config/tsconfig.common.json",
"@n8n/typescript-config/tsconfig.backend.json"
],
"compilerOptions": {
"paths": {
"@test/*": ["./test/*"],

View file

@ -1,6 +1,6 @@
{
"name": "n8n-workflow",
"version": "1.78.0",
"version": "1.79.0",
"description": "Workflow base code of n8n",
"main": "dist/index.js",
"module": "src/index.ts",
@ -32,6 +32,7 @@
"devDependencies": {
"@langchain/core": "catalog:",
"@n8n/config": "workspace:*",
"@n8n/typescript-config": "workspace:*",
"@types/deep-equal": "^1.0.1",
"@types/express": "catalog:",
"@types/jmespath": "^0.15.0",

View file

@ -1,5 +1,5 @@
{
"extends": ["./tsconfig.json", "../../tsconfig.build.json"],
"extends": ["./tsconfig.json", "@n8n/typescript-config/tsconfig.build.json"],
"compilerOptions": {
"composite": true,
"rootDir": "src",

View file

@ -1,5 +1,5 @@
{
"extends": "../../tsconfig.json",
"extends": "@n8n/typescript-config/tsconfig.common.json",
"compilerOptions": {
"rootDir": ".",
"baseUrl": "src",

View file

@ -302,6 +302,9 @@ importers:
'@n8n/config':
specifier: workspace:*
version: link:../config
'@n8n/typescript-config':
specifier: workspace:*
version: link:../typescript-config
n8n-workflow:
specifier: workspace:*
version: link:../../workflow
@ -324,6 +327,9 @@ importers:
specifier: ^8.1.4
version: 8.1.4
devDependencies:
'@n8n/typescript-config':
specifier: workspace:*
version: link:../typescript-config
'@types/convict':
specifier: ^6.1.1
version: 6.1.1
@ -336,6 +342,10 @@ importers:
axios:
specifier: 'catalog:'
version: 1.7.4
devDependencies:
'@n8n/typescript-config':
specifier: workspace:*
version: link:../typescript-config
packages/@n8n/codemirror-lang:
dependencies:
@ -352,6 +362,9 @@ importers:
'@lezer/generator':
specifier: ^1.7.0
version: 1.7.0
'@n8n/typescript-config':
specifier: workspace:*
version: link:../typescript-config
packages/@n8n/config:
dependencies:
@ -361,12 +374,20 @@ importers:
reflect-metadata:
specifier: 'catalog:'
version: 0.2.2
devDependencies:
'@n8n/typescript-config':
specifier: workspace:*
version: link:../typescript-config
packages/@n8n/di:
dependencies:
reflect-metadata:
specifier: 'catalog:'
version: 0.2.2
devDependencies:
'@n8n/typescript-config':
specifier: workspace:*
version: link:../typescript-config
packages/@n8n/imap:
dependencies:
@ -386,6 +407,9 @@ importers:
specifier: 0.0.4
version: 0.0.4
devDependencies:
'@n8n/typescript-config':
specifier: workspace:*
version: link:../typescript-config
'@types/imap':
specifier: ^0.8.40
version: 0.8.40
@ -401,6 +425,9 @@ importers:
packages/@n8n/json-schema-to-zod:
devDependencies:
'@n8n/typescript-config':
specifier: workspace:*
version: link:../typescript-config
'@types/json-schema':
specifier: ^7.0.15
version: 7.0.15
@ -485,6 +512,9 @@ importers:
'@n8n/typeorm':
specifier: 0.3.20-12
version: 0.3.20-12(@sentry/node@8.52.1)(ioredis@5.3.2)(mssql@10.0.2)(mysql2@3.11.0)(pg@8.12.0)(redis@4.6.12)(sqlite3@5.1.7)(ts-node@10.9.2(@types/node@18.16.16)(typescript@5.7.2))
'@n8n/typescript-config':
specifier: workspace:*
version: link:../typescript-config
'@n8n/vm2':
specifier: 3.9.25
version: 3.9.25
@ -598,7 +628,11 @@ importers:
specifier: workspace:*
version: link:../../core
packages/@n8n/permissions: {}
packages/@n8n/permissions:
devDependencies:
'@n8n/typescript-config':
specifier: workspace:*
version: link:../typescript-config
packages/@n8n/storybook:
devDependencies:
@ -681,10 +715,15 @@ importers:
specifier: '>=8.17.1'
version: 8.17.1
devDependencies:
'@n8n/typescript-config':
specifier: workspace:*
version: link:../typescript-config
'@types/lodash':
specifier: 'catalog:'
version: 4.14.195
packages/@n8n/typescript-config: {}
packages/@n8n_io/eslint-config:
devDependencies:
'@types/eslint':
@ -775,8 +814,8 @@ importers:
specifier: 1.13.0
version: 1.13.0
'@n8n_io/license-sdk':
specifier: 2.16.1
version: 2.16.1
specifier: 2.17.0
version: 2.17.0
'@oclif/core':
specifier: 4.0.7
version: 4.0.7
@ -1006,6 +1045,9 @@ importers:
specifier: 'catalog:'
version: 3.24.1
devDependencies:
'@n8n/typescript-config':
specifier: workspace:*
version: link:../@n8n/typescript-config
'@redocly/cli':
specifier: ^1.25.5
version: 1.25.5(encoding@0.1.13)(enzyme@3.11.0)
@ -1187,6 +1229,9 @@ importers:
specifier: 'catalog:'
version: 3.24.1
devDependencies:
'@n8n/typescript-config':
specifier: workspace:*
version: link:../@n8n/typescript-config
'@types/aws4':
specifier: ^1.5.1
version: 1.11.2
@ -1266,15 +1311,15 @@ importers:
'@n8n/frontend-eslint-config':
specifier: workspace:*
version: link:../frontend/tooling/eslint-config
'@n8n/frontend-typescript-config':
specifier: workspace:*
version: link:../frontend/tooling/typescript-config
'@n8n/frontend-vitest-config':
specifier: workspace:*
version: link:../frontend/tooling/vitest-config
'@n8n/storybook':
specifier: workspace:*
version: link:../@n8n/storybook
'@n8n/typescript-config':
specifier: workspace:*
version: link:../@n8n/typescript-config
'@testing-library/jest-dom':
specifier: ^6.6.3
version: 6.6.3
@ -1570,12 +1615,12 @@ importers:
'@n8n/frontend-eslint-config':
specifier: workspace:*
version: link:../frontend/tooling/eslint-config
'@n8n/frontend-typescript-config':
specifier: workspace:*
version: link:../frontend/tooling/typescript-config
'@n8n/frontend-vitest-config':
specifier: workspace:*
version: link:../frontend/tooling/vitest-config
'@n8n/typescript-config':
specifier: workspace:*
version: link:../@n8n/typescript-config
'@pinia/testing':
specifier: ^0.1.6
version: 0.1.6(pinia@2.2.4(typescript@5.7.2)(vue@3.5.13(typescript@5.7.2)))(vue@3.5.13(typescript@5.7.2))
@ -1670,6 +1715,9 @@ importers:
'@n8n/storybook':
specifier: workspace:*
version: link:../../../@n8n/storybook
'@n8n/typescript-config':
specifier: workspace:*
version: link:../../../@n8n/typescript-config
'@vitejs/plugin-vue':
specifier: catalog:frontend
version: 5.2.1(vite@6.0.2(@types/node@18.16.16)(jiti@1.21.0)(sass@1.64.1)(terser@5.16.1))(vue@3.5.13(typescript@5.7.2))
@ -1697,12 +1745,12 @@ importers:
'@n8n/frontend-eslint-config':
specifier: workspace:*
version: link:../../tooling/eslint-config
'@n8n/frontend-typescript-config':
specifier: workspace:*
version: link:../../tooling/typescript-config
'@n8n/frontend-vitest-config':
specifier: workspace:*
version: link:../../tooling/vitest-config
'@n8n/typescript-config':
specifier: workspace:*
version: link:../../../@n8n/typescript-config
'@testing-library/jest-dom':
specifier: catalog:frontend
version: 6.6.3
@ -1752,8 +1800,6 @@ importers:
specifier: ^1.0.0
version: 1.0.0
packages/frontend/tooling/typescript-config: {}
packages/frontend/tooling/vitest-config:
devDependencies:
vite:
@ -1793,6 +1839,9 @@ importers:
specifier: ^3.0.3
version: 3.0.3
devDependencies:
'@n8n/typescript-config':
specifier: workspace:*
version: link:../@n8n/typescript-config
'@types/inquirer':
specifier: ^6.5.0
version: 6.5.0
@ -2001,6 +2050,9 @@ importers:
specifier: 'catalog:'
version: 0.6.2
devDependencies:
'@n8n/typescript-config':
specifier: workspace:*
version: link:../@n8n/typescript-config
'@types/amqplib':
specifier: ^0.10.1
version: 0.10.1
@ -2146,6 +2198,9 @@ importers:
'@n8n/config':
specifier: workspace:*
version: link:../@n8n/config
'@n8n/typescript-config':
specifier: workspace:*
version: link:../@n8n/typescript-config
'@types/deep-equal':
specifier: ^1.0.1
version: 1.0.1
@ -4567,8 +4622,8 @@ packages:
resolution: {integrity: sha512-16kftFTeX3/lBinHJaBK0OL1lB4FpPaUoHX4h25AkvgHvmjUHpWNY2ZtKos0rY89+pkzDsNxMZqSUkeKU45iRg==}
engines: {node: '>=20.15', pnpm: '>=8.14'}
'@n8n_io/license-sdk@2.16.1':
resolution: {integrity: sha512-J3zsSzu0JftKAsLnxeKtprKkNSWpfNNMy7kUGkBgEsd6R7kJ6bzKKIcyTu+pHPP+Gfe6iTKw+SXz2TXSZEmK2A==}
'@n8n_io/license-sdk@2.17.0':
resolution: {integrity: sha512-oa+P1qnJtVDysLSyaeLqHwUwUd5tXqbiWnj1+kuZrtF9hrJUacxGUQdFuBlGJwr8wUTTJVh2XIcE5N2Mn7x2Bg==}
engines: {node: '>=18.12.1'}
'@n8n_io/riot-tmpl@4.0.0':
@ -13315,9 +13370,6 @@ packages:
vue-component-type-helpers@2.1.10:
resolution: {integrity: sha512-lfgdSLQKrUmADiSV6PbBvYgQ33KF3Ztv6gP85MfGaGaSGMTXORVaHT1EHfsqCgzRNBstPKYDmvAV9Do5CmJ07A==}
vue-component-type-helpers@2.2.2:
resolution: {integrity: sha512-6lLY+n2xz2kCYshl59mL6gy8OUUTmkscmDFMO8i7Lj+QKwgnIFUZmM1i/iTYObtrczZVdw7UakPqDTGwVSGaRg==}
vue-component-type-helpers@2.2.4:
resolution: {integrity: sha512-F66p0XLbAu92BRz6kakHyAcaUSF7HWpWX/THCqL0TxySSj7z/nok5UUMohfNkkCm1pZtawsdzoJ4p1cjNqCx0Q==}
@ -16975,7 +17027,7 @@ snapshots:
'@n8n_io/ai-assistant-sdk@1.13.0': {}
'@n8n_io/license-sdk@2.16.1':
'@n8n_io/license-sdk@2.17.0':
dependencies:
crypto-js: 4.2.0
node-machine-id: 1.1.12
@ -19480,7 +19532,7 @@ snapshots:
'@vue/test-utils@2.4.6':
dependencies:
js-beautify: 1.14.9
vue-component-type-helpers: 2.2.2
vue-component-type-helpers: 2.2.4
'@vue/tsconfig@0.7.0(typescript@5.7.2)(vue@3.5.13(typescript@5.7.2))':
optionalDependencies:
@ -27778,8 +27830,6 @@ snapshots:
vue-component-type-helpers@2.1.10: {}
vue-component-type-helpers@2.2.2: {}
vue-component-type-helpers@2.2.4: {}
vue-demi@0.14.10(vue@3.5.13(typescript@5.7.2)):

View file

@ -1,26 +1,4 @@
{
"compilerOptions": {
"strict": true,
"module": "commonjs",
"moduleResolution": "node",
"target": "es2021",
"lib": ["es2021", "es2022.error", "dom"],
"removeComments": true,
"useUnknownInCatchVariables": true,
"forceConsistentCasingInFileNames": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"strictNullChecks": true,
"preserveConstEnums": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"incremental": true,
"declaration": false,
"sourceMap": true,
"skipLibCheck": true
},
"exclude": ["**/dist/**/*", "**/node_modules/**/*", "cypress"],
"files": ["node_modules/jest-expect-message/types/index.d.ts"]
"extends": "./packages/@n8n/typescript-config/tsconfig.common.json",
"exclude": ["**/dist/**/*", "**/node_modules/**/*", "cypress"]
}