diff --git a/.github/workflows/docker-images-benchmark.yml b/.github/workflows/docker-images-benchmark.yml new file mode 100644 index 0000000000..e4b09cab61 --- /dev/null +++ b/.github/workflows/docker-images-benchmark.yml @@ -0,0 +1,43 @@ +name: Benchmark Docker Image CI + +on: + workflow_dispatch: + push: + branches: + - main + paths: + - 'packages/benchmark/**' + - 'pnpm-lock.yaml' + - 'pnpm-workspace.yaml' + - '.github/workflows/docker-images-benchmark.yml' + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4.1.1 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3.0.0 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3.0.0 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3.0.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build + uses: docker/build-push-action@v5.1.0 + with: + context: . + file: ./packages/benchmark/Dockerfile + platforms: linux/amd64 + provenance: false + push: true + tags: | + ghcr.io/${{ github.repository_owner }}/n8n-benchmark:latest diff --git a/packages/@n8n/benchmark/Dockerfile b/packages/@n8n/benchmark/Dockerfile new file mode 100644 index 0000000000..5fa1aeae93 --- /dev/null +++ b/packages/@n8n/benchmark/Dockerfile @@ -0,0 +1,62 @@ +# syntax=docker/dockerfile:1 +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/@n8n/benchmark/package.json /app/packages/@n8n/benchmark/package.json +COPY --chown=node:node ./patches /app/patches +COPY --chown=node:node ./scripts /app/scripts + +RUN pnpm install --frozen-lockfile + +# 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/@n8n/benchmark/tsconfig.json /app/packages/@n8n/benchmark/tsconfig.json +COPY --chown=node:node ./packages/@n8n/benchmark/tsconfig.build.json /app/packages/@n8n/benchmark/tsconfig.build.json + +# Source files +COPY --chown=node:node ./packages/@n8n/benchmark/src /app/packages/@n8n/benchmark/src +COPY --chown=node:node ./packages/@n8n/benchmark/bin /app/packages/@n8n/benchmark/bin +COPY --chown=node:node ./packages/@n8n/benchmark/scenarios /app/packages/@n8n/benchmark/scenarios + +WORKDIR /app/packages/@n8n/benchmark +RUN pnpm build + +# +# Runner +FROM base AS runner + +COPY --from=builder /app /app + +WORKDIR /app/packages/@n8n/benchmark +USER node + +ENTRYPOINT [ "/app/packages/@n8n/benchmark/bin/n8n-benchmark" ] diff --git a/packages/@n8n/benchmark/README.md b/packages/@n8n/benchmark/README.md new file mode 100644 index 0000000000..569bcf897f --- /dev/null +++ b/packages/@n8n/benchmark/README.md @@ -0,0 +1,55 @@ +# n8n benchmarking tool + +Tool for executing benchmarks against an n8n instance. + +## Running locally with Docker + +Build the Docker image: + +```sh +# Must be run in the repository root +# k6 doesn't have an arm64 build available for linux, we need to build against amd64 +docker build --platform linux/amd64 -t n8n-benchmark -f packages/@n8n/benchmark/Dockerfile . +``` + +Run the image + +```sh +docker run \ + -e N8N_USER_EMAIL=user@n8n.io \ + -e N8N_USER_PASSWORD=password \ + # For macos, n8n running outside docker + -e N8N_BASE_URL=http://host.docker.internal:5678 \ + n8n-benchmark +``` + +## Running locally without Docker + +Requirements: + +- [k6](https://grafana.com/docs/k6/latest/set-up/install-k6/) +- Node.js v20 or higher + +```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) + +## Benchmark scenarios + +A benchmark scenario defines one or multiple steps to execute and measure. It consists of: + +- Manifest file which describes and configures the scenario +- Any test data that is imported before the scenario is run +- A [`k6`](https://grafana.com/docs/k6/latest/using-k6/http-requests/) script which executes the steps and receives `API_BASE_URL` environment variable in runtime. + +Available scenarios are located in [`./scenarios`](./scenarios/). diff --git a/packages/@n8n/benchmark/bin/n8n-benchmark b/packages/@n8n/benchmark/bin/n8n-benchmark new file mode 100755 index 0000000000..c7f0996f09 --- /dev/null +++ b/packages/@n8n/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/@n8n/benchmark/package.json b/packages/@n8n/benchmark/package.json new file mode 100644 index 0000000000..3a7afb50a3 --- /dev/null +++ b/packages/@n8n/benchmark/package.json @@ -0,0 +1,48 @@ +{ + "name": "@n8n/n8n-benchmark", + "version": "1.0.0", + "description": "Cli for running benchmark tests for n8n", + "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": [ + "automate", + "automation", + "IaaS", + "iPaaS", + "n8n", + "workflow", + "benchmark", + "performance" + ], + "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/@n8n/benchmark/scenarios/scenario.schema.json b/packages/@n8n/benchmark/scenarios/scenario.schema.json new file mode 100644 index 0000000000..661fc054b6 --- /dev/null +++ b/packages/@n8n/benchmark/scenarios/scenario.schema.json @@ -0,0 +1,42 @@ +{ + "definitions": { + "ScenarioData": { + "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 scenario" + }, + "description": { + "type": "string", + "description": "A longer description of the scenario" + }, + "scriptPath": { + "type": "string", + "description": "Relative path to the k6 test script" + }, + "scenarioData": { + "$ref": "#/definitions/ScenarioData", + "description": "Data to import before running the scenario" + } + }, + "required": ["name", "description", "scriptPath", "scenarioData"], + "additionalProperties": false +} diff --git a/packages/@n8n/benchmark/scenarios/singleWebhook/singleWebhook.json b/packages/@n8n/benchmark/scenarios/singleWebhook/singleWebhook.json new file mode 100644 index 0000000000..cba1aa5832 --- /dev/null +++ b/packages/@n8n/benchmark/scenarios/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/@n8n/benchmark/scenarios/singleWebhook/singleWebhook.manifest.json b/packages/@n8n/benchmark/scenarios/singleWebhook/singleWebhook.manifest.json new file mode 100644 index 0000000000..e9b4664a96 --- /dev/null +++ b/packages/@n8n/benchmark/scenarios/singleWebhook/singleWebhook.manifest.json @@ -0,0 +1,7 @@ +{ + "$schema": "../scenario.schema.json", + "name": "SingleWebhook", + "description": "A single webhook trigger that responds with a 200 status code", + "scenarioData": { "workflowFiles": ["singleWebhook.json"] }, + "scriptPath": "singleWebhook.script.ts" +} diff --git a/packages/@n8n/benchmark/scenarios/singleWebhook/singleWebhook.script.ts b/packages/@n8n/benchmark/scenarios/singleWebhook/singleWebhook.script.ts new file mode 100644 index 0000000000..72e2563cbe --- /dev/null +++ b/packages/@n8n/benchmark/scenarios/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/@n8n/benchmark/src/commands/list.ts b/packages/@n8n/benchmark/src/commands/list.ts new file mode 100644 index 0000000000..fcc60b1b81 --- /dev/null +++ b/packages/@n8n/benchmark/src/commands/list.ts @@ -0,0 +1,21 @@ +import { Command } from '@oclif/core'; +import { ScenarioLoader } from '@/scenario/scenarioLoader'; +import { loadConfig } from '@/config/config'; + +export default class ListCommand extends Command { + static description = 'List all available scenarios'; + + async run() { + const config = loadConfig(); + const scenarioLoader = new ScenarioLoader(); + + const allScenarios = scenarioLoader.loadAll(config.get('testScenariosPath')); + + console.log('Available test scenarios:'); + console.log(''); + + for (const scenario of allScenarios) { + console.log('\t', scenario.name, ':', scenario.description); + } + } +} diff --git a/packages/@n8n/benchmark/src/commands/run.ts b/packages/@n8n/benchmark/src/commands/run.ts new file mode 100644 index 0000000000..d69b4a54d4 --- /dev/null +++ b/packages/@n8n/benchmark/src/commands/run.ts @@ -0,0 +1,39 @@ +import { Command, Flags } from '@oclif/core'; +import { loadConfig } from '@/config/config'; +import { ScenarioLoader } from '@/scenario/scenarioLoader'; +import { ScenarioRunner } from '@/testExecution/scenarioRunner'; +import { N8nApiClient } from '@/n8nApiClient/n8nApiClient'; +import { ScenarioDataFileLoader } from '@/scenario/scenarioDataLoader'; +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 scenarioLoader = new ScenarioLoader(); + + const scenarioRunner = new ScenarioRunner( + new N8nApiClient(config.get('n8n.baseUrl')), + new ScenarioDataFileLoader(), + new K6Executor(config.get('k6ExecutablePath'), config.get('n8n.baseUrl')), + { + email: config.get('n8n.user.email'), + password: config.get('n8n.user.password'), + }, + ); + + const allScenarios = scenarioLoader.loadAll(config.get('testScenariosPath')); + + await scenarioRunner.runManyScenarios(allScenarios); + } +} diff --git a/packages/@n8n/benchmark/src/config/config.ts b/packages/@n8n/benchmark/src/config/config.ts new file mode 100644 index 0000000000..896ecc9296 --- /dev/null +++ b/packages/@n8n/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 scenarios', + format: String, + default: 'scenarios', + }, + 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/@n8n/benchmark/src/n8nApiClient/authenticatedN8nApiClient.ts b/packages/@n8n/benchmark/src/n8nApiClient/authenticatedN8nApiClient.ts new file mode 100644 index 0000000000..93cd767347 --- /dev/null +++ b/packages/@n8n/benchmark/src/n8nApiClient/authenticatedN8nApiClient.ts @@ -0,0 +1,67 @@ +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) { + return await this.authenticatedRequest(endpoint, { + method: 'DELETE', + }); + } + + protected async authenticatedRequest(endpoint: string, init: Omit) { + return await this.restApiRequest(endpoint, { + ...init, + headers: { + ...init.headers, + cookie: this.authCookie, + }, + }); + } +} diff --git a/packages/@n8n/benchmark/src/n8nApiClient/n8nApiClient.ts b/packages/@n8n/benchmark/src/n8nApiClient/n8nApiClient.ts new file mode 100644 index 0000000000..86ca52aff8 --- /dev/null +++ b/packages/@n8n/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 HEALTH_ENDPOINT = 'healthz'; + const START_TIME = Date.now(); + const INTERVAL_MS = 1000; + const TIMEOUT_MS = 60_000; + + while (Date.now() - START_TIME < TIMEOUT_MS) { + try { + const response = await axios.request({ + url: `${this.apiBaseUrl}/${HEALTH_ENDPOINT}`, + method: 'GET', + }); + + if (response.status === 200 && response.data.status === 'ok') { + return; + } + } catch {} + + console.log(`n8n instance not online yet, retrying in ${INTERVAL_MS / 1000} seconds...`); + await this.delay(INTERVAL_MS); + } + + throw new Error(`n8n instance did not come online within ${TIMEOUT_MS / 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/@n8n/benchmark/src/n8nApiClient/n8nApiClient.types.ts b/packages/@n8n/benchmark/src/n8nApiClient/n8nApiClient.types.ts new file mode 100644 index 0000000000..ff6aa6930b --- /dev/null +++ b/packages/@n8n/benchmark/src/n8nApiClient/n8nApiClient.types.ts @@ -0,0 +1,8 @@ +/** + * n8n workflow. This is a simplified version of the actual workflow object. + */ +export type Workflow = { + id: string; + name: string; + tags?: string[]; +}; diff --git a/packages/@n8n/benchmark/src/n8nApiClient/workflowsApiClient.ts b/packages/@n8n/benchmark/src/n8nApiClient/workflowsApiClient.ts new file mode 100644 index 0000000000..18f2ecbcda --- /dev/null +++ b/packages/@n8n/benchmark/src/n8nApiClient/workflowsApiClient.ts @@ -0,0 +1,31 @@ +import { Workflow } from '@/n8nApiClient/n8nApiClient.types'; +import { AuthenticatedN8nApiClient } from './authenticatedN8nApiClient'; + +export class WorkflowApiClient { + constructor(private readonly apiClient: AuthenticatedN8nApiClient) {} + + async getAllWorkflows(): Promise { + const response = await this.apiClient.get<{ count: number; data: Workflow[] }>('/workflows'); + + return response.data.data; + } + + async createWorkflow(workflow: unknown): Promise { + const response = await this.apiClient.post<{ data: Workflow }>('/workflows', workflow); + + return response.data.data; + } + + async activateWorkflow(workflow: Workflow): Promise { + const response = await this.apiClient.patch<{ data: Workflow }>(`/workflows/${workflow.id}`, { + ...workflow, + active: true, + }); + + return response.data.data; + } + + async deleteWorkflow(workflowId: Workflow['id']): Promise { + await this.apiClient.delete(`/workflows/${workflowId}`); + } +} diff --git a/packages/@n8n/benchmark/src/scenario/scenarioDataLoader.ts b/packages/@n8n/benchmark/src/scenario/scenarioDataLoader.ts new file mode 100644 index 0000000000..43638a2e00 --- /dev/null +++ b/packages/@n8n/benchmark/src/scenario/scenarioDataLoader.ts @@ -0,0 +1,35 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { Scenario } from '@/types/scenario'; +import { Workflow } from '@/n8nApiClient/n8nApiClient.types'; + +/** + * Loads scenario data files from FS + */ +export class ScenarioDataFileLoader { + async loadDataForScenario(scenario: Scenario): Promise<{ + workflows: Workflow[]; + }> { + const workflows = await Promise.all( + scenario.scenarioData.workflowFiles?.map((workflowFilePath) => + this.loadSingleWorkflowFromFile(path.join(scenario.scenarioDirPath, workflowFilePath)), + ) ?? [], + ); + + return { + workflows, + }; + } + + private loadSingleWorkflowFromFile(workflowFilePath: string): Workflow { + const fileContent = fs.readFileSync(workflowFilePath, 'utf8'); + + try { + return JSON.parse(fileContent); + } catch (error) { + throw new Error( + `Failed to parse workflow file ${workflowFilePath}: ${error instanceof Error ? error.message : error}`, + ); + } + } +} diff --git a/packages/@n8n/benchmark/src/scenario/scenarioLoader.ts b/packages/@n8n/benchmark/src/scenario/scenarioLoader.ts new file mode 100644 index 0000000000..475ce495ac --- /dev/null +++ b/packages/@n8n/benchmark/src/scenario/scenarioLoader.ts @@ -0,0 +1,67 @@ +import * as fs from 'node:fs'; +import * as path from 'path'; +import { createHash } from 'node:crypto'; +import type { Scenario, ScenarioManifest } from '@/types/scenario'; + +export class ScenarioLoader { + /** + * Loads all scenarios from the given path + */ + loadAll(pathToScenarios: string): Scenario[] { + pathToScenarios = path.resolve(pathToScenarios); + const scenarioFolders = fs + .readdirSync(pathToScenarios, { withFileTypes: true }) + .filter((dirent) => dirent.isDirectory()) + .map((dirent) => dirent.name); + + const scenarios: Scenario[] = []; + + for (const folder of scenarioFolders) { + const scenarioPath = path.join(pathToScenarios, folder); + const manifestFileName = `${folder}.manifest.json`; + const scenarioManifestPath = path.join(pathToScenarios, folder, manifestFileName); + if (!fs.existsSync(scenarioManifestPath)) { + console.warn(`Scenario at ${scenarioPath} is missing the ${manifestFileName} file`); + continue; + } + + // Load the scenario manifest file + const [scenario, validationErrors] = + this.loadAndValidateScenarioManifest(scenarioManifestPath); + if (validationErrors) { + console.warn( + `Scenario at ${scenarioPath} has the following validation errors: ${validationErrors.join(', ')}`, + ); + continue; + } + + scenarios.push({ + ...scenario, + id: this.formScenarioId(scenarioPath), + scenarioDirPath: scenarioPath, + }); + } + + return scenarios; + } + + private loadAndValidateScenarioManifest( + scenarioManifestPath: string, + ): [ScenarioManifest, null] | [null, string[]] { + const scenario = JSON.parse(fs.readFileSync(scenarioManifestPath, 'utf8')); + const validationErrors: string[] = []; + + if (!scenario.name) { + validationErrors.push(`Scenario at ${scenarioManifestPath} is missing a name`); + } + if (!scenario.description) { + validationErrors.push(`Scenario at ${scenarioManifestPath} is missing a description`); + } + + return validationErrors.length === 0 ? [scenario, null] : [null, validationErrors]; + } + + private formScenarioId(scenarioPath: string): string { + return createHash('sha256').update(scenarioPath).digest('hex'); + } +} diff --git a/packages/@n8n/benchmark/src/testExecution/k6Executor.ts b/packages/@n8n/benchmark/src/testExecution/k6Executor.ts new file mode 100644 index 0000000000..903d06ca74 --- /dev/null +++ b/packages/@n8n/benchmark/src/testExecution/k6Executor.ts @@ -0,0 +1,28 @@ +import { $ } from 'zx'; +import { Scenario } from '@/types/scenario'; + +/** + * Executes test scenarios using k6 + */ +export class K6Executor { + constructor( + private readonly k6ExecutablePath: string, + private readonly n8nApiBaseUrl: string, + ) {} + + async executeTestScenario(scenario: Scenario) { + // For 1 min with 5 virtual users + const stage = '1m:5'; + + const processPromise = $({ + cwd: scenario.scenarioDirPath, + env: { + API_BASE_URL: this.n8nApiBaseUrl, + }, + })`${this.k6ExecutablePath} run --quiet --stage ${stage} ${scenario.scriptPath}`; + + for await (const chunk of processPromise.stdout) { + console.log(chunk.toString()); + } + } +} diff --git a/packages/@n8n/benchmark/src/testExecution/scenarioDataImporter.ts b/packages/@n8n/benchmark/src/testExecution/scenarioDataImporter.ts new file mode 100644 index 0000000000..1c1ec3777e --- /dev/null +++ b/packages/@n8n/benchmark/src/testExecution/scenarioDataImporter.ts @@ -0,0 +1,56 @@ +import { AuthenticatedN8nApiClient } from '@/n8nApiClient/authenticatedN8nApiClient'; +import { Workflow } from '@/n8nApiClient/n8nApiClient.types'; +import { WorkflowApiClient } from '@/n8nApiClient/workflowsApiClient'; + +/** + * Imports scenario data into an n8n instance + */ +export class ScenarioDataImporter { + 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 removing any existing workflows with the same name + */ + private async importWorkflow(opts: { existingWorkflows: Workflow[]; workflow: Workflow }) { + const existingWorkflows = this.findExistingWorkflows(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), + }); + + return await this.workflowApiClient.activateWorkflow(createdWorkflow); + } + + private findExistingWorkflows( + 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/@n8n/benchmark/src/testExecution/scenarioRunner.ts b/packages/@n8n/benchmark/src/testExecution/scenarioRunner.ts new file mode 100644 index 0000000000..83e1077680 --- /dev/null +++ b/packages/@n8n/benchmark/src/testExecution/scenarioRunner.ts @@ -0,0 +1,50 @@ +import { Scenario } from '@/types/scenario'; +import { N8nApiClient } from '@/n8nApiClient/n8nApiClient'; +import { ScenarioDataFileLoader } from '@/scenario/scenarioDataLoader'; +import { K6Executor } from './k6Executor'; +import { ScenarioDataImporter } from '@/testExecution/scenarioDataImporter'; +import { AuthenticatedN8nApiClient } from '@/n8nApiClient/authenticatedN8nApiClient'; + +/** + * Runs scenarios + */ +export class ScenarioRunner { + constructor( + private readonly n8nClient: N8nApiClient, + private readonly dataLoader: ScenarioDataFileLoader, + private readonly k6Executor: K6Executor, + private readonly ownerConfig: { + email: string; + password: string; + }, + ) {} + + async runManyScenarios(scenarios: Scenario[]) { + 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 ScenarioDataImporter(authenticatedN8nClient); + + for (const scenario of scenarios) { + await this.runSingleTestScenario(testDataImporter, scenario); + } + } + + private async runSingleTestScenario(testDataImporter: ScenarioDataImporter, scenario: Scenario) { + console.log('Running scenario:', scenario.name); + + console.log('Loading and importing data'); + const testData = await this.dataLoader.loadDataForScenario(scenario); + await testDataImporter.importTestScenarioData(testData.workflows); + + console.log('Executing scenario script'); + await this.k6Executor.executeTestScenario(scenario); + } +} diff --git a/packages/@n8n/benchmark/src/types/scenario.ts b/packages/@n8n/benchmark/src/types/scenario.ts new file mode 100644 index 0000000000..19c52fd45b --- /dev/null +++ b/packages/@n8n/benchmark/src/types/scenario.ts @@ -0,0 +1,27 @@ +export type ScenarioData = { + /** Relative paths to the workflow files */ + workflowFiles?: string[]; +}; + +/** + * Configuration that defines the benchmark scenario + */ +export type ScenarioManifest = { + /** The name of the scenario */ + name: string; + /** A longer description of the scenario */ + description: string; + /** Relative path to the k6 script */ + scriptPath: string; + /** Data to import before running the scenario */ + scenarioData: ScenarioData; +}; + +/** + * Scenario with additional metadata + */ +export type Scenario = ScenarioManifest & { + id: string; + /** Path to the directory containing the scenario */ + scenarioDirPath: string; +}; diff --git a/packages/@n8n/benchmark/tsconfig.build.json b/packages/@n8n/benchmark/tsconfig.build.json new file mode 100644 index 0000000000..b91db37a4a --- /dev/null +++ b/packages/@n8n/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/@n8n/benchmark/tsconfig.json b/packages/@n8n/benchmark/tsconfig.json new file mode 100644 index 0000000000..58a1b48f65 --- /dev/null +++ b/packages/@n8n/benchmark/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": ["../../../tsconfig.json", "../../../tsconfig.backend.json"], + "compilerOptions": { + "rootDir": ".", + "baseUrl": "src", + "paths": { + "@/*": ["./*"] + } + }, + "include": ["src/**/*.ts"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f77ec66a97..b562d950ff 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -125,7 +125,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 @@ -137,7 +137,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 @@ -158,7 +158,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 @@ -209,6 +209,40 @@ importers: specifier: workspace:* version: link:../packages/workflow + packages/@n8n/benchmark: + dependencies: + '@oclif/core': + specifier: 4.0.7 + version: 4.0.7 + axios: + specifier: 'catalog:' + version: 1.7.4(debug@4.3.6) + 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/@n8n/chat: dependencies: '@vueuse/core': @@ -390,7 +424,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 @@ -522,7 +556,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) @@ -534,7 +568,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)) @@ -630,7 +664,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 @@ -1107,7 +1141,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) @@ -1146,7 +1180,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) @@ -2398,10 +2432,6 @@ packages: resolution: {integrity: sha512-CvLSkwXGWnYlF9+J3iZUvwgAxKiYzK3BWuo+mLzD/MDGOZDj7Gq8+hqaOkMxmJwmlv0iu86uH5fdADd9Hxkymw==} engines: {node: '>=6.9.0'} - '@babel/helper-string-parser@7.23.4': - resolution: {integrity: sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==} - engines: {node: '>=6.9.0'} - '@babel/helper-string-parser@7.24.6': resolution: {integrity: sha512-WdJjwMEkmBicq5T9fm/cHND3+UlFa2Yj8ALLgmoSQAJZysYbBjw+azChSGPN4DSPLXOcooGRvDwZWMcF/mLO2Q==} engines: {node: '>=6.9.0'} @@ -3007,10 +3037,6 @@ packages: resolution: {integrity: sha512-OsNjaJwT9Zn8ozxcfoBc+RaHdj3gFmCmYoQLUII1o6ZrUwku0BMg80FoOTPx+Gi6XhcQxAYE4xyjPTo4SxEQqw==} engines: {node: '>=6.9.0'} - '@babel/types@7.23.6': - resolution: {integrity: sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==} - engines: {node: '>=6.9.0'} - '@babel/types@7.24.0': resolution: {integrity: sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==} engines: {node: '>=6.9.0'} @@ -3078,6 +3104,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'} @@ -3548,10 +3578,6 @@ packages: resolution: {integrity: sha512-tPKQNMPuXgvdOn2/Lg9HNfUvjYVGolt04Hp03f5hAk878uwOLikN+JzeLY0HcVgKgFl9Hs3EIqpu3WX27XNhnw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - '@jridgewell/gen-mapping@0.3.2': - resolution: {integrity: sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==} - engines: {node: '>=6.0.0'} - '@jridgewell/gen-mapping@0.3.5': resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} engines: {node: '>=6.0.0'} @@ -3560,10 +3586,6 @@ packages: resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==} engines: {node: '>=6.0.0'} - '@jridgewell/set-array@1.1.2': - resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} - engines: {node: '>=6.0.0'} - '@jridgewell/set-array@1.2.1': resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} engines: {node: '>=6.0.0'} @@ -3577,6 +3599,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==} @@ -5282,6 +5307,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==} @@ -5399,6 +5436,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==} @@ -5456,12 +5496,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==} @@ -6168,6 +6214,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==} @@ -6732,10 +6781,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'} @@ -6975,6 +7020,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==} @@ -7344,6 +7392,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'} @@ -9631,10 +9683,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'} @@ -10007,10 +10055,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'} @@ -11008,10 +11052,6 @@ packages: resolution: {integrity: sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - pretty-format@29.6.2: - resolution: {integrity: sha512-1q0oC8eRveTg5nnBEWMXAU2qpv65Gnuf2eCQzSjxpWFkPaPARwqZZDGuNE0zPAZfTCHzIk3A8dIjwlQKKLphyg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - pretty-format@29.7.0: resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -12377,6 +12417,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==} @@ -12777,6 +12831,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'} @@ -13288,6 +13345,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'} @@ -13316,6 +13377,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': {} @@ -13323,7 +13389,7 @@ snapshots: '@acuminous/bitsyntax@0.1.2': dependencies: buffer-more-ints: 1.0.0 - debug: 4.3.6 + debug: 4.3.6(supports-color@8.1.1) safe-buffer: 5.1.2 transitivePeerDependencies: - supports-color @@ -14432,7 +14498,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 @@ -14452,7 +14518,7 @@ snapshots: '@babel/traverse': 7.24.6 '@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 @@ -14553,7 +14619,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: @@ -14564,7 +14630,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: @@ -14694,8 +14760,6 @@ snapshots: dependencies: '@babel/types': 7.24.6 - '@babel/helper-string-parser@7.23.4': {} - '@babel/helper-string-parser@7.24.6': {} '@babel/helper-validator-identifier@7.22.20': {} @@ -14733,7 +14797,7 @@ snapshots: '@babel/highlight@7.23.4': dependencies: - '@babel/helper-validator-identifier': 7.22.20 + '@babel/helper-validator-identifier': 7.24.6 chalk: 2.4.2 js-tokens: 4.0.0 @@ -14746,7 +14810,7 @@ snapshots: '@babel/parser@7.24.0': dependencies: - '@babel/types': 7.23.6 + '@babel/types': 7.24.6 '@babel/parser@7.24.6': dependencies: @@ -15448,7 +15512,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 @@ -15463,21 +15527,15 @@ snapshots: '@babel/helper-split-export-declaration': 7.24.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 - '@babel/types@7.23.6': - dependencies: - '@babel/helper-string-parser': 7.23.4 - '@babel/helper-validator-identifier': 7.22.20 - to-fast-properties: 2.0.0 - '@babel/types@7.24.0': dependencies: - '@babel/helper-string-parser': 7.23.4 - '@babel/helper-validator-identifier': 7.22.20 + '@babel/helper-string-parser': 7.24.6 + '@babel/helper-validator-identifier': 7.24.6 to-fast-properties: 2.0.0 '@babel/types@7.24.6': @@ -15600,6 +15658,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': {} @@ -15745,7 +15808,7 @@ snapshots: '@eslint/eslintrc@2.1.4': dependencies: ajv: 6.12.6 - debug: 4.3.6 + debug: 4.3.6(supports-color@8.1.1) espree: 9.6.1 globals: 13.20.0 ignore: 5.2.4 @@ -15903,7 +15966,7 @@ snapshots: '@humanwhocodes/config-array@0.11.14': dependencies: '@humanwhocodes/object-schema': 2.0.2 - debug: 4.3.6 + debug: 4.3.6(supports-color@8.1.1) minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -15933,7 +15996,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 @@ -15995,7 +16058,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 @@ -16009,7 +16072,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 @@ -16022,7 +16085,7 @@ snapshots: jest-validate: 29.6.2 jest-watcher: 29.6.2 micromatch: 4.0.5 - pretty-format: 29.6.2 + pretty-format: 29.7.0 slash: 3.0.0 strip-ansi: 6.0.1 transitivePeerDependencies: @@ -16156,12 +16219,6 @@ snapshots: '@types/yargs': 17.0.19 chalk: 4.1.2 - '@jridgewell/gen-mapping@0.3.2': - dependencies: - '@jridgewell/set-array': 1.1.2 - '@jridgewell/sourcemap-codec': 1.4.15 - '@jridgewell/trace-mapping': 0.3.25 - '@jridgewell/gen-mapping@0.3.5': dependencies: '@jridgewell/set-array': 1.2.1 @@ -16170,8 +16227,6 @@ snapshots: '@jridgewell/resolve-uri@3.1.0': {} - '@jridgewell/set-array@1.1.2': {} - '@jridgewell/set-array@1.2.1': {} '@jridgewell/source-map@0.3.2': @@ -16187,6 +16242,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': {} @@ -16227,7 +16288,7 @@ snapshots: '@kwsites/file-exists@1.1.1': dependencies: - debug: 4.3.6 + debug: 4.3.6(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -16639,7 +16700,7 @@ snapshots: '@n8n/localtunnel@3.0.0': dependencies: axios: 1.7.4(debug@4.3.6) - debug: 4.3.6 + debug: 4.3.6(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -16656,7 +16717,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 @@ -16665,7 +16726,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 @@ -16683,10 +16744,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 @@ -16695,7 +16757,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 @@ -16713,6 +16775,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 @@ -16781,14 +16844,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 @@ -17805,11 +17868,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 @@ -17995,7 +18058,7 @@ snapshots: dependencies: '@babel/core': 7.24.6 '@babel/preset-env': 7.24.6(@babel/core@7.24.6) - '@babel/types': 7.24.0 + '@babel/types': 7.24.6 '@storybook/csf': 0.1.7 '@storybook/csf-tools': 8.1.4 '@storybook/node-logger': 8.1.4 @@ -18257,14 +18320,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 @@ -18411,7 +18474,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 @@ -18424,10 +18487,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 @@ -18440,7 +18503,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)': @@ -18468,6 +18531,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 @@ -18599,6 +18674,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 @@ -18668,12 +18749,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': @@ -18989,7 +19077,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.6 + debug: 4.3.6(supports-color@8.1.1) eslint: 8.57.0 ts-api-utils: 1.0.1(typescript@5.5.2) optionalDependencies: @@ -19005,7 +19093,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 @@ -19019,7 +19107,7 @@ snapshots: dependencies: '@typescript-eslint/types': 7.2.0 '@typescript-eslint/visitor-keys': 7.2.0 - debug: 4.3.6 + debug: 4.3.6(supports-color@8.1.1) globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.3 @@ -19079,7 +19167,7 @@ snapshots: dependencies: '@ampproject/remapping': 2.2.1 '@bcoe/v8-coverage': 0.2.3 - debug: 4.3.6 + debug: 4.3.6(supports-color@8.1.1) istanbul-lib-coverage: 3.2.2 istanbul-lib-report: 3.0.1 istanbul-lib-source-maps: 5.0.4 @@ -19202,7 +19290,7 @@ snapshots: '@vue/compiler-core@3.4.21': dependencies: - '@babel/parser': 7.24.0 + '@babel/parser': 7.24.6 '@vue/shared': 3.4.21 entities: 4.5.0 estree-walker: 2.0.2 @@ -19262,7 +19350,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 @@ -19418,19 +19506,19 @@ snapshots: agent-base@6.0.2: dependencies: - debug: 4.3.6 + debug: 4.3.6(supports-color@8.1.1) transitivePeerDependencies: - supports-color agent-base@7.1.0: dependencies: - debug: 4.3.6 + debug: 4.3.6(supports-color@8.1.1) transitivePeerDependencies: - supports-color agentkeepalive@4.2.1: dependencies: - debug: 4.3.6 + debug: 4.3.6(supports-color@8.1.1) depd: 1.1.2 humanize-ms: 1.2.1 transitivePeerDependencies: @@ -19527,6 +19615,9 @@ snapshots: readable-stream: 3.6.0 optional: true + arg@4.1.3: + optional: true + arg@5.0.2: {} argparse@1.0.10: @@ -20207,8 +20298,6 @@ snapshots: dependencies: restore-cursor: 3.1.0 - cli-spinners@2.9.0: {} - cli-spinners@2.9.2: {} cli-table3@0.6.3: @@ -20461,6 +20550,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): @@ -20730,16 +20822,16 @@ 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 + + debug@4.3.6(supports-color@8.1.1): dependencies: ms: 2.1.2 optionalDependencies: supports-color: 8.1.1 - debug@4.3.6: - dependencies: - ms: 2.1.2 - decamelize@1.2.0: {} decimal.js@10.4.3: {} @@ -20859,7 +20951,7 @@ snapshots: detect-port@1.5.1: dependencies: address: 1.2.2 - debug: 4.3.6 + debug: 4.3.6(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -20872,6 +20964,9 @@ snapshots: diff-sequences@29.6.3: {} + diff@4.0.2: + optional: true + diff@5.2.0: {} digest-fetch@1.3.0: @@ -21238,7 +21333,7 @@ snapshots: esbuild-register@3.5.0(esbuild@0.20.2): dependencies: - debug: 4.3.6 + debug: 4.3.6(supports-color@8.1.1) esbuild: 0.20.2 transitivePeerDependencies: - supports-color @@ -21720,7 +21815,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: @@ -21901,7 +21996,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: @@ -22173,7 +22268,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 @@ -22476,7 +22571,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 @@ -22485,14 +22580,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.6 + debug: 4.3.6(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -22507,14 +22602,14 @@ snapshots: https-proxy-agent@5.0.1: dependencies: agent-base: 6.0.2 - debug: 4.3.6 + 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.6 + debug: 4.3.6(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -22651,7 +22746,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 @@ -22886,7 +22981,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: @@ -22895,7 +22990,7 @@ snapshots: istanbul-lib-source-maps@5.0.4: dependencies: '@jridgewell/trace-mapping': 0.3.25 - debug: 4.3.6 + debug: 4.3.6(supports-color@8.1.1) istanbul-lib-coverage: 3.2.2 transitivePeerDependencies: - supports-color @@ -22949,16 +23044,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 @@ -22969,7 +23064,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 @@ -22995,6 +23090,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 @@ -23112,9 +23208,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 @@ -23272,12 +23368,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 @@ -23749,8 +23845,6 @@ snapshots: lilconfig@2.1.0: {} - lilconfig@3.0.0: {} - lilconfig@3.1.2: {} lines-and-columns@1.2.4: {} @@ -24121,10 +24215,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 @@ -24267,7 +24357,7 @@ snapshots: mqtt-packet@9.0.0: dependencies: bl: 6.0.12 - debug: 4.3.6 + debug: 4.3.6(supports-color@8.1.1) process-nextick-args: 2.0.1 transitivePeerDependencies: - supports-color @@ -24323,7 +24413,7 @@ snapshots: dependencies: '@tediousjs/connection-string': 0.5.0 commander: 11.1.0 - debug: 4.3.6 + debug: 4.3.6(supports-color@8.1.1) rfdc: 1.3.0 tarn: 3.0.2 tedious: 16.7.1 @@ -24562,7 +24652,7 @@ snapshots: number-allocator@1.0.14: dependencies: - debug: 4.3.6 + debug: 4.3.6(supports-color@8.1.1) js-sdsl: 4.3.0 transitivePeerDependencies: - supports-color @@ -24755,7 +24845,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 @@ -25075,12 +25165,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: @@ -25179,12 +25270,6 @@ snapshots: ansi-styles: 5.2.0 react-is: 18.2.0 - pretty-format@29.6.2: - dependencies: - '@jest/schemas': 29.6.0 - ansi-styles: 5.2.0 - react-is: 18.2.0 - pretty-format@29.7.0: dependencies: '@jest/schemas': 29.6.3 @@ -26130,7 +26215,7 @@ snapshots: simple-websocket@9.1.0: dependencies: - debug: 4.3.6 + debug: 4.3.6(supports-color@8.1.1) queue-microtask: 1.2.3 randombytes: 2.1.0 readable-stream: 3.6.0 @@ -26210,7 +26295,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 @@ -26336,7 +26421,7 @@ snapshots: arg: 5.0.2 bluebird: 3.7.2 check-more-types: 2.24.0 - debug: 4.3.6 + debug: 4.3.6(supports-color@8.1.1) execa: 5.1.1 lazy-ass: 1.6.0 ps-tree: 1.2.0 @@ -26524,7 +26609,7 @@ snapshots: sucrase@3.35.0: dependencies: - '@jridgewell/gen-mapping': 0.3.2 + '@jridgewell/gen-mapping': 0.3.5 commander: 4.1.1 glob: 10.3.10 lines-and-columns: 1.2.4 @@ -26536,7 +26621,7 @@ snapshots: dependencies: component-emitter: 1.3.0 cookiejar: 2.1.4 - debug: 4.3.6 + debug: 4.3.6(supports-color@8.1.1) fast-safe-stringify: 2.1.1 form-data: 4.0.0 formidable: 3.5.1 @@ -26599,7 +26684,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 @@ -26618,7 +26703,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 @@ -26852,11 +26937,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 @@ -26871,6 +26956,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): @@ -27133,7 +27237,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 @@ -27148,7 +27252,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 @@ -27259,6 +27363,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 @@ -27283,7 +27390,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.6 + 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) @@ -27302,7 +27409,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 @@ -27340,7 +27447,7 @@ snapshots: '@vitest/utils': 1.6.0 acorn-walk: 8.3.2 chai: 4.3.10 - debug: 4.3.6 + debug: 4.3.6(supports-color@8.1.1) execa: 8.0.1 local-pkg: 0.5.0 magic-string: 0.30.10 @@ -27817,6 +27924,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: {} @@ -27846,3 +27956,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