diff --git a/packages/@n8n/task-runner/jest.config.js b/packages/@n8n/task-runner/jest.config.js deleted file mode 100644 index 5c3abe1ef78..00000000000 --- a/packages/@n8n/task-runner/jest.config.js +++ /dev/null @@ -1,5 +0,0 @@ -/** @type {import('jest').Config} */ -module.exports = { - ...require('../../../jest.config'), - testTimeout: 10_000, -}; diff --git a/packages/@n8n/task-runner/package.json b/packages/@n8n/task-runner/package.json index c9710de6aab..fc39574a56d 100644 --- a/packages/@n8n/task-runner/package.json +++ b/packages/@n8n/task-runner/package.json @@ -10,9 +10,9 @@ "build": "tsc -p ./tsconfig.build.json && tsc-alias -p tsconfig.build.json", "format": "biome format --write src", "format:check": "biome ci src", - "test": "jest", - "test:unit": "jest", - "test:watch": "jest --watch", + "test": "vitest run", + "test:unit": "vitest run", + "test:dev": "vitest --silent=false", "lint": "eslint . --quiet", "lint:fix": "eslint . --fix", "watch": "tsc-watch -p tsconfig.build.json --onCompilationComplete \"tsc-alias -p tsconfig.build.json\"" @@ -51,6 +51,10 @@ }, "devDependencies": { "@n8n/typescript-config": "workspace:*", - "@types/lodash": "catalog:" + "@n8n/vitest-config": "workspace:*", + "@types/lodash": "catalog:", + "@vitest/coverage-v8": "catalog:", + "vitest": "catalog:", + "vitest-mock-extended": "catalog:" } } diff --git a/packages/@n8n/task-runner/src/__tests__/task-runner-sentry.test.ts b/packages/@n8n/task-runner/src/__tests__/task-runner-sentry.test.ts index b70bd8bd92a..6638d190c82 100644 --- a/packages/@n8n/task-runner/src/__tests__/task-runner-sentry.test.ts +++ b/packages/@n8n/task-runner/src/__tests__/task-runner-sentry.test.ts @@ -1,6 +1,6 @@ import type { ErrorEvent } from '@sentry/core'; -import { mock } from 'jest-mock-extended'; import type { ErrorReporter } from 'n8n-core'; +import { mock } from 'vitest-mock-extended'; import { TaskRunnerSentry } from '../task-runner-sentry'; @@ -14,7 +14,7 @@ describe('TaskRunnerSentry', () => { }; afterEach(() => { - jest.resetAllMocks(); + vi.resetAllMocks(); }); describe('filterOutUserCodeErrors', () => { diff --git a/packages/@n8n/task-runner/src/data-request/__tests__/data-request-response-reconstruct.test.ts b/packages/@n8n/task-runner/src/data-request/__tests__/data-request-response-reconstruct.test.ts index 40768bb5cc6..67575945d3f 100644 --- a/packages/@n8n/task-runner/src/data-request/__tests__/data-request-response-reconstruct.test.ts +++ b/packages/@n8n/task-runner/src/data-request/__tests__/data-request-response-reconstruct.test.ts @@ -1,10 +1,10 @@ -import { mock } from 'jest-mock-extended'; import type { IExecuteData, INode, INodeExecutionData, ITaskDataConnectionsSource, } from 'n8n-workflow'; +import { mock } from 'vitest-mock-extended'; import type { DataRequestResponse, InputDataChunkDefinition } from '@/runner-types'; @@ -32,7 +32,7 @@ describe('DataRequestResponseReconstruct', () => { const result = reconstruct.reconstructConnectionInputItems(inputData, chunk); - expect(result).toEqual([undefined, undefined, { json: { key: 'chunked' } }, undefined]); + expect(result).toEqual([undefined, undefined, { json: { key: 'chunked' } }]); }); it('should handle empty input data gracefully', () => { diff --git a/packages/@n8n/task-runner/src/data-request/data-request-response-reconstruct.ts b/packages/@n8n/task-runner/src/data-request/data-request-response-reconstruct.ts index 29f50c36108..f9ecc3a1b80 100644 --- a/packages/@n8n/task-runner/src/data-request/data-request-response-reconstruct.ts +++ b/packages/@n8n/task-runner/src/data-request/data-request-response-reconstruct.ts @@ -19,14 +19,17 @@ export class DataRequestResponseReconstruct { return inputItems; } - // Only a chunk of the input items was requested. We reconstruct - // the array by filling in the missing items with `undefined`. + // Only a chunk of the input items was requested (the sender slices the data down to + // the chunk). We reconstruct the array by prefixing the chunk with `undefined` for the + // items before `startIndex`, so the chunk items land at their original indices — + // WorkflowDataProxy addresses items by position. Items after the chunk are never + // iterated, and the original total length isn't recoverable from the chunk, so we + // don't pad the tail. let sparseInputItems: Array = []; sparseInputItems = sparseInputItems .concat(Array.from({ length: chunk.startIndex })) - .concat(inputItems) - .concat(Array.from({ length: inputItems.length - chunk.startIndex - chunk.count })); + .concat(inputItems); return sparseInputItems; } diff --git a/packages/@n8n/task-runner/src/js-task-runner/__tests__/js-task-runner.test.ts b/packages/@n8n/task-runner/src/js-task-runner/__tests__/js-task-runner.test.ts index 20595802171..d6be58c673b 100644 --- a/packages/@n8n/task-runner/src/js-task-runner/__tests__/js-task-runner.test.ts +++ b/packages/@n8n/task-runner/src/js-task-runner/__tests__/js-task-runner.test.ts @@ -30,7 +30,7 @@ import { wrapIntoJson, } from './test-data'; -jest.mock('ws'); +vi.mock('ws'); const defaultConfig = new MainConfig(); defaultConfig.jsRunnerConfig ??= { @@ -78,12 +78,12 @@ describe('JsTaskRunner', () => { taskData: DataRequestResponse; runner?: JsTaskRunner; }) => { - jest.spyOn(runner, 'requestData').mockResolvedValue(taskData); + vi.spyOn(runner, 'requestData').mockResolvedValue(taskData); return await runner.executeTask(task, new AbortController().signal); }; afterEach(() => { - jest.restoreAllMocks(); + vi.restoreAllMocks(); }); const executeForAllItems = async ({ @@ -236,7 +236,7 @@ describe('JsTaskRunner', () => { test.each<[CodeExecutionMode]>([['runOnceForAllItems'], ['runOnceForEachItem']])( 'should make an rpc call for console log in %s mode', async (nodeMode) => { - jest.spyOn(defaultTaskRunner, 'makeRpcCall').mockResolvedValue(undefined); + vi.spyOn(defaultTaskRunner, 'makeRpcCall').mockResolvedValue(undefined); const task = newTaskParamsWithSettings({ code: "console.log('Hello', 'world!'); return {}", nodeMode, @@ -302,7 +302,7 @@ describe('JsTaskRunner', () => { }); it('should log the context object as [[ExecutionContext]]', async () => { - const rpcCallSpy = jest.spyOn(defaultTaskRunner, 'makeRpcCall').mockResolvedValue(undefined); + const rpcCallSpy = vi.spyOn(defaultTaskRunner, 'makeRpcCall').mockResolvedValue(undefined); const task = newTaskParamsWithSettings({ code: ` @@ -777,7 +777,7 @@ describe('JsTaskRunner', () => { for (const group of groups) { it(`${group.method} for runOnceForAllItems`, async () => { // Arrange - const rpcCallSpy = jest + const rpcCallSpy = vi .spyOn(defaultTaskRunner, 'makeRpcCall') .mockResolvedValue(undefined); @@ -799,7 +799,7 @@ describe('JsTaskRunner', () => { it(`${group.method} for runOnceForEachItem`, async () => { // Arrange - const rpcCallSpy = jest + const rpcCallSpy = vi .spyOn(defaultTaskRunner, 'makeRpcCall') .mockResolvedValue(undefined); @@ -1347,11 +1347,11 @@ describe('JsTaskRunner', () => { }; runner.runningTasks.set(taskId, task); - const sendSpy = jest.spyOn(runner.ws, 'send').mockImplementation(() => {}); - jest.spyOn(runner, 'sendOffers').mockImplementation(() => {}); - jest - .spyOn(runner, 'requestData') - .mockResolvedValue(newDataRequestResponse([wrapIntoJson({ a: 1 })])); + const sendSpy = vi.spyOn(runner.ws, 'send').mockImplementation(() => {}); + vi.spyOn(runner, 'sendOffers').mockImplementation(() => {}); + vi.spyOn(runner, 'requestData').mockResolvedValue( + newDataRequestResponse([wrapIntoJson({ a: 1 })]), + ); await runner.receivedSettings(taskId, taskSettings); @@ -1374,22 +1374,22 @@ describe('JsTaskRunner', () => { describe('idle timeout', () => { beforeEach(() => { - jest.useFakeTimers(); + vi.useFakeTimers(); }); afterEach(() => { - jest.useRealTimers(); + vi.useRealTimers(); }); it('should set idle timer when instantiated', () => { const idleTimeout = 5; const runner = createRunnerWithOpts({}, { idleTimeout }); - const emitSpy = jest.spyOn(runner, 'emit'); + const emitSpy = vi.spyOn(runner, 'emit'); - jest.advanceTimersByTime(idleTimeout * 1000 - 100); + vi.advanceTimersByTime(idleTimeout * 1000 - 100); expect(emitSpy).not.toHaveBeenCalledWith('runner:reached-idle-timeout'); - jest.advanceTimersByTime(idleTimeout * 1000); + vi.advanceTimersByTime(idleTimeout * 1000); expect(emitSpy).toHaveBeenCalledWith('runner:reached-idle-timeout'); }); @@ -1398,9 +1398,9 @@ describe('JsTaskRunner', () => { const runner = createRunnerWithOpts({}, { idleTimeout }); const taskId = '123'; const offerId = 'offer123'; - const emitSpy = jest.spyOn(runner, 'emit'); + const emitSpy = vi.spyOn(runner, 'emit'); - jest.advanceTimersByTime(idleTimeout * 1000 - 100); + vi.advanceTimersByTime(idleTimeout * 1000 - 100); expect(emitSpy).not.toHaveBeenCalledWith('runner:reached-idle-timeout'); runner.openOffers.set(offerId, { @@ -1409,12 +1409,12 @@ describe('JsTaskRunner', () => { }); runner.offerAccepted(offerId, taskId); - jest.advanceTimersByTime(200); + vi.advanceTimersByTime(200); expect(emitSpy).not.toHaveBeenCalledWith('runner:reached-idle-timeout'); // because timer was reset runner.runningTasks.clear(); - jest.advanceTimersByTime(idleTimeout * 1000); + vi.advanceTimersByTime(idleTimeout * 1000); expect(emitSpy).toHaveBeenCalledWith('runner:reached-idle-timeout'); }); @@ -1422,28 +1422,28 @@ describe('JsTaskRunner', () => { const idleTimeout = 5; const runner = createRunnerWithOpts({}, { idleTimeout }); const taskId = '123'; - const emitSpy = jest.spyOn(runner, 'emit'); - jest.spyOn(runner, 'executeTask').mockResolvedValue({ result: [] }); + const emitSpy = vi.spyOn(runner, 'emit'); + vi.spyOn(runner, 'executeTask').mockResolvedValue({ result: [] }); runner.runningTasks.set(taskId, newTaskState(taskId)); - jest.advanceTimersByTime(idleTimeout * 1000 - 100); + vi.advanceTimersByTime(idleTimeout * 1000 - 100); expect(emitSpy).not.toHaveBeenCalledWith('runner:reached-idle-timeout'); await runner.receivedSettings(taskId, {}); - jest.advanceTimersByTime(200); + vi.advanceTimersByTime(200); expect(emitSpy).not.toHaveBeenCalledWith('runner:reached-idle-timeout'); // because timer was reset - jest.advanceTimersByTime(idleTimeout * 1000); + vi.advanceTimersByTime(idleTimeout * 1000); expect(emitSpy).toHaveBeenCalledWith('runner:reached-idle-timeout'); }); it('should never reach idle timeout if idle timeout is set to 0', () => { const runner = createRunnerWithOpts({}, { idleTimeout: 0 }); - const emitSpy = jest.spyOn(runner, 'emit'); + const emitSpy = vi.spyOn(runner, 'emit'); - jest.advanceTimersByTime(999999); + vi.advanceTimersByTime(999999); expect(emitSpy).not.toHaveBeenCalledWith('runner:reached-idle-timeout'); }); @@ -1451,12 +1451,12 @@ describe('JsTaskRunner', () => { const idleTimeout = 5; const runner = createRunnerWithOpts({}, { idleTimeout }); const taskId = '123'; - const emitSpy = jest.spyOn(runner, 'emit'); + const emitSpy = vi.spyOn(runner, 'emit'); const task = newTaskState(taskId); runner.runningTasks.set(taskId, task); - jest.advanceTimersByTime(idleTimeout * 1000); + vi.advanceTimersByTime(idleTimeout * 1000); expect(emitSpy).not.toHaveBeenCalledWith('runner:reached-idle-timeout'); task.cleanup(); }); @@ -1764,11 +1764,11 @@ describe('JsTaskRunner', () => { }; runner.runningTasks.set(taskId, task); - const sendSpy = jest.spyOn(runner.ws, 'send').mockImplementation(() => {}); - jest.spyOn(runner, 'sendOffers').mockImplementation(() => {}); - jest - .spyOn(runner, 'requestData') - .mockResolvedValue(newDataRequestResponse([wrapIntoJson({ a: 1 })])); + const sendSpy = vi.spyOn(runner.ws, 'send').mockImplementation(() => {}); + vi.spyOn(runner, 'sendOffers').mockImplementation(() => {}); + vi.spyOn(runner, 'requestData').mockResolvedValue( + newDataRequestResponse([wrapIntoJson({ a: 1 })]), + ); await runner.receivedSettings(taskId, taskSettings); @@ -1817,7 +1817,7 @@ describe('JsTaskRunner', () => { }); // runCode mode doesn't fetch data, so we can pass empty response - jest.spyOn(runner, 'requestData').mockResolvedValue(newDataRequestResponse([])); + vi.spyOn(runner, 'requestData').mockResolvedValue(newDataRequestResponse([])); return await runner.executeTask(task, new AbortController().signal); }; @@ -1958,7 +1958,7 @@ describe('JsTaskRunner', () => { describe('console methods', () => { it('should allow console.log without making RPC calls', async () => { - const rpcSpy = jest.spyOn(defaultTaskRunner, 'makeRpcCall'); + const rpcSpy = vi.spyOn(defaultTaskRunner, 'makeRpcCall'); const outcome = await executeRunCode({ code: ` diff --git a/packages/@n8n/task-runner/src/js-task-runner/__tests__/task-runner.test.ts b/packages/@n8n/task-runner/src/js-task-runner/__tests__/task-runner.test.ts index 19caa0f113b..c14c6aa81d9 100644 --- a/packages/@n8n/task-runner/src/js-task-runner/__tests__/task-runner.test.ts +++ b/packages/@n8n/task-runner/src/js-task-runner/__tests__/task-runner.test.ts @@ -1,3 +1,4 @@ +import type { Mock, MockInstance } from 'vitest'; import { WebSocket } from 'ws'; import { newTaskState } from '@/js-task-runner/__tests__/test-data'; @@ -7,7 +8,7 @@ import type { TaskStatus } from '@/task-state'; class TestRunner extends TaskRunner {} -jest.mock('ws'); +vi.mock('ws'); describe('TestRunner', () => { let runner: TestRunner; @@ -36,7 +37,7 @@ describe('TestRunner', () => { describe('constructor', () => { afterEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); it('should correctly construct WebSocket URI with provided taskBrokerUri', () => { @@ -82,11 +83,11 @@ describe('TestRunner', () => { describe('sendOffers', () => { beforeEach(() => { - jest.useFakeTimers(); + vi.useFakeTimers(); }); afterEach(() => { - jest.clearAllTimers(); + vi.clearAllTimers(); }); it('should not send offers if canSendOffers is false', () => { @@ -94,7 +95,7 @@ describe('TestRunner', () => { taskType: 'test-task', maxConcurrency: 2, }); - const sendSpy = jest.spyOn(runner, 'send'); + const sendSpy = vi.spyOn(runner, 'send'); expect(runner.canSendOffers).toBe(false); runner.sendOffers(); @@ -123,7 +124,7 @@ describe('TestRunner', () => { type: 'broker:runnerregistered', }); - const sendSpy = jest.spyOn(runner, 'send'); + const sendSpy = vi.spyOn(runner, 'send'); runner.sendOffers(); runner.sendOffers(); @@ -159,7 +160,7 @@ describe('TestRunner', () => { }); const taskState = newTaskState('test-task'); runner.runningTasks.set('test-task', taskState); - const sendSpy = jest.spyOn(runner, 'send'); + const sendSpy = vi.spyOn(runner, 'send'); runner.sendOffers(); @@ -186,13 +187,13 @@ describe('TestRunner', () => { type: 'broker:runnerregistered', }); - const sendSpy = jest.spyOn(runner, 'send'); + const sendSpy = vi.spyOn(runner, 'send'); runner.sendOffers(); expect(sendSpy).toHaveBeenCalledTimes(2); sendSpy.mockClear(); - jest.advanceTimersByTime(6000); + vi.advanceTimersByTime(6000); runner.sendOffers(); expect(sendSpy).toHaveBeenCalledTimes(2); }); @@ -222,7 +223,7 @@ describe('TestRunner', () => { const taskId = 'test-task'; const task = newTaskState(taskId); - const taskCleanupSpy = jest.spyOn(task, 'cleanup'); + const taskCleanupSpy = vi.spyOn(task, 'cleanup'); runner.runningTasks.set(taskId, task); await runner.taskCancelled(taskId, 'test-reason'); @@ -239,20 +240,20 @@ describe('TestRunner', () => { task.status = 'running'; runner.runningTasks.set(taskId, task); - const dataRequestReject = jest.fn(); - const nodeTypesRequestReject = jest.fn(); + const dataRequestReject = vi.fn(); + const nodeTypesRequestReject = vi.fn(); runner.dataRequests.set('data-req', { taskId, requestId: 'data-req', - resolve: jest.fn(), + resolve: vi.fn(), reject: dataRequestReject, }); runner.nodeTypesRequests.set('node-req', { taskId, requestId: 'node-req', - resolve: jest.fn(), + resolve: vi.fn(), reject: nodeTypesRequestReject, }); @@ -283,7 +284,7 @@ describe('TestRunner', () => { const task = newTaskState(taskId); task.status = 'waitingForSettings'; runner.runningTasks.set(taskId, task); - const sendSpy = jest.spyOn(runner, 'send'); + const sendSpy = vi.spyOn(runner, 'send'); await runner.taskTimedOut(taskId); @@ -303,20 +304,20 @@ describe('TestRunner', () => { task.status = 'running'; runner.runningTasks.set(taskId, task); - const dataRequestReject = jest.fn(); - const nodeTypesRequestReject = jest.fn(); + const dataRequestReject = vi.fn(); + const nodeTypesRequestReject = vi.fn(); runner.dataRequests.set('data-req', { taskId, requestId: 'data-req', - resolve: jest.fn(), + resolve: vi.fn(), reject: dataRequestReject, }); runner.nodeTypesRequests.set('node-req', { taskId, requestId: 'node-req', - resolve: jest.fn(), + resolve: vi.fn(), reject: nodeTypesRequestReject, }); @@ -349,7 +350,7 @@ describe('TestRunner', () => { void runner.stop(); - const sendSpy = jest.spyOn(runner, 'send'); + const sendSpy = vi.spyOn(runner, 'send'); runner.offerAccepted(offerId, 'task-1'); @@ -387,10 +388,10 @@ describe('TestRunner', () => { }); describe('connection close', () => { - let processExitSpy: jest.SpyInstance; + let processExitSpy: MockInstance; beforeEach(() => { - processExitSpy = jest.spyOn(process, 'exit').mockImplementation(() => undefined as never); + processExitSpy = vi.spyOn(process, 'exit').mockImplementation(() => undefined as never); }); afterEach(() => { @@ -401,8 +402,8 @@ describe('TestRunner', () => { runner = newTestRunner(); // Get the close handler that was registered - const closeHandler = (runner.ws.addEventListener as jest.Mock).mock.calls.find( - ([event]: [string]) => event === 'close', + const closeHandler = (runner.ws.addEventListener as unknown as Mock).mock.calls.find( + ([event]: unknown[]) => event === 'close', )?.[1] as () => void; expect(closeHandler).toBeDefined(); @@ -415,8 +416,8 @@ describe('TestRunner', () => { runner = newTestRunner(); // Get the close handler registered via addEventListener in constructor - const closeHandler = (runner.ws.addEventListener as jest.Mock).mock.calls.find( - ([event]: [string]) => event === 'close', + const closeHandler = (runner.ws.addEventListener as unknown as Mock).mock.calls.find( + ([event]: unknown[]) => event === 'close', )?.[1] as () => void; expect(closeHandler).toBeDefined(); diff --git a/packages/@n8n/task-runner/tsconfig.json b/packages/@n8n/task-runner/tsconfig.json index b217a9eee18..b3eed675db1 100644 --- a/packages/@n8n/task-runner/tsconfig.json +++ b/packages/@n8n/task-runner/tsconfig.json @@ -6,6 +6,7 @@ "compilerOptions": { "emitDecoratorMetadata": true, "experimentalDecorators": true, + "types": ["node", "vitest/globals"], "paths": { "@/*": ["./src/*"] }, diff --git a/packages/@n8n/task-runner/vite.config.ts b/packages/@n8n/task-runner/vite.config.ts new file mode 100644 index 00000000000..e1edff36374 --- /dev/null +++ b/packages/@n8n/task-runner/vite.config.ts @@ -0,0 +1,19 @@ +import { createVitestConfigWithDecorators } from '@n8n/vitest-config/node-decorators'; +import path from 'node:path'; +import { mergeConfig } from 'vite'; + +export default mergeConfig( + createVitestConfigWithDecorators({ + // The n8n root jest.config sets `restoreMocks: true`, and test files silently rely on + // it — omit this and mocks bleed between tests. + restoreMocks: true, + testTimeout: 10_000, + }), + { + resolve: { + alias: { + '@': path.resolve(__dirname, './src'), + }, + }, + }, +); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7fe455082d8..8a6b1ef4668 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2684,9 +2684,21 @@ importers: '@n8n/typescript-config': specifier: workspace:* version: link:../typescript-config + '@n8n/vitest-config': + specifier: workspace:* + version: link:../vitest-config '@types/lodash': specifier: 'catalog:' version: 4.17.17 + '@vitest/coverage-v8': + specifier: 'catalog:' + version: 4.1.1(vitest@4.1.1) + vitest: + specifier: 'catalog:' + version: 4.1.1(@opentelemetry/api@1.9.0)(@types/node@20.19.21)(@vitest/browser-playwright@4.0.16)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@types/node@20.19.21)(esbuild@0.25.10)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + vitest-mock-extended: + specifier: 'catalog:' + version: 3.1.0(typescript@6.0.2)(vitest@4.1.1(@opentelemetry/api@1.9.0)(@types/node@20.19.21)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@types/node@20.19.21)(esbuild@0.25.10)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3))) packages/@n8n/tournament: dependencies: @@ -24632,7 +24644,7 @@ snapshots: '@currents/commit-info': 1.0.1-beta.0 async-retry: 1.3.3 axios: 1.16.1(debug@4.4.3) - axios-retry: 4.5.0(axios@1.16.1(debug@4.4.3)) + axios-retry: 4.5.0(axios@1.16.1) chalk: 4.1.2 commander: 13.1.0 date-fns: 2.30.0 @@ -28022,7 +28034,7 @@ snapshots: '@rudderstack/rudder-sdk-node@3.0.5': dependencies: axios: 1.16.1(debug@4.4.3) - axios-retry: 4.5.0(axios@1.16.1(debug@4.4.3)) + axios-retry: 4.5.0(axios@1.16.1) component-type: 2.0.0 join-component: 1.1.0 lodash.clonedeep: 4.5.0 @@ -31190,7 +31202,7 @@ snapshots: axe-core@4.7.2: {} - axios-retry@4.5.0(axios@1.16.1(debug@4.4.3)): + axios-retry@4.5.0(axios@1.16.1): dependencies: axios: 1.16.1(debug@4.4.3) is-retry-allowed: 2.2.0