From 709deb3123bf48972a9f445492b44d22277dad40 Mon Sep 17 00:00:00 2001 From: Pedro Gomes <37028775+pedro-gomes-92@users.noreply.github.com> Date: Sat, 2 Nov 2024 23:31:30 +0800 Subject: [PATCH] feat: support project sync --- .../src/functions/syncDependencies.ts | 121 ----------------- .../src/functions/syncProjectDependencies.ts | 125 +++++++++++++++++- .../src/functions/updateSubspace.ts | 11 +- .../src/migrateProject.ts | 19 +-- .../src/prompts/dependency.ts | 33 ++++- .../src/prompts/project.ts | 21 ++- .../src/syncVersions.ts | 46 ++++++- .../src/utilities/dependency.ts | 49 +++++++ .../src/utilities/project.ts | 24 ++++ .../src/utilities/repository.ts | 9 ++ .../src/utilities/subspace.ts | 37 +++++- 11 files changed, 337 insertions(+), 158 deletions(-) delete mode 100644 rush-plugins/rush-migrate-subspace-plugin/src/functions/syncDependencies.ts create mode 100644 rush-plugins/rush-migrate-subspace-plugin/src/utilities/dependency.ts diff --git a/rush-plugins/rush-migrate-subspace-plugin/src/functions/syncDependencies.ts b/rush-plugins/rush-migrate-subspace-plugin/src/functions/syncDependencies.ts deleted file mode 100644 index bd93655..0000000 --- a/rush-plugins/rush-migrate-subspace-plugin/src/functions/syncDependencies.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { VersionMismatchFinderEntity } from '@rushstack/rush-sdk/lib/logic/versionMismatch/VersionMismatchFinderEntity'; -import { RushConstants } from '@rushstack/rush-sdk'; -import { JsonFile, JsonObject } from '@rushstack/node-core-library'; -import { chooseDependencyPrompt, enterVersionPrompt, requestVersionTypePrompt } from '../prompts/dependency'; -import { Colorize } from '@rushstack/terminal'; -import Console from '../providers/console'; -import { updateProjectDependency } from './updateProjectDependency'; -import { getRushSubspaceConfigurationFolderPath } from '../utilities/subspace'; -import { updateSubspaceAlternativeVersions } from './updateSubspace'; - -const syncDependencyVersion = async ( - dependencyToUpdate: string, - mismatches: ReadonlyMap, - subspaceName: string, - projectToUpdate?: string -): Promise => { - const mismatchedVersions: string[] = Array.from(mismatches.keys()); - const currentVersion: string | undefined = mismatchedVersions.find((version) => { - return mismatches.get(version)?.find(({ friendlyName }) => friendlyName === projectToUpdate); - }); - - if (!currentVersion) { - Console.error(`No version found for ${Colorize.bold(dependencyToUpdate)}! Skipping...`); - return; - } - - const subspaceCommonVersionsPath: string = `${getRushSubspaceConfigurationFolderPath(subspaceName)}/${ - RushConstants.commonVersionsFilename - }`; - const subspaceCommonVersionsJson: JsonObject = JsonFile.load(subspaceCommonVersionsPath); - - let selectedVersion: string | undefined; - const commonVersions: string[] = - subspaceCommonVersionsJson.allowedAlternativeVersions[dependencyToUpdate] || []; - - const availableVersions: { name: string; value: string }[] = []; - - for (const mismatchedVersion of mismatchedVersions) { - const mismatchEntities: readonly VersionMismatchFinderEntity[] = mismatches.get(mismatchedVersion) || []; - availableVersions.push({ - name: `${mismatchedVersion} - used by ${mismatchEntities - ?.map(({ friendlyName }) => friendlyName) - .join(', ')}}`, - value: mismatchedVersion - }); - } - - for (const commonVersion of commonVersions) { - if (!availableVersions.find(({ value }) => value === commonVersion)) { - availableVersions.push({ - name: `${commonVersion} - in allowedAlternativeVersions`, - value: commonVersion - }); - } - } - - const versionToSync: string = await requestVersionTypePrompt(availableVersions); - if (versionToSync === 'skip') { - return; - } else if (versionToSync === 'manual') { - const newVersion: string = await enterVersionPrompt(dependencyToUpdate); - selectedVersion = newVersion.trim(); - } else if (versionToSync === 'alternative') { - subspaceCommonVersionsJson.allowedAlternativeVersions[dependencyToUpdate] = - updateSubspaceAlternativeVersions( - subspaceCommonVersionsJson.allowedAlternativeVersions[dependencyToUpdate], - [currentVersion] - ); - - JsonFile.save(subspaceCommonVersionsJson, subspaceCommonVersionsPath, { - updateExistingFile: true - }); - - return; - } else { - selectedVersion = versionToSync; - } - - const allProjects: string[] = []; - if (projectToUpdate) { - allProjects.push(projectToUpdate); - } else { - // Only update package.json if we don't use alternative - for (const version of mismatchedVersions) { - if (version !== selectedVersion) { - const packagesWithVersion: readonly VersionMismatchFinderEntity[] = mismatches.get(version) || []; - allProjects.push(...packagesWithVersion.map(({ friendlyName }) => friendlyName)); - } - } - } - - for (const projectToUpdate of allProjects) { - await updateProjectDependency(projectToUpdate, dependencyToUpdate, selectedVersion); - } - - return; -}; - -export const syncDependencies = async ( - mismatches: ReadonlyMap>, - subspaceName: string, - projectName?: string -): Promise => { - const missingMismatchedDependencies: string[] = Array.from(mismatches.keys()); - do { - const selectedDependency: string = await chooseDependencyPrompt(missingMismatchedDependencies); - const selectedDependencyMismatches: ReadonlyMap = - mismatches.get(selectedDependency) as ReadonlyMap; - - Console.warn( - `There are ${Colorize.bold( - `${selectedDependencyMismatches.size}` - )} mismatches for the dependency ${Colorize.bold(selectedDependency)}...` - ); - - await syncDependencyVersion(selectedDependency, selectedDependencyMismatches, subspaceName, projectName); - missingMismatchedDependencies.splice(missingMismatchedDependencies.indexOf(selectedDependency), 1); - } while (missingMismatchedDependencies.length > 0); - - return missingMismatchedDependencies.length === 0; -}; diff --git a/rush-plugins/rush-migrate-subspace-plugin/src/functions/syncProjectDependencies.ts b/rush-plugins/rush-migrate-subspace-plugin/src/functions/syncProjectDependencies.ts index 5d1a07a..f8f37a9 100644 --- a/rush-plugins/rush-migrate-subspace-plugin/src/functions/syncProjectDependencies.ts +++ b/rush-plugins/rush-migrate-subspace-plugin/src/functions/syncProjectDependencies.ts @@ -1,11 +1,110 @@ -import { getProjectMismatches, queryProject } from '../utilities/project'; +import { getProjectDependencies, getProjectMismatches, queryProject } from '../utilities/project'; import { VersionMismatchFinderEntity } from '@rushstack/rush-sdk/lib/logic/versionMismatch/VersionMismatchFinderEntity'; import Console from '../providers/console'; import { Colorize } from '@rushstack/terminal'; -import { syncDependencies } from './syncDependencies'; import { IRushConfigurationProjectJson } from '@rushstack/rush-sdk/lib/api/RushConfigurationProject'; +import { chooseDependencyPrompt, chooseVersionPrompt, enterVersionPrompt } from '../prompts/dependency'; +import { updateProjectDependency } from './updateProjectDependency'; +import { getRecommendedVersion } from '../utilities/dependency'; +import { IPackageJsonDependencyTable, JsonFile, JsonObject } from '@rushstack/node-core-library'; +import { getRushSubspaceConfigurationFolderPath, getSubspaceDependencies } from '../utilities/subspace'; +import { updateSubspaceAlternativeVersions } from './updateSubspace'; +import { RushConstants } from '@rushstack/rush-sdk'; +import { confirmProjectSyncVersions } from '../prompts/project'; -export const syncProjectDependencies = async (projectName: string): Promise => { +const addVersionToCommonVersionConfiguration = ( + subspaceName: string, + dependencyName: string, + selectedVersion: string +): void => { + const subspaceCommonVersionsPath: string = `${getRushSubspaceConfigurationFolderPath(subspaceName)}/${ + RushConstants.commonVersionsFilename + }`; + const subspaceCommonVersionsJson: JsonObject = JsonFile.load(subspaceCommonVersionsPath); + + Console.debug( + `Adding ${Colorize.bold(selectedVersion)} to allowedAlternativeVersions of ${Colorize.bold( + subspaceCommonVersionsPath + )}` + ); + + subspaceCommonVersionsJson.allowedAlternativeVersions[dependencyName] = updateSubspaceAlternativeVersions( + subspaceCommonVersionsJson.allowedAlternativeVersions[dependencyName], + [selectedVersion] + ); + + JsonFile.save(subspaceCommonVersionsJson, subspaceCommonVersionsPath, { + updateExistingFile: true + }); +}; + +const syncDependencyVersion = async (dependencyToUpdate: string, projectToUpdate: string): Promise => { + const project: IRushConfigurationProjectJson | undefined = queryProject(projectToUpdate); + if (!project || !project.subspaceName) { + return; + } + + const subspaceDependencies: Map> = getSubspaceDependencies( + project.subspaceName + ); + const availableVersionsMap: Map = subspaceDependencies.get(dependencyToUpdate) as Map< + string, + string[] + >; + const availableVersions: string[] = Array.from(availableVersionsMap.keys()); + const projectDependencies: IPackageJsonDependencyTable | undefined = + getProjectDependencies(projectToUpdate); + const currentVersion: string | undefined = projectDependencies?.[dependencyToUpdate]; + if (!currentVersion) { + Console.error( + `Dependency ${Colorize.bold(dependencyToUpdate)} doesn't exist in the project ${Colorize.bold( + projectToUpdate + )}! Skipping...` + ); + return; + } + + const recommendedVersion: string | undefined = currentVersion + ? getRecommendedVersion(currentVersion, availableVersions) + : undefined; + + const versionToSync: string = await chooseVersionPrompt( + availableVersionsMap, + currentVersion, + recommendedVersion + ); + + if (versionToSync === 'skip') { + return; + } else if (versionToSync === 'manual') { + const newVersion: string = (await enterVersionPrompt(dependencyToUpdate)).trim(); + addVersionToCommonVersionConfiguration(project.subspaceName, dependencyToUpdate, newVersion); + await updateProjectDependency(projectToUpdate, dependencyToUpdate, newVersion); + } else if (versionToSync === 'alternative') { + addVersionToCommonVersionConfiguration(project.subspaceName, dependencyToUpdate, currentVersion); + } else { + await updateProjectDependency(projectToUpdate, dependencyToUpdate, versionToSync); + } +}; + +export const syncProjectDependencies = async ( + dependencies: string[], + projectName: string +): Promise => { + const dependenciesToSync: string[] = [...dependencies]; + do { + const selectedDependency: string = await chooseDependencyPrompt(dependenciesToSync); + await syncDependencyVersion(selectedDependency, projectName); + dependenciesToSync.splice(dependenciesToSync.indexOf(selectedDependency), 1); + } while (dependenciesToSync.length > 0); + + return dependenciesToSync.length === 0; +}; + +export const syncProjectMismatchedDependencies = async ( + projectName: string, + forceSync: boolean = false +): Promise => { Console.title(`🔄 Syncing version mismatches for project ${Colorize.bold(projectName)}...`); const projectMismatches: ReadonlyMap< @@ -13,22 +112,36 @@ export const syncProjectDependencies = async (projectName: string): Promise > = getProjectMismatches(projectName); - if (projectMismatches.size === 0) { + const mismatchedDependencies: string[] = Array.from(projectMismatches.keys()); + + if (mismatchedDependencies.length === 0) { Console.success(`No mismatches found in the project ${Colorize.bold(projectName)}!`); return true; } Console.warn( `There are ${Colorize.bold( - `${projectMismatches.size}` + `${mismatchedDependencies.length}` )} mismatched dependencies for the project ${Colorize.bold(projectName)}...` ); + const confirmSync: boolean = forceSync || (await confirmProjectSyncVersions()); + if (!confirmSync) { + return true; + } + const project: IRushConfigurationProjectJson | undefined = queryProject(projectName); if (!project || !project.subspaceName) { Console.error(`Project ${Colorize.bold(projectName)} is not part of a subspace!`); return false; } - return await syncDependencies(projectMismatches, project.subspaceName, project.packageName); + if (await syncProjectDependencies(mismatchedDependencies, project.packageName)) { + Console.success( + `All mismatched dependencies for the subspace ${Colorize.bold(projectName)} have been synced!` + ); + return true; + } + + return false; }; diff --git a/rush-plugins/rush-migrate-subspace-plugin/src/functions/updateSubspace.ts b/rush-plugins/rush-migrate-subspace-plugin/src/functions/updateSubspace.ts index 5ee19f6..4229fe0 100644 --- a/rush-plugins/rush-migrate-subspace-plugin/src/functions/updateSubspace.ts +++ b/rush-plugins/rush-migrate-subspace-plugin/src/functions/updateSubspace.ts @@ -3,7 +3,8 @@ import Console from '../providers/console'; import { Colorize } from '@rushstack/terminal'; import { RushConstants } from '@rushstack/rush-sdk'; import { RushNameConstants } from '../constants/paths'; -import { satisfies } from 'semver'; +import { intersects } from 'semver'; +import { sortVersions } from '../utilities/dependency'; export const updateSubspaceAlternativeVersions = ( availableVersions: string[], @@ -11,9 +12,11 @@ export const updateSubspaceAlternativeVersions = ( ): string[] => { const newVersions: string[] = [...availableVersions, ...versionsToUpdate]; - // Remove duplicates - const validVersions: string[] = newVersions.filter((version, index, currValidVersions) => { - return currValidVersions.some((validVersion) => satisfies(version, validVersion)); + // Remove duplicates & unnecessary versions + const validVersions: string[] = sortVersions(newVersions).filter((version, index, currValidVersions) => { + const compareVersions: string[] = [...currValidVersions]; + compareVersions.splice(index, 1); + return !compareVersions.some((validVersion) => intersects(version, validVersion)); }); return validVersions; diff --git a/rush-plugins/rush-migrate-subspace-plugin/src/migrateProject.ts b/rush-plugins/rush-migrate-subspace-plugin/src/migrateProject.ts index ecf43b1..b594b31 100644 --- a/rush-plugins/rush-migrate-subspace-plugin/src/migrateProject.ts +++ b/rush-plugins/rush-migrate-subspace-plugin/src/migrateProject.ts @@ -14,7 +14,7 @@ import { querySubspaces } from './utilities/repository'; import { RushConstants } from '@rushstack/rush-sdk'; -import { syncProjectDependencies } from './functions/syncProjectDependencies'; +import { syncProjectMismatchedDependencies } from './functions/syncProjectDependencies'; export const migrateProject = async (): Promise => { Console.debug('Executing project migration command...'); @@ -87,14 +87,17 @@ export const migrateProject = async (): Promise => { return; } - const sourceProjectToMigrate: IRushConfigurationProjectJson = await chooseProjectPrompt( - sourceAvailableProjects + const sourceProjectNameToMigrate: string = await chooseProjectPrompt( + sourceAvailableProjects.map(({ packageName }) => packageName) ); - Console.title( - `🏃 Migrating project ${sourceProjectToMigrate.packageName} to subspace ${Colorize.bold( - targetSubspace - )}...` + const sourceProjectToMigrate: IRushConfigurationProjectJson | undefined = sourceAvailableProjects.find( + ({ packageName }) => packageName === sourceProjectNameToMigrate ); + if (!sourceProjectToMigrate) { + return; + } + + Console.title(`🏃 Migrating project ${sourceProjectToMigrate} to subspace ${targetSubspace}...`); if (sourceProjectToMigrate.subspaceName) { const sourceSubspaceConfigurationFolderPath: string = getRushSubspaceConfigurationFolderPath( @@ -110,7 +113,7 @@ export const migrateProject = async (): Promise => { } await addProjectToSubspace(sourceProjectToMigrate, targetSubspace, sourceMonorepoPath); - await syncProjectDependencies(sourceProjectToMigrate.packageName); + await syncProjectMismatchedDependencies(sourceProjectToMigrate.packageName); } while (await confirmNextProjectPrompt(targetSubspace)); Console.warn( diff --git a/rush-plugins/rush-migrate-subspace-plugin/src/prompts/dependency.ts b/rush-plugins/rush-migrate-subspace-plugin/src/prompts/dependency.ts index b206a53..066fcf6 100644 --- a/rush-plugins/rush-migrate-subspace-plugin/src/prompts/dependency.ts +++ b/rush-plugins/rush-migrate-subspace-plugin/src/prompts/dependency.ts @@ -1,18 +1,39 @@ import inquirer from 'inquirer'; -export const requestVersionTypePrompt = async ( - availableVersions: { name: string; value: string }[] +export const chooseVersionPrompt = async ( + availableVersionsMap: Map, + currentVersion: string, + recommendedVersion?: string ): Promise => { + const availableVersions: string[] = Array.from(availableVersionsMap.keys()); const { versionToSync } = await inquirer.prompt([ { type: 'list', name: 'versionToSync', - message: 'Which version would you like to use?', + message: `Which version would you like to use?`, + suffix: `(Current version: ${currentVersion})`, choices: [ - ...availableVersions, + ...(recommendedVersion + ? [ + { type: 'separator', line: '==== Recommended version:' }, + { name: recommendedVersion, value: recommendedVersion } + ] + : []), + { type: 'separator', line: '==== All versions:' }, + ...availableVersions.map((version) => { + const projects: string[] = availableVersionsMap.get(version) || []; + + return { + name: `${version} - used by ${ + projects.length > 4 ? `${projects.length} projects` : projects.join(',') + }`, + value: version + }; + }), + { type: 'separator', line: '==== Other options:' }, + { name: 'Add current to allowedAlternativeVersions', value: 'alternative' }, { name: 'Add manual version', value: 'manual' }, - { name: 'Skip this dependency', value: 'skip' }, - { name: 'Create exception (allowedAlternativeVersions)', value: 'alternative' } + { name: 'Skip this dependency', value: 'skip' } ] } ]); diff --git a/rush-plugins/rush-migrate-subspace-plugin/src/prompts/project.ts b/rush-plugins/rush-migrate-subspace-plugin/src/prompts/project.ts index ed2ef1b..d398cc6 100644 --- a/rush-plugins/rush-migrate-subspace-plugin/src/prompts/project.ts +++ b/rush-plugins/rush-migrate-subspace-plugin/src/prompts/project.ts @@ -32,19 +32,30 @@ export const enterNewProjectLocationPrompt = async ( return `${targetSubspaceFolderPath}/${projectFolderName}`; }; -export const chooseProjectPrompt = async ( - projects: IRushConfigurationProjectJson[] -): Promise => { +export const chooseProjectPrompt = async (projects: string[]): Promise => { const { projectName } = await inquirer.prompt([ { message: `Please select the project name (Type to filter).`, type: 'search-list', name: 'projectName', - choices: projects.map(({ packageName }) => ({ name: packageName, value: packageName })) + choices: projects.map((name) => ({ name, value: name })) } ]); - return projects.find(({ packageName }) => packageName === projectName) as IRushConfigurationProjectJson; + return projectName; +}; + +export const confirmProjectSyncVersions = async (): Promise => { + const { confirmSync } = await inquirer.prompt([ + { + message: `Do you want to start the mismatch fix?`, + type: 'confirm', + name: 'confirmSync', + default: true + } + ]); + + return confirmSync; }; export const confirmNextProjectPrompt = async (subspaceName: string): Promise => { diff --git a/rush-plugins/rush-migrate-subspace-plugin/src/syncVersions.ts b/rush-plugins/rush-migrate-subspace-plugin/src/syncVersions.ts index cc86f1c..bd5ab93 100644 --- a/rush-plugins/rush-migrate-subspace-plugin/src/syncVersions.ts +++ b/rush-plugins/rush-migrate-subspace-plugin/src/syncVersions.ts @@ -2,10 +2,31 @@ import { chooseSubspacePrompt } from './prompts/subspace'; import { VersionMismatchFinderEntity } from '@rushstack/rush-sdk/lib/logic/versionMismatch/VersionMismatchFinderEntity'; import Console from './providers/console'; import { Colorize } from '@rushstack/terminal'; -import { querySubspaces } from './utilities/repository'; +import { queryProjectsFromSubspace, querySubspaces } from './utilities/repository'; import { getRootPath } from './utilities/path'; -import { syncDependencies } from './functions/syncDependencies'; import { getSubspaceMismatches } from './utilities/subspace'; +import { IRushConfigurationProjectJson } from '@rushstack/rush-sdk/lib/api/RushConfigurationProject'; +import { chooseProjectPrompt } from './prompts/project'; +import { syncProjectMismatchedDependencies } from './functions/syncProjectDependencies'; + +const syncSubspaceMismatchedDependencies = async ( + subspaceName: string, + mismatchedProjects: string[] +): Promise => { + const projects: IRushConfigurationProjectJson[] = queryProjectsFromSubspace(subspaceName); + + do { + const selectedProjectName: string = await chooseProjectPrompt(mismatchedProjects); + const selectedProjectIndex: number = projects.findIndex( + ({ packageName }) => packageName === selectedProjectName + ); + + await syncProjectMismatchedDependencies(selectedProjectName, true); + projects.splice(selectedProjectIndex, 1); + } while (projects.length > 0); + + return projects.length === 0; +}; export const syncVersions = async (): Promise => { Console.debug('Executing project version synchronization command...'); @@ -29,8 +50,23 @@ export const syncVersions = async (): Promise => { return; } - Console.warn(`There are ${Colorize.bold(`${subspaceMismatches.size}`)} mismatched dependencies...`); - if (await syncDependencies(subspaceMismatches, selectedSubspaceName)) { - Console.success('Version sync complete! Please test and validate all affected packages.'); + const mismatchedProjects: string[] = []; + for (const [, versions] of subspaceMismatches) { + for (const [, entities] of versions) { + mismatchedProjects.push( + ...entities + .filter(({ friendlyName }) => !mismatchedProjects.includes(friendlyName)) + .map(({ friendlyName }) => friendlyName) + ); + } + } + + Console.warn(`There are ${Colorize.bold(`${mismatchedProjects.length}`)} mismatched projects...`); + if (await syncSubspaceMismatchedDependencies(selectedSubspaceName, mismatchedProjects)) { + Console.success( + `All mismatched projects for subspace ${Colorize.bold( + selectedSubspaceName + )} have been successfully synchronized!` + ); } }; diff --git a/rush-plugins/rush-migrate-subspace-plugin/src/utilities/dependency.ts b/rush-plugins/rush-migrate-subspace-plugin/src/utilities/dependency.ts new file mode 100644 index 0000000..87ff789 --- /dev/null +++ b/rush-plugins/rush-migrate-subspace-plugin/src/utilities/dependency.ts @@ -0,0 +1,49 @@ +import { compare, eq, intersects, minVersion, satisfies, subset, validRange } from 'semver'; + +export const sortVersions = (versions: string[]): string[] => { + return versions.sort((v1, v2) => { + if (v1 === v2) { + // e.g. 1.0.0 , 1.0.0 + return 0; + } + + if (v1 === 'workspace:*' || v2 === 'workspace:*') { + // e.g. workspace:* + return 1; + } + + const v1Range: string | undefined = validRange(v1) ? v1 : undefined; + const v2Range: string | undefined = validRange(v2) ? v2 : undefined; + const v1Fixed: string | undefined = v1Range ? minVersion(v1)?.format() : v1; + const v2Fixed: string | undefined = v2Range ? minVersion(v2)?.format() : v2; + + if (!v1Fixed || !v2Fixed) { + return -1; + } + + if (eq(v1Fixed, v2Fixed)) { + if (v1Range && v2Range) { + if (subset(v1Range, v2Range)) { + // e.g. ~1.0.0 , ^1.0.0 + return -1; + } else if (subset(v2Range, v1Range)) { + // e.g. ^1.0.0 , ~1.0.0 + return 1; + } + } else if (v1Range) { + // e.g. ~1.0.0 , 1.0.0 + return satisfies(v2Fixed, v1Range) ? -1 : 1; + } else if (v2Range) { + // e.g. 1.0.0 , ~1.0.0 + return satisfies(v1Fixed, v2Range) ? 1 : -1; + } + } + + // e.g. 1.0.0 , 1.0.1 + return compare(v1Fixed, v2Fixed); + }); +}; + +export const getRecommendedVersion = (targetVersion: string, versions: string[]): string => { + return sortVersions(versions).find((version) => intersects(targetVersion, version)) || versions[0]; +}; diff --git a/rush-plugins/rush-migrate-subspace-plugin/src/utilities/project.ts b/rush-plugins/rush-migrate-subspace-plugin/src/utilities/project.ts index ffe88cd..4767cef 100644 --- a/rush-plugins/rush-migrate-subspace-plugin/src/utilities/project.ts +++ b/rush-plugins/rush-migrate-subspace-plugin/src/utilities/project.ts @@ -5,6 +5,7 @@ import { loadRushConfiguration } from './repository'; import { getSubspaceMismatches } from './subspace'; import { VersionMismatchFinderEntity } from '@rushstack/rush-sdk/lib/logic/versionMismatch/VersionMismatchFinderEntity'; import { RushNameConstants } from '../constants/paths'; +import { IPackageJson, IPackageJsonDependencyTable, JsonFile } from '@rushstack/node-core-library'; export const getProjectPackageFilePath = ( projectFolder: string, @@ -13,6 +14,13 @@ export const getProjectPackageFilePath = ( return `${rootPath}/${projectFolder}/${RushNameConstants.PackageName}`; }; +export const loadProjectPackageJson = ( + projectFolder: string, + rootPath: string = getRootPath() +): IPackageJson => { + return JsonFile.load(getProjectPackageFilePath(projectFolder, rootPath)); +}; + export const queryProjects = (rootPath: string = getRootPath()): IRushConfigurationProjectJson[] => { const rushJson: IRushConfigurationJson = loadRushConfiguration(rootPath); return rushJson.projects; @@ -65,3 +73,19 @@ export const getProjectMismatches = ( return projectMismatches; }; + +export const getProjectDependencies = ( + projectName: string, + rootPath: string = getRootPath() +): IPackageJsonDependencyTable | undefined => { + const project: IRushConfigurationProjectJson | undefined = queryProject(projectName, rootPath); + if (!project || !project.subspaceName) { + return; + } + + const projectPackageJson: IPackageJson = loadProjectPackageJson(project.projectFolder, rootPath); + return { + ...projectPackageJson.dependencies, + ...projectPackageJson.devDependencies + }; +}; diff --git a/rush-plugins/rush-migrate-subspace-plugin/src/utilities/repository.ts b/rush-plugins/rush-migrate-subspace-plugin/src/utilities/repository.ts index 428222d..96049a3 100644 --- a/rush-plugins/rush-migrate-subspace-plugin/src/utilities/repository.ts +++ b/rush-plugins/rush-migrate-subspace-plugin/src/utilities/repository.ts @@ -4,6 +4,7 @@ import { IRushConfigurationJson } from '@rushstack/rush-sdk/lib/api/RushConfigur import { RushPathConstants } from '../constants/paths'; import { ISubspacesConfigurationJson } from '@rushstack/rush-sdk/lib/api/SubspacesConfiguration'; import { RushConstants } from '@rushstack/rush-sdk'; +import { IRushConfigurationProjectJson } from '@rushstack/rush-sdk/lib/api/RushConfigurationProject'; export const getRushConfigurationJsonPath = (rootPath: string = getRootPath()): string => `${rootPath}/${RushConstants.rushJsonFilename}`; @@ -25,6 +26,14 @@ export const querySubspaces = (rootPath: string = getRootPath()): string[] => { return subspaceJson.subspaceNames; }; +export const queryProjectsFromSubspace = ( + targetSubspaceName: string, + rootPath: string = getRootPath() +): IRushConfigurationProjectJson[] => { + const rushConfig: IRushConfigurationJson = loadRushConfiguration(rootPath); + return rushConfig.projects.filter(({ subspaceName }) => subspaceName === targetSubspaceName); +}; + export const isExternalMonorepo = ( sourceRootPath: string, targetRootPath: string = getRootPath() diff --git a/rush-plugins/rush-migrate-subspace-plugin/src/utilities/subspace.ts b/rush-plugins/rush-migrate-subspace-plugin/src/utilities/subspace.ts index 020f1d7..5f5b5f7 100644 --- a/rush-plugins/rush-migrate-subspace-plugin/src/utilities/subspace.ts +++ b/rush-plugins/rush-migrate-subspace-plugin/src/utilities/subspace.ts @@ -1,4 +1,4 @@ -import { FileSystem } from '@rushstack/node-core-library'; +import { FileSystem, IPackageJsonDependencyTable } from '@rushstack/node-core-library'; import { RushPathConstants } from '../constants/paths'; import { ISubspacesConfigurationJson } from '@rushstack/rush-sdk/lib/api/SubspacesConfiguration'; import { getRootPath } from './path'; @@ -6,12 +6,15 @@ import { getRushConfigurationJsonPath, getRushSubspacesConfigurationJsonPath, loadRushSubspacesConfiguration, + queryProjectsFromSubspace, querySubspaces } from './repository'; import { RushConstants } from '@rushstack/rush-sdk'; import { RushConfiguration } from '@rushstack/rush-sdk/lib/api/RushConfiguration'; import { VersionMismatchFinder } from '@rushstack/rush-sdk/lib/logic/versionMismatch/VersionMismatchFinder'; import { VersionMismatchFinderEntity } from '@rushstack/rush-sdk/lib/logic/versionMismatch/VersionMismatchFinderEntity'; +import { IRushConfigurationProjectJson } from '@rushstack/rush-sdk/lib/api/RushConfigurationProject'; +import { getProjectDependencies } from './project'; const getRushLegacySubspaceConfigurationFolderPath = ( subspaceName: string, @@ -53,9 +56,9 @@ export const isSubspaceSupported = (rootPath: string = getRootPath()): boolean = return false; }; -export const querySubspace = (subspaceName: string, rootPath: string = getRootPath()): string | undefined => { +export const subspaceExists = (subspaceName: string, rootPath: string = getRootPath()): boolean => { const subspaces: string[] = querySubspaces(rootPath); - return subspaces.find((name) => name === subspaceName); + return !!subspaces.find((name) => name === subspaceName); }; export const getSubspaceMismatches = ( @@ -65,6 +68,7 @@ export const getSubspaceMismatches = ( const rushConfig: RushConfiguration = RushConfiguration.loadFromConfigurationFile( getRushConfigurationJsonPath(rootPath) ); + const { mismatches } = VersionMismatchFinder.getMismatches(rushConfig, { variant: undefined, subspace: rushConfig.getSubspace(subspaceName) @@ -72,3 +76,30 @@ export const getSubspaceMismatches = ( return mismatches; }; + +export const getSubspaceDependencies = ( + subspaceName: string, + rootPath: string = getRootPath() +): Map> => { + const subspaceProjects: IRushConfigurationProjectJson[] = queryProjectsFromSubspace(subspaceName, rootPath); + const subspaceDependencies: Map> = new Map>(); + + for (const project of subspaceProjects) { + const projectDependencies: IPackageJsonDependencyTable | undefined = getProjectDependencies( + project.packageName, + rootPath + ); + + if (projectDependencies) { + for (const [dependency, version] of Object.entries(projectDependencies)) { + const subspaceDependency: Map | undefined = + subspaceDependencies.get(dependency) || new Map(); + const subspaceDependencyProjects: string[] = subspaceDependency.get(version) || []; + subspaceDependency.set(version, [...subspaceDependencyProjects, project.packageName]); + subspaceDependencies.set(dependency, subspaceDependency); + } + } + } + + return subspaceDependencies; +};