From 769428d1be0f76bf5ba6c9910b4efa2c524f4ba9 Mon Sep 17 00:00:00 2001 From: Jonas Helming Date: Wed, 13 Nov 2024 18:42:56 +0100 Subject: [PATCH 1/7] Add preference to ignore files in workspace functions fixed #14448 Signed-off-by: Jonas Helming --- packages/ai-workspace-agent/package.json | 4 +- .../src/browser/frontend-module.ts | 3 + .../src/browser/functions.ts | 85 ++++++++++++++++--- .../src/browser/workspace-preferences.ts | 41 +++++++++ yarn.lock | 12 +++ 5 files changed, 132 insertions(+), 13 deletions(-) create mode 100644 packages/ai-workspace-agent/src/browser/workspace-preferences.ts diff --git a/packages/ai-workspace-agent/package.json b/packages/ai-workspace-agent/package.json index 9078198afc962..e908495a54473 100644 --- a/packages/ai-workspace-agent/package.json +++ b/packages/ai-workspace-agent/package.json @@ -21,7 +21,9 @@ "@theia/navigator": "1.55.0", "@theia/terminal": "1.55.0", "@theia/ai-core": "1.55.0", - "@theia/ai-chat": "1.55.0" + "@theia/ai-chat": "1.55.0", + "ignore": "^6.0.0", + "minimatch": "^10.0.0" }, "publishConfig": { "access": "public" diff --git a/packages/ai-workspace-agent/src/browser/frontend-module.ts b/packages/ai-workspace-agent/src/browser/frontend-module.ts index f0136ce37cc7d..63fb8d3b84489 100644 --- a/packages/ai-workspace-agent/src/browser/frontend-module.ts +++ b/packages/ai-workspace-agent/src/browser/frontend-module.ts @@ -18,8 +18,11 @@ import { ChatAgent } from '@theia/ai-chat/lib/common'; import { Agent, ToolProvider } from '@theia/ai-core/lib/common'; import { WorkspaceAgent } from './workspace-agent'; import { FileContentFunction, GetWorkspaceDirectoryStructure, GetWorkspaceFileList, WorkspaceFunctionScope } from './functions'; +import { PreferenceContribution } from '@theia/core/lib/browser'; +import { WorkspacePreferencesSchema } from './workspace-preferences'; export default new ContainerModule(bind => { + bind(PreferenceContribution).toConstantValue({ schema: WorkspacePreferencesSchema }); bind(WorkspaceAgent).toSelf().inSingletonScope(); bind(Agent).toService(WorkspaceAgent); bind(ChatAgent).toService(WorkspaceAgent); diff --git a/packages/ai-workspace-agent/src/browser/functions.ts b/packages/ai-workspace-agent/src/browser/functions.ts index ecb35e01c2265..95067b0d29c52 100644 --- a/packages/ai-workspace-agent/src/browser/functions.ts +++ b/packages/ai-workspace-agent/src/browser/functions.ts @@ -20,12 +20,26 @@ import { FileService } from '@theia/filesystem/lib/browser/file-service'; import { FileStat } from '@theia/filesystem/lib/common/files'; import { WorkspaceService } from '@theia/workspace/lib/browser'; import { FILE_CONTENT_FUNCTION_ID, GET_WORKSPACE_DIRECTORY_STRUCTURE_FUNCTION_ID, GET_WORKSPACE_FILE_LIST_FUNCTION_ID } from '../common/functions'; - +import ignore from 'ignore'; +import { Minimatch } from 'minimatch'; +import { PreferenceService } from '@theia/core/lib/browser'; +import { CONSIDER_GITIGNORE_PREF, USER_EXCLUDES_PREF } from './workspace-preferences'; @injectable() export class WorkspaceFunctionScope { + protected readonly GITIGNORE_FILE_NAME = '.gitignore'; + @inject(WorkspaceService) protected workspaceService: WorkspaceService; + @inject(FileService) + protected fileService: FileService; + + @inject(PreferenceService) + protected preferences: PreferenceService; + + private gitignoreMatcher: ReturnType | undefined; + private gitignoreWatcherInitialized = false; + async getWorkspaceRoot(): Promise { const wsRoots = await this.workspaceService.roots; if (wsRoots.length === 0) { @@ -39,15 +53,59 @@ export class WorkspaceFunctionScope { throw new Error('Access outside of the workspace is not allowed'); } } - /** - * Determines whether a given file or directory should be excluded from workspace operations. - * - * @param stat - The `FileStat` object representing the file or directory to check. - * @returns `true` if the file or directory should be excluded, `false` otherwise. - */ - shouldExclude(stat: FileStat): boolean { - const excludedFolders = ['node_modules', 'lib']; - return stat.resource.path.base.startsWith('.') || excludedFolders.includes(stat.resource.path.base); + + private async initializeGitignoreWatcher(workspaceRoot: URI): Promise { + if (this.gitignoreWatcherInitialized) { + return; + } + + const gitignoreUri = workspaceRoot.resolve(this.GITIGNORE_FILE_NAME); + this.fileService.watch(gitignoreUri); + + this.fileService.onDidFilesChange(async event => { + if (event.contains(gitignoreUri)) { + this.gitignoreMatcher = undefined; + } + }); + + this.gitignoreWatcherInitialized = true; + } + + async shouldExclude(stat: FileStat): Promise { + const considerGitIgnore = this.preferences.get(CONSIDER_GITIGNORE_PREF, false); + const userExcludes = this.preferences.get(USER_EXCLUDES_PREF, []); + + const fileName = stat.resource.path.base; + + for (const pattern of userExcludes) { + const matcher = new Minimatch(pattern, { dot: true }); + if (matcher.match(fileName)) { return true; } + } + + if (considerGitIgnore) { + const workspaceRoot = await this.getWorkspaceRoot(); + await this.initializeGitignoreWatcher(workspaceRoot); + + const gitignoreUri = workspaceRoot.resolve(this.GITIGNORE_FILE_NAME); + + try { + const fileStat = await this.fileService.resolve(gitignoreUri); + if (fileStat && !fileStat.isDirectory) { + if (!this.gitignoreMatcher) { + const gitignoreContent = await this.fileService.read(gitignoreUri); + this.gitignoreMatcher = ignore().add(gitignoreContent.value); + } + const relativePath = workspaceRoot.relative(stat.resource); + if (relativePath && this.gitignoreMatcher.ignores(relativePath.toString())) { + return true; + } + } + } catch (error) { + // If .gitignore does not exist or cannot be read, continue without error + } + } + + return false; } } @@ -88,7 +146,7 @@ export class GetWorkspaceDirectoryStructure implements ToolProvider { if (stat && stat.isDirectory && stat.children) { for (const child of stat.children) { - if (!child.isDirectory || this.workspaceScope.shouldExclude(child)) { continue; }; + if (!child.isDirectory || await this.workspaceScope.shouldExclude(child)) { continue; }; const path = `${prefix}${child.resource.path.base}/`; result.push(path); result.push(...await this.buildDirectoryStructure(child.resource, `${path}`)); @@ -225,12 +283,15 @@ export class GetWorkspaceFileList implements ToolProvider { const result: string[] = []; if (stat && stat.isDirectory) { - if (this.workspaceScope.shouldExclude(stat)) { + if (await this.workspaceScope.shouldExclude(stat)) { return result; } const children = await this.fileService.resolve(uri); if (children.children) { for (const child of children.children) { + if (await this.workspaceScope.shouldExclude(child)) { + continue; + }; const relativePath = workspaceRootUri.relative(child.resource); if (relativePath) { result.push(relativePath.toString()); diff --git a/packages/ai-workspace-agent/src/browser/workspace-preferences.ts b/packages/ai-workspace-agent/src/browser/workspace-preferences.ts new file mode 100644 index 0000000000000..f12d8d173d5e5 --- /dev/null +++ b/packages/ai-workspace-agent/src/browser/workspace-preferences.ts @@ -0,0 +1,41 @@ +// ***************************************************************************** +// Copyright (C) 2024 EclipseSource GmbH. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0. +// +// This Source Code may also be made available under the following Secondary +// Licenses when the conditions for such availability set forth in the Eclipse +// Public License v. 2.0 are satisfied: GNU General Public License, version 2 +// with the GNU Classpath Exception which is available at +// https://www.gnu.org/software/classpath/license.html. +// +// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 +// ***************************************************************************** + +import { PreferenceSchema } from '@theia/core/lib/browser/preferences/preference-contribution'; + +export const CONSIDER_GITIGNORE_PREF = 'ai-features.workspace-functions.considerGitIgnore'; +export const USER_EXCLUDES_PREF = 'ai-features.workspace-functions.userExcludes'; + +export const WorkspacePreferencesSchema: PreferenceSchema = { + type: 'object', + properties: { + [CONSIDER_GITIGNORE_PREF]: { + type: 'boolean', + title: 'Consider .gitignore', + description: 'If enabled, excludes files/folders specified in a global .gitignore file (expected location is the workspace root).', + default: false + }, + [USER_EXCLUDES_PREF]: { + type: 'array', + title: 'User Excludes', + description: 'List of patterns (glob or regex) for files/folders to exclude by default.', + default: ['node_modules', 'lib', '.*'], + items: { + type: 'string' + } + } + } +}; diff --git a/yarn.lock b/yarn.lock index f153a205443bf..9e2a18e3280e8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6953,6 +6953,11 @@ ignore@^5.0.4, ignore@^5.2.0, ignore@^5.2.4: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== +ignore@^6.0.0: + version "6.0.2" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-6.0.2.tgz#77cccb72a55796af1b6d2f9eb14fa326d24f4283" + integrity sha512-InwqeHHN2XpumIkMvpl/DCJVrAHgCsG5+cn1XlnLWGwtZBm8QJfSusItfrwx81CTp5agNZqpKU2J/ccC5nGT4A== + image-size@~0.5.0: version "0.5.5" resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.5.5.tgz#09dfd4ab9d20e29eb1c3e80b8990378df9e3cb9c" @@ -8474,6 +8479,13 @@ minimatch@9.0.3, minimatch@^9.0.0, minimatch@^9.0.1: dependencies: brace-expansion "^2.0.1" +minimatch@^10.0.0: + version "10.0.1" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.0.1.tgz#ce0521856b453c86e25f2c4c0d03e6ff7ddc440b" + integrity sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ== + dependencies: + brace-expansion "^2.0.1" + minimatch@^3.0.0, minimatch@^3.0.3, minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" From 3961d7200b22a6bf806f6b35cbca40c98571faf7 Mon Sep 17 00:00:00 2001 From: Jonas Helming Date: Thu, 14 Nov 2024 11:24:45 +0100 Subject: [PATCH 2/7] Update packages/ai-workspace-agent/src/browser/functions.ts Co-authored-by: Philip Langer --- packages/ai-workspace-agent/src/browser/functions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ai-workspace-agent/src/browser/functions.ts b/packages/ai-workspace-agent/src/browser/functions.ts index 95067b0d29c52..572456c59e4f5 100644 --- a/packages/ai-workspace-agent/src/browser/functions.ts +++ b/packages/ai-workspace-agent/src/browser/functions.ts @@ -100,7 +100,7 @@ export class WorkspaceFunctionScope { return true; } } - } catch (error) { + } catch { // If .gitignore does not exist or cannot be read, continue without error } } From 481594471b1405e06441f0d37432f95eca13317e Mon Sep 17 00:00:00 2001 From: Jonas Helming Date: Thu, 14 Nov 2024 11:27:05 +0100 Subject: [PATCH 3/7] Update packages/ai-workspace-agent/src/browser/workspace-preferences.ts Co-authored-by: Philip Langer --- .../ai-workspace-agent/src/browser/workspace-preferences.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ai-workspace-agent/src/browser/workspace-preferences.ts b/packages/ai-workspace-agent/src/browser/workspace-preferences.ts index f12d8d173d5e5..af59fd909373e 100644 --- a/packages/ai-workspace-agent/src/browser/workspace-preferences.ts +++ b/packages/ai-workspace-agent/src/browser/workspace-preferences.ts @@ -31,7 +31,7 @@ export const WorkspacePreferencesSchema: PreferenceSchema = { [USER_EXCLUDES_PREF]: { type: 'array', title: 'User Excludes', - description: 'List of patterns (glob or regex) for files/folders to exclude by default.', + description: 'List of patterns (glob or regex) for files/folders to exclude.', default: ['node_modules', 'lib', '.*'], items: { type: 'string' From 14b70857f4b26bb47da0edfd94453fddedbde7cc Mon Sep 17 00:00:00 2001 From: Jonas Helming Date: Thu, 14 Nov 2024 12:04:41 +0100 Subject: [PATCH 4/7] Adressed review comments Signed-off-by: Jonas Helming --- .../src/browser/functions.ts | 55 ++++++++++--------- .../src/browser/workspace-preferences.ts | 8 +-- 2 files changed, 34 insertions(+), 29 deletions(-) diff --git a/packages/ai-workspace-agent/src/browser/functions.ts b/packages/ai-workspace-agent/src/browser/functions.ts index 572456c59e4f5..20a8cb479342b 100644 --- a/packages/ai-workspace-agent/src/browser/functions.ts +++ b/packages/ai-workspace-agent/src/browser/functions.ts @@ -23,7 +23,7 @@ import { FILE_CONTENT_FUNCTION_ID, GET_WORKSPACE_DIRECTORY_STRUCTURE_FUNCTION_ID import ignore from 'ignore'; import { Minimatch } from 'minimatch'; import { PreferenceService } from '@theia/core/lib/browser'; -import { CONSIDER_GITIGNORE_PREF, USER_EXCLUDES_PREF } from './workspace-preferences'; +import { CONSIDER_GITIGNORE_PREF, USER_EXCLUDE_PATTERN_PREF } from './workspace-preferences'; @injectable() export class WorkspaceFunctionScope { protected readonly GITIGNORE_FILE_NAME = '.gitignore'; @@ -72,41 +72,46 @@ export class WorkspaceFunctionScope { } async shouldExclude(stat: FileStat): Promise { - const considerGitIgnore = this.preferences.get(CONSIDER_GITIGNORE_PREF, false); - const userExcludes = this.preferences.get(USER_EXCLUDES_PREF, []); + const shouldConsiderGitIgnore = this.preferences.get(CONSIDER_GITIGNORE_PREF, false); + const userExcludePatterns = this.preferences.get(USER_EXCLUDE_PATTERN_PREF, []); - const fileName = stat.resource.path.base; - - for (const pattern of userExcludes) { - const matcher = new Minimatch(pattern, { dot: true }); - if (matcher.match(fileName)) { return true; } + if (this.isUserExcluded(stat.resource.path.base, userExcludePatterns)) { + return true; + } + const workspaceRoot = await this.getWorkspaceRoot(); + if (shouldConsiderGitIgnore && await this.isGitIgnored(stat, workspaceRoot)) { + return true; } - if (considerGitIgnore) { - const workspaceRoot = await this.getWorkspaceRoot(); - await this.initializeGitignoreWatcher(workspaceRoot); + return false; + } - const gitignoreUri = workspaceRoot.resolve(this.GITIGNORE_FILE_NAME); + private isUserExcluded(fileName: string, userExcludePatterns: string[]): boolean { + return userExcludePatterns.some(pattern => new Minimatch(pattern, { dot: true }).match(fileName)); + } - try { - const fileStat = await this.fileService.resolve(gitignoreUri); - if (fileStat && !fileStat.isDirectory) { - if (!this.gitignoreMatcher) { - const gitignoreContent = await this.fileService.read(gitignoreUri); - this.gitignoreMatcher = ignore().add(gitignoreContent.value); - } - const relativePath = workspaceRoot.relative(stat.resource); - if (relativePath && this.gitignoreMatcher.ignores(relativePath.toString())) { - return true; - } + private async isGitIgnored(stat: FileStat, workspaceRoot: URI): Promise { + await this.initializeGitignoreWatcher(workspaceRoot); + + const gitignoreUri = workspaceRoot.resolve(this.GITIGNORE_FILE_NAME); + + try { + const fileStat = await this.fileService.resolve(gitignoreUri); + if (fileStat && !fileStat.isDirectory) { + if (!this.gitignoreMatcher) { + const gitignoreContent = await this.fileService.read(gitignoreUri); + this.gitignoreMatcher = ignore().add(gitignoreContent.value); } - } catch { - // If .gitignore does not exist or cannot be read, continue without error + const relativePath = workspaceRoot.relative(stat.resource); + if (relativePath && this.gitignoreMatcher.ignores(relativePath.toString())) { return true; } } + } catch { + // If .gitignore does not exist or cannot be read, continue without error } return false; } + } @injectable() diff --git a/packages/ai-workspace-agent/src/browser/workspace-preferences.ts b/packages/ai-workspace-agent/src/browser/workspace-preferences.ts index af59fd909373e..98b0bb00ef0b2 100644 --- a/packages/ai-workspace-agent/src/browser/workspace-preferences.ts +++ b/packages/ai-workspace-agent/src/browser/workspace-preferences.ts @@ -16,8 +16,8 @@ import { PreferenceSchema } from '@theia/core/lib/browser/preferences/preference-contribution'; -export const CONSIDER_GITIGNORE_PREF = 'ai-features.workspace-functions.considerGitIgnore'; -export const USER_EXCLUDES_PREF = 'ai-features.workspace-functions.userExcludes'; +export const CONSIDER_GITIGNORE_PREF = 'ai-features.workspaceFunctions.considerGitIgnore'; +export const USER_EXCLUDE_PATTERN_PREF = 'ai-features.workspaceFunctions.userExcludes'; export const WorkspacePreferencesSchema: PreferenceSchema = { type: 'object', @@ -28,9 +28,9 @@ export const WorkspacePreferencesSchema: PreferenceSchema = { description: 'If enabled, excludes files/folders specified in a global .gitignore file (expected location is the workspace root).', default: false }, - [USER_EXCLUDES_PREF]: { + [USER_EXCLUDE_PATTERN_PREF]: { type: 'array', - title: 'User Excludes', + title: 'Excluded File Patterns', description: 'List of patterns (glob or regex) for files/folders to exclude.', default: ['node_modules', 'lib', '.*'], items: { From 25c61382d5b8f7b3309bbd0e86c80a5f54c797bf Mon Sep 17 00:00:00 2001 From: Jonas Helming Date: Thu, 14 Nov 2024 15:43:55 +0100 Subject: [PATCH 5/7] Update packages/ai-workspace-agent/src/browser/functions.ts Co-authored-by: Philip Langer --- packages/ai-workspace-agent/src/browser/functions.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/ai-workspace-agent/src/browser/functions.ts b/packages/ai-workspace-agent/src/browser/functions.ts index 20a8cb479342b..5854c98c69c3d 100644 --- a/packages/ai-workspace-agent/src/browser/functions.ts +++ b/packages/ai-workspace-agent/src/browser/functions.ts @@ -24,6 +24,7 @@ import ignore from 'ignore'; import { Minimatch } from 'minimatch'; import { PreferenceService } from '@theia/core/lib/browser'; import { CONSIDER_GITIGNORE_PREF, USER_EXCLUDE_PATTERN_PREF } from './workspace-preferences'; + @injectable() export class WorkspaceFunctionScope { protected readonly GITIGNORE_FILE_NAME = '.gitignore'; From 047d4fac744420dbdf74e549212d6a55851860f6 Mon Sep 17 00:00:00 2001 From: Jonas Helming Date: Thu, 14 Nov 2024 15:46:02 +0100 Subject: [PATCH 6/7] Update packages/ai-workspace-agent/src/browser/functions.ts Co-authored-by: Philip Langer --- packages/ai-workspace-agent/src/browser/functions.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/ai-workspace-agent/src/browser/functions.ts b/packages/ai-workspace-agent/src/browser/functions.ts index 5854c98c69c3d..27a771db9a057 100644 --- a/packages/ai-workspace-agent/src/browser/functions.ts +++ b/packages/ai-workspace-agent/src/browser/functions.ts @@ -98,13 +98,18 @@ export class WorkspaceFunctionScope { try { const fileStat = await this.fileService.resolve(gitignoreUri); - if (fileStat && !fileStat.isDirectory) { + if (fileStat) { if (!this.gitignoreMatcher) { const gitignoreContent = await this.fileService.read(gitignoreUri); this.gitignoreMatcher = ignore().add(gitignoreContent.value); } const relativePath = workspaceRoot.relative(stat.resource); - if (relativePath && this.gitignoreMatcher.ignores(relativePath.toString())) { return true; } + if (relativePath) { + const relativePathStr = relativePath.toString() + (stat.isDirectory ? '/' : ''); + if (this.gitignoreMatcher.ignores(relativePathStr)) { + return true; + } + } } } catch { // If .gitignore does not exist or cannot be read, continue without error From 78aae54e1c83c4a11289aa2575bcfac731254f2d Mon Sep 17 00:00:00 2001 From: Jonas Helming Date: Thu, 14 Nov 2024 20:38:31 +0100 Subject: [PATCH 7/7] Made functions protected Downgraded minimatch for Node 18 compatibility Signed-off-by: Jonas Helming --- packages/ai-workspace-agent/package.json | 2 +- packages/ai-workspace-agent/src/browser/functions.ts | 4 ++-- yarn.lock | 7 ------- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/packages/ai-workspace-agent/package.json b/packages/ai-workspace-agent/package.json index e908495a54473..ce14788d25f64 100644 --- a/packages/ai-workspace-agent/package.json +++ b/packages/ai-workspace-agent/package.json @@ -23,7 +23,7 @@ "@theia/ai-core": "1.55.0", "@theia/ai-chat": "1.55.0", "ignore": "^6.0.0", - "minimatch": "^10.0.0" + "minimatch": "^9.0.0" }, "publishConfig": { "access": "public" diff --git a/packages/ai-workspace-agent/src/browser/functions.ts b/packages/ai-workspace-agent/src/browser/functions.ts index 27a771db9a057..bf38d6ba207c0 100644 --- a/packages/ai-workspace-agent/src/browser/functions.ts +++ b/packages/ai-workspace-agent/src/browser/functions.ts @@ -87,11 +87,11 @@ export class WorkspaceFunctionScope { return false; } - private isUserExcluded(fileName: string, userExcludePatterns: string[]): boolean { + protected isUserExcluded(fileName: string, userExcludePatterns: string[]): boolean { return userExcludePatterns.some(pattern => new Minimatch(pattern, { dot: true }).match(fileName)); } - private async isGitIgnored(stat: FileStat, workspaceRoot: URI): Promise { + protected async isGitIgnored(stat: FileStat, workspaceRoot: URI): Promise { await this.initializeGitignoreWatcher(workspaceRoot); const gitignoreUri = workspaceRoot.resolve(this.GITIGNORE_FILE_NAME); diff --git a/yarn.lock b/yarn.lock index 9e2a18e3280e8..b591344420ee7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8479,13 +8479,6 @@ minimatch@9.0.3, minimatch@^9.0.0, minimatch@^9.0.1: dependencies: brace-expansion "^2.0.1" -minimatch@^10.0.0: - version "10.0.1" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.0.1.tgz#ce0521856b453c86e25f2c4c0d03e6ff7ddc440b" - integrity sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ== - dependencies: - brace-expansion "^2.0.1" - minimatch@^3.0.0, minimatch@^3.0.3, minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"