mirror of
https://github.com/tiktok/sparo.git
synced 2024-11-14 11:25:09 -05:00
feat: introduce a quicker selection algo if applicable
This commit is contained in:
parent
90cb8f6008
commit
9ba131bc76
6 changed files with 596 additions and 65 deletions
|
@ -22,6 +22,7 @@
|
|||
"@rushstack/terminal": "~0.8.1",
|
||||
"git-repo-info": "~2.1.1",
|
||||
"inversify": "~6.0.2",
|
||||
"npm-package-arg": "~6.1.0",
|
||||
"reflect-metadata": "~0.2.1",
|
||||
"semver": "~7.6.0",
|
||||
"update-notifier": "~5.1.0",
|
||||
|
@ -32,6 +33,7 @@
|
|||
"@rushstack/heft-node-rig": "2.4.5",
|
||||
"@types/heft-jest": "1.0.6",
|
||||
"@types/node": "20.11.16",
|
||||
"@types/npm-package-arg": "6.1.0",
|
||||
"@types/semver": "7.5.7",
|
||||
"@types/update-notifier": "6.0.8",
|
||||
"@types/yargs": "17.0.32",
|
||||
|
|
178
apps/sparo-lib/src/logic/DependencySpecifier.ts
Normal file
178
apps/sparo-lib/src/logic/DependencySpecifier.ts
Normal file
|
@ -0,0 +1,178 @@
|
|||
// This is copied from rush.js source code
|
||||
// https://github.com/microsoft/rushstack/blob/312b8bc554e64d66b586c65499a512dbf1c329ff/libraries/rush-lib/src/logic/DependencySpecifier.ts
|
||||
|
||||
import npmPackageArg from 'npm-package-arg';
|
||||
import { InternalError } from '@rushstack/node-core-library';
|
||||
|
||||
/**
|
||||
* match workspace protocol in dependencies value declaration in `package.json`
|
||||
* example:
|
||||
* `"workspace:*"`
|
||||
* `"workspace:alias@1.2.3"`
|
||||
*/
|
||||
const WORKSPACE_PREFIX_REGEX: RegExp = /^workspace:((?<alias>[^._/][^@]*)@)?(?<version>.*)$/;
|
||||
|
||||
/**
|
||||
* resolve workspace protocol(from `@pnpm/workspace.spec-parser`).
|
||||
* used by pnpm. see [pkgs-graph](https://github.com/pnpm/pnpm/blob/27c33f0319f86c45c1645d064cd9c28aada80780/workspace/pkgs-graph/src/index.ts#L49)
|
||||
*/
|
||||
class WorkspaceSpec {
|
||||
public readonly alias?: string;
|
||||
public readonly version: string;
|
||||
public readonly versionSpecifier: string;
|
||||
|
||||
public constructor(version: string, alias?: string) {
|
||||
this.version = version;
|
||||
this.alias = alias;
|
||||
this.versionSpecifier = alias ? `${alias}@${version}` : version;
|
||||
}
|
||||
|
||||
public static tryParse(pref: string): WorkspaceSpec | undefined {
|
||||
const parts: RegExpExecArray | null = WORKSPACE_PREFIX_REGEX.exec(pref);
|
||||
if (parts?.groups) {
|
||||
return new WorkspaceSpec(parts.groups.version, parts.groups.alias);
|
||||
}
|
||||
}
|
||||
|
||||
public toString(): `workspace:${string}` {
|
||||
return `workspace:${this.versionSpecifier}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The parsed format of a provided version specifier.
|
||||
*/
|
||||
export enum DependencySpecifierType {
|
||||
/**
|
||||
* A git repository
|
||||
*/
|
||||
Git = 'Git',
|
||||
|
||||
/**
|
||||
* A tagged version, e.g. "example@latest"
|
||||
*/
|
||||
Tag = 'Tag',
|
||||
|
||||
/**
|
||||
* A specific version number, e.g. "example@1.2.3"
|
||||
*/
|
||||
Version = 'Version',
|
||||
|
||||
/**
|
||||
* A version range, e.g. "example@2.x"
|
||||
*/
|
||||
Range = 'Range',
|
||||
|
||||
/**
|
||||
* A local .tar.gz, .tar or .tgz file
|
||||
*/
|
||||
File = 'File',
|
||||
|
||||
/**
|
||||
* A local directory
|
||||
*/
|
||||
Directory = 'Directory',
|
||||
|
||||
/**
|
||||
* An HTTP url to a .tar.gz, .tar or .tgz file
|
||||
*/
|
||||
Remote = 'Remote',
|
||||
|
||||
/**
|
||||
* A package alias, e.g. "npm:other-package@^1.2.3"
|
||||
*/
|
||||
Alias = 'Alias',
|
||||
|
||||
/**
|
||||
* A package specified using workspace protocol, e.g. "workspace:^1.2.3"
|
||||
*/
|
||||
Workspace = 'Workspace'
|
||||
}
|
||||
|
||||
/**
|
||||
* An NPM "version specifier" is a string that can appear as a package.json "dependencies" value.
|
||||
* Example version specifiers: `^1.2.3`, `file:./blah.tgz`, `npm:other-package@~1.2.3`, and so forth.
|
||||
* A "dependency specifier" is the version specifier information, combined with the dependency package name.
|
||||
*/
|
||||
export class DependencySpecifier {
|
||||
/**
|
||||
* The dependency package name, i.e. the key from a "dependencies" key/value table.
|
||||
*/
|
||||
public readonly packageName: string;
|
||||
|
||||
/**
|
||||
* The dependency version specifier, i.e. the value from a "dependencies" key/value table.
|
||||
* Example values: `^1.2.3`, `file:./blah.tgz`, `npm:other-package@~1.2.3`
|
||||
*/
|
||||
public readonly versionSpecifier: string;
|
||||
|
||||
/**
|
||||
* The type of the `versionSpecifier`.
|
||||
*/
|
||||
public readonly specifierType: DependencySpecifierType;
|
||||
|
||||
/**
|
||||
* If `specifierType` is `alias`, then this is the parsed target dependency.
|
||||
* For example, if version specifier i `"npm:other-package@^1.2.3"` then this is the parsed object for
|
||||
* `other-package@^1.2.3`.
|
||||
*/
|
||||
public readonly aliasTarget: DependencySpecifier | undefined;
|
||||
|
||||
public constructor(packageName: string, versionSpecifier: string) {
|
||||
this.packageName = packageName;
|
||||
this.versionSpecifier = versionSpecifier;
|
||||
|
||||
// Workspace ranges are a feature from PNPM and Yarn. Set the version specifier
|
||||
// to the trimmed version range.
|
||||
const workspaceSpecResult: WorkspaceSpec | undefined = WorkspaceSpec.tryParse(versionSpecifier);
|
||||
if (workspaceSpecResult) {
|
||||
this.specifierType = DependencySpecifierType.Workspace;
|
||||
this.versionSpecifier = workspaceSpecResult.versionSpecifier;
|
||||
|
||||
if (workspaceSpecResult.alias) {
|
||||
// "workspace:some-package@^1.2.3" should be resolved as alias
|
||||
this.aliasTarget = new DependencySpecifier(workspaceSpecResult.alias, workspaceSpecResult.version);
|
||||
} else {
|
||||
this.aliasTarget = undefined;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const result: npmPackageArg.Result = npmPackageArg.resolve(packageName, versionSpecifier);
|
||||
this.specifierType = DependencySpecifier.getDependencySpecifierType(result.type);
|
||||
|
||||
if (this.specifierType === DependencySpecifierType.Alias) {
|
||||
const aliasResult: npmPackageArg.AliasResult = result as npmPackageArg.AliasResult;
|
||||
if (!aliasResult.subSpec || !aliasResult.subSpec.name) {
|
||||
throw new InternalError('Unexpected result from npm-package-arg');
|
||||
}
|
||||
this.aliasTarget = new DependencySpecifier(aliasResult.subSpec.name, aliasResult.subSpec.rawSpec);
|
||||
} else {
|
||||
this.aliasTarget = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
public static getDependencySpecifierType(specifierType: string): DependencySpecifierType {
|
||||
switch (specifierType) {
|
||||
case 'git':
|
||||
return DependencySpecifierType.Git;
|
||||
case 'tag':
|
||||
return DependencySpecifierType.Tag;
|
||||
case 'version':
|
||||
return DependencySpecifierType.Version;
|
||||
case 'range':
|
||||
return DependencySpecifierType.Range;
|
||||
case 'file':
|
||||
return DependencySpecifierType.File;
|
||||
case 'directory':
|
||||
return DependencySpecifierType.Directory;
|
||||
case 'remote':
|
||||
return DependencySpecifierType.Remote;
|
||||
case 'alias':
|
||||
return DependencySpecifierType.Alias;
|
||||
default:
|
||||
throw new InternalError(`Unexpected npm-package-arg result type "${specifierType}"`);
|
||||
}
|
||||
}
|
||||
}
|
117
apps/sparo-lib/src/logic/RushProjectSlim.ts
Normal file
117
apps/sparo-lib/src/logic/RushProjectSlim.ts
Normal file
|
@ -0,0 +1,117 @@
|
|||
import * as path from 'path';
|
||||
import * as semver from 'semver';
|
||||
import { JsonFile } from '@rushstack/node-core-library';
|
||||
import { DependencySpecifier, DependencySpecifierType } from './DependencySpecifier';
|
||||
|
||||
export interface IProjectJson {
|
||||
packageName: string;
|
||||
projectFolder: string;
|
||||
decoupledLocalDependencies?: string[];
|
||||
cyclicDependencyProjects?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* A slim version of RushConfigurationProject
|
||||
*/
|
||||
export class RushProjectSlim {
|
||||
public packageName: string;
|
||||
public projectFolder: string;
|
||||
public relativeProjectFolder: string;
|
||||
public packageJson: {
|
||||
name: string;
|
||||
version: string;
|
||||
dependencies?: Record<string, string>;
|
||||
devDependencies?: Record<string, string>;
|
||||
optionalDependencies?: Record<string, string>;
|
||||
};
|
||||
public decoupledLocalDependencies: Set<string>;
|
||||
|
||||
private _packageNameToRushProjectSlim: Map<string, RushProjectSlim>;
|
||||
private _dependencyProjects: Set<RushProjectSlim> | undefined;
|
||||
private _consumingProjects: Set<RushProjectSlim> | undefined;
|
||||
|
||||
public constructor(
|
||||
projectJson: IProjectJson,
|
||||
repoRootPath: string,
|
||||
packageNameToRushProjectSlim: Map<string, RushProjectSlim>
|
||||
) {
|
||||
this.packageName = projectJson.packageName;
|
||||
this.projectFolder = path.resolve(repoRootPath, projectJson.projectFolder);
|
||||
this.relativeProjectFolder = projectJson.projectFolder;
|
||||
const packageJsonPath: string = path.resolve(this.projectFolder, 'package.json');
|
||||
this.packageJson = JsonFile.load(packageJsonPath);
|
||||
this._packageNameToRushProjectSlim = packageNameToRushProjectSlim;
|
||||
|
||||
this.decoupledLocalDependencies = new Set<string>();
|
||||
if (projectJson.cyclicDependencyProjects || projectJson.decoupledLocalDependencies) {
|
||||
if (projectJson.cyclicDependencyProjects && projectJson.decoupledLocalDependencies) {
|
||||
throw new Error(
|
||||
'A project configuration cannot specify both "decoupledLocalDependencies" and "cyclicDependencyProjects". Please use "decoupledLocalDependencies" only -- the other name is deprecated.'
|
||||
);
|
||||
}
|
||||
for (const cyclicDependencyProject of projectJson.cyclicDependencyProjects ||
|
||||
projectJson.decoupledLocalDependencies ||
|
||||
[]) {
|
||||
this.decoupledLocalDependencies.add(cyclicDependencyProject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public get dependencyProjects(): ReadonlySet<RushProjectSlim> {
|
||||
if (this._dependencyProjects) {
|
||||
return this._dependencyProjects;
|
||||
}
|
||||
const dependencyProjects: Set<RushProjectSlim> = new Set<RushProjectSlim>();
|
||||
const { packageJson } = this;
|
||||
for (const dependencySet of [
|
||||
packageJson.dependencies,
|
||||
packageJson.devDependencies,
|
||||
packageJson.optionalDependencies
|
||||
]) {
|
||||
if (dependencySet) {
|
||||
for (const [dependency, version] of Object.entries(dependencySet)) {
|
||||
const dependencySpecifier: DependencySpecifier = new DependencySpecifier(dependency, version);
|
||||
const dependencyName: string =
|
||||
dependencySpecifier.aliasTarget?.packageName ?? dependencySpecifier.packageName;
|
||||
// Skip if we can't find the local project or it's a cyclic dependency
|
||||
const localProject: RushProjectSlim | undefined =
|
||||
this._packageNameToRushProjectSlim.get(dependencyName);
|
||||
if (localProject && !this.decoupledLocalDependencies.has(dependency)) {
|
||||
// Set the value if it's a workspace project, or if we have a local project and the semver is satisfied
|
||||
switch (dependencySpecifier.specifierType) {
|
||||
case DependencySpecifierType.Version:
|
||||
case DependencySpecifierType.Range:
|
||||
if (
|
||||
semver.satisfies(localProject.packageJson.version, dependencySpecifier.versionSpecifier)
|
||||
) {
|
||||
dependencyProjects.add(localProject);
|
||||
}
|
||||
break;
|
||||
case DependencySpecifierType.Workspace:
|
||||
dependencyProjects.add(localProject);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this._dependencyProjects = dependencyProjects;
|
||||
return this._dependencyProjects;
|
||||
}
|
||||
|
||||
public get consumingProjects(): ReadonlySet<RushProjectSlim> {
|
||||
if (!this._consumingProjects) {
|
||||
// Force initialize all dependencies relationship
|
||||
for (const project of this._packageNameToRushProjectSlim.values()) {
|
||||
project._consumingProjects = new Set();
|
||||
}
|
||||
|
||||
for (const project of this._packageNameToRushProjectSlim.values()) {
|
||||
for (const dependency of project.dependencyProjects) {
|
||||
dependency._consumingProjects!.add(project);
|
||||
}
|
||||
}
|
||||
}
|
||||
return this._consumingProjects!;
|
||||
}
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
import * as path from 'path';
|
||||
import * as child_process from 'child_process';
|
||||
import { inject } from 'inversify';
|
||||
import { Service } from '../decorator';
|
||||
import { GitService } from './GitService';
|
||||
import { TerminalService } from './TerminalService';
|
||||
import { Executable, FileSystem, JsonFile, JsonSyntax } from '@rushstack/node-core-library';
|
||||
import { SelectionParameterService } from './SelectionParameterService';
|
||||
import { FileSystem, JsonFile, JsonSyntax } from '@rushstack/node-core-library';
|
||||
import { Stopwatch } from '../logic/Stopwatch';
|
||||
|
||||
import type { ISelection } from '../logic/SparoProfile';
|
||||
|
@ -26,6 +26,7 @@ export interface IRushProject {
|
|||
export class GitSparseCheckoutService {
|
||||
@inject(GitService) private _gitService!: GitService;
|
||||
@inject(TerminalService) private _terminalService!: TerminalService;
|
||||
@inject(SelectionParameterService) private _selectionParameterService!: SelectionParameterService;
|
||||
|
||||
private _rushConfigLoaded: boolean = false;
|
||||
private _rushProjects: IRushProject[] = [];
|
||||
|
@ -156,16 +157,46 @@ export class GitSparseCheckoutService {
|
|||
}
|
||||
}
|
||||
|
||||
// Validate incorrect package names
|
||||
const unfoundedPackages: string[] = [];
|
||||
for (const selector of [...toSelectors, ...fromSelectors]) {
|
||||
if (selector.indexOf(':') < 0) {
|
||||
const packageName: string = selector;
|
||||
|
||||
let additionalFullPackageNames: string[] = [];
|
||||
for (const selectorArg of toSelectors) {
|
||||
if (selectorArg.indexOf(':') < 0) {
|
||||
const packageName: string = selectorArg;
|
||||
const result: string | undefined = this._findProjectByShorthandName(packageName);
|
||||
if (!result) {
|
||||
unfoundedPackages.push(packageName);
|
||||
} else {
|
||||
// Ensure full package name used
|
||||
toSelectors.delete(packageName);
|
||||
// DO NOT add back to selectors in this loop
|
||||
additionalFullPackageNames.push(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const fullPackageName of additionalFullPackageNames) {
|
||||
toSelectors.add(fullPackageName);
|
||||
}
|
||||
additionalFullPackageNames = [];
|
||||
for (const selectorArg of fromSelectors) {
|
||||
if (selectorArg.indexOf(':') < 0) {
|
||||
const packageName: string = selectorArg;
|
||||
const result: string | undefined = this._findProjectByShorthandName(packageName);
|
||||
if (!result) {
|
||||
unfoundedPackages.push(packageName);
|
||||
} else {
|
||||
// Ensure full package name used
|
||||
fromSelectors.delete(packageName);
|
||||
// DO NOT add back to selectors in this loop
|
||||
additionalFullPackageNames.push(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const fullPackageName of additionalFullPackageNames) {
|
||||
fromSelectors.add(fullPackageName);
|
||||
}
|
||||
additionalFullPackageNames = [];
|
||||
|
||||
if (unfoundedPackages.length > 0) {
|
||||
throw new Error(`These packages: ${unfoundedPackages.join(', ')} does not exist in rush.json`);
|
||||
|
@ -175,11 +206,13 @@ export class GitSparseCheckoutService {
|
|||
|
||||
if (toSelectors.size !== 0 || fromSelectors.size !== 0) {
|
||||
const stopwatch: Stopwatch = Stopwatch.start();
|
||||
targetFolders = this._getTargetFoldersByRushList({ toSelectors, fromSelectors });
|
||||
terminal.writeVerboseLine(`Run rush list command. (${stopwatch.toString()})`);
|
||||
targetFolders = this._selectionParameterService.getSelectedFolders({ toSelectors, fromSelectors });
|
||||
terminal.writeVerboseLine(`Get selected folders. (${stopwatch.toString()})`);
|
||||
stopwatch.stop();
|
||||
} else {
|
||||
terminal.writeDebugLine('Skip rush list regarding the absence of from selectors and to selectors');
|
||||
terminal.writeDebugLine(
|
||||
'Skip getting selected folders regarding the absence of from selectors and to selectors'
|
||||
);
|
||||
}
|
||||
|
||||
// include rule
|
||||
|
@ -393,61 +426,4 @@ export class GitSparseCheckoutService {
|
|||
}
|
||||
return packageName;
|
||||
}
|
||||
|
||||
private _getTargetFoldersByRushList({
|
||||
toSelectors,
|
||||
fromSelectors
|
||||
}: {
|
||||
toSelectors: Iterable<string>;
|
||||
fromSelectors: Iterable<string>;
|
||||
}): string[] {
|
||||
const { terminal } = this._terminalService;
|
||||
|
||||
const args: string[] = ['list', '--json'];
|
||||
|
||||
for (const toSelector of toSelectors) {
|
||||
args.push('--to');
|
||||
args.push(toSelector);
|
||||
}
|
||||
for (const fromSelector of fromSelectors) {
|
||||
args.push('--from');
|
||||
args.push(fromSelector);
|
||||
}
|
||||
|
||||
terminal.writeVerboseLine(`Run command: rush ${args.join(' ')}`);
|
||||
|
||||
const result: child_process.SpawnSyncReturns<string> = Executable.spawnSync('rush', args, {
|
||||
stdio: ['pipe', 'pipe', 'pipe']
|
||||
});
|
||||
|
||||
if (result.status !== 0) {
|
||||
throw new Error(`Failed to evaluate the Sparo profile's project selectors:\nstdout: ${result.stdout}\nstderr: ${result.stderr}`);
|
||||
}
|
||||
|
||||
const processedResult: string = this._processListResult(result.stdout.toString());
|
||||
|
||||
terminal.writeVerboseLine(processedResult);
|
||||
|
||||
const { projects: targetDeps } = JSON.parse(processedResult) as {
|
||||
projects: { path: string }[];
|
||||
};
|
||||
|
||||
return targetDeps.map((targetDep) => targetDep.path);
|
||||
}
|
||||
|
||||
private _processListResult(input: string): string {
|
||||
const stringList: string[] = input.split('\n');
|
||||
let endOfInstallScript: number = -1;
|
||||
|
||||
for (let i: number = 0; i < stringList.length; ++i) {
|
||||
if (stringList[i][0] === '{') {
|
||||
endOfInstallScript = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const jsonStringList: string[] = stringList.slice(endOfInstallScript);
|
||||
|
||||
return jsonStringList.join('\n');
|
||||
}
|
||||
}
|
||||
|
|
200
apps/sparo-lib/src/services/SelectionParameterService.ts
Normal file
200
apps/sparo-lib/src/services/SelectionParameterService.ts
Normal file
|
@ -0,0 +1,200 @@
|
|||
import * as child_process from 'child_process';
|
||||
import { inject } from 'inversify';
|
||||
import { Service } from '../decorator';
|
||||
import { TerminalService } from './TerminalService';
|
||||
import { Executable, JsonFile, Sort } from '@rushstack/node-core-library';
|
||||
import { GitService } from './GitService';
|
||||
import { RushProjectSlim } from '../logic/RushProjectSlim';
|
||||
|
||||
@Service()
|
||||
export class SelectionParameterService {
|
||||
@inject(GitService) private _gitService!: GitService;
|
||||
@inject(TerminalService) private _terminalService!: TerminalService;
|
||||
|
||||
public getSelectedFolders({
|
||||
toSelectors,
|
||||
fromSelectors
|
||||
}: {
|
||||
toSelectors: Iterable<string>;
|
||||
fromSelectors: Iterable<string>;
|
||||
}): string[] {
|
||||
let hasProtocolSelection: boolean = false;
|
||||
if (!hasProtocolSelection) {
|
||||
for (const toSelector of toSelectors) {
|
||||
if (toSelector.indexOf(':') >= 0) {
|
||||
hasProtocolSelection = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!hasProtocolSelection) {
|
||||
for (const fromSelector of fromSelectors) {
|
||||
if (fromSelector.indexOf(':') >= 0) {
|
||||
hasProtocolSelection = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasProtocolSelection) {
|
||||
this._terminalService.terminal.writeDebugLine(`Opt in strategy without running rush list`);
|
||||
// Selectors passed down here are ensured to contain full package names only
|
||||
return this._getSelectedFoldersByNamedProjectsOnly({
|
||||
toSelectors,
|
||||
fromSelectors
|
||||
});
|
||||
} else {
|
||||
return this._getSelectedFoldersByRushList({
|
||||
toSelectors,
|
||||
fromSelectors
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function provides a quick way to get selected folders by
|
||||
* avoiding call "rush list" command
|
||||
*/
|
||||
private _getSelectedFoldersByNamedProjectsOnly({
|
||||
toSelectors,
|
||||
fromSelectors
|
||||
}: {
|
||||
toSelectors: Iterable<string>;
|
||||
fromSelectors: Iterable<string>;
|
||||
}): string[] {
|
||||
let rushJson: { projects?: { packageName: string; projectFolder: string }[] } = {};
|
||||
const root: string = this._gitService.getRepoInfo().root;
|
||||
try {
|
||||
rushJson = JsonFile.load(`${root}/rush.json`);
|
||||
} catch (e) {
|
||||
// no-catch
|
||||
}
|
||||
|
||||
const packageNameToRushProjectSlim: Map<string, RushProjectSlim> = new Map<string, RushProjectSlim>();
|
||||
if (Array.isArray(rushJson.projects)) {
|
||||
const { projects } = rushJson;
|
||||
for (const project of projects) {
|
||||
const rushProjectSlim: RushProjectSlim = new RushProjectSlim(
|
||||
project,
|
||||
root,
|
||||
packageNameToRushProjectSlim
|
||||
);
|
||||
packageNameToRushProjectSlim.set(project.packageName, rushProjectSlim);
|
||||
}
|
||||
}
|
||||
|
||||
const selectedProjects: Set<RushProjectSlim> = new Set<RushProjectSlim>();
|
||||
const evalToSelectorForProject = (
|
||||
rushProjectSlim: RushProjectSlim,
|
||||
visited: Set<RushProjectSlim> = new Set<RushProjectSlim>()
|
||||
): void => {
|
||||
if (visited.has(rushProjectSlim)) {
|
||||
return;
|
||||
}
|
||||
visited.add(rushProjectSlim);
|
||||
selectedProjects.add(rushProjectSlim);
|
||||
for (const dependencyProject of rushProjectSlim.dependencyProjects) {
|
||||
evalToSelectorForProject(dependencyProject, visited);
|
||||
}
|
||||
};
|
||||
for (const toSelector of toSelectors) {
|
||||
const rushProjectSlim: RushProjectSlim | undefined = packageNameToRushProjectSlim.get(toSelector);
|
||||
if (!rushProjectSlim) {
|
||||
throw new Error(`Can not found project definition for "${toSelector}"`);
|
||||
}
|
||||
evalToSelectorForProject(rushProjectSlim);
|
||||
}
|
||||
const evalFromSelectorForProject = (
|
||||
rushProjectSlim: RushProjectSlim,
|
||||
visited: Set<RushProjectSlim> = new Set<RushProjectSlim>()
|
||||
): void => {
|
||||
if (visited.has(rushProjectSlim)) {
|
||||
return;
|
||||
}
|
||||
visited.add(rushProjectSlim);
|
||||
selectedProjects.add(rushProjectSlim);
|
||||
for (const dependencyProject of rushProjectSlim.dependencyProjects) {
|
||||
evalToSelectorForProject(dependencyProject);
|
||||
}
|
||||
for (const consumingProject of rushProjectSlim.consumingProjects) {
|
||||
evalFromSelectorForProject(consumingProject, visited);
|
||||
}
|
||||
};
|
||||
for (const fromSelector of fromSelectors) {
|
||||
const rushProjectSlim: RushProjectSlim | undefined = packageNameToRushProjectSlim.get(fromSelector);
|
||||
if (!rushProjectSlim) {
|
||||
throw new Error(`Can not found project definition for "${fromSelector}"`);
|
||||
}
|
||||
evalFromSelectorForProject(rushProjectSlim);
|
||||
}
|
||||
|
||||
const { terminal } = this._terminalService;
|
||||
|
||||
terminal.writeDebugLine(`Selected ${selectedProjects.size} projects:`);
|
||||
Sort.sortSetBy(selectedProjects, (x) => x.packageName);
|
||||
for (const project of selectedProjects) {
|
||||
terminal.writeDebugLine(project.packageName);
|
||||
}
|
||||
|
||||
return Array.from(selectedProjects).map((x) => x.relativeProjectFolder);
|
||||
}
|
||||
|
||||
private _getSelectedFoldersByRushList({
|
||||
toSelectors,
|
||||
fromSelectors
|
||||
}: {
|
||||
toSelectors: Iterable<string>;
|
||||
fromSelectors: Iterable<string>;
|
||||
}): string[] {
|
||||
const { terminal } = this._terminalService;
|
||||
|
||||
const args: string[] = ['list', '--json'];
|
||||
|
||||
for (const toSelector of toSelectors) {
|
||||
args.push('--to');
|
||||
args.push(toSelector);
|
||||
}
|
||||
for (const fromSelector of fromSelectors) {
|
||||
args.push('--from');
|
||||
args.push(fromSelector);
|
||||
}
|
||||
|
||||
terminal.writeVerboseLine(`Run command: rush ${args.join(' ')}`);
|
||||
|
||||
const result: child_process.SpawnSyncReturns<string> = Executable.spawnSync('rush', args, {
|
||||
stdio: ['pipe', 'pipe', 'pipe']
|
||||
});
|
||||
|
||||
if (result.status !== 0) {
|
||||
throw new Error(
|
||||
`Failed to evaluate the Sparo profile's project selectors:\nstdout: ${result.stdout}\nstderr: ${result.stderr}`
|
||||
);
|
||||
}
|
||||
|
||||
const processedResult: string = this._processListResult(result.stdout.toString());
|
||||
|
||||
terminal.writeVerboseLine(processedResult);
|
||||
|
||||
const { projects: targetDeps } = JSON.parse(processedResult) as {
|
||||
projects: { path: string }[];
|
||||
};
|
||||
|
||||
return targetDeps.map((targetDep) => targetDep.path);
|
||||
}
|
||||
|
||||
private _processListResult(input: string): string {
|
||||
const stringList: string[] = input.split('\n');
|
||||
let endOfInstallScript: number = -1;
|
||||
|
||||
for (let i: number = 0; i < stringList.length; ++i) {
|
||||
if (stringList[i][0] === '{') {
|
||||
endOfInstallScript = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const jsonStringList: string[] = stringList.slice(endOfInstallScript);
|
||||
|
||||
return jsonStringList.join('\n');
|
||||
}
|
||||
}
|
|
@ -55,6 +55,9 @@ importers:
|
|||
inversify:
|
||||
specifier: ~6.0.2
|
||||
version: 6.0.2
|
||||
npm-package-arg:
|
||||
specifier: ~6.1.0
|
||||
version: 6.1.0
|
||||
reflect-metadata:
|
||||
specifier: ~0.2.1
|
||||
version: 0.2.1
|
||||
|
@ -80,6 +83,9 @@ importers:
|
|||
'@types/node':
|
||||
specifier: 20.11.16
|
||||
version: 20.11.16
|
||||
'@types/npm-package-arg':
|
||||
specifier: 6.1.0
|
||||
version: 6.1.0
|
||||
'@types/semver':
|
||||
specifier: 7.5.7
|
||||
version: 7.5.7
|
||||
|
@ -3956,6 +3962,10 @@ packages:
|
|||
dependencies:
|
||||
undici-types: 5.26.5
|
||||
|
||||
/@types/npm-package-arg@6.1.0:
|
||||
resolution: {integrity: sha512-vbt5fb0y1svMhu++1lwtKmZL76d0uPChFlw7kEzyUmTwfmpHRcFb8i0R8ElT69q/L+QLgK2hgECivIAvaEDwag==}
|
||||
dev: true
|
||||
|
||||
/@types/parse-json@4.0.2:
|
||||
resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==}
|
||||
dev: false
|
||||
|
@ -4890,6 +4900,10 @@ packages:
|
|||
/buffer-from@1.1.2:
|
||||
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
|
||||
|
||||
/builtins@1.0.3:
|
||||
resolution: {integrity: sha512-uYBjakWipfaO/bXI7E8rq6kpwHRZK5cNYrUv2OzZSI/FvmdMyXJ2tG9dKcjEC5YHmHpUAwsargWIZNWdxb/bnQ==}
|
||||
dev: false
|
||||
|
||||
/bytes@3.0.0:
|
||||
resolution: {integrity: sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
@ -7286,6 +7300,10 @@ packages:
|
|||
react-is: 16.13.1
|
||||
dev: false
|
||||
|
||||
/hosted-git-info@2.8.9:
|
||||
resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==}
|
||||
dev: false
|
||||
|
||||
/hpack.js@2.1.6:
|
||||
resolution: {integrity: sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==}
|
||||
dependencies:
|
||||
|
@ -9506,6 +9524,15 @@ packages:
|
|||
resolution: {integrity: sha512-5PDmaAsVfnWUgTUbJ3ERwn7u79Z0dYxN9ErxCpVJJqe2RK0PJ3z+iFUxuqjwtlDDegXvtWoxD/3Fzxox7tFGWA==}
|
||||
dev: false
|
||||
|
||||
/npm-package-arg@6.1.0:
|
||||
resolution: {integrity: sha512-zYbhP2k9DbJhA0Z3HKUePUgdB1x7MfIfKssC+WLPFMKTBZKpZh5m13PgexJjCq6KW7j17r0jHWcCpxEqnnncSA==}
|
||||
dependencies:
|
||||
hosted-git-info: 2.8.9
|
||||
osenv: 0.1.5
|
||||
semver: 5.7.2
|
||||
validate-npm-package-name: 3.0.0
|
||||
dev: false
|
||||
|
||||
/npm-run-path@4.0.1:
|
||||
resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==}
|
||||
engines: {node: '>=8'}
|
||||
|
@ -9629,6 +9656,24 @@ packages:
|
|||
type-check: 0.4.0
|
||||
dev: true
|
||||
|
||||
/os-homedir@1.0.2:
|
||||
resolution: {integrity: sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: false
|
||||
|
||||
/os-tmpdir@1.0.2:
|
||||
resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: false
|
||||
|
||||
/osenv@0.1.5:
|
||||
resolution: {integrity: sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==}
|
||||
deprecated: This package is no longer supported.
|
||||
dependencies:
|
||||
os-homedir: 1.0.2
|
||||
os-tmpdir: 1.0.2
|
||||
dev: false
|
||||
|
||||
/p-cancelable@1.1.0:
|
||||
resolution: {integrity: sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==}
|
||||
engines: {node: '>=6'}
|
||||
|
@ -11096,6 +11141,11 @@ packages:
|
|||
semver: 7.6.0
|
||||
dev: false
|
||||
|
||||
/semver@5.7.2:
|
||||
resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==}
|
||||
hasBin: true
|
||||
dev: false
|
||||
|
||||
/semver@6.3.1:
|
||||
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
|
||||
hasBin: true
|
||||
|
@ -12079,6 +12129,12 @@ packages:
|
|||
convert-source-map: 2.0.0
|
||||
dev: true
|
||||
|
||||
/validate-npm-package-name@3.0.0:
|
||||
resolution: {integrity: sha512-M6w37eVCMMouJ9V/sdPGnC5H4uDr73/+xdq0FBLO3TFFX1+7wiUY6Es328NN+y43tmY+doUdN9g9J21vqB7iLw==}
|
||||
dependencies:
|
||||
builtins: 1.0.3
|
||||
dev: false
|
||||
|
||||
/validator@13.11.0:
|
||||
resolution: {integrity: sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==}
|
||||
engines: {node: '>= 0.10'}
|
||||
|
@ -12570,6 +12626,7 @@ time:
|
|||
/@rushstack/terminal@0.8.1: '2024-02-20T21:48:45.317Z'
|
||||
/@types/heft-jest@1.0.6: '2023-11-07T07:22:05.861Z'
|
||||
/@types/node@20.11.16: '2024-02-01T17:35:24.929Z'
|
||||
/@types/npm-package-arg@6.1.0: '2018-11-01T17:41:31.538Z'
|
||||
/@types/react-dom@18.2.19: '2024-02-07T17:06:58.412Z'
|
||||
/@types/react@18.2.59: '2024-02-26T19:07:45.111Z'
|
||||
/@types/semver@7.5.7: '2024-02-11T14:35:16.597Z'
|
||||
|
@ -12583,6 +12640,7 @@ time:
|
|||
/jest-diff@29.7.0: '2023-09-12T06:43:43.883Z'
|
||||
/lunr-languages@1.14.0: '2023-10-09T20:33:09.608Z'
|
||||
/lunr@2.3.9: '2020-08-19T20:30:07.948Z'
|
||||
/npm-package-arg@6.1.0: '2018-04-10T17:45:45.725Z'
|
||||
/prism-react-renderer@2.3.1: '2023-12-18T14:23:38.265Z'
|
||||
/prismjs@1.29.0: '2022-08-23T10:42:14.395Z'
|
||||
/react-dom@18.2.0: '2022-06-14T19:46:48.370Z'
|
||||
|
|
Loading…
Reference in a new issue