|
1 | | -import {BaseCommand, WorkspaceRequiredError} from '@yarnpkg/cli'; |
2 | | -import {Configuration, LocatorHash, Project, scriptUtils, Workspace} from '@yarnpkg/core'; |
3 | | -import {DescriptorHash, MessageName, Report, StreamReport} from '@yarnpkg/core'; |
4 | | -import {formatUtils, miscUtils, structUtils, nodeUtils} from '@yarnpkg/core'; |
5 | | -import {gitUtils} from '@yarnpkg/plugin-git'; |
6 | | -import {Command, Option, Usage, UsageError} from 'clipanion'; |
7 | | -import micromatch from 'micromatch'; |
8 | | -import pLimit from 'p-limit'; |
9 | | -import {Writable} from 'stream'; |
10 | | -import {WriteStream} from 'tty'; |
11 | | -import * as t from 'typanion'; |
| 1 | +import {BaseCommand, WorkspaceRequiredError} from '@yarnpkg/cli'; |
| 2 | +import {Configuration, LocatorHash, Manifest, Project, scriptUtils, ThrowReport, Workspace} from '@yarnpkg/core'; |
| 3 | +import {Descriptor, DescriptorHash, HardDependencies, MessageName, Report, StreamReport} from '@yarnpkg/core'; |
| 4 | +import {formatUtils, miscUtils, structUtils, nodeUtils} from '@yarnpkg/core'; |
| 5 | +import {gitUtils} from '@yarnpkg/plugin-git'; |
| 6 | +import {Command, Option, Usage, UsageError} from 'clipanion'; |
| 7 | +import micromatch from 'micromatch'; |
| 8 | +import pLimit from 'p-limit'; |
| 9 | +import {Writable} from 'stream'; |
| 10 | +import {WriteStream} from 'tty'; |
| 11 | +import * as t from 'typanion'; |
12 | 12 |
|
13 | 13 | // eslint-disable-next-line arca/no-default-export |
14 | 14 | export default class WorkspacesForeachCommand extends BaseCommand { |
@@ -149,6 +149,90 @@ export default class WorkspacesForeachCommand extends BaseCommand { |
149 | 149 | if (command.path.length === 0) |
150 | 150 | throw new UsageError(`Invalid subcommand name for iteration - use the 'run' keyword if you wish to execute a script`); |
151 | 151 |
|
| 152 | + const resolver = configuration.makeResolver(); |
| 153 | + const resolveOptions = { |
| 154 | + project, |
| 155 | + resolver, |
| 156 | + report: new ThrowReport(), |
| 157 | + }; |
| 158 | + |
| 159 | + const resolvedWorkspaceDependencies = new Map<string, Promise<Workspace | null>>(); |
| 160 | + const tryWorkspaceByDescriptor = async (workspace: Workspace, descriptor: Descriptor) => { |
| 161 | + const cacheKey = `${workspace.anchoredLocator.locatorHash}:${descriptor.descriptorHash}`; |
| 162 | + let promise = resolvedWorkspaceDependencies.get(cacheKey); |
| 163 | + if (typeof promise === `undefined`) { |
| 164 | + promise = (async () => { |
| 165 | + const dependency = await configuration.reduceHook(hooks => { |
| 166 | + return hooks.reduceDependency; |
| 167 | + }, descriptor, project, workspace.anchoredLocator, descriptor, { |
| 168 | + resolver, |
| 169 | + resolveOptions, |
| 170 | + }); |
| 171 | + |
| 172 | + if (!structUtils.areIdentsEqual(descriptor, dependency)) |
| 173 | + throw new Error(`Assertion failed: The descriptor ident cannot be changed through aliases`); |
| 174 | + |
| 175 | + return project.tryWorkspaceByDescriptor(dependency); |
| 176 | + })(); |
| 177 | + |
| 178 | + resolvedWorkspaceDependencies.set(cacheKey, promise); |
| 179 | + } |
| 180 | + |
| 181 | + return await promise; |
| 182 | + }; |
| 183 | + |
| 184 | + const getRecursiveWorkspaceDependencies = async (workspace: Workspace, {dependencies = Manifest.hardDependencies}: {dependencies?: Array<HardDependencies>} = {}) => { |
| 185 | + const workspaceList = new Set<Workspace>(); |
| 186 | + |
| 187 | + const visitWorkspace = async (workspace: Workspace) => { |
| 188 | + for (const dependencyType of dependencies) { |
| 189 | + for (const descriptor of workspace.manifest[dependencyType].values()) { |
| 190 | + const foundWorkspace = await tryWorkspaceByDescriptor(workspace, descriptor); |
| 191 | + if (foundWorkspace === null || workspaceList.has(foundWorkspace)) |
| 192 | + continue; |
| 193 | + |
| 194 | + workspaceList.add(foundWorkspace); |
| 195 | + await visitWorkspace(foundWorkspace); |
| 196 | + } |
| 197 | + } |
| 198 | + }; |
| 199 | + |
| 200 | + await visitWorkspace(workspace); |
| 201 | + return workspaceList; |
| 202 | + }; |
| 203 | + |
| 204 | + const getRecursiveWorkspaceDependents = async (workspace: Workspace, {dependencies = Manifest.hardDependencies}: {dependencies?: Array<HardDependencies>} = {}) => { |
| 205 | + const workspaceList = new Set<Workspace>(); |
| 206 | + |
| 207 | + const visitWorkspace = async (workspace: Workspace) => { |
| 208 | + for (const projectWorkspace of project.workspaces) { |
| 209 | + let isDependent = false; |
| 210 | + |
| 211 | + for (const dependencyType of dependencies) { |
| 212 | + for (const descriptor of projectWorkspace.manifest[dependencyType].values()) { |
| 213 | + const foundWorkspace = await tryWorkspaceByDescriptor(projectWorkspace, descriptor); |
| 214 | + if (foundWorkspace !== null && structUtils.areLocatorsEqual(foundWorkspace.anchoredLocator, workspace.anchoredLocator)) { |
| 215 | + isDependent = true; |
| 216 | + break; |
| 217 | + } |
| 218 | + } |
| 219 | + |
| 220 | + if (isDependent) { |
| 221 | + break; |
| 222 | + } |
| 223 | + } |
| 224 | + |
| 225 | + if (isDependent && !workspaceList.has(projectWorkspace)) { |
| 226 | + workspaceList.add(projectWorkspace); |
| 227 | + await visitWorkspace(projectWorkspace); |
| 228 | + } |
| 229 | + } |
| 230 | + }; |
| 231 | + |
| 232 | + await visitWorkspace(workspace); |
| 233 | + return workspaceList; |
| 234 | + }; |
| 235 | + |
152 | 236 | const log = (msg: string) => { |
153 | 237 | if (!this.dryRun) |
154 | 238 | return; |
@@ -200,10 +284,14 @@ export default class WorkspacesForeachCommand extends BaseCommand { |
200 | 284 | if (this.recursive) { |
201 | 285 | if (this.since) { |
202 | 286 | log(`Option --recursive --since is set; recursively selecting all dependent workspaces`); |
203 | | - extra = new Set(selection.map(workspace => [...workspace.getRecursiveWorkspaceDependents()]).flat()); |
| 287 | + extra = new Set((await Promise.all(selection.map(async workspace => { |
| 288 | + return [...await getRecursiveWorkspaceDependents(workspace)]; |
| 289 | + }))).flat()); |
204 | 290 | } else { |
205 | 291 | log(`Option --recursive is set; recursively selecting all transitive dependencies`); |
206 | | - extra = new Set(selection.map(workspace => [...workspace.getRecursiveWorkspaceDependencies()]).flat()); |
| 292 | + extra = new Set((await Promise.all(selection.map(async workspace => { |
| 293 | + return [...await getRecursiveWorkspaceDependencies(workspace)]; |
| 294 | + }))).flat()); |
207 | 295 | } |
208 | 296 | } else if (this.worktree) { |
209 | 297 | log(`Option --worktree is set; recursively selecting all nested workspaces`); |
@@ -387,8 +475,8 @@ export default class WorkspacesForeachCommand extends BaseCommand { |
387 | 475 | : workspace.manifest.dependencies; |
388 | 476 |
|
389 | 477 | for (const descriptor of resolvedSet.values()) { |
390 | | - const workspace = project.tryWorkspaceByDescriptor(descriptor); |
391 | | - isRunnable = workspace === null || !needsProcessing.has(workspace.anchoredLocator.locatorHash); |
| 478 | + const dependencyWorkspace = await tryWorkspaceByDescriptor(workspace, descriptor); |
| 479 | + isRunnable = dependencyWorkspace === null || !needsProcessing.has(dependencyWorkspace.anchoredLocator.locatorHash); |
392 | 480 |
|
393 | 481 | if (!isRunnable) { |
394 | 482 | break; |
|
0 commit comments