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: 47b89a70b5

Closes #2077

Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
This commit is contained in:
Akos Kitta 2023-11-23 11:34:05 +01:00 committed by Akos Kitta
parent 3eef857b48
commit 074f654457
5 changed files with 228 additions and 148 deletions

View File

@ -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",

View File

@ -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';

View File

@ -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 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 {
SketchesService,
Sketch,
SketchRef,
SketchContainer,
SketchesError,
} 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 {
ArchiveSketchRequest,
LoadSketchRequest,
} from './cli-protocol/cc/arduino/cli/commands/v1/commands_pb';
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 { ServiceError } from './service-error';
import type { Mutable } from '@theia/core/lib/common/types';
import URI from '@theia/core/lib/common/uri';
import { FileUri } from '@theia/core/lib/node/file-uri';
import { inject, injectable, named } from '@theia/core/shared/inversify';
import glob from 'glob';
import crypto from 'node:crypto';
import {
IsTempSketch,
maybeNormalizeDrive,
TempSketchPrefix,
Win32DriveRegex,
} from './is-temp-sketch';
import { join } from 'node:path';
import { ErrnoException } from './utils/errors';
import { isWindows } from '@theia/core/lib/common/os';
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 {
Sketch,
SketchContainer,
SketchRef,
SketchesError,
SketchesService,
} from '../common/protocol/sketches-service';
import {
firstToLowerCase,
firstToUpperCase,
startsWithUpperCase,
} from '../common/utils';
import {
ArchiveSketchRequest,
LoadSketchRequest,
} from './cli-protocol/cc/arduino/cli/commands/v1/commands_pb';
import { ConfigServiceImpl } from './config-service-impl';
import { CoreClientAware } from './core-client-provider';
import {
IsTempSketch,
TempSketchPrefix,
Win32DriveRegex,
maybeNormalizeDrive,
} from './is-temp-sketch';
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<string> {

View File

@ -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<SketchesServiceImpl>(SketchesService);
const destinationPath = await sketchesService['createTempFolder']();
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<SketchesServiceImpl>(SketchesService);
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<SketchesServiceImpl>(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<SketchesServiceImpl>(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<SketchesServiceImpl>(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)',

View File

@ -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"