diff --git a/package.json b/package.json index 405ae511a..82e639667 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,8 @@ "husky": "^8.0.3", "prettier": "^3.3.0", "typescript": "^5.3.0", + "find-up": "^2.1.0", + "@types/find-up": "^2.1.1", "wsrun": "^5.2.2" }, "scripts": { diff --git a/packages/hardhat-zksync-deploy/src/core/config-env.ts b/packages/hardhat-zksync-deploy/src/core/config-env.ts new file mode 100644 index 000000000..c69d2d568 --- /dev/null +++ b/packages/hardhat-zksync-deploy/src/core/config-env.ts @@ -0,0 +1,27 @@ +import { ActionType, ConfigurableTaskDefinition, TaskArguments } from 'hardhat/types'; +import { HardhatContext } from 'hardhat/internal/context'; +import { ZKSyncTasksForWrapping, ZKSyncTasksWithWrappedNode } from './global-tasks'; + +export function taskWithEraTestNode( + name: string, + description?: string, + withNode?: boolean, + action?: ActionType, +): ConfigurableTaskDefinition { + const ctx = HardhatContext.getHardhatContext(); + const dsl = ctx.tasksDSL; + + if (withNode) { + if (!(global as ZKSyncTasksWithWrappedNode)._zkSyncTasksForWrapping) { + (global as ZKSyncTasksWithWrappedNode)._zkSyncTasksForWrapping = new ZKSyncTasksForWrapping(); + } + + (global as ZKSyncTasksWithWrappedNode)._zkSyncTasksForWrapping.addTask(name); + } + + if (description === undefined) { + return dsl.task(name); + } + + return dsl.task(name, description, action); +} diff --git a/packages/hardhat-zksync-deploy/src/core/global-tasks.ts b/packages/hardhat-zksync-deploy/src/core/global-tasks.ts new file mode 100644 index 000000000..9f35a8057 --- /dev/null +++ b/packages/hardhat-zksync-deploy/src/core/global-tasks.ts @@ -0,0 +1,18 @@ +import { Network } from 'hardhat/types'; + +export type ZKSyncTasksWithWrappedNode = typeof global & { + _zkSyncTasksForWrapping: ZKSyncTasksForWrapping; + _zkSyncNodeNetwork?: Network; +}; + +export class ZKSyncTasksForWrapping { + public taskNames: string[] = []; + + constructor() { + this.taskNames = []; + } + + public addTask(taskName: string) { + this.taskNames.push(taskName); + } +} diff --git a/packages/hardhat-zksync-deploy/src/index.ts b/packages/hardhat-zksync-deploy/src/index.ts index a276db620..a430e9336 100644 --- a/packages/hardhat-zksync-deploy/src/index.ts +++ b/packages/hardhat-zksync-deploy/src/index.ts @@ -1,4 +1,4 @@ -import { extendConfig, extendEnvironment, task, types } from 'hardhat/config'; +import { extendConfig, extendEnvironment, types } from 'hardhat/config'; import chalk from 'chalk'; import { string } from 'hardhat/internal/core/params/argumentTypes'; import { TASK_DEPLOY_ZKSYNC, TASK_DEPLOY_ZKSYNC_LIBRARIES, TASK_DEPLOY_ZKSYNC_CONTRACT } from './task-names'; @@ -6,7 +6,7 @@ import './type-extensions'; import { deployZkSyncContract, zkSyncDeploy, zkSyncLibraryDeploy } from './task-actions'; import { DEFAULT_DEPLOY_SCRIPTS_PATH, defaultAccountDeployerSettings } from './constants'; import { DeployerExtension } from './deployer-extension'; - +import { taskWithEraTestNode } from './core/config-env'; export * from './deployer'; extendConfig((config, userConfig) => { @@ -27,12 +27,12 @@ extendEnvironment((hre) => { hre.deployer = new DeployerExtension(hre); }); -task(TASK_DEPLOY_ZKSYNC, 'Runs the deploy scripts for ZKsync network') +taskWithEraTestNode(TASK_DEPLOY_ZKSYNC, 'Runs the deploy scripts for ZKsync network', true) .addParam('script', 'A certain deploy script to be launched', '') .addOptionalParam('tags', 'specify which deploy script to execute via tags, separated by commas', undefined, string) .setAction(zkSyncDeploy); -task(TASK_DEPLOY_ZKSYNC_LIBRARIES, 'Runs the library deploy for ZKsync network') +taskWithEraTestNode(TASK_DEPLOY_ZKSYNC_LIBRARIES, 'Runs the library deploy for ZKsync network', true) .addOptionalParam( 'privateKeyOrIndex', 'Private key or index of the account that will deploy the libraries', @@ -53,7 +53,7 @@ task(TASK_DEPLOY_ZKSYNC_LIBRARIES, 'Runs the library deploy for ZKsync network') .addFlag('compileAllContracts', 'Flag to compile all contracts at the end of the process') .setAction(zkSyncLibraryDeploy); -task(TASK_DEPLOY_ZKSYNC_CONTRACT, 'Runs the deploy scripts for ZKsync network') +taskWithEraTestNode(TASK_DEPLOY_ZKSYNC_CONTRACT, 'Runs the deploy scripts for ZKsync network', true) .addParam('contractName', 'A contract name or a FQN', '') .addOptionalVariadicPositionalParam( 'constructorArgsParams', diff --git a/packages/hardhat-zksync-node/package.json b/packages/hardhat-zksync-node/package.json index 8edb1bc5b..9a15e182a 100644 --- a/packages/hardhat-zksync-node/package.json +++ b/packages/hardhat-zksync-node/package.json @@ -41,7 +41,9 @@ "chai": "^4.3.4", "undici": "^6.18.2", "sinon-chai": "^3.7.0", - "sinon": "^18.0.0" + "sinon": "^18.0.0", + "source-map-support": "^0.5.21", + "debug": "^4.3.5" }, "devDependencies": { "@types/chai": "^4.3.16", diff --git a/packages/hardhat-zksync-node/src/core/global-interceptor.ts b/packages/hardhat-zksync-node/src/core/global-interceptor.ts new file mode 100644 index 000000000..862b7db0a --- /dev/null +++ b/packages/hardhat-zksync-node/src/core/global-interceptor.ts @@ -0,0 +1,51 @@ +import { RunSuperFunction, TaskArguments } from 'hardhat/types'; +import { GlobalWithHardhatContext } from 'hardhat/src/internal/context'; +import { HARDHAT_NETWORK_NAME } from 'hardhat/plugins'; +import { configureNetwork, startServer, waitForNodeToBeReady } from '../utils'; +import { ZKSyncTasksWithWrappedNode } from './global-task'; + +export function interceptAndWrapTasksWithNode() { + const zkSyncGlobal = global as ZKSyncTasksWithWrappedNode & GlobalWithHardhatContext; + const taskMap = zkSyncGlobal.__hardhatContext.tasksDSL.getTaskDefinitions(); + + if (!zkSyncGlobal._zkSyncTasksForWrapping) { + return; + } + + zkSyncGlobal._zkSyncTasksForWrapping.taskNames.forEach((taskName) => { + const foundTask = taskMap[taskName]; + + if (!foundTask) { + return; + } + + if (foundTask.isSubtask) { + zkSyncGlobal.__hardhatContext.tasksDSL.subtask(foundTask.name, foundTask.description, wrapTaskWithNode); + } + + zkSyncGlobal.__hardhatContext.tasksDSL.task(foundTask.name, foundTask.description, wrapTaskWithNode); + }); +} + +async function wrapTaskWithNode(taskArgs: TaskArguments, env: any, runSuper: RunSuperFunction) { + if (env.network.zksync !== true || env.network.name !== HARDHAT_NETWORK_NAME) { + return await runSuper(taskArgs); + } + const zkSyncGlobal = global as ZKSyncTasksWithWrappedNode; + const { commandArgs, server, port } = await startServer(); + try { + await server.listen(commandArgs, false); + await waitForNodeToBeReady(port); + const oldNetwork = env.network; + await configureNetwork(env.config, env.network, port); + env.injectToGlobal(); + zkSyncGlobal._zkSyncNodeNetwork = env.network; + const result = await runSuper(taskArgs); + env.network = oldNetwork; + delete zkSyncGlobal._zkSyncNodeNetwork; + env.injectToGlobal(); + return result; + } finally { + await server.stop(); + } +} diff --git a/packages/hardhat-zksync-node/src/core/global-task.ts b/packages/hardhat-zksync-node/src/core/global-task.ts new file mode 100644 index 000000000..9f35a8057 --- /dev/null +++ b/packages/hardhat-zksync-node/src/core/global-task.ts @@ -0,0 +1,18 @@ +import { Network } from 'hardhat/types'; + +export type ZKSyncTasksWithWrappedNode = typeof global & { + _zkSyncTasksForWrapping: ZKSyncTasksForWrapping; + _zkSyncNodeNetwork?: Network; +}; + +export class ZKSyncTasksForWrapping { + public taskNames: string[] = []; + + constructor() { + this.taskNames = []; + } + + public addTask(taskName: string) { + this.taskNames.push(taskName); + } +} diff --git a/packages/hardhat-zksync-node/src/core/register.ts b/packages/hardhat-zksync-node/src/core/register.ts new file mode 100644 index 000000000..bf2e3b37a --- /dev/null +++ b/packages/hardhat-zksync-node/src/core/register.ts @@ -0,0 +1,77 @@ +import debug from 'debug'; + +import { HardhatContext } from 'hardhat/internal/context'; +import { loadConfigAndTasks } from 'hardhat/internal/core/config/config-loading'; +import { getEnvHardhatArguments } from 'hardhat/internal/core/params/env-variables'; +import { HARDHAT_PARAM_DEFINITIONS } from 'hardhat/internal/core/params/hardhat-params'; +import { Environment } from 'hardhat/internal/core/runtime-environment'; +import { loadTsNode, willRunWithTypescript } from 'hardhat/internal/core/typescript-support'; +import { disableReplWriterShowProxy, isNodeCalledWithoutAScript } from 'hardhat/internal/util/console'; +import { LazyInitializationProviderAdapter } from 'hardhat/internal/core/providers/lazy-initialization'; +import { log } from 'console'; +import { createProvider } from 'hardhat/internal/core/providers/construction'; +import { MessageTrace } from 'hardhat/internal/hardhat-network/stack-traces/message-trace'; +import { Artifacts } from 'hardhat/internal/artifacts'; +import { BASE_URL, ZKSYNC_ERA_TEST_NODE_NETWORK_NAME } from '../constants'; +import { getNetworkConfig } from '../utils'; + +if (!HardhatContext.isCreated()) { + require('source-map-support/register'); + + const ctx = HardhatContext.createHardhatContext(); + + if (isNodeCalledWithoutAScript()) { + disableReplWriterShowProxy(); + } + + const hardhatArguments = getEnvHardhatArguments(HARDHAT_PARAM_DEFINITIONS, process.env); + + if (hardhatArguments.verbose) { + debug.enable('hardhat*'); + } + + if (willRunWithTypescript(hardhatArguments.config)) { + loadTsNode(hardhatArguments.tsconfig, hardhatArguments.typecheck); + } + + const { resolvedConfig, userConfig } = loadConfigAndTasks(hardhatArguments); + + const env = new Environment( + resolvedConfig, + hardhatArguments, + ctx.tasksDSL.getTaskDefinitions(), + ctx.tasksDSL.getScopesDefinitions(), + ctx.environmentExtenders, + ctx.experimentalHardhatNetworkMessageTraceHooks, + userConfig, + ctx.providerExtenders, + ); + + const zksyncNodePort = process.env.ZKNodePort; + const url = `${BASE_URL}:${zksyncNodePort}`; + const networkName = ZKSYNC_ERA_TEST_NODE_NETWORK_NAME; + + env.network.name = networkName; + Object.assign(env.network.config, getNetworkConfig(url)); + resolvedConfig.networks[env.network.name] = env.network.config; + + const artifacts = new Artifacts(resolvedConfig.paths.artifacts); + + const provider = new LazyInitializationProviderAdapter(async () => { + log(`Creating provider for network ${networkName}`); + return createProvider( + resolvedConfig, + networkName, + artifacts, + ctx.experimentalHardhatNetworkMessageTraceHooks.map( + (hook) => (trace: MessageTrace, isCallMessageTrace: boolean) => hook(env, trace, isCallMessageTrace), + ), + ctx.providerExtenders, + ); + }); + + env.network.provider = provider; + ctx.setHardhatRuntimeEnvironment(env); + + env.injectToGlobal(); +} diff --git a/packages/hardhat-zksync-node/src/core/script-runner.ts b/packages/hardhat-zksync-node/src/core/script-runner.ts new file mode 100644 index 000000000..69c96ec01 --- /dev/null +++ b/packages/hardhat-zksync-node/src/core/script-runner.ts @@ -0,0 +1,87 @@ +import { isRunningHardhatCoreTests } from 'hardhat/internal/core/execution-mode'; +import { HardhatArguments } from 'hardhat/types'; +import { getEnvVariablesMap } from 'hardhat/internal/core/params/env-variables'; +import path from 'path'; +import { startServer, waitForNodeToBeReady } from '../utils'; + +export async function runScript( + scriptPath: string, + scriptArgs: string[] = [], + extraNodeArgs: string[] = [], + extraEnvVars: { [name: string]: string } = {}, +): Promise { + const { fork } = await import('child_process'); + const processExecArgv = withFixedInspectArg(process.execArgv); + + const nodeArgs = [ + ...processExecArgv, + ...getTsNodeArgsIfNeeded(scriptPath, extraEnvVars.HARDHAT_TYPECHECK === 'true'), + ...extraNodeArgs, + ]; + + const { commandArgs, server, port } = await startServer(); + await server.listen(commandArgs, false); + await waitForNodeToBeReady(port); + + const envVars = { ...process.env, ...extraEnvVars, ZKNodePort: port.toString() }; + + return new Promise((resolve, reject) => { + const childProcess = fork(scriptPath, scriptArgs, { + stdio: 'inherit', + execArgv: nodeArgs, + env: envVars, + }); + + childProcess.once('close', async (status) => { + await server.stop(); + resolve(status as number); + }); + + childProcess.once('error', async (error) => { + await server.stop(); + reject(error); + }); + }); +} + +export async function runScriptWithHardhat( + hardhatArguments: HardhatArguments, + scriptPath: string, + scriptArgs: string[] = [], + extraNodeArgs: string[] = [], + extraEnvVars: { [name: string]: string } = {}, +): Promise { + return runScript(scriptPath, scriptArgs, [...extraNodeArgs, '--require', path.join(__dirname, 'register')], { + ...getEnvVariablesMap(hardhatArguments), + ...extraEnvVars, + }); +} + +function withFixedInspectArg(argv: string[]) { + const fixIfInspectArg = (arg: string) => { + if (arg.toLowerCase().includes('--inspect-brk=')) { + return '--inspect'; + } + return arg; + }; + return argv.map(fixIfInspectArg); +} + +function getTsNodeArgsIfNeeded(scriptPath: string, shouldTypecheck: boolean): string[] { + if (process.execArgv.includes('ts-node/register')) { + return []; + } + + // if we are running the tests we only want to transpile, or these tests + // take forever + if (isRunningHardhatCoreTests()) { + return ['--require', 'ts-node/register/transpile-only']; + } + + // If the script we are going to run is .ts we need ts-node + if (/\.tsx?$/i.test(scriptPath)) { + return ['--require', `ts-node/register${shouldTypecheck ? '' : '/transpile-only'}`]; + } + + return []; +} diff --git a/packages/hardhat-zksync-node/src/index.ts b/packages/hardhat-zksync-node/src/index.ts index fc91d2fb7..1aa882b87 100644 --- a/packages/hardhat-zksync-node/src/index.ts +++ b/packages/hardhat-zksync-node/src/index.ts @@ -2,12 +2,16 @@ import { spawn } from 'child_process'; import { task, subtask, types } from 'hardhat/config'; import { TASK_COMPILE, + TASK_NODE, + TASK_RUN, TASK_TEST, TASK_TEST_GET_TEST_FILES, TASK_TEST_RUN_MOCHA_TESTS, } from 'hardhat/builtin-tasks/task-names'; import { HARDHAT_NETWORK_NAME } from 'hardhat/plugins'; +import { TaskArguments } from 'hardhat/types'; +import path from 'path'; import { MAX_PORT_ATTEMPTS, START_PORT, @@ -16,7 +20,7 @@ import { TASK_NODE_ZKSYNC_DOWNLOAD_BINARY, TASK_RUN_NODE_ZKSYNC_IN_SEPARATE_PROCESS, } from './constants'; -import { JsonRpcServer } from './server'; +import { JsonRpcServer, RpcServer } from './server'; import { adjustTaskArgsForPort, configureNetwork, @@ -28,6 +32,17 @@ import { } from './utils'; import { RPCServerDownloader } from './downloader'; import { ZkSyncNodePluginError } from './errors'; +import { interceptAndWrapTasksWithNode } from './core/global-interceptor'; +import { runScriptWithHardhat } from './core/script-runner'; + +task(TASK_RUN).setAction(async (args, hre, runSuper) => { + if (!hre.network.zksync || hre.network.name !== HARDHAT_NETWORK_NAME) { + await runSuper(args, hre); + return; + } + + await runScriptWithHardhat(hre.hardhatArguments, path.resolve(args.script)); +}); // Subtask to download the binary subtask(TASK_NODE_ZKSYNC_DOWNLOAD_BINARY, 'Downloads the JSON-RPC server binary') @@ -75,6 +90,65 @@ subtask(TASK_NODE_ZKSYNC_CREATE_SERVER, 'Creates a JSON-RPC server for ZKsync no }, ); +task(TASK_NODE, 'Start a ZKSync Node') + .addOptionalParam('log', 'Log filter level (error, warn, info, debug) - default: info', undefined, types.string) + .addOptionalParam( + 'logFilePath', + 'Path to the file where logs should be written - default: `era_test_node.log`', + undefined, + types.string, + ) + .addOptionalParam('cache', 'Cache type (none, disk, memory) - default: disk', undefined, types.string) + .addOptionalParam( + 'cacheDir', + 'Cache directory location for `disk` cache - default: `.cache`', + undefined, + types.string, + ) + .addFlag('resetCache', 'Reset the local `disk` cache') + .addOptionalParam( + 'showCalls', + 'Show call debug information (none, user, system, all) - default: none', + undefined, + types.string, + ) + .addOptionalParam( + 'showStorageLogs', + 'Show storage log information (none, read, write, all) - default: none', + undefined, + types.string, + ) + .addOptionalParam( + 'showVmDetails', + 'Show VM details information (none, all) - default: none', + undefined, + types.string, + ) + .addOptionalParam( + 'showGasDetails', + 'Show Gas details information (none, all) - default: none', + undefined, + types.string, + ) + .addFlag( + 'resolveHashes', + 'Try to contact openchain to resolve the ABI & topic names. It enabled, it makes debug log more readable, but will decrease the performance', + ) + .addFlag( + 'devUseLocalContracts', + 'Loads the locally compiled system contracts (useful when doing changes to system contracts or bootloader)', + ) + .addOptionalParam('replayTx', 'Transaction hash to replay', undefined, types.string) + .addOptionalParam('tag', 'Specified node release for use', undefined) + // .addOptionalParam('force', 'Force download', undefined, types.boolean) + .setAction(async (args: TaskArguments, { network, run }, runSuper) => { + if (network.zksync !== true || network.name !== HARDHAT_NETWORK_NAME) { + return await runSuper(); + } + + await run(TASK_NODE_ZKSYNC, args); + }); + // Main task of the plugin. It starts the server and listens for requests. task(TASK_NODE_ZKSYNC, 'Starts a JSON-RPC server for ZKsync node') .addOptionalParam('port', 'Port to listen on - default: 8011', undefined, types.int) @@ -196,7 +270,7 @@ task(TASK_NODE_ZKSYNC, 'Starts a JSON-RPC server for ZKsync node') const binaryPath: string = await run(TASK_NODE_ZKSYNC_DOWNLOAD_BINARY, { force: false, tag }); // Create the server - const server: JsonRpcServer = await run(TASK_NODE_ZKSYNC_CREATE_SERVER, { binaryPath }); + const server: RpcServer = await run(TASK_NODE_ZKSYNC_CREATE_SERVER, { binaryPath }); try { await server.listen(commandArgs); @@ -291,3 +365,5 @@ task( } }, ); + +interceptAndWrapTasksWithNode(); diff --git a/packages/hardhat-zksync-node/src/server.ts b/packages/hardhat-zksync-node/src/server.ts index 620a853fe..c9bf7e76f 100644 --- a/packages/hardhat-zksync-node/src/server.ts +++ b/packages/hardhat-zksync-node/src/server.ts @@ -3,7 +3,12 @@ import chalk from 'chalk'; import { PROCESS_TERMINATION_SIGNALS } from './constants'; -export class JsonRpcServer { +export interface RpcServer { + listen(args?: string[], blockProcess?: boolean): Promise; + stop(): Promise; +} + +export class JsonRpcServer implements RpcServer { private serverProcess: ChildProcess | null = null; // eslint-disable-next-line @typescript-eslint/naming-convention diff --git a/packages/hardhat-zksync-node/src/utils.ts b/packages/hardhat-zksync-node/src/utils.ts index e2aa8b59d..3154169ee 100644 --- a/packages/hardhat-zksync-node/src/utils.ts +++ b/packages/hardhat-zksync-node/src/utils.ts @@ -19,16 +19,20 @@ import { ALLOWED_SHOW_STORAGE_LOGS_VALUES, ALLOWED_SHOW_VM_DETAILS_VALUES, BASE_URL, + MAX_PORT_ATTEMPTS, NETWORK_ACCOUNTS, NETWORK_ETH, NETWORK_GAS, NETWORK_GAS_PRICE, PLATFORM_MAP, + START_PORT, TEMP_FILE_PREFIX, ZKSYNC_ERA_TEST_NODE_NETWORK_NAME, } from './constants'; import { ZkSyncNodePluginError } from './errors'; import { CommandArguments } from './types'; +import { RPCServerDownloader } from './downloader'; +import { JsonRpcServer } from './server'; // Generates command arguments for running the era-test-node binary export function constructCommandArgs(args: CommandArguments): string[] { @@ -384,7 +388,7 @@ export function adjustTaskArgsForPort(taskArgs: string[], currentPort: number): return taskArgs; } -function getNetworkConfig(url: string) { +export function getNetworkConfig(url: string) { return { accounts: NETWORK_ACCOUNTS.REMOTE, gas: NETWORK_GAS.AUTO, @@ -394,6 +398,7 @@ function getNetworkConfig(url: string) { timeout: 20000, url, ethNetwork: NETWORK_ETH.LOCALHOST, + chainId: 260, zksync: true, }; } @@ -406,3 +411,25 @@ export async function configureNetwork(config: HardhatConfig, network: any, port config.networks[network.name] = network.config; network.provider = await createProvider(config, network.name); } + +export const startServer = async (tag?: string, force: boolean = false) => { + const platform = getPlatform(); + if (platform === 'windows' || platform === '') { + throw new ZkSyncNodePluginError(`Unsupported platform: ${platform}`); + } + const rpcServerBinaryDir = await getRPCServerBinariesDir(); + + const downloader: RPCServerDownloader = new RPCServerDownloader(rpcServerBinaryDir, tag || 'latest'); + + await downloader.downloadIfNeeded(force); + const binaryPath = await downloader.getBinaryPath(); + + const currentPort = await getAvailablePort(START_PORT, MAX_PORT_ATTEMPTS); + const commandArgs = constructCommandArgs({ port: currentPort }); + + return { + commandArgs, + server: new JsonRpcServer(binaryPath), + port: currentPort, + }; +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bb0fc58f2..29f03b36b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,9 +14,15 @@ importers: '@npmcli/promise-spawn': specifier: ^6.0.2 version: 6.0.2 + '@types/find-up': + specifier: ^2.1.1 + version: 2.1.1 commander: specifier: ^10.0.0 version: 10.0.1 + find-up: + specifier: ^2.1.0 + version: 2.1.0 get-monorepo-packages: specifier: ^1.2.0 version: 1.2.0 @@ -1130,6 +1136,9 @@ importers: chalk: specifier: ^4.1.2 version: 4.1.2 + debug: + specifier: ^4.3.5 + version: 4.3.5 fs-extra: specifier: ^11.2.0 version: 11.2.0 @@ -1142,6 +1151,9 @@ importers: sinon-chai: specifier: ^3.7.0 version: 3.7.0(chai@4.4.1)(sinon@18.0.0) + source-map-support: + specifier: ^0.5.21 + version: 0.5.21 undici: specifier: ^6.18.2 version: 6.18.2 @@ -2305,6 +2317,9 @@ packages: '@types/dockerode@3.3.29': resolution: {integrity: sha512-5PRRq/yt5OT/Jf77ltIdz4EiR9+VLnPF+HpU4xGFwUqmV24Co2HKBNW3w+slqZ1CYchbcDeqJASHDYWzZCcMiQ==, tarball: https://registry.npmjs.org/@types/dockerode/-/dockerode-3.3.29.tgz} + '@types/find-up@2.1.1': + resolution: {integrity: sha512-60LC501bQRN9/3yfVaEEMd7IndaufffL56PBRAejPpUrY304Ps1jfnjNqPw5jmM5R8JHWiKBAe5IHzNcPV41AA==, tarball: https://registry.npmjs.org/@types/find-up/-/find-up-2.1.1.tgz} + '@types/fs-extra@11.0.4': resolution: {integrity: sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==, tarball: https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.4.tgz} @@ -6024,6 +6039,8 @@ snapshots: '@types/node': 18.19.33 '@types/ssh2': 1.15.0 + '@types/find-up@2.1.1': {} + '@types/fs-extra@11.0.4': dependencies: '@types/jsonfile': 6.1.4