From 074f65445728cb1950bfe1a01017c8227e43aba6 Mon Sep 17 00:00:00 2001 From: Akos Kitta Date: Thu, 23 Nov 2023 11:34:05 +0100 Subject: [PATCH] fix: sketch Save As preserves the folder structure This commit rewrites how IDE copies sketches as part of the _Save As_ operation. Instead of copying to the destination, IDE copies the sketch into a temporary location, then to the desired destination. This commit drops [`cpy`](https://www.npmjs.com/package/cpy). Ref: https://github.com/sindresorhus/cpy/commit/47b89a70b51af32133750ea2c73bed65fbe0ce56 Closes #2077 Signed-off-by: Akos Kitta --- arduino-ide-extension/package.json | 1 - .../src/common/protocol/sketches-service.ts | 10 ++ .../src/node/sketches-service-impl.ts | 138 ++++++++++++------ .../node/sketches-service-impl.slow-test.ts | 130 ++++++++++++++--- yarn.lock | 81 +--------- 5 files changed, 220 insertions(+), 140 deletions(-) diff --git a/arduino-ide-extension/package.json b/arduino-ide-extension/package.json index ba340a0f7..a7bc5354e 100644 --- a/arduino-ide-extension/package.json +++ b/arduino-ide-extension/package.json @@ -63,7 +63,6 @@ "auth0-js": "^9.23.2", "btoa": "^1.2.1", "classnames": "^2.3.1", - "cpy": "^10.0.0", "cross-fetch": "^3.1.5", "dateformat": "^3.0.3", "deepmerge": "^4.2.2", diff --git a/arduino-ide-extension/src/common/protocol/sketches-service.ts b/arduino-ide-extension/src/common/protocol/sketches-service.ts index 3c98252c1..43c739433 100644 --- a/arduino-ide-extension/src/common/protocol/sketches-service.ts +++ b/arduino-ide-extension/src/common/protocol/sketches-service.ts @@ -8,6 +8,7 @@ export namespace SketchesError { export const Codes = { NotFound: 5001, InvalidName: 5002, + InvalidFolderName: 5003, }; export const NotFound = ApplicationError.declare( Codes.NotFound, @@ -27,6 +28,15 @@ export namespace SketchesError { }; } ); + export const InvalidFolderName = ApplicationError.declare( + Codes.InvalidFolderName, + (message: string, invalidFolderName: string) => { + return { + message, + data: { invalidFolderName }, + }; + } + ); } export const SketchesServicePath = '/services/sketches-service'; diff --git a/arduino-ide-extension/src/node/sketches-service-impl.ts b/arduino-ide-extension/src/node/sketches-service-impl.ts index e14b123dd..8fb2ada07 100644 --- a/arduino-ide-extension/src/node/sketches-service-impl.ts +++ b/arduino-ide-extension/src/node/sketches-service-impl.ts @@ -1,48 +1,55 @@ -import { injectable, inject, named } from '@theia/core/shared/inversify'; -import { promises as fs, realpath, lstat, Stats, constants } from 'node:fs'; -import os from 'node:os'; -import temp from 'temp'; -import path from 'node:path'; -import glob from 'glob'; -import crypto from 'node:crypto'; -import PQueue from 'p-queue'; +import { EnvVariablesServer } from '@theia/core/lib/common/env-variables'; +import { ILogger } from '@theia/core/lib/common/logger'; +import { nls } from '@theia/core/lib/common/nls'; +import { isWindows } from '@theia/core/lib/common/os'; +import { Deferred } from '@theia/core/lib/common/promise-util'; +import { escapeRegExpCharacters } from '@theia/core/lib/common/strings'; import type { Mutable } from '@theia/core/lib/common/types'; import URI from '@theia/core/lib/common/uri'; -import { ILogger } from '@theia/core/lib/common/logger'; import { FileUri } from '@theia/core/lib/node/file-uri'; -import { ConfigServiceImpl } from './config-service-impl'; +import { inject, injectable, named } from '@theia/core/shared/inversify'; +import glob from 'glob'; +import crypto from 'node:crypto'; +import { + CopyOptions, + Stats, + constants, + promises as fs, + lstat, + realpath, +} from 'node:fs'; +import os from 'node:os'; +import path, { join } from 'node:path'; +import PQueue from 'p-queue'; +import temp from 'temp'; +import { NotificationServiceServer } from '../common/protocol'; import { - SketchesService, Sketch, - SketchRef, SketchContainer, + SketchRef, SketchesError, + SketchesService, } from '../common/protocol/sketches-service'; -import { NotificationServiceServer } from '../common/protocol'; -import { EnvVariablesServer } from '@theia/core/lib/common/env-variables'; -import { CoreClientAware } from './core-client-provider'; +import { + firstToLowerCase, + firstToUpperCase, + startsWithUpperCase, +} from '../common/utils'; import { ArchiveSketchRequest, LoadSketchRequest, } from './cli-protocol/cc/arduino/cli/commands/v1/commands_pb'; -import { Deferred } from '@theia/core/lib/common/promise-util'; -import { escapeRegExpCharacters } from '@theia/core/lib/common/strings'; -import { ServiceError } from './service-error'; +import { ConfigServiceImpl } from './config-service-impl'; +import { CoreClientAware } from './core-client-provider'; import { IsTempSketch, - maybeNormalizeDrive, TempSketchPrefix, Win32DriveRegex, + maybeNormalizeDrive, } from './is-temp-sketch'; -import { join } from 'node:path'; -import { ErrnoException } from './utils/errors'; -import { isWindows } from '@theia/core/lib/common/os'; -import { - firstToLowerCase, - firstToUpperCase, - startsWithUpperCase, -} from '../common/utils'; +import { ServiceError } from './service-error'; import { SettingsReader } from './settings-reader'; +import { ErrnoException } from './utils/errors'; const RecentSketches = 'recent-sketches.json'; const DefaultIno = `void setup() { @@ -510,26 +517,75 @@ export class SketchesServiceImpl } const sourceFolderBasename = path.basename(source); const destinationFolderBasename = path.basename(destination); - let filter; + + const errorMessage = Sketch.validateSketchFolderName( + destinationFolderBasename + ); + if (errorMessage) { + const message = `${nls.localize( + 'arduino/sketch/invalidSketchFolderNameMessage', + "Invalid sketch folder name: '{0}'", + destinationFolderBasename + )} ${errorMessage}`; + throw SketchesError.InvalidFolderName(message, destinationFolderBasename); + } + + let filter: CopyOptions['filter']; if (onlySketchFiles) { - const sketchFilePaths = Sketch.uris(sketch).map(FileUri.fsPath); - filter = (file: { path: string }) => sketchFilePaths.includes(file.path); + // The Windows paths, can be a trash (see below). Hence, it must be resolved with Node.js. + // After resolving the path, the drive letter is still a gamble (can be upper or lower case) and could result in a false negative match. + // Here, all sketch file paths must be resolved by Node.js, to provide the same drive letter casing. + const sketchFilePaths = await Promise.all( + Sketch.uris(sketch) + .map(FileUri.fsPath) + .map((path) => fs.realpath(path)) + ); + filter = async (s) => { + // On Windows, the source path could start with a complete trash. For example, \\\\?\\c:\\Users\\kittaakos\\AppData\\Local\\Temp\\.arduinoIDE-unsaved20231024-9300-1hp64fi.g8yh\\sketch_nov24d. + // The path must be resolved. + const resolvedSource = await fs.realpath(s); + if (sketchFilePaths.includes(resolvedSource)) { + return true; + } + const stat = await fs.stat(resolvedSource); + if (stat.isFile()) { + return false; + } + // Copy the folder if any of the sketch file path starts with this folder + return sketchFilePaths.some((sketchFilePath) => + sketchFilePath.startsWith(resolvedSource) + ); + }; } else { filter = () => true; } - const cpyModule = await import('cpy'); - const cpy = cpyModule.default; - await cpy(sourceFolderBasename, destination, { - rename: (basename) => - sourceFolderBasename !== destinationFolderBasename && - basename === `${sourceFolderBasename}.ino` - ? `${destinationFolderBasename}.ino` - : basename, + + const tempRoot = await this.createTempFolder(); + const temp = join(tempRoot, destinationFolderBasename); + await fs.mkdir(temp, { recursive: true }); + + // copy to temp folder + await fs.cp(source, temp, { filter, - cwd: path.dirname(source), + recursive: true, + force: true, }); - const copiedSketch = await this.doLoadSketch(destinationUri, false); - return copiedSketch; + + // rename the main sketch file + await fs.rename( + join(temp, `${sourceFolderBasename}.ino`), + join(temp, `${destinationFolderBasename}.ino`) + ); + + // copy to destination + try { + await fs.cp(temp, destination, { recursive: true, force: true }); + const copiedSketch = await this.doLoadSketch(destinationUri, false); + return copiedSketch; + } finally { + // remove temp + fs.rm(tempRoot, { recursive: true, force: true, maxRetries: 5 }); // no await + } } async archive(sketch: Sketch, destinationUri: string): Promise { diff --git a/arduino-ide-extension/src/test/node/sketches-service-impl.slow-test.ts b/arduino-ide-extension/src/test/node/sketches-service-impl.slow-test.ts index f9d750d3c..d4e4f154c 100644 --- a/arduino-ide-extension/src/test/node/sketches-service-impl.slow-test.ts +++ b/arduino-ide-extension/src/test/node/sketches-service-impl.slow-test.ts @@ -8,9 +8,10 @@ import { Container } from '@theia/core/shared/inversify'; import { expect } from 'chai'; import { promises as fs } from 'node:fs'; import { basename, join } from 'node:path'; +import { rejects } from 'node:assert/strict'; import { sync as rimrafSync } from 'rimraf'; import temp from 'temp'; -import { Sketch, SketchesService } from '../../common/protocol'; +import { Sketch, SketchesError, SketchesService } from '../../common/protocol'; import { isAccessibleSketchPath, SketchesServiceImpl, @@ -138,12 +139,31 @@ describe('sketches-service-impl', () => { after(() => toDispose.dispose()); - describe('copy', () => { - it('should copy a sketch when the destination does not exist', async function () { - this.timeout(testTimeout); + describe('copy', function () { + this.timeout(testTimeout); + this.slow(250); + + it('should error when the destination sketch folder name is invalid', async () => { + const sketchesService = + container.get(SketchesService); + const tempDirPath = await sketchesService['createTempFolder'](); + const destinationPath = join(tempDirPath, 'invalid with spaces'); + const sketch = await sketchesService.createNewSketch(); + toDispose.push(disposeSketch(sketch)); + await rejects( + sketchesService.copy(sketch, { + destinationUri: FileUri.create(destinationPath).toString(), + }), + SketchesError.InvalidFolderName.is + ); + }); + + it('should copy a sketch when the destination does not exist', async () => { const sketchesService = container.get(SketchesService); - const destinationPath = await sketchesService['createTempFolder'](); + const tempDirPath = await sketchesService['createTempFolder'](); + const destinationPath = join(tempDirPath, 'Does_Not_Exist_but_valid'); + await rejects(fs.readdir(destinationPath), ErrnoException.isENOENT); let sketch = await sketchesService.createNewSketch(); toDispose.push(disposeSketch(sketch)); const sourcePath = FileUri.fsPath(sketch.uri); @@ -187,11 +207,11 @@ describe('sketches-service-impl', () => { ).to.be.true; }); - it("should copy only sketch files if 'onlySketchFiles' is true", async function () { - this.timeout(testTimeout); + it("should copy only sketch files if 'onlySketchFiles' is true", async () => { const sketchesService = container.get(SketchesService); - const destinationPath = await sketchesService['createTempFolder'](); + const tempDirPath = await sketchesService['createTempFolder'](); + const destinationPath = join(tempDirPath, 'OnlySketchFiles'); let sketch = await sketchesService.createNewSketch(); toDispose.push(disposeSketch(sketch)); const sourcePath = FileUri.fsPath(sketch.uri); @@ -207,11 +227,25 @@ describe('sketches-service-impl', () => { const logContent = 'log file content'; const logPath = join(sourcePath, logBasename); await fs.writeFile(logPath, logContent, { encoding: 'utf8' }); + const srcPath = join(sourcePath, 'src'); + await fs.mkdir(srcPath, { recursive: true }); + const libInSrcBasename = 'lib_in_src.cpp'; + const libInSrcContent = 'lib in src content'; + const libInSrcPath = join(srcPath, libInSrcBasename); + await fs.writeFile(libInSrcPath, libInSrcContent, { encoding: 'utf8' }); + const logInSrcBasename = 'inols-clangd-err_in_src.log'; + const logInSrcContent = 'log file content in src'; + const logInSrcPath = join(srcPath, logInSrcBasename); + await fs.writeFile(logInSrcPath, logInSrcContent, { encoding: 'utf8' }); sketch = await sketchesService.loadSketch(sketch.uri); expect(Sketch.isInSketch(FileUri.create(libPath), sketch)).to.be.true; expect(Sketch.isInSketch(FileUri.create(headerPath), sketch)).to.be.true; expect(Sketch.isInSketch(FileUri.create(logPath), sketch)).to.be.false; + expect(Sketch.isInSketch(FileUri.create(libInSrcPath), sketch)).to.be + .true; + expect(Sketch.isInSketch(FileUri.create(logInSrcPath), sketch)).to.be + .false; const reloadedLogContent = await fs.readFile(logPath, { encoding: 'utf8', }); @@ -249,20 +283,25 @@ describe('sketches-service-impl', () => { copied ) ).to.be.false; - try { - await fs.readFile(join(destinationPath, logBasename), { - encoding: 'utf8', - }); - expect.fail( - 'Log file must not exist in the destination. Expected ENOENT when loading the log file.' - ); - } catch (err) { - expect(ErrnoException.isENOENT(err)).to.be.true; - } + expect( + Sketch.isInSketch( + FileUri.create(join(destinationPath, 'src', libInSrcBasename)), + copied + ) + ).to.be.true; + expect( + Sketch.isInSketch( + FileUri.create(join(destinationPath, 'src', logInSrcBasename)), + copied + ) + ).to.be.false; + await rejects( + fs.readFile(join(destinationPath, logBasename)), + ErrnoException.isENOENT + ); }); - it('should copy sketch inside the sketch folder', async function () { - this.timeout(testTimeout); + it('should copy sketch inside the sketch folder', async () => { const sketchesService = container.get(SketchesService); let sketch = await sketchesService.createNewSketch(); @@ -309,6 +348,55 @@ describe('sketches-service-impl', () => { ).to.be.true; }); + it('should not modify the subfolder structure', async () => { + const sketchesService = + container.get(SketchesService); + const tempDirPath = await sketchesService['createTempFolder'](); + const destinationPath = join(tempDirPath, 'HasSubfolders_copy'); + await fs.mkdir(destinationPath, { recursive: true }); + let sketch = await sketchesService.createNewSketch('HasSubfolders'); + toDispose.push(disposeSketch(sketch)); + + const sourcePath = FileUri.fsPath(sketch.uri); + const srcPath = join(sourcePath, 'src'); + await fs.mkdir(srcPath, { recursive: true }); + const headerPath = join(srcPath, 'FomSubfolder.h'); + await fs.writeFile(headerPath, '// empty', { encoding: 'utf8' }); + + sketch = await sketchesService.loadSketch(sketch.uri); + + expect(sketch.mainFileUri).to.be.equal( + FileUri.create(join(sourcePath, 'HasSubfolders.ino')).toString() + ); + expect(sketch.additionalFileUris).to.be.deep.equal([ + FileUri.create(join(srcPath, 'FomSubfolder.h')).toString(), + ]); + expect(sketch.otherSketchFileUris).to.be.empty; + expect(sketch.rootFolderFileUris).to.be.empty; + + const destinationUri = FileUri.create(destinationPath).toString(); + const copySketch = await sketchesService.copy(sketch, { destinationUri }); + toDispose.push(disposeSketch(copySketch)); + expect(copySketch.mainFileUri).to.be.equal( + FileUri.create( + join(destinationPath, 'HasSubfolders_copy.ino') + ).toString() + ); + expect(copySketch.additionalFileUris).to.be.deep.equal([ + FileUri.create( + join(destinationPath, 'src', 'FomSubfolder.h') + ).toString(), + ]); + expect(copySketch.otherSketchFileUris).to.be.empty; + expect(copySketch.rootFolderFileUris).to.be.empty; + + const actualHeaderContent = await fs.readFile( + join(destinationPath, 'src', 'FomSubfolder.h'), + { encoding: 'utf8' } + ); + expect(actualHeaderContent).to.be.equal('// empty'); + }); + it('should copy sketch with overwrite when source and destination sketch folder names are the same', async function () { this.timeout(testTimeout); const sketchesService = @@ -346,7 +434,7 @@ describe('sketches-service-impl', () => { [ '<', '>', - 'chevrons', + 'lt+gt', { predicate: () => isWindows, why: '< (less than) and > (greater than) are reserved characters on Windows (https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file#naming-conventions)', diff --git a/yarn.lock b/yarn.lock index c75cfaf75..7e7f75a09 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4026,11 +4026,6 @@ arrify@^2.0.1: resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa" integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug== -arrify@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/arrify/-/arrify-3.0.0.tgz#ccdefb8eaf2a1d2ab0da1ca2ce53118759fd46bc" - integrity sha512-tLkvA81vQG/XqE2mjDkGQHoOINtMHtysSnemrmoGe6PydDPMRbVugqyk4A6V/WDWEfm3l+0d8anA9r8cv/5Jaw== - asap@^2.0.0: version "2.0.6" resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" @@ -5281,29 +5276,6 @@ cosmiconfig@^8.2.0: parse-json "^5.2.0" path-type "^4.0.0" -cp-file@^10.0.0: - version "10.0.0" - resolved "https://registry.yarnpkg.com/cp-file/-/cp-file-10.0.0.tgz#bbae9ecb9f505951b862880d2901e1f56de7a4dc" - integrity sha512-vy2Vi1r2epK5WqxOLnskeKeZkdZvTKfFZQCplE3XWsP+SUJyd5XAUFC9lFgTjjXJF2GMne/UML14iEmkAaDfFg== - dependencies: - graceful-fs "^4.2.10" - nested-error-stacks "^2.1.1" - p-event "^5.0.1" - -cpy@^10.0.0: - version "10.1.0" - resolved "https://registry.yarnpkg.com/cpy/-/cpy-10.1.0.tgz#85517387036b9be480f6424e54089261fc6f4bab" - integrity sha512-VC2Gs20JcTyeQob6UViBLnyP0bYHkBh6EiKzot9vi2DmeGlFT9Wd7VG3NBrkNx/jYvFBeyDOMMHdHQhbtKLgHQ== - dependencies: - arrify "^3.0.0" - cp-file "^10.0.0" - globby "^13.1.4" - junk "^4.0.1" - micromatch "^4.0.5" - nested-error-stacks "^2.1.1" - p-filter "^3.0.0" - p-map "^6.0.0" - crc@^3.8.0: version "3.8.0" resolved "https://registry.yarnpkg.com/crc/-/crc-3.8.0.tgz#ad60269c2c856f8c299e2c4cc0de4556914056c6" @@ -6627,7 +6599,7 @@ fast-fifo@^1.1.0, fast-fifo@^1.2.0: resolved "https://registry.yarnpkg.com/fast-fifo/-/fast-fifo-1.3.2.tgz#286e31de96eb96d38a97899815740ba2a4f3640c" integrity sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ== -fast-glob@^3.2.5, fast-glob@^3.2.9, fast-glob@^3.3.0: +fast-glob@^3.2.5, fast-glob@^3.2.9: version "3.3.1" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.1.tgz#784b4e897340f3dbbef17413b3f11acf03c874c4" integrity sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg== @@ -7347,17 +7319,6 @@ globby@11.1.0, globby@^11.0.3, globby@^11.1.0: merge2 "^1.4.1" slash "^3.0.0" -globby@^13.1.4: - version "13.2.2" - resolved "https://registry.yarnpkg.com/globby/-/globby-13.2.2.tgz#63b90b1bf68619c2135475cbd4e71e66aa090592" - integrity sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w== - dependencies: - dir-glob "^3.0.1" - fast-glob "^3.3.0" - ignore "^5.2.4" - merge2 "^1.4.1" - slash "^4.0.0" - globby@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/globby/-/globby-7.1.1.tgz#fb2ccff9401f8600945dfada97440cca972b8680" @@ -7421,7 +7382,7 @@ got@^12.0.0, got@^12.1.0, got@^12.6.1: p-cancelable "^3.0.0" responselike "^3.0.0" -graceful-fs@4.2.11, graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.10, graceful-fs@^4.2.11, graceful-fs@^4.2.2, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: +graceful-fs@4.2.11, graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.2, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== @@ -7763,7 +7724,7 @@ ignore@^3.3.5: resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043" integrity sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug== -ignore@^5.0.4, ignore@^5.2.0, ignore@^5.2.4: +ignore@^5.0.4, ignore@^5.2.0: version "5.2.4" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== @@ -8521,11 +8482,6 @@ jsonparse@^1.2.0, jsonparse@^1.3.1: object.assign "^4.1.4" object.values "^1.1.6" -junk@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/junk/-/junk-4.0.1.tgz#7ee31f876388c05177fe36529ee714b07b50fbed" - integrity sha512-Qush0uP+G8ZScpGMZvHUiRfI0YBWuB3gVBYlI0v0vvOJt5FLicco+IkP0a50LqTTQhmts/m6tP5SWE+USyIvcQ== - just-diff@^5.1.1: version "5.2.0" resolved "https://registry.yarnpkg.com/just-diff/-/just-diff-5.2.0.tgz#60dca55891cf24cd4a094e33504660692348a241" @@ -9396,7 +9352,7 @@ micromark@^3.0.0: micromark-util-types "^1.0.1" uvu "^0.5.0" -micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5: +micromatch@^4.0.2, micromatch@^4.0.4: version "4.0.5" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== @@ -9832,11 +9788,6 @@ neo-async@^2.6.0, neo-async@^2.6.2: resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== -nested-error-stacks@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/nested-error-stacks/-/nested-error-stacks-2.1.1.tgz#26c8a3cee6cc05fbcf1e333cd2fc3e003326c0b5" - integrity sha512-9iN1ka/9zmX1ZvLV9ewJYEk9h7RyRRtqdK0woXcqohu8EWIerfPUjYJPg0ULy0UqP7cslmdGc8xKDJcojlKiaw== - nice-grpc-common@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/nice-grpc-common/-/nice-grpc-common-2.0.2.tgz#e6aeebb2bd19d87114b351e291e30d79dd38acf7" @@ -10422,13 +10373,6 @@ p-event@^5.0.1: dependencies: p-timeout "^5.0.2" -p-filter@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/p-filter/-/p-filter-3.0.0.tgz#ce50e03b24b23930e11679ab8694bd09a2d7ed35" - integrity sha512-QtoWLjXAW++uTX67HZQz1dbTpqBfiidsB6VtQUC9iR85S120+s0T5sO6s+B5MLzFcZkrEd/DGMmCjR+f2Qpxwg== - dependencies: - p-map "^5.1.0" - p-finally@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" @@ -10500,18 +10444,6 @@ p-map@4.0.0, p-map@^4.0.0: dependencies: aggregate-error "^3.0.0" -p-map@^5.1.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/p-map/-/p-map-5.5.0.tgz#054ca8ca778dfa4cf3f8db6638ccb5b937266715" - integrity sha512-VFqfGDHlx87K66yZrNdI4YGtD70IRyd+zSvgks6mzHPRNkoKy+9EKP4SFC77/vTTQYmRmti7dvqC+m5jBrBAcg== - dependencies: - aggregate-error "^4.0.0" - -p-map@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/p-map/-/p-map-6.0.0.tgz#4d9c40d3171632f86c47601b709f4b4acd70fed4" - integrity sha512-T8BatKGY+k5rU+Q/GTYgrEf2r4xRMevAN5mtXc2aPc4rS1j3s+vWTaO2Wag94neXuCAUAs8cxBL9EeB5EA6diw== - p-pipe@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/p-pipe/-/p-pipe-3.1.0.tgz#48b57c922aa2e1af6a6404cb7c6bf0eb9cc8e60e" @@ -12109,11 +12041,6 @@ slash@^1.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" integrity sha512-3TYDR7xWt4dIqV2JauJr+EJeW356RXijHeUlO+8djJ+uBXPn8/2dpzBc8yQhh583sVvc9CvFAeQVgijsH+PNNg== -slash@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-4.0.0.tgz#2422372176c4c6c5addb5e2ada885af984b396a7" - integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew== - slice-ansi@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-3.0.0.tgz#31ddc10930a1b7e0b67b08c96c2f49b77a789787"