diff --git a/.yarn/versions/607ebb1b.yml b/.yarn/versions/607ebb1b.yml new file mode 100644 index 00000000000..6c24923445d --- /dev/null +++ b/.yarn/versions/607ebb1b.yml @@ -0,0 +1,36 @@ +releases: + "@yarnpkg/core": patch + +declined: + - "@yarnpkg/plugin-catalog" + - "@yarnpkg/plugin-compat" + - "@yarnpkg/plugin-constraints" + - "@yarnpkg/plugin-dlx" + - "@yarnpkg/plugin-essentials" + - "@yarnpkg/plugin-exec" + - "@yarnpkg/plugin-file" + - "@yarnpkg/plugin-git" + - "@yarnpkg/plugin-github" + - "@yarnpkg/plugin-http" + - "@yarnpkg/plugin-init" + - "@yarnpkg/plugin-interactive-tools" + - "@yarnpkg/plugin-jsr" + - "@yarnpkg/plugin-link" + - "@yarnpkg/plugin-nm" + - "@yarnpkg/plugin-npm" + - "@yarnpkg/plugin-npm-cli" + - "@yarnpkg/plugin-pack" + - "@yarnpkg/plugin-patch" + - "@yarnpkg/plugin-pnp" + - "@yarnpkg/plugin-pnpm" + - "@yarnpkg/plugin-stage" + - "@yarnpkg/plugin-typescript" + - "@yarnpkg/plugin-version" + - "@yarnpkg/plugin-workspace-tools" + - "@yarnpkg/builder" + - "@yarnpkg/cli" + - "@yarnpkg/doctor" + - "@yarnpkg/extensions" + - "@yarnpkg/nm" + - "@yarnpkg/pnpify" + - "@yarnpkg/sdks" diff --git a/packages/acceptance-tests/pkg-tests-specs/sources/workspace.test.ts b/packages/acceptance-tests/pkg-tests-specs/sources/workspace.test.ts index 868f70f1863..0d53a20d63b 100644 --- a/packages/acceptance-tests/pkg-tests-specs/sources/workspace.test.ts +++ b/packages/acceptance-tests/pkg-tests-specs/sources/workspace.test.ts @@ -35,6 +35,27 @@ describe(`Workspaces tests`, () => { }), ); + test( + `it should support basic glob patterns with enableWorkspacePatternAnalysis`, + makeTemporaryMonorepoEnv({ + workspaces: [ + `packages/*`, + ], + }, { + [`packages/foo`]: {}, + [`packages/bar`]: {}, + [`packages/baz`]: {}, + }, async ({path, run, source}) => { + await run(`config`, `set`, `enableWorkspacePatternAnalysis`, `true`); + await expect(getWorkspaces(run)).resolves.toStrictEqual([ + `.`, + `packages/bar`, + `packages/baz`, + `packages/foo`, + ]); + }), + ); + test( `it should support negated glob patterns`, makeTemporaryMonorepoEnv({ @@ -55,6 +76,27 @@ describe(`Workspaces tests`, () => { }), ); + test( + `it should support negated glob patterns with enableWorkspacePatternAnalysis`, + makeTemporaryMonorepoEnv({ + workspaces: [ + `packages/*`, + `!packages/foo`, + ], + }, { + [`packages/foo`]: {}, + [`packages/bar`]: {}, + [`packages/baz`]: {}, + }, async ({path, run, source}) => { + await run(`config`, `set`, `enableWorkspacePatternAnalysis`, `true`); + await expect(getWorkspaces(run)).resolves.toStrictEqual([ + `.`, + `packages/bar`, + `packages/baz`, + ]); + }), + ); + test( `it should not implicitly make workspaces require-able`, makeTemporaryEnv( diff --git a/packages/yarnpkg-core/sources/Configuration.ts b/packages/yarnpkg-core/sources/Configuration.ts index ed63eb4cd20..6e6b0d1ab8c 100644 --- a/packages/yarnpkg-core/sources/Configuration.ts +++ b/packages/yarnpkg-core/sources/Configuration.ts @@ -364,6 +364,11 @@ export const coreDefinitions: {[coreSettingName: string]: SettingsDefinition} = type: SettingsType.STRING, default: `npm:`, }, + enableWorkspacePatternAnalysis: { + description: `If true, optimizes workspace pattern matching by separating static and dynamic globs for better performance when using mostly static patterns.`, + type: SettingsType.BOOLEAN, + default: false, + }, enableTransparentWorkspaces: { description: `If false, Yarn won't automatically resolve workspace dependencies unless they use the \`workspace:\` protocol`, type: SettingsType.BOOLEAN, diff --git a/packages/yarnpkg-core/sources/Workspace.ts b/packages/yarnpkg-core/sources/Workspace.ts index 45828031b54..18e455a7783 100644 --- a/packages/yarnpkg-core/sources/Workspace.ts +++ b/packages/yarnpkg-core/sources/Workspace.ts @@ -48,17 +48,40 @@ export class Workspace { // @ts-expect-error: It's ok to initialize it now, even if it's readonly (setup is called right after construction) this.anchoredLocator = structUtils.makeLocator(ident, `${WorkspaceResolver.protocol}${this.relativeCwd}`); - const patterns = this.manifest.workspaceDefinitions.map(({pattern}) => pattern); + const workspaceDefinitionPatterns = this.manifest.workspaceDefinitions.map(({pattern}) => pattern); - if (patterns.length === 0) + if (workspaceDefinitionPatterns.length === 0) return; - const relativeCwds = await fastGlob(patterns, { + const resolvePatternsWithAnalysis = async () => { + const staticPatterns: Array = []; + const dynamicPatterns: Array = []; + + for (const pattern of workspaceDefinitionPatterns) { + if (fastGlob.isDynamicPattern(pattern)) { + dynamicPatterns.push(pattern); + } else { + staticPatterns.push(pattern); + } + } + + const globResults = dynamicPatterns.length > 0 + ? await resolveGlobPatterns(dynamicPatterns) + : []; + + return [...staticPatterns, ...globResults]; + }; + + const resolveGlobPatterns = (globPatterns: Array) => fastGlob(globPatterns, { cwd: npath.fromPortablePath(this.cwd), onlyDirectories: true, ignore: [`**/node_modules`, `**/.git`, `**/.yarn`], }); + const relativeCwds = this.project.configuration.get(`enableWorkspacePatternAnalysis`) ? + await resolvePatternsWithAnalysis() : + await resolveGlobPatterns(workspaceDefinitionPatterns); + // fast-glob returns results in arbitrary order relativeCwds.sort();