Skip to content

Commit

Permalink
Merged release/1.6.0 into master
Browse files Browse the repository at this point in the history
  • Loading branch information
heikomat committed Feb 5, 2017
2 parents ceb0e85 + db1f4fb commit cbddaf5
Show file tree
Hide file tree
Showing 6 changed files with 175 additions and 18 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# 1.6.0
### New Features
- added `--no-link` and `--loglevel` flags

# 1.5.0
### New Features
- Support for local modules as dependencies in other local modules.
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ my-modular-app
}
```

## Parameters
Minstall knows the following flags:
- `--no-link` prevents minstall from linking the local modules to the root-node_modules
- `--loglevel <loglevel>` sets the loglevel (`error`, `warn`, `info` `verbose`, `debug`, `silly`)

## In collaboration with

![5Minds IT-Solutions](img/5minds_logo.png "5Minds IT-Solutions")
Expand Down
128 changes: 116 additions & 12 deletions lib/minstall.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const os = require('os');
const path = require('path');
const Promise = require('bluebird');
const readline = require('readline');
const logger = require('winston');

const cwd = process.cwd();

Expand All @@ -20,9 +21,14 @@ let isInProjectRoot = true;
let installedAsDependency = false;
let dependencyInstallLinkedFolders = [];
let projectFolderName = null;
let linkModules = true;

function logVerbose() {
return ['verbose', 'debug', 'silly'].indexOf(logger.level) >= 0;
}

function logIfInRoot(message) {
if (isInProjectRoot) {
if (message && message.length > 0 && (isInProjectRoot || logVerbose())) {
console.log(message);
}
}
Expand All @@ -48,18 +54,23 @@ function getInstalledModules() {
}

function checkStartConditions() {
logger.debug('checking start conditions');
return systools.verifyFolderName(cwd, 'node_modules')
.then((folderName) => {
if (folderName == null) {
logger.debug('project folder has no node_modules-folder');

const pathParts = cwd.split(path.sep);
if (pathParts[pathParts.length - 2] !== 'node_modules') {
logger.debug('project is not in a node_modules folder');
throw new UncriticalError('minstall started from outside the project-root. aborting.');
} else {
logger.debug('project is in a node_modules folder. It\'s therefore installed as a dependency');
installedAsDependency = true;
}
}

logger.debug('project folder has node_modules-folder');
if (moduletools.modulesFolder === '.') {
return Promise.resolve('.');
}
Expand Down Expand Up @@ -96,6 +107,7 @@ function checkStartConditions() {
}

function linkModulesToRoot(moduleInfos) {
logger.debug('linking local modules to root-node_modules');
return Promise.all(moduleInfos.map((moduleInfo) => {

// local modules should be linked using the folder-names they should have,
Expand All @@ -107,12 +119,14 @@ function linkModulesToRoot(moduleInfos) {
}

function linkRootToModule(moduleFolder) {
logger.debug('linking root-node_modules to module-node_modules');
const rootModuleFolder = path.join(cwd, 'node_modules');
const targetFolder = path.join(cwd, moduletools.modulesFolder, moduleFolder, 'node_modules');
return systools.link(rootModuleFolder, targetFolder);
}

function installMissingModuleDependencies(moduleFolder, missingDependencies) {
logger.debug('install missing dependencies');
const moduleNodeModules = path.join(cwd, moduletools.modulesFolder, moduleFolder, 'node_modules');
return systools.delete(moduleNodeModules)
.then(() => {
Expand All @@ -122,16 +136,18 @@ function installMissingModuleDependencies(moduleFolder, missingDependencies) {

function runPostinstall(moduleInfo) {
if (!moduleInfo.postinstall) {
logger.debug('won\'t run postinstall because there is no postinstall script');
return Promise.resolve();
}

logger.debug('running postinstall');
const modulePath = path.join(cwd, moduletools.modulesFolder, moduleInfo.realFolderName);
return linkRootToModule(moduleInfo.realFolderName)
.then(() => {

let command = moduleInfo.postinstall;
if (command.indexOf('minstall') === 0) {
command += ' isChildProcess';
command += ' --isChildProcess';
}
return systools.runCommand(`cd ${modulePath}${commandConcatSymbol} ${command}`);
})
Expand All @@ -144,6 +160,7 @@ function runPostinstall(moduleInfo) {
function cacheConflictingModules(conflictedDependencies, localModules, moduleFolderName) {

if (conflictedDependencies.length === 0) {
logger.debug('there are no conflicting packages, thus nothing needs to be cached');
return Promise.resolve([]);
}

Expand All @@ -153,17 +170,24 @@ function cacheConflictingModules(conflictedDependencies, localModules, moduleFol

// local modules are linked at the very end of the installation, thus all
// local modules are "not yet linked"
const cachedModuleFolders = conflictedDependencies.filter((module) => {
return localModuleNames.indexOf(module.name) < 0;
})
.map((dependency) => {
let cachedModuleFolders = conflictedDependencies;
if (linkModules) {
// don't try to cache away local modules, that will be linked later
cachedModuleFolders = cachedModuleFolders.filter((module) => {
return localModuleNames.indexOf(module.name) < 0;
});
}

cachedModuleFolders = cachedModuleFolders.map((dependency) => {
return dependency.folderName;
});

if (cachedModuleFolders.length === 0) {
logger.debug('there are no conflicting packages, thus nothing needs to be cached');
return Promise.resolve([]);
}

logger.debug('caching conflicting packages:', cachedModuleFolders);
const rootNodeModules = path.join(cwd, 'node_modules');
const cacheFolder = path.join(rootNodeModules, `${moduleFolderName}_cache`);
return systools.moveMultiple(cachedModuleFolders, rootNodeModules, cacheFolder)
Expand All @@ -175,6 +199,7 @@ function cacheConflictingModules(conflictedDependencies, localModules, moduleFol
function uncacheConflictingModules(cachedModuleFolders, conflictingModules, moduleFolderName) {

if (cachedModuleFolders.length === 0 && conflictingModules.length === 0) {
logger.debug('there are no packages that need to be uncached');
return Promise.resolve();
}

Expand All @@ -185,6 +210,7 @@ function uncacheConflictingModules(cachedModuleFolders, conflictingModules, modu
return module.folderName;
});

logger.debug('uncaching previously cached packages');
const rootNodeModules = path.join(cwd, 'node_modules');
const cacheFolder = path.join(rootNodeModules, `${moduleFolderName}_cache`);
const moduleFolder = path.join(cwd, moduletools.modulesFolder, moduleFolderName);
Expand All @@ -209,13 +235,18 @@ function persistExistingConfilctingDependencies(packetsToKeep, moduleFolderName)
return packet.folderName;
});

logger.debug('persist existing dependencies', packetFolderNames);
const rootNodeModules = path.join(cwd, 'node_modules');
const moduleNodeModules = path.join(cwd, moduletools.modulesFolder, moduleFolderName, 'node_modules');
return systools.moveMultiple(packetFolderNames, moduleNodeModules, rootNodeModules);
}

function installModules(installedModules, moduleInfos, index) {

if (logVerbose()) {
process.stdout.write(`\n`);
}

const moduleIndex = index || 0;
if (moduleIndex >= moduleInfos.length) {
return Promise.resolve();
Expand All @@ -232,20 +263,31 @@ function installModules(installedModules, moduleInfos, index) {
if (moduleInfos.length > 9 && moduleNumber <= 9) {
moduleNumber = `0${moduleNumber}`;
}

process.stdout.write(`installing module ${moduleNumber}/${moduleInfos.length} -> ${moduleInfo.realFolderName} `);

if (logVerbose()) {
process.stdout.write(`\n`);
}

}

let packetsToKeep;
let cachedModuleFolders;
const deps = moduletools.getModuleDependencies(installedModules, moduleInfos, moduleInfo);
const deps = moduletools.getModuleDependencies(installedModules, moduleInfos, moduleInfo, linkModules);
if (deps.messages.length > 0) {
logVersionConflict(deps.messages, moduleInfo.realFolderName);
}

return moduletools.getPreinstalledPacketsEvaluation(moduleInfo.realFolderName, deps.missing)
.then((result) => {
packetsToKeep = result.keep;
logger.debug('keeping the following preinstalled packages:', packetsToKeep.map((packet) => {
return packet.name;
}));

deps.missing = result.missing;
logger.debug('the following dependencies are missing:', deps.missing);
return cacheConflictingModules(deps.conflicted, moduleInfos, moduleInfo.realFolderName);
})
.then((newlyCachedModuleFolders) => {
Expand Down Expand Up @@ -274,6 +316,7 @@ function prepareInstallAsDependency() {
return Promise.resolve();
}

logger.debug('prepare install as dependency');
return systools.mkdir(path.join(cwd, 'node_modules'))
.then(() => {
return systools.getFolderNames(path.join(cwd, '..'));
Expand All @@ -293,6 +336,7 @@ function cleanupInstallAsDependency() {
return Promise.resolve();
}

logger.debug('cleanup after install as dependency');
return systools.deleteMultiple(dependencyInstallLinkedFolders, path.join(cwd, 'node_modules'))
.then(() => {
return systools.getFolderNames(path.join(cwd, 'node_modules'));
Expand All @@ -305,23 +349,72 @@ function cleanupInstallAsDependency() {
});
}

function setupLogger() {
logger.remove(logger.transports.Console);
logger.add(logger.transports.Console, {
stderrLevels: ['warn', 'error', 'critial'],
colorize: true,
handleExceptions: true,
humanReadableUnhandledException: true,
timestamp: false,
prettyPrint: true,
});

const logLevels = {
critical: {level: 0, color: 'red'},
error: {level: 1, color: 'magenta'},
warn: {level: 2, color: 'yellow'},
info: {level: 3, color: 'green'},
verbose: {level: 4, color: 'gray'},
debug: {level: 5, color: 'blue'},
silly: {level: 6, color: 'cyan'},
};
const levels = {};
const colors = {};

Object.keys(logLevels)
.forEach((name) => {
levels[name] = logLevels[name].level;
colors[name] = logLevels[name].color;
});

logger.setLevels(levels);
logger.addColors(colors);
}

function run() {
const startTime = Date.now();
if (process.argv[2] && process.argv[2] !== 'isChildProcess') {
moduletools.setModulesFolder(process.argv[2]);
}

if (process.argv.indexOf('isChildProcess') === process.argv.length - 1) {
isInProjectRoot = false;
setupLogger();
logger.level = 'info';
for (let i = 2; i < process.argv.length; i++) {
if (process.argv[i].indexOf('--') !== 0) {
moduletools.setModulesFolder(process.argv[i]);
} else if (process.argv[i] === '--isChildProcess') {
isInProjectRoot = false;
} else if (process.argv[i] === '--loglevel') {
logger.level = process.argv[i + 1];
i++;
} else if (process.argv[i] === '--no-link') {
linkModules = false;
}
}

systools.setLogger(logger);
moduletools.setLogger(logger);

logger.silly('process arguments:', process.argv);
logger.silly('os platfrom:', os.platform());
logger.debug('loglevel:', logger.level);
logger.debug('isChildProcess:', !isInProjectRoot);
if (os.platform() === 'win32') {
commandConcatSymbol = '&';
moduletools.setNullTarget('NUL');
}

const pathParts = cwd.split(path.sep);
projectFolderName = pathParts[pathParts.length - 1];
logger.debug('project folder name:', projectFolderName);

let installedModules;
let moduleInfos;
Expand All @@ -337,6 +430,8 @@ function run() {
})
.then((modules) => {
installedModules = modules[0];
logger.silly('installed modules:', installedModules.map((module) => { return module.name; }));

moduleInfos = modules[1];
moduleInfos.sort((module1, module2) => {
if (Object.keys(module1.dependencies).indexOf(module2.name) >= 0) {
Expand All @@ -350,6 +445,7 @@ function run() {
return 0;
});

logger.debug('found local modules:', moduleInfos.map((module) => { return module.name; }));
if (moduleInfos.length === 0) {
throw new UncriticalError('no modules found, thus minstall is done :)');
}
Expand All @@ -358,17 +454,25 @@ function run() {
if (moduleInfos.length === 1) {
moduleText = 'module';
}

logIfInRoot(`minstall found ${moduleInfos.length} local ${moduleText} in '${moduletools.modulesFolder}'`);
logger.debug('deleting previously linked local modules');
return Promise.all(moduleInfos.map((moduleInfo) => {
// local modules should be linked using the folder-names they should have,
// no matter what folder-name they actually have, therefore don't use .realFolderName here
return systools.delete(path.join(cwd, 'node_modules', moduleInfo.folderName));
}));
})
.then(() => {
logger.debug('install local modules');
return installModules(installedModules, moduleInfos);
})
.then(() => {
if (!linkModules) {
logger.debug('not linking local modules to root-node_modules, linking was disabled via --no-link flag');
return Promise.resolve();
}

return linkModulesToRoot(moduleInfos);
})
.then(() => {
Expand Down
Loading

0 comments on commit cbddaf5

Please sign in to comment.