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
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('-r, --replace', 'Whether to replace existing files or not', false)
.parse(process.argv);
@@ -54,7 +57,8 @@ if (!existsSync(resolve(path, 'package.json'))) {
const packageJsonPath = resolve(path, 'package.json');
// 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
if (packageJson.name === '@openstapps/configuration') {
@@ -93,8 +97,10 @@ checkCIConfig(rules, path);
checkCopyrightYears(path, 'src');
const indentation = 2;
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}'.`);
}

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 {execSync} from 'child_process';
import {copyFileSync, existsSync, lstatSync, PathLike, readdirSync, readFileSync} from 'fs';
@@ -6,6 +20,73 @@ import {satisfies, valid} from 'semver';
import {isDeepStrictEqual} from 'util';
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
*/
@@ -47,7 +128,7 @@ export interface Rules {
/**
* Expected CI config
*/
ciConfig: any;
ciConfig: { [k: string]: string | object; };
/**
* Expected dependencies
*/
@@ -67,7 +148,7 @@ export interface Rules {
/**
* Expected NYC configuration
*/
nycConfiguration: any;
nycConfiguration: NYCConfiguration;
/**
* Expected scripts
*/
@@ -82,7 +163,7 @@ export interface Rules {
export function consoleInfo(...args: string[]): void {
args.forEach((arg) => {
/* 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) => {
const lines = arg.split('\n');
/* 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)) {
/* tslint:disable-next-line:no-console */
console.info(line);
@@ -111,7 +192,7 @@ export function consoleWarn(...args: string[]): void {
export function consoleLog(...args: string[]): void {
args.forEach((arg) => {
/* 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 packageJson package.json to check dependencies in
*/
export function checkDependencies(rules: Rules, packageJson: any): void {
for (const dependency of rules.dependencies) {
const [name, version] = dependency.split(':');
const installedVersion = packageJson.dependencies[name];
export function checkDependencies(rules: Rules, packageJson: PackageJSON): void {
if (typeof packageJson.dependencies === 'object') {
for (const dependency of rules.dependencies) {
const [name, version] = dependency.split(':');
const installedVersion = packageJson.dependencies[name];
if (typeof packageJson.dependencies === 'undefined' || typeof packageJson.dependencies[name] === 'undefined') {
consoleWarn(`Dependency '${name}' is missing.
if (typeof packageJson.dependencies === 'undefined' || typeof packageJson.dependencies[name] === 'undefined') {
consoleWarn(`Dependency '${name}' is missing.
Please install with 'npm install --save-exact ${name}'.`);
} else if (
typeof version !== 'undefined'
&& valid(version)
&& !satisfies(installedVersion, version)
) {
consoleWarn(
`Version '${installedVersion}' of dependency '${name} does not satisfy constraint '${version}'.`,
);
} else if (
typeof version !== 'undefined'
&& typeof valid(version) === 'string'
&& !satisfies(installedVersion, version)
) {
consoleWarn(
`Version '${installedVersion}' of dependency '${name} does not satisfy constraint '${version}'.`,
);
}
}
}
for (const devDependency of rules.devDependencies) {
const [name, version] = devDependency.split(':');
const installedVersion = packageJson.dependencies[name];
if (typeof packageJson.devDependencies === 'object') {
for (const devDependency of rules.devDependencies) {
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 (
typeof packageJson.dependencies === 'undefined' || typeof packageJson.dependencies[name] === 'undefined'
&& typeof packageJson.devDependencies === 'undefined' || typeof packageJson.devDependencies[name] === 'undefined'
) {
consoleWarn(`Dev dependency '${name}' is missing.
Please install with 'npm install --save-exact --save-dev ${name}'.`);
} else if (
typeof version !== 'undefined'
&& valid(version)
&& !satisfies(installedVersion, version)
) {
consoleWarn(
`Version '${installedVersion}' of dev dependency '${name} does not satisfy constraint '${version}'.`,
);
if (
(typeof packageJson.dependencies === 'undefined' || typeof packageJson.dependencies[devName] === 'undefined')
&& (
typeof packageJson.devDependencies === 'undefined'
|| typeof packageJson.devDependencies[devName] === 'undefined'
)
) {
consoleWarn(`Dev dependency '${devName}' is missing.
Please install with 'npm install --save-exact --save-dev ${devName}'.`);
} else if (
typeof devVersion !== 'undefined'
&& typeof valid(devVersion) === 'string'
&& !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}`;
if (existsSync(fileToCheck)) {
const configFile = JSON.parse(readFileSync(fileToCheck).toString());
const configFile = JSON.parse(readFileSync(fileToCheck)
.toString());
const configFileExtended = (
typeof configFile.extends === 'string'
@@ -190,7 +285,7 @@ export function checkConfigurationFilesAreExtended(path: string): void {
consoleWarn(`File '${fileToCheck}' should extend '${expectedPath}'!
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;
// copy needed files
rules.files.forEach((file) => {
for (let file of rules.files) {
let destinationFile = file;
// remove templates directory for destination files
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}`);
}
@@ -225,7 +322,8 @@ export function checkNeededFiles(rules: Rules, path: string, replaceFlag: boolea
copyFileSync(source, destination);
consoleInfo(`Copied file '${source}' to '${destination}'.`);
} else if (destinationFile === '.npmignore') {
const npmIgnore = readFileSync(destination).toString();
const npmIgnore = readFileSync(destination)
.toString();
const ignoredPatterns = npmIgnore.split('\n');
@@ -256,7 +354,7 @@ https://gitlab.com/openstapps/configuration/issues/11`);
suggestOverwrite = true;
}
}
});
}
return suggestOverwrite;
}
@@ -267,7 +365,7 @@ https://gitlab.com/openstapps/configuration/issues/11`);
* @param rules Rules for check
* @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
if (rules.licenses.indexOf(packageJson.license) === -1) {
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
* @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 suggestOverwrite = false;
// 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) {
// add NYC configuration
packageJson.nyc = rules.nycConfiguration;
@@ -313,7 +413,7 @@ export function checkNYCConfiguration(rules: Rules, packageJson: any, replaceFla
* @param replaceFlag Whether or not to replace scripts
* @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;
// check if scripts is a map
@@ -323,7 +423,11 @@ export function checkScripts(rules: Rules, packageJson: any, replaceFlag: boolea
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];
// 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:
'${rules.scripts[scriptName].replace('\n', '\\n')}'.`);
}
});
}
return packageJsonChanged;
}
@@ -348,25 +452,26 @@ export function checkScripts(rules: Rules, packageJson: any, replaceFlag: boolea
* @param path Path to directory
* @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`);
for (let author of execBuffer.toString().split('\n')) {
author = author.trim();
for (let person of execBuffer.toString()
.split('\n')) {
person = person.trim();
if (author === '') {
if (person === '') {
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;
}));
|| (typeof packageJson.author === 'string' && packageJson.author.indexOf(person) >= 0)
|| (Array.isArray(packageJson.contributors) && packageJson.contributors.findIndex((contributor) => {
return typeof contributor === 'string' && contributor.indexOf(person) >= 0;
}) >= 0);
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])) {
const completeEntry: any = {};
const completeEntry: { [k: string]: string | object; } = {};
completeEntry[entry] = rules.ciConfig[entry];
consoleWarn(`Entry '${entry}' in '${pathToCiConfig}' is incorrect. Expected value is:
${stringify(completeEntry)}`);
@@ -428,11 +533,13 @@ export function checkCopyrightYears(path: PathLike, subDir: PathLike): void {
.split('\n')
.map((date) => parseInt(date.split('-')[0], 10))
.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;
}
seen.push(year);
return true;
})
.sort();
@@ -440,13 +547,16 @@ export function checkCopyrightYears(path: PathLike, subDir: PathLike): void {
const fileStats = lstatSync(fileSystemObjectPath);
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) {
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];
}
}
@@ -454,7 +564,8 @@ export function checkCopyrightYears(path: PathLike, subDir: PathLike): void {
if (copyrightYearsString === '') {
consoleWarn(`Copyright line for file '${fileSystemObjectPath}' could not be found!`);
} else {
const copyrightYearsWithIntervals = copyrightYearsString.split(',').map((year) => year.trim());
const copyrightYearsWithIntervals = copyrightYearsString.split(',')
.map((year) => year.trim());
const copyrightYears: number[] = [];
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
*/
export function getConfiguration(packageJson: any): Configuration {
export function getConfiguration(packageJson: PackageJSON): Configuration {
const defaultConfiguration: Configuration = {
forPackaging: true,
hasCli: true,
@@ -569,7 +680,7 @@ export function getRules(configuration: Configuration): Rules {
// expected scripts
const scripts: { [k: string]: string; } = {
/* 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',
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
const ciConfig: { [k: string]: any; } = {
const ciConfig = {
/* tslint:disable:object-literal-sort-keys */
image: 'registry.gitlab.com/openstapps/projectmanagement/node',
cache: {
@@ -660,6 +771,8 @@ export function getRules(configuration: Configuration): Rules {
};
for (const ignoreCiEntry of configuration.ignoreCiEntries) {
// tslint:disable-next-line:ban-ts-ignore
// @ts-ignore
delete ciConfig[ignoreCiEntry];
}