From 594348825e0a76185e0e0469e91bb39a3bf21a17 Mon Sep 17 00:00:00 2001 From: Jonah Graham Date: Wed, 25 Jan 2023 11:08:23 -0500 Subject: [PATCH 1/5] Factor out common code in attach/launch request methods The majority of the code in attach/launch request is the same with just small differences based on whether to execute the target program or not. This is a code cleanup that makes it easier to implement #227 --- src/GDBDebugSession.ts | 111 ++++++++++++++++------------------- src/GDBTargetDebugSession.ts | 22 +++++-- 2 files changed, 67 insertions(+), 66 deletions(-) diff --git a/src/GDBDebugSession.ts b/src/GDBDebugSession.ts index 0dae9df7..ea47f9dd 100644 --- a/src/GDBDebugSession.ts +++ b/src/GDBDebugSession.ts @@ -242,40 +242,62 @@ export class GDBDebugSession extends LoggingDebugSession { this.sendResponse(response); } - protected async attachRequest( - response: DebugProtocol.AttachResponse, - args: AttachRequestArguments - ): Promise { - try { - logger.setup( - args.verbose ? Logger.LogLevel.Verbose : Logger.LogLevel.Warn, - args.logFile || false - ); + protected async attachOrLaunchRequest( + response: DebugProtocol.Response, + request: 'launch' | 'attach', + args: LaunchRequestArguments | AttachRequestArguments + ) { + logger.setup( + args.verbose ? Logger.LogLevel.Verbose : Logger.LogLevel.Warn, + args.logFile || false + ); - this.gdb.on('consoleStreamOutput', (output, category) => { - this.sendEvent(new OutputEvent(output, category)); - }); + this.gdb.on('consoleStreamOutput', (output, category) => { + this.sendEvent(new OutputEvent(output, category)); + }); - this.gdb.on('execAsync', (resultClass, resultData) => - this.handleGDBAsync(resultClass, resultData) - ); - this.gdb.on('notifyAsync', (resultClass, resultData) => - this.handleGDBNotify(resultClass, resultData) - ); + this.gdb.on('execAsync', (resultClass, resultData) => + this.handleGDBAsync(resultClass, resultData) + ); + this.gdb.on('notifyAsync', (resultClass, resultData) => + this.handleGDBNotify(resultClass, resultData) + ); - await this.spawn(args); - await this.gdb.sendFileExecAndSymbols(args.program); - await this.gdb.sendEnablePrettyPrint(); + await this.spawn(args); + await this.gdb.sendFileExecAndSymbols(args.program); + await this.gdb.sendEnablePrettyPrint(); - await mi.sendTargetAttachRequest(this.gdb, { pid: args.processId }); + if (request === 'attach') { + const attachArgs = args as AttachRequestArguments; + await mi.sendTargetAttachRequest(this.gdb, { + pid: attachArgs.processId, + }); this.sendEvent( - new OutputEvent(`attached to process ${args.processId}`) + new OutputEvent(`attached to process ${attachArgs.processId}`) ); - await this.gdb.sendCommands(args.initCommands); + } - this.sendEvent(new InitializedEvent()); - this.sendResponse(response); - this.isInitialized = true; + await this.gdb.sendCommands(args.initCommands); + + if (request === 'launch') { + const launchArgs = args as LaunchRequestArguments; + if (launchArgs.arguments) { + await mi.sendExecArguments(this.gdb, { + arguments: launchArgs.arguments, + }); + } + } + this.sendEvent(new InitializedEvent()); + this.sendResponse(response); + this.isInitialized = true; + } + + protected async attachRequest( + response: DebugProtocol.AttachResponse, + args: AttachRequestArguments + ): Promise { + try { + await this.attachOrLaunchRequest(response, 'attach', args); } catch (err) { this.sendErrorResponse( response, @@ -290,40 +312,7 @@ export class GDBDebugSession extends LoggingDebugSession { args: LaunchRequestArguments ): Promise { try { - logger.setup( - args.verbose ? Logger.LogLevel.Verbose : Logger.LogLevel.Warn, - args.logFile || false - ); - - this.gdb.on('consoleStreamOutput', (output, category) => { - this.sendEvent(new OutputEvent(output, category)); - }); - - this.gdb.on('execAsync', (resultClass, resultData) => - this.handleGDBAsync(resultClass, resultData) - ); - this.gdb.on('notifyAsync', (resultClass, resultData) => - this.handleGDBNotify(resultClass, resultData) - ); - - await this.spawn(args); - await this.gdb.sendFileExecAndSymbols(args.program); - await this.gdb.sendEnablePrettyPrint(); - - if (args.initCommands) { - for (const command of args.initCommands) { - await this.gdb.sendCommand(command); - } - } - - if (args.arguments) { - await mi.sendExecArguments(this.gdb, { - arguments: args.arguments, - }); - } - this.sendEvent(new InitializedEvent()); - this.sendResponse(response); - this.isInitialized = true; + await this.attachOrLaunchRequest(response, 'launch', args); } catch (err) { this.sendErrorResponse( response, diff --git a/src/GDBTargetDebugSession.ts b/src/GDBTargetDebugSession.ts index 282a6f6e..b2b63882 100644 --- a/src/GDBTargetDebugSession.ts +++ b/src/GDBTargetDebugSession.ts @@ -77,14 +77,27 @@ export interface TargetLaunchRequestArguments export class GDBTargetDebugSession extends GDBDebugSession { protected gdbserver?: ChildProcess; + protected async attachOrLaunchRequest( + response: DebugProtocol.Response, + request: 'launch' | 'attach', + args: TargetLaunchRequestArguments | TargetAttachRequestArguments + ) { + this.setupCommonLoggerAndHandlers(args); + + if (request === 'launch') { + const launchArgs = args as TargetLaunchRequestArguments; + await this.startGDBServer(launchArgs); + } + + await this.startGDBAndAttachToTarget(response, args); + } + protected async launchRequest( response: DebugProtocol.LaunchResponse, args: TargetLaunchRequestArguments ): Promise { try { - this.setupCommonLoggerAndHandlers(args); - await this.startGDBServer(args); - await this.startGDBAndAttachToTarget(response, args); + await this.attachOrLaunchRequest(response, 'launch', args); } catch (err) { this.sendErrorResponse( response, @@ -99,8 +112,7 @@ export class GDBTargetDebugSession extends GDBDebugSession { args: TargetAttachRequestArguments ): Promise { try { - this.setupCommonLoggerAndHandlers(args); - await this.startGDBAndAttachToTarget(response, args); + await this.attachOrLaunchRequest(response, 'attach', args); } catch (err) { this.sendErrorResponse( response, From 819e5235e57cd9820b5e7b62d05293bdcd0c7ae9 Mon Sep 17 00:00:00 2001 From: Jonah Graham Date: Wed, 25 Jan 2023 11:51:42 -0500 Subject: [PATCH 2/5] better error message on missing program field in launch request Prior to this change the error message said "Error: arg is not iterable" it now says "The program must be specified in the launch request arguments" --- src/GDBDebugSession.ts | 8 ++++++++ src/GDBTargetDebugSession.ts | 11 +++++++++++ src/integration-tests/launch.spec.ts | 14 ++++++++++++++ 3 files changed, 33 insertions(+) diff --git a/src/GDBDebugSession.ts b/src/GDBDebugSession.ts index ea47f9dd..4da0cd63 100644 --- a/src/GDBDebugSession.ts +++ b/src/GDBDebugSession.ts @@ -264,6 +264,14 @@ export class GDBDebugSession extends LoggingDebugSession { ); await this.spawn(args); + if (!args.program) { + this.sendErrorResponse( + response, + 1, + 'The program must be specified in the request arguments' + ); + return; + } await this.gdb.sendFileExecAndSymbols(args.program); await this.gdb.sendEnablePrettyPrint(); diff --git a/src/GDBTargetDebugSession.ts b/src/GDBTargetDebugSession.ts index b2b63882..a0505bac 100644 --- a/src/GDBTargetDebugSession.ts +++ b/src/GDBTargetDebugSession.ts @@ -86,6 +86,17 @@ export class GDBTargetDebugSession extends GDBDebugSession { if (request === 'launch') { const launchArgs = args as TargetLaunchRequestArguments; + if ( + launchArgs.target?.serverParameters === undefined && + !launchArgs.program + ) { + this.sendErrorResponse( + response, + 1, + 'The program must be specified in the launch request arguments' + ); + return; + } await this.startGDBServer(launchArgs); } diff --git a/src/integration-tests/launch.spec.ts b/src/integration-tests/launch.spec.ts index 25832e94..8cf4ea2d 100644 --- a/src/integration-tests/launch.spec.ts +++ b/src/integration-tests/launch.spec.ts @@ -74,4 +74,18 @@ describe('launch', function () { } ); }); + + it('provides a decent error if program is omitted', async function () { + const errorMessage = await new Promise((resolve, reject) => { + dc.launchRequest( + fillDefaults(this.test, {} as LaunchRequestArguments) + ) + .then(reject) + .catch(resolve); + }); + + expect(errorMessage.message).to.satisfy((msg: string) => + msg.includes('program must be specified') + ); + }); }); From 6c554c14da183b85c898e1c0f06ab644a1cf21d5 Mon Sep 17 00:00:00 2001 From: Jonah Graham Date: Tue, 24 Jan 2023 14:49:15 -0500 Subject: [PATCH 3/5] Increase timeout on Windows to 25s in previously missed locations Most of the timeouts for tests are the mocha timeouts, but DebugClient has its own timeout values. This change synchronizes those values. This increased timeout is specifically needed for the attachRemote test which takes more than 5 seconds to do the initial connection on the GitHub actions build regularly. This is a follow up to commit 3708cea2e0833e0d48fbcc7ca1e4718894c9809e part of #222 --- .mocharc-windows-ci.json | 1 + .mocharc.json | 1 + src/integration-tests/utils.ts | 4 ++++ 3 files changed, 6 insertions(+) diff --git a/.mocharc-windows-ci.json b/.mocharc-windows-ci.json index 23d229cb..9d756fd8 100644 --- a/.mocharc-windows-ci.json +++ b/.mocharc-windows-ci.json @@ -1,3 +1,4 @@ { + "//": "This timeout should match what is in CdtDebugClient constructor", "timeout": "25000" } diff --git a/.mocharc.json b/.mocharc.json index b1f5707d..bca73d03 100644 --- a/.mocharc.json +++ b/.mocharc.json @@ -1,3 +1,4 @@ { + "//": "This timeout should match what is in CdtDebugClient constructor", "timeout": "5000" } diff --git a/src/integration-tests/utils.ts b/src/integration-tests/utils.ts index 73f0bfde..6f3a29ee 100644 --- a/src/integration-tests/utils.ts +++ b/src/integration-tests/utils.ts @@ -201,6 +201,10 @@ export async function standardBeforeEach( shell: true, } ); + + // These timeouts should match what is in .mocharc.json and .mocharc-windows-ci.json + dc.defaultTimeout = os.platform() === 'win32' ? 25000 : 5000; + await dc.start(debugServerPort); await dc.initializeRequest(); From 9b503f47e874f13c8b052c7950c7f55ceef85da7 Mon Sep 17 00:00:00 2001 From: Jonah Graham Date: Mon, 23 Jan 2023 12:30:11 -0500 Subject: [PATCH 4/5] Allow passing command line arguments to adapter without shell Remove shell=true when spawning adapter in tests The shell=true was a workaround so that arguments could be passed to node. But it leads to platform specific handling. Therefore remove the shell=true which also means the workarounds that would have been needed to quote JSON on the command line properly are not needed. Needed to test #227 --- src/integration-tests/debugClient.ts | 81 ++++++++++++++++++++++++++-- src/integration-tests/utils.ts | 27 ++-------- 2 files changed, 82 insertions(+), 26 deletions(-) diff --git a/src/integration-tests/debugClient.ts b/src/integration-tests/debugClient.ts index ce752e60..a8e6f38b 100644 --- a/src/integration-tests/debugClient.ts +++ b/src/integration-tests/debugClient.ts @@ -1,5 +1,5 @@ /********************************************************************* - * Copyright (c) 2018 Ericsson and others + * Copyright (c) 2018, 2023 Ericsson and others * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 @@ -10,6 +10,9 @@ import * as cp from 'child_process'; import { DebugClient } from '@vscode/debugadapter-testsupport'; import { DebugProtocol } from '@vscode/debugprotocol'; +import * as path from 'path'; +import { defaultAdapter } from './utils'; +import * as os from 'os'; export type ReverseRequestHandler< A = any, @@ -23,11 +26,83 @@ export interface ReverseRequestHandlers { >; } +function getAdapterAndArgs(adapter?: string): string[] { + const chosenAdapter = adapter !== undefined ? adapter : defaultAdapter; + const adapterPath: string = path.join( + __dirname, + '../../dist', + chosenAdapter + ); + if (process.env.INSPECT_DEBUG_ADAPTER) { + return ['--inspect-brk', adapterPath]; + } + return [adapterPath]; +} + /** - * Extend the DebugClient to support Reverse Requests: - * https://microsoft.github.io/debug-adapter-protocol/specification#Reverse_Requests_RunInTerminal + * Extend the standard DebugClient to support additional client features */ export class CdtDebugClient extends DebugClient { + private _cdt_args: string[]; + private _cdt_adapterProcess?: cp.ChildProcess; + constructor(adapter?: string, extraArgs?: string[]) { + // The unused are as such because we do override process launching + super('unused', 'unused', 'gdb'); + this._cdt_args = getAdapterAndArgs(adapter); + if (extraArgs) { + this._cdt_args.push(...extraArgs); + } + // These timeouts should match what is in .mocharc.json and .mocharc-windows-ci.json + this.defaultTimeout = os.platform() === 'win32' ? 25000 : 5000; + } + + /** + * Start a debug session allowing command line arguments to be supplied + */ + public start(port?: number): Promise { + if (typeof port === 'number') { + return super.start(port); + } + + return new Promise((resolve, reject) => { + this._cdt_adapterProcess = cp.spawn('node', this._cdt_args); + this._cdt_adapterProcess.on('error', (err) => { + console.log(err); + reject(err); + }); + + if ( + this._cdt_adapterProcess.stdout === null || + this._cdt_adapterProcess.stdin === null + ) { + reject('Missing stdout/stdin'); + return; + } + this.connect( + this._cdt_adapterProcess.stdout, + this._cdt_adapterProcess.stdin + ); + resolve(); + }); + } + + public stop(): Promise { + return super + .stop() + .then(() => { + this.killAdapter(); + }) + .catch(() => { + this.killAdapter(); + }); + } + + private killAdapter() { + if (this._cdt_adapterProcess) { + this._cdt_adapterProcess.kill(); + this._cdt_adapterProcess = undefined; + } + } /** * Reverse Request Handlers: */ diff --git a/src/integration-tests/utils.ts b/src/integration-tests/utils.ts index 6f3a29ee..978512c0 100644 --- a/src/integration-tests/utils.ts +++ b/src/integration-tests/utils.ts @@ -1,5 +1,5 @@ /********************************************************************* - * Copyright (c) 2018 Ericsson and others + * Copyright (c) 2018, 2023 Ericsson and others * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 @@ -181,30 +181,11 @@ export const testProgramsDir = path.join( 'test-programs' ); -function getAdapterAndArgs(adapter?: string): string { - const chosenAdapter = adapter !== undefined ? adapter : defaultAdapter; - let args: string = path.join(__dirname, '../../dist', chosenAdapter); - if (process.env.INSPECT_DEBUG_ADAPTER) { - args = '--inspect-brk ' + args; - } - return args; -} - export async function standardBeforeEach( - adapter?: string + adapter?: string, + extraArgs?: string[] ): Promise { - const dc: CdtDebugClient = new CdtDebugClient( - 'node', - getAdapterAndArgs(adapter), - 'cppdbg', - { - shell: true, - } - ); - - // These timeouts should match what is in .mocharc.json and .mocharc-windows-ci.json - dc.defaultTimeout = os.platform() === 'win32' ? 25000 : 5000; - + const dc: CdtDebugClient = new CdtDebugClient(adapter, extraArgs); await dc.start(debugServerPort); await dc.initializeRequest(); From 55510156bd2231a9f30bdc408a2387505c5f586d Mon Sep 17 00:00:00 2001 From: Jonah Graham Date: Wed, 18 Jan 2023 15:33:35 -0500 Subject: [PATCH 5/5] Allow pre-filling launch/attach on command line to adapter Start the adapter using the given configuration as a starting point for the args in `launch` or `attach` request. For example, the default GDB can be set like this: ```sh node debugTargetAdapter.js --config='{"gdb":"arm-none-eabi-gdb"}' ``` The config can be passed on the command line as JSON, or a response file can be used by starting the argument with `@`. The rest of the argument will be interpreted as a file name to read. For example, to start the adapter defaulting to a process ID to attach to, create a file containing the JSON and reference it like this: ```sh cat >config.json <config.json < { try { - await this.attachOrLaunchRequest(response, 'attach', args); + const [request, resolvedArgs] = this.applyRequestArguments( + 'attach', + args + ); + await this.attachOrLaunchRequest(response, request, resolvedArgs); } catch (err) { this.sendErrorResponse( response, @@ -320,7 +398,11 @@ export class GDBDebugSession extends LoggingDebugSession { args: LaunchRequestArguments ): Promise { try { - await this.attachOrLaunchRequest(response, 'launch', args); + const [request, resolvedArgs] = this.applyRequestArguments( + 'launch', + args + ); + await this.attachOrLaunchRequest(response, request, resolvedArgs); } catch (err) { this.sendErrorResponse( response, diff --git a/src/GDBTargetDebugSession.ts b/src/GDBTargetDebugSession.ts index a0505bac..2c4075cb 100644 --- a/src/GDBTargetDebugSession.ts +++ b/src/GDBTargetDebugSession.ts @@ -108,7 +108,11 @@ export class GDBTargetDebugSession extends GDBDebugSession { args: TargetLaunchRequestArguments ): Promise { try { - await this.attachOrLaunchRequest(response, 'launch', args); + const [request, resolvedArgs] = this.applyRequestArguments( + 'launch', + args + ); + await this.attachOrLaunchRequest(response, request, resolvedArgs); } catch (err) { this.sendErrorResponse( response, @@ -123,7 +127,11 @@ export class GDBTargetDebugSession extends GDBDebugSession { args: TargetAttachRequestArguments ): Promise { try { - await this.attachOrLaunchRequest(response, 'attach', args); + const [request, resolvedArgs] = this.applyRequestArguments( + 'attach', + args + ); + await this.attachOrLaunchRequest(response, request, resolvedArgs); } catch (err) { this.sendErrorResponse( response, diff --git a/src/integration-tests/config.spec.ts b/src/integration-tests/config.spec.ts new file mode 100644 index 00000000..1fc4af2a --- /dev/null +++ b/src/integration-tests/config.spec.ts @@ -0,0 +1,165 @@ +/********************************************************************* + * Copyright (c) 2023 Kichwa Coders Canada Inc. and others. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *********************************************************************/ + +import * as path from 'path'; +import * as tmp from 'tmp'; +import * as fs from 'fs'; +import { + LaunchRequestArguments, + AttachRequestArguments, +} from '../GDBDebugSession'; +import { + debugServerPort, + defaultAdapter, + fillDefaults, + standardBeforeEach, + testProgramsDir, +} from './utils'; + +describe('config', function () { + const emptyProgram = path.join(testProgramsDir, 'empty'); + const emptySrc = path.join(testProgramsDir, 'empty.c'); + + async function verifyLaunchWorks( + test: Mocha.Context, + commandLine: string[], + requestArgs: LaunchRequestArguments + ) { + if (debugServerPort) { + // This test requires launching the adapter to work + test.skip(); + } + + const dc = await standardBeforeEach(defaultAdapter, commandLine); + + try { + await dc.hitBreakpoint(fillDefaults(test.test, requestArgs), { + path: emptySrc, + line: 3, + }); + } finally { + await dc.stop(); + } + } + + it('can specify program via --config=', async function () { + const config = { program: emptyProgram }; + await verifyLaunchWorks( + this, + [`--config=${JSON.stringify(config)}`], + {} as LaunchRequestArguments + ); + }); + + it('program via --config= can be overridden', async function () { + const config = { program: '/program/that/does/not/exist' }; + await verifyLaunchWorks(this, [`--config=${JSON.stringify(config)}`], { + program: emptyProgram, + } as LaunchRequestArguments); + }); + + it('can specify program via --config-frozen=', async function () { + const config = { program: emptyProgram }; + await verifyLaunchWorks( + this, + [`--config-frozen=${JSON.stringify(config)}`], + {} as LaunchRequestArguments + ); + }); + + it('program via --config-frozen= can not be overridden', async function () { + const config = { program: emptyProgram }; + await verifyLaunchWorks( + this, + [`--config-frozen=${JSON.stringify(config)}`], + { + program: '/program/that/does/not/exist', + } as LaunchRequestArguments + ); + }); + + it('can specify program via --config= using response file', async function () { + const config = { program: emptyProgram }; + const json = JSON.stringify(config); + const jsonFile = tmp.fileSync(); + fs.writeFileSync(jsonFile.fd, json); + fs.closeSync(jsonFile.fd); + + await verifyLaunchWorks( + this, + [`--config=@${jsonFile.name}`], + {} as LaunchRequestArguments + ); + }); + + it('can specify program via --config-frozen= using response file', async function () { + const config = { program: emptyProgram }; + const json = JSON.stringify(config); + const jsonFile = tmp.fileSync(); + fs.writeFileSync(jsonFile.fd, json); + fs.closeSync(jsonFile.fd); + + await verifyLaunchWorks( + this, + [`--config-frozen=@${jsonFile.name}`], + {} as LaunchRequestArguments + ); + }); + + // This test most closely models the original design goal + // for the change that added --config and --config-frozen + // as discussed in #227 and #228 + // In summary we force a launch request for the given program, + // but the user does not specify the program and specifies + // an attach request + it('config frozen forces specific launch type', async function () { + if (debugServerPort) { + // This test requires launching the adapter to work + this.skip(); + } + + const config = { request: 'launch', program: emptyProgram }; + + // Launch the adapter with the frozen config + const dc = await standardBeforeEach(defaultAdapter, [ + `--config-frozen=${JSON.stringify(config)}`, + ]); + + try { + await Promise.all([ + // Do an attach request omitting the program that we want + // the adapter to force into a launch request + dc.attachRequest( + fillDefaults(this.test, {} as AttachRequestArguments) + ), + + // The rest of this code is to ensure we launcher properly by verifying + // we can run to a breakpoint + dc.waitForEvent('initialized').then((_event) => { + return dc + .setBreakpointsRequest({ + lines: [3], + breakpoints: [{ line: 3 }], + source: { path: emptySrc }, + }) + .then((_response) => { + return dc.configurationDoneRequest(); + }); + }), + dc.assertStoppedLocation('breakpoint', { + path: emptySrc, + line: 3, + }), + ]); + } finally { + await dc.stop(); + } + }); +}); diff --git a/yarn.lock b/yarn.lock index 052d9ccc..3e372339 100644 --- a/yarn.lock +++ b/yarn.lock @@ -213,6 +213,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.17.tgz#37d3c01043fd09f3f17ffa8c17062bbb580f9558" integrity sha512-oajWz4kOajqpKJMPgnCvBajPq8QAvl2xIWoFjlAJPKGu6n7pjov5SxGE45a+0RxHDoo4ycOMoZw1SCOWtDERbw== +"@types/tmp@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@types/tmp/-/tmp-0.2.3.tgz#908bfb113419fd6a42273674c00994d40902c165" + integrity sha512-dDZH/tXzwjutnuk4UacGgFRwV+JSLaXL1ikvidfJprkb7L9Nx1njcRHHmi3Dsvt7pgqqTEeucQuOrWHPFgzVHA== + "@typescript-eslint/eslint-plugin@^5.10.1": version "5.10.1" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.10.1.tgz#870195d0f2146b36d11fc71131b75aba52354c69" @@ -2445,7 +2450,7 @@ reusify@^1.0.4: resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== -rimraf@^3.0.2: +rimraf@^3.0.0, rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== @@ -2759,6 +2764,13 @@ text-table@^0.2.0: resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= +tmp@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" + integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== + dependencies: + rimraf "^3.0.0" + to-regex-range@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"