Compare commits

...

15 Commits

Author SHA1 Message Date
n8n-assistant[bot]
6707c67f50
🚀 Release 2.20.5 (#30010)
Co-authored-by: konstantintieber <46342664+konstantintieber@users.noreply.github.com>
2026-05-07 13:43:17 +00:00
n8n-assistant[bot]
4d9a9f8079
chore: Bump Axios, hono, vm2 and fast-xml-parser (backport to release-candidate/2.20.x) (#30000)
Co-authored-by: aikido-autofix[bot] <119856028+aikido-autofix[bot]@users.noreply.github.com>
Co-authored-by: Matsuuu <huhta.matias@gmail.com>
2026-05-07 15:59:23 +03:00
n8n-assistant[bot]
ce016859cb
fix(core): Simple-git update broke https connection (backport to release-candidate/2.20.x) (#30003)
Co-authored-by: Konstantin Tieber <46342664+konstantintieber@users.noreply.github.com>
2026-05-07 12:50:29 +00:00
n8n-assistant[bot]
8b1103bfa6
fix(editor): Change read-only background color so it's visible (no-changelog) (backport to release-candidate/2.20.x) (#29984)
Co-authored-by: Rob Hough <robhough180@gmail.com>
2026-05-07 10:48:28 +00:00
Matsu
3bcb6378c5
🚀 Release 2.20.4 (#29955)
Co-authored-by: Matsuuu <16068444+Matsuuu@users.noreply.github.com>
2026-05-07 09:13:22 +03:00
n8n-assistant[bot]
c9b1463c58
ci: Exclude all monorepo packages from safechain minimum age (backport to release-candidate/2.20.x) (#29954)
Co-authored-by: Matsu <huhta.matias@gmail.com>
2026-05-07 09:08:42 +03:00
n8n-assistant[bot]
ae115d199f
ci: Apply SafeChain @n8n/* exclusion to all setup-nodejs steps (backport to release-candidate/2.20.x) (#29951)
Co-authored-by: Matsu <huhta.matias@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 08:11:21 +03:00
n8n-assistant[bot]
2f31aca2dc
fix(core): Allow GIT_SSH_COMMAND in simple-git after 3.36.0 upgrade (backport to release-candidate/2.20.x) (#29946)
Co-authored-by: Daria <daria.staferova@n8n.io>
2026-05-07 07:56:44 +03:00
n8n-assistant[bot]
71d4122438
fix(core): Add support for context establishment hooks in webhook mode (backport to release-candidate/2.20.x) (#29900)
Co-authored-by: Andreas Fitzek <andreas.fitzek@n8n.io>
2026-05-06 14:02:40 +00:00
n8n-assistant[bot]
98004c6269
fix(Snowflake Node): Fix issue with Insert and Update operations not working (backport to release-candidate/2.20.x) (#29809) 2026-05-06 13:29:12 +01:00
n8n-assistant[bot]
ad6e890d85
ci: Exclude @n8n packages from SafeChain minimum package age check (no-changelog) (backport to release-candidate/2.20.x) (#29890)
Co-authored-by: Declan Carroll <declan@n8n.io>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-06 12:21:38 +00:00
n8n-assistant[bot]
513f7cd3dc
ci: Fix race condition between npm releases and daytona snapshots (backport to release-candidate/2.20.x) (#29874)
Co-authored-by: Matsu <huhta.matias@gmail.com>
2026-05-06 10:41:08 +00:00
n8n-assistant[bot]
2c19035590
refactor(core): Extract leader election client and improve robustness (no-changelog) (backport to release-candidate/2.20.x) (#29804)
Co-authored-by: Tomi Turtiainen <10324676+tomi@users.noreply.github.com>
2026-05-06 11:33:31 +03:00
n8n-assistant[bot]
9dd7ce9486
fix(core): Add workflow details to builder telemetry (no-changelog) (backport to release-candidate/2.20.x) (#29845)
Co-authored-by: Albert Alises <albert.alises@gmail.com>
2026-05-06 07:40:37 +00:00
n8n-assistant[bot]
642fc2e18e
chore: Bump simple-git to 3.36.0 (backport to release-candidate/2.20.x) (#29835)
Co-authored-by: Matsu <huhta.matias@gmail.com>
2026-05-06 06:53:05 +00:00
54 changed files with 1387 additions and 424 deletions

View File

@ -54,6 +54,10 @@ runs:
echo "${EXPECTED_SHA256} install-safe-chain.sh" | sha256sum -c -
sh install-safe-chain.sh --ci
rm install-safe-chain.sh
# Exclude first-party @n8n/* packages from SafeChain's minimum-package-age
# filter so freshly-published versions stay visible to every subsequent
# step in the job (install, build, and publish).
echo "SAFE_CHAIN_MINIMUM_PACKAGE_AGE_EXCLUSIONS=@n8n/*,n8n,n8n-containers,n8n-core,n8n-editor-ui,n8n-node-dev,n8n-nodes-base,n8n-playwright,n8n-workflow" >> "$GITHUB_ENV"
shell: bash
- name: Install Dependencies

View File

@ -107,7 +107,7 @@ jobs:
build-daytona-snapshot:
name: Build Daytona snapshot
needs: [determine-version-info]
needs: [determine-version-info, publish-to-npm]
if: github.event.pull_request.merged == true
uses: ./.github/workflows/release-build-daytona-snapshot.yml
with:

View File

@ -1,3 +1,23 @@
## [2.20.5](https://github.com/n8n-io/n8n/compare/n8n@2.20.4...n8n@2.20.5) (2026-05-07)
### Bug Fixes
* **core:** Simple-git update broke https connection ([#30003](https://github.com/n8n-io/n8n/issues/30003)) ([ce01685](https://github.com/n8n-io/n8n/commit/ce016859cb055b7d193208412e5e656b0048a1fa))
## [2.20.4](https://github.com/n8n-io/n8n/compare/n8n@2.20.0...n8n@2.20.4) (2026-05-07)
## [2.20.3](https://github.com/n8n-io/n8n/compare/n8n@2.20.0...n8n@2.20.3) (2026-05-07)
### Bug Fixes
* **core:** Add support for context establishment hooks in webhook mode ([#29900](https://github.com/n8n-io/n8n/issues/29900)) ([71d4122](https://github.com/n8n-io/n8n/commit/71d41224385e64098000569bf9ac4838a61c669c))
* **core:** Allow GIT_SSH_COMMAND in simple-git after 3.36.0 upgrade ([#29946](https://github.com/n8n-io/n8n/issues/29946)) ([2f31aca](https://github.com/n8n-io/n8n/commit/2f31aca2dc4b5258492a678a44464146a2a29d01))
* **Snowflake Node:** Fix issue with Insert and Update operations not working ([#29809](https://github.com/n8n-io/n8n/issues/29809)) ([98004c6](https://github.com/n8n-io/n8n/commit/98004c6269456c3bfe600da951856c81b3861034))
# [2.20.0](https://github.com/n8n-io/n8n/compare/n8n@2.19.0...n8n@2.20.0) (2026-05-05)

View File

@ -1,6 +1,6 @@
{
"name": "n8n-monorepo",
"version": "2.20.0",
"version": "2.20.5",
"private": true,
"engines": {
"node": ">=22.16",
@ -73,7 +73,7 @@
"jest-mock-extended": "^3.0.4",
"lefthook": "^1.7.15",
"license-checker": "^25.0.1",
"nock": "^14.0.13",
"nock": "^14.0.14",
"nodemon": "^3.0.1",
"npm-run-all2": "^7.0.2",
"p-limit": "^3.1.0",
@ -103,7 +103,6 @@
"@mistralai/mistralai": "^1.10.0",
"@n8n/typeorm>@sentry/node": "catalog:sentry",
"@types/node": "^20.17.50",
"axios": "1.15.0",
"chokidar": "4.0.3",
"esbuild": "^0.25.0",
"expr-eval@2.0.2": "npm:expr-eval-fork@3.0.0",
@ -140,7 +139,6 @@
"undici@6": "^6.24.0",
"undici@7": "^7.24.0",
"tar": "^7.5.11",
"hono": "4.12.14",
"ajv@6": "6.14.0",
"ajv@7": "8.18.0",
"ajv@8": "8.18.0",
@ -167,7 +165,9 @@
"@xmldom/xmldom": "0.8.13",
"langsmith": "0.5.19",
"yaml@<=2.8.3": "2.8.3",
"fast-xml-parser": "5.7.0"
"hono": "4.12.16",
"axios": "1.16.0",
"fast-xml-parser": "5.7.2"
},
"patchedDependencies": {
"bull@4.16.4": "patches/bull@4.16.4.patch",

View File

@ -1,6 +1,6 @@
{
"name": "@n8n/ai-node-sdk",
"version": "0.11.0",
"version": "0.11.1",
"description": "SDK for building AI nodes in n8n",
"types": "dist/esm/index.d.ts",
"module": "dist/esm/index.js",

View File

@ -1,6 +1,6 @@
{
"name": "@n8n/ai-utilities",
"version": "0.14.0",
"version": "0.14.1",
"description": "Utilities for building AI nodes in n8n",
"types": "dist/esm/index.d.ts",
"module": "dist/esm/index.js",

View File

@ -1,6 +1,6 @@
{
"name": "@n8n/ai-workflow-builder",
"version": "1.20.0",
"version": "1.20.1",
"scripts": {
"clean": "rimraf dist .turbo",
"typecheck": "tsc --noEmit",

View File

@ -1,6 +1,6 @@
{
"name": "@n8n/backend-common",
"version": "1.20.0",
"version": "1.20.1",
"scripts": {
"clean": "rimraf dist .turbo",
"dev": "pnpm watch",

View File

@ -1,6 +1,6 @@
{
"name": "@n8n/backend-test-utils",
"version": "1.20.0",
"version": "1.20.2",
"scripts": {
"clean": "rimraf dist .turbo",
"dev": "pnpm watch",

View File

@ -1,6 +1,6 @@
{
"name": "@n8n/n8n-benchmark",
"version": "2.7.0",
"version": "2.7.1",
"description": "Cli for running benchmark tests for n8n",
"main": "dist/index",
"scripts": {

View File

@ -1,6 +1,6 @@
{
"name": "@n8n/client-oauth2",
"version": "1.4.0",
"version": "1.4.1",
"scripts": {
"clean": "rimraf dist .turbo",
"dev": "pnpm watch",

View File

@ -1,6 +1,6 @@
{
"name": "@n8n/config",
"version": "2.19.0",
"version": "2.19.1",
"scripts": {
"clean": "rimraf dist .turbo",
"dev": "pnpm watch",

View File

@ -13,4 +13,8 @@ export class MultiMainSetupConfig {
/** Interval in seconds between leader eligibility checks in multi-main setup. */
@Env('N8N_MULTI_MAIN_SETUP_CHECK_INTERVAL')
interval: number = 3;
/** Whether to use the new leader election implementation (Lua-script based). */
@Env('N8N_NEW_LEADER_ELECTION_IMPLEMENTATION')
newLeaderElection: boolean = false;
}

View File

@ -78,6 +78,7 @@ export { ChatTriggerConfig } from './configs/chat-trigger.config';
export { InstanceAiConfig } from './configs/instance-ai.config';
export { ExpressionEngineConfig } from './configs/expression-engine.config';
export { PasswordConfig } from './configs/password.config';
export { RedisConfig } from './configs/redis.config';
const protocolSchema = z.enum(['http', 'https']);

View File

@ -371,6 +371,7 @@ describe('GlobalConfig', () => {
enabled: false,
ttl: 10,
interval: 3,
newLeaderElection: false,
},
evaluation: {
parallelExecutionEnabled: false,

View File

@ -1,6 +1,6 @@
{
"name": "@n8n/create-node",
"version": "0.29.0",
"version": "0.29.1",
"description": "Official CLI to create new community nodes for n8n",
"bin": {
"create-node": "bin/create-node.cjs"

View File

@ -1,6 +1,6 @@
{
"name": "@n8n/db",
"version": "1.20.0",
"version": "1.20.2",
"scripts": {
"clean": "rimraf dist .turbo",
"dev": "pnpm watch",

View File

@ -1,6 +1,6 @@
{
"name": "@n8n/node-cli",
"version": "0.30.0",
"version": "0.30.1",
"description": "Official CLI for developing community nodes for n8n",
"bin": {
"n8n-node": "bin/n8n-node.mjs"

View File

@ -1,6 +1,6 @@
{
"name": "@n8n/n8n-nodes-langchain",
"version": "2.20.0",
"version": "2.20.2",
"description": "",
"main": "index.js",
"exports": {

View File

@ -1,6 +1,6 @@
{
"name": "@n8n/scan-community-package",
"version": "0.17.0",
"version": "0.17.1",
"description": "Static code analyser for n8n community packages",
"license": "none",
"bin": "scanner/cli.mjs",

View File

@ -1,6 +1,6 @@
{
"name": "@n8n/task-runner",
"version": "2.20.0",
"version": "2.20.2",
"scripts": {
"clean": "rimraf dist .turbo",
"start": "node dist/start.js",

View File

@ -1,6 +1,6 @@
{
"name": "n8n",
"version": "2.20.0",
"version": "2.20.5",
"description": "n8n Workflow Automation Tool",
"main": "dist/index",
"types": "dist/index.d.ts",

View File

@ -1,13 +1,19 @@
import { mockInstance } from '@n8n/backend-test-utils';
import { DbConnection, DeploymentKeyRepository } from '@n8n/db';
import { Container } from '@n8n/di';
import { BinaryDataConfig } from 'n8n-core';
import { DeprecationService } from '@/deprecation/deprecation.service';
import { MessageEventBus } from '@/eventbus/message-event-bus/message-event-bus';
import { LogStreamingEventRelay } from '@/events/relays/log-streaming.event-relay';
import { LoadNodesAndCredentials } from '@/load-nodes-and-credentials';
import { PubSubRegistry } from '@/scaling/pubsub/pubsub.registry';
import { Subscriber } from '@/scaling/pubsub/subscriber.service';
import { JwtService } from '@/services/jwt.service';
import { RedisClientService } from '@/services/redis-client.service';
import { WebhookServer } from '@/webhooks/webhook-server';
import { BaseCommand } from '../base-command';
import { Webhook } from '../webhook';
jest.mock('@/scaling/scaling.service', () => ({
@ -26,7 +32,14 @@ mockInstance(RedisClientService);
mockInstance(PubSubRegistry);
const mockSubscriber = mockInstance(Subscriber);
const mockWebhookServer = mockInstance(WebhookServer);
mockInstance(LoadNodesAndCredentials);
const mockLoadNodesAndCredentials = mockInstance(LoadNodesAndCredentials);
mockLoadNodesAndCredentials.postProcessLoaders.mockResolvedValue(undefined);
mockInstance(DeprecationService);
mockInstance(JwtService, { initialize: jest.fn().mockResolvedValue(undefined) });
mockInstance(BinaryDataConfig, { initialize: jest.fn().mockResolvedValue(undefined) });
mockInstance(MessageEventBus, { initialize: jest.fn().mockResolvedValue(undefined) });
mockInstance(LogStreamingEventRelay);
describe('Webhook', () => {
beforeEach(() => {
@ -74,4 +87,57 @@ describe('Webhook', () => {
expect(mockWebhookServer.markAsReady).toHaveBeenCalled();
});
});
describe('init', () => {
let baseInitSpy: jest.SpyInstance;
beforeEach(() => {
baseInitSpy = jest.spyOn(BaseCommand.prototype, 'init').mockResolvedValue(undefined);
});
afterEach(() => {
baseInitSpy.mockRestore();
});
it('should call executionContextHookRegistry.init before LoadNodesAndCredentials.postProcessLoaders', async () => {
const webhook = new Webhook();
// @ts-expect-error - Accessing protected property for testing
webhook.globalConfig = { executions: { mode: 'queue' } };
// @ts-expect-error - Accessing protected method for testing
webhook.initCrashJournal = jest.fn().mockResolvedValue(undefined);
webhook.initLicense = jest.fn().mockResolvedValue(undefined);
// @ts-expect-error - Accessing protected method for testing
webhook.initCommunityPackages = jest.fn().mockResolvedValue(undefined);
webhook.initOrchestration = jest.fn().mockResolvedValue(undefined);
webhook.initBinaryDataService = jest.fn().mockResolvedValue(undefined);
// @ts-expect-error - Accessing protected method for testing
webhook.initDataDeduplicationService = jest.fn().mockResolvedValue(undefined);
webhook.initExternalHooks = jest.fn().mockResolvedValue(undefined);
// @ts-expect-error - Accessing protected property for testing
webhook.moduleRegistry = { initModules: jest.fn().mockResolvedValue(undefined) };
// @ts-expect-error - Accessing protected property for testing
webhook.instanceSettings = {
hostId: 'test',
instanceType: 'webhook',
initialize: jest.fn().mockResolvedValue(undefined),
};
// @ts-expect-error - Accessing protected property for testing
webhook.executionContextHookRegistry = {
init: jest.fn().mockResolvedValue(undefined),
};
await webhook.init();
// @ts-expect-error - Accessing protected property for testing
const hookInitMock = webhook.executionContextHookRegistry.init as jest.Mock;
const postProcessMock = mockLoadNodesAndCredentials.postProcessLoaders as jest.Mock;
expect(hookInitMock).toHaveBeenCalled();
expect(postProcessMock).toHaveBeenCalled();
expect(hookInitMock.mock.invocationCallOrder[0]).toBeLessThan(
postProcessMock.mock.invocationCallOrder[0],
);
});
});
});

View File

@ -92,6 +92,9 @@ export class Webhook extends BaseCommand {
Container.get(LogStreamingEventRelay).init();
await this.moduleRegistry.initModules(this.instanceSettings.instanceType);
await this.executionContextHookRegistry.init();
await Container.get(LoadNodesAndCredentials).postProcessLoaders();
}
async run() {

View File

@ -1349,8 +1349,10 @@ function createWorkflowAdapterForTests(overrides?: {
const mockWorkflowService = {
archive: jest.fn().mockResolvedValue(undefined),
activateWorkflow: jest.fn().mockResolvedValue({ activeVersionId: 'version-1' }),
update: jest.fn().mockResolvedValue(savedWorkflow),
};
const mockTelemetry = { track: jest.fn() };
const mockUser = { id: 'user-1', role: { slug: 'global:member' } } as unknown as User;
@ -1403,7 +1405,7 @@ function createWorkflowAdapterForTests(overrides?: {
{} as unknown as ConstructorParameters<typeof InstanceAiAdapterService>[26],
{} as unknown as ConstructorParameters<typeof InstanceAiAdapterService>[27],
{} as unknown as ConstructorParameters<typeof InstanceAiAdapterService>[28],
{ track: jest.fn() } as unknown as ConstructorParameters<typeof InstanceAiAdapterService>[29],
mockTelemetry as unknown as ConstructorParameters<typeof InstanceAiAdapterService>[29],
mockAiBuilderTemporaryWorkflowRepository as unknown as AiBuilderTemporaryWorkflowRepository,
{} as unknown as ConstructorParameters<typeof InstanceAiAdapterService>[31],
);
@ -1420,6 +1422,7 @@ function createWorkflowAdapterForTests(overrides?: {
mockSharedWorkflowRepository,
mockAiBuilderTemporaryWorkflowRepository,
mockWorkflowService,
mockTelemetry,
mockUser,
};
}
@ -1477,6 +1480,18 @@ describe('createWorkflowAdapter', () => {
).rejects.toThrow('User does not have the required permissions in this project');
});
it('tracks workflow id when publishing a builder workflow', async () => {
const { adapter, mockTelemetry } = createWorkflowAdapterForTests();
await adapter.publish('wf-new');
expect(mockTelemetry.track).toHaveBeenCalledWith('Builder published workflow', {
thread_id: 'thread-1',
workflow_id: 'wf-new',
executed_by: 'ai',
});
});
it('marks the workflow as AI-builder temporary when markAsAiTemporary is true', async () => {
const {
adapter,
@ -1928,7 +1943,15 @@ describe('resolveDataTableByIdOrName', () => {
// createExecutionAdapter run() forces save settings
// ---------------------------------------------------------------------------
function createRunAdapterForTests(workflow: Record<string, unknown>) {
function createRunAdapterForTests(
workflow: Record<string, unknown>,
options?: {
activeExecution?: boolean;
execution?: ReturnType<typeof makeExecution>;
postExecutePromise?: Promise<unknown>;
threadId?: string;
},
) {
const mockWorkflowFinderService = {
findWorkflowForUser: jest.fn().mockResolvedValue(workflow),
};
@ -1938,12 +1961,17 @@ function createRunAdapterForTests(workflow: Record<string, unknown>) {
};
const mockActiveExecutions = {
has: jest.fn().mockReturnValue(false),
getPostExecutePromise: jest
.fn()
.mockReturnValue(options?.postExecutePromise ?? Promise.resolve()),
has: jest.fn().mockReturnValue(options?.activeExecution ?? false),
stopExecution: jest.fn(),
};
const mockExecutionRepository = {
findSingleExecution: jest.fn().mockResolvedValue(undefined),
findSingleExecution: jest.fn().mockResolvedValue(options?.execution),
};
const mockTelemetry = { track: jest.fn() };
const mockUser = { id: 'user-1', role: { slug: 'global:member' } } as unknown as User;
@ -1987,14 +2015,14 @@ function createRunAdapterForTests(workflow: Record<string, unknown>) {
{} as unknown as ConstructorParameters<typeof InstanceAiAdapterService>[26],
{} as unknown as ConstructorParameters<typeof InstanceAiAdapterService>[27],
{} as unknown as ConstructorParameters<typeof InstanceAiAdapterService>[28],
{} as unknown as ConstructorParameters<typeof InstanceAiAdapterService>[29],
mockTelemetry as unknown as ConstructorParameters<typeof InstanceAiAdapterService>[29],
{} as unknown as ConstructorParameters<typeof InstanceAiAdapterService>[30],
{} as unknown as ConstructorParameters<typeof InstanceAiAdapterService>[31],
);
const adapter = service.createContext(mockUser).executionService;
const adapter = service.createContext(mockUser, { threadId: options?.threadId }).executionService;
return { adapter, mockWorkflowRunner };
return { adapter, mockActiveExecutions, mockTelemetry, mockWorkflowRunner };
}
describe('createExecutionAdapter run()', () => {
@ -2041,4 +2069,78 @@ describe('createExecutionAdapter run()', () => {
saveDataErrorExecution: 'all',
});
});
it('tracks workflow id and success status when a builder execution finishes', async () => {
const { adapter, mockTelemetry } = createRunAdapterForTests(
{
id: 'wf-1',
nodes: [],
},
{
execution: makeExecution({ status: 'success' }),
threadId: 'thread-1',
},
);
await adapter.run('wf-1');
expect(mockTelemetry.track).toHaveBeenCalledWith('Builder executed workflow', {
thread_id: 'thread-1',
workflow_id: 'wf-1',
executed_by: 'ai',
pinned_node_count: 0,
exec_type: 'manual',
status: 'success',
});
});
it('tracks error status when a builder execution fails', async () => {
const { adapter, mockTelemetry } = createRunAdapterForTests(
{
id: 'wf-1',
nodes: [],
},
{
execution: makeExecution({ status: 'error', error: { message: 'boom' } }),
threadId: 'thread-1',
},
);
await adapter.run('wf-1');
expect(mockTelemetry.track).toHaveBeenCalledWith(
'Builder executed workflow',
expect.objectContaining({
workflow_id: 'wf-1',
status: 'error',
}),
);
});
it('tracks timeout cancellation as an error status', async () => {
const { adapter, mockActiveExecutions, mockTelemetry } = createRunAdapterForTests(
{
id: 'wf-1',
nodes: [],
},
{
activeExecution: true,
postExecutePromise: new Promise(() => {}),
threadId: 'thread-1',
},
);
await expect(adapter.run('wf-1', undefined, { timeout: 1 })).resolves.toMatchObject({
status: 'error',
});
expect(mockActiveExecutions.stopExecution).toHaveBeenCalled();
expect(mockTelemetry.track).toHaveBeenCalledWith(
'Builder executed workflow',
expect.objectContaining({
workflow_id: 'wf-1',
status: 'error',
}),
);
});
});

View File

@ -381,6 +381,7 @@ export class InstanceAiAdapterService {
if (threadId) {
telemetry.track('Builder published workflow', {
thread_id: threadId,
workflow_id: workflowId,
executed_by: 'ai',
});
}
@ -807,6 +808,19 @@ export class InstanceAiAdapterService {
runData.pinData = basePinData;
}
const trackBuilderExecutedWorkflow = (status: ExecutionResult['status']) => {
if (!threadId) return;
telemetry.track('Builder executed workflow', {
thread_id: threadId,
workflow_id: workflowId,
executed_by: 'ai',
pinned_node_count: Object.keys(runData.pinData ?? {}).length,
exec_type: runData.executionMode,
status,
});
};
const executionId = await workflowRunner.run(runData);
// Wait for completion with timeout protection
@ -838,30 +852,25 @@ export class InstanceAiAdapterService {
} catch {
// Execution may have completed between timeout and cancel
}
return {
const result = {
executionId,
status: 'error',
error: `Execution timed out after ${timeoutMs}ms and was cancelled`,
} satisfies ExecutionResult;
trackBuilderExecutedWorkflow(result.status);
return result;
}
throw error;
}
}
if (threadId) {
telemetry.track('Builder executed workflow', {
thread_id: threadId,
executed_by: 'ai',
pinned_node_count: Object.keys(runData.pinData ?? {}).length,
exec_type: runData.executionMode,
});
}
return await extractExecutionResult(
const result = await extractExecutionResult(
executionRepository,
executionId,
allowSendingParameterValues,
);
trackBuilderExecutedWorkflow(result.status);
return result;
},
async getStatus(executionId: string) {

View File

@ -329,6 +329,7 @@ describe('SourceControlGitService', () => {
maxConcurrentProcesses: 6,
trimmed: false,
config: [`credential.helper=${expectedCredentialScript}`, 'credential.useHttpPath=true'],
unsafe: { allowUnsafeCredentialHelper: true },
}),
);
});
@ -366,9 +367,15 @@ describe('SourceControlGitService', () => {
mockSourceControlPreferencesService.getPreferences.mockReturnValue({
connectionType: 'ssh',
} as never);
(simpleGit as jest.Mock).mockClear();
await sourceControlGitService.setGitCommand();
expect(simpleGit).toHaveBeenCalledWith(
expect.objectContaining({
unsafe: { allowUnsafeSshCommand: true },
}),
);
expect(mockGitInstance.env).toHaveBeenCalledWith(
'GIT_SSH_COMMAND',
'ssh -o UserKnownHostsFile=".ssh/known_hosts" -o StrictHostKeyChecking=accept-new -i "private-key"',

View File

@ -140,6 +140,7 @@ export class SourceControlGitService {
// ensures that the credentials are only used for the configured repositoryUrl of the environment
'credential.useHttpPath=true',
],
unsafe: { allowUnsafeCredentialHelper: true },
};
// Add proxy configuration if proxy environment variables are set
@ -171,7 +172,12 @@ export class SourceControlGitService {
// - Subsequent connections: verifies against saved key
const sshCommand = `ssh -o UserKnownHostsFile="${escapedKnownHostsPath}" -o StrictHostKeyChecking=accept-new -i "${escapedPrivateKeyPath}"`;
this.git = simpleGit(this.gitOptions)
// Allow GIT_SSH_COMMAND so we can point SSH at n8n's own private key and known_hosts.
// This is safe because the command is constructed internally above, not from user input.
this.git = simpleGit({
...this.gitOptions,
unsafe: { allowUnsafeSshCommand: true },
})
.env('GIT_SSH_COMMAND', sshCommand)
.env('GIT_TERMINAL_PROMPT', '0');
}

View File

@ -1,110 +1,382 @@
import type { LeaderElectionClient } from '@/scaling/leader-election-client';
import type { Publisher } from '@/scaling/pubsub/publisher.service';
import type { RedisClientService } from '@/services/redis-client.service';
import { mockLogger } from '@n8n/backend-test-utils';
import type { GlobalConfig } from '@n8n/config';
import { MultiMainMetadata } from '@n8n/decorators';
import { mock } from 'jest-mock-extended';
import type { ErrorReporter, InstanceSettings } from 'n8n-core';
import type { Publisher } from '@/scaling/pubsub/publisher.service';
import type { RedisClientService } from '@/services/redis-client.service';
import { createResultOk, createResultError } from 'n8n-workflow';
import { MultiMainSetup } from '../multi-main-setup.ee';
function createInstanceSettings(hostId: string) {
let isLeader = false;
const settings = mock<InstanceSettings>({ hostId });
Object.defineProperty(settings, 'isLeader', {
get: () => isLeader,
configurable: true,
});
Object.defineProperty(settings, 'markAsLeader', {
value: jest.fn(() => {
isLeader = true;
}),
configurable: true,
writable: true,
});
Object.defineProperty(settings, 'markAsFollower', {
value: jest.fn(() => {
isLeader = false;
}),
configurable: true,
writable: true,
});
return settings;
}
describe('MultiMainSetup', () => {
const hostId = 'main-n8n-main-0';
const logger = mockLogger();
const publisher = mock<Publisher>();
const redisClientService = mock<RedisClientService>();
const errorReporter = mock<ErrorReporter>();
const metadata = new MultiMainMetadata();
const globalConfig = mock<GlobalConfig>({
redis: { prefix: 'n8n' },
multiMainSetup: { ttl: 10, interval: 3, enabled: true },
describe('with legacy implementation (flag off)', () => {
const logger = mockLogger();
const publisher = mock<Publisher>();
const redisClientService = mock<RedisClientService>();
const errorReporter = mock<ErrorReporter>();
const globalConfig = mock<GlobalConfig>({
redis: { prefix: 'n8n' },
multiMainSetup: { ttl: 10, interval: 3, enabled: true, newLeaderElection: false },
});
let instanceSettings: InstanceSettings;
let multiMainSetup: MultiMainSetup;
beforeEach(() => {
jest.clearAllMocks();
instanceSettings = createInstanceSettings(hostId);
redisClientService.toValidPrefix.mockReturnValue('n8n');
multiMainSetup = new MultiMainSetup(
logger,
instanceSettings,
globalConfig,
metadata,
errorReporter,
publisher,
redisClientService,
);
});
describe('init', () => {
it('should become leader if setIfNotExists succeeds', async () => {
publisher.setIfNotExists.mockResolvedValue(true);
const emit = jest.spyOn(multiMainSetup, 'emit');
await multiMainSetup.init();
expect(publisher.setIfNotExists).toHaveBeenCalled();
expect(instanceSettings.markAsLeader).toHaveBeenCalled();
expect(emit).toHaveBeenCalledWith('leader-takeover');
});
it('should remain follower if setIfNotExists returns false', async () => {
publisher.setIfNotExists.mockResolvedValue(false);
const emit = jest.spyOn(multiMainSetup, 'emit');
await multiMainSetup.init();
expect(instanceSettings.markAsLeader).not.toHaveBeenCalled();
expect(emit).not.toHaveBeenCalledWith('leader-takeover');
});
});
describe('checkLeader', () => {
it('should renew TTL when this instance is the leader', async () => {
publisher.setIfNotExists.mockResolvedValue(true);
await multiMainSetup.init();
jest.clearAllMocks();
publisher.get.mockResolvedValue(hostId);
await multiMainSetup['strategy'].checkLeader();
expect(publisher.setExpiration).toHaveBeenCalled();
});
it('should emit leader-takeover on mismatch recovery', async () => {
publisher.setIfNotExists.mockResolvedValue(false);
await multiMainSetup.init();
jest.clearAllMocks();
publisher.get.mockResolvedValue(hostId);
const emit = jest.spyOn(multiMainSetup, 'emit');
await multiMainSetup['strategy'].checkLeader();
expect(instanceSettings.markAsLeader).toHaveBeenCalled();
expect(emit).toHaveBeenCalledWith('leader-takeover');
});
it('should step down when another instance is leader', async () => {
publisher.setIfNotExists.mockResolvedValue(true);
await multiMainSetup.init();
jest.clearAllMocks();
publisher.get.mockResolvedValue('main-n8n-main-1');
const emit = jest.spyOn(multiMainSetup, 'emit');
await multiMainSetup['strategy'].checkLeader();
expect(instanceSettings.markAsFollower).toHaveBeenCalled();
expect(emit).toHaveBeenCalledWith('leader-stepdown');
});
it('should attempt to become leader when leadership is vacant', async () => {
publisher.setIfNotExists.mockResolvedValue(true);
await multiMainSetup.init();
jest.clearAllMocks();
publisher.get.mockResolvedValue(null);
publisher.setIfNotExists.mockResolvedValue(true);
const emit = jest.spyOn(multiMainSetup, 'emit');
await multiMainSetup['strategy'].checkLeader();
expect(emit).toHaveBeenCalledWith('leader-stepdown');
expect(publisher.setIfNotExists).toHaveBeenCalled();
expect(emit).toHaveBeenCalledWith('leader-takeover');
});
});
});
redisClientService.toValidPrefix.mockReturnValue('n8n');
describe('with new implementation (flag on)', () => {
const logger = mockLogger();
const publisher = mock<Publisher>();
const redisClientService = mock<RedisClientService>();
const errorReporter = mock<ErrorReporter>();
const client = mock<LeaderElectionClient>();
let instanceSettings: InstanceSettings;
let multiMainSetup: MultiMainSetup;
beforeEach(() => {
instanceSettings = mock<InstanceSettings>({ hostId, isLeader: false });
multiMainSetup = new MultiMainSetup(
logger,
instanceSettings,
publisher,
redisClientService,
globalConfig,
metadata,
errorReporter,
);
jest.clearAllMocks();
});
describe('checkLeader', () => {
beforeEach(async () => {
await multiMainSetup.init();
const globalConfig = mock<GlobalConfig>({
redis: { prefix: 'n8n' },
multiMainSetup: { ttl: 10, interval: 3, enabled: true, newLeaderElection: true },
});
it('should emit `leader-takeover` when Redis has own `hostId` but instance thinks it is follower', async () => {
publisher.get.mockResolvedValue(hostId);
const emit = jest.spyOn(multiMainSetup, 'emit');
let instanceSettings: InstanceSettings;
let multiMainSetup: MultiMainSetup;
// @ts-expect-error - private method
await multiMainSetup.checkLeader();
beforeEach(() => {
jest.clearAllMocks();
instanceSettings = createInstanceSettings(hostId);
expect(instanceSettings.markAsLeader).toHaveBeenCalled();
expect(emit).toHaveBeenCalledWith('leader-takeover');
jest.mock('@/scaling/leader-election-client', () => ({
LeaderElectionClient: jest.fn(),
}));
const { Container } = jest.requireActual('@n8n/di');
Container.set(
jest.requireMock('@/scaling/leader-election-client').LeaderElectionClient,
client,
);
multiMainSetup = new MultiMainSetup(
logger,
instanceSettings,
globalConfig,
metadata,
errorReporter,
publisher,
redisClientService,
);
});
it('should not emit `leader-takeover` when already leader', async () => {
publisher.get.mockResolvedValue(hostId);
Object.defineProperty(instanceSettings, 'isLeader', { get: () => true });
const emit = jest.spyOn(multiMainSetup, 'emit');
describe('init', () => {
it('should become leader if setLeaderIfNotExists succeeds', async () => {
client.setLeaderIfNotExists.mockResolvedValue(createResultOk(true));
const emit = jest.spyOn(multiMainSetup, 'emit');
// @ts-expect-error - private method
await multiMainSetup.checkLeader();
await multiMainSetup.init();
expect(instanceSettings.markAsLeader).not.toHaveBeenCalled();
expect(emit).not.toHaveBeenCalledWith('leader-takeover');
expect(publisher.setExpiration).toHaveBeenCalled();
expect(client.setLeaderIfNotExists).toHaveBeenCalled();
expect(instanceSettings.markAsLeader).toHaveBeenCalled();
expect(emit).toHaveBeenCalledWith('leader-takeover');
});
it('should remain follower if setLeaderIfNotExists returns false', async () => {
client.setLeaderIfNotExists.mockResolvedValue(createResultOk(false));
const emit = jest.spyOn(multiMainSetup, 'emit');
await multiMainSetup.init();
expect(instanceSettings.markAsLeader).not.toHaveBeenCalled();
expect(emit).not.toHaveBeenCalledWith('leader-takeover');
});
it('should remain follower if setLeaderIfNotExists fails', async () => {
client.setLeaderIfNotExists.mockResolvedValue(
createResultError(new Error('Command timed out')),
);
const emit = jest.spyOn(multiMainSetup, 'emit');
await multiMainSetup.init();
expect(instanceSettings.markAsLeader).not.toHaveBeenCalled();
expect(emit).not.toHaveBeenCalledWith('leader-takeover');
});
});
it('should emit `leader-stepdown` when another instance is leader', async () => {
publisher.get.mockResolvedValue('main-n8n-main-1');
Object.defineProperty(instanceSettings, 'isLeader', { get: () => true });
const emit = jest.spyOn(multiMainSetup, 'emit');
describe('checkLeader (leader path)', () => {
beforeEach(async () => {
client.setLeaderIfNotExists.mockResolvedValue(createResultOk(true));
await multiMainSetup.init();
jest.clearAllMocks();
});
// @ts-expect-error - private method
await multiMainSetup.checkLeader();
it('should stay leader when TTL renewal succeeds', async () => {
client.tryRenewLeaderTtl.mockResolvedValue(createResultOk({ id: 'success' }));
const emit = jest.spyOn(multiMainSetup, 'emit');
expect(instanceSettings.markAsFollower).toHaveBeenCalled();
expect(emit).toHaveBeenCalledWith('leader-stepdown');
await multiMainSetup['strategy'].checkLeader();
expect(client.tryRenewLeaderTtl).toHaveBeenCalled();
expect(instanceSettings.markAsFollower).not.toHaveBeenCalled();
expect(emit).not.toHaveBeenCalledWith('leader-stepdown');
});
it('should step down when another host is leader', async () => {
client.tryRenewLeaderTtl.mockResolvedValue(
createResultOk({ id: 'other-host-is-leader', currentLeaderId: 'main-n8n-main-1' }),
);
const emit = jest.spyOn(multiMainSetup, 'emit');
await multiMainSetup['strategy'].checkLeader();
expect(instanceSettings.markAsFollower).toHaveBeenCalled();
expect(emit).toHaveBeenCalledWith('leader-stepdown');
});
it('should try to re-acquire leader key when key is missing', async () => {
client.tryRenewLeaderTtl.mockResolvedValue(createResultOk({ id: 'key-missing' }));
client.setLeaderIfNotExists.mockResolvedValue(createResultOk(true));
await multiMainSetup['strategy'].checkLeader();
expect(client.setLeaderIfNotExists).toHaveBeenCalled();
expect(instanceSettings.markAsFollower).not.toHaveBeenCalled();
});
it('should step down when key is missing and re-acquire fails', async () => {
client.tryRenewLeaderTtl.mockResolvedValue(createResultOk({ id: 'key-missing' }));
client.setLeaderIfNotExists.mockResolvedValue(createResultOk(false));
const emit = jest.spyOn(multiMainSetup, 'emit');
await multiMainSetup['strategy'].checkLeader();
expect(instanceSettings.markAsFollower).toHaveBeenCalled();
expect(emit).toHaveBeenCalledWith('leader-stepdown');
});
it('should step down when key is missing and Redis command fails', async () => {
client.tryRenewLeaderTtl.mockResolvedValue(createResultOk({ id: 'key-missing' }));
client.setLeaderIfNotExists.mockResolvedValue(
createResultError(new Error('Command timed out')),
);
const emit = jest.spyOn(multiMainSetup, 'emit');
await multiMainSetup['strategy'].checkLeader();
expect(instanceSettings.markAsFollower).toHaveBeenCalled();
expect(emit).toHaveBeenCalledWith('leader-stepdown');
});
it('should stay leader when TTL renewal Redis command fails', async () => {
client.tryRenewLeaderTtl.mockResolvedValue(
createResultError(new Error('Command timed out')),
);
const emit = jest.spyOn(multiMainSetup, 'emit');
await multiMainSetup['strategy'].checkLeader();
expect(instanceSettings.markAsFollower).not.toHaveBeenCalled();
expect(emit).not.toHaveBeenCalledWith('leader-stepdown');
});
});
it('should not emit `leader-stepdown` when already a follower', async () => {
publisher.get.mockResolvedValue('main-n8n-main-1');
const emit = jest.spyOn(multiMainSetup, 'emit');
describe('checkLeader (follower path)', () => {
beforeEach(async () => {
client.setLeaderIfNotExists.mockResolvedValue(createResultOk(false));
await multiMainSetup.init();
jest.clearAllMocks();
});
// @ts-expect-error - private method
await multiMainSetup.checkLeader();
it('should become leader when Redis shows own hostId as leader (mismatch recovery)', async () => {
client.getLeader.mockResolvedValue(createResultOk(hostId));
const emit = jest.spyOn(multiMainSetup, 'emit');
expect(emit).not.toHaveBeenCalledWith('leader-stepdown');
});
await multiMainSetup['strategy'].checkLeader();
it('should attempt to become leader when leadership is vacant', async () => {
publisher.get.mockResolvedValue(null);
publisher.setIfNotExists.mockResolvedValue(true);
const emit = jest.spyOn(multiMainSetup, 'emit');
expect(errorReporter.info).toHaveBeenCalled();
expect(instanceSettings.markAsLeader).toHaveBeenCalled();
expect(emit).toHaveBeenCalledWith('leader-takeover');
});
// @ts-expect-error - private method
await multiMainSetup.checkLeader();
it('should stay follower when another instance is leader', async () => {
client.getLeader.mockResolvedValue(createResultOk('main-n8n-main-1'));
const emit = jest.spyOn(multiMainSetup, 'emit');
expect(instanceSettings.markAsFollower).toHaveBeenCalled();
expect(emit).toHaveBeenCalledWith('leader-stepdown');
expect(emit).toHaveBeenCalledWith('leader-takeover');
expect(instanceSettings.markAsLeader).toHaveBeenCalled();
await multiMainSetup['strategy'].checkLeader();
expect(instanceSettings.markAsLeader).not.toHaveBeenCalled();
expect(emit).not.toHaveBeenCalledWith('leader-takeover');
expect(emit).not.toHaveBeenCalledWith('leader-stepdown');
});
it('should attempt to become leader when leadership is vacant', async () => {
client.getLeader.mockResolvedValue(createResultOk(null));
client.setLeaderIfNotExists.mockResolvedValue(createResultOk(true));
const emit = jest.spyOn(multiMainSetup, 'emit');
await multiMainSetup['strategy'].checkLeader();
expect(client.setLeaderIfNotExists).toHaveBeenCalled();
expect(instanceSettings.markAsLeader).toHaveBeenCalled();
expect(emit).toHaveBeenCalledWith('leader-takeover');
});
it('should stay follower when leadership is vacant but setLeaderIfNotExists fails', async () => {
client.getLeader.mockResolvedValue(createResultOk(null));
client.setLeaderIfNotExists.mockResolvedValue(createResultOk(false));
const emit = jest.spyOn(multiMainSetup, 'emit');
await multiMainSetup['strategy'].checkLeader();
expect(instanceSettings.markAsLeader).not.toHaveBeenCalled();
expect(emit).not.toHaveBeenCalledWith('leader-takeover');
});
it('should stay follower when Redis is unreachable', async () => {
client.getLeader.mockResolvedValue(createResultError(new Error('Command timed out')));
const emit = jest.spyOn(multiMainSetup, 'emit');
await multiMainSetup['strategy'].checkLeader();
expect(instanceSettings.markAsLeader).not.toHaveBeenCalled();
expect(instanceSettings.markAsFollower).not.toHaveBeenCalled();
expect(emit).not.toHaveBeenCalledWith('leader-takeover');
expect(emit).not.toHaveBeenCalledWith('leader-stepdown');
});
it('should stay follower when leadership is vacant and Redis command fails', async () => {
client.getLeader.mockResolvedValue(createResultOk(null));
client.setLeaderIfNotExists.mockResolvedValue(
createResultError(new Error('Command timed out')),
);
const emit = jest.spyOn(multiMainSetup, 'emit');
await multiMainSetup['strategy'].checkLeader();
expect(instanceSettings.markAsLeader).not.toHaveBeenCalled();
expect(emit).not.toHaveBeenCalledWith('leader-takeover');
});
});
});
});

View File

@ -0,0 +1,151 @@
import { RedisClientService } from '@/services/redis-client.service';
import { GlobalConfig } from '@n8n/config';
import { Service } from '@n8n/di';
import type { Cluster, Redis } from 'ioredis';
import { InstanceSettings } from 'n8n-core';
import { ensureError, type Result, createResultOk, createResultError } from 'n8n-workflow';
const COMMAND_TIMEOUT_MS = 5_000;
/**
* Atomically extends the leader key TTL only if the current value matches the
* provided hostId
*
* KEYS[1] - leader key
* ARGV[1] - hostId
* ARGV[2] - TTL in seconds
*
* Returns:
* -1 key does not exist
* "actual-value" key exists but value is not the expected value
* 1 value matched and TTL was extended
* 0 value matched, but TTL was not extended
*/
const INCREASE_TTL_IF_LEADER = `
-- Renew only if we still hold the lock
local currentValue = redis.call("GET", KEYS[1])
if not currentValue then
return -1
end
if currentValue ~= ARGV[1] then
return currentValue
end
return redis.call("EXPIRE", KEYS[1], tonumber(ARGV[2]))
`;
export type TtlRenewalResultKeyMissing = { id: 'key-missing' };
export type TtlRenewalResultOtherHostIsLeader = {
id: 'other-host-is-leader';
currentLeaderId: string;
};
export type TtlRenewalResultSuccess = { id: 'success' };
export type TtlRenewalResult =
| TtlRenewalResultKeyMissing
| TtlRenewalResultOtherHostIsLeader
| TtlRenewalResultSuccess;
/**
* Redis-backed client for leader election in multi-main setups. Uses a TTL-based key to
* track which instance is the current leader.
*/
@Service()
export class LeaderElectionClient {
private readonly redisClient: Redis | Cluster;
private readonly leaderKey: string;
private readonly leaderKeyTtlInS: number;
private get hostId() {
return this.instanceSettings.hostId;
}
constructor(
private readonly instanceSettings: InstanceSettings,
globalConfig: GlobalConfig,
redisClientService: RedisClientService,
) {
const prefix = redisClientService.toValidPrefix(globalConfig.redis.prefix);
this.leaderKey = prefix + ':main_instance_leader';
this.leaderKeyTtlInS = globalConfig.multiMainSetup.ttl;
this.redisClient = redisClientService.createClient({
type: 'leader(n8n)',
extraOptions: { commandTimeout: COMMAND_TIMEOUT_MS },
});
}
/** Return the current leader's hostId, or `null` if the key is absent. */
async getLeader(): Promise<Result<string | null, Error>> {
try {
return createResultOk(await this.redisClient.get(this.leaderKey));
} catch (e) {
return createResultError(ensureError(e));
}
}
/** Claim leadership with a TTL. Returns `true` if the key was set (i.e. no leader yet). */
async setLeaderIfNotExists(): Promise<Result<boolean, Error>> {
try {
const result = await this.redisClient.set(
this.leaderKey,
this.hostId,
'EX',
this.leaderKeyTtlInS,
'NX',
);
return createResultOk(result === 'OK');
} catch (e) {
return createResultError(ensureError(e));
}
}
/** Atomically extend the leader key TTL only if this host still holds it. */
async tryRenewLeaderTtl(): Promise<Result<TtlRenewalResult, Error>> {
try {
const result = await this.redisClient.eval(
INCREASE_TTL_IF_LEADER,
1,
this.leaderKey,
this.hostId,
this.leaderKeyTtlInS,
);
if (result === -1 || result === 0) {
return createResultOk({ id: 'key-missing' });
}
if (result === 1) {
return createResultOk({ id: 'success' });
}
if (typeof result === 'string') {
return createResultOk({ id: 'other-host-is-leader', currentLeaderId: result });
}
return createResultError(
new Error(`Unexpected result from Redis script: ${JSON.stringify(result)}`),
);
} catch (e) {
return createResultError(ensureError(e));
}
}
/** Delete the leader key so another instance can claim leadership. */
async clearLeader(): Promise<Result<void, Error>> {
try {
await this.redisClient.del(this.leaderKey);
return createResultOk(undefined);
} catch (e) {
return createResultError(ensureError(e));
}
}
/** Disconnect the underlying Redis client. */
destroy() {
this.redisClient.disconnect();
}
}

View File

@ -0,0 +1,120 @@
import type { Logger } from '@n8n/backend-common';
import type { GlobalConfig } from '@n8n/config';
import type { ErrorReporter, InstanceSettings } from 'n8n-core';
import type { Publisher } from '@/scaling/pubsub/publisher.service';
import type { RedisClientService } from '@/services/redis-client.service';
import type { MultiMainStrategy } from './multi-main-setup.types';
type EmitFn = (event: 'leader-takeover' | 'leader-stepdown') => void;
export class MultiMainSetupLegacy implements MultiMainStrategy {
private leaderKey: string;
private readonly leaderKeyTtl: number;
constructor(
private readonly logger: Logger,
private readonly instanceSettings: InstanceSettings,
private readonly publisher: Publisher,
private readonly redisClientService: RedisClientService,
private readonly globalConfig: GlobalConfig,
private readonly errorReporter: ErrorReporter,
private readonly emit: EmitFn,
) {
this.leaderKeyTtl = this.globalConfig.multiMainSetup.ttl;
}
async init() {
const prefix = this.globalConfig.redis.prefix;
const validPrefix = this.redisClientService.toValidPrefix(prefix);
this.leaderKey = validPrefix + ':main_instance_leader';
await this.tryBecomeLeader();
}
async shutdown() {
const { isLeader } = this.instanceSettings;
if (isLeader) await this.publisher.clear(this.leaderKey);
}
async checkLeader() {
const leaderId = await this.publisher.get(this.leaderKey);
const { hostId } = this.instanceSettings;
if (leaderId === hostId) {
if (!this.instanceSettings.isLeader) {
this.errorReporter.info(
`[Instance ID ${hostId}] Remote/Local leadership mismatch, marking self as leader`,
{
shouldBeLogged: true,
shouldReport: true,
},
);
this.instanceSettings.markAsLeader();
this.emit('leader-takeover');
}
this.logger.debug(`[Instance ID ${hostId}] Leader is this instance`);
await this.publisher.setExpiration(this.leaderKey, this.leaderKeyTtl);
return;
}
if (leaderId && leaderId !== hostId) {
this.logger.debug(`[Instance ID ${hostId}] Leader is other instance "${leaderId}"`);
if (this.instanceSettings.isLeader) {
this.instanceSettings.markAsFollower();
this.emit('leader-stepdown');
this.logger.warn('[Multi-main setup] Leader failed to renew leader key');
}
return;
}
if (!leaderId) {
this.logger.debug(
`[Instance ID ${hostId}] Leadership vacant, attempting to become leader...`,
);
this.instanceSettings.markAsFollower();
this.emit('leader-stepdown');
await this.tryBecomeLeader();
}
}
private async tryBecomeLeader() {
const { hostId } = this.instanceSettings;
const keySetSuccessfully = await this.publisher.setIfNotExists(
this.leaderKey,
hostId,
this.leaderKeyTtl,
);
if (keySetSuccessfully) {
this.logger.info(`[Instance ID ${hostId}] Leader is now this instance`);
this.instanceSettings.markAsLeader();
this.emit('leader-takeover');
} else {
this.instanceSettings.markAsFollower();
}
}
async fetchLeaderKey() {
return await this.publisher.get(this.leaderKey);
}
}

View File

@ -0,0 +1,191 @@
import type { LeaderElectionClient } from '@/scaling/leader-election-client';
import type { Logger } from '@n8n/backend-common';
import type { ErrorReporter, InstanceSettings } from 'n8n-core';
import assert from 'node:assert';
import type { MultiMainStrategy } from './multi-main-setup.types';
type EmitFn = (event: 'leader-takeover' | 'leader-stepdown') => void;
export class MultiMainSetupV2 implements MultiMainStrategy {
private leaderCheckInProgress = false;
private get hostId() {
return this.instanceSettings.hostId;
}
constructor(
private readonly logger: Logger,
private readonly instanceSettings: InstanceSettings,
private readonly errorReporter: ErrorReporter,
private readonly client: LeaderElectionClient,
private readonly emit: EmitFn,
) {}
async init() {
const result = await this.client.setLeaderIfNotExists();
if (!result.ok) {
this.logRedisCommandFailure('Failed to set leader key in Redis during init', result.error);
this.instanceSettings.markAsFollower();
} else if (result.result) {
this.takeOverAsLeader();
} else {
this.instanceSettings.markAsFollower();
}
}
async shutdown() {
const { isLeader } = this.instanceSettings;
if (isLeader) {
// TODO: We should guard here that we only remove the key the key in Redis matches
// our host ID.
const result = await this.client.clearLeader();
if (!result.ok) {
this.logger.warn('Failed to clear leader key from Redis', { error: result.error });
}
}
this.client.destroy();
}
async checkLeader() {
if (this.leaderCheckInProgress) {
this.logger.warn('Previous leader check is still in progress, skipping this check');
return;
}
this.leaderCheckInProgress = true;
try {
if (this.instanceSettings.isLeader) {
await this.checkAreWeStillLeader();
} else {
await this.checkCanBecomeLeader();
}
} finally {
this.leaderCheckInProgress = false;
}
}
/** Renew our leadership lease. If we've lost the lease, step down to follower. */
private async checkAreWeStillLeader() {
assert(this.instanceSettings.isLeader);
const renewTtlResult = await this.client.tryRenewLeaderTtl();
if (!renewTtlResult.ok) {
this.logRedisCommandFailure('Failed to renew leader TTL', renewTtlResult.error);
// There's a decision to be made here: Do we step down or not? Redis might
// be unavailable for all clients or only for us. We could also track the TTL
// locally, but this would make the implementation more complex and error-prone.
// For now we accept that this might cause some inconsistencies in a network
// partition scenario, but eventually the system will recover once Redis is available again.
return;
}
const renewalResult = renewTtlResult.result;
if (renewalResult.id === 'success') {
this.logger.debug(`[Instance ID ${this.hostId}] Leader is this instance`);
return;
}
this.logger.warn('[Multi-main setup] Leader failed to renew leader key');
if (renewalResult.id === 'other-host-is-leader') {
this.logger.debug(
`[Instance ID ${this.hostId}] Leader is other instance "${renewalResult.currentLeaderId}"`,
);
this.stepDownToFollower();
return;
}
// The only remaining case is 'key-missing', which means we lost leadership
// (e.g. due to Redis unavailability or a network partition). In this case
// we try to become leader and step down if that fails.
assert(renewalResult.id === 'key-missing');
const result = await this.client.setLeaderIfNotExists();
if (!result.ok) {
this.logRedisCommandFailure('Failed to set leader key in Redis', result.error);
this.stepDownToFollower();
return;
}
const wasSet = result.result;
if (!wasSet) {
this.stepDownToFollower();
}
}
private async checkCanBecomeLeader() {
assert(!this.instanceSettings.isLeader);
const getResult = await this.client.getLeader();
if (!getResult.ok) {
this.logRedisCommandFailure('Failed to get leader key from Redis', getResult.error);
return;
}
const leaderId = getResult.result;
if (leaderId && leaderId === this.hostId) {
this.errorReporter.info(
`[Instance ID ${this.hostId}] Remote/Local leadership mismatch, marking self as leader`,
{
shouldBeLogged: true,
shouldReport: true,
},
);
this.takeOverAsLeader();
return;
}
if (leaderId) {
this.logger.debug(`[Instance ID ${this.hostId}] Leader is other instance "${leaderId}"`);
return;
}
this.logger.debug(
`[Instance ID ${this.hostId}] Leadership vacant, attempting to become leader...`,
);
const result = await this.client.setLeaderIfNotExists();
if (!result.ok) {
this.logger.warn('Failed to try leader key set in Redis', { error: result.error });
return;
}
const wasSet = result.result;
if (wasSet) {
this.takeOverAsLeader();
}
}
private takeOverAsLeader() {
assert(!this.instanceSettings.isLeader);
this.logger.info(`[Instance ID ${this.hostId}] Leader is now this instance`);
this.instanceSettings.markAsLeader();
this.emit('leader-takeover');
}
private stepDownToFollower() {
assert(this.instanceSettings.isLeader);
this.logger.info(`[Instance ID ${this.hostId}] This is now a follower instance`);
this.instanceSettings.markAsFollower();
this.emit('leader-stepdown');
}
async fetchLeaderKey() {
const result = await this.client.getLeader();
return result.ok ? result.result : null;
}
private logRedisCommandFailure(message: string, error: Error) {
this.logger.warn(`${message}: ${error.message}`, { error });
}
}

View File

@ -1,3 +1,4 @@
import { TypedEmitter } from '@/typed-emitter';
import { Logger } from '@n8n/backend-common';
import { GlobalConfig } from '@n8n/config';
import { Time } from '@n8n/constants';
@ -5,9 +6,13 @@ import { MultiMainMetadata } from '@n8n/decorators';
import { Container, Service } from '@n8n/di';
import { ErrorReporter, InstanceSettings } from 'n8n-core';
import type * as LeaderElectionClientModule from '@/scaling/leader-election-client';
import { Publisher } from '@/scaling/pubsub/publisher.service';
import { RedisClientService } from '@/services/redis-client.service';
import { TypedEmitter } from '@/typed-emitter';
import { MultiMainSetupLegacy } from './multi-main-setup-legacy';
import type { MultiMainStrategy } from './multi-main-setup.types';
import { MultiMainSetupV2 } from './multi-main-setup-v2';
type MultiMainEvents = {
/**
@ -28,34 +33,53 @@ type MultiMainEvents = {
/** Designates leader and followers when running multiple main processes. */
@Service()
export class MultiMainSetup extends TypedEmitter<MultiMainEvents> {
constructor(
private readonly logger: Logger,
private readonly instanceSettings: InstanceSettings,
private readonly publisher: Publisher,
private readonly redisClientService: RedisClientService,
private readonly globalConfig: GlobalConfig,
private readonly metadata: MultiMainMetadata,
private readonly errorReporter: ErrorReporter,
) {
super();
this.logger = this.logger.scoped(['scaling', 'multi-main-setup']);
}
private leaderKey: string;
private readonly leaderKeyTtl = this.globalConfig.multiMainSetup.ttl;
private readonly strategy: MultiMainStrategy;
private leaderCheckInterval: NodeJS.Timeout | undefined;
async init() {
const prefix = this.globalConfig.redis.prefix;
const validPrefix = this.redisClientService.toValidPrefix(prefix);
this.leaderKey = validPrefix + ':main_instance_leader';
constructor(
private readonly logger: Logger,
private readonly instanceSettings: InstanceSettings,
private readonly globalConfig: GlobalConfig,
private readonly metadata: MultiMainMetadata,
private readonly errorReporter: ErrorReporter,
private readonly publisher: Publisher,
private readonly redisClientService: RedisClientService,
) {
super();
this.logger = this.logger.scoped(['scaling', 'multi-main-setup']);
await this.tryBecomeLeader(); // prevent initial wait
const emitFn = (event: 'leader-takeover' | 'leader-stepdown') => this.emit(event);
if (this.globalConfig.multiMainSetup.newLeaderElection) {
const { LeaderElectionClient } =
require('@/scaling/leader-election-client') as typeof LeaderElectionClientModule;
const client = Container.get(LeaderElectionClient);
this.strategy = new MultiMainSetupV2(
this.logger,
this.instanceSettings,
this.errorReporter,
client,
emitFn,
);
} else {
this.strategy = new MultiMainSetupLegacy(
this.logger,
this.instanceSettings,
this.publisher,
this.redisClientService,
this.globalConfig,
this.errorReporter,
emitFn,
);
}
}
async init() {
await this.strategy.init();
this.leaderCheckInterval = setInterval(async () => {
await this.checkLeader();
await this.strategy.checkLeader();
}, this.globalConfig.multiMainSetup.interval * Time.seconds.toMilliseconds);
}
@ -63,90 +87,11 @@ export class MultiMainSetup extends TypedEmitter<MultiMainEvents> {
async shutdown() {
clearInterval(this.leaderCheckInterval);
const { isLeader } = this.instanceSettings;
if (isLeader) await this.publisher.clear(this.leaderKey);
await this.strategy.shutdown();
}
private async checkLeader() {
const leaderId = await this.publisher.get(this.leaderKey);
const { hostId } = this.instanceSettings;
if (leaderId === hostId) {
if (!this.instanceSettings.isLeader) {
// This indicates that the remote state indicated that this host is the leader, but this
// host believed it was a follower. See CAT-2200 for more context.
this.errorReporter.info(
`[Instance ID ${hostId}] Remote/Local leadership mismatch, marking self as leader`,
{
shouldBeLogged: true,
shouldReport: true,
},
);
this.instanceSettings.markAsLeader();
this.emit('leader-takeover');
}
this.logger.debug(`[Instance ID ${hostId}] Leader is this instance`);
await this.publisher.setExpiration(this.leaderKey, this.leaderKeyTtl);
return;
}
if (leaderId && leaderId !== hostId) {
this.logger.debug(`[Instance ID ${hostId}] Leader is other instance "${leaderId}"`);
if (this.instanceSettings.isLeader) {
this.instanceSettings.markAsFollower();
this.emit('leader-stepdown');
this.logger.warn('[Multi-main setup] Leader failed to renew leader key');
}
return;
}
if (!leaderId) {
this.logger.debug(
`[Instance ID ${hostId}] Leadership vacant, attempting to become leader...`,
);
this.instanceSettings.markAsFollower();
this.emit('leader-stepdown');
await this.tryBecomeLeader();
}
}
private async tryBecomeLeader() {
const { hostId } = this.instanceSettings;
// this can only succeed if leadership is currently vacant
const keySetSuccessfully = await this.publisher.setIfNotExists(
this.leaderKey,
hostId,
this.leaderKeyTtl,
);
if (keySetSuccessfully) {
this.logger.info(`[Instance ID ${hostId}] Leader is now this instance`);
this.instanceSettings.markAsLeader();
this.emit('leader-takeover');
} else {
this.instanceSettings.markAsFollower();
}
}
async fetchLeaderKey() {
return await this.publisher.get(this.leaderKey);
async fetchLeaderKey(): Promise<string | null> {
return await this.strategy.fetchLeaderKey();
}
registerEventHandlers() {
@ -155,7 +100,6 @@ export class MultiMainSetup extends TypedEmitter<MultiMainEvents> {
for (const { eventHandlerClass, methodName, eventName } of handlers) {
const instance = Container.get(eventHandlerClass);
this.on(eventName, async () => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return await instance[methodName].call(instance);
});
}

View File

@ -0,0 +1,6 @@
export interface MultiMainStrategy {
init(): Promise<void>;
shutdown(): Promise<void>;
checkLeader(): Promise<void>;
fetchLeaderKey(): Promise<string | null>;
}

View File

@ -118,9 +118,7 @@ export class Publisher {
// #endregion
// #region Utils for multi-main setup
// @TODO: The following methods are not pubsub-specific. Consider a dedicated client for multi-main setup.
// #region Key-value utils (used by MCP session store and legacy leader election)
async setIfNotExists(key: string, value: string, ttl: number) {
const result = await this.client.set(key, value, 'EX', ttl, 'NX');

View File

@ -7,7 +7,12 @@ export type RedisClientType = N8nRedisClientType | BullRedisClientType;
* - `publisher(n8n)` to send messages into scaling mode pubsub channels
* - `cache(n8n)` for caching operations (variables, resource ownership, etc.)
*/
type N8nRedisClientType = 'subscriber(n8n)' | 'publisher(n8n)' | 'cache(n8n)' | 'registry(n8n)';
type N8nRedisClientType =
| 'subscriber(n8n)'
| 'publisher(n8n)'
| 'cache(n8n)'
| 'registry(n8n)'
| 'leader(n8n)';
/**
* Redis client used internally by Bull. Suffixed with `(bull)` at `ScalingService.setupQueue`.

View File

@ -3,7 +3,6 @@ import type { WorkflowEntity } from '@n8n/db';
import { generateNanoId, WorkflowRepository } from '@n8n/db';
import { Container } from '@n8n/di';
import { InstanceSettings } from 'n8n-core';
import { ActiveWorkflowManager } from '@/active-workflow-manager';
import { MultiMainSetup } from '@/scaling/multi-main-setup.ee';

View File

@ -1,6 +1,6 @@
{
"name": "n8n-core",
"version": "2.20.0",
"version": "2.20.2",
"description": "Core functionality of n8n",
"main": "dist/index",
"types": "dist/index.d.ts",

View File

@ -501,7 +501,7 @@ describe('Request Helper Functions', () => {
hostname: 'example.de',
href: requestObject.uri,
};
axiosOptions.beforeRedirect!(redirectOptions, mock());
axiosOptions.beforeRedirect!(redirectOptions, mock(), mock());
expect(redirectOptions.agent).toEqual(redirectOptions.agents.https);
expect((redirectOptions.agent as HttpsAgent).options).toMatchObject({
servername: 'example.de',
@ -2240,7 +2240,7 @@ describe('Request Helper Functions', () => {
};
expect(axiosOptions.beforeRedirect).toBeDefined();
expect(() => axiosOptions.beforeRedirect!(redirectOptions, mock())).toThrow(
expect(() => axiosOptions.beforeRedirect!(redirectOptions, mock(), mock())).toThrow(
'Domain not allowed',
);
});
@ -2259,7 +2259,9 @@ describe('Request Helper Functions', () => {
};
expect(axiosOptions.beforeRedirect).toBeDefined();
expect(() => axiosOptions.beforeRedirect!(redirectOptions, mock())).not.toThrow();
expect(() =>
axiosOptions.beforeRedirect!(redirectOptions, mock(), mock()),
).not.toThrow();
},
);
});
@ -2278,7 +2280,7 @@ describe('Request Helper Functions', () => {
};
expect(axiosConfig.beforeRedirect).toBeDefined();
expect(() => axiosConfig.beforeRedirect!(redirectOptions, mock())).toThrow(
expect(() => axiosConfig.beforeRedirect!(redirectOptions, mock(), mock())).toThrow(
'Domain not allowed',
);
});
@ -2298,7 +2300,9 @@ describe('Request Helper Functions', () => {
};
expect(axiosConfig.beforeRedirect).toBeDefined();
expect(() => axiosConfig.beforeRedirect!(redirectOptions, mock())).not.toThrow();
expect(() =>
axiosConfig.beforeRedirect!(redirectOptions, mock(), mock()),
).not.toThrow();
},
);
});

View File

@ -424,7 +424,11 @@ export async function proxyRequestToAxios(
): Promise<any> {
let axiosConfig: AxiosRequestConfig = {
maxBodyLength: Infinity,
maxContentLength: Infinity,
// -1 is the Axios sentinel for "no limit". Infinity also means no limit but
// Axios 1.15.1+ treats any value > -1 as a finite cap, wrapping stream responses
// in Readable.from() even when the limit is Infinity. That breaks the downstream
// `instanceof IncomingMessage` checks in parseIncomingMessage / prepareBinaryData.
maxContentLength: -1,
};
let configObject: IRequestOptions;
if (typeof uriOrObject === 'string') {

View File

@ -1,6 +1,6 @@
{
"name": "@n8n/chat",
"version": "1.20.0",
"version": "1.20.1",
"scripts": {
"dev": "pnpm run --dir=../storybook dev --initial-path=/docs/chat-chat--docs",
"build": "pnpm build:vite && pnpm build:bundle",

View File

@ -1,7 +1,7 @@
{
"type": "module",
"name": "@n8n/design-system",
"version": "2.20.0",
"version": "2.20.1",
"main": "src/index.ts",
"import": "src/index.ts",
"scripts": {

View File

@ -70,7 +70,7 @@
// Canvas
--canvas--color--background: var(--color--neutral-125);
--canvas--dot--color: var(--color--neutral-500);
--canvas--read-only-line--color: var(--color--neutral-100);
--canvas--read-only-line--color: var(--color--neutral-200);
--canvas--color--selected: var(--color--neutral-150);
--canvas--color--selected-transparent: hsla(220, 47%, 30%, 0.1);
--canvas--label--color: var(--color--neutral-600);

View File

@ -1,7 +1,7 @@
{
"name": "@n8n/rest-api-client",
"type": "module",
"version": "2.20.0",
"version": "2.20.1",
"files": [
"dist"
],

View File

@ -1,6 +1,6 @@
{
"name": "n8n-editor-ui",
"version": "2.20.0",
"version": "2.20.1",
"description": "Workflow Editor UI for n8n",
"main": "index.js",
"type": "module",

View File

@ -4,7 +4,7 @@ import nock from 'nock';
describe('Test Binary Data Download', () => {
const baseUrl = 'https://dummy.domain';
beforeAll(async () => {
beforeAll(() => {
nock(baseUrl)
.persist()
.get('/path/to/image.png')

View File

@ -73,6 +73,44 @@ export async function destroy(conn: snowflake.Connection) {
});
}
export function escapeSnowflakeIdentifier(identifier: string): string {
if (identifier.startsWith('"') && identifier.endsWith('"') && identifier.length > 2) {
// Already quoted — preserve case (Snowflake quoted identifiers are case-sensitive)
const bare = identifier.slice(1, -1).replace(/""/g, '"');
return `"${bare.replace(/"/g, '""')}"`;
}
// Snowflake stores unquoted identifiers as UPPERCASE by default; uppercase for compatibility
return `"${identifier.toUpperCase().replace(/"/g, '""')}"`;
}
export function escapeSnowflakeObjectIdentifier(identifier: string): string {
const parts: string[] = [];
let current = '';
let inQuotes = false;
for (let i = 0; i < identifier.length; i++) {
const char = identifier[i];
if (char === '"') {
if (inQuotes && identifier[i + 1] === '"') {
// Escaped double-quote inside a quoted identifier
current += '""';
i++;
} else {
inQuotes = !inQuotes;
current += char;
}
} else if (char === '.' && !inQuotes) {
parts.push(current);
current = '';
} else {
current += char;
}
}
parts.push(current);
return parts.map(escapeSnowflakeIdentifier).join('.');
}
export async function execute(
conn: snowflake.Connection,
sqlText: string,

View File

@ -13,6 +13,8 @@ import { getResolvables } from '@utils/utilities';
import {
connect,
destroy,
escapeSnowflakeIdentifier,
escapeSnowflakeObjectIdentifier,
execute,
getConnectionOptions,
type SnowflakeCredential,
@ -215,9 +217,11 @@ export class Snowflake implements INodeType {
const table = this.getNodeParameter('table', 0) as string;
const columnString = this.getNodeParameter('columns', 0) as string;
const columns = columnString.split(',').map((column) => column.trim());
const query = `INSERT INTO IDENTIFIER(?)(${columns.map(() => 'IDENTIFIER(?)').join(',')}) VALUES (${columns.map(() => '?').join(',')})`;
const quotedTable = escapeSnowflakeObjectIdentifier(table);
const quotedColumns = columns.map(escapeSnowflakeIdentifier);
const query = `INSERT INTO ${quotedTable} (${quotedColumns.join(',')}) VALUES (${columns.map(() => '?').join(',')})`;
const data = this.helpers.copyInputItems(items, columns);
const binds = data.map((element) => [table, ...columns, ...Object.values(element)]);
const binds = data.map((element) => [...Object.values(element)]);
await execute(connection, query, binds as unknown as snowflake.InsertBinds);
data.forEach((d, i) => {
const executionData = this.helpers.constructExecutionMetaData(
@ -242,15 +246,18 @@ export class Snowflake implements INodeType {
columns.unshift(updateKey);
}
const query = `UPDATE IDENTIFIER(?) SET ${columns.map(() => 'IDENTIFIER(?) = ?').join(',')} WHERE IDENTIFIER(?) = ?;`;
const quotedTable = escapeSnowflakeObjectIdentifier(table);
const quotedColumns = columns.map(escapeSnowflakeIdentifier);
const quotedUpdateKey = escapeSnowflakeIdentifier(updateKey);
const query = `UPDATE ${quotedTable} SET ${quotedColumns.map((col) => `${col} = ?`).join(',')} WHERE ${quotedUpdateKey} = ?;`;
const data = this.helpers.copyInputItems(items, columns);
const binds = data.map((element) => {
const values = Object.values(element);
const rowBinds: unknown[] = [table];
columns.forEach((col, idx) => {
rowBinds.push(col, values[idx]);
const rowBinds: unknown[] = [];
columns.forEach((_col, idx) => {
rowBinds.push(values[idx]);
});
rowBinds.push(updateKey, element[updateKey]);
rowBinds.push(element[updateKey]);
return rowBinds;
});
for (let i = 0; i < binds.length; i++) {

View File

@ -1,9 +1,33 @@
import crypto from 'crypto';
import { getConnectionOptions } from '../GenericFunctions';
import { escapeSnowflakeObjectIdentifier, getConnectionOptions } from '../GenericFunctions';
jest.mock('crypto');
describe('escapeSnowflakeObjectIdentifier', () => {
it('quotes a single-part identifier', () => {
expect(escapeSnowflakeObjectIdentifier('orders')).toBe('"ORDERS"');
});
it('quotes each segment of a two-part identifier', () => {
expect(escapeSnowflakeObjectIdentifier('schema.orders')).toBe('"SCHEMA"."ORDERS"');
});
it('quotes each segment of a three-part identifier', () => {
expect(escapeSnowflakeObjectIdentifier('db.schema.orders')).toBe('"DB"."SCHEMA"."ORDERS"');
});
it('preserves case for pre-quoted identifiers', () => {
expect(escapeSnowflakeObjectIdentifier('"myTable"')).toBe('"myTable"');
});
it('does not split on dots inside quoted segments', () => {
expect(escapeSnowflakeObjectIdentifier('"my.schema"."my.table"')).toBe(
'"my.schema"."my.table"',
);
});
});
describe('getConnectionOptions', () => {
const commonOptions = {
account: 'test-account',

View File

@ -39,8 +39,8 @@ describe('Test Snowflake, insert - parameter binding', () => {
expect(mockExecute).toHaveBeenCalledTimes(1);
expect(mockExecute).toHaveBeenCalledWith(
expect.objectContaining({
sqlText: 'INSERT INTO IDENTIFIER(?)(IDENTIFIER(?),IDENTIFIER(?)) VALUES (?,?)',
binds: [['orders', 'name', 'status', 'Alice', 'active']],
sqlText: 'INSERT INTO "ORDERS" ("NAME","STATUS") VALUES (?,?)',
binds: [['Alice', 'active']],
}),
);
},

View File

@ -39,12 +39,11 @@ describe('Test Snowflake, update - parameter binding', () => {
// UPDATE executes one query per row; one input row → one call
expect(mockExecute).toHaveBeenCalledTimes(1);
// Columns list is ["id", "status"] (updateKey "id" prepended since not in "status")
// Binds: [table, col1, val1, col2, val2, updateKey, updateKeyValue]
// Binds: [id_value, status_value, updateKey_value]
expect(mockExecute).toHaveBeenCalledWith(
expect.objectContaining({
sqlText:
'UPDATE IDENTIFIER(?) SET IDENTIFIER(?) = ?,IDENTIFIER(?) = ? WHERE IDENTIFIER(?) = ?;',
binds: ['orders', 'id', 1, 'status', 'shipped', 'id', 1],
sqlText: 'UPDATE "ORDERS" SET "ID" = ?,"STATUS" = ? WHERE "ID" = ?;',
binds: [1, 'shipped', 1],
}),
);
},

View File

@ -1,6 +1,6 @@
{
"name": "n8n-nodes-base",
"version": "2.20.0",
"version": "2.20.2",
"description": "Base nodes of n8n",
"main": "index.js",
"scripts": {

View File

@ -202,8 +202,8 @@ catalogs:
specifier: ^1.98.0
version: 1.98.0
simple-git:
specifier: 3.32.3
version: 3.32.3
specifier: 3.36.0
version: 3.36.0
stream-json:
specifier: 1.9.1
version: 1.9.1
@ -232,8 +232,8 @@ catalogs:
specifier: ^3.1.0
version: 3.1.0
vm2:
specifier: ^3.10.5
version: 3.10.5
specifier: 3.11.2
version: 3.11.2
xml2js:
specifier: 0.6.2
version: 0.6.2
@ -361,7 +361,6 @@ overrides:
'@mistralai/mistralai': ^1.10.0
'@n8n/typeorm>@sentry/node': ^10.36.0
'@types/node': ^20.17.50
axios: 1.15.0
chokidar: 4.0.3
esbuild: ^0.25.0
expr-eval@2.0.2: npm:expr-eval-fork@3.0.0
@ -398,7 +397,6 @@ overrides:
undici@6: ^6.24.0
undici@7: ^7.24.0
tar: ^7.5.11
hono: 4.12.14
ajv@6: 6.14.0
ajv@7: 8.18.0
ajv@8: 8.18.0
@ -425,7 +423,9 @@ overrides:
'@xmldom/xmldom': 0.8.13
langsmith: 0.5.19
yaml@<=2.8.3: 2.8.3
fast-xml-parser: 5.7.0
hono: 4.12.16
axios: 1.16.0
fast-xml-parser: 5.7.2
patchedDependencies:
'@lezer/highlight':
@ -530,8 +530,8 @@ importers:
specifier: ^25.0.1
version: 25.0.1
nock:
specifier: ^14.0.13
version: 14.0.13
specifier: ^14.0.14
version: 14.0.14
nodemon:
specifier: ^3.0.1
version: 3.0.1
@ -644,7 +644,7 @@ importers:
version: 1.0.27(@langchain/core@1.1.41(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.213.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.6.0(@opentelemetry/api@1.9.0))(openai@6.34.0(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.213.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.6.0(@opentelemetry/api@1.9.0))(cheerio@1.0.0)(openai@6.34.0(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10))
'@langchain/community':
specifier: 'catalog:'
version: 1.1.27(9d1780938e2a3bdbdbc6aaf41ad92004)
version: 1.1.27(fc62cbc93d74cace03ba310d8e53131b)
'@langchain/core':
specifier: 'catalog:'
version: 1.1.41(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.213.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.6.0(@opentelemetry/api@1.9.0))(openai@6.34.0(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10))
@ -692,8 +692,8 @@ importers:
specifier: 'catalog:'
version: 3.0.1
axios:
specifier: 1.15.0
version: 1.15.0
specifier: 1.16.0
version: 1.16.0
jest-mock-extended:
specifier: ^3.0.4
version: 3.0.4(jest@29.7.0(@types/node@20.19.21)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.17))(@types/node@20.19.21)(typescript@6.0.2)))(typescript@6.0.2)
@ -927,8 +927,8 @@ importers:
specifier: 4.0.7
version: 4.0.7
axios:
specifier: 1.15.0
version: 1.15.0
specifier: 1.16.0
version: 1.16.0
dotenv:
specifier: 17.2.3
version: 17.2.3
@ -981,8 +981,8 @@ importers:
packages/@n8n/client-oauth2:
dependencies:
axios:
specifier: 1.15.0
version: 1.15.0
specifier: 1.16.0
version: 1.16.0
devDependencies:
'@n8n/typescript-config':
specifier: workspace:*
@ -1961,7 +1961,7 @@ importers:
version: 1.0.1(@langchain/core@1.1.41(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.213.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.6.0(@opentelemetry/api@1.9.0))(openai@6.34.0(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(encoding@0.1.13)
'@langchain/community':
specifier: 'catalog:'
version: 1.1.27(7d9a0518aab37da79d9f494cd5b9494b)
version: 1.1.27(f2f54e7010350c3b50a1b81272c39ebc)
'@langchain/core':
specifier: 'catalog:'
version: 1.1.41(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.213.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.6.0(@opentelemetry/api@1.9.0))(openai@6.34.0(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10))
@ -2150,7 +2150,7 @@ importers:
version: 3.0.3
vm2:
specifier: 'catalog:'
version: 3.10.5
version: 3.11.2
weaviate-client:
specifier: 3.9.0
version: 3.9.0(encoding@0.1.13)
@ -2226,8 +2226,8 @@ importers:
specifier: workspace:*
version: link:../eslint-plugin-community-nodes
axios:
specifier: 1.15.0
version: 1.15.0
specifier: 1.16.0
version: 1.16.0
eslint:
specifier: 'catalog:'
version: 9.29.0(jiti@2.6.1)
@ -2601,8 +2601,8 @@ importers:
specifier: 1.11.0
version: 1.11.0
axios:
specifier: 1.15.0
version: 1.15.0
specifier: 1.16.0
version: 1.16.0
bcryptjs:
specifier: 2.4.3
version: 2.4.3
@ -2779,7 +2779,7 @@ importers:
version: 0.8.5
simple-git:
specifier: 'catalog:'
version: 3.32.3
version: 3.36.0
source-map-support:
specifier: 0.5.21
version: 0.5.21
@ -2962,8 +2962,8 @@ importers:
specifier: catalog:sentry
version: 10.36.0
axios:
specifier: 1.15.0
version: 1.15.0
specifier: 1.16.0
version: 1.16.0
callsites:
specifier: 'catalog:'
version: 3.1.0
@ -3435,8 +3435,8 @@ importers:
specifier: workspace:*
version: link:../../../@n8n/utils
axios:
specifier: 1.15.0
version: 1.15.0
specifier: 1.16.0
version: 1.16.0
flatted:
specifier: 3.4.2
version: 3.4.2
@ -3762,8 +3762,8 @@ importers:
specifier: 1.1.4
version: 1.1.4
axios:
specifier: 1.15.0
version: 1.15.0
specifier: 1.16.0
version: 1.16.0
bowser:
specifier: 2.11.0
version: 2.11.0
@ -4254,7 +4254,7 @@ importers:
version: 2.1.0
simple-git:
specifier: 'catalog:'
version: 3.32.3
version: 3.36.0
snowflake-sdk:
specifier: 2.1.0
version: 2.1.0(asn1.js@5.4.1)(encoding@0.1.13)
@ -4272,7 +4272,7 @@ importers:
version: 10.0.0
vm2:
specifier: 'catalog:'
version: 3.10.5
version: 3.11.2
xlsx:
specifier: https://cdn.sheetjs.com/xlsx-0.20.2/xlsx-0.20.2.tgz
version: https://cdn.sheetjs.com/xlsx-0.20.2/xlsx-0.20.2.tgz
@ -6914,7 +6914,7 @@ packages:
resolution: {integrity: sha512-TsQLe4i2gvoTtrHje625ngThGBySOgSK3Xo2XRYOdqGN1teR8+I7vchQC46uLJi8OF62YTYA3AhSpumtkhsaKQ==}
engines: {node: '>=18.14.1'}
peerDependencies:
hono: 4.12.14
hono: 4.12.16
'@huggingface/inference@4.0.5':
resolution: {integrity: sha512-/Qc45BGrN+FBA3JfdeoHfafxfNShH/dxvOsXbBdcxyxIRIYOyefeiXSlShZGVCaiqYpm+10na28D0YtvjKPTlw==}
@ -7467,7 +7467,7 @@ packages:
duck-duck-scrape: ^2.2.5
epub2: ^3.0.1
faiss-node: '*'
fast-xml-parser: 5.7.0
fast-xml-parser: 5.7.2
firebase-admin: ^13.6.1
google-auth-library: '*'
googleapis: '*'
@ -8546,12 +8546,6 @@ packages:
peerDependencies:
'@opentelemetry/api': '>=1.0.0 <1.10.0'
'@opentelemetry/context-async-hooks@2.5.0':
resolution: {integrity: sha512-uOXpVX0ZjO7heSVjhheW2XEPrhQAWr2BScDPoZ9UDycl5iuHG+Usyc3AIfG6kZeC1GyLpMInpQ6X5+9n69yOFw==}
engines: {node: ^18.19.0 || >=20.6.0}
peerDependencies:
'@opentelemetry/api': '>=1.0.0 <1.10.0'
'@opentelemetry/context-async-hooks@2.6.0':
resolution: {integrity: sha512-L8UyDwqpTcbkIK5cgwDRDYDoEhQoj8wp8BwsO19w3LB1Z41yEQm2VJyNfAi9DrLP/YTqXqWpKHyZfR9/tFYo1Q==}
engines: {node: ^18.19.0 || >=20.6.0}
@ -10148,6 +10142,12 @@ packages:
pinia:
optional: true
'@simple-git/args-pathspec@1.0.3':
resolution: {integrity: sha512-ngJMaHlsWDTfjyq9F3VIQ8b7NXbBLq5j9i5bJ6XLYtD6qlDXT7fdKY2KscWWUF8t18xx052Y/PUO1K1TRc9yKA==}
'@simple-git/argv-parser@1.1.1':
resolution: {integrity: sha512-Q9lBcfQ+VQCpQqGJFHe5yooOS5hGdLFFbJ5R+R5aDsnkPCahtn1hSkMcORX65J2Z5lxSkD0lQorMsncuBQxYUw==}
'@sinclair/typebox@0.25.21':
resolution: {integrity: sha512-gFukHN4t8K4+wVC+ECqeqwzBDeFeTzBXroBTqE6vcWrQGbEUpHO7LYdG0f4xnvYq4VOEwITSlHlp0JBAIFMS/g==}
@ -12638,10 +12638,10 @@ packages:
axios-retry@4.5.0:
resolution: {integrity: sha512-aR99oXhpEDGo0UuAlYcn2iGRds30k366Zfa05XWScR9QaQD4JYiP3/1Qt1u7YlefUOK+cn0CcwoL1oefavQUlQ==}
peerDependencies:
axios: 1.15.0
axios: 1.16.0
axios@1.15.0:
resolution: {integrity: sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==}
axios@1.16.0:
resolution: {integrity: sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w==}
b4a@1.6.7:
resolution: {integrity: sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==}
@ -14831,10 +14831,6 @@ packages:
resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==}
engines: {node: '>=10'}
execa@9.6.0:
resolution: {integrity: sha512-jpWzZ1ZhwUmeWRhS7Qv3mhpOhLfwI+uAX4e5fOcXqwMR7EcJ0pj2kV1CVzHVMX/LphnKWD3LObjZCoJ71lKpHw==}
engines: {node: ^18.19.0 || >=20.5.0}
execa@9.6.1:
resolution: {integrity: sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==}
engines: {node: ^18.19.0 || >=20.5.0}
@ -14989,8 +14985,8 @@ packages:
fast-xml-builder@1.1.5:
resolution: {integrity: sha512-4TJn/8FKLeslLAH3dnohXqE3QSoxkhvaMzepOIZytwJXZO69Bfz0HBdDHzOTOon6G59Zrk6VQ2bEiv1t61rfkA==}
fast-xml-parser@5.7.0:
resolution: {integrity: sha512-MTcrUoRQ1GSQ9iG3QJzBGquYYYeA7piZaJoIWbPFGbRn6Jj6z7xgoAyi4DrZX4y2ZIQQBF59gc/zmvvejjgoFQ==}
fast-xml-parser@5.7.2:
resolution: {integrity: sha512-P7oW7tLbYnhOLQk/Gv7cZgzgMPP/XN03K02/Jy6Y/NHzyIAIpxuZIM/YqAkfiXFPxA2CTm7NtCijK9EDu09u2w==}
hasBin: true
fastest-levenshtein@1.0.16:
@ -15615,7 +15611,7 @@ packages:
'@standard-community/standard-json': ^0.3.5
'@standard-community/standard-openapi': ^0.2.9
'@types/json-schema': ^7.0.15
hono: 4.12.14
hono: 4.12.16
openapi-types: ^12.1.3
peerDependenciesMeta:
'@hono/standard-validator':
@ -15623,8 +15619,8 @@ packages:
hono:
optional: true
hono@4.12.14:
resolution: {integrity: sha512-am5zfg3yu6sqn5yjKBNqhnTX7Cv+m00ox+7jbaKkrLMRJ4rAdldd1xPd/JzbBWspqaQv6RSTrgFN95EsfhC+7w==}
hono@4.12.16:
resolution: {integrity: sha512-jN0ZewiNAWSe5khM3EyCmBb250+b40wWbwNILNfEvq84VREWwOIkuUsFONk/3i3nqkz7Oe1PcpM2mwQEK2L9Kg==}
engines: {node: '>=16.9.0'}
hookable@5.5.3:
@ -18092,8 +18088,8 @@ packages:
no-case@3.0.4:
resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==}
nock@14.0.13:
resolution: {integrity: sha512-SCPsQmGVNY8h1rfS3aU0MzOGYY+wKIFukHEsoSIwPRCYocZkya7MFIlWIEYPWQZj+Gaksg6EyUaY255ZDqpQuA==}
nock@14.0.14:
resolution: {integrity: sha512-PKk7tex0O3RRXUZC5XDKJ9yM3rYRPS13myduT85VIIYDBnib42Fpxoe6KxRSzqB4iL2NDxkcJ2yiskZ18hGLEQ==}
engines: {node: '>=18.20.0 <20 || >=20.12.1'}
node-abi@3.75.0:
@ -18794,10 +18790,6 @@ packages:
resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
engines: {node: '>=16 || 14 >=14.18'}
path-scurry@2.0.1:
resolution: {integrity: sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==}
engines: {node: 20 || >=22}
path-scurry@2.0.2:
resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==}
engines: {node: 18 || 20 || >=22}
@ -19393,7 +19385,6 @@ packages:
resolution: {integrity: sha512-gv6vLGcmAOg96/fgo3d9tvA4dJNZL3fMyBqVRrGxQ+Q/o4k9QzbJ3NQF9cOO/71wRodoXhaPgphvMFU68qVAJQ==}
deprecated: |-
You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other.
(For a CapTP with native promises, see @endo/eventual-send and @endo/captp)
qrcode.vue@3.3.4:
@ -19762,7 +19753,7 @@ packages:
resolution: {integrity: sha512-pOLi+Gdll3JekwuFjXO3fTq+L9lzMQGcSq7M5gIjExcl3Gu1hd4XXuf5o3+LuSBsaULQH7DiNbsqPd1chVpQGQ==}
engines: {node: '>=10.7.0'}
peerDependencies:
axios: 1.15.0
axios: 1.16.0
retry-request@7.0.2:
resolution: {integrity: sha512-dUOvLMJ0/JJYEn8NrpOaGNE7X3vpI5XlZS/u0ANjqtcZVKnIxP7IgCFwrKTxENw29emmwug53awKtaMm4i9g5w==}
@ -20267,8 +20258,8 @@ packages:
simple-get@4.0.1:
resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==}
simple-git@3.32.3:
resolution: {integrity: sha512-56a5oxFdWlsGygOXHWrG+xjj5w9ZIt2uQbzqiIGdR/6i5iococ7WQ/bNPzWxCJdEUGUCmyMH0t9zMpRJTaKxmw==}
simple-git@3.36.0:
resolution: {integrity: sha512-cGQjLjK8bxJw4QuYT7gxHw3/IouVESbhahSsHrX97MzCL1gu2u7oy38W6L2ZIGECEfIBG4BabsWDPjBxJENv9Q==}
simple-lru-cache@0.0.2:
resolution: {integrity: sha512-uEv/AFO0ADI7d99OHDmh1QfYzQk/izT1vCmu/riQfh7qjBVUUgRT87E5s5h7CxWCA/+YoZerykpEthzVrW3LIw==}
@ -21574,6 +21565,7 @@ packages:
uuid@10.0.0:
resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==}
deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).
hasBin: true
uuid@11.1.0:
@ -21591,6 +21583,7 @@ packages:
uuid@9.0.1:
resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==}
deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).
hasBin: true
v-code-diff@1.13.1:
@ -21772,8 +21765,8 @@ packages:
vm-browserify@1.1.2:
resolution: {integrity: sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==}
vm2@3.10.5:
resolution: {integrity: sha512-3P/2QDccVFBcujfCOeP8vVNuGfuBJHEuvGR8eMmI10p/iwLL2UwF5PDaNaoOS2pRGQEDmJRyeEcc8kmm2Z59RA==}
vm2@3.11.2:
resolution: {integrity: sha512-hnsYAgBSAgwwPM/Gq66gMmUY0VlY9mKC8nvVRAZiJp+tYxF4sfNRlZymP8uqzIUK2U/7+SVZ/H8p7USxNHLlZA==}
engines: {node: '>=6.0'}
hasBin: true
@ -21815,8 +21808,8 @@ packages:
vue-component-type-helpers@2.2.12:
resolution: {integrity: sha512-YbGqHZ5/eW4SnkPNR44mKVc6ZKQoRs/Rux1sxC6rdwXb4qpbOSYfDr9DsTHolOTGmIKgM9j141mZbBeg05R1pw==}
vue-component-type-helpers@3.2.7:
resolution: {integrity: sha512-+gPp5YGmhfsj1IN+xUo7y0fb4clfnOiiUA39y07yW1VzCRjzVgwLbtmdWlghh7mXrPsEaYc7rrIir/HT6C8vYQ==}
vue-component-type-helpers@3.2.8:
resolution: {integrity: sha512-9689efAXhN/EV86plgkL/XFiJSXhGtWPG6JDboZ+QnjlUWUUQrQ0ILKQtw4iQsuwIwu5k6Aw+JnehDe7161e7A==}
vue-demi@0.14.10:
resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==}
@ -22382,7 +22375,7 @@ snapshots:
'@1password/connect@1.4.2':
dependencies:
axios: 1.15.0(debug@4.4.3)
axios: 1.16.0(debug@4.4.3)
debug: 4.4.3(supports-color@8.1.1)
lodash.clonedeep: 4.5.0
slugify: 1.6.6
@ -23247,7 +23240,7 @@ snapshots:
'@smithy/smithy-client': 4.9.8
'@smithy/types': 4.9.0
'@smithy/util-middleware': 4.2.5
fast-xml-parser: 5.7.0
fast-xml-parser: 5.7.2
tslib: 2.8.1
'@aws-sdk/core@3.916.0':
@ -24139,13 +24132,13 @@ snapshots:
'@aws-sdk/xml-builder@3.914.0':
dependencies:
'@smithy/types': 4.13.1
fast-xml-parser: 5.7.0
fast-xml-parser: 5.7.2
tslib: 2.8.1
'@aws-sdk/xml-builder@3.930.0':
dependencies:
'@smithy/types': 4.13.1
fast-xml-parser: 5.7.0
fast-xml-parser: 5.7.2
tslib: 2.8.1
'@aws/lambda-invoke-store@0.0.1': {}
@ -24280,7 +24273,7 @@ snapshots:
'@azure/core-xml@1.4.5':
dependencies:
fast-xml-parser: 5.7.0
fast-xml-parser: 5.7.2
tslib: 2.8.1
'@azure/identity@4.13.0':
@ -24402,6 +24395,7 @@ snapshots:
'@babel/helper-validator-identifier': 7.28.5
js-tokens: 4.0.0
picocolors: 1.1.1
optional: true
'@babel/code-frame@7.29.0':
dependencies:
@ -25817,7 +25811,7 @@ snapshots:
'@codspeed/core@5.2.0':
dependencies:
axios: 1.15.0
axios: 1.16.0
find-up: 6.3.0
form-data: 4.0.4
node-gyp-build: 4.8.4
@ -25876,19 +25870,19 @@ snapshots:
'@currents/playwright@1.15.3(bufferutil@4.0.9)(magicast@0.3.5)(utf-8-validate@5.0.10)':
dependencies:
'@babel/code-frame': 7.27.1
'@babel/code-frame': 7.29.0
'@commander-js/extra-typings': 12.1.0(commander@12.1.0)
'@currents/commit-info': 1.0.1-beta.0
async-retry: 1.3.3
axios: 1.15.0(debug@4.4.3)
axios-retry: 4.5.0(axios@1.15.0)
axios: 1.16.0(debug@4.4.3)
axios-retry: 4.5.0(axios@1.16.0)
c12: 1.11.2(magicast@0.3.5)
chalk: 4.1.2
commander: 12.1.0
date-fns: 2.30.0
debug: 4.4.3(supports-color@8.1.1)
dotenv: 16.6.1
execa: 9.6.0
execa: 9.6.1
getos: 3.2.1
https-proxy-agent: 7.0.6
istanbul-lib-coverage: 3.2.2
@ -25931,13 +25925,13 @@ snapshots:
'@daytonaio/api-client@0.143.0':
dependencies:
axios: 1.15.0
axios: 1.16.0
transitivePeerDependencies:
- debug
'@daytonaio/api-client@0.149.0':
dependencies:
axios: 1.15.0
axios: 1.16.0
transitivePeerDependencies:
- debug
@ -25956,7 +25950,7 @@ snapshots:
'@opentelemetry/sdk-node': 0.207.0(@opentelemetry/api@1.9.0)
'@opentelemetry/sdk-trace-base': 2.6.0(@opentelemetry/api@1.9.0)
'@opentelemetry/semantic-conventions': 1.40.0
axios: 1.15.0
axios: 1.16.0
busboy: 1.6.0
dotenv: 17.2.3
expand-tilde: 2.0.2
@ -25987,7 +25981,7 @@ snapshots:
'@opentelemetry/sdk-node': 0.207.0(@opentelemetry/api@1.9.0)
'@opentelemetry/sdk-trace-base': 2.6.0(@opentelemetry/api@1.9.0)
'@opentelemetry/semantic-conventions': 1.40.0
axios: 1.15.0
axios: 1.16.0
busboy: 1.6.0
dotenv: 17.2.3
expand-tilde: 2.0.2
@ -26005,13 +25999,13 @@ snapshots:
'@daytonaio/toolbox-api-client@0.143.0':
dependencies:
axios: 1.15.0
axios: 1.16.0
transitivePeerDependencies:
- debug
'@daytonaio/toolbox-api-client@0.149.0':
dependencies:
axios: 1.15.0
axios: 1.16.0
transitivePeerDependencies:
- debug
@ -26382,7 +26376,7 @@ snapshots:
abort-controller: 3.0.0
async-retry: 1.3.3
duplexify: 4.1.3
fast-xml-parser: 5.7.0
fast-xml-parser: 5.7.2
gaxios: 7.1.3
google-auth-library: 9.15.1
html-entities: 2.5.2
@ -26436,9 +26430,9 @@ snapshots:
protobufjs: 7.5.5
yargs: 17.7.2
'@hono/node-server@1.19.13(hono@4.12.14)':
'@hono/node-server@1.19.13(hono@4.12.16)':
dependencies:
hono: 4.12.14
hono: 4.12.16
'@huggingface/inference@4.0.5':
dependencies:
@ -27121,7 +27115,7 @@ snapshots:
- aws-crt
- encoding
'@langchain/community@1.1.27(7d9a0518aab37da79d9f494cd5b9494b)':
'@langchain/community@1.1.27(f2f54e7010350c3b50a1b81272c39ebc)':
dependencies:
'@browserbasehq/stagehand': 1.14.0(@playwright/test@1.58.0)(bufferutil@4.0.9)(deepmerge@4.3.1)(dotenv@17.3.1)(encoding@0.1.13)(openai@6.34.0(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(utf-8-validate@5.0.10)(zod@3.25.67)
'@ibm-cloud/watsonx-ai': 1.1.2
@ -27161,7 +27155,7 @@ snapshots:
crypto-js: 4.2.0
d3-dsv: 2.0.0
epub2: 3.0.2(ts-toolbelt@9.6.0)
fast-xml-parser: 5.7.0
fast-xml-parser: 5.7.2
google-auth-library: 10.1.0
html-to-text: 9.0.5
ignore: 5.2.4
@ -27183,7 +27177,7 @@ snapshots:
- '@opentelemetry/sdk-trace-base'
- peggy
'@langchain/community@1.1.27(9d1780938e2a3bdbdbc6aaf41ad92004)':
'@langchain/community@1.1.27(fc62cbc93d74cace03ba310d8e53131b)':
dependencies:
'@browserbasehq/stagehand': 1.14.0(@playwright/test@1.58.0)(bufferutil@4.0.9)(deepmerge@4.3.1)(dotenv@17.3.1)(encoding@0.1.13)(openai@6.34.0(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(utf-8-validate@5.0.10)(zod@3.25.67)
'@ibm-cloud/watsonx-ai': 1.1.2
@ -27214,7 +27208,7 @@ snapshots:
chromadb: 3.2.0
crypto-js: 4.2.0
epub2: 3.0.2(ts-toolbelt@9.6.0)
fast-xml-parser: 5.7.0
fast-xml-parser: 5.7.2
google-auth-library: 10.1.0
html-to-text: 9.0.5
ignore: 7.0.5
@ -27586,8 +27580,8 @@ snapshots:
dotenv: 17.3.1
execa: 9.6.1
gray-matter: 4.0.3
hono: 4.12.14
hono-openapi: 1.3.0(@standard-community/standard-json@0.3.5(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(quansync@0.2.11)(zod-to-json-schema@3.25.1(zod@3.25.67))(zod@3.25.67))(@standard-community/standard-openapi@0.2.9(@standard-community/standard-json@0.3.5(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(quansync@0.2.11)(zod-to-json-schema@3.25.1(zod@3.25.67))(zod@3.25.67))(@standard-schema/spec@1.1.0)(openapi-types@12.1.3)(zod@3.25.67))(@types/json-schema@7.0.15)(hono@4.12.14)(openapi-types@12.1.3)
hono: 4.12.16
hono-openapi: 1.3.0(@standard-community/standard-json@0.3.5(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(quansync@0.2.11)(zod-to-json-schema@3.25.1(zod@3.25.67))(zod@3.25.67))(@standard-community/standard-openapi@0.2.9(@standard-community/standard-json@0.3.5(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(quansync@0.2.11)(zod-to-json-schema@3.25.1(zod@3.25.67))(zod@3.25.67))(@standard-schema/spec@1.1.0)(openapi-types@12.1.3)(zod@3.25.67))(@types/json-schema@7.0.15)(hono@4.12.16)(openapi-types@12.1.3)
ignore: 7.0.5
js-tiktoken: 1.0.21
json-schema: 0.4.0
@ -27788,7 +27782,7 @@ snapshots:
'@microsoft/agents-a365-runtime': 0.1.0-preview.113
'@microsoft/agents-a365-tooling': 0.1.0-preview.113(zod@3.25.67)
'@microsoft/agents-hosting': 1.2.3
hono: 4.12.14
hono: 4.12.16
langchain: 1.2.30(@langchain/core@1.1.41(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.213.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.6.0(@opentelemetry/api@1.9.0))(openai@6.34.0(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.213.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.6.0(@opentelemetry/api@1.9.0))(openai@6.34.0(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(vue@3.5.26(typescript@6.0.2))(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod-to-json-schema@3.23.3(zod@3.25.67))
uuid: 9.0.1
optionalDependencies:
@ -27816,7 +27810,7 @@ snapshots:
'@microsoft/agents-hosting': 1.2.3
'@modelcontextprotocol/sdk': 1.26.0(zod@3.25.67)
express: 5.2.1
hono: 4.12.14
hono: 4.12.16
transitivePeerDependencies:
- '@cfworker/json-schema'
- debug
@ -27836,7 +27830,7 @@ snapshots:
'@azure/core-auth': 1.10.1
'@azure/msal-node': 3.8.4
'@microsoft/agents-activity': 1.2.3
axios: 1.15.0
axios: 1.16.0
jsonwebtoken: 9.0.3
jwks-rsa: 3.2.2
object-path: 0.11.8
@ -27895,7 +27889,7 @@ snapshots:
'@modelcontextprotocol/sdk@1.26.0(zod@3.25.67)':
dependencies:
'@hono/node-server': 1.19.13(hono@4.12.14)
'@hono/node-server': 1.19.13(hono@4.12.16)
ajv: 8.18.0
ajv-formats: 3.0.1(ajv@8.18.0)
content-type: 1.0.5
@ -27905,7 +27899,7 @@ snapshots:
eventsource-parser: 3.0.6
express: 5.2.1
express-rate-limit: 8.2.2(express@5.2.1)
hono: 4.12.14
hono: 4.12.16
jose: 6.1.3
json-schema-typed: 8.0.2
pkce-challenge: 5.0.0(patch_hash=651e785d0b7bbf5be9210e1e895c39a16dc3ce8a5a3843b4819565fb6e175b90)
@ -27917,7 +27911,7 @@ snapshots:
'@modelcontextprotocol/sdk@1.28.0(zod@3.25.67)':
dependencies:
'@hono/node-server': 1.19.13(hono@4.12.14)
'@hono/node-server': 1.19.13(hono@4.12.16)
ajv: 8.18.0
ajv-formats: 3.0.1(ajv@8.18.0)
content-type: 1.0.5
@ -27927,7 +27921,7 @@ snapshots:
eventsource-parser: 3.0.6
express: 5.2.1
express-rate-limit: 8.2.2(express@5.2.1)
hono: 4.12.14
hono: 4.12.16
jose: 6.2.2
json-schema-typed: 8.0.2
pkce-challenge: 5.0.0(patch_hash=651e785d0b7bbf5be9210e1e895c39a16dc3ce8a5a3843b4819565fb6e175b90)
@ -28319,10 +28313,6 @@ snapshots:
dependencies:
'@opentelemetry/api': 1.9.0
'@opentelemetry/context-async-hooks@2.5.0(@opentelemetry/api@1.9.0)':
dependencies:
'@opentelemetry/api': 1.9.0
'@opentelemetry/context-async-hooks@2.6.0(@opentelemetry/api@1.9.0)':
dependencies:
'@opentelemetry/api': 1.9.0
@ -29804,8 +29794,8 @@ snapshots:
'@rudderstack/rudder-sdk-node@3.0.5':
dependencies:
axios: 1.15.0
axios-retry: 4.5.0(axios@1.15.0)
axios: 1.16.0
axios-retry: 4.5.0(axios@1.16.0)
component-type: 2.0.0
join-component: 1.1.0
lodash.clonedeep: 4.5.0
@ -29960,18 +29950,18 @@ snapshots:
'@sentry/core@10.36.0': {}
'@sentry/node-core@10.36.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.210.0(@opentelemetry/api@1.9.0))(@opentelemetry/resources@2.6.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.6.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.40.0)':
'@sentry/node-core@10.36.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.6.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.6.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.210.0(@opentelemetry/api@1.9.0))(@opentelemetry/resources@2.6.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.6.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.40.0)':
dependencies:
'@apm-js-collab/tracing-hooks': 0.3.1
'@opentelemetry/api': 1.9.0
'@opentelemetry/context-async-hooks': 2.5.0(@opentelemetry/api@1.9.0)
'@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0)
'@opentelemetry/context-async-hooks': 2.6.0(@opentelemetry/api@1.9.0)
'@opentelemetry/core': 2.6.0(@opentelemetry/api@1.9.0)
'@opentelemetry/instrumentation': 0.210.0(@opentelemetry/api@1.9.0)
'@opentelemetry/resources': 2.6.0(@opentelemetry/api@1.9.0)
'@opentelemetry/sdk-trace-base': 2.6.0(@opentelemetry/api@1.9.0)
'@opentelemetry/semantic-conventions': 1.40.0
'@sentry/core': 10.36.0
'@sentry/opentelemetry': 10.36.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.6.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.40.0)
'@sentry/opentelemetry': 10.36.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.6.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.6.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.6.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.40.0)
import-in-the-middle: 2.0.5
transitivePeerDependencies:
- supports-color
@ -29987,8 +29977,8 @@ snapshots:
'@sentry/node@10.36.0':
dependencies:
'@opentelemetry/api': 1.9.0
'@opentelemetry/context-async-hooks': 2.5.0(@opentelemetry/api@1.9.0)
'@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0)
'@opentelemetry/context-async-hooks': 2.6.0(@opentelemetry/api@1.9.0)
'@opentelemetry/core': 2.6.0(@opentelemetry/api@1.9.0)
'@opentelemetry/instrumentation': 0.210.0(@opentelemetry/api@1.9.0)
'@opentelemetry/instrumentation-amqplib': 0.57.0(@opentelemetry/api@1.9.0)
'@opentelemetry/instrumentation-connect': 0.53.0(@opentelemetry/api@1.9.0)
@ -30017,18 +30007,18 @@ snapshots:
'@opentelemetry/semantic-conventions': 1.40.0
'@prisma/instrumentation': 7.2.0(@opentelemetry/api@1.9.0)
'@sentry/core': 10.36.0
'@sentry/node-core': 10.36.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.210.0(@opentelemetry/api@1.9.0))(@opentelemetry/resources@2.6.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.6.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.40.0)
'@sentry/opentelemetry': 10.36.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.6.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.40.0)
'@sentry/node-core': 10.36.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.6.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.6.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.210.0(@opentelemetry/api@1.9.0))(@opentelemetry/resources@2.6.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.6.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.40.0)
'@sentry/opentelemetry': 10.36.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.6.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.6.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.6.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.40.0)
import-in-the-middle: 2.0.5
minimatch: 9.0.9
transitivePeerDependencies:
- supports-color
'@sentry/opentelemetry@10.36.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.6.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.40.0)':
'@sentry/opentelemetry@10.36.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.6.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.6.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.6.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.40.0)':
dependencies:
'@opentelemetry/api': 1.9.0
'@opentelemetry/context-async-hooks': 2.5.0(@opentelemetry/api@1.9.0)
'@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0)
'@opentelemetry/context-async-hooks': 2.6.0(@opentelemetry/api@1.9.0)
'@opentelemetry/core': 2.6.0(@opentelemetry/api@1.9.0)
'@opentelemetry/sdk-trace-base': 2.6.0(@opentelemetry/api@1.9.0)
'@opentelemetry/semantic-conventions': 1.40.0
'@sentry/core': 10.36.0
@ -30057,6 +30047,12 @@ snapshots:
optionalDependencies:
pinia: 2.2.4(typescript@6.0.2)(vue@3.5.26(typescript@6.0.2))
'@simple-git/args-pathspec@1.0.3': {}
'@simple-git/argv-parser@1.1.1':
dependencies:
'@simple-git/args-pathspec': 1.0.3
'@sinclair/typebox@0.25.21': {}
'@sinclair/typebox@0.27.8': {}
@ -31098,7 +31094,7 @@ snapshots:
storybook: 10.1.11(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(prettier@3.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(utf-8-validate@5.0.10)
type-fest: 2.19.0
vue: 3.5.26(typescript@6.0.2)
vue-component-type-helpers: 3.2.7
vue-component-type-helpers: 3.2.8
'@stylistic/eslint-plugin@5.0.0(eslint@9.29.0(jiti@2.6.1))':
dependencies:
@ -32520,7 +32516,7 @@ snapshots:
'@vue/shared': 3.5.26
estree-walker: 2.0.2
magic-string: 0.30.21
postcss: 8.5.6
postcss: 8.5.8
source-map-js: 1.2.1
'@vue/compiler-ssr@3.5.26':
@ -33212,12 +33208,12 @@ snapshots:
axe-core@4.7.2: {}
axios-retry@4.5.0(axios@1.15.0):
axios-retry@4.5.0(axios@1.16.0):
dependencies:
axios: 1.15.0
axios: 1.16.0
is-retry-allowed: 2.2.0
axios@1.15.0:
axios@1.16.0:
dependencies:
follow-redirects: 1.16.0(debug@4.4.1)
form-data: 4.0.4
@ -33225,7 +33221,7 @@ snapshots:
transitivePeerDependencies:
- debug
axios@1.15.0(debug@4.4.3):
axios@1.16.0(debug@4.4.3):
dependencies:
follow-redirects: 1.16.0(debug@4.4.3)
form-data: 4.0.4
@ -34870,11 +34866,11 @@ snapshots:
dependencies:
node-source-walk: 7.0.1
detective-postcss@7.0.1(postcss@8.5.6):
detective-postcss@7.0.1(postcss@8.5.8):
dependencies:
is-url: 1.2.4
postcss: 8.5.6
postcss-values-parser: 6.0.2(postcss@8.5.6)
postcss: 8.5.8
postcss-values-parser: 6.0.2(postcss@8.5.8)
detective-sass@6.0.1:
dependencies:
@ -35575,7 +35571,7 @@ snapshots:
eslint-import-context@0.1.8(unrs-resolver@1.9.2):
dependencies:
get-tsconfig: 4.10.1
get-tsconfig: 4.13.0
stable-hash-x: 0.1.1
optionalDependencies:
unrs-resolver: 1.9.2
@ -35911,21 +35907,6 @@ snapshots:
signal-exit: 3.0.7
strip-final-newline: 2.0.0
execa@9.6.0:
dependencies:
'@sindresorhus/merge-streams': 4.0.0
cross-spawn: 7.0.6
figures: 6.1.0
get-stream: 9.0.1
human-signals: 8.0.1
is-plain-obj: 4.1.0
is-stream: 4.0.1
npm-run-path: 6.0.0
pretty-ms: 9.2.0
signal-exit: 4.1.0
strip-final-newline: 4.0.0
yoctocolors: 2.1.1
execa@9.6.1:
dependencies:
'@sindresorhus/merge-streams': 4.0.0
@ -36209,7 +36190,7 @@ snapshots:
dependencies:
path-expression-matcher: 1.5.0
fast-xml-parser@5.7.0:
fast-xml-parser@5.7.2:
dependencies:
'@nodable/entities': 2.1.0
fast-xml-builder: 1.1.5
@ -36679,9 +36660,9 @@ snapshots:
foreground-child: 3.3.1
jackspeak: 4.1.1
minimatch: 10.2.3
minipass: 7.1.2
minipass: 7.1.3
package-json-from-dist: 1.0.0
path-scurry: 2.0.1
path-scurry: 2.0.2
glob@13.0.6:
dependencies:
@ -36970,16 +36951,16 @@ snapshots:
dependencies:
parse-passwd: 1.0.0
hono-openapi@1.3.0(@standard-community/standard-json@0.3.5(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(quansync@0.2.11)(zod-to-json-schema@3.25.1(zod@3.25.67))(zod@3.25.67))(@standard-community/standard-openapi@0.2.9(@standard-community/standard-json@0.3.5(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(quansync@0.2.11)(zod-to-json-schema@3.25.1(zod@3.25.67))(zod@3.25.67))(@standard-schema/spec@1.1.0)(openapi-types@12.1.3)(zod@3.25.67))(@types/json-schema@7.0.15)(hono@4.12.14)(openapi-types@12.1.3):
hono-openapi@1.3.0(@standard-community/standard-json@0.3.5(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(quansync@0.2.11)(zod-to-json-schema@3.25.1(zod@3.25.67))(zod@3.25.67))(@standard-community/standard-openapi@0.2.9(@standard-community/standard-json@0.3.5(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(quansync@0.2.11)(zod-to-json-schema@3.25.1(zod@3.25.67))(zod@3.25.67))(@standard-schema/spec@1.1.0)(openapi-types@12.1.3)(zod@3.25.67))(@types/json-schema@7.0.15)(hono@4.12.16)(openapi-types@12.1.3):
dependencies:
'@standard-community/standard-json': 0.3.5(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(quansync@0.2.11)(zod-to-json-schema@3.25.1(zod@3.25.67))(zod@3.25.67)
'@standard-community/standard-openapi': 0.2.9(@standard-community/standard-json@0.3.5(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(quansync@0.2.11)(zod-to-json-schema@3.25.1(zod@3.25.67))(zod@3.25.67))(@standard-schema/spec@1.1.0)(openapi-types@12.1.3)(zod@3.25.67)
'@types/json-schema': 7.0.15
openapi-types: 12.1.3
optionalDependencies:
hono: 4.12.14
hono: 4.12.16
hono@4.12.14: {}
hono@4.12.16: {}
hookable@5.5.3: {}
@ -37174,7 +37155,7 @@ snapshots:
'@types/debug': 4.1.12
'@types/node': 20.19.21
'@types/tough-cookie': 4.0.5
axios: 1.15.0(debug@4.4.3)
axios: 1.16.0(debug@4.4.3)
camelcase: 6.3.0
debug: 4.4.3(supports-color@8.1.1)
dotenv: 16.6.1
@ -37184,7 +37165,7 @@ snapshots:
isstream: 0.1.2
jsonwebtoken: 9.0.3
mime-types: 2.1.35
retry-axios: 2.6.0(axios@1.15.0)
retry-axios: 2.6.0(axios@1.16.0)
tough-cookie: 4.1.4
transitivePeerDependencies:
- supports-color
@ -37288,7 +37269,7 @@ snapshots:
infisical-node@1.3.0:
dependencies:
axios: 1.15.0
axios: 1.16.0
dotenv: 16.6.1
tweetnacl: 1.0.3
tweetnacl-util: 0.15.1
@ -40301,7 +40282,7 @@ snapshots:
lower-case: 2.0.2
tslib: 2.8.1
nock@14.0.13:
nock@14.0.14:
dependencies:
'@mswjs/interceptors': 0.41.6
json-stringify-safe: 5.0.1
@ -41107,11 +41088,6 @@ snapshots:
lru-cache: 10.2.2
minipass: 7.1.3
path-scurry@2.0.1:
dependencies:
lru-cache: 11.2.7
minipass: 7.1.3
path-scurry@2.0.2:
dependencies:
lru-cache: 11.2.7
@ -41401,11 +41377,11 @@ snapshots:
postcss-value-parser@4.2.0: {}
postcss-values-parser@6.0.2(postcss@8.5.6):
postcss-values-parser@6.0.2(postcss@8.5.8):
dependencies:
color-name: 1.1.4
is-url-superb: 4.0.0
postcss: 8.5.6
postcss: 8.5.8
quote-unquote: 1.0.0
postcss@8.4.31:
@ -41444,7 +41420,7 @@ snapshots:
posthog-node@3.2.1:
dependencies:
axios: 1.15.0
axios: 1.16.0
rusha: 0.8.14
transitivePeerDependencies:
- debug
@ -41476,7 +41452,7 @@ snapshots:
detective-amd: 6.0.1
detective-cjs: 6.0.1
detective-es6: 5.0.1
detective-postcss: 7.0.1(postcss@8.5.6)
detective-postcss: 7.0.1(postcss@8.5.8)
detective-sass: 6.0.1
detective-scss: 5.0.1
detective-stylus: 5.0.1
@ -41484,7 +41460,7 @@ snapshots:
detective-vue2: 2.2.0(typescript@6.0.2)
module-definition: 6.0.1
node-source-walk: 7.0.1
postcss: 8.5.6
postcss: 8.5.8
typescript: 6.0.2
transitivePeerDependencies:
- supports-color
@ -41692,7 +41668,7 @@ snapshots:
jstransformer: 1.0.0
pug-error: 2.1.0
pug-walk: 2.0.0
resolve: 1.22.10
resolve: 1.22.11
pug-lexer@5.0.1:
dependencies:
@ -42250,9 +42226,9 @@ snapshots:
retimer@3.0.0: {}
retry-axios@2.6.0(axios@1.15.0):
retry-axios@2.6.0(axios@1.16.0):
dependencies:
axios: 1.15.0
axios: 1.16.0
retry-request@7.0.2(encoding@0.1.13):
dependencies:
@ -42875,10 +42851,12 @@ snapshots:
once: 1.4.0
simple-concat: 1.0.1
simple-git@3.32.3:
simple-git@3.36.0:
dependencies:
'@kwsites/file-exists': 1.1.1
'@kwsites/promise-deferred': 1.1.1
'@simple-git/args-pathspec': 1.0.3
'@simple-git/argv-parser': 1.1.1
debug: 4.4.3(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
@ -42962,14 +42940,14 @@ snapshots:
asn1.js: 5.4.1
asn1.js-rfc2560: 5.0.1(asn1.js@5.4.1)
asn1.js-rfc5280: 3.0.0
axios: 1.15.0
axios: 1.16.0
big-integer: 1.6.52
bignumber.js: 9.1.2
binascii: 0.0.2
bn.js: 5.2.3
browser-request: 0.3.3
expand-tilde: 2.0.2
fast-xml-parser: 5.7.0
fast-xml-parser: 5.7.2
fastest-levenshtein: 1.0.16
generic-pool: 3.9.0
glob: 10.5.0
@ -44715,9 +44693,9 @@ snapshots:
vm-browserify@1.1.2: {}
vm2@3.10.5:
vm2@3.11.2:
dependencies:
acorn: 8.15.0
acorn: 8.16.0
acorn-walk: 8.3.4
void-elements@3.1.0: {}
@ -44753,7 +44731,7 @@ snapshots:
vue-component-type-helpers@2.2.12: {}
vue-component-type-helpers@3.2.7: {}
vue-component-type-helpers@3.2.8: {}
vue-demi@0.14.10(vue@3.5.26(typescript@6.0.2)):
dependencies:

View File

@ -54,7 +54,7 @@ catalog:
'@types/uuid': ^10.0.0
'@types/xml2js': ^0.4.14
'@vitest/coverage-v8': 4.1.1
axios: 1.15.1
axios: 1.16.0
basic-auth: 2.0.1
callsites: 3.1.0
chokidar: 4.0.3
@ -89,7 +89,7 @@ catalog:
reflect-metadata: 0.2.2
rimraf: 6.0.1
sass-embedded: ^1.98.0
simple-git: 3.32.3
simple-git: 3.36.0
stream-json: 1.9.1
testcontainers: ^11.13.0
ts-morph: ^27.0.2
@ -101,7 +101,7 @@ catalog:
vite-plugin-dts: ^4.5.4
vitest: ^4.1.1
vitest-mock-extended: ^3.1.0
vm2: ^3.10.5
vm2: 3.11.2
xml2js: 0.6.2
xss: 1.0.15
yaml: 2.8.3