diff --git a/packages/benchmark/Dockerfile b/packages/benchmark/Dockerfile new file mode 100644 index 0000000000000..18673141fd565 --- /dev/null +++ b/packages/benchmark/Dockerfile @@ -0,0 +1,67 @@ +# NOTE: +# This Dockerfile needs to be built in the root of the repository +# `docker build -t n8n-benchmark -f packages/benchmark/Dockerfile .` +FROM node:20.16.0 AS base + +# Install required dependencies +RUN apt-get update && apt-get install -y gnupg2 curl + +# Add k6 GPG key and repository +RUN mkdir -p /etc/apt/keyrings && \ + curl -sS https://dl.k6.io/key.gpg | gpg --dearmor --yes -o /etc/apt/keyrings/k6.gpg && \ + chmod a+x /etc/apt/keyrings/k6.gpg && \ + echo "deb [signed-by=/etc/apt/keyrings/k6.gpg] https://dl.k6.io/deb stable main" | tee /etc/apt/sources.list.d/k6.list + +# Update and install k6 +RUN apt-get update && \ + apt-get install -y k6 tini && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +ENV PNPM_HOME="/pnpm" +ENV PATH="$PNPM_HOME:$PATH" +RUN corepack enable + +# +# Builder +FROM base AS builder + +WORKDIR /app + +COPY --chown=node:node ./pnpm-lock.yaml /app/pnpm-lock.yaml +COPY --chown=node:node ./pnpm-workspace.yaml /app/pnpm-workspace.yaml +COPY --chown=node:node ./package.json /app/package.json +COPY --chown=node:node ./packages/benchmark/package.json /app/packages/benchmark/package.json +COPY --chown=node:node ./patches /app/patches +COPY --chown=node:node ./scripts /app/scripts + +RUN pnpm install --frozen-lockfile + +# Package.json +COPY --chown=node:node ./packages/benchmark/package.json /app/packages/benchmark/package.json + +# TS config files +COPY --chown=node:node ./tsconfig.json /app/tsconfig.json +COPY --chown=node:node ./tsconfig.build.json /app/tsconfig.build.json +COPY --chown=node:node ./tsconfig.backend.json /app/tsconfig.backend.json +COPY --chown=node:node ./packages/benchmark/tsconfig.json /app/packages/benchmark/tsconfig.json +COPY --chown=node:node ./packages/benchmark/tsconfig.build.json /app/packages/benchmark/tsconfig.build.json + +# Source files +COPY --chown=node:node ./packages/benchmark/src /app/packages/benchmark/src +COPY --chown=node:node ./packages/benchmark/bin /app/packages/benchmark/bin +COPY --chown=node:node ./packages/benchmark/testScenarios /app/packages/benchmark/testScenarios + +WORKDIR /app/packages/benchmark +RUN pnpm build + +# +# Runner +FROM base AS runner + +COPY --from=builder /app /app + +WORKDIR /app/packages/benchmark +USER node + +ENTRYPOINT [ "/app/packages/benchmark/bin/n8n-benchmark" ] diff --git a/packages/benchmark/README.md b/packages/benchmark/README.md new file mode 100644 index 0000000000000..eaaa36631371b --- /dev/null +++ b/packages/benchmark/README.md @@ -0,0 +1,33 @@ +# n8n benchmarking tool + +Tool for executing benchmarks against an n8n instance. + +## Requirements + +- [k6](https://grafana.com/docs/k6/latest/) +- Node.js v20 or higher + +## Running locally + +```sh +pnpm build + +# Run tests against http://localhost:5678 with specified email and password +N8N_USER_EMAIL=user@n8n.io N8N_USER_PASSWORD=password ./bin/n8n-benchmark run + +# If you installed k6 using brew, you might have to specify it explicitly +K6_PATH=/opt/homebrew/bin/k6 N8N_USER_EMAIL=user@n8n.io N8N_USER_PASSWORD=password ./bin/n8n-benchmark run +``` + +## Configuration + +The configuration options the cli accepts can be seen from [config.ts](./src/config/config.ts) + +## Docker build + +Because k6 doesn't have an arm64 build available for linux, we need to build against amd64 + +```sh +# In the repository root +docker build --platform linux/amd64 -f packages/benchmark/Dockerfile -t n8n-benchmark:latest . +``` diff --git a/packages/benchmark/bin/n8n-benchmark b/packages/benchmark/bin/n8n-benchmark new file mode 100755 index 0000000000000..c7f0996f09476 --- /dev/null +++ b/packages/benchmark/bin/n8n-benchmark @@ -0,0 +1,13 @@ +#!/usr/bin/env node + +// Check if version should be displayed +const versionFlags = ['-v', '-V', '--version']; +if (versionFlags.includes(process.argv.slice(-1)[0])) { + console.log(require('../package').version); + process.exit(0); +} + +(async () => { + const oclif = require('@oclif/core'); + await oclif.execute({ dir: __dirname }); +})(); diff --git a/packages/benchmark/nodemon.json b/packages/benchmark/nodemon.json new file mode 100644 index 0000000000000..393762d234d2d --- /dev/null +++ b/packages/benchmark/nodemon.json @@ -0,0 +1,6 @@ +{ + "ignore": ["**/*.spec.ts", ".git", "node_modules"], + "watch": ["commands", "index.ts", "src"], + "exec": "npm start", + "ext": "ts" +} diff --git a/packages/benchmark/package.json b/packages/benchmark/package.json new file mode 100644 index 0000000000000..c1d2ba6b4b0cd --- /dev/null +++ b/packages/benchmark/package.json @@ -0,0 +1,41 @@ +{ + "name": "n8n-benchmark", + "version": "1.0.0", + "description": "", + "main": "dist/index", + "scripts": { + "build": "tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json", + "start": "./bin/n8n-benchmark", + "test": "echo \"Error: no test specified\" && exit 1", + "typecheck": "tsc --noEmit", + "watch": "concurrently \"tsc -w -p tsconfig.build.json\" \"tsc-alias -w -p tsconfig.build.json\"" + }, + "engines": { + "node": ">=20.10" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "@oclif/core": "4.0.7", + "axios": "catalog:", + "convict": "6.2.4", + "dotenv": "8.6.0", + "zx": "^8.1.4" + }, + "devDependencies": { + "@types/convict": "^6.1.1", + "@types/k6": "^0.52.0", + "@types/node": "^20.14.8", + "tsc-alias": "^1.8.7", + "typescript": "^5.5.2" + }, + "bin": { + "n8n-benchmark": "./bin/n8n-benchmark" + }, + "oclif": { + "bin": "n8n-benchmark", + "commands": "./dist/commands", + "topicSeparator": " " + } +} diff --git a/packages/benchmark/src/commands/list.ts b/packages/benchmark/src/commands/list.ts new file mode 100644 index 0000000000000..603f746f286ae --- /dev/null +++ b/packages/benchmark/src/commands/list.ts @@ -0,0 +1,21 @@ +import { Command } from '@oclif/core'; +import { TestScenarioLoader } from '@/testScenario/testScenarioLoader'; +import { loadConfig } from '@/config/config'; + +export default class ListCommand extends Command { + static description = 'List all available test scenarios'; + + async run() { + const config = loadConfig(); + const testScenarioLoader = new TestScenarioLoader(); + + const allScenarios = testScenarioLoader.loadAllTestScenarios(config.get('testScenariosPath')); + + console.log('Available test scenarios:'); + console.log(''); + + for (const testCase of allScenarios) { + console.log('\t', testCase.name, ':', testCase.description); + } + } +} diff --git a/packages/benchmark/src/commands/run.ts b/packages/benchmark/src/commands/run.ts new file mode 100644 index 0000000000000..0b524906e3e20 --- /dev/null +++ b/packages/benchmark/src/commands/run.ts @@ -0,0 +1,39 @@ +import { Command, Flags } from '@oclif/core'; +import { loadConfig } from '@/config/config'; +import { TestScenarioLoader } from '@/testScenario/testScenarioLoader'; +import { TestScenarioRunner } from '@/testExecution/testScenarioRunner'; +import { N8nApiClient } from '@/n8nApiClient/n8nApiClient'; +import { TestDataFileLoader } from '@/testScenario/testDataLoader'; +import { K6Executor } from '@/testExecution/k6Executor'; + +export default class RunCommand extends Command { + static description = 'Run all (default) or specified test scenarios'; + + // TODO: Add support for filtering scenarios + static flags = { + scenarios: Flags.string({ + char: 't', + description: 'Comma-separated list of test scenarios to run', + required: false, + }), + }; + + async run() { + const config = loadConfig(); + const testScenarioLoader = new TestScenarioLoader(); + + const testScenarioRunner = new TestScenarioRunner( + new N8nApiClient(config.get('n8n.baseUrl')), + new TestDataFileLoader(), + new K6Executor(config.get('k6ExecutablePath'), config.get('n8n.baseUrl')), + { + email: config.get('n8n.user.email'), + password: config.get('n8n.user.password'), + }, + ); + + const allScenarios = testScenarioLoader.loadAllTestScenarios(config.get('testScenariosPath')); + + await testScenarioRunner.runManyTestScenarios(allScenarios); + } +} diff --git a/packages/benchmark/src/config/config.ts b/packages/benchmark/src/config/config.ts new file mode 100644 index 0000000000000..d20bfbe8779f8 --- /dev/null +++ b/packages/benchmark/src/config/config.ts @@ -0,0 +1,50 @@ +import convict from 'convict'; +import dotenv from 'dotenv'; + +dotenv.config(); + +const configSchema = { + testScenariosPath: { + doc: 'The path to the test scenarios', + format: String, + default: 'testScenarios', + }, + n8n: { + baseUrl: { + doc: 'The base URL for the n8n instance', + format: String, + default: 'http://localhost:5678', + env: 'N8N_BASE_URL', + }, + user: { + email: { + doc: 'The email address of the n8n user', + format: String, + default: 'benchmark-user@n8n.io', + env: 'N8N_USER_EMAIL', + }, + password: { + doc: 'The password of the n8n user', + format: String, + default: 'VerySecret!123', + env: 'N8N_USER_PASSWORD', + }, + }, + }, + k6ExecutablePath: { + doc: 'The path to the k6 binary', + format: String, + default: 'k6', + env: 'K6_PATH', + }, +}; + +export type Config = ReturnType; + +export function loadConfig() { + const config = convict(configSchema); + + config.validate({ allowed: 'strict' }); + + return config; +} diff --git a/packages/benchmark/src/n8nApiClient/authenticatedN8nApiClient.ts b/packages/benchmark/src/n8nApiClient/authenticatedN8nApiClient.ts new file mode 100644 index 0000000000000..16f5058e18aa2 --- /dev/null +++ b/packages/benchmark/src/n8nApiClient/authenticatedN8nApiClient.ts @@ -0,0 +1,68 @@ +import { strict as assert } from 'node:assert'; +import { N8nApiClient } from './n8nApiClient'; +import { AxiosRequestConfig } from 'axios'; + +export class AuthenticatedN8nApiClient extends N8nApiClient { + constructor( + apiBaseUrl: string, + private readonly authCookie: string, + ) { + super(apiBaseUrl); + } + + static async createUsingUsernameAndPassword( + apiClient: N8nApiClient, + loginDetails: { + email: string; + password: string; + }, + ) { + const response = await apiClient.restApiRequest('/login', { + method: 'POST', + data: loginDetails, + }); + + const cookieHeader = response.headers['set-cookie']; + const authCookie = Array.isArray(cookieHeader) ? cookieHeader.join('; ') : cookieHeader; + assert(authCookie); + + return new AuthenticatedN8nApiClient(apiClient.apiBaseUrl, authCookie); + } + + async get(endpoint: string) { + return await this.authenticatedRequest(endpoint, { + method: 'GET', + }); + } + + async post(endpoint: string, data: unknown) { + return await this.authenticatedRequest(endpoint, { + method: 'POST', + data, + }); + } + + async patch(endpoint: string, data: unknown) { + return await this.authenticatedRequest(endpoint, { + method: 'PATCH', + data, + }); + } + + async delete(endpoint: string, data?: unknown) { + return await this.authenticatedRequest(endpoint, { + method: 'DELETE', + data, + }); + } + + protected async authenticatedRequest(endpoint: string, init: Omit) { + return await this.restApiRequest(endpoint, { + ...init, + headers: { + ...init.headers, + cookie: this.authCookie, + }, + }); + } +} diff --git a/packages/benchmark/src/n8nApiClient/n8nApiClient.ts b/packages/benchmark/src/n8nApiClient/n8nApiClient.ts new file mode 100644 index 0000000000000..5c5c94b555cf4 --- /dev/null +++ b/packages/benchmark/src/n8nApiClient/n8nApiClient.ts @@ -0,0 +1,78 @@ +import axios, { AxiosError, AxiosRequestConfig } from 'axios'; + +export class N8nApiClient { + constructor(public readonly apiBaseUrl: string) {} + + async waitForInstanceToBecomeOnline(): Promise { + const healthEndpoint = 'healthz'; + const startTime = Date.now(); + const intervalMs = 1000; + const timeout = 60000; + + while (Date.now() - startTime < timeout) { + try { + const response = await axios.request({ + url: `${this.apiBaseUrl}/${healthEndpoint}`, + method: 'GET', + }); + + if (response.status === 200 && response.data.status === 'ok') { + return; + } + } catch {} + + console.log(`n8n instance not online yet, retrying in ${intervalMs / 1000} seconds...`); + await this.delay(intervalMs); + } + + throw new Error(`n8n instance did not come online within ${timeout / 1000} seconds`); + } + + async setupOwnerIfNeeded(loginDetails: { email: string; password: string }) { + const response = await this.restApiRequest<{ message: string }>('/owner/setup', { + method: 'POST', + data: { + email: loginDetails.email, + password: loginDetails.password, + firstName: 'Test', + lastName: 'User', + }, + // Don't throw on non-2xx responses + validateStatus: () => true, + }); + + const responsePayload = response.data; + + if (response.status === 200) { + console.log('Owner setup successful'); + } else if (response.status === 400) { + if (responsePayload.message === 'Instance owner already setup') + console.log('Owner already set up'); + } else { + throw new Error( + `Owner setup failed with status ${response.status}: ${responsePayload.message}`, + ); + } + } + + async restApiRequest(endpoint: string, init: Omit) { + try { + return await axios.request({ + ...init, + url: this.getRestEndpointUrl(endpoint), + }); + } catch (e) { + const error = e as AxiosError; + console.error(`[ERROR] Request failed ${init.method} ${endpoint}`, error?.response?.data); + throw error; + } + } + + protected getRestEndpointUrl(endpoint: string) { + return `${this.apiBaseUrl}/rest${endpoint}`; + } + + private delay(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); + } +} diff --git a/packages/benchmark/src/n8nApiClient/n8nApiClient.types.ts b/packages/benchmark/src/n8nApiClient/n8nApiClient.types.ts new file mode 100644 index 0000000000000..9758d66b32d4b --- /dev/null +++ b/packages/benchmark/src/n8nApiClient/n8nApiClient.types.ts @@ -0,0 +1,8 @@ +/** + * n8n workflow. This is a simplified version of the actual workflow object. + */ +export interface Workflow { + id: string; + name: string; + tags?: string[]; +} diff --git a/packages/benchmark/src/n8nApiClient/workflowsApiClient.ts b/packages/benchmark/src/n8nApiClient/workflowsApiClient.ts new file mode 100644 index 0000000000000..5d871a8d17194 --- /dev/null +++ b/packages/benchmark/src/n8nApiClient/workflowsApiClient.ts @@ -0,0 +1,50 @@ +import { Workflow } from '@/n8nApiClient/n8nApiClient.types'; +import { AuthenticatedN8nApiClient } from './authenticatedN8nApiClient'; + +export class WorkflowApiClient { + private readonly apiEndpoint = '/workflows'; + + constructor(private readonly apiClient: AuthenticatedN8nApiClient) {} + + async getAllWorkflows(): Promise { + const response = await this.apiClient.get<{ count: number; data: Workflow[] }>( + this.apiEndpoint, + ); + + return response.data.data; + } + + async createWorkflow(workflow: unknown): Promise { + const response = await this.apiClient.post<{ data: Workflow }>(this.apiEndpoint, workflow); + + return response.data.data; + } + + async activateWorkflow(workflow: Workflow): Promise { + const response = await this.apiClient.patch<{ data: Workflow }>( + `${this.apiEndpoint}/${workflow.id}`, + { + ...workflow, + active: true, + }, + ); + + return response.data.data; + } + + async deleteWorkflow(workflowId: Workflow['id']): Promise { + await this.apiClient.delete(`${this.apiEndpoint}/${workflowId}`); + } + + async tagWorkflow(workflow: Workflow, tagId: string): Promise { + const response = await this.apiClient.patch<{ data: Workflow }>( + `${this.apiEndpoint}/${workflow.id}`, + { + ...workflow, + tags: [...(workflow.tags ?? []), tagId], + }, + ); + + return response.data.data; + } +} diff --git a/packages/benchmark/src/testExecution/k6Executor.ts b/packages/benchmark/src/testExecution/k6Executor.ts new file mode 100644 index 0000000000000..f2f1768331db1 --- /dev/null +++ b/packages/benchmark/src/testExecution/k6Executor.ts @@ -0,0 +1,28 @@ +import { $ } from 'zx'; +import { TestScenario } from '@/types/testScenario'; + +/** + * Executes test scenarios using k6 + */ +export class K6Executor { + constructor( + private readonly k6ExecutablePath: string, + private readonly n8nApiBaseUrl: string, + ) {} + + async executeTestScenario(testCase: TestScenario) { + // For 1 min with 5 virtual users + const stage = '1m:5'; + + const processPromise = $({ + cwd: testCase.testScenarioPath, + env: { + API_BASE_URL: this.n8nApiBaseUrl, + }, + })`${this.k6ExecutablePath} run --quiet --stage ${stage} ${testCase.testScriptPath}`; + + for await (const chunk of processPromise.stdout) { + console.log(chunk.toString()); + } + } +} diff --git a/packages/benchmark/src/testExecution/testDataImporter.ts b/packages/benchmark/src/testExecution/testDataImporter.ts new file mode 100644 index 0000000000000..a56224ee7fbce --- /dev/null +++ b/packages/benchmark/src/testExecution/testDataImporter.ts @@ -0,0 +1,57 @@ +import { AuthenticatedN8nApiClient } from '@/n8nApiClient/authenticatedN8nApiClient'; +import { Workflow } from '@/n8nApiClient/n8nApiClient.types'; +import { WorkflowApiClient } from '@/n8nApiClient/workflowsApiClient'; + +/** + * Imports test data into an n8n instance + */ +export class TestDataImporter { + private readonly workflowApiClient: WorkflowApiClient; + + constructor(n8nApiClient: AuthenticatedN8nApiClient) { + this.workflowApiClient = new WorkflowApiClient(n8nApiClient); + } + + async importTestScenarioData(workflows: Workflow[]) { + const existingWorkflows = await this.workflowApiClient.getAllWorkflows(); + + for (const workflow of workflows) { + await this.importWorkflow({ existingWorkflows, workflow }); + } + } + + /** + * Imports a single workflow into n8n and tags it with the given testCaseId + */ + private async importWorkflow(opts: { existingWorkflows: Workflow[]; workflow: Workflow }) { + const existingWorkflows = this.tryFindExistingWorkflows(opts.existingWorkflows, opts.workflow); + if (existingWorkflows.length > 0) { + for (const toDelete of existingWorkflows) { + await this.workflowApiClient.deleteWorkflow(toDelete.id); + } + } + + const createdWorkflow = await this.workflowApiClient.createWorkflow({ + ...opts.workflow, + name: this.getBenchmarkWorkflowName(opts.workflow), + }); + const activeWorkflow = await this.workflowApiClient.activateWorkflow(createdWorkflow); + + return activeWorkflow; + } + + private tryFindExistingWorkflows( + existingWorkflows: Workflow[], + workflowToImport: Workflow, + ): Workflow[] { + const benchmarkWorkflowName = this.getBenchmarkWorkflowName(workflowToImport); + + return existingWorkflows.filter( + (existingWorkflow) => existingWorkflow.name === benchmarkWorkflowName, + ); + } + + private getBenchmarkWorkflowName(workflow: Workflow) { + return `[BENCHMARK] ${workflow.name}`; + } +} diff --git a/packages/benchmark/src/testExecution/testScenarioRunner.ts b/packages/benchmark/src/testExecution/testScenarioRunner.ts new file mode 100644 index 0000000000000..2078864880f2f --- /dev/null +++ b/packages/benchmark/src/testExecution/testScenarioRunner.ts @@ -0,0 +1,50 @@ +import { TestScenario } from '@/types/testScenario'; +import { N8nApiClient } from '@/n8nApiClient/n8nApiClient'; +import { TestDataFileLoader } from '@/testScenario/testDataLoader'; +import { K6Executor } from './k6Executor'; +import { TestDataImporter } from '@/testExecution/testDataImporter'; +import { AuthenticatedN8nApiClient } from '@/n8nApiClient/authenticatedN8nApiClient'; + +/** + * Runs test scenarios + */ +export class TestScenarioRunner { + constructor( + private readonly n8nClient: N8nApiClient, + private readonly testDataLoader: TestDataFileLoader, + private readonly k6Executor: K6Executor, + private readonly ownerConfig: { + email: string; + password: string; + }, + ) {} + + async runManyTestScenarios(testCases: TestScenario[]) { + console.log(`Waiting for n8n ${this.n8nClient.apiBaseUrl} to become online`); + await this.n8nClient.waitForInstanceToBecomeOnline(); + + console.log('Setting up owner'); + await this.n8nClient.setupOwnerIfNeeded(this.ownerConfig); + + const authenticatedN8nClient = await AuthenticatedN8nApiClient.createUsingUsernameAndPassword( + this.n8nClient, + this.ownerConfig, + ); + const testDataImporter = new TestDataImporter(authenticatedN8nClient); + + for (const testCase of testCases) { + await this.runSingleTestScenario(testDataImporter, testCase); + } + } + + private async runSingleTestScenario(testDataImporter: TestDataImporter, scenario: TestScenario) { + console.log('Running scenario:', scenario.name); + + console.log('Loading and importing test data'); + const testData = await this.testDataLoader.loadTestDataForScenario(scenario); + await testDataImporter.importTestScenarioData(testData.workflows); + + console.log('Executing test script'); + await this.k6Executor.executeTestScenario(scenario); + } +} diff --git a/packages/benchmark/src/testScenario/testDataLoader.ts b/packages/benchmark/src/testScenario/testDataLoader.ts new file mode 100644 index 0000000000000..3badad5e0cd32 --- /dev/null +++ b/packages/benchmark/src/testScenario/testDataLoader.ts @@ -0,0 +1,31 @@ +import fs from 'fs'; +import path from 'path'; +import { TestScenario } from '@/types/testScenario'; +import { Workflow } from '@/n8nApiClient/n8nApiClient.types'; + +/** + * Loads test data files from FS + */ +export class TestDataFileLoader { + async loadTestDataForScenario(testScenario: TestScenario): Promise<{ + workflows: Workflow[]; + }> { + const workflows = await Promise.all( + testScenario.testData.workflowFiles?.map((workflowFilePath) => + this.loadSingleWorkflowFromFile(path.join(testScenario.testScenarioPath, workflowFilePath)), + ) ?? [], + ); + + return { + workflows, + }; + } + + private loadSingleWorkflowFromFile(workflowFilePath: string): Workflow { + const fileContent = fs.readFileSync(workflowFilePath, 'utf8'); + + const workflow = JSON.parse(fileContent); + + return workflow; + } +} diff --git a/packages/benchmark/src/testScenario/testScenarioLoader.ts b/packages/benchmark/src/testScenario/testScenarioLoader.ts new file mode 100644 index 0000000000000..b0a6d8ca5e6ba --- /dev/null +++ b/packages/benchmark/src/testScenario/testScenarioLoader.ts @@ -0,0 +1,67 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import { createHash } from 'node:crypto'; +import type { TestScenario, TestScenarioManifest } from '@/types/testScenario'; + +export class TestScenarioLoader { + /** + * Loads all test scenarios from the given path + */ + loadAllTestScenarios(testScenariosPath: string): TestScenario[] { + testScenariosPath = path.resolve(testScenariosPath); + const testScenarioFolders = fs + .readdirSync(testScenariosPath, { withFileTypes: true }) + .filter((dirent) => dirent.isDirectory()) + .map((dirent) => dirent.name); + + const scenarios: TestScenario[] = []; + + for (const folder of testScenarioFolders) { + const scenarioPath = path.join(testScenariosPath, folder); + const manifestFileName = `${folder}.manifest.json`; + const scenarioManifestPath = path.join(testScenariosPath, folder, manifestFileName); + if (!fs.existsSync(scenarioManifestPath)) { + console.warn(`Test scenario at ${scenarioPath} is missing the ${manifestFileName} file`); + continue; + } + + // Load the scenario manifest file + const [testCase, validationErrors] = + this.loadAndValidateTestScenarioManifest(scenarioManifestPath); + if (validationErrors) { + console.warn( + `Test case at ${scenarioPath} has the following validation errors: ${validationErrors.join(', ')}`, + ); + continue; + } + + scenarios.push({ + ...testCase, + id: this.formTestScenarioId(scenarioPath), + testScenarioPath: scenarioPath, + }); + } + + return scenarios; + } + + private loadAndValidateTestScenarioManifest( + testScenarioManifestPath: string, + ): [TestScenarioManifest, null] | [null, string[]] { + const testCase = JSON.parse(fs.readFileSync(testScenarioManifestPath, 'utf8')); + const validationErrors: string[] = []; + + if (!testCase.name) { + validationErrors.push(`Test case at ${testScenarioManifestPath} is missing a name`); + } + if (!testCase.description) { + validationErrors.push(`Test case at ${testScenarioManifestPath} is missing a description`); + } + + return validationErrors.length === 0 ? [testCase, null] : [null, validationErrors]; + } + + private formTestScenarioId(testCasePath: string): string { + return createHash('sha256').update(testCasePath).digest('hex'); + } +} diff --git a/packages/benchmark/src/types/testScenario.ts b/packages/benchmark/src/types/testScenario.ts new file mode 100644 index 0000000000000..7946301bd12f5 --- /dev/null +++ b/packages/benchmark/src/types/testScenario.ts @@ -0,0 +1,26 @@ +export type TestData = { + /** Relative paths to the workflow files */ + workflowFiles?: string[]; +}; + +/** + * Configuration that defines the test scenario + */ +export type TestScenarioManifest = { + /** The name of the test scenario */ + name: string; + /** A longer description of the test scenario */ + description: string; + /** Relative path to the k6 test script */ + testScriptPath: string; + /** Test data to import before running the scenario */ + testData: TestData; +}; + +/** + * A test scenario with additional metadata + */ +export type TestScenario = TestScenarioManifest & { + id: string; + testScenarioPath: string; +}; diff --git a/packages/benchmark/testScenarios/codeNode/codeNode.json b/packages/benchmark/testScenarios/codeNode/codeNode.json new file mode 100644 index 0000000000000..6831d22068493 --- /dev/null +++ b/packages/benchmark/testScenarios/codeNode/codeNode.json @@ -0,0 +1,36 @@ +{ + "createdAt": "2024-08-06T13:37:07.119Z", + "updatedAt": "2024-08-06T13:39:27.000Z", + "name": "Code Node", + "active": true, + "nodes": [ + { + "parameters": { + "mode": "runOnceForEachItem", + "jsCode": "$input.item.json.myNewField = Math.random();\n\nreturn $input.item;" + }, + "id": "7d100719-8d9e-497d-a365-78c793c244d6", + "name": "Code", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [980, 400] + }, + { + "parameters": { "path": "code-node", "responseMode": "lastNode", "options": {} }, + "id": "1e172d8f-f6cd-43ec-8257-e6a8212add00", + "name": "Webhook", + "type": "n8n-nodes-base.webhook", + "typeVersion": 2, + "position": [760, 400], + "webhookId": "224a56e0-f929-4f01-b49e-d91861b1b8d7" + } + ], + "connections": { "Webhook": { "main": [[{ "node": "Code", "type": "main", "index": 0 }]] } }, + "settings": { "executionOrder": "v1" }, + "staticData": null, + "meta": { "templateCredsSetupCompleted": true }, + "pinData": {}, + "versionId": "a91dfe67-971d-425f-9066-feab46150f95", + "triggerCount": 1, + "tags": [] +} diff --git a/packages/benchmark/testScenarios/codeNode/codeNode.manifest.json b/packages/benchmark/testScenarios/codeNode/codeNode.manifest.json new file mode 100644 index 0000000000000..a44b49af8f1d7 --- /dev/null +++ b/packages/benchmark/testScenarios/codeNode/codeNode.manifest.json @@ -0,0 +1,9 @@ +{ + "$schema": "../testScenario.schema.json", + "name": "CodeNode", + "description": "Workflow with a single code node that is triggered using a webhook", + "testData": { + "workflowFiles": ["codeNode.json"] + }, + "testScriptPath": "codeNode.script.ts" +} diff --git a/packages/benchmark/testScenarios/codeNode/codeNode.script.ts b/packages/benchmark/testScenarios/codeNode/codeNode.script.ts new file mode 100644 index 0000000000000..b9ec9b345ec98 --- /dev/null +++ b/packages/benchmark/testScenarios/codeNode/codeNode.script.ts @@ -0,0 +1,11 @@ +import http from 'k6/http'; +import { check } from 'k6'; + +const apiBaseUrl = __ENV.API_BASE_URL; + +export default function () { + const res = http.get(`${apiBaseUrl}/webhook/code-node`); + check(res, { + 'is status 200': (r) => r.status === 200, + }); +} diff --git a/packages/benchmark/testScenarios/singleWebhook/singleWebhook.json b/packages/benchmark/testScenarios/singleWebhook/singleWebhook.json new file mode 100644 index 0000000000000..cba1aa5832f70 --- /dev/null +++ b/packages/benchmark/testScenarios/singleWebhook/singleWebhook.json @@ -0,0 +1,25 @@ +{ + "createdAt": "2024-08-06T12:19:51.268Z", + "updatedAt": "2024-08-06T12:20:45.000Z", + "name": "Single Webhook", + "active": true, + "nodes": [ + { + "parameters": { "path": "single-webhook", "options": {} }, + "id": "7587ab0e-cc15-424f-83c0-c887a0eb97fb", + "name": "Webhook", + "type": "n8n-nodes-base.webhook", + "typeVersion": 2, + "position": [760, 400], + "webhookId": "fa563fc2-c73f-4631-99a1-39c16f1f858f" + } + ], + "connections": {}, + "settings": { "executionOrder": "v1" }, + "staticData": null, + "meta": { "templateCredsSetupCompleted": true, "responseMode": "lastNode", "options": {} }, + "pinData": {}, + "versionId": "840a38a1-ba37-433d-9f20-de73f5131a2b", + "triggerCount": 1, + "tags": [] +} diff --git a/packages/benchmark/testScenarios/singleWebhook/singleWebhook.manifest.json b/packages/benchmark/testScenarios/singleWebhook/singleWebhook.manifest.json new file mode 100644 index 0000000000000..9314cf510a173 --- /dev/null +++ b/packages/benchmark/testScenarios/singleWebhook/singleWebhook.manifest.json @@ -0,0 +1,6 @@ +{ + "name": "SingleWebhook", + "description": "A single webhook trigger that responds with a 200 status code", + "testData": { "workflowFiles": ["singleWebhook.json"] }, + "testScriptPath": "singleWebhook.script.ts" +} diff --git a/packages/benchmark/testScenarios/singleWebhook/singleWebhook.script.ts b/packages/benchmark/testScenarios/singleWebhook/singleWebhook.script.ts new file mode 100644 index 0000000000000..72e2563cbea9f --- /dev/null +++ b/packages/benchmark/testScenarios/singleWebhook/singleWebhook.script.ts @@ -0,0 +1,11 @@ +import http from 'k6/http'; +import { check } from 'k6'; + +const apiBaseUrl = __ENV.API_BASE_URL; + +export default function () { + const res = http.get(`${apiBaseUrl}/webhook/single-webhook`); + check(res, { + 'is status 200': (r) => r.status === 200, + }); +} diff --git a/packages/benchmark/testScenarios/testScenario.schema.json b/packages/benchmark/testScenarios/testScenario.schema.json new file mode 100644 index 0000000000000..da02b9d35ca3f --- /dev/null +++ b/packages/benchmark/testScenarios/testScenario.schema.json @@ -0,0 +1,42 @@ +{ + "definitions": { + "TestData": { + "type": "object", + "properties": { + "workflowFiles": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [], + "additionalProperties": false + } + }, + "type": "object", + "properties": { + "$schema": { + "type": "string", + "description": "The JSON schema to validate this file" + }, + "name": { + "type": "string", + "description": "The name of the test scenario" + }, + "description": { + "type": "string", + "description": "A longer description of the test scenario" + }, + "testScriptPath": { + "type": "string", + "description": "Relative path to the k6 test script" + }, + "testData": { + "$ref": "#/definitions/TestData", + "description": "Test data to import before running the scenario" + } + }, + "required": ["name", "description", "testScriptPath", "testData"], + "additionalProperties": false +} diff --git a/packages/benchmark/tsconfig.build.json b/packages/benchmark/tsconfig.build.json new file mode 100644 index 0000000000000..cb80f3c918e4d --- /dev/null +++ b/packages/benchmark/tsconfig.build.json @@ -0,0 +1,9 @@ +{ + "extends": ["./tsconfig.json", "../../tsconfig.build.json"], + "compilerOptions": { + "rootDir": "src", + "outDir": "dist", + "tsBuildInfoFile": "dist/build.tsbuildinfo" + }, + "include": ["src/**/*.ts"] +} diff --git a/packages/benchmark/tsconfig.json b/packages/benchmark/tsconfig.json new file mode 100644 index 0000000000000..d2e1fe362751a --- /dev/null +++ b/packages/benchmark/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": ["../../tsconfig.json", "../../tsconfig.backend.json"], + "compilerOptions": { + "rootDir": ".", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "baseUrl": "src", + "paths": { + "@/*": ["./*"] + } + }, + "include": ["src/**/*.ts"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e3b834bfa4025..931037a3f2717 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -123,7 +123,7 @@ importers: version: 6.0.2 jest: specifier: ^29.6.2 - version: 29.6.2(@types/node@18.16.16) + version: 29.6.2(@types/node@18.16.16)(ts-node@10.9.2(@types/node@18.16.16)(typescript@5.5.2)) jest-environment-jsdom: specifier: ^29.6.2 version: 29.6.2 @@ -135,7 +135,7 @@ importers: version: 29.6.2 jest-mock-extended: specifier: ^3.0.4 - version: 3.0.4(jest@29.6.2(@types/node@18.16.16))(typescript@5.5.2) + version: 3.0.4(jest@29.6.2(@types/node@18.16.16)(ts-node@10.9.2(@types/node@18.16.16)(typescript@5.5.2)))(typescript@5.5.2) nock: specifier: ^13.3.2 version: 13.3.2 @@ -156,7 +156,7 @@ importers: version: 7.0.0 ts-jest: specifier: ^29.1.1 - version: 29.1.1(@babel/core@7.24.0)(@jest/types@29.6.1)(babel-jest@29.6.2(@babel/core@7.24.0))(jest@29.6.2(@types/node@18.16.16))(typescript@5.5.2) + version: 29.1.1(@babel/core@7.24.0)(@jest/types@29.6.1)(babel-jest@29.6.2(@babel/core@7.24.0))(jest@29.6.2(@types/node@18.16.16)(ts-node@10.9.2(@types/node@18.16.16)(typescript@5.5.2)))(typescript@5.5.2) tsc-alias: specifier: ^1.8.7 version: 1.8.7 @@ -388,7 +388,7 @@ importers: version: 0.5.0 '@n8n/typeorm': specifier: 0.3.20-10 - version: 0.3.20-10(@sentry/node@7.87.0)(ioredis@5.3.2)(mssql@10.0.2)(mysql2@3.11.0)(pg@8.12.0)(redis@4.6.12)(sqlite3@5.1.7) + version: 0.3.20-10(@sentry/node@7.87.0)(ioredis@5.3.2)(mssql@10.0.2)(mysql2@3.11.0)(pg@8.12.0)(redis@4.6.12)(sqlite3@5.1.7)(ts-node@10.9.2(@types/node@18.16.16)(typescript@5.5.2)) '@n8n/vm2': specifier: 3.9.25 version: 3.9.25 @@ -520,7 +520,7 @@ importers: version: 8.1.4(@types/react@18.0.27)(encoding@0.1.13)(prettier@3.2.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@storybook/addon-interactions': specifier: ^8.1.4 - version: 8.1.4(@jest/globals@29.6.2)(@types/jest@29.5.3)(jest@29.6.2(@types/node@18.16.16))(vitest@1.6.0(@types/node@18.16.16)(sass@1.64.1)(terser@5.16.1)) + version: 8.1.4(@jest/globals@29.6.2)(@types/jest@29.5.3)(jest@29.6.2(@types/node@18.16.16)(ts-node@10.9.2(@types/node@18.16.16)(typescript@5.5.2)))(vitest@1.6.0(@types/node@18.16.16)(sass@1.64.1)(terser@5.16.1)) '@storybook/addon-links': specifier: ^8.1.4 version: 8.1.4(react@18.2.0) @@ -532,7 +532,7 @@ importers: version: 8.1.4(@types/react@18.0.27)(encoding@0.1.13)(prettier@3.2.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@storybook/test': specifier: ^8.1.4 - version: 8.1.4(@jest/globals@29.6.2)(@types/jest@29.5.3)(jest@29.6.2(@types/node@18.16.16))(vitest@1.6.0(@types/node@18.16.16)(sass@1.64.1)(terser@5.16.1)) + version: 8.1.4(@jest/globals@29.6.2)(@types/jest@29.5.3)(jest@29.6.2(@types/node@18.16.16)(ts-node@10.9.2(@types/node@18.16.16)(typescript@5.5.2)))(vitest@1.6.0(@types/node@18.16.16)(sass@1.64.1)(terser@5.16.1)) '@storybook/vue3': specifier: ^8.1.4 version: 8.1.4(encoding@0.1.13)(prettier@3.2.5)(vue@3.4.21(typescript@5.5.2)) @@ -600,6 +600,40 @@ importers: specifier: ^9.4.2 version: 9.4.2(eslint@8.57.0) + packages/benchmark: + dependencies: + '@oclif/core': + specifier: 4.0.7 + version: 4.0.7 + axios: + specifier: 1.7.3 + version: 1.7.3(debug@3.2.7) + convict: + specifier: 6.2.4 + version: 6.2.4 + dotenv: + specifier: 8.6.0 + version: 8.6.0 + zx: + specifier: ^8.1.4 + version: 8.1.4 + devDependencies: + '@types/convict': + specifier: ^6.1.1 + version: 6.1.1 + '@types/k6': + specifier: ^0.52.0 + version: 0.52.0 + '@types/node': + specifier: ^18.16.16 + version: 18.16.16 + tsc-alias: + specifier: ^1.8.7 + version: 1.8.7 + typescript: + specifier: ^5.5.2 + version: 5.5.2 + packages/cli: dependencies: '@azure/identity': @@ -628,7 +662,7 @@ importers: version: link:../@n8n/permissions '@n8n/typeorm': specifier: 0.3.20-10 - version: 0.3.20-10(@sentry/node@7.87.0)(ioredis@5.3.2)(mssql@10.0.2)(mysql2@3.11.0)(pg@8.12.0)(redis@4.6.14)(sqlite3@5.1.7) + version: 0.3.20-10(@sentry/node@7.87.0)(ioredis@5.3.2)(mssql@10.0.2)(mysql2@3.11.0)(pg@8.12.0)(redis@4.6.14)(sqlite3@5.1.7)(ts-node@10.9.2(@types/node@18.16.16)(typescript@5.5.2)) '@n8n_io/ai-assistant-sdk': specifier: 1.9.4 version: 1.9.4 @@ -1102,7 +1136,7 @@ importers: version: link:../@n8n/storybook '@testing-library/jest-dom': specifier: ^6.1.5 - version: 6.1.5(@jest/globals@29.6.2)(@types/jest@29.5.3)(jest@29.6.2(@types/node@18.16.16))(vitest@1.6.0(@types/node@18.16.16)(jsdom@23.0.1)(sass@1.64.1)(terser@5.16.1)) + version: 6.1.5(@jest/globals@29.6.2)(@types/jest@29.5.3)(jest@29.6.2(@types/node@18.16.16)(ts-node@10.9.2(@types/node@18.16.16)(typescript@5.5.2)))(vitest@1.6.0(@types/node@18.16.16)(jsdom@23.0.1)(sass@1.64.1)(terser@5.16.1)) '@testing-library/user-event': specifier: ^14.5.1 version: 14.5.1(@testing-library/dom@9.3.4) @@ -1141,7 +1175,7 @@ importers: version: 1.64.1 tailwindcss: specifier: ^3.4.3 - version: 3.4.3 + version: 3.4.3(ts-node@10.9.2(@types/node@18.16.16)(typescript@5.5.2)) unplugin-icons: specifier: ^0.19.0 version: 0.19.0(@vue/compiler-sfc@3.4.21)(vue-template-compiler@2.7.14) @@ -3079,6 +3113,10 @@ packages: resolution: {integrity: sha512-dkk7FX8L/JLia5pi+IQ11lCw2D6FTmbWL2iWTHgCbP40/deeXgknlkEQcQ/rOkjwQbqp8RZ4ey/anR17K66sqw==} engines: {node: '>=16'} + '@cspotcode/source-map-support@0.8.1': + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + '@ctrl/tinycolor@3.6.0': resolution: {integrity: sha512-/Z3l6pXthq0JvMYdUFyX9j0MaCltlIn6mfh9jLyQwg5aPKxkyNa0PTHtU1AlFXLNk55ZuAeJRcpvq+tmLfKmaQ==} engines: {node: '>=10'} @@ -3578,6 +3616,9 @@ packages: '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + '@jridgewell/trace-mapping@0.3.9': + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + '@js-joda/core@5.6.1': resolution: {integrity: sha512-Xla/d7ZMMR6+zRd6lTio0wRZECfcfFJP7GGe9A9L4tDOlD5CX4YcZ4YZle9w58bBYzssojVapI84RraKWDQZRg==} @@ -5259,6 +5300,18 @@ packages: resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} engines: {node: '>= 10'} + '@tsconfig/node10@1.0.11': + resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==} + + '@tsconfig/node12@1.0.11': + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + + '@tsconfig/node14@1.0.3': + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + + '@tsconfig/node16@1.0.4': + resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + '@types/amqplib@0.10.1': resolution: {integrity: sha512-j6ANKT79ncUDnAs/+9r9eDujxbeJoTjoVu33gHHcaPfmLQaMhvfbH2GqSe8KUM444epAp1Vl3peVOQfZk3UIqA==} @@ -5376,6 +5429,9 @@ packages: '@types/formidable@3.4.5': resolution: {integrity: sha512-s7YPsNVfnsng5L8sKnG/Gbb2tiwwJTY1conOkJzTMRvJAlLFW1nEua+ADsJQu8N1c0oTHx9+d5nqg10WuT9gHQ==} + '@types/fs-extra@11.0.4': + resolution: {integrity: sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==} + '@types/ftp@0.3.33': resolution: {integrity: sha512-L7wFlX3t9GsGgNS0oxLt6zbAZZGgsdptMmciL4cdxHmbL3Hz4Lysh8YqAR34eHsJ1uacJITcZBBDl5XpQlxPpQ==} @@ -5433,12 +5489,18 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/jsonfile@6.1.4': + resolution: {integrity: sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==} + '@types/jsonpath@0.2.0': resolution: {integrity: sha512-v7qlPA0VpKUlEdhghbDqRoKMxFB3h3Ch688TApBJ6v+XLDdvWCGLJIYiPKGZnS6MAOie+IorCfNYVHOPIHSWwQ==} '@types/jsonwebtoken@9.0.6': resolution: {integrity: sha512-/5hndP5dCjloafCXns6SZyESp3Ldq7YjH3zwzwczYnjxIT0Fqzk5ROSYVGfFyczIue7IUEj8hkvLbPoLQ18vQw==} + '@types/k6@0.52.0': + resolution: {integrity: sha512-yaw2wg61nKQtToDML+nngzgXVjZ6wNA4R0Q3jKDTeadG5EqfZgis5a1Q2hwY7kjuGuXmu8eM6gHg3tgnOj4vNw==} + '@types/linkify-it@3.0.5': resolution: {integrity: sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw==} @@ -6145,6 +6207,9 @@ packages: engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} deprecated: This package is no longer supported. + arg@4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + arg@5.0.2: resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} @@ -6709,10 +6774,6 @@ packages: resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} engines: {node: '>=8'} - cli-spinners@2.9.0: - resolution: {integrity: sha512-4/aL9X3Wh0yiMQlE+eeRhWP6vclO3QRtw1JHKIT0FFUs5FjpFmESqtMvYZ0+lbzBw900b95mS0hohy+qn2VK/g==} - engines: {node: '>=6'} - cli-spinners@2.9.2: resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} engines: {node: '>=6'} @@ -6952,6 +7013,9 @@ packages: resolution: {integrity: sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==} engines: {node: '>=10.0.0'} + create-require@1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + crelt@1.0.5: resolution: {integrity: sha512-+BO9wPPi+DWTDcNYhr/W90myha8ptzftZT+LwcmUbbok0rcP/fequmFYCw8NMoH7pkAZQzU78b3kYrlua5a9eA==} @@ -7324,6 +7388,10 @@ packages: resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + diff@4.0.2: + resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + engines: {node: '>=0.3.1'} + diff@5.2.0: resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==} engines: {node: '>=0.3.1'} @@ -8370,6 +8438,7 @@ packages: google-p12-pem@4.0.1: resolution: {integrity: sha512-WPkN4yGtz05WZ5EhtlxNDWPhC4JIic6G8ePitwUWy4l+XPVYec+a0j0Ts47PDtW59y3RwAhUd9/h9ZZ63px6RQ==} engines: {node: '>=12.0.0'} + deprecated: Package is no longer maintained hasBin: true gopd@1.0.1: @@ -9607,10 +9676,6 @@ packages: resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} engines: {node: '>=10'} - lilconfig@3.0.0: - resolution: {integrity: sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==} - engines: {node: '>=14'} - lilconfig@3.1.2: resolution: {integrity: sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==} engines: {node: '>=14'} @@ -9983,10 +10048,6 @@ packages: resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} engines: {node: '>=16 || 14 >=14.17'} - minimatch@9.0.4: - resolution: {integrity: sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==} - engines: {node: '>=16 || 14 >=14.17'} - minimatch@9.0.5: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} @@ -12355,6 +12416,20 @@ packages: ts-map@1.0.3: resolution: {integrity: sha512-vDWbsl26LIcPGmDpoVzjEP6+hvHZkBkLW7JpvwbCv/5IYPJlsbzCVXY3wsCeAxAUeTclNOUZxnLdGh3VBD/J6w==} + ts-node@10.9.2: + resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': ^18.16.16 + typescript: ^5.5.2 + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + ts-toolbelt@9.6.0: resolution: {integrity: sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w==} @@ -12759,6 +12834,9 @@ packages: v3-infinite-loading@1.2.2: resolution: {integrity: sha512-MWJc6yChnqeUasBFJ3Enu8IGPcQgRMSTrAEtT1MsHBEx+QjwvNTaY8o+8V9DgVt1MVhQSl3MC55hsaWLJmpRMw==} + v8-compile-cache-lib@3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + v8-to-istanbul@9.1.0: resolution: {integrity: sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==} engines: {node: '>=10.12.0'} @@ -13281,6 +13359,10 @@ packages: yauzl@2.10.0: resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} + yn@3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} @@ -13309,6 +13391,11 @@ packages: zod@3.23.8: resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} + zx@8.1.4: + resolution: {integrity: sha512-QFDYYpnzdpRiJ3dL2102Cw26FpXpWshW4QLTGxiYfIcwdAqg084jRCkK/kuP/NOSkxOjydRwNFG81qzA5r1a6w==} + engines: {node: '>= 12.17.0'} + hasBin: true + snapshots: '@aashutoshrathi/word-wrap@1.2.6': {} @@ -13316,7 +13403,7 @@ snapshots: '@acuminous/bitsyntax@0.1.2': dependencies: buffer-more-ints: 1.0.0 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.6(supports-color@8.1.1) safe-buffer: 5.1.2 transitivePeerDependencies: - supports-color @@ -14433,7 +14520,7 @@ snapshots: '@babel/traverse': 7.24.0 '@babel/types': 7.24.6 convert-source-map: 2.0.0 - debug: 4.3.6 + debug: 4.3.6(supports-color@8.1.1) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 7.6.0 @@ -14453,7 +14540,7 @@ snapshots: '@babel/traverse': 7.24.6 '@babel/types': 7.24.6 convert-source-map: 2.0.0 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5 gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 7.6.0 @@ -14554,7 +14641,7 @@ snapshots: '@babel/core': 7.24.6 '@babel/helper-compilation-targets': 7.24.6 '@babel/helper-plugin-utils': 7.24.6 - debug: 4.3.6 + debug: 4.3.6(supports-color@8.1.1) lodash.debounce: 4.0.8 resolve: 1.22.8 transitivePeerDependencies: @@ -14565,7 +14652,7 @@ snapshots: '@babel/core': 7.24.6 '@babel/helper-compilation-targets': 7.24.6 '@babel/helper-plugin-utils': 7.24.6 - debug: 4.3.6 + debug: 4.3.6(supports-color@8.1.1) lodash.debounce: 4.0.8 resolve: 1.22.8 transitivePeerDependencies: @@ -15449,7 +15536,7 @@ snapshots: '@babel/helper-split-export-declaration': 7.22.6 '@babel/parser': 7.24.6 '@babel/types': 7.24.6 - debug: 4.3.6 + debug: 4.3.6(supports-color@8.1.1) globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -15464,7 +15551,7 @@ snapshots: '@babel/helper-split-export-declaration': 7.24.6 '@babel/parser': 7.24.6 '@babel/types': 7.24.6 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.6(supports-color@8.1.1) globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -15601,6 +15688,11 @@ snapshots: '@common.js/is-network-error@1.0.1': {} + '@cspotcode/source-map-support@0.8.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + optional: true + '@ctrl/tinycolor@3.6.0': {} '@curlconverter/yargs-parser@0.0.1': {} @@ -15746,7 +15838,7 @@ snapshots: '@eslint/eslintrc@2.1.4': dependencies: ajv: 6.12.6 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.6(supports-color@8.1.1) espree: 9.6.1 globals: 13.20.0 ignore: 5.2.4 @@ -15906,7 +15998,7 @@ snapshots: '@humanwhocodes/config-array@0.11.14': dependencies: '@humanwhocodes/object-schema': 2.0.2 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.6(supports-color@8.1.1) minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -15936,7 +16028,7 @@ snapshots: '@antfu/install-pkg': 0.1.1 '@antfu/utils': 0.7.10 '@iconify/types': 2.0.0 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.6(supports-color@8.1.1) kolorist: 1.8.0 local-pkg: 0.5.0 mlly: 1.7.1 @@ -15998,7 +16090,7 @@ snapshots: jest-util: 29.6.2 slash: 3.0.0 - '@jest/core@29.6.2': + '@jest/core@29.6.2(ts-node@10.9.2(@types/node@18.16.16)(typescript@5.5.2))': dependencies: '@jest/console': 29.6.2 '@jest/reporters': 29.6.2 @@ -16012,7 +16104,7 @@ snapshots: exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.5.0 - jest-config: 29.6.2(@types/node@18.16.16) + jest-config: 29.6.2(@types/node@18.16.16)(ts-node@10.9.2(@types/node@18.16.16)(typescript@5.5.2)) jest-haste-map: 29.6.2 jest-message-util: 29.6.2 jest-regex-util: 29.4.3 @@ -16190,6 +16282,12 @@ snapshots: '@jridgewell/resolve-uri': 3.1.0 '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/trace-mapping@0.3.9': + dependencies: + '@jridgewell/resolve-uri': 3.1.0 + '@jridgewell/sourcemap-codec': 1.4.15 + optional: true + '@js-joda/core@5.6.1': {} '@js-sdsl/ordered-map@4.4.2': {} @@ -16230,7 +16328,7 @@ snapshots: '@kwsites/file-exists@1.1.1': dependencies: - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.6(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -16641,7 +16739,7 @@ snapshots: '@n8n/localtunnel@3.0.0': dependencies: axios: 1.7.3(debug@4.3.6) - debug: 4.3.6 + debug: 4.3.6(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -16658,7 +16756,7 @@ snapshots: esprima-next: 5.8.4 recast: 0.22.0 - '@n8n/typeorm@0.3.20-10(@sentry/node@7.87.0)(ioredis@5.3.2)(mssql@10.0.2)(mysql2@3.11.0)(pg@8.12.0)(redis@4.6.12)(sqlite3@5.1.7)': + '@n8n/typeorm@0.3.20-10(@sentry/node@7.87.0)(ioredis@5.3.2)(mssql@10.0.2)(mysql2@3.11.0)(pg@8.12.0)(redis@4.6.12)(sqlite3@5.1.7)(ts-node@10.9.2(@types/node@18.16.16)(typescript@5.5.2))': dependencies: '@n8n/p-retry': 6.2.0-2 '@sqltools/formatter': 1.2.5 @@ -16667,7 +16765,7 @@ snapshots: buffer: 6.0.3 chalk: 4.1.2 dayjs: 1.11.10 - debug: 4.3.6 + debug: 4.3.6(supports-color@8.1.1) dotenv: 16.3.1 glob: 10.3.10 mkdirp: 2.1.3 @@ -16685,10 +16783,11 @@ snapshots: pg: 8.12.0 redis: 4.6.12 sqlite3: 5.1.7 + ts-node: 10.9.2(@types/node@18.16.16)(typescript@5.5.2) transitivePeerDependencies: - supports-color - '@n8n/typeorm@0.3.20-10(@sentry/node@7.87.0)(ioredis@5.3.2)(mssql@10.0.2)(mysql2@3.11.0)(pg@8.12.0)(redis@4.6.14)(sqlite3@5.1.7)': + '@n8n/typeorm@0.3.20-10(@sentry/node@7.87.0)(ioredis@5.3.2)(mssql@10.0.2)(mysql2@3.11.0)(pg@8.12.0)(redis@4.6.14)(sqlite3@5.1.7)(ts-node@10.9.2(@types/node@18.16.16)(typescript@5.5.2))': dependencies: '@n8n/p-retry': 6.2.0-2 '@sqltools/formatter': 1.2.5 @@ -16697,7 +16796,7 @@ snapshots: buffer: 6.0.3 chalk: 4.1.2 dayjs: 1.11.10 - debug: 4.3.6 + debug: 4.3.6(supports-color@8.1.1) dotenv: 16.3.1 glob: 10.3.10 mkdirp: 2.1.3 @@ -16715,6 +16814,7 @@ snapshots: pg: 8.12.0 redis: 4.6.14 sqlite3: 5.1.7 + ts-node: 10.9.2(@types/node@18.16.16)(typescript@5.5.2) transitivePeerDependencies: - supports-color @@ -16783,14 +16883,14 @@ snapshots: ansis: 3.2.0 clean-stack: 3.0.1 cli-spinners: 2.9.2 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.6(supports-color@8.1.1) ejs: 3.1.10 get-package-type: 0.1.0 globby: 11.1.0 indent-string: 4.0.0 is-wsl: 2.2.0 lilconfig: 3.1.2 - minimatch: 9.0.4 + minimatch: 9.0.5 string-width: 4.2.3 supports-color: 8.1.1 widest-line: 3.1.0 @@ -17775,11 +17875,11 @@ snapshots: dependencies: '@storybook/global': 5.0.0 - '@storybook/addon-interactions@8.1.4(@jest/globals@29.6.2)(@types/jest@29.5.3)(jest@29.6.2(@types/node@18.16.16))(vitest@1.6.0(@types/node@18.16.16)(sass@1.64.1)(terser@5.16.1))': + '@storybook/addon-interactions@8.1.4(@jest/globals@29.6.2)(@types/jest@29.5.3)(jest@29.6.2(@types/node@18.16.16)(ts-node@10.9.2(@types/node@18.16.16)(typescript@5.5.2)))(vitest@1.6.0(@types/node@18.16.16)(sass@1.64.1)(terser@5.16.1))': dependencies: '@storybook/global': 5.0.0 '@storybook/instrumenter': 8.1.4 - '@storybook/test': 8.1.4(@jest/globals@29.6.2)(@types/jest@29.5.3)(jest@29.6.2(@types/node@18.16.16))(vitest@1.6.0(@types/node@18.16.16)(sass@1.64.1)(terser@5.16.1)) + '@storybook/test': 8.1.4(@jest/globals@29.6.2)(@types/jest@29.5.3)(jest@29.6.2(@types/node@18.16.16)(ts-node@10.9.2(@types/node@18.16.16)(typescript@5.5.2)))(vitest@1.6.0(@types/node@18.16.16)(sass@1.64.1)(terser@5.16.1)) '@storybook/types': 8.1.4 polished: 4.2.2 ts-dedent: 2.2.0 @@ -18227,14 +18327,14 @@ snapshots: - prettier - supports-color - '@storybook/test@8.1.4(@jest/globals@29.6.2)(@types/jest@29.5.3)(jest@29.6.2(@types/node@18.16.16))(vitest@1.6.0(@types/node@18.16.16)(sass@1.64.1)(terser@5.16.1))': + '@storybook/test@8.1.4(@jest/globals@29.6.2)(@types/jest@29.5.3)(jest@29.6.2(@types/node@18.16.16)(ts-node@10.9.2(@types/node@18.16.16)(typescript@5.5.2)))(vitest@1.6.0(@types/node@18.16.16)(sass@1.64.1)(terser@5.16.1))': dependencies: '@storybook/client-logger': 8.1.4 '@storybook/core-events': 8.1.4 '@storybook/instrumenter': 8.1.4 '@storybook/preview-api': 8.1.4 '@testing-library/dom': 9.3.4 - '@testing-library/jest-dom': 6.4.2(@jest/globals@29.6.2)(@types/jest@29.5.3)(jest@29.6.2(@types/node@18.16.16))(vitest@1.6.0(@types/node@18.16.16)(sass@1.64.1)(terser@5.16.1)) + '@testing-library/jest-dom': 6.4.2(@jest/globals@29.6.2)(@types/jest@29.5.3)(jest@29.6.2(@types/node@18.16.16)(ts-node@10.9.2(@types/node@18.16.16)(typescript@5.5.2)))(vitest@1.6.0(@types/node@18.16.16)(sass@1.64.1)(terser@5.16.1)) '@testing-library/user-event': 14.5.2(@testing-library/dom@9.3.4) '@vitest/expect': 1.3.1 '@vitest/spy': 1.3.1 @@ -18381,7 +18481,7 @@ snapshots: lz-string: 1.5.0 pretty-format: 27.5.1 - '@testing-library/jest-dom@6.1.5(@jest/globals@29.6.2)(@types/jest@29.5.3)(jest@29.6.2(@types/node@18.16.16))(vitest@1.6.0(@types/node@18.16.16)(jsdom@23.0.1)(sass@1.64.1)(terser@5.16.1))': + '@testing-library/jest-dom@6.1.5(@jest/globals@29.6.2)(@types/jest@29.5.3)(jest@29.6.2(@types/node@18.16.16)(ts-node@10.9.2(@types/node@18.16.16)(typescript@5.5.2)))(vitest@1.6.0(@types/node@18.16.16)(jsdom@23.0.1)(sass@1.64.1)(terser@5.16.1))': dependencies: '@adobe/css-tools': 4.3.2 '@babel/runtime': 7.22.6 @@ -18394,10 +18494,10 @@ snapshots: optionalDependencies: '@jest/globals': 29.6.2 '@types/jest': 29.5.3 - jest: 29.6.2(@types/node@18.16.16) + jest: 29.6.2(@types/node@18.16.16)(ts-node@10.9.2(@types/node@18.16.16)(typescript@5.5.2)) vitest: 1.6.0(@types/node@18.16.16)(jsdom@23.0.1)(sass@1.64.1)(terser@5.16.1) - '@testing-library/jest-dom@6.4.2(@jest/globals@29.6.2)(@types/jest@29.5.3)(jest@29.6.2(@types/node@18.16.16))(vitest@1.6.0(@types/node@18.16.16)(sass@1.64.1)(terser@5.16.1))': + '@testing-library/jest-dom@6.4.2(@jest/globals@29.6.2)(@types/jest@29.5.3)(jest@29.6.2(@types/node@18.16.16)(ts-node@10.9.2(@types/node@18.16.16)(typescript@5.5.2)))(vitest@1.6.0(@types/node@18.16.16)(sass@1.64.1)(terser@5.16.1))': dependencies: '@adobe/css-tools': 4.3.2 '@babel/runtime': 7.23.6 @@ -18410,7 +18510,7 @@ snapshots: optionalDependencies: '@jest/globals': 29.6.2 '@types/jest': 29.5.3 - jest: 29.6.2(@types/node@18.16.16) + jest: 29.6.2(@types/node@18.16.16)(ts-node@10.9.2(@types/node@18.16.16)(typescript@5.5.2)) vitest: 1.6.0(@types/node@18.16.16)(jsdom@23.0.1)(sass@1.64.1)(terser@5.16.1) '@testing-library/user-event@14.5.1(@testing-library/dom@9.3.4)': @@ -18438,6 +18538,18 @@ snapshots: '@tootallnate/once@2.0.0': {} + '@tsconfig/node10@1.0.11': + optional: true + + '@tsconfig/node12@1.0.11': + optional: true + + '@tsconfig/node14@1.0.3': + optional: true + + '@tsconfig/node16@1.0.4': + optional: true + '@types/amqplib@0.10.1': dependencies: '@types/node': 18.16.16 @@ -18569,6 +18681,12 @@ snapshots: dependencies: '@types/node': 18.16.16 + '@types/fs-extra@11.0.4': + dependencies: + '@types/jsonfile': 6.1.4 + '@types/node': 18.16.16 + optional: true + '@types/ftp@0.3.33': dependencies: '@types/node': 18.16.16 @@ -18638,12 +18756,19 @@ snapshots: '@types/json-schema@7.0.15': {} + '@types/jsonfile@6.1.4': + dependencies: + '@types/node': 18.16.16 + optional: true + '@types/jsonpath@0.2.0': {} '@types/jsonwebtoken@9.0.6': dependencies: '@types/node': 18.16.16 + '@types/k6@0.52.0': {} + '@types/linkify-it@3.0.5': {} '@types/lodash-es@4.17.6': @@ -18959,7 +19084,7 @@ snapshots: dependencies: '@typescript-eslint/typescript-estree': 7.2.0(typescript@5.5.2) '@typescript-eslint/utils': 7.2.0(eslint@8.57.0)(typescript@5.5.2) - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.6(supports-color@8.1.1) eslint: 8.57.0 ts-api-utils: 1.0.1(typescript@5.5.2) optionalDependencies: @@ -18975,7 +19100,7 @@ snapshots: dependencies: '@typescript-eslint/types': 6.7.5 '@typescript-eslint/visitor-keys': 6.7.5 - debug: 4.3.6 + debug: 4.3.6(supports-color@8.1.1) globby: 11.1.0 is-glob: 4.0.3 semver: 7.6.0 @@ -18989,7 +19114,7 @@ snapshots: dependencies: '@typescript-eslint/types': 7.2.0 '@typescript-eslint/visitor-keys': 7.2.0 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.6(supports-color@8.1.1) globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.3 @@ -19049,7 +19174,7 @@ snapshots: dependencies: '@ampproject/remapping': 2.2.1 '@bcoe/v8-coverage': 0.2.3 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5 istanbul-lib-coverage: 3.2.2 istanbul-lib-report: 3.0.1 istanbul-lib-source-maps: 5.0.4 @@ -19232,7 +19357,7 @@ snapshots: '@vue/compiler-dom': 3.4.21 '@vue/shared': 3.4.21 computeds: 0.0.1 - minimatch: 9.0.4 + minimatch: 9.0.5 muggle-string: 0.3.1 path-browserify: 1.0.1 vue-template-compiler: 2.7.14 @@ -19388,19 +19513,19 @@ snapshots: agent-base@6.0.2: dependencies: - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.6(supports-color@8.1.1) transitivePeerDependencies: - supports-color agent-base@7.1.0: dependencies: - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.6(supports-color@8.1.1) transitivePeerDependencies: - supports-color agentkeepalive@4.2.1: dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.6(supports-color@8.1.1) depd: 1.1.2 humanize-ms: 1.2.1 transitivePeerDependencies: @@ -19497,6 +19622,9 @@ snapshots: readable-stream: 3.6.0 optional: true + arg@4.1.3: + optional: true + arg@5.0.2: {} argparse@1.0.10: @@ -20193,8 +20321,6 @@ snapshots: dependencies: restore-cursor: 3.1.0 - cli-spinners@2.9.0: {} - cli-spinners@2.9.2: {} cli-table3@0.6.3: @@ -20447,6 +20573,9 @@ snapshots: nan: 2.20.0 optional: true + create-require@1.1.1: + optional: true + crelt@1.0.5: {} crlf-normalize@1.0.19(ts-toolbelt@9.6.0): @@ -20718,15 +20847,15 @@ snapshots: optionalDependencies: supports-color: 8.1.1 - debug@4.3.5(supports-color@8.1.1): + debug@4.3.5: dependencies: ms: 2.1.2 - optionalDependencies: - supports-color: 8.1.1 - debug@4.3.6: + debug@4.3.6(supports-color@8.1.1): dependencies: ms: 2.1.2 + optionalDependencies: + supports-color: 8.1.1 decamelize@1.2.0: {} @@ -20847,7 +20976,7 @@ snapshots: detect-port@1.5.1: dependencies: address: 1.2.2 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.6(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -20860,6 +20989,9 @@ snapshots: diff-sequences@29.6.3: {} + diff@4.0.2: + optional: true + diff@5.2.0: {} digest-fetch@1.3.0: @@ -21221,7 +21353,7 @@ snapshots: esbuild-register@3.5.0(esbuild@0.20.2): dependencies: - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.6(supports-color@8.1.1) esbuild: 0.20.2 transitivePeerDependencies: - supports-color @@ -21703,7 +21835,7 @@ snapshots: extract-zip@2.0.1(supports-color@8.1.1): dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.6(supports-color@8.1.1) get-stream: 5.2.0 yauzl: 2.10.0 optionalDependencies: @@ -21892,7 +22024,7 @@ snapshots: follow-redirects@1.15.6(debug@4.3.6): optionalDependencies: - debug: 4.3.6 + debug: 4.3.6(supports-color@8.1.1) for-each@0.3.3: dependencies: @@ -22164,7 +22296,7 @@ snapshots: dependencies: foreground-child: 3.1.1 jackspeak: 2.3.6 - minimatch: 9.0.3 + minimatch: 9.0.5 minipass: 7.0.2 path-scurry: 1.10.1 @@ -22465,7 +22597,7 @@ snapshots: dependencies: '@tootallnate/once': 1.1.2 agent-base: 6.0.2 - debug: 4.3.6 + debug: 4.3.6(supports-color@8.1.1) transitivePeerDependencies: - supports-color optional: true @@ -22474,14 +22606,14 @@ snapshots: dependencies: '@tootallnate/once': 2.0.0 agent-base: 6.0.2 - debug: 4.3.6 + debug: 4.3.6(supports-color@8.1.1) transitivePeerDependencies: - supports-color http-proxy-agent@7.0.0: dependencies: agent-base: 7.1.0 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.6(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -22496,14 +22628,14 @@ snapshots: https-proxy-agent@5.0.1: dependencies: agent-base: 6.0.2 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.6(supports-color@8.1.1) transitivePeerDependencies: - supports-color https-proxy-agent@7.0.2: dependencies: agent-base: 7.1.0 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.6(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -22640,7 +22772,7 @@ snapshots: dependencies: '@ioredis/commands': 1.2.0 cluster-key-slot: 1.1.2 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5 denque: 2.1.0 lodash.defaults: 4.2.0 lodash.isarguments: 3.1.0 @@ -22875,7 +23007,7 @@ snapshots: istanbul-lib-source-maps@4.0.1: dependencies: - debug: 4.3.6 + debug: 4.3.6(supports-color@8.1.1) istanbul-lib-coverage: 3.2.2 source-map: 0.6.1 transitivePeerDependencies: @@ -22884,7 +23016,7 @@ snapshots: istanbul-lib-source-maps@5.0.4: dependencies: '@jridgewell/trace-mapping': 0.3.25 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.6(supports-color@8.1.1) istanbul-lib-coverage: 3.2.2 transitivePeerDependencies: - supports-color @@ -22938,16 +23070,16 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@29.6.2(@types/node@18.16.16): + jest-cli@29.6.2(@types/node@18.16.16)(ts-node@10.9.2(@types/node@18.16.16)(typescript@5.5.2)): dependencies: - '@jest/core': 29.6.2 + '@jest/core': 29.6.2(ts-node@10.9.2(@types/node@18.16.16)(typescript@5.5.2)) '@jest/test-result': 29.6.2 '@jest/types': 29.6.1 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 import-local: 3.1.0 - jest-config: 29.6.2(@types/node@18.16.16) + jest-config: 29.6.2(@types/node@18.16.16)(ts-node@10.9.2(@types/node@18.16.16)(typescript@5.5.2)) jest-util: 29.6.2 jest-validate: 29.6.2 prompts: 2.4.2 @@ -22958,7 +23090,7 @@ snapshots: - supports-color - ts-node - jest-config@29.6.2(@types/node@18.16.16): + jest-config@29.6.2(@types/node@18.16.16)(ts-node@10.9.2(@types/node@18.16.16)(typescript@5.5.2)): dependencies: '@babel/core': 7.24.0 '@jest/test-sequencer': 29.6.2 @@ -22984,6 +23116,7 @@ snapshots: strip-json-comments: 3.1.1 optionalDependencies: '@types/node': 18.16.16 + ts-node: 10.9.2(@types/node@18.16.16)(typescript@5.5.2) transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -23101,9 +23234,9 @@ snapshots: slash: 3.0.0 stack-utils: 2.0.6 - jest-mock-extended@3.0.4(jest@29.6.2(@types/node@18.16.16))(typescript@5.5.2): + jest-mock-extended@3.0.4(jest@29.6.2(@types/node@18.16.16)(ts-node@10.9.2(@types/node@18.16.16)(typescript@5.5.2)))(typescript@5.5.2): dependencies: - jest: 29.6.2(@types/node@18.16.16) + jest: 29.6.2(@types/node@18.16.16)(ts-node@10.9.2(@types/node@18.16.16)(typescript@5.5.2)) ts-essentials: 7.0.3(typescript@5.5.2) typescript: 5.5.2 @@ -23261,12 +23394,12 @@ snapshots: merge-stream: 2.0.0 supports-color: 8.1.1 - jest@29.6.2(@types/node@18.16.16): + jest@29.6.2(@types/node@18.16.16)(ts-node@10.9.2(@types/node@18.16.16)(typescript@5.5.2)): dependencies: - '@jest/core': 29.6.2 + '@jest/core': 29.6.2(ts-node@10.9.2(@types/node@18.16.16)(typescript@5.5.2)) '@jest/types': 29.6.1 import-local: 3.1.0 - jest-cli: 29.6.2(@types/node@18.16.16) + jest-cli: 29.6.2(@types/node@18.16.16)(ts-node@10.9.2(@types/node@18.16.16)(typescript@5.5.2)) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -23737,8 +23870,6 @@ snapshots: lilconfig@2.1.0: {} - lilconfig@3.0.0: {} - lilconfig@3.1.2: {} lines-and-columns@1.2.4: {} @@ -24109,10 +24240,6 @@ snapshots: dependencies: brace-expansion: 2.0.1 - minimatch@9.0.4: - dependencies: - brace-expansion: 2.0.1 - minimatch@9.0.5: dependencies: brace-expansion: 2.0.1 @@ -24255,7 +24382,7 @@ snapshots: mqtt-packet@9.0.0: dependencies: bl: 6.0.12 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.6(supports-color@8.1.1) process-nextick-args: 2.0.1 transitivePeerDependencies: - supports-color @@ -24311,7 +24438,7 @@ snapshots: dependencies: '@tediousjs/connection-string': 0.5.0 commander: 11.1.0 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5 rfdc: 1.3.0 tarn: 3.0.2 tedious: 16.7.1 @@ -24550,7 +24677,7 @@ snapshots: number-allocator@1.0.14: dependencies: - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.6(supports-color@8.1.1) js-sdsl: 4.3.0 transitivePeerDependencies: - supports-color @@ -24743,7 +24870,7 @@ snapshots: bl: 4.1.0 chalk: 4.1.2 cli-cursor: 3.1.0 - cli-spinners: 2.9.0 + cli-spinners: 2.9.2 is-interactive: 1.0.0 is-unicode-supported: 0.1.0 log-symbols: 4.1.0 @@ -25063,12 +25190,13 @@ snapshots: camelcase-css: 2.0.1 postcss: 8.4.38 - postcss-load-config@4.0.2(postcss@8.4.38): + postcss-load-config@4.0.2(postcss@8.4.38)(ts-node@10.9.2(@types/node@18.16.16)(typescript@5.5.2)): dependencies: - lilconfig: 3.0.0 + lilconfig: 3.1.2 yaml: 2.3.4 optionalDependencies: postcss: 8.4.38 + ts-node: 10.9.2(@types/node@18.16.16)(typescript@5.5.2) postcss-nested@6.0.1(postcss@8.4.38): dependencies: @@ -25777,7 +25905,7 @@ snapshots: retry-request@5.0.2: dependencies: - debug: 4.3.6 + debug: 4.3.6(supports-color@8.1.1) extend: 3.0.2 transitivePeerDependencies: - supports-color @@ -26125,7 +26253,7 @@ snapshots: simple-websocket@9.1.0: dependencies: - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.6(supports-color@8.1.1) queue-microtask: 1.2.3 randombytes: 2.1.0 readable-stream: 3.6.0 @@ -26206,7 +26334,7 @@ snapshots: socks-proxy-agent@6.2.1: dependencies: agent-base: 6.0.2 - debug: 4.3.6 + debug: 4.3.6(supports-color@8.1.1) socks: 2.7.1 transitivePeerDependencies: - supports-color @@ -26530,7 +26658,7 @@ snapshots: dependencies: component-emitter: 1.3.0 cookiejar: 2.1.4 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.6(supports-color@8.1.1) fast-safe-stringify: 2.1.1 form-data: 4.0.0 formidable: 3.5.1 @@ -26593,7 +26721,7 @@ snapshots: syslog-client@1.1.1: {} - tailwindcss@3.4.3: + tailwindcss@3.4.3(ts-node@10.9.2(@types/node@18.16.16)(typescript@5.5.2)): dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 @@ -26612,7 +26740,7 @@ snapshots: postcss: 8.4.38 postcss-import: 15.1.0(postcss@8.4.38) postcss-js: 4.0.1(postcss@8.4.38) - postcss-load-config: 4.0.2(postcss@8.4.38) + postcss-load-config: 4.0.2(postcss@8.4.38)(ts-node@10.9.2(@types/node@18.16.16)(typescript@5.5.2)) postcss-nested: 6.0.1(postcss@8.4.38) postcss-selector-parser: 6.0.16 resolve: 1.22.8 @@ -26855,11 +26983,11 @@ snapshots: ts-interface-checker@0.1.13: {} - ts-jest@29.1.1(@babel/core@7.24.0)(@jest/types@29.6.1)(babel-jest@29.6.2(@babel/core@7.24.0))(jest@29.6.2(@types/node@18.16.16))(typescript@5.5.2): + ts-jest@29.1.1(@babel/core@7.24.0)(@jest/types@29.6.1)(babel-jest@29.6.2(@babel/core@7.24.0))(jest@29.6.2(@types/node@18.16.16)(ts-node@10.9.2(@types/node@18.16.16)(typescript@5.5.2)))(typescript@5.5.2): dependencies: bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 - jest: 29.6.2(@types/node@18.16.16) + jest: 29.6.2(@types/node@18.16.16)(ts-node@10.9.2(@types/node@18.16.16)(typescript@5.5.2)) jest-util: 29.5.0 json5: 2.2.3 lodash.memoize: 4.1.2 @@ -26874,6 +27002,25 @@ snapshots: ts-map@1.0.3: {} + ts-node@10.9.2(@types/node@18.16.16)(typescript@5.5.2): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.11 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 18.16.16 + acorn: 8.12.1 + acorn-walk: 8.3.2 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.5.2 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + optional: true + ts-toolbelt@9.6.0: {} ts-type@3.0.1(ts-toolbelt@9.6.0): @@ -27138,7 +27285,7 @@ snapshots: '@antfu/install-pkg': 0.3.3 '@antfu/utils': 0.7.10 '@iconify/utils': 2.1.25 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5 kolorist: 1.8.0 local-pkg: 0.5.0 unplugin: 1.11.0 @@ -27153,7 +27300,7 @@ snapshots: '@antfu/utils': 0.7.10 '@rollup/pluginutils': 5.1.0(rollup@4.18.0) chokidar: 3.5.2 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5 fast-glob: 3.3.2 local-pkg: 0.5.0 magic-string: 0.30.10 @@ -27264,6 +27411,9 @@ snapshots: v3-infinite-loading@1.2.2: {} + v8-compile-cache-lib@3.0.1: + optional: true + v8-to-istanbul@9.1.0: dependencies: '@jridgewell/trace-mapping': 0.3.25 @@ -27288,7 +27438,7 @@ snapshots: vite-node@1.6.0(@types/node@18.16.16)(sass@1.64.1)(terser@5.16.1): dependencies: cac: 6.7.14 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.6(supports-color@8.1.1) pathe: 1.1.2 picocolors: 1.0.1 vite: 5.2.12(@types/node@18.16.16)(sass@1.64.1)(terser@5.16.1) @@ -27307,7 +27457,7 @@ snapshots: '@microsoft/api-extractor': 7.43.0(@types/node@18.16.16) '@rollup/pluginutils': 5.1.0(rollup@4.18.0) '@vue/language-core': 1.8.27(typescript@5.5.2) - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5 kolorist: 1.8.0 magic-string: 0.30.8 typescript: 5.5.2 @@ -27345,7 +27495,7 @@ snapshots: '@vitest/utils': 1.6.0 acorn-walk: 8.3.2 chai: 4.3.10 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5 execa: 8.0.1 local-pkg: 0.5.0 magic-string: 0.30.10 @@ -27826,6 +27976,9 @@ snapshots: buffer-crc32: 0.2.13 fd-slicer: 1.1.0 + yn@3.1.1: + optional: true + yocto-queue@0.1.0: {} yocto-queue@1.0.0: {} @@ -27855,3 +28008,8 @@ snapshots: zod@3.22.4: {} zod@3.23.8: {} + + zx@8.1.4: + optionalDependencies: + '@types/fs-extra': 11.0.4 + '@types/node': 18.16.16