Merge branch 'master' of https://github.com/n8n-io/n8n into node-1714-show-result-of-waiting-execution-on-canvas-after-execution

This commit is contained in:
Michael Kret 2024-09-17 15:49:43 +03:00
commit 41512376a0
94 changed files with 832 additions and 466 deletions

View file

@ -30,6 +30,9 @@ jobs:
- name: Build
run: pnpm build
- name: Run formatcheck
run: pnpm format:check
- name: Run typecheck
run: pnpm typecheck

View file

@ -7,3 +7,11 @@ packages/nodes-base/nodes/**/test
packages/cli/templates/form-trigger.handlebars
cypress/fixtures
CHANGELOG.md
.github/pull_request_template.md
# Ignored for now
**/*.md
# Handled by biome
**/*.ts
**/*.js
**/*.json
**/*.jsonc

View file

@ -1,5 +1,6 @@
{
"recommendations": [
"biomejs.biome",
"streetsidesoftware.code-spell-checker",
"dangmai.workspace-default-settings",
"dbaeumer.vscode-eslint",

View file

@ -1,6 +1,22 @@
{
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"[javascript]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[typescript]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[json]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[jsonc]": {
"editor.defaultFormatter": "biomejs.biome"
},
"editor.codeActionsOnSave": {
"quickfix.biome": "explicit",
"source.organizeImports.biome": "never"
},
"search.exclude": {
"node_modules": true,
"dist": true,

48
biome.jsonc Normal file
View file

@ -0,0 +1,48 @@
{
"$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
"vcs": {
"clientKind": "git",
"enabled": true,
"useIgnoreFile": true
},
"files": {
"ignore": [
"**/.turbo",
"**/coverage",
"**/dist",
"**/package.json",
"**/pnpm-lock.yaml",
"**/CHANGELOG.md"
]
},
"formatter": {
"enabled": true,
"formatWithErrors": false,
"indentStyle": "tab",
"indentWidth": 2,
"lineEnding": "lf",
"lineWidth": 100,
"attributePosition": "auto",
"ignore": [
// Handled by prettier
"**/*.vue"
]
},
"organizeImports": { "enabled": false },
"linter": {
"enabled": false
},
"javascript": {
"formatter": {
"jsxQuoteStyle": "double",
"quoteProperties": "asNeeded",
"trailingCommas": "all",
"semicolons": "always",
"arrowParentheses": "always",
"bracketSpacing": true,
"bracketSameLine": false,
"quoteStyle": "single",
"attributePosition": "auto"
}
}
}

7
cypress/biome.jsonc Normal file
View file

@ -0,0 +1,7 @@
{
"$schema": "../node_modules/@biomejs/biome/configuration_schema.json",
"extends": ["../biome.jsonc"],
"formatter": {
"ignore": ["fixtures/**"]
}
}

View file

@ -227,7 +227,7 @@ describe('NDV', () => {
workflowPage.actions.zoomToFit();
/* prettier-ignore */
// biome-ignore format:
const PINNED_DATA = [
{
"id": "abc",
@ -263,7 +263,6 @@ describe('NDV', () => {
]
}
];
/* prettier-ignore */
workflowPage.actions.openNode('Get thread details1');
ndv.actions.pastePinnedData(PINNED_DATA);
ndv.actions.close();

View file

@ -43,7 +43,9 @@ describe('Code node', () => {
const getParameter = () => ndv.getters.parameterInput('jsCode').should('be.visible');
const getEditor = () => getParameter().find('.cm-content').should('exist');
getEditor().type('{selectall}').paste(`$input.itemMatching()
getEditor()
.type('{selectall}')
.paste(`$input.itemMatching()
$input.item
$('When clicking Test workflow').item
$input.first(1)
@ -68,7 +70,9 @@ return
ndv.getters.parameterInput('mode').click();
ndv.actions.selectOptionInParameterDropdown('mode', 'Run Once for Each Item');
getEditor().type('{selectall}').paste(`$input.itemMatching()
getEditor()
.type('{selectall}')
.paste(`$input.itemMatching()
$input.all()
$input.first()
$input.item()

View file

@ -7,7 +7,8 @@
"test:e2e:ui": "scripts/run-e2e.js ui",
"test:e2e:dev": "scripts/run-e2e.js dev",
"test:e2e:all": "scripts/run-e2e.js all",
"format": "prettier --write . --ignore-path ../.prettierignore",
"format": "biome format --write .",
"format:check": "biome ci .",
"lint": "eslint . --quiet",
"lintfix": "eslint . --fix",
"develop": "cd ..; pnpm dev",

10
lefthook.yml Normal file
View file

@ -0,0 +1,10 @@
pre-commit:
commands:
biome_check:
glob: 'packages/**/*.{js,ts,json}'
run: ./node_modules/.bin/biome check --write --no-errors-on-unmatched --files-ignore-unknown=true --colors=off {staged_files}
stage_fixed: true
prettier_check:
glob: 'packages/**/*.{vue,yml,md}'
run: ./node_modules/.bin/prettier --write --ignore-unknown --no-error-on-unmatched-pattern {staged_files}
stage_fixed: true

View file

@ -1,7 +1,7 @@
{
"folders": [
{
"path": ".",
},
],
"path": "."
}
]
}

View file

@ -8,6 +8,7 @@
},
"packageManager": "pnpm@9.6.0",
"scripts": {
"prepare": "lefthook install",
"preinstall": "node scripts/block-npm-install.js",
"build": "turbo run build",
"build:backend": "turbo run build:backend",
@ -19,6 +20,7 @@
"clean": "turbo run clean --parallel",
"reset": "node scripts/ensure-zx.mjs && zx scripts/reset.mjs",
"format": "turbo run format && node scripts/format.mjs",
"format:check": "turbo run format:check",
"lint": "turbo run lint",
"lintfix": "turbo run lintfix",
"lint:backend": "turbo run lint:backend",
@ -38,6 +40,7 @@
"worker": "./packages/cli/bin/n8n worker"
},
"devDependencies": {
"@biomejs/biome": "^1.9.0",
"@n8n_io/eslint-config": "workspace:*",
"@types/jest": "^29.5.3",
"@types/supertest": "^6.0.2",
@ -46,6 +49,7 @@
"jest-expect-message": "^1.1.3",
"jest-mock": "^29.6.2",
"jest-mock-extended": "^3.0.4",
"lefthook": "^1.7.15",
"nock": "^13.3.2",
"nodemon": "^3.0.1",
"p-limit": "^3.1.0",

View file

@ -6,7 +6,8 @@
"dev": "pnpm watch",
"typecheck": "tsc --noEmit",
"build": "tsc -p tsconfig.build.json",
"format": "prettier --write . --ignore-path ../../../.prettierignore",
"format": "biome format --write .",
"format:check": "biome ci .",
"lint": "eslint .",
"lintfix": "eslint . --fix",
"watch": "tsc -p tsconfig.build.json --watch",

View file

@ -0,0 +1,7 @@
{
"$schema": "../node_modules/@biomejs/biome/configuration_schema.json",
"extends": ["../../../biome.jsonc"],
"files": {
"ignore": ["scripts/mock-api/**"]
}
}

View file

@ -5,6 +5,8 @@
"main": "dist/index",
"scripts": {
"build": "tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json",
"format": "biome format --write .",
"format:check": "biome ci .",
"lint": "eslint .",
"lintfix": "eslint . --fix",
"start": "./bin/n8n-benchmark",

View file

@ -12,7 +12,8 @@
"typecheck": "vue-tsc --noEmit",
"lint": "eslint . --ext .js,.ts,.vue --quiet",
"lintfix": "eslint . --ext .js,.ts,.vue --fix",
"format": "prettier --write src/",
"format": "biome format --write src .storybook && prettier --write src/ --ignore-path ../../.prettierignore",
"format:check": "biome ci src .storybook && prettier --check src/ --ignore-path ../../.prettierignore",
"storybook": "storybook dev -p 6006 --no-open",
"build:storybook": "storybook build"
},

View file

@ -45,8 +45,6 @@
vertical-align: inherit; /* 2 */
}
/*
Remove inconsistent and unnecessary margins
*/
@ -63,7 +61,8 @@
button, /* (Safari) 3 lines */
input,
select,
textarea { /* (Firefox, Safari) */
textarea {
/* (Firefox, Safari) */
margin: 0;
}
@ -80,11 +79,13 @@
Add correct display
*/
main, /* (IE11) */
details { /* (Edge 18-, IE) */
details {
/* (Edge 18-, IE) */
display: block;
}
summary { /* (all) */
summary {
/* (all) */
display: list-item;
}
@ -106,18 +107,19 @@
kbd,
samp {
font-family:
/* macOS 10.10+ */ "Menlo",
/* Windows 6+ */ "Consolas",
/* Android 4+ */ "Roboto Mono",
/* Ubuntu 10.10+ */ "Ubuntu Monospace",
/* KDE Plasma 5+ */ "Noto Mono",
/* KDE Plasma 4+ */ "Oxygen Mono",
/* Linux/OpenOffice fallback */ "Liberation Mono",
/* macOS 10.10+ */
'Menlo',
/* Windows 6+ */ 'Consolas',
/* Android 4+ */ 'Roboto Mono',
/* Ubuntu 10.10+ */ 'Ubuntu Monospace',
/* KDE Plasma 5+ */ 'Noto Mono',
/* KDE Plasma 4+ */ 'Oxygen Mono',
/* Linux/OpenOffice fallback */ 'Liberation Mono',
/* fallback */ monospace,
/* macOS emoji */ "Apple Color Emoji",
/* Windows emoji */ "Segoe UI Emoji",
/* Windows emoji */ "Segoe UI Symbol",
/* Linux emoji */ "Noto Color Emoji"; /* 1 */
/* macOS emoji */ 'Apple Color Emoji',
/* Windows emoji */ 'Segoe UI Emoji',
/* Windows emoji */ 'Segoe UI Symbol',
/* Linux emoji */ 'Noto Color Emoji'; /* 1 */
font-size: 1em; /* 2 */
}
@ -201,9 +203,9 @@
Correct inability to style buttons (iOS, Safari)
*/
button,
[type="button"],
[type="reset"],
[type="submit"] {
[type='button'],
[type='reset'],
[type='submit'] {
-webkit-appearance: button;
}
@ -249,7 +251,7 @@
1. Correct outline style (Safari)
2. Correct odd appearance (Chrome, Edge, Safari)
*/
[type="search"] {
[type='search'] {
outline-offset: -2px; /* 1 */
-webkit-appearance: textfield; /* 2 */
}
@ -311,7 +313,7 @@
/*
Change cursor on busy elements (all)
*/
[aria-busy="true"] {
[aria-busy='true'] {
cursor: progress;
}
@ -325,7 +327,7 @@
/*
Change cursor on disabled, non-editable, or inoperable elements (all)
*/
[aria-disabled="true"],
[aria-disabled='true'],
[disabled] {
cursor: not-allowed;
}
@ -333,12 +335,12 @@
/*
Change display on visually hidden accessible elements (all)
*/
[aria-hidden="false"][hidden] {
[aria-hidden='false'][hidden] {
display: inline;
display: initial;
}
[aria-hidden="false"][hidden]:not(:focus) {
[aria-hidden='false'][hidden]:not(:focus) {
clip: rect(0, 0, 0, 0);
position: absolute;
}
@ -347,8 +349,8 @@
Print out URLs after links (all)
*/
@media print {
a[href^="http"]::after {
content: " (" attr(href) ")";
a[href^='http']::after {
content: ' (' attr(href) ')';
}
}
/* ----- Variables ----- */
@ -586,7 +588,7 @@
/* ----- Forms ----- */
/* ----- Misc ----- */
[tabindex="-1"]:focus {
[tabindex='-1']:focus {
outline: none;
}

View file

@ -6,7 +6,8 @@
"dev": "pnpm watch",
"typecheck": "tsc --noEmit",
"build": "tsc -p tsconfig.build.json",
"format": "prettier --write . --ignore-path ../../../.prettierignore",
"format": "biome format --write src test",
"format:check": "biome ci src test",
"lint": "eslint . --quiet",
"lintfix": "eslint . --fix",
"watch": "tsc -p tsconfig.build.json --watch",

View file

@ -8,7 +8,5 @@ module.exports = {
...sharedOptions(__dirname),
ignorePatterns: [
'src/expressions/grammar*.ts'
]
ignorePatterns: ['src/expressions/grammar*.ts'],
};

View file

@ -24,7 +24,8 @@
"test": "jest",
"lint": "eslint . --ext .ts --quiet",
"lintfix": "eslint . --ext .ts --fix",
"format": "prettier --write --ignore-path ../../.prettierignore src test"
"format": "biome format --write src test",
"format:check": "biome ci src test"
},
"peerDependencies": {
"@codemirror/language": "*",

View file

@ -6,7 +6,8 @@
"dev": "pnpm watch",
"typecheck": "tsc --noEmit",
"build": "tsc -p tsconfig.build.json",
"format": "prettier --write . --ignore-path ../../../.prettierignore",
"format": "biome format --write src test",
"format:check": "biome ci src test",
"lint": "eslint .",
"lintfix": "eslint . --fix",
"watch": "tsc -p tsconfig.build.json --watch",

View file

@ -6,7 +6,8 @@
"dev": "pnpm watch",
"typecheck": "tsc --noEmit",
"build": "tsc -p tsconfig.build.json",
"format": "prettier --write . --ignore-path ../../../.prettierignore",
"format": "biome format --write src test",
"format:check": "biome ci src test",
"lint": "eslint . --quiet",
"lintfix": "eslint . --fix",
"watch": "tsc -p tsconfig.build.json --watch",

View file

@ -1,51 +0,0 @@
module.exports = {
/**
* https://prettier.io/docs/en/options.html#semicolons
*/
semi: true,
/**
* https://prettier.io/docs/en/options.html#trailing-commas
*/
trailingComma: 'all',
/**
* https://prettier.io/docs/en/options.html#bracket-spacing
*/
bracketSpacing: true,
/**
* https://prettier.io/docs/en/options.html#tabs
*/
useTabs: true,
/**
* https://prettier.io/docs/en/options.html#tab-width
*/
tabWidth: 2,
/**
* https://prettier.io/docs/en/options.html#arrow-function-parentheses
*/
arrowParens: 'always',
/**
* https://prettier.io/docs/en/options.html#quotes
*/
singleQuote: true,
/**
* https://prettier.io/docs/en/options.html#quote-props
*/
quoteProps: 'as-needed',
/**
* https://prettier.io/docs/en/options.html#end-of-line
*/
endOfLine: 'lf',
/**
* https://prettier.io/docs/en/options.html#print-width
*/
printWidth: 100,
};

View file

@ -1,7 +1,3 @@
{
"recommendations": [
"dbaeumer.vscode-eslint",
"EditorConfig.EditorConfig",
"esbenp.prettier-vscode",
]
"recommendations": ["dbaeumer.vscode-eslint", "EditorConfig.EditorConfig", "biomejs.biome"]
}

View file

@ -9,7 +9,8 @@
"typecheck": "tsc --noEmit",
"build": "tsc -p tsconfig.build.json && pnpm n8n-copy-icons && pnpm build:metadata",
"build:metadata": "pnpm n8n-generate-known && pnpm n8n-generate-ui-types",
"format": "prettier nodes credentials --write",
"format": "biome format --write .",
"format:check": "biome ci .",
"lint": "eslint nodes credentials --quiet",
"lintfix": "eslint nodes credentials --fix",
"watch": "tsc-watch -p tsconfig.build.json --onCompilationComplete \"tsc-alias -p tsconfig.build.json\" --onSuccess \"pnpm n8n-generate-ui-types\"",

View file

@ -6,7 +6,8 @@
"dev": "pnpm watch",
"typecheck": "tsc --noEmit",
"build": "tsc -p tsconfig.build.json",
"format": "prettier --write . --ignore-path ../../../.prettierignore",
"format": "biome format --write .",
"format:check": "biome ci .",
"lint": "eslint . --quiet",
"lintfix": "eslint . --fix",
"watch": "tsc -p tsconfig.build.json --watch",

View file

@ -22,12 +22,6 @@ const config = (module.exports = {
*/
'@typescript-eslint',
/**
* Plugin to report formatting violations as lint violations
* https://github.com/prettier/eslint-plugin-prettier
*/
'eslint-plugin-prettier',
/*
* Plugin to allow specifying local ESLint rules.
* https://github.com/ivov/eslint-plugin-n8n-local-rules
@ -76,28 +70,6 @@ const config = (module.exports = {
],
rules: {
// ******************************************************************
// required by prettier plugin
// ******************************************************************
// The following rule enables eslint-plugin-prettier
// See: https://github.com/prettier/eslint-plugin-prettier#recommended-configuration
'prettier/prettier': ['error', { endOfLine: 'auto' }],
// The following two rules must be disabled when using eslint-plugin-prettier:
// See: https://github.com/prettier/eslint-plugin-prettier#arrow-body-style-and-prefer-arrow-callback-issue
/**
* https://eslint.org/docs/rules/arrow-body-style
*/
'arrow-body-style': 'off',
/**
* https://eslint.org/docs/rules/prefer-arrow-callback
*/
'prefer-arrow-callback': 'off',
// ******************************************************************
// additions to base ruleset
// ******************************************************************

View file

@ -15,7 +15,6 @@
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-lodash": "^7.4.0",
"eslint-plugin-n8n-local-rules": "^1.0.0",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-unicorn": "^51.0.1",
"eslint-plugin-unused-imports": "^3.1.0",
"eslint-plugin-vue": "^9.23.0",

View file

@ -17,7 +17,8 @@
"dev": "concurrently -k -n \"TypeScript,Node\" -c \"yellow.bold,cyan.bold\" \"npm run watch\" \"nodemon\"",
"dev:worker": "concurrently -k -n \"TypeScript,Node\" -c \"yellow.bold,cyan.bold\" \"npm run watch\" \"nodemon worker\"",
"dev:webhook": "concurrently -k -n \"TypeScript,Node\" -c \"yellow.bold,cyan.bold\" \"npm run watch\" \"nodemon webhook\"",
"format": "prettier --write . --ignore-path ../../.prettierignore",
"format": "biome format --write .",
"format:check": "biome ci .",
"lint": "eslint . --quiet",
"lintfix": "eslint . --fix",
"start": "run-script-os",

View file

@ -84,8 +84,7 @@ type ExceptionPaths = {
// string literals map
// -----------------------------------
type GetPathSegmentsWithUnions<T> =
T extends ReadonlyArray<infer C>
type GetPathSegmentsWithUnions<T> = T extends ReadonlyArray<infer C>
? [C]
: {
[K in ValidKeys<T>]: [K, ...GetPathSegmentsWithUnions<T[K]>];

View file

@ -12,6 +12,7 @@ import { UserRepository } from '@/databases/repositories/user.repository';
import { Patch, Post, RestController } from '@/decorators';
import { MessageEventBus } from '@/eventbus/message-event-bus/message-event-bus';
import type { BooleanLicenseFeature, NumericLicenseFeature } from '@/interfaces';
import type { FeatureReturnType } from '@/license';
import { License } from '@/license';
import { Logger } from '@/logger';
import { MfaService } from '@/mfa/mfa.service';
@ -115,10 +116,18 @@ export class E2EController {
) {
license.isFeatureEnabled = (feature: BooleanLicenseFeature) =>
this.enabledFeatures[feature] ?? false;
// @ts-expect-error Overriding method
// eslint-disable-next-line @typescript-eslint/unbound-method
license.getFeatureValue<NumericLicenseFeature> = (feature: NumericLicenseFeature) =>
this.numericFeatures[feature] ?? UNLIMITED_LICENSE_QUOTA;
// Ugly hack to satisfy biome parser
const getFeatureValue = <T extends keyof FeatureReturnType>(
feature: T,
): FeatureReturnType[T] => {
if (feature in this.numericFeatures) {
return this.numericFeatures[feature as NumericLicenseFeature] as FeatureReturnType[T];
} else {
return UNLIMITED_LICENSE_QUOTA as FeatureReturnType[T];
}
};
license.getFeatureValue = getFeatureValue;
license.getPlanName = () => 'Enterprise';
}

View file

@ -11,7 +11,10 @@ export class AuthIdentity extends WithTimestamps {
@Column()
userId: string;
@ManyToOne(() => User, (user) => user.authIdentities)
@ManyToOne(
() => User,
(user) => user.authIdentities,
)
user: User;
@PrimaryColumn()

View file

@ -8,6 +8,8 @@ export class AddStatusToExecutions1674138566000 implements ReversibleMigration {
}
async down({ queryRunner, tablePrefix }: MigrationContext) {
await queryRunner.query(`ALTER TABLE \`${tablePrefix}execution_entity\` DROP COLUMN \`status\``);
await queryRunner.query(
`ALTER TABLE \`${tablePrefix}execution_entity\` DROP COLUMN \`status\``,
);
}
}

View file

@ -8,9 +8,11 @@ import { EventService } from './event.service';
export class EventRelay {
constructor(readonly eventService: EventService) {}
protected setupListeners<EventNames extends keyof RelayEventMap>(map: {
protected setupListeners<EventNames extends keyof RelayEventMap>(
map: {
[EventName in EventNames]?: (event: RelayEventMap[EventName]) => void | Promise<void>;
}) {
},
) {
for (const [eventName, handler] of Object.entries(map) as Array<
[EventNames, (event: RelayEventMap[EventNames]) => void | Promise<void>]
>) {

View file

@ -28,10 +28,10 @@ export function toSaveSettings(workflowSettings: IWorkflowSettings = {}) {
manual:
workflowSettings === undefined || workflowSettings.saveManualExecutions === 'DEFAULT'
? DEFAULTS.MANUAL
: workflowSettings.saveManualExecutions ?? DEFAULTS.MANUAL,
: (workflowSettings.saveManualExecutions ?? DEFAULTS.MANUAL),
progress:
workflowSettings === undefined || workflowSettings.saveExecutionProgress === 'DEFAULT'
? DEFAULTS.PROGRESS
: workflowSettings.saveExecutionProgress ?? DEFAULTS.PROGRESS,
: (workflowSettings.saveExecutionProgress ?? DEFAULTS.PROGRESS),
};
}

View file

@ -292,7 +292,7 @@ export class ExternalSecretsManager {
}
settings[provider] = {
connected,
connectedAt: connected ? new Date() : settings[provider]?.connectedAt ?? null,
connectedAt: connected ? new Date() : (settings[provider]?.connectedAt ?? null),
settings: settings[provider]?.settings ?? {},
};

View file

@ -21,7 +21,7 @@ import type { BooleanLicenseFeature, NumericLicenseFeature } from './interfaces'
import type { RedisServicePubSubPublisher } from './services/redis/redis-service-pub-sub-publisher';
import { RedisService } from './services/redis.service';
type FeatureReturnType = Partial<
export type FeatureReturnType = Partial<
{
planName: string;
} & { [K in NumericLicenseFeature]: number } & { [K in BooleanLicenseFeature]: boolean }

View file

@ -42,12 +42,9 @@ export class MultiMainSetup extends TypedEmitter<MultiMainEvents> {
await this.tryBecomeLeader(); // prevent initial wait
this.leaderCheckInterval = setInterval(
async () => {
this.leaderCheckInterval = setInterval(async () => {
await this.checkLeader();
},
config.getEnv('multiMainSetup.interval') * TIME.SECOND,
);
}, config.getEnv('multiMainSetup.interval') * TIME.SECOND);
}
async shutdown() {

View file

@ -15,7 +15,8 @@
"typecheck": "tsc --noEmit",
"build": "tsc -p tsconfig.build.json",
"dev": "pnpm watch",
"format": "prettier --write . --ignore-path ../../.prettierignore",
"format": "biome format --write .",
"format:check": "biome ci .",
"lint": "eslint . --quiet",
"lintfix": "eslint . --fix",
"watch": "tsc -p tsconfig.build.json --watch",

View file

@ -1651,9 +1651,9 @@ export class WorkflowExecute {
// array as this shows that the parent nodes executed but they did not have any
// data to pass on.
const inputsWithData = this.runExecutionData
.executionData!.waitingExecution[
nodeName
][firstRunIndex].main.map((data, index) => (data === null ? null : index))
.executionData!.waitingExecution[nodeName][firstRunIndex].main.map((data, index) =>
data === null ? null : index,
)
.filter((data) => data !== null);
if (requiredInputs !== undefined) {

View file

@ -8,7 +8,7 @@ import { library } from '@fortawesome/fontawesome-svg-core';
import { fas } from '@fortawesome/free-solid-svg-icons';
import ElementPlus from 'element-plus';
import lang from 'element-plus/dist/locale/en.mjs'
import lang from 'element-plus/dist/locale/en.mjs';
import { N8nPlugin } from '../src/plugin';

View file

@ -0,0 +1,7 @@
{
"$schema": "../../node_modules/@biomejs/biome/configuration_schema.json",
"extends": ["../../biome.jsonc"],
"formatter": {
"ignore": ["theme/**"]
}
}

View file

@ -13,7 +13,8 @@
"build:storybook": "storybook build",
"storybook": "storybook dev -p 6006",
"chromatic": "chromatic",
"format": "prettier --write . --ignore-path ../../.prettierignore",
"format": "biome format --write . && prettier --write . --ignore-path ../../.prettierignore",
"format:check": "biome ci . && prettier --check . --ignore-path ../../.prettierignore",
"lint": "eslint src --ext .js,.ts,.vue --quiet",
"lintfix": "eslint src --ext .js,.ts,.vue --fix"
},

View file

@ -37,7 +37,7 @@
--color-assistant-highlight-1: #8c90f2;
--color-assistant-highlight-2: #a977f0;
--color-assistant-highlight-3: #f0778b;
--color-askAssistant-button-background: #2E2E2E;
--color-askAssistant-button-background: #2e2e2e;
--color-askAssistant-button-background-hover: #383839;
--color-askAssistant-button-background-active: #414244;
--color-assistant-inner-highlight-hover: var(--color-askAssistant-button-background-hover);

View file

@ -0,0 +1 @@
export { n8nTruncate } from './n8n-truncate';

View file

@ -0,0 +1,76 @@
import { render } from '@testing-library/vue';
import { n8nTruncate } from './n8n-truncate';
describe('Directive n8n-truncate', () => {
it('should truncate text to 30 chars by default', async () => {
const { html } = render(
{
props: {
text: {
type: String,
},
},
template: '<div v-n8n-truncate>{{text}}</div>',
},
{
props: {
text: 'This is a very long text that should be truncated',
},
global: {
directives: {
n8nTruncate,
},
},
},
);
expect(html()).toBe('<div>This is a very long text that...</div>');
});
it('should truncate text to 30 chars in case of wrong argument', async () => {
const { html } = render(
{
props: {
text: {
type: String,
},
},
template: '<div v-n8n-truncate:ab>{{text}}</div>',
},
{
props: {
text: 'This is a very long text that should be truncated',
},
global: {
directives: {
n8nTruncate,
},
},
},
);
expect(html()).toBe('<div>This is a very long text that...</div>');
});
it('should truncate text to given length', async () => {
const { html } = render(
{
props: {
text: {
type: String,
},
},
template: '<div v-n8n-truncate:25>{{text}}</div>',
},
{
props: {
text: 'This is a very long text that should be truncated',
},
global: {
directives: {
n8nTruncate,
},
},
},
);
expect(html()).toBe('<div>This is a very long text...</div>');
});
});

View file

@ -0,0 +1,26 @@
import type { DirectiveBinding, ObjectDirective } from 'vue';
import { truncate } from '../utils/string';
/**
* Custom directive `n8nTruncate` to truncate text content of an HTML element.
*
* Usage:
* In your Vue template, use the directive `v-n8n-truncate` with an argument to specify the length to truncate to.
*
* Example:
* <p v-n8n-truncate:10>Some long text that will be truncated</p>
*
* This will truncate the text content of the paragraph to 10 characters.
*
* Hint: Do not use it on components
* https://vuejs.org/guide/reusability/custom-directives#usage-on-components
*/
export const n8nTruncate: ObjectDirective = {
mounted(el: HTMLElement, binding: DirectiveBinding) {
el.textContent = truncate(el.textContent ?? '', Number(binding.arg) || undefined);
},
updated(el: HTMLElement, binding: DirectiveBinding) {
el.textContent = truncate(el.textContent ?? '', Number(binding.arg) || undefined);
},
};

View file

@ -5,4 +5,5 @@ export * from './components';
export * from './plugin';
export * from './types';
export * from './utils';
export * from './directives';
export { locale };

View file

@ -1,5 +1,6 @@
import type { Component, Plugin } from 'vue';
import * as components from './components';
import * as directives from './directives';
export interface N8nPluginOptions {}
@ -8,5 +9,9 @@ export const N8nPlugin: Plugin<N8nPluginOptions> = {
for (const [name, component] of Object.entries(components)) {
app.component(name, component as unknown as Component);
}
for (const [name, directive] of Object.entries(directives)) {
app.directive(name, directive);
}
},
};

View file

@ -0,0 +1,17 @@
import { truncate } from './string';
describe('Utils string', () => {
describe('truncate', () => {
it('should truncate text to 30 chars by default', () => {
expect(truncate('This is a very long text that should be truncated')).toBe(
'This is a very long text that...',
);
});
it('should truncate text to given length', () => {
expect(truncate('This is a very long text that should be truncated', 25)).toBe(
'This is a very long text...',
);
});
});
});

View file

@ -0,0 +1,2 @@
export const truncate = (text: string, length = 30): string =>
text.length > length ? text.slice(0, length).trim() + '...' : text;

View file

@ -11,7 +11,12 @@
"allowSyntheticDefaultImports": true,
"baseUrl": ".",
"types": ["vitest/globals"],
"typeRoots": ["./node_modules/@testing-library", "./node_modules/@types", "../../node_modules", "../../node_modules/@types"],
"typeRoots": [
"./node_modules/@testing-library",
"./node_modules/@types",
"../../node_modules",
"../../node_modules/@types"
],
"paths": {
"n8n-design-system/*": ["./src/*"]
},

View file

@ -44,9 +44,9 @@ export default mergeConfig(
dts: false,
resolvers: [
iconsResolver({
prefix: 'icon'
})
]
prefix: 'icon',
}),
],
}),
],
resolve: {

View file

@ -0,0 +1,8 @@
{
"$schema": "../../node_modules/@biomejs/biome/configuration_schema.json",
"extends": ["../../biome.jsonc"]
// Although nothing is extended here, it is required so biome use the
// editor-ui gitignore file:
// > For now, Biome only takes the ignore file in the working directory into account.
// https://biomejs.dev/guides/integrate-in-vcs/#use-the-ignore-file
}

View file

@ -10,7 +10,8 @@
"dev": "pnpm serve",
"lint": "eslint src --ext .js,.ts,.vue --quiet",
"lintfix": "eslint src --ext .js,.ts,.vue --fix",
"format": "prettier --write . --ignore-path ../../.prettierignore",
"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 dev",
"test": "vitest run",
"test:dev": "vitest"

View file

@ -95,7 +95,7 @@ export const codeNodeEditorTheme = ({
: { minHeight: rows && rows !== -1 ? `${Number(rows + 1) * 1.3}em` : 'auto' }),
},
'.cm-gutter,.cm-content': {
minHeight: rows && rows !== -1 ? 'auto' : minHeight ?? 'calc(35vh - var(--spacing-2xl))',
minHeight: rows && rows !== -1 ? 'auto' : (minHeight ?? 'calc(35vh - var(--spacing-2xl))'),
},
'.cm-diagnosticAction': {
backgroundColor: BASE_STYLING.diagnosticButton.backgroundColor,

View file

@ -142,7 +142,7 @@ export function groupItemsInSections(
title: section.title,
children: sortAlphabetically
? sortNodeCreateElements(children[section.key] ?? [])
: children[section.key] ?? [],
: (children[section.key] ?? []),
}),
);

View file

@ -405,7 +405,8 @@ export const useExpressionEditor = ({
if (pos === 'lastExpression') {
const END_OF_EXPRESSION = ' }}';
const endOfLastExpression = readEditorValue().lastIndexOf(END_OF_EXPRESSION);
pos = endOfLastExpression !== -1 ? endOfLastExpression : editor.value?.state.doc.length ?? 0;
pos =
endOfLastExpression !== -1 ? endOfLastExpression : (editor.value?.state.doc.length ?? 0);
} else if (pos === 'end') {
pos = editor.value?.state.doc.length ?? 0;
}
@ -414,7 +415,7 @@ export const useExpressionEditor = ({
function select(anchor: number, head: number | 'end' = 'end'): void {
editor.value?.dispatch({
selection: { anchor, head: head === 'end' ? editor.value?.state.doc.length ?? 0 : head },
selection: { anchor, head: head === 'end' ? (editor.value?.state.doc.length ?? 0) : head },
});
}

View file

@ -433,12 +433,14 @@ export class N8nConnector extends AbstractConnector {
const sourceStubWithOffset =
sourceStub +
(this.getEndpointOffset && params.sourceEndpoint
? this.getEndpointOffset(params.sourceEndpoint) ?? 0
? (this.getEndpointOffset(params.sourceEndpoint) ?? 0)
: 0);
const targetStubWithOffset =
targetStub +
(this.getEndpointOffset && targetEndpoint ? this.getEndpointOffset(targetEndpoint) ?? 0 : 0);
(this.getEndpointOffset && targetEndpoint
? (this.getEndpointOffset(targetEndpoint) ?? 0)
: 0);
// same as paintinfo generated by jsplumb AbstractConnector type
const result = {

View file

@ -76,7 +76,7 @@ export class I18nClass {
* Render a string of dynamic text, i.e. a string with a constructed path to the localized value.
*/
private dynamicRender({ key, fallback }: { key: string; fallback?: string }) {
return this.i18n.te(key) ? this.i18n.t(key).toString() : fallback ?? '';
return this.i18n.te(key) ? this.i18n.t(key).toString() : (fallback ?? '');
}
displayTimer(msPassed: number, showMs = false): string {

View file

@ -739,7 +739,7 @@ function withCanvasReadOnlyMeta(route: RouteRecordRaw) {
}
const router = createRouter({
history: createWebHistory(import.meta.env.DEV ? '/' : window.BASE_PATH ?? '/'),
history: createWebHistory(import.meta.env.DEV ? '/' : (window.BASE_PATH ?? '/')),
scrollBehavior(to: RouteLocationNormalized, _, savedPosition) {
// saved position == null means the page is NOT visited from history (back button)
if (savedPosition === null && to.name === VIEWS.TEMPLATES && to.meta?.setScrollPosition) {

View file

@ -189,7 +189,7 @@ export const useCredentialsStore = defineStore(STORES.CREDENTIALS, () => {
? email
? `${name} (${email})`
: name
: email ?? i18n.baseText('credentialEdit.credentialSharing.info.sharee.fallback');
: (email ?? i18n.baseText('credentialEdit.credentialSharing.info.sharee.fallback'));
};
});

View file

@ -91,7 +91,7 @@ export const useNDVStore = defineStore(STORES.NDV, {
ndvInputDataWithPinnedData(): INodeExecutionData[] {
const data = this.ndvInputData;
return this.ndvInputNodeName
? useWorkflowsStore().pinDataByNodeName(this.ndvInputNodeName) ?? data
? (useWorkflowsStore().pinDataByNodeName(this.ndvInputNodeName) ?? data)
: data;
},
hasInputData(): boolean {

View file

@ -21,7 +21,7 @@ export const useWorkflowsEEStore = defineStore(STORES.WORKFLOWS_EE, {
const workflow = useWorkflowsStore().getWorkflowById(workflowId);
const { name, email } = splitName(workflow?.homeProject?.name ?? '');
return name ? (email ? `${name} (${email})` : name) : email ?? fallback;
return name ? (email ? `${name} (${email})` : name) : (email ?? fallback);
};
},
},

View file

@ -1,2 +1,2 @@
@import "codemirror";
@import "vueflow";
@import 'codemirror';
@import 'vueflow';

View file

@ -302,8 +302,10 @@ export type ExternalHooksKey = {
[K in keyof ExternalHooks]: `${K}.${Extract<keyof ExternalHooks[K], string>}`;
}[keyof ExternalHooks];
type ExtractHookMethodArray<P extends keyof ExternalHooks, S extends keyof ExternalHooks[P]> =
ExternalHooks[P][S] extends Array<infer U> ? U : never;
type ExtractHookMethodArray<
P extends keyof ExternalHooks,
S extends keyof ExternalHooks[P],
> = ExternalHooks[P][S] extends Array<infer U> ? U : never;
type ExtractHookMethodFunction<T> = T extends ExternalHooksMethod ? T : never;

View file

@ -170,6 +170,8 @@ describe('SettingsSourceControl', () => {
['git@192.168.1.101:2222:user/repo', true],
['git@ssh.dev.azure.com:v3/User/repo/directory', true],
['ssh://git@mydomain.example:2224/gitolite-admin', true],
['gituser@192.168.1.1:ABC/Repo4.git', true],
['root@192.168.1.1/repo.git', true],
['http://github.com/user/repository', false],
['https://github.com/user/repository', false],
])('%s', async (url: string, isValid: boolean) => {

View file

@ -131,7 +131,7 @@ const repoUrlValidationRules: Array<Rule | RuleGroup> = [
name: 'MATCH_REGEX',
config: {
regex:
/^(ssh:\/\/)?git@(?:\[[0-9a-fA-F:]+\]|(?:[a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+)(?::[0-9]+)*:(?:v[0-9]+\/)?[a-zA-Z0-9_.\-\/]+(\.git)?(?:\/[a-zA-Z0-9_.\-\/]+)*$/,
/^(?:git@|ssh:\/\/git@|[\w-]+@)(?:[\w.-]+|\[[0-9a-fA-F:]+])(?::\d+)?[:\/][\w\-~]+(?:\/[\w\-~]+)*(?:\.git)?(?:\/.*)?$/,
message: locale.baseText('settings.sourceControl.repoUrlInvalid'),
},
},

View file

@ -6,7 +6,7 @@ import { sentryVitePlugin } from '@sentry/vite-plugin';
import packageJSON from './package.json';
import { vitestConfig } from '../design-system/vite.config.mts';
import icons from 'unplugin-icons/vite';
import iconsResolver from 'unplugin-icons/resolver'
import iconsResolver from 'unplugin-icons/resolver';
import components from 'unplugin-vue-components/vite';
const vendorChunks = ['vue', 'vue-router'];
@ -80,9 +80,9 @@ const plugins = [
dts: './src/components.d.ts',
resolvers: [
iconsResolver({
prefix: 'icon'
})
]
prefix: 'icon',
}),
],
}),
vue(),
];

View file

@ -13,7 +13,8 @@
"dev": "pnpm watch",
"build": "tsc --noEmit",
"build-node-dev": "tsc",
"format": "prettier --write . --ignore-path ../../.prettierignore",
"format": "biome format --write .",
"format:check": "biome ci .",
"lint": "eslint . --quiet",
"lintfix": "eslint . --fix",
"prepack": "echo \"Building project...\" && rm -rf dist && tsc -b",

View file

@ -0,0 +1,7 @@
{
"$schema": "../../node_modules/@biomejs/biome/configuration_schema.json",
"extends": ["../../biome.jsonc"],
"formatter": {
"ignore": ["nodes/**/test/*.json"]
}
}

View file

@ -169,7 +169,7 @@ export class TableFieldMapper {
}
setField(field: string) {
return this.mapIds ? field : this.nameToIdMapping[field] ?? field;
return this.mapIds ? field : (this.nameToIdMapping[field] ?? field);
}
idsToNames(obj: Record<string, unknown>) {

View file

@ -19,15 +19,15 @@ export namespace BrevoNode {
type ValidatedEmail = ToEmail | SenderEmail | CCEmail | BBCEmail;
const enum OVERRIDE_MAP_VALUES {
'CATEGORY' = 'category',
'NORMAL' = 'boolean',
'TRANSACTIONAL' = 'id',
CATEGORY = 'category',
NORMAL = 'boolean',
TRANSACTIONAL = 'id',
}
const enum OVERRIDE_MAP_TYPE {
'CATEGORY' = 'category',
'NORMAL' = 'normal',
'TRANSACTIONAL' = 'transactional',
CATEGORY = 'category',
NORMAL = 'normal',
TRANSACTIONAL = 'transactional',
}
export const INTERCEPTORS = new Map<string, (body: JsonObject) => void>([

View file

@ -16,19 +16,19 @@ export const enum TLP {
}
export const enum ObservableDataType {
'domain' = 'domain',
'file' = 'file',
'filename' = 'filename',
'fqdn' = 'fqdn',
'hash' = 'hash',
'ip' = 'ip',
'mail' = 'mail',
'mail_subject' = 'mail_subject',
'other' = 'other',
'regexp' = 'regexp',
'registry' = 'registry',
'uri_path' = 'uri_path',
'url' = 'url',
domain = 'domain',
file = 'file',
filename = 'filename',
fqdn = 'fqdn',
hash = 'hash',
ip = 'ip',
mail = 'mail',
mail_subject = 'mail_subject',
other = 'other',
regexp = 'regexp',
registry = 'registry',
uri_path = 'uri_path',
url = 'url',
'user-agent' = 'user-agent',
}
export interface IJob {

View file

@ -32,9 +32,9 @@ export interface IUser {
isAnonymous?: boolean;
}
const enum Type {
'TYPE_UNSPECIFIED',
'HUMAN',
'BOT',
TYPE_UNSPECIFIED,
HUMAN,
BOT,
}
// // TODO: define other interfaces

View file

@ -485,7 +485,6 @@ export class GoogleSheet {
columnValuesList?: string[];
}) {
const decodedRange = this.getDecodedSheetRange(range);
// prettier-ignore
const keyRowRange = `${decodedRange.name}!${decodedRange.start?.column || ''}${keyRowIndex + 1}:${decodedRange.end?.column || ''}${keyRowIndex + 1}`;
const sheetDatakeyRow = columnNamesList || (await this.getData(keyRowRange, valueRenderMode));

View file

@ -84,7 +84,6 @@ export async function sheetsSearch(
returnData.push({
name: sheet.properties!.title as string,
value: (sheet.properties!.sheetId as number) || 'gid=0',
//prettier-ignore
url: `https://docs.google.com/spreadsheets/d/${spreadsheetId}/edit#gid=${sheet.properties!.sheetId}`,
});
}

View file

@ -144,9 +144,12 @@ export class KoBoToolboxTrigger implements INodeType {
const req = this.getRequestObject();
const formatOptions = this.getNodeParameter('formatOptions') as IDataObject;
// prettier-ignore
const responseData = formatOptions.reformat
? formatSubmission(req.body as IDataObject, parseStringList(formatOptions.selectMask as string), parseStringList(formatOptions.numberMask as string))
? formatSubmission(
req.body as IDataObject,
parseStringList(formatOptions.selectMask as string),
parseStringList(formatOptions.numberMask as string),
)
: req.body;
if (formatOptions.download) {

View file

@ -27,7 +27,6 @@ import { filters } from './descriptions/Filters';
function uuidValidateWithoutDashes(this: IExecuteFunctions, value: string) {
if (uuidValidate(value)) return true;
if (value.length == 32) {
//prettier-ignore
const strWithDashes = `${value.slice(0, 8)}-${value.slice(8, 12)}-${value.slice(12, 16)}-${value.slice(16, 20)}-${value.slice(20)}`;
if (uuidValidate(strWithDashes)) return true;
}
@ -309,7 +308,6 @@ export function formatBlocks(blocks: IDataObject[]) {
[block.type as string]: {
...(block.type === 'to_do' ? { checked: block.checked } : {}),
...(block.type === 'image' ? { type: 'external', external: { url: block.url } } : {}),
// prettier-ignore,
...(!['image'].includes(block.type as string) ? getTextBlocks(block) : {}),
},
});
@ -867,9 +865,12 @@ export type FileRecord = {
};
};
};
// prettier-ignore
export async function downloadFiles(this: IExecuteFunctions | IPollFunctions, records: FileRecord[], pairedItem?: IPairedItemData[]): Promise<INodeExecutionData[]> {
export async function downloadFiles(
this: IExecuteFunctions | IPollFunctions,
records: FileRecord[],
pairedItem?: IPairedItemData[],
): Promise<INodeExecutionData[]> {
const elements: INodeExecutionData[] = [];
for (const record of records) {
const element: INodeExecutionData = { json: {}, binary: {} };
@ -887,10 +888,12 @@ export async function downloadFiles(this: IExecuteFunctions | IPollFunctions, re
'',
{},
{},
file?.file?.url as string || file?.external?.url as string,
(file?.file?.url as string) || (file?.external?.url as string),
{ json: false, encoding: null },
);
element.binary![`${key}_${index}`] = await this.helpers.prepareBinaryData(data as Buffer);
element.binary![`${key}_${index}`] = await this.helpers.prepareBinaryData(
data as Buffer,
);
}
}
}

View file

@ -536,8 +536,10 @@ export async function pgUpdate(
} else {
const where =
' WHERE ' +
updateKeys
// eslint-disable-next-line n8n-local-rules/no-interpolation-in-regular-string
updateKeys.map((entry) => pgp.as.name(entry.name) + ' = ${' + entry.prop + '}').join(' AND ');
.map((entry) => pgp.as.name(entry.name) + ' = ${' + entry.prop + '}')
.join(' AND ');
if (mode === 'transaction') {
return await db.tx(async (t) => {
const result: IDataObject[] = [];
@ -664,8 +666,10 @@ export async function pgUpdateV2(
} else {
const where =
' WHERE ' +
updateKeys
// eslint-disable-next-line n8n-local-rules/no-interpolation-in-regular-string
updateKeys.map((entry) => pgp.as.name(entry.name) + ' = ${' + entry.prop + '}').join(' AND ');
.map((entry) => pgp.as.name(entry.name) + ' = ${' + entry.prop + '}')
.join(' AND ');
if (mode === 'transaction') {
return await db.tx(async (t) => {
const result: IDataObject[] = [];

View file

@ -62,9 +62,10 @@ export async function quickbaseApiRequest(
}
}
//@ts-ignore
// prettier-ignore
export async function getFieldsObject(this: IHookFunctions | ILoadOptionsFunctions | IExecuteFunctions, tableId: string): any {
export async function getFieldsObject(
this: IHookFunctions | ILoadOptionsFunctions | IExecuteFunctions,
tableId: string,
): Promise<any> {
const fieldsLabelKey: { [key: string]: number } = {};
const fieldsIdKey: { [key: number]: string } = {};
const data = await quickbaseApiRequest.call(this, 'GET', '/fields', {}, { tableId });

View file

@ -158,9 +158,12 @@ export function addAdditionalFields(
}
sendRows.push(sendButtonData);
}
// @ts-ignore
// prettier-ignore
((body.reply_markup as ITelegramInlineReply | ITelegramReplyKeyboard)[setParameterName] as ITelegramKeyboardButton[][]).push(sendRows);
const array = (body.reply_markup as ITelegramInlineReply | ITelegramReplyKeyboard)[
setParameterName
] as ITelegramKeyboardButton[][];
array.push(sendRows);
}
}
} else if (replyMarkupOption === 'forceReply') {

View file

@ -5,19 +5,19 @@ export const enum ObservableStatus {
DELETED = 'Deleted',
}
export const enum ObservableDataType {
'domain' = 'domain',
'file' = 'file',
'filename' = 'filename',
'fqdn' = 'fqdn',
'hash' = 'hash',
'ip' = 'ip',
'mail' = 'mail',
'mail_subject' = 'mail_subject',
'other' = 'other',
'regexp' = 'regexp',
'registry' = 'registry',
'uri_path' = 'uri_path',
'url' = 'url',
domain = 'domain',
file = 'file',
filename = 'filename',
fqdn = 'fqdn',
hash = 'hash',
ip = 'ip',
mail = 'mail',
mail_subject = 'mail_subject',
other = 'other',
regexp = 'regexp',
registry = 'registry',
uri_path = 'uri_path',
url = 'url',
'user-agent' = 'user-agent',
}

View file

@ -9,7 +9,8 @@
"typecheck": "tsc --noEmit",
"build": "tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json && pnpm n8n-copy-icons && pnpm n8n-generate-translations && pnpm build:metadata",
"build:metadata": "pnpm n8n-generate-known && pnpm n8n-generate-ui-types",
"format": "prettier --write . --ignore-path ../../.prettierignore",
"format": "biome format --write .",
"format:check": "biome ci .",
"lint": "eslint . --quiet && node ./scripts/validate-load-options-methods.js",
"lintfix": "eslint . --fix",
"watch": "tsc-watch -p tsconfig.build.json --onCompilationComplete \"tsc-alias -p tsconfig.build.json\" --onSuccess \"pnpm n8n-generate-ui-types\"",

View file

@ -18,7 +18,8 @@
"dev": "pnpm watch",
"typecheck": "tsc --noEmit",
"build": "tsc -p tsconfig.build.json",
"format": "prettier --write . --ignore-path ../../.prettierignore",
"format": "biome format --write .",
"format:check": "biome ci .",
"lint": "eslint . --quiet",
"lintfix": "eslint . --fix",
"watch": "tsc -p tsconfig.build.json --watch",

View file

@ -157,13 +157,11 @@ describe('tmpl Expression Parser', () => {
});
test('Multiple optional chains in an expression', () => {
expect(extendTransform('$json.test?.test2($json.test?.test2)')?.code)
.toBe(`window.chainCancelToken2 = ((window.chainValue2 = $json.test) ?? undefined) === undefined, window.chainCancelToken2 === true ? undefined : window.chainValue2.test2(
expect(extendTransform('$json.test?.test2($json.test?.test2)')?.code).toBe(`window.chainCancelToken2 = ((window.chainValue2 = $json.test) ?? undefined) === undefined, window.chainCancelToken2 === true ? undefined : window.chainValue2.test2(
(window.chainCancelToken1 = ((window.chainValue1 = $json.test) ?? undefined) === undefined, window.chainCancelToken1 === true ? undefined : window.chainValue1.test2)
);`);
expect(extendTransform('$json.test?.test2($json.test.sum?.())')?.code)
.toBe(`window.chainCancelToken2 = ((window.chainValue2 = $json.test) ?? undefined) === undefined, window.chainCancelToken2 === true ? undefined : window.chainValue2.test2(
expect(extendTransform('$json.test?.test2($json.test.sum?.())')?.code).toBe(`window.chainCancelToken2 = ((window.chainValue2 = $json.test) ?? undefined) === undefined, window.chainCancelToken2 === true ? undefined : window.chainValue2.test2(
(window.chainCancelToken1 = ((window.chainValue1 = extendOptional($json.test, "sum")) ?? undefined) === undefined, window.chainCancelToken1 === true ? undefined : window.chainValue1())
);`);
});

View file

@ -1959,7 +1959,7 @@ describe('RoutingNode', () => {
executeSingleFunctions.getNodeParameter = (parameterName: string) =>
parameterName in testData.input.node.parameters
? testData.input.node.parameters[parameterName]
: getNodeParameter(parameterName) ?? {};
: (getNodeParameter(parameterName) ?? {});
const result = await routingNode.runNode(
inputData,

View file

@ -27,10 +27,7 @@
"name": "PinnedSet",
"type": "n8n-nodes-base.set",
"typeVersion": 3.3,
"position": [
1120,
380
]
"position": [1120, 380]
},
{
"parameters": {
@ -50,20 +47,14 @@
"name": "NotPinnedSet1",
"type": "n8n-nodes-base.set",
"typeVersion": 3.3,
"position": [
1360,
380
]
"position": [1360, 380]
},
{
"parameters": {},
"id": "f36672e5-8c87-480e-a5b8-de9da6b63192",
"name": "Start",
"type": "n8n-nodes-base.manualTrigger",
"position": [
920,
380
],
"position": [920, 380],
"typeVersion": 1
}
],

View file

@ -123,6 +123,9 @@ importers:
.:
devDependencies:
'@biomejs/biome':
specifier: ^1.9.0
version: 1.9.0
'@n8n_io/eslint-config':
specifier: workspace:*
version: link:packages/@n8n_io/eslint-config
@ -147,6 +150,9 @@ importers:
jest-mock-extended:
specifier: ^3.0.4
version: 3.0.4(jest@29.6.2(@types/node@18.16.16)(ts-node@10.9.2(@types/node@18.16.16)(typescript@5.6.2)))(typescript@5.6.2)
lefthook:
specifier: ^1.7.15
version: 1.7.15
nock:
specifier: ^13.3.2
version: 13.3.2
@ -641,9 +647,6 @@ importers:
eslint-plugin-n8n-local-rules:
specifier: ^1.0.0
version: 1.0.0
eslint-plugin-prettier:
specifier: ^5.1.3
version: 5.1.3(@types/eslint@8.56.5)(eslint-config-prettier@9.1.0(eslint@8.57.0))(eslint@8.57.0)(prettier@3.2.5)
eslint-plugin-unicorn:
specifier: ^51.0.1
version: 51.0.1(eslint@8.57.0)
@ -1853,7 +1856,7 @@ importers:
devDependencies:
'@langchain/core':
specifier: 'catalog:'
version: 0.2.31(langchain@0.2.18(axios@1.7.4)(openai@4.58.0))(openai@4.58.0)
version: 0.2.31(langchain@0.2.18(axios@1.7.4)(openai@4.58.0(zod@3.23.8)))(openai@4.58.0(zod@3.23.8))
'@types/deep-equal':
specifier: ^1.0.1
version: 1.0.1
@ -3137,6 +3140,59 @@ packages:
'@bcoe/v8-coverage@0.2.3':
resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
'@biomejs/biome@1.9.0':
resolution: {integrity: sha512-NlWh2F1wbxB3O/wE+aohGL0BziTS6e+6+dyFvpdeqLsbQZY7EsiklFb9W5Xs41U4vEmY7ANgdNp+oVDij6sQdA==}
engines: {node: '>=14.21.3'}
hasBin: true
'@biomejs/cli-darwin-arm64@1.9.0':
resolution: {integrity: sha512-2w9v/NRtYSmodx5QWQ49OGcyGKSECdWKbzc7n532Iq5sBhkKk996fd19icT6BuL54f01KFKRCRibAW+A2rg1Kw==}
engines: {node: '>=14.21.3'}
cpu: [arm64]
os: [darwin]
'@biomejs/cli-darwin-x64@1.9.0':
resolution: {integrity: sha512-fBVt8jJQi0zX0SJ1C+tdzUbRpuX/07sgtBXEhunWRkPjdi6W/2S1sYHQ1wKn4OKiRAKfHM2Cf2FNO7hQvY61dA==}
engines: {node: '>=14.21.3'}
cpu: [x64]
os: [darwin]
'@biomejs/cli-linux-arm64-musl@1.9.0':
resolution: {integrity: sha512-Jy84mZ4vcppdmWMgQWOCfd8qIVC/vHmlaS5gy7GXkdWlBKSQ56YxEXTU58MHTbZ16LwJQpK2IulqRCC/rqWLBA==}
engines: {node: '>=14.21.3'}
cpu: [arm64]
os: [linux]
'@biomejs/cli-linux-arm64@1.9.0':
resolution: {integrity: sha512-l8U2lcqsl9yKPP5WUdIrKH//C1pWyM2cSUfcTBn6GSvXmsSjBNEdGSdM4Wfne777Oe/9ONaD1Ga53U2HksHHLw==}
engines: {node: '>=14.21.3'}
cpu: [arm64]
os: [linux]
'@biomejs/cli-linux-x64-musl@1.9.0':
resolution: {integrity: sha512-N3enoFoIrkB6qJWyYfTiYmFdB1R/Mrij1dd1xBHqxxCKZY9GRkEswRX3F1Uqzo5T+9Iu8nAQobDqI/ygicYy/Q==}
engines: {node: '>=14.21.3'}
cpu: [x64]
os: [linux]
'@biomejs/cli-linux-x64@1.9.0':
resolution: {integrity: sha512-8jAzjrrJTj510pwq4aVs7ZKkOvEy1D+nzl9DKvrPh4TOyUw5Ie+0EDwXGE2RAkCKHkGNOQBZ78WtIdsATgz5sA==}
engines: {node: '>=14.21.3'}
cpu: [x64]
os: [linux]
'@biomejs/cli-win32-arm64@1.9.0':
resolution: {integrity: sha512-AIjwJTGfdWGMRluSQ9pDB29nzce077dfHh0/HMqzztKzgD3spyuo2R9VoaFpbR0hLHPWEH6g6OxxDO7hfkXNkQ==}
engines: {node: '>=14.21.3'}
cpu: [arm64]
os: [win32]
'@biomejs/cli-win32-x64@1.9.0':
resolution: {integrity: sha512-4/4wTjNSoyNkm1SzcUaStDx46baX1VJRXtUoeEHjX9LfedR5N3qwZz5KfrRUnCd2fl5bmXK1CwMqKBkoF6zEiA==}
engines: {node: '>=14.21.3'}
cpu: [x64]
os: [win32]
'@chromatic-com/storybook@1.5.0':
resolution: {integrity: sha512-LkLKv7SWu/6kGep1ft2HA1T/cm14wU0zoW71gE4cZRcgUoRQJtyhITFTLHrjqAxz6bVqNgqzQtd5oBZ2nK3L3g==}
engines: {node: '>=16.0.0', yarn: '>=1.22.18'}
@ -9956,6 +10012,60 @@ packages:
leac@0.6.0:
resolution: {integrity: sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==}
lefthook-darwin-arm64@1.7.15:
resolution: {integrity: sha512-o8JgCnLM7UgF9g0MwarHJFoj6aVSSkUInHpsQZegV1c7CVQY/LIXgSeAWRb9XBvuUjByJ/HiHFMp9/hAALTwxQ==}
cpu: [arm64]
os: [darwin]
lefthook-darwin-x64@1.7.15:
resolution: {integrity: sha512-nrdoex0icmXYl7AAvG7WtfEzjQtX/kWvM88jPu/gijH6VhAjp110Y8VScB7rWOcApb5kNNeqd1nKMAFgZ8KhAg==}
cpu: [x64]
os: [darwin]
lefthook-freebsd-arm64@1.7.15:
resolution: {integrity: sha512-zl1TPynklJZZ/YsWb2H0gfErQbey318i2W85wIKGEk8kC2UzOgYTuPffnfi7kANei9ntZnhvGgilc6tqYOPuGQ==}
cpu: [arm64]
os: [freebsd]
lefthook-freebsd-x64@1.7.15:
resolution: {integrity: sha512-/gKPwhWhZ3Q/efGs73/qw6nyR4WCT74oSTgn8wabAJO5+T/2FXTKzo7IiKkQmScmY5OcnD/0teJbJZ17VWSSOg==}
cpu: [x64]
os: [freebsd]
lefthook-linux-arm64@1.7.15:
resolution: {integrity: sha512-re2f8WmYw19n8ojit0rnKbERAzD1/iCpU/Y8anXOjD/ROw/cpSfO88uKQrUCNY9Rp4XLtkkp9oDJs3Eg7JS7vA==}
cpu: [arm64]
os: [linux]
lefthook-linux-x64@1.7.15:
resolution: {integrity: sha512-Kj6ieTlhFGlbPDyVFeOb296MS9x/Jj5y/xTPLBM+EKbdsTJSvUYu5FdtEWfhpLyWmPfkZtULHcTQE1hoo9Q4Cg==}
cpu: [x64]
os: [linux]
lefthook-openbsd-arm64@1.7.15:
resolution: {integrity: sha512-85amE23mJ4BC9OThLkt+QCeVwue2Cr0ezN9LSwP0h8+royyj7YRcSu0VM/Et3B9LO50T2bpdI9norOqTcPZ9yA==}
cpu: [arm64]
os: [openbsd]
lefthook-openbsd-x64@1.7.15:
resolution: {integrity: sha512-vXx/PpcalFgdvqkoHLI4KTGZp0ti+VCCL7RqDTA6n+GZpxPTWEXSOz3GuwNALX93Dn6MOYQYktKtXADwy24fcA==}
cpu: [x64]
os: [openbsd]
lefthook-windows-arm64@1.7.15:
resolution: {integrity: sha512-jPKdQOLWQLRPO3VfI3ptpmdQBBsXTwaCLKXMo/gcSyU0xE/ltPD4QqvHzTAFJo00VcKRHjv9QeH69qhENjJtjw==}
cpu: [arm64]
os: [win32]
lefthook-windows-x64@1.7.15:
resolution: {integrity: sha512-tlkSU669+b64AsqytGy1W3au7h8kFjt5ejLhTkErJpylTqThZIHm/GI0wUmpX+ud8kekM+9j407dweAHYOQ1XA==}
cpu: [x64]
os: [win32]
lefthook@1.7.15:
resolution: {integrity: sha512-HW2mYkhg0a3RH2t57+ZJwacJiSIuDEhsXQAaCw6iGeN7zowdUV7g5QtnRdFdPkaK2eaNFpG6Rp0GsTrl/v0gNg==}
hasBin: true
leven@3.1.0:
resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==}
engines: {node: '>=6'}
@ -16142,6 +16252,41 @@ snapshots:
'@bcoe/v8-coverage@0.2.3': {}
'@biomejs/biome@1.9.0':
optionalDependencies:
'@biomejs/cli-darwin-arm64': 1.9.0
'@biomejs/cli-darwin-x64': 1.9.0
'@biomejs/cli-linux-arm64': 1.9.0
'@biomejs/cli-linux-arm64-musl': 1.9.0
'@biomejs/cli-linux-x64': 1.9.0
'@biomejs/cli-linux-x64-musl': 1.9.0
'@biomejs/cli-win32-arm64': 1.9.0
'@biomejs/cli-win32-x64': 1.9.0
'@biomejs/cli-darwin-arm64@1.9.0':
optional: true
'@biomejs/cli-darwin-x64@1.9.0':
optional: true
'@biomejs/cli-linux-arm64-musl@1.9.0':
optional: true
'@biomejs/cli-linux-arm64@1.9.0':
optional: true
'@biomejs/cli-linux-x64-musl@1.9.0':
optional: true
'@biomejs/cli-linux-x64@1.9.0':
optional: true
'@biomejs/cli-win32-arm64@1.9.0':
optional: true
'@biomejs/cli-win32-x64@1.9.0':
optional: true
'@chromatic-com/storybook@1.5.0(react@18.2.0)':
dependencies:
chromatic: 11.4.1
@ -17149,20 +17294,7 @@ snapshots:
dependencies:
'@langchain/core': 0.2.31(langchain@0.2.18(axios@1.7.4)(openai@4.58.0(zod@3.23.8)))(openai@4.58.0(zod@3.23.8))
js-tiktoken: 1.0.12
openai: 4.58.0(zod@3.23.8)
zod: 3.23.8
zod-to-json-schema: 3.23.2(zod@3.23.8)
transitivePeerDependencies:
- encoding
- langchain
- supports-color
optional: true
'@langchain/openai@0.2.10(langchain@0.2.18(axios@1.7.4)(openai@4.58.0))':
dependencies:
'@langchain/core': 0.2.31(langchain@0.2.18(axios@1.7.4)(openai@4.58.0))(openai@4.58.0(zod@3.23.8))
js-tiktoken: 1.0.12
openai: 4.58.0(zod@3.23.8)
openai: 4.58.0(encoding@0.1.13)(zod@3.23.8)
zod: 3.23.8
zod-to-json-schema: 3.23.2(zod@3.23.8)
transitivePeerDependencies:
@ -22425,7 +22557,7 @@ snapshots:
eslint-import-resolver-node@0.3.9:
dependencies:
debug: 3.2.7(supports-color@5.5.0)
debug: 3.2.7(supports-color@8.1.1)
is-core-module: 2.13.1
resolve: 1.22.8
transitivePeerDependencies:
@ -22450,7 +22582,7 @@ snapshots:
eslint-module-utils@2.8.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.6.2))(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0):
dependencies:
debug: 3.2.7(supports-color@5.5.0)
debug: 3.2.7(supports-color@8.1.1)
optionalDependencies:
'@typescript-eslint/parser': 7.2.0(eslint@8.57.0)(typescript@5.6.2)
eslint: 8.57.0
@ -22470,7 +22602,7 @@ snapshots:
array.prototype.findlastindex: 1.2.3
array.prototype.flat: 1.3.2
array.prototype.flatmap: 1.3.2
debug: 3.2.7(supports-color@5.5.0)
debug: 3.2.7(supports-color@8.1.1)
doctrine: 2.1.0
eslint: 8.57.0
eslint-import-resolver-node: 0.3.9
@ -23336,7 +23468,7 @@ snapshots:
array-parallel: 0.1.3
array-series: 0.1.5
cross-spawn: 4.0.2
debug: 3.2.7(supports-color@5.5.0)
debug: 3.2.7(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
@ -24816,34 +24948,7 @@ snapshots:
optionalDependencies:
'@langchain/core': 0.2.31(langchain@0.2.18(axios@1.7.4)(openai@4.58.0(zod@3.23.8)))(openai@4.58.0(zod@3.23.8))
langchain: 0.2.18(axios@1.7.4)(openai@4.58.0(zod@3.23.8))
openai: 4.58.0(zod@3.23.8)
langsmith@0.1.51(@langchain/core@0.2.31(langchain@0.2.18(axios@1.7.4)(openai@4.58.0))(openai@4.58.0(zod@3.23.8)))(langchain@0.2.18(axios@1.7.4)(openai@4.58.0))(openai@4.58.0(zod@3.23.8)):
dependencies:
'@types/uuid': 10.0.0
commander: 10.0.1
p-queue: 6.6.2
p-retry: 4.6.2
semver: 7.6.0
uuid: 10.0.0
optionalDependencies:
'@langchain/core': 0.2.31(langchain@0.2.18(axios@1.7.4)(openai@4.58.0))(openai@4.58.0(zod@3.23.8))
langchain: 0.2.18(axios@1.7.4)(openai@4.58.0)
openai: 4.58.0(zod@3.23.8)
optional: true
langsmith@0.1.51(@langchain/core@0.2.31(langchain@0.2.18(axios@1.7.4)(openai@4.58.0))(openai@4.58.0))(langchain@0.2.18(axios@1.7.4)(openai@4.58.0))(openai@4.58.0):
dependencies:
'@types/uuid': 10.0.0
commander: 10.0.1
p-queue: 6.6.2
p-retry: 4.6.2
semver: 7.6.0
uuid: 10.0.0
optionalDependencies:
'@langchain/core': 0.2.31(langchain@0.2.18(axios@1.7.4)(openai@4.58.0))(openai@4.58.0)
langchain: 0.2.18(axios@1.7.4)(openai@4.58.0)
openai: 4.58.0(zod@3.23.8)
openai: 4.58.0(encoding@0.1.13)(zod@3.23.8)
lazy-ass@1.6.0: {}
@ -24867,6 +24972,49 @@ snapshots:
leac@0.6.0: {}
lefthook-darwin-arm64@1.7.15:
optional: true
lefthook-darwin-x64@1.7.15:
optional: true
lefthook-freebsd-arm64@1.7.15:
optional: true
lefthook-freebsd-x64@1.7.15:
optional: true
lefthook-linux-arm64@1.7.15:
optional: true
lefthook-linux-x64@1.7.15:
optional: true
lefthook-openbsd-arm64@1.7.15:
optional: true
lefthook-openbsd-x64@1.7.15:
optional: true
lefthook-windows-arm64@1.7.15:
optional: true
lefthook-windows-x64@1.7.15:
optional: true
lefthook@1.7.15:
optionalDependencies:
lefthook-darwin-arm64: 1.7.15
lefthook-darwin-x64: 1.7.15
lefthook-freebsd-arm64: 1.7.15
lefthook-freebsd-x64: 1.7.15
lefthook-linux-arm64: 1.7.15
lefthook-linux-x64: 1.7.15
lefthook-openbsd-arm64: 1.7.15
lefthook-openbsd-x64: 1.7.15
lefthook-windows-arm64: 1.7.15
lefthook-windows-x64: 1.7.15
leven@3.1.0: {}
levn@0.3.0:
@ -26152,24 +26300,6 @@ snapshots:
- encoding
- supports-color
openai@4.58.0(zod@3.23.8):
dependencies:
'@types/node': 18.16.16
'@types/node-fetch': 2.6.4
'@types/qs': 6.9.15
abort-controller: 3.0.0
agentkeepalive: 4.2.1
form-data-encoder: 1.7.2
formdata-node: 4.4.1
node-fetch: 2.7.0(encoding@0.1.13)
qs: 6.11.0
optionalDependencies:
zod: 3.23.8
transitivePeerDependencies:
- encoding
- supports-color
optional: true
openapi-sampler@1.4.0:
dependencies:
'@types/json-schema': 7.0.15
@ -26374,7 +26504,7 @@ snapshots:
pdf-parse@1.1.1:
dependencies:
debug: 3.2.7(supports-color@5.5.0)
debug: 3.2.7(supports-color@8.1.1)
node-ensure: 0.0.0
transitivePeerDependencies:
- supports-color
@ -27272,7 +27402,7 @@ snapshots:
rhea@1.0.24:
dependencies:
debug: 3.2.7(supports-color@5.5.0)
debug: 3.2.7(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color

View file

@ -5,42 +5,61 @@ import path from 'path';
import { execSync } from 'child_process';
const prettier = path.resolve('node_modules', '.bin', 'prettier');
const biome = path.resolve('node_modules', '.bin', 'biome');
if (!fs.existsSync(prettier)) {
[prettier, biome].forEach((bin) => {
if (!fs.existsSync(bin)) {
throw new Error(
[`Prettier not found at path: ${prettier}`, 'Please run `pnpm i` first'].join('\n'),
[`${path.basename(bin)} not found at path: ${bin}`, 'Please run `pnpm i` first'].join('\n'),
);
}
}
});
const config = path.resolve('.prettierrc.js');
const prettierConfig = path.resolve('.prettierrc.js');
const biomeConfig = path.resolve('biome.jsonc');
const ignore = path.resolve('.prettierignore');
const ROOT_DIRS_TO_SKIP = ['.git', 'node_modules', 'packages'];
const EXTENSIONS_TO_FORMAT = ['.md', '.yml', '.js', '.json', '.ts'];
const ROOT_DIRS_TO_SKIP = ['.git', 'node_modules', 'packages', '.turbo', 'cypress'];
const EXTENSIONS_TO_FORMAT_WITH_PRETTIER = ['.yml'];
const EXTENSIONS_TO_FORMAT_WITH_BIOME = ['.js', '.json', '.ts'];
const isDir = (path) => fs.lstatSync(path).isDirectory();
const isTarget = (path) => EXTENSIONS_TO_FORMAT.some((ext) => path.endsWith(ext));
const isPrettierTarget = (path) =>
EXTENSIONS_TO_FORMAT_WITH_PRETTIER.some((ext) => path.endsWith(ext));
const isBiomeTarget = (path) => EXTENSIONS_TO_FORMAT_WITH_BIOME.some((ext) => path.endsWith(ext));
const walk = (dir, test, found = []) => {
const biomeTargets = [];
const prettierTargets = [];
const walk = (dir) => {
fs.readdirSync(dir).forEach((entry) => {
const entryPath = path.resolve(dir, entry);
if (isDir(entryPath)) walk(entryPath, test, found);
if (test(entryPath)) found.push(entryPath);
if (isDir(entryPath)) walk(entryPath);
if (isPrettierTarget(entryPath)) prettierTargets.push(entryPath);
if (isBiomeTarget(entryPath)) biomeTargets.push(entryPath);
});
return found;
};
const targets = fs
.readdirSync('.')
.reduce((acc, cur) => {
if (ROOT_DIRS_TO_SKIP.includes(cur)) return acc;
if (isDir(cur)) return [...acc, ...walk(cur, isTarget)];
if (isTarget(cur)) return [...acc, cur];
fs.readdirSync('.').forEach((cur) => {
if (ROOT_DIRS_TO_SKIP.includes(cur)) return;
if (isDir(cur)) walk(cur);
if (isPrettierTarget(cur)) prettierTargets.push(cur);
if (isBiomeTarget(cur)) biomeTargets.push(cur);
});
return acc;
}, [])
.join(' ');
execSync(
[
prettier,
'--config',
prettierConfig,
'--ignore-path',
ignore,
'--write',
prettierTargets.join(' '),
].join(' '),
);
execSync([prettier, '--config', config, '--ignore-path', ignore, '--write', targets].join(' '));
execSync(
[biome, 'format', '--write', `--config-path=${biomeConfig}`, biomeTargets.join(' ')].join(' '),
);

View file

@ -23,6 +23,7 @@
"dependsOn": ["^typecheck"]
},
"format": {},
"format:check": {},
"lint:backend": {
"dependsOn": [
"@n8n/api-types#lint",