@hyperse/dependency-sync
Version:
A comprehensive Node.js utility for managing dependencies in monorepo environments, specifically designed for Hyperse plugin ecosystems.
143 lines (142 loc) • 7.32 kB
JavaScript
import { writeFileSync } from 'node:fs';
import { join } from 'node:path';
import { getPackages } from '../utils/getPackages.js';
import { readJson } from '../utils/readJson.js';
import { sortPackageJson } from '../utils/sortPackageJson.js';
import { checkMissedPackageDeclaration } from './checkMissedPackageDeclaration.js';
import { extractImportedModules } from './extractImportedModules.js';
/**
* Resolve the infered peer dependencies
* @param dependencies - The dependencies
* @param maxRangeCanbeSyncPeerDependencies - The max range can be sync peer dependencies
* @param dependenciesReferPeepDependencies - The dependencies refer peer dependencies
*/
function resolveInferedPeerDependencies(dependencies, maxRangeCanbeSyncPeerDependencies, dependenciesReferPeepDependencies) {
const newPeerDependencies = {};
for (const [key] of Object.entries(dependencies)) {
const referPeerDependencies = dependenciesReferPeepDependencies[key];
if (referPeerDependencies && referPeerDependencies.length) {
for (const referPeerDependency of referPeerDependencies) {
if (maxRangeCanbeSyncPeerDependencies[referPeerDependency]) {
newPeerDependencies[referPeerDependency] =
maxRangeCanbeSyncPeerDependencies[referPeerDependency];
}
else {
console.warn(`referPeerDependency ${referPeerDependency} not found in maxRangeCanbeSyncPeerDependencies`);
}
}
}
}
return newPeerDependencies;
}
/**
* Clean the empty dependencies
* @param packageJson - The package.json file
*/
function cleanEmptyDependencies(packageJson) {
const dependencies = packageJson['dependencies'];
const devDependencies = packageJson['devDependencies'];
const peerDependencies = packageJson['peerDependencies'];
if (Object.keys(dependencies).length === 0) {
delete packageJson['dependencies'];
}
if (Object.keys(devDependencies).length === 0) {
delete packageJson['devDependencies'];
}
if (Object.keys(peerDependencies).length === 0) {
delete packageJson['peerDependencies'];
}
}
/**
* Tidy the peer dependencies
* @param packageCwd - The package directory
* @param maxRangeCanbeSyncPeerDependencies - The max range can be sync peer dependencies
* @param dependenciesReferPeepDependencies - The dependencies refer peer dependencies
*/
const tidyPeerDependencies = async (packageCwd, maxRangeCanbeSyncPeerDependencies, dependenciesReferPeepDependencies) => {
// Extract the imported modules of this project from the source files
const importedModulesOfThisProject = await extractImportedModules(packageCwd);
// Sort the peer dependencies by length, to avoid the longest match
const maxRangeCanbeSyncPeerDependencyKeys = Object.keys(maxRangeCanbeSyncPeerDependencies).sort((a, b) => b.length - a.length);
const needSyncToPeerDependencies = {};
for (const moduleName of importedModulesOfThisProject) {
// Check if the imported module is a peer dependency
const matchModule = maxRangeCanbeSyncPeerDependencyKeys.find((x) =>
// handle these imports e.g. graphql, graphql-tag, '@vendure/common/lib/generated-shop-types'
moduleName.startsWith(x));
if (matchModule) {
needSyncToPeerDependencies[matchModule] =
maxRangeCanbeSyncPeerDependencies[matchModule];
}
}
// Extract the package.json file
const packageJsonFile = join(packageCwd, 'package.json');
// Extract the package.json file
const packgeJson = readJson(packageJsonFile);
// Extract the dependencies and devDependencies from the package.json file
const oldPackageDependencies = (packgeJson['dependencies'] || {});
// Extract the devDependencies from the package.json file
const oldPackageDevDependencies = (packgeJson['devDependencies'] ||
{});
// Infer the dependency peers from the dependencies and devDependencies
const newInferredPeerDependencies = resolveInferedPeerDependencies(
// Do not includes devDependencies here, because devDependencies normally is redundant.
Object.assign({}, oldPackageDependencies, needSyncToPeerDependencies), maxRangeCanbeSyncPeerDependencies, dependenciesReferPeepDependencies);
// Sort the peer dependencies
packgeJson.peerDependencies = {
// Keep the original peer dependencies, because we may need to fixed some peer dependencies
...packgeJson.peerDependencies,
...needSyncToPeerDependencies,
...newInferredPeerDependencies,
};
// Tidy finnal dependencies, devDependencies
for (const [key, value] of Object.entries(oldPackageDependencies)) {
// If the dependency is a peer dependency, move it to devDependencies
if (packgeJson.peerDependencies[key]) {
// move to devDependencies
oldPackageDevDependencies[key] = value;
// delete the dependency
delete oldPackageDependencies[key];
}
}
// If the peer dependency is not in the devDependencies, move it to devDependencies
for (const [key, value] of Object.entries(packgeJson.peerDependencies)) {
if (!oldPackageDevDependencies[key]) {
oldPackageDevDependencies[key] = value;
}
}
packgeJson['dependencies'] = oldPackageDependencies;
packgeJson['devDependencies'] = oldPackageDevDependencies;
// Clean the empty dependencies
cleanEmptyDependencies(packgeJson);
// Write the package.json file
writeFileSync(packageJsonFile, JSON.stringify(sortPackageJson(packgeJson), null, 2) + '\n');
};
/**
* Sync dependencies for all packages in the workspace
* @param workspaceRoot - The root directory of the workspace (defaults to process.cwd())
* @param options - Configuration options
* @param options.excludedPackages - Whether to exclude admin-ui packages (default: true)
* @param options.checkMissing - Whether to check for missing package declarations (default: true)
* @param options.maxRangeCanbeSyncPeerDependencies - Map of peer dependencies and their versions
* @param options.dependenciesReferPeepDependencies - Map of dependencies that reference peer dependencies
* @param options.ignoredCheckList - List of packages to ignore when checking for missing declarations
*/
export async function syncDependencies(workspaceRoot = process.cwd(), options = {}) {
const { excludedPackages, checkMissing = true, maxRangeCanbeSyncPeerDependencies = {}, dependenciesReferPeepDependencies = {}, ignoredCheckList = [], } = options;
const { packages } = await getPackages(workspaceRoot);
// filter the packages that need to be checked
const needCheckProjects = excludedPackages
? packages.filter((x) => !excludedPackages(x))
: packages;
// step1. sync peer dependencies
for (const packageItem of needCheckProjects) {
await tidyPeerDependencies(packageItem.dir, maxRangeCanbeSyncPeerDependencies, dependenciesReferPeepDependencies);
}
// step2. check missing package declarations
if (checkMissing) {
for (const packageItem of needCheckProjects) {
await checkMissedPackageDeclaration(packageItem.dir, ignoredCheckList);
}
}
}