Skip to content

Commit

Permalink
feat: drop singleton nature
Browse files Browse the repository at this point in the history
Additionally refactor setupDependencies to external module
  • Loading branch information
medikoo committed Oct 26, 2018
1 parent 9cc5e78 commit a138b2e
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 122 deletions.
71 changes: 36 additions & 35 deletions bin/dev-package.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,48 +60,49 @@ const log = require("log4").get("dev-package")
, format = require("cli-sprintf-format")
, cliFooter = require("cli-progress-footer")()
, DevPackageError = require("../lib/dev-package-error")
, installPackage = require("../lib/install-package")
, resolveUserConfiguration = require("../lib/resolve-user-configuration");

const installsInProgress = new Map();

const logWordForms = {
present: { install: "installing", update: "updating" },
past: { install: "installed", update: "updated" }
};
, resolveUserConfiguration = require("../lib/resolve-user-configuration")
, installPackage = require("../");

cliFooter.shouldAddProgressAnimationPrefix = true;
cliFooter.updateProgress(["resolving user configuration"]);

const updateProgress = () => {
cliFooter.updateProgress(
Array.from(installsInProgress, ([inProgressPackageName, { type }]) =>
format(`${ logWordForms.present[type] } %s`, inProgressPackageName)
)
);
};

installPackage.on("start", event => {
installsInProgress.set(event.name, event);
updateProgress();
});
installPackage.on("end", ({ name: endedPackageName }) => {
const { type } = installsInProgress.get(endedPackageName);
installsInProgress.delete(endedPackageName);
log.notice(`${ logWordForms.past[type] } %s`, endedPackageName);
updateProgress();
});
resolveUserConfiguration()
.then(configuration =>
installPackage({ name: packageName }, configuration, {
skipGitUpdate: argv["skip-git-update"]
})
)
.catch(error => {
(async () => {
const configuration = await resolveUserConfiguration();
const installPromise = installPackage(packageName, configuration, {
skipGitUpdate: argv["skip-git-update"]
});

const installsInProgress = new Map();

const logWordForms = {
present: { install: "installing", update: "updating" },
past: { install: "installed", update: "updated" }
};
const updateProgress = () => {
cliFooter.updateProgress(
Array.from(installsInProgress, ([inProgressPackageName, { type }]) =>
format(`${ logWordForms.present[type] } %s`, inProgressPackageName)
)
);
};
installPromise.on("start", event => {
installsInProgress.set(event.name, event);
updateProgress();
});
installPromise.on("end", ({ name: endedPackageName }) => {
const { type } = installsInProgress.get(endedPackageName);
installsInProgress.delete(endedPackageName);
log.notice(`${ logWordForms.past[type] } %s`, endedPackageName);
updateProgress();
});
try {
await installPromise;
} catch (error) {
if (error instanceof DevPackageError) {
process.stdout.write(`\n${ clc.red(error.message) }\n`);
process.exit(1);
return;
}
throw error;
});
}
})();
15 changes: 12 additions & 3 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,19 @@

const toPlainObject = require("es5-ext/object/normalize-options")
, ensureString = require("es5-ext/object/validate-stringifiable-value")
, ee = require("event-emitter")
, unifyEmitters = require("event-emitter/unify")
, ensureConfiguration = require("./lib/ensure-configuration")
, installPackage = require("./lib/install-package");

module.exports = (name, configuration, options = {}) =>
installPackage(
{ name: ensureString(name) }, ensureConfiguration(configuration), toPlainObject(options)
module.exports = (name, configuration, options = {}) => {
const progressData = ee({ done: new Set(), ongoingMap: new Map() });
const promise = ee(
installPackage(
{ name: ensureString(name) }, ensureConfiguration(configuration),
toPlainObject(options), progressData
)
);
unifyEmitters(progressData, promise);
return promise;
};
108 changes: 25 additions & 83 deletions lib/install-package.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,79 +2,14 @@

const { resolve } = require("path")
, log = require("log4").get("dev-package")
, ee = require("event-emitter")
, isDirectory = require("fs2/is-directory")
, isSymlink = require("fs2/is-symlink")
, readdir = require("fs2/readdir")
, rm = require("fs2/rm")
, cleanupNpmInstall = require("./cleanup-npm-install")
, getNpmModulesPath = require("./get-npm-modules-path")
, runProgram = require("./run-program")
, setupRepository = require("./setup-repository")
, setupNpmLink = require("./setup-npm-link");

const ongoingMap = new Map();
const done = new Set();

const setupDependency = async (context, configuration, options) => {
const { dependencyName, isExternal } = context;
if (!isExternal) {
if (ongoingMap.has(dependencyName)) {
ongoingMap.get(dependencyName).push(() => setupNpmLink(context));
return;
}
if (!done.has(dependencyName)) {
await module.exports({ name: dependencyName }, configuration, options);
}
}
await setupNpmLink(context);
};

const resolveDependencyContext = (context, dependencyName, configuration) => {
const { name, path } = context;
const { packagesMeta } = configuration;
return {
dependentName: name,
dependentPath: path,
dependencyName,
isExternal: !packagesMeta[dependencyName]
};
};

const setupDependencies = async (context, configuration, options) => {
const { name, path } = context;
const pkgJson = (context.pkgJson = require(resolve(path, "package.json")));
const dependencies = (context.dependencies = new Set(
Object.keys(pkgJson.dependencies || {}).concat(Object.keys(pkgJson.devDependencies || {}))
));
dependencies.delete(name);

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

// Eventual optional dependencies
for (const dependencyName of Object.keys(pkgJson.optionalDependencies || {})) {
if (dependencyName === name) continue;
if (dependencies.has(dependencyName)) continue;
dependencies.add(dependencyName);
const dependencyContext = resolveDependencyContext(context, dependencyName, configuration);
if (dependencyContext.isExternal) {
await setupDependency(dependencyContext, configuration, options);
continue;
}
try { await setupNpmLink(dependencyContext); }
catch (error) {
log.error(
`Could not link optional dependency %s, crashed with:\n${ error.stack }`,
dependencyName
);
}
}
};
, setupRepository = require("./setup-repository");

const linkPackage = async context => {
const { path, name } = context;
Expand Down Expand Up @@ -118,7 +53,8 @@ const cleanNonDirectDependencies = async (context, { userDependencies }) => {
);
};

const finalize = async ({ name }) => {
const finalize = async ({ name }, progressData) => {
const { done, ongoingMap } = progressData;
log.debug("mark %s as done", name);
done.add(name);

Expand All @@ -131,39 +67,45 @@ const finalize = async ({ name }) => {
log.info("run pending jobs of %s", name);
for (const pendingJob of pendingJobs) await pendingJob();
}
module.exports.emit("end", { name });
progressData.emit("end", { name });
};

module.exports = ee(async (context, configuration, options) => {
const { name } = context;
const { hooks, packagesPath, packagesMeta } = configuration;
context.meta = packagesMeta[name];
const path = (context.path = resolve(packagesPath, name));
module.exports = async (packageContext, userConfiguration, inputOptions, progressData) => {
const { name } = packageContext;
const { hooks, packagesPath, packagesMeta } = userConfiguration;
const { ongoingMap } = progressData;
packageContext.meta = packagesMeta[name];
const path = (packageContext.path = resolve(packagesPath, name));

module.exports.emit("start", { name, type: (await isDirectory(path)) ? "update" : "install" });
progressData.emit("start", { name, type: (await isDirectory(path)) ? "update" : "install" });

const pendingJobs = (context.pendingJobs = []);
const pendingJobs = (packageContext.pendingJobs = []);
log.debug("mark %s as ongoing", name);
ongoingMap.set(name, pendingJobs);

// Ensure repository is up to date
await setupRepository(context, options);
await setupRepository(packageContext, inputOptions);

// Cleanup outcome of eventual previous npm crashes
await cleanupNpmInstall(context);
await cleanupNpmInstall(packageContext);

// Setup dependencies
await setupDependencies(context, configuration, options);
// (cyclic dependency so required on spot)
await require("./private/setup-dependencies")(
packageContext, userConfiguration, inputOptions, progressData
);

// Link package
await linkPackage(context);
await linkPackage(packageContext);

// Run eventual afterPackageInstall hooks
if (hooks.afterPackageInstall) await hooks.afterPackageInstall(context, configuration, options);
if (hooks.afterPackageInstall) {
await hooks.afterPackageInstall(packageContext, userConfiguration, inputOptions);
}

// Cleanup unexpected dependencies from node_modules
await cleanNonDirectDependencies(context, configuration);
await cleanNonDirectDependencies(packageContext, userConfiguration);

// Notify and cleanup
return finalize(context);
});
return finalize(packageContext, progressData);
};
77 changes: 77 additions & 0 deletions lib/private/setup-dependencies.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
"use strict";

const { resolve } = require("path")
, log = require("log4").get("dev-package")
, setupNpmLink = require("../setup-npm-link");

const setupDependency = async (
dependencyContext,
userConfiguration,
inputOptions,
progressData
) => {
const { dependencyName, isExternal } = dependencyContext;
const { done, ongoingMap } = progressData;
if (!isExternal) {
if (ongoingMap.has(dependencyName)) {
ongoingMap.get(dependencyName).push(() => setupNpmLink(dependencyContext));
return;
}
if (!done.has(dependencyName)) {
// Cyclical dependncy, hence required on spot
await require("../install-package")(
{ name: dependencyName }, userConfiguration, inputOptions, progressData
);
}
}
await setupNpmLink(dependencyContext);
};

const resolveDependencyContext = (context, dependencyName, configuration) => {
const { name, path } = context;
const { packagesMeta } = configuration;
return {
dependentName: name,
dependentPath: path,
dependencyName,
isExternal: !packagesMeta[dependencyName]
};
};

module.exports = async (packageContext, userConfiguration, inputOptions, progressData) => {
const { name, path } = packageContext;
const pkgJson = (packageContext.pkgJson = require(resolve(path, "package.json")));
const dependencies = (packageContext.dependencies = new Set(
Object.keys(pkgJson.dependencies || {}).concat(Object.keys(pkgJson.devDependencies || {}))
));
dependencies.delete(name);

log.info("for %s setup dependencies %o", name, dependencies);
for (const dependencyName of dependencies) {
await setupDependency(
resolveDependencyContext(packageContext, dependencyName, userConfiguration),
userConfiguration, inputOptions, progressData
);
}

// Eventual optional dependencies
for (const dependencyName of Object.keys(pkgJson.optionalDependencies || {})) {
if (dependencyName === name) continue;
if (dependencies.has(dependencyName)) continue;
dependencies.add(dependencyName);
const dependencyContext = resolveDependencyContext(
packageContext, dependencyName, userConfiguration
);
if (dependencyContext.isExternal) {
await setupDependency(dependencyContext, userConfiguration, inputOptions);
continue;
}
try { await setupNpmLink(dependencyContext); }
catch (error) {
log.error(
`Could not link optional dependency %s, crashed with:\n${ error.stack }`,
dependencyName
);
}
}
};
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@
},
"overrides": [
{
"files": "lib/install-package.js",
"files": [
"lib/install-package.js",
"lib/private/setup-dependencies.js"
],
"rules": {
"no-await-in-loop": "off"
}
Expand Down

0 comments on commit a138b2e

Please sign in to comment.