refactor: extract code into several functions

This commit is contained in:
Karl-Philipp Wulfert
2019-04-16 11:31:47 +02:00
parent 34d3d43ff5
commit a0c3a12799
2 changed files with 311 additions and 201 deletions

View File

@@ -12,52 +12,25 @@
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import chalk from 'chalk';
import {execSync} from 'child_process';
import * as commander from 'commander';
import {copyFileSync, existsSync, readFileSync, writeFileSync} from 'fs';
import {join, resolve, sep} from 'path';
import {existsSync, readFileSync, writeFileSync} from 'fs';
import {resolve} from 'path';
import {cwd} from 'process';
import {isDeepStrictEqual} from 'util';
import {parse, stringify} from 'yaml';
import {EXPECTED_CI_CONFIG, EXPECTED_LICENSES, NEEDED_FILES, NYC_CONFIGURATION, SCRIPTS} from './configuration';
/* tslint:disable:no-console */
/**
* Wrapper for console.info that outputs every argument in cyan
* @param args
*/
function consoleInfo(...args: string[]): void {
args.forEach((arg) => {
console.info('\n' + chalk.cyan(arg));
});
}
/**
* Wrapper for console.warn that outputs every argument in red
* @param args
*/
function consoleWarn(...args: string[]): void {
args.forEach((arg) => {
console.warn('\n' + chalk.red.bold(arg));
});
}
/**
* Wrapper for console.log that outputs every argument in green
* @param args
*/
function consoleLog(...args: string[]): void {
args.forEach((arg) => {
console.log('\n' + chalk.green.bold(arg));
});
}
import {
checkCIConfig,
checkConfigurationFilesAreExtended,
checkContributors,
checkLicenses,
checkNeededFiles,
checkNYCConfiguration,
checkScripts,
consoleInfo,
consoleLog,
} from './common';
// current working directory
const currentWorkingDirectory = cwd();
let suggestOverwrite = false;
// configure commander
commander
.version(JSON.parse(readFileSync(resolve(__dirname, '..', 'package.json')).toString()).version)
@@ -76,9 +49,12 @@ if (!existsSync(resolve(path, 'package.json'))) {
// path to examined package.json
const packageJsonPath = resolve(path, 'package.json');
// wheter or not the contents of the package.json were changed
// whether or not the contents of the package.json were changed
let packageJsonChanged = false;
// whether or not to suggest an overwrite
let suggestOverwrite = false;
// read package.json in provided path
const packageJson = JSON.parse(readFileSync(packageJsonPath).toString());
@@ -88,172 +64,22 @@ if (packageJson.name === '@openstapps/configuration') {
process.exit(0);
}
// check if license is one of the expected ones
if (EXPECTED_LICENSES.indexOf(packageJson.license) === -1) {
consoleWarn(`License should be one of "${EXPECTED_LICENSES.join(', ')}"!`);
}
checkLicenses(packageJson);
// check if configuration files are extended
['tsconfig.json', 'tslint.json'].forEach((file) => {
const fileToCheck = resolve(path, file);
const expectedPath = `./node_modules/@openstapps/configuration/${file}`;
checkConfigurationFilesAreExtended(path);
if (existsSync(fileToCheck)) {
const configFile = JSON.parse(readFileSync(fileToCheck).toString());
suggestOverwrite = suggestOverwrite || checkNeededFiles(path, commander.replace);
const configFileExtended =
(typeof configFile.extends === 'string' && configFile.extends === expectedPath)
|| (file === 'tslint.json' && Array.isArray(configFile.extends) && configFile.extends.indexOf(expectedPath) >= 0);
const checkedNYCConfiguration = checkNYCConfiguration(packageJson, commander.replace);
if (!configFileExtended) {
consoleWarn(`
File "${fileToCheck}" should extend "${expectedPath}"!
packageJsonChanged = packageJsonChanged || checkedNYCConfiguration[0];
suggestOverwrite = suggestOverwrite || checkedNYCConfiguration[1];
Example:
${readFileSync(resolve(__dirname, '..', 'templates', 'template-' + file))}`);
}
}
});
packageJsonChanged = packageJsonChanged || checkScripts(packageJson, commander.replace);
// copy needed files
NEEDED_FILES.forEach((file) => {
let destinationFile = file;
checkContributors(packageJson);
// remove templates directory for destination files
if (destinationFile.indexOf('templates') === 0) {
destinationFile = destinationFile.split(sep).slice(1).join(sep);
file = join('templates', `template-${destinationFile}`);
}
const source = resolve(__dirname, '..', file);
const destination = resolve(path, destinationFile);
// check if file exists or replace flag is set
if (!existsSync(destination) || commander.replace) {
copyFileSync(source, destination);
consoleInfo(`Copied file "${source}" to "${destination}".`);
} else if (destinationFile === '.npmignore') {
const npmIgnore = readFileSync(destination).toString();
const ignoredPatterns = npmIgnore.split('\n');
let ignoresEverything = false;
let unignoresDocs = false;
for (const ignoredPattern of ignoredPatterns) {
if (ignoredPattern === '/*') {
ignoresEverything = true;
}
if (ignoredPattern === '!docs') {
unignoresDocs = true;
}
}
if (!ignoresEverything) {
consoleWarn(`'.npmignore' should have '/*' as first pattern to ignore everything.`);
suggestOverwrite = true;
}
if (unignoresDocs) {
consoleWarn(`'.npmignore' contains '!docs' and thus the package will contain the documentation.
Please double check that this is desired behavior since the docs can become huge:
https://gitlab.com/openstapps/configuration/issues/11`);
suggestOverwrite = true;
}
}
});
// check if nyc is a dependency
if (typeof packageJson.devDependencies === 'object' && Object.keys(packageJson.devDependencies).indexOf('nyc') >= 0) {
if (typeof packageJson.nyc === 'undefined' || commander.replace) {
// add NYC configuration
packageJson.nyc = NYC_CONFIGURATION;
packageJsonChanged = true;
consoleLog(`Added NYC configuration in "${packageJsonPath}".`);
} else if (!isDeepStrictEqual(packageJson.nyc, NYC_CONFIGURATION)) {
consoleInfo(`NYC configuration in '${packageJsonPath}' differs from the proposed one. Please check manually...`);
suggestOverwrite = true;
}
}
// check if scripts is a map
if (typeof packageJson.scripts !== 'object') {
packageJson.scripts = {};
packageJsonChanged = true;
}
Object.keys(SCRIPTS).forEach((scriptName) => {
const scriptToCheck = packageJson.scripts[scriptName];
// check if script exists
if (typeof scriptToCheck === 'undefined' || commander.replace) {
packageJson.scripts[scriptName] = SCRIPTS[scriptName];
packageJsonChanged = true;
consoleInfo(`Added '${scriptName}' script to '${packageJsonPath}'.`);
} else if (typeof scriptToCheck === 'string' && scriptToCheck !== SCRIPTS[scriptName]) {
consoleWarn(`NPM script '${scriptName}' should be "${SCRIPTS[scriptName].replace('\n', '\\n')}".`);
}
});
const execBuffer = execSync('git log --format=\'%aN\' | sort -u');
for (let author of execBuffer.toString().split('\n')) {
author = author.trim();
if (author === '') {
continue;
}
let authorIsAttributed = false;
authorIsAttributed = authorIsAttributed
|| (typeof packageJson.author === 'string' && packageJson.author.indexOf(author) >= 0)
|| (Array.isArray(packageJson.contributors) && packageJson.contributors.find((contributor: string) => {
return contributor.indexOf(author) >= 0;
}));
if (!authorIsAttributed) {
consoleWarn(`'${author}' should be attributed as author or contributor.`);
}
}
// check CI config if it exists
const pathToCiConfig = resolve(path, '.gitlab-ci.yml');
if (existsSync(pathToCiConfig)) {
// read CI config
const buffer = readFileSync(pathToCiConfig);
try {
const ciConfig = parse(buffer.toString());
// check entries
for (const entry in EXPECTED_CI_CONFIG) {
if (!EXPECTED_CI_CONFIG.hasOwnProperty(entry)) {
continue;
}
if (!isDeepStrictEqual(EXPECTED_CI_CONFIG[entry], ciConfig[entry])) {
consoleWarn(`Entry '${entry}' in ${pathToCiConfig} is incorrect. Expected value is:`);
consoleInfo(stringify((() => {
const completeEntry: any = {};
completeEntry[entry] = EXPECTED_CI_CONFIG[entry];
return completeEntry;
})()));
}
}
} catch (error) {
consoleWarn(`Could not parse ${pathToCiConfig} because of '${error.message}'.
Please ensure consistency of CI config manually.`);
consoleInfo(stringify(EXPECTED_CI_CONFIG));
}
}
checkCIConfig(path);
if (packageJsonChanged) {
writeFileSync(resolve(path, 'package.json'), JSON.stringify(packageJson, null, 2));

284
src/common.ts Normal file
View File

@@ -0,0 +1,284 @@
import chalk from 'chalk';
import {execSync} from 'child_process';
import {copyFileSync, existsSync, readFileSync} from 'fs';
import {join, resolve, sep} from 'path';
import {isDeepStrictEqual} from 'util';
import {parse, stringify} from 'yaml';
import {EXPECTED_CI_CONFIG, EXPECTED_LICENSES, NEEDED_FILES, NYC_CONFIGURATION, SCRIPTS} from './configuration';
/**
* Wrapper for console.info that outputs every argument in cyan
*
* @param args Arguments to output
*/
export function consoleInfo(...args: string[]): void {
args.forEach((arg) => {
/* tslint:disable-next-line:no-console */
console.info('\n' + chalk.cyan(arg));
});
}
/**
* Wrapper for console.warn that outputs every argument in red
*
* @param args Arguments to output
*/
export function consoleWarn(...args: string[]): void {
args.forEach((arg) => {
/* tslint:disable-next-line:no-console */
console.warn('\n' + chalk.red.bold(arg));
});
}
/**
* Wrapper for console.log that outputs every argument in green
*
* @param args Arguments to output
*/
export function consoleLog(...args: string[]): void {
args.forEach((arg) => {
/* tslint:disable-next-line:no-console */
console.log('\n' + chalk.green.bold(arg));
});
}
/**
* Check that configuration files are extended
*
* @param path Path, where configuration files are located
*/
export function checkConfigurationFilesAreExtended(path: string): void {
// check if configuration files are extended
['tsconfig.json', 'tslint.json'].forEach((file) => {
const fileToCheck = resolve(path, file);
const expectedPath = `./node_modules/@openstapps/configuration/${file}`;
if (existsSync(fileToCheck)) {
const configFile = JSON.parse(readFileSync(fileToCheck).toString());
const configFileExtended = (
typeof configFile.extends === 'string'
&& configFile.extends === expectedPath
)
|| (
file === 'tslint.json'
&& Array.isArray(configFile.extends)
&& configFile.extends.indexOf(expectedPath) >= 0
);
if (!configFileExtended) {
consoleWarn(`File "${fileToCheck}" should extend "${expectedPath}"!
Example:
${readFileSync(resolve(__dirname, '..', 'templates', 'template-' + file))}`);
}
}
});
}
/**
* Check needed files
*
* @param path Path to files to check
* @param replaceFlag Whether or not to replace files
* @return Whether or not overwrite is suggested
*/
export function checkNeededFiles(path: string, replaceFlag: boolean): boolean {
let suggestOverwrite = false;
// copy needed files
NEEDED_FILES.forEach((file) => {
let destinationFile = file;
// remove templates directory for destination files
if (destinationFile.indexOf('templates') === 0) {
destinationFile = destinationFile.split(sep).slice(1).join(sep);
file = join('templates', `template-${destinationFile}`);
}
const source = resolve(__dirname, '..', file);
const destination = resolve(path, destinationFile);
// check if file exists or replace flag is set
if (!existsSync(destination) || replaceFlag) {
copyFileSync(source, destination);
consoleInfo(`Copied file "${source}" to "${destination}".`);
} else if (destinationFile === '.npmignore') {
const npmIgnore = readFileSync(destination).toString();
const ignoredPatterns = npmIgnore.split('\n');
let ignoresEverything = false;
let unignoresDocs = false;
for (const ignoredPattern of ignoredPatterns) {
if (ignoredPattern === '/*') {
ignoresEverything = true;
}
if (ignoredPattern === '!docs') {
unignoresDocs = true;
}
}
if (!ignoresEverything) {
consoleWarn(`'.npmignore' should have '/*' as first pattern to ignore everything.`);
suggestOverwrite = true;
}
if (unignoresDocs) {
consoleWarn(`'.npmignore' contains '!docs' and thus the package will contain the documentation.
Please double check that this is desired behavior since the docs can become huge:
https://gitlab.com/openstapps/configuration/issues/11`);
suggestOverwrite = true;
}
}
});
return suggestOverwrite;
}
/**
* Check licenses
*
* @param packageJson package.json to check license in
*/
export function checkLicenses(packageJson: any): void {
// check if license is one of the expected ones
if (EXPECTED_LICENSES.indexOf(packageJson.license) === -1) {
consoleWarn(`License should be one of "${EXPECTED_LICENSES.join(', ')}"!`);
}
}
/**
* Check NYC configuration
*
* @param packageJson package.json to check NYC configuration in
* @param replaceFlag Whether or not to replace NYC configuration
* @return Whether or not package.json was changed and if overwrite is suggested
*/
export function checkNYCConfiguration(packageJson: any, replaceFlag: boolean): [boolean, boolean] {
let packageJsonChanged = false;
let suggestOverwrite = false;
// check if nyc is a dependency
if (typeof packageJson.devDependencies === 'object' && Object.keys(packageJson.devDependencies).indexOf('nyc') >= 0) {
if (typeof packageJson.nyc === 'undefined' || replaceFlag) {
// add NYC configuration
packageJson.nyc = NYC_CONFIGURATION;
packageJsonChanged = true;
consoleLog(`Added NYC configuration in to 'package.json'.`);
} else if (!isDeepStrictEqual(packageJson.nyc, NYC_CONFIGURATION)) {
consoleInfo(`NYC configuration in 'package.json' differs from the proposed one. Please check manually.`);
suggestOverwrite = true;
}
}
return [packageJsonChanged, suggestOverwrite];
}
/**
* Check scripts
*
* @param packageJson package.json to check scripts in
* @param replaceFlag Whether or not to replace scripts
* @return Whether or not the package.json was changed
*/
export function checkScripts(packageJson: any, replaceFlag: boolean): boolean {
let packageJsonChanged = false;
// check if scripts is a map
if (typeof packageJson.scripts !== 'object') {
packageJson.scripts = {};
packageJsonChanged = true;
}
Object.keys(SCRIPTS).forEach((scriptName) => {
const scriptToCheck = packageJson.scripts[scriptName];
// check if script exists
if (typeof scriptToCheck === 'undefined' || replaceFlag) {
packageJson.scripts[scriptName] = SCRIPTS[scriptName];
packageJsonChanged = true;
consoleInfo(`Added '${scriptName}' script to 'package.json'.`);
} else if (typeof scriptToCheck === 'string' && scriptToCheck !== SCRIPTS[scriptName]) {
consoleWarn(`Script '${scriptName}' in 'package.json' should be:
"${SCRIPTS[scriptName].replace('\n', '\\n')}".`);
}
});
return packageJsonChanged;
}
/**
* Check contributors
*
* @param packageJson package.json to check contributors in
*/
export function checkContributors(packageJson: any): void {
const execBuffer = execSync('git log --format=\'%aN\' | sort -u');
for (let author of execBuffer.toString().split('\n')) {
author = author.trim();
if (author === '') {
continue;
}
let authorIsAttributed = false;
authorIsAttributed = authorIsAttributed
|| (typeof packageJson.author === 'string' && packageJson.author.indexOf(author) >= 0)
|| (Array.isArray(packageJson.contributors) && packageJson.contributors.find((contributor: string) => {
return contributor.indexOf(author) >= 0;
}));
if (!authorIsAttributed) {
consoleWarn(`'${author}' should be attributed as author or contributor.`);
}
}
}
/**
* Check CI config
*
* @param path Path to CI config
*/
export function checkCIConfig(path: string): void {
// check CI config if it exists
const pathToCiConfig = resolve(path, '.gitlab-ci.yml');
if (existsSync(pathToCiConfig)) {
// read CI config
const buffer = readFileSync(pathToCiConfig);
try {
const ciConfig = parse(buffer.toString());
// check entries
for (const entry in EXPECTED_CI_CONFIG) {
if (!EXPECTED_CI_CONFIG.hasOwnProperty(entry)) {
continue;
}
if (!isDeepStrictEqual(EXPECTED_CI_CONFIG[entry], ciConfig[entry])) {
consoleWarn(`Entry '${entry}' in ${pathToCiConfig} is incorrect. Expected value is:`);
consoleInfo(stringify((() => {
const completeEntry: any = {};
completeEntry[entry] = EXPECTED_CI_CONFIG[entry];
return completeEntry;
})()));
}
}
} catch (error) {
consoleWarn(`Could not parse ${pathToCiConfig} because of '${error.message}'.
Please ensure consistency of CI config manually.`);
consoleInfo(stringify(EXPECTED_CI_CONFIG));
}
}
}