@jsenv/monorepo
Version:
Helpers to manage packages in a monorepo
196 lines (190 loc) • 6.72 kB
JavaScript
/*
* Try to upgrade all packages that are external to a monorepo.
* - "external" means a package that is not part of the monorepo
* - "upgrade" means check if there is a more recent version on NPM registry
* and if yes, update the version in the package.json
*
* Versions declared in "dependencies", "devDependencies"
*
* Be sure to check ../readme.md#upgrade-dependencies
*/
import { createTaskLog, UNICODE } from "@jsenv/humanize";
import { fetchLatestInRegistry } from "@jsenv/package-publish/src/internal/fetch_latest_in_registry.js";
import { collectWorkspacePackages } from "./internal/collect_workspace_packages.js";
import {
compareTwoPackageVersions,
VERSION_COMPARE_RESULTS,
} from "./internal/compare_two_package_versions.js";
import { increaseVersion } from "./internal/increase_version.js";
import { shouldUpdateVersion } from "./internal/should_update_version.js";
import { syncPackagesVersions } from "./sync_packages_versions.js";
export const upgradeExternalVersions = async ({
directoryUrl,
packagesRelations = {},
}) => {
const internalPackages = await collectWorkspacePackages({ directoryUrl });
const internalPackageNames = Object.keys(internalPackages);
let externalPackages = {};
collect_external_packages: {
const addExternalPackage = ({
internalPackageName,
name,
type,
version,
}) => {
if (!shouldUpdateVersion(version)) {
return;
}
const existing = externalPackages[name];
if (existing) {
externalPackages[name].push({
internalPackageName,
type,
version,
});
return;
}
externalPackages[name] = [];
externalPackages[name].push({
internalPackageName,
type,
version,
});
};
for (const internalPackageName of internalPackageNames) {
const internalPackage = internalPackages[internalPackageName];
const internalPackageObject = internalPackage.packageObject;
const {
dependencies = {},
devDependencies = {},
private: isPrivate,
} = internalPackageObject;
if (isPrivate) {
continue;
}
const dependencyNames = Object.keys(dependencies);
dependencyNames.forEach((dependencyName) => {
addExternalPackage({
internalPackageName,
type: "dependencies",
name: dependencyName,
version: dependencies[dependencyName],
});
});
const devDependencyNames = Object.keys(devDependencies);
devDependencyNames.forEach((devDependencyName) => {
addExternalPackage({
internalPackageName,
type: "devDependencies",
name: devDependencyName,
version: devDependencies[devDependencyName],
});
});
}
}
const externalPackageNames = Object.keys(externalPackages);
console.log(
`${UNICODE.INFO} ${externalPackageNames.length} external packages found`,
);
const latestVersions = {};
fetch_latest_versions: {
let done = 0;
const total = externalPackageNames.length;
const fetchTask = createTaskLog(`fetch latest versions`);
try {
await Promise.all(
externalPackageNames.map(async (externalPackageName) => {
const latestPackageInRegistry = await fetchLatestInRegistry({
registryUrl: "https://registry.npmjs.org",
packageName: externalPackageName,
});
if (latestPackageInRegistry === null) {
latestVersions[externalPackageName] = null;
console.warn(
`${UNICODE.WARN} "${externalPackageName}" not published on NPM`,
);
} else {
const registryLatestVersion = latestPackageInRegistry.version;
latestVersions[externalPackageName] = registryLatestVersion;
}
done++;
fetchTask.setRightText(`${done}/${total}`);
}),
);
fetchTask.done();
} catch (e) {
fetchTask.fail();
throw e;
}
}
const packageFilesToUpdate = {};
const updates = [];
let someInternalUpdate = false;
for (const externalPackageName of externalPackageNames) {
const externalPackageRefs = externalPackages[externalPackageName];
for (const externalPackageRef of externalPackageRefs) {
const internalPackageName = externalPackageRef.internalPackageName;
const internalPackageObject =
internalPackages[internalPackageName].packageObject;
const internalPackageDeps =
internalPackageObject[externalPackageRef.type];
const versionDeclared = internalPackageDeps[externalPackageName];
const registryLatestVersion = latestVersions[externalPackageName];
if (registryLatestVersion === null) {
continue;
}
const comparisonResult = compareTwoPackageVersions(
versionDeclared,
registryLatestVersion,
);
if (comparisonResult === VERSION_COMPARE_RESULTS.GREATER) {
console.warn(
`${UNICODE.WARNING} ${externalPackageName} version declared in ${internalPackageName} "${externalPackageRef.type}" (${versionDeclared}) is greater than latest version in registry (${registryLatestVersion})`,
);
continue;
}
if (comparisonResult === VERSION_COMPARE_RESULTS.SMALLER) {
updates.push({
packageName: internalPackageName,
dependencyName: externalPackageName,
from: versionDeclared,
to: registryLatestVersion,
});
if (
externalPackageRef.type === "dependencies" &&
shouldUpdateVersion(internalPackageObject.version)
) {
someInternalUpdate = true;
internalPackageObject.version = increaseVersion(
internalPackageObject.version,
internalPackageObject.packageUrl,
);
}
internalPackageDeps[externalPackageName] = registryLatestVersion;
packageFilesToUpdate[internalPackageName] = true;
}
}
}
Object.keys(packageFilesToUpdate).forEach((packageName) => {
const internalPackage = internalPackages[packageName];
internalPackage.updateFile(internalPackage.packageObject);
});
if (updates.length === 0) {
console.log(
`${UNICODE.OK} all versions declared in package.json files are in up-to-date with registry`,
);
} else {
console.log(
`${UNICODE.INFO} ${updates.length} versions modified in package.json files
Use a tool like "git diff" to review these changes then run "npm install"`,
);
}
if (someInternalUpdate) {
await syncPackagesVersions({
logs: false,
directoryUrl,
packagesRelations,
});
}
return updates;
};