refactor: adjust code to stricter rules

This commit is contained in:
Karl-Philipp Wulfert
2019-05-24 17:53:55 +02:00
parent 14b6420c33
commit ac0166020e
2 changed files with 188 additions and 69 deletions

View File

@@ -37,7 +37,10 @@ const currentWorkingDirectory = cwd();
// configure commander // configure commander
commander commander
.version(JSON.parse(readFileSync(resolve(__dirname, '..', 'package.json')).toString()).version) .version(JSON.parse(
readFileSync(resolve(__dirname, '..', 'package.json'))
.toString(),
).version)
.option('-p, --path <path>', `Path of project to add files to (${currentWorkingDirectory})`, currentWorkingDirectory) .option('-p, --path <path>', `Path of project to add files to (${currentWorkingDirectory})`, currentWorkingDirectory)
.option('-r, --replace', 'Whether to replace existing files or not', false) .option('-r, --replace', 'Whether to replace existing files or not', false)
.parse(process.argv); .parse(process.argv);
@@ -54,7 +57,8 @@ if (!existsSync(resolve(path, 'package.json'))) {
const packageJsonPath = resolve(path, 'package.json'); const packageJsonPath = resolve(path, 'package.json');
// read package.json in provided path // read package.json in provided path
const packageJson = JSON.parse(readFileSync(packageJsonPath).toString()); const packageJson = JSON.parse(readFileSync(packageJsonPath)
.toString());
// check if provided path is this package // check if provided path is this package
if (packageJson.name === '@openstapps/configuration') { if (packageJson.name === '@openstapps/configuration') {
@@ -93,8 +97,10 @@ checkCIConfig(rules, path);
checkCopyrightYears(path, 'src'); checkCopyrightYears(path, 'src');
const indentation = 2;
if (packageJsonChanged) { if (packageJsonChanged) {
writeFileSync(resolve(path, 'package.json'), JSON.stringify(packageJson, null, 2)); writeFileSync(resolve(path, 'package.json'), JSON.stringify(packageJson, null, indentation));
consoleLog(`Changes were written to '${packageJsonPath}'.`); consoleLog(`Changes were written to '${packageJsonPath}'.`);
} }

View File

@@ -1,3 +1,17 @@
/*
* Copyright (C) 2018, 2019 StApps
* 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, version 3.
*
* 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 <https://www.gnu.org/licenses/>.
*/
import chalk from 'chalk'; import chalk from 'chalk';
import {execSync} from 'child_process'; import {execSync} from 'child_process';
import {copyFileSync, existsSync, lstatSync, PathLike, readdirSync, readFileSync} from 'fs'; import {copyFileSync, existsSync, lstatSync, PathLike, readdirSync, readFileSync} from 'fs';
@@ -6,6 +20,73 @@ import {satisfies, valid} from 'semver';
import {isDeepStrictEqual} from 'util'; import {isDeepStrictEqual} from 'util';
import {parse, stringify} from 'yaml'; import {parse, stringify} from 'yaml';
interface NYCConfiguration {
[prop: string]: boolean | number | string[];
}
interface PackageJSONPerson {
/**
* Email of the author
*/
email?: string;
/**
* Name of the author
*/
name: string;
/**
* URL of the author
*/
url?: string;
}
interface PackageJSON {
/**
* Author of the package
*/
author: string | PackageJSONPerson;
/**
* Contributors of the package
*/
contributors: Array<string | PackageJSONPerson>;
/**
* Dependencies
*/
dependencies?: {
[dependency: string]: string;
};
/**
* Development dependencies
*/
devDependencies?: {
[devDependency: string]: string;
};
/**
* License
*/
license: string;
/**
* NYC configuration
*/
nyc: NYCConfiguration;
/**
* Openstapps configuration
*/
openstappsConfiguration: Configuration;
/**
* Scripts
*/
scripts: {
[name: string]: string;
};
}
/** /**
* Configuration for the configuration check * Configuration for the configuration check
*/ */
@@ -47,7 +128,7 @@ export interface Rules {
/** /**
* Expected CI config * Expected CI config
*/ */
ciConfig: any; ciConfig: { [k: string]: string | object; };
/** /**
* Expected dependencies * Expected dependencies
*/ */
@@ -67,7 +148,7 @@ export interface Rules {
/** /**
* Expected NYC configuration * Expected NYC configuration
*/ */
nycConfiguration: any; nycConfiguration: NYCConfiguration;
/** /**
* Expected scripts * Expected scripts
*/ */
@@ -82,7 +163,7 @@ export interface Rules {
export function consoleInfo(...args: string[]): void { export function consoleInfo(...args: string[]): void {
args.forEach((arg) => { args.forEach((arg) => {
/* tslint:disable-next-line:no-console */ /* tslint:disable-next-line:no-console */
console.info('\n' + chalk.cyan(arg)); console.info(`\n${chalk.cyan(arg)}`);
}); });
} }
@@ -95,7 +176,7 @@ export function consoleWarn(...args: string[]): void {
args.forEach((arg) => { args.forEach((arg) => {
const lines = arg.split('\n'); const lines = arg.split('\n');
/* tslint:disable-next-line:no-console */ /* tslint:disable-next-line:no-console */
console.warn('\n' + chalk.red.bold(lines[0])); console.warn(`\n${chalk.red.bold(lines[0])}`);
for (const line of lines.slice(1)) { for (const line of lines.slice(1)) {
/* tslint:disable-next-line:no-console */ /* tslint:disable-next-line:no-console */
console.info(line); console.info(line);
@@ -111,7 +192,7 @@ export function consoleWarn(...args: string[]): void {
export function consoleLog(...args: string[]): void { export function consoleLog(...args: string[]): void {
args.forEach((arg) => { args.forEach((arg) => {
/* tslint:disable-next-line:no-console */ /* tslint:disable-next-line:no-console */
console.log('\n' + chalk.green.bold(arg)); console.log(`\n${chalk.green.bold(arg)}`);
}); });
} }
@@ -121,43 +202,56 @@ export function consoleLog(...args: string[]): void {
* @param rules Rules for check * @param rules Rules for check
* @param packageJson package.json to check dependencies in * @param packageJson package.json to check dependencies in
*/ */
export function checkDependencies(rules: Rules, packageJson: any): void { export function checkDependencies(rules: Rules, packageJson: PackageJSON): void {
for (const dependency of rules.dependencies) { if (typeof packageJson.dependencies === 'object') {
const [name, version] = dependency.split(':'); for (const dependency of rules.dependencies) {
const installedVersion = packageJson.dependencies[name]; const [name, version] = dependency.split(':');
const installedVersion = packageJson.dependencies[name];
if (typeof packageJson.dependencies === 'undefined' || typeof packageJson.dependencies[name] === 'undefined') { if (typeof packageJson.dependencies === 'undefined' || typeof packageJson.dependencies[name] === 'undefined') {
consoleWarn(`Dependency '${name}' is missing. consoleWarn(`Dependency '${name}' is missing.
Please install with 'npm install --save-exact ${name}'.`); Please install with 'npm install --save-exact ${name}'.`);
} else if ( } else if (
typeof version !== 'undefined' typeof version !== 'undefined'
&& valid(version) && typeof valid(version) === 'string'
&& !satisfies(installedVersion, version) && !satisfies(installedVersion, version)
) { ) {
consoleWarn( consoleWarn(
`Version '${installedVersion}' of dependency '${name} does not satisfy constraint '${version}'.`, `Version '${installedVersion}' of dependency '${name} does not satisfy constraint '${version}'.`,
); );
}
} }
} }
for (const devDependency of rules.devDependencies) { if (typeof packageJson.devDependencies === 'object') {
const [name, version] = devDependency.split(':'); for (const devDependency of rules.devDependencies) {
const installedVersion = packageJson.dependencies[name]; const [devName, devVersion] = devDependency.split(':');
let installedVersion = packageJson.devDependencies[devName];
if (typeof packageJson.dependencies === 'object') {
const [name] = devDependency.split(':');
if (typeof packageJson.devDependencies[name] === 'string') {
installedVersion = packageJson.dependencies[name];
}
}
if ( if (
typeof packageJson.dependencies === 'undefined' || typeof packageJson.dependencies[name] === 'undefined' (typeof packageJson.dependencies === 'undefined' || typeof packageJson.dependencies[devName] === 'undefined')
&& typeof packageJson.devDependencies === 'undefined' || typeof packageJson.devDependencies[name] === 'undefined' && (
) { typeof packageJson.devDependencies === 'undefined'
consoleWarn(`Dev dependency '${name}' is missing. || typeof packageJson.devDependencies[devName] === 'undefined'
Please install with 'npm install --save-exact --save-dev ${name}'.`); )
} else if ( ) {
typeof version !== 'undefined' consoleWarn(`Dev dependency '${devName}' is missing.
&& valid(version) Please install with 'npm install --save-exact --save-dev ${devName}'.`);
&& !satisfies(installedVersion, version) } else if (
) { typeof devVersion !== 'undefined'
consoleWarn( && typeof valid(devVersion) === 'string'
`Version '${installedVersion}' of dev dependency '${name} does not satisfy constraint '${version}'.`, && !satisfies(installedVersion, devVersion)
); ) {
consoleWarn(
`Version '${installedVersion}' of dev dependency '${devName} does not satisfy constraint '${devVersion}'.`,
);
}
} }
} }
} }
@@ -174,7 +268,8 @@ export function checkConfigurationFilesAreExtended(path: string): void {
const expectedPath = `./node_modules/@openstapps/configuration/${file}`; const expectedPath = `./node_modules/@openstapps/configuration/${file}`;
if (existsSync(fileToCheck)) { if (existsSync(fileToCheck)) {
const configFile = JSON.parse(readFileSync(fileToCheck).toString()); const configFile = JSON.parse(readFileSync(fileToCheck)
.toString());
const configFileExtended = ( const configFileExtended = (
typeof configFile.extends === 'string' typeof configFile.extends === 'string'
@@ -190,7 +285,7 @@ export function checkConfigurationFilesAreExtended(path: string): void {
consoleWarn(`File '${fileToCheck}' should extend '${expectedPath}'! consoleWarn(`File '${fileToCheck}' should extend '${expectedPath}'!
Example: Example:
${readFileSync(resolve(__dirname, '..', 'templates', 'template-' + file))}`); ${readFileSync(resolve(__dirname, '..', 'templates', `template-${file}`))}`);
} }
} }
}); });
@@ -208,12 +303,14 @@ export function checkNeededFiles(rules: Rules, path: string, replaceFlag: boolea
let suggestOverwrite = false; let suggestOverwrite = false;
// copy needed files // copy needed files
rules.files.forEach((file) => { for (let file of rules.files) {
let destinationFile = file; let destinationFile = file;
// remove templates directory for destination files // remove templates directory for destination files
if (destinationFile.indexOf('templates') === 0) { if (destinationFile.indexOf('templates') === 0) {
destinationFile = destinationFile.split(sep).slice(1).join(sep); destinationFile = destinationFile.split(sep)
.slice(1)
.join(sep);
file = join('templates', `template-${destinationFile}`); file = join('templates', `template-${destinationFile}`);
} }
@@ -225,7 +322,8 @@ export function checkNeededFiles(rules: Rules, path: string, replaceFlag: boolea
copyFileSync(source, destination); copyFileSync(source, destination);
consoleInfo(`Copied file '${source}' to '${destination}'.`); consoleInfo(`Copied file '${source}' to '${destination}'.`);
} else if (destinationFile === '.npmignore') { } else if (destinationFile === '.npmignore') {
const npmIgnore = readFileSync(destination).toString(); const npmIgnore = readFileSync(destination)
.toString();
const ignoredPatterns = npmIgnore.split('\n'); const ignoredPatterns = npmIgnore.split('\n');
@@ -256,7 +354,7 @@ https://gitlab.com/openstapps/configuration/issues/11`);
suggestOverwrite = true; suggestOverwrite = true;
} }
} }
}); }
return suggestOverwrite; return suggestOverwrite;
} }
@@ -267,7 +365,7 @@ https://gitlab.com/openstapps/configuration/issues/11`);
* @param rules Rules for check * @param rules Rules for check
* @param packageJson package.json to check license in * @param packageJson package.json to check license in
*/ */
export function checkLicenses(rules: Rules, packageJson: any): void { export function checkLicenses(rules: Rules, packageJson: PackageJSON): void {
// check if license is one of the expected ones // check if license is one of the expected ones
if (rules.licenses.indexOf(packageJson.license) === -1) { if (rules.licenses.indexOf(packageJson.license) === -1) {
consoleWarn(`License should be one of '${rules.licenses.join(', ')}'!`); consoleWarn(`License should be one of '${rules.licenses.join(', ')}'!`);
@@ -282,12 +380,14 @@ export function checkLicenses(rules: Rules, packageJson: any): void {
* @param replaceFlag Whether or not to replace NYC configuration * @param replaceFlag Whether or not to replace NYC configuration
* @return Whether or not package.json was changed and if overwrite is suggested * @return Whether or not package.json was changed and if overwrite is suggested
*/ */
export function checkNYCConfiguration(rules: Rules, packageJson: any, replaceFlag: boolean): [boolean, boolean] { export function checkNYCConfiguration(rules: Rules, packageJson: PackageJSON, replaceFlag: boolean)
: [boolean, boolean] {
let packageJsonChanged = false; let packageJsonChanged = false;
let suggestOverwrite = false; let suggestOverwrite = false;
// check if nyc is a dependency // check if nyc is a dependency
if (typeof packageJson.devDependencies === 'object' && Object.keys(packageJson.devDependencies).indexOf('nyc') >= 0) { if (typeof packageJson.devDependencies === 'object' && Object.keys(packageJson.devDependencies)
.indexOf('nyc') >= 0) {
if (typeof packageJson.nyc === 'undefined' || replaceFlag) { if (typeof packageJson.nyc === 'undefined' || replaceFlag) {
// add NYC configuration // add NYC configuration
packageJson.nyc = rules.nycConfiguration; packageJson.nyc = rules.nycConfiguration;
@@ -313,7 +413,7 @@ export function checkNYCConfiguration(rules: Rules, packageJson: any, replaceFla
* @param replaceFlag Whether or not to replace scripts * @param replaceFlag Whether or not to replace scripts
* @return Whether or not the package.json was changed * @return Whether or not the package.json was changed
*/ */
export function checkScripts(rules: Rules, packageJson: any, replaceFlag: boolean): boolean { export function checkScripts(rules: Rules, packageJson: PackageJSON, replaceFlag: boolean): boolean {
let packageJsonChanged = false; let packageJsonChanged = false;
// check if scripts is a map // check if scripts is a map
@@ -323,7 +423,11 @@ export function checkScripts(rules: Rules, packageJson: any, replaceFlag: boolea
packageJsonChanged = true; packageJsonChanged = true;
} }
Object.keys(rules.scripts).forEach((scriptName) => { for (const scriptName in rules.scripts) {
if (!rules.scripts.hasOwnProperty(scriptName)) {
continue;
}
const scriptToCheck = packageJson.scripts[scriptName]; const scriptToCheck = packageJson.scripts[scriptName];
// check if script exists // check if script exists
@@ -337,7 +441,7 @@ export function checkScripts(rules: Rules, packageJson: any, replaceFlag: boolea
consoleWarn(`Script '${scriptName}' in 'package.json' should be: consoleWarn(`Script '${scriptName}' in 'package.json' should be:
'${rules.scripts[scriptName].replace('\n', '\\n')}'.`); '${rules.scripts[scriptName].replace('\n', '\\n')}'.`);
} }
}); }
return packageJsonChanged; return packageJsonChanged;
} }
@@ -348,25 +452,26 @@ export function checkScripts(rules: Rules, packageJson: any, replaceFlag: boolea
* @param path Path to directory * @param path Path to directory
* @param packageJson package.json to check contributors in * @param packageJson package.json to check contributors in
*/ */
export function checkContributors(path: PathLike, packageJson: any): void { export function checkContributors(path: PathLike, packageJson: PackageJSON): void {
const execBuffer = execSync(`git --git-dir=${path}/.git --work-tree=${path} log --format=\'%aN\' | sort -u`); const execBuffer = execSync(`git --git-dir=${path}/.git --work-tree=${path} log --format=\'%aN\' | sort -u`);
for (let author of execBuffer.toString().split('\n')) { for (let person of execBuffer.toString()
author = author.trim(); .split('\n')) {
person = person.trim();
if (author === '') { if (person === '') {
continue; continue;
} }
let authorIsAttributed = false; let authorIsAttributed = false;
authorIsAttributed = authorIsAttributed authorIsAttributed = authorIsAttributed
|| (typeof packageJson.author === 'string' && packageJson.author.indexOf(author) >= 0) || (typeof packageJson.author === 'string' && packageJson.author.indexOf(person) >= 0)
|| (Array.isArray(packageJson.contributors) && packageJson.contributors.find((contributor: string) => { || (Array.isArray(packageJson.contributors) && packageJson.contributors.findIndex((contributor) => {
return contributor.indexOf(author) >= 0; return typeof contributor === 'string' && contributor.indexOf(person) >= 0;
})); }) >= 0);
if (!authorIsAttributed) { if (!authorIsAttributed) {
consoleWarn(`'${author}' should be attributed as author or contributor.`); consoleWarn(`'${person}' should be attributed as author or contributor.`);
} }
} }
} }
@@ -393,7 +498,7 @@ export function checkCIConfig(rules: Rules, path: string): void {
} }
if (!isDeepStrictEqual(rules.ciConfig[entry], ciConfig[entry])) { if (!isDeepStrictEqual(rules.ciConfig[entry], ciConfig[entry])) {
const completeEntry: any = {}; const completeEntry: { [k: string]: string | object; } = {};
completeEntry[entry] = rules.ciConfig[entry]; completeEntry[entry] = rules.ciConfig[entry];
consoleWarn(`Entry '${entry}' in '${pathToCiConfig}' is incorrect. Expected value is: consoleWarn(`Entry '${entry}' in '${pathToCiConfig}' is incorrect. Expected value is:
${stringify(completeEntry)}`); ${stringify(completeEntry)}`);
@@ -428,11 +533,13 @@ export function checkCopyrightYears(path: PathLike, subDir: PathLike): void {
.split('\n') .split('\n')
.map((date) => parseInt(date.split('-')[0], 10)) .map((date) => parseInt(date.split('-')[0], 10))
.filter((year) => { .filter((year) => {
if (seen.indexOf(year) >= 0 || !year.toString().match(/[0-9]{4}/)) { const stringYear = year.toString();
if (seen.indexOf(year) >= 0 || stringYear.match(/[0-9]{4}/) !== null) {
return false; return false;
} }
seen.push(year); seen.push(year);
return true; return true;
}) })
.sort(); .sort();
@@ -440,13 +547,16 @@ export function checkCopyrightYears(path: PathLike, subDir: PathLike): void {
const fileStats = lstatSync(fileSystemObjectPath); const fileStats = lstatSync(fileSystemObjectPath);
if (fileStats.isFile()) { if (fileStats.isFile()) {
const content = readFileSync(fileSystemObjectPath).toString().split('\n'); const content = readFileSync(fileSystemObjectPath)
.toString()
.split('\n');
let copyrightYearsString: string = ''; let copyrightYearsString = '';
for (const line of content) { for (const line of content) {
const match = line.match(/^ \* Copyright \(C\) ([0-9\-,\s]*) StApps$/); const match = line.match(/^ \* Copyright \(C\) ([0-9\-,\s]*) StApps$/);
const expectedMatchLength = 2;
if (Array.isArray(match) && match.length === 2) { if (Array.isArray(match) && match.length === expectedMatchLength) {
copyrightYearsString = match[1]; copyrightYearsString = match[1];
} }
} }
@@ -454,7 +564,8 @@ export function checkCopyrightYears(path: PathLike, subDir: PathLike): void {
if (copyrightYearsString === '') { if (copyrightYearsString === '') {
consoleWarn(`Copyright line for file '${fileSystemObjectPath}' could not be found!`); consoleWarn(`Copyright line for file '${fileSystemObjectPath}' could not be found!`);
} else { } else {
const copyrightYearsWithIntervals = copyrightYearsString.split(',').map((year) => year.trim()); const copyrightYearsWithIntervals = copyrightYearsString.split(',')
.map((year) => year.trim());
const copyrightYears: number[] = []; const copyrightYears: number[] = [];
for (const copyrightYear of copyrightYearsWithIntervals) { for (const copyrightYear of copyrightYearsWithIntervals) {
@@ -497,7 +608,7 @@ export function checkCopyrightYears(path: PathLike, subDir: PathLike): void {
* *
* @param packageJson package.json to get configuration from * @param packageJson package.json to get configuration from
*/ */
export function getConfiguration(packageJson: any): Configuration { export function getConfiguration(packageJson: PackageJSON): Configuration {
const defaultConfiguration: Configuration = { const defaultConfiguration: Configuration = {
forPackaging: true, forPackaging: true,
hasCli: true, hasCli: true,
@@ -569,7 +680,7 @@ export function getRules(configuration: Configuration): Rules {
// expected scripts // expected scripts
const scripts: { [k: string]: string; } = { const scripts: { [k: string]: string; } = {
/* tslint:disable-next-line:max-line-length */ /* tslint:disable-next-line:max-line-length */
'changelog': 'conventional-changelog -p angular -i CHANGELOG.md -s -r 0 && git add CHANGELOG.md && git commit -m \'docs: update changelog\'', changelog: 'conventional-changelog -p angular -i CHANGELOG.md -s -r 0 && git add CHANGELOG.md && git commit -m \'docs: update changelog\'',
'check-configuration': 'openstapps-configuration', 'check-configuration': 'openstapps-configuration',
tslint: 'tslint -p tsconfig.json -c tslint.json \'src/**/*.ts\'', tslint: 'tslint -p tsconfig.json -c tslint.json \'src/**/*.ts\'',
}; };
@@ -581,7 +692,7 @@ export function getRules(configuration: Configuration): Rules {
]; ];
// expected values in CI config // expected values in CI config
const ciConfig: { [k: string]: any; } = { const ciConfig = {
/* tslint:disable:object-literal-sort-keys */ /* tslint:disable:object-literal-sort-keys */
image: 'registry.gitlab.com/openstapps/projectmanagement/node', image: 'registry.gitlab.com/openstapps/projectmanagement/node',
cache: { cache: {
@@ -660,6 +771,8 @@ export function getRules(configuration: Configuration): Rules {
}; };
for (const ignoreCiEntry of configuration.ignoreCiEntries) { for (const ignoreCiEntry of configuration.ignoreCiEntries) {
// tslint:disable-next-line:ban-ts-ignore
// @ts-ignore
delete ciConfig[ignoreCiEntry]; delete ciConfig[ignoreCiEntry];
} }