grunt-npm-bower
Version:
Grunt plugin which helps migrating from Bower to npm.
229 lines (192 loc) • 7.57 kB
JavaScript
/*
* Wire
* Copyright (C) 2017 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*
*/
const chalk = require('chalk');
const glob = require('glob');
const grunt = require('grunt');
const path = require('path');
const fs = require('fs-extra');
const CONFIG_TYPE = require('./CONFIG_TYPE');
const MIGRATION_PREFIX = '@bower_components/';
function copyFiles(options, {pattern, sourceDir, targetDir}) {
return new Promise(function(resolve, reject) {
glob(pattern, {cwd: sourceDir}, function(error, files) {
if (!error) {
const copyTasks = [];
const logger = options.verbose ? grunt.log : grunt.verbose;
files.forEach(filePath => {
const sourcePath = path.join(sourceDir, filePath);
const targetPath = path.join(targetDir, path.basename(filePath));
const message = chalk`Copied file from "{blue ${sourcePath}}" to "{blue ${targetPath}}".`;
copyTasks.push(fs.copy(sourcePath, targetPath).then(() => logger.writeln(message)));
});
Promise.all(copyTasks).then(resolve);
} else {
reject(new Error(`Pattern "${pattern}" is invalid: ${error.message}`));
}
});
});
}
function migrateDependencyNames(config, type) {
let allDependencies = [];
let migratedNames = [];
for (const dependencyType of ['dependencies', 'devDependencies']) {
if (config[dependencyType]) {
allDependencies = allDependencies.concat(Object.keys(config[dependencyType]));
}
}
if (type === CONFIG_TYPE.BOWER) {
migratedNames = allDependencies.map(name => `${MIGRATION_PREFIX}${name}`);
} else {
migratedNames = allDependencies.filter(name => name.startsWith(MIGRATION_PREFIX));
}
return migratedNames.sort();
}
function migrateOverridePatterns(config, type, options) {
const overridePatterns = config[options.overrideProp] || {};
let migratedPatterns = {};
if (type === CONFIG_TYPE.BOWER) {
for (const name in overridePatterns) {
const pattern = overridePatterns[name];
migratedPatterns[`${MIGRATION_PREFIX}${name}`] = pattern;
}
} else {
for (const name in overridePatterns) {
const pattern = overridePatterns[name];
if (name.startsWith(MIGRATION_PREFIX)) {
migratedPatterns[name] = pattern;
}
}
}
return migratedPatterns;
}
function logDependencyOverview(migratedNames, migratedPatterns) {
let logDependencies = 'Bower dependencies: ';
migratedNames.forEach(name => {
if (Object.keys(migratedPatterns).indexOf(name) > -1) {
logDependencies += `${chalk.yellowBright(name)}, `;
} else {
logDependencies += `${chalk.blue(name)}, `;
}
});
return logDependencies.substr(0, logDependencies.length - 2);
}
function resolveName(sourceDir, packageName) {
const candidates = [
path.join(sourceDir, '.bower.json'),
path.join(sourceDir, 'bower.json'),
path.join(sourceDir, 'package.json'),
];
let name = packageName;
if (name.startsWith(MIGRATION_PREFIX)) {
name = name.substr(MIGRATION_PREFIX.length);
}
for (let i = 0; i < candidates.length; i++) {
try {
const descriptor = grunt.file.readJSON(candidates[i]);
name = descriptor.name;
break;
} catch (error) {
continue;
}
}
return name;
}
function writeMigrationsToPackageJSON(options, migratedPatterns) {
const hasOverrides = Object.keys(migratedPatterns).length > 0;
const file = path.join(options.cwd, options.npmConfig);
if (hasOverrides) {
const message = chalk`Writing property "{yellowBright ${options.overrideProp}}" into "{blue ${options.verbose
? file
: options.npmConfig}}"...`;
grunt.log.writeln(message);
const packageJSON = grunt.file.readJSON(file);
packageJSON[options.overrideProp] = Object.assign({}, packageJSON[options.overrideProp], migratedPatterns);
grunt.file.write(file, JSON.stringify(packageJSON, null, 2));
}
}
function copyBowerComponentsToTargetDir(options, migratedNames, migratedPatterns) {
const rootSourceDir = path.join(options.cwd, options.componentDir);
const rootTargetDir = path.join(options.cwd, options.targetDir);
grunt.log.writeln(
chalk`Copying Bower components from "{blue ${options.verbose
? rootSourceDir
: options.componentDir}}" to "{blue ${options.verbose ? rootTargetDir : options.targetDir}}".`
);
const promises = [];
for (const packageName of migratedNames) {
const logger = options.verbose ? grunt.log : grunt.verbose;
logger.writeln(chalk`Processing "{blue ${packageName}}"...`);
const sourceDir = path.join(rootSourceDir, packageName);
const targetPackageName = options.resolveName ? resolveName(sourceDir, packageName) : packageName;
const hasOverridePattern = Object.keys(migratedPatterns).indexOf(packageName) > -1;
if (hasOverridePattern) {
const payload = migratedPatterns[packageName];
for (const type in payload) {
const pattern = payload[type];
const pathInfos = {
pattern,
sourceDir,
targetDir: path.join(rootTargetDir, type, targetPackageName),
};
if (typeof pattern === 'string') {
promises.push(copyFiles(options, pathInfos));
} else if (pattern instanceof Array) {
pattern.forEach(patternEntry => {
pathInfos.pattern = patternEntry;
promises.push(copyFiles(options, pathInfos));
});
}
}
} else {
const pathInfos = {
pattern: '**/*.*',
sourceDir,
targetDir: path.join(rootTargetDir, targetPackageName),
};
promises.push(() => {
return copyFiles(options, pathInfos);
});
}
}
return Promise.all(promises);
}
function run(options, config) {
grunt.log.writeln(`Migrating dependency names and dependency overrides...`);
const migratedNames = migrateDependencyNames(config[CONFIG_TYPE.BOWER]).concat(
migrateDependencyNames(config[CONFIG_TYPE.NPM])
);
const migratedPatterns = Object.assign(
{},
migrateOverridePatterns(config[CONFIG_TYPE.BOWER], CONFIG_TYPE.BOWER, options),
migrateOverridePatterns(config[CONFIG_TYPE.NPM], CONFIG_TYPE.NPM, options)
);
const message = logDependencyOverview(migratedNames, migratedPatterns);
grunt.log.writeln(message);
if (Object.keys(config[CONFIG_TYPE.BOWER]).length) {
writeMigrationsToPackageJSON(options, migratedPatterns);
}
return copyBowerComponentsToTargetDir(options, migratedNames, migratedPatterns).then(() => ({
message: chalk`Copied Bower components from "{blue ${options.componentDir}}" to "{blue ${options.targetDir}}".`,
}));
}
module.exports = {
migrateDependencyNames,
MIGRATION_PREFIX,
run,
};