Skip to content

Commit

Permalink
feat: upgrade external when higher version is discovered
Browse files Browse the repository at this point in the history
  • Loading branch information
medikoo committed Nov 9, 2018
1 parent 15ae780 commit 407b616
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 17 deletions.
23 changes: 16 additions & 7 deletions lib/install-package.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,25 @@ const { resolve } = require("path")
const ongoingMap = new Map();
const done = new Set();

const setupDependency = async (packagePath, dependencyName, configuration, options) => {
const setupDependency = async (
packageName,
packagePath,
dependencyName,
configuration,
options
) => {
const { packagesMeta } = configuration;
if (packagesMeta[dependencyName]) {
const isExternal = !packagesMeta[dependencyName];
if (!isExternal) {
if (ongoingMap.has(dependencyName)) {
ongoingMap.get(dependencyName).push(() => setupNpmLink(packagePath, dependencyName));
ongoingMap
.get(dependencyName)
.push(() => setupNpmLink(packageName, packagePath, dependencyName));
return;
}
if (!done.has(dependencyName)) await module.exports(dependencyName, configuration, options);
}
await setupNpmLink(packagePath, dependencyName);
await setupNpmLink(packageName, packagePath, dependencyName, { isExternal });
};

const setupDependencies = async (packageName, configuration, options) => {
Expand All @@ -41,7 +50,7 @@ const setupDependencies = async (packageName, configuration, options) => {

log.info("for %s setup dependencies %o", packageName, dependencies);
for (const dependencyName of dependencies) {
await setupDependency(packagePath, dependencyName, configuration, options);
await setupDependency(packageName, packagePath, dependencyName, configuration, options);
}

// Eventual optional dependencies
Expand All @@ -50,10 +59,10 @@ const setupDependencies = async (packageName, configuration, options) => {
if (dependencies.has(dependencyName)) continue;
dependencies.add(dependencyName);
if (packagesMeta[dependencyName]) {
await setupDependency(packagePath, dependencyName, configuration, options);
await setupDependency(packageName, packagePath, dependencyName, configuration, options);
continue;
}
try { await setupNpmLink(packagePath, dependencyName); }
try { await setupNpmLink(packageName, packagePath, dependencyName); }
catch (error) {
log.error(
`Could not link optional dependency %s, crashed with:\n${ error.stack }`,
Expand Down
96 changes: 86 additions & 10 deletions lib/setup-npm-link.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,95 @@
"use strict";

const { basename, resolve } = require("path")
, log = require("log4").get("dev-package")
, rm = require("fs2/rm")
, getNpmModulesPath = require("./get-npm-modules-path")
, isValidSymlink = require("./is-valid-symlink")
, runProgram = require("./run-program");
const isObject = require("es5-ext/object/is-object")
, memoizee = require("memoizee")
, { resolve } = require("path")
, log = require("log4").get("dev-package")
, readFile = require("fs2/read-file")
, rm = require("fs2/rm")
, semver = require("semver")
, getNpmModulesPath = require("./get-npm-modules-path")
, isValidSymlink = require("./is-valid-symlink")
, isDirectory = require("./is-directory")
, runProgram = require("./run-program");

module.exports = async (packagePath, dependencyName) => {
const getDependencyVersionRange = (packagePath, dependencyName) => {
const pkgJson = require(resolve(packagePath, "package.json"));
if (pkgJson.dependencies && pkgJson.dependencies[dependencyName]) {
return pkgJson.dependencies[dependencyName];
}
if (pkgJson.devDependencies && pkgJson.devDependencies[dependencyName]) {
return pkgJson.devDependencies[dependencyName];
}
return pkgJson.optionalDependencies[dependencyName];
};

const getAllPackageVersions = memoizee(
async dependencyName => {
const promise = runProgram("npm", ["view", dependencyName, "versions", "--json"]);
let data = "";
promise.child.stdout.on("data", chunk => (data += chunk));
await promise;
return JSON.parse(data);
},
{ promise: true }
);

const resolveExternalDependencyUpgradeVersion = async (
packageName,
packagePath,
dependencyName,
linkedPath
) => {
const dependencyVersionRange = getDependencyVersionRange(packagePath, dependencyName);
if (!semver.validRange(dependencyVersionRange)) {
log.warning(
"%s references %s not by semver range %s opting out from upgrade process", packageName,
dependencyName, dependencyVersionRange
);
return null;
}
const latestSupportedVersion = semver.maxSatisfying(
await getAllPackageVersions(dependencyName), dependencyVersionRange
);
if (!latestSupportedVersion) {
log.error(
"%s references %s with not satisfiable version range %s", packageName, dependencyName,
dependencyVersionRange
);
return null;
}

if (await isDirectory(linkedPath)) {
const installedVersion = JSON.parse(await readFile(resolve(linkedPath, "package.json")))
.version;
if (installedVersion === latestSupportedVersion) return null;
if (semver.gt(installedVersion, latestSupportedVersion)) {
log.error(
"%s expects %s in range %s when linked version reflects %s", packageName,
dependencyName, dependencyVersionRange, installedVersion
);
return null;
}
}
log.notice("upgrading external %s to %s", dependencyName, latestSupportedVersion);
return latestSupportedVersion;
};

module.exports = async (packageName, packagePath, dependencyName, options = {}) => {
if (!isObject(options)) options = {};
const dependencyLinkPath = resolve(packagePath, "node_modules", dependencyName);
const linkedPath = resolve(await getNpmModulesPath(), dependencyName);
if (await isValidSymlink(dependencyLinkPath, linkedPath)) return;
log.info("link %s in %s", dependencyName, basename(packagePath));
const linkVersion =
options.isExternal &&
(await resolveExternalDependencyUpgradeVersion(
packageName, packagePath, dependencyName, linkedPath
));
if (linkVersion) await rm(linkedPath, { loose: true, recursive: true, force: true });

if (!linkVersion && (await isValidSymlink(dependencyLinkPath, linkedPath))) return;
log.info("%s link dependency %s", packageName, dependencyName);
await rm(dependencyLinkPath, { loose: true, recursive: true, force: true });
await runProgram("npm", ["link", dependencyName], {
await runProgram("npm", ["link", dependencyName + (linkVersion ? `@${ linkVersion }` : "")], {
cwd: packagePath,
logger: log.levelRoot.get("npm:link")
});
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"log4-nodejs": "^3.1.1",
"memoizee": "^0.4.14",
"minimist": "^1.2",
"semver": "^5.6",
"split": "^1.0.1"
},
"devDependencies": {
Expand Down

0 comments on commit 407b616

Please sign in to comment.