feat: transition from TSLint to ESLint

This commit is contained in:
Thea Schöbl
2022-06-01 12:00:53 +00:00
parent 73501e0fa3
commit 9430e10387
8 changed files with 1371 additions and 564 deletions

2
.eslintignore Normal file
View File

@@ -0,0 +1,2 @@
resources
openapi

3
.eslintrc.json Normal file
View File

@@ -0,0 +1,3 @@
{
"extends": "@openstapps"
}

1320
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,7 @@
"version": "0.29.1", "version": "0.29.1",
"description": "A collection of configuration base files for StApps projects.", "description": "A collection of configuration base files for StApps projects.",
"scripts": { "scripts": {
"build": "npm run tslint && npm run compile", "build": "npm run lint && npm run compile",
"compile": "rimraf lib && tsc --outDir lib && prepend lib/cli.js '#!/usr/bin/env node\n'", "compile": "rimraf lib && tsc --outDir lib && prepend lib/cli.js '#!/usr/bin/env node\n'",
"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'",
"documentation": "typedoc --out docs --readme README.md --includeVersion --listInvalidSymbolLinks --entryPointStrategy expand src", "documentation": "typedoc --out docs --readme README.md --includeVersion --listInvalidSymbolLinks --entryPointStrategy expand src",
@@ -12,7 +12,7 @@
"preversion": "npm run prepublishOnly", "preversion": "npm run prepublishOnly",
"push": "git push && git push origin \"v$npm_package_version\"", "push": "git push && git push origin \"v$npm_package_version\"",
"test": "echo \"Error: no test specified\" && exit 1", "test": "echo \"Error: no test specified\" && exit 1",
"tslint": "tslint -p tsconfig.json -c tslint.json 'src/**/*.ts'" "lint": "eslint -c .eslintrc.json --ignore-path .eslintignore --ext .ts src/"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@@ -22,7 +22,8 @@
"contributors": [ "contributors": [
"Anselm Rochus Stordeur", "Anselm Rochus Stordeur",
"Jovan Krunic", "Jovan Krunic",
"Rainer Killinger <mail-openstapps@killinger.co>" "Rainer Killinger <mail-openstapps@killinger.co>",
"Thea Schöbl"
], ],
"license": "GPL-3.0-only", "license": "GPL-3.0-only",
"dependencies": { "dependencies": {
@@ -32,16 +33,23 @@
"chalk": "4.1.2", "chalk": "4.1.2",
"commander": "9.2.0", "commander": "9.2.0",
"semver": "7.3.7", "semver": "7.3.7",
"tslint": "6.1.3",
"tslint-eslint-rules": "5.4.0",
"yaml": "1.10.2" "yaml": "1.10.2"
}, },
"devDependencies": { "devDependencies": {
"@openstapps/eslint-config": "0.0.3",
"conventional-changelog-cli": "2.2.2", "conventional-changelog-cli": "2.2.2",
"prepend-file-cli": "1.0.6", "prepend-file-cli": "1.0.6",
"rimraf": "3.0.2", "rimraf": "3.0.2",
"typedoc": "0.22.15", "typedoc": "0.22.15",
"typescript": "4.4.4" "typescript": "4.4.4",
"eslint": "8.14.0",
"eslint-config-prettier": "8.5.0",
"eslint-plugin-jsdoc": "39.2.8",
"eslint-plugin-prettier": "4.0.0",
"eslint-plugin-unicorn": "42.0.0",
"prettier": "2.6.2",
"@typescript-eslint/eslint-plugin": "5.20.0",
"@typescript-eslint/parser": "5.20.0"
}, },
"peerDependencies": { "peerDependencies": {
"typescript": ">=4.4.4" "typescript": ">=4.4.4"

View File

@@ -14,7 +14,7 @@
*/ */
import {Command} from 'commander'; import {Command} from 'commander';
import {existsSync, readFileSync, writeFileSync} from 'fs'; import {existsSync, readFileSync, writeFileSync} from 'fs';
import {resolve} from 'path'; import path from 'path';
import {cwd} from 'process'; import {cwd} from 'process';
import { import {
checkCIConfig, checkCIConfig,
@@ -39,42 +39,42 @@ const currentWorkingDirectory = cwd();
const commander = new Command('openstapps-configuration'); const commander = new Command('openstapps-configuration');
// configure commander // configure commander
commander // eslint-disable-next-line unicorn/prefer-module
.version(JSON.parse( commander.version(JSON.parse(readFileSync(path.resolve(__dirname, '..', 'package.json')).toString()).version);
readFileSync(resolve(__dirname, '..', 'package.json'))
.toString(),
).version);
commander commander
.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);
// make path absolute // make path absolute
const path = resolve(commander.opts().path); const projectPath = path.resolve(commander.opts().path);
// check for existing package.json in provided path // check for existing package.json in provided path
if (!existsSync(resolve(path, 'package.json'))) { if (!existsSync(path.resolve(projectPath, 'package.json'))) {
throw new Error(`No 'package.json' in '${path}'.`); throw new Error(`No 'package.json' in '${projectPath}'.`);
} }
// path to examined package.json // path to examined package.json
const packageJsonPath = resolve(path, 'package.json'); const packageJsonPath = path.resolve(projectPath, 'package.json');
// read package.json in provided path // read package.json in provided path
const packageJson = JSON.parse(readFileSync(packageJsonPath) const packageJson = JSON.parse(readFileSync(packageJsonPath).toString());
.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') {
consoleInfo('I\'m not going to check myself!'); consoleInfo("I'm not going to check myself!");
process.exit(0); process.exit(0);
} }
// whether or not the contents of the package.json were changed // whether the contents of the package.json were changed
let packageJsonChanged = false; let packageJsonChanged = false;
// whether or not to suggest an overwrite // whether to suggest an overwrite
let suggestOverwrite = false; let suggestOverwrite = false;
const configuration = getConfiguration(packageJson); const configuration = getConfiguration(packageJson);
@@ -85,9 +85,9 @@ checkDependencies(rules, packageJson);
checkLicenses(rules, packageJson); checkLicenses(rules, packageJson);
checkConfigurationFilesAreExtended(path); checkConfigurationFilesAreExtended(projectPath);
suggestOverwrite = suggestOverwrite || checkNeededFiles(rules, path, commander.opts().replace); suggestOverwrite = suggestOverwrite || checkNeededFiles(rules, projectPath, commander.opts().replace);
const checkedNYCConfiguration = checkNYCConfiguration(rules, packageJson, commander.opts().replace); const checkedNYCConfiguration = checkNYCConfiguration(rules, packageJson, commander.opts().replace);
@@ -96,16 +96,17 @@ suggestOverwrite = suggestOverwrite || checkedNYCConfiguration[1];
packageJsonChanged = packageJsonChanged || checkScripts(rules, packageJson, commander.opts().replace); packageJsonChanged = packageJsonChanged || checkScripts(rules, packageJson, commander.opts().replace);
checkContributors(path, packageJson); checkContributors(projectPath, packageJson);
checkCIConfig(rules, path); checkCIConfig(rules, projectPath);
checkCopyrightYears(path, 'src'); checkCopyrightYears(projectPath, 'src');
const indentation = 2; const indentation = 2;
if (packageJsonChanged) { if (packageJsonChanged) {
writeFileSync(resolve(path, 'package.json'), JSON.stringify(packageJson, null, indentation)); // eslint-disable-next-line unicorn/no-null
writeFileSync(path.resolve(projectPath, 'package.json'), JSON.stringify(packageJson, null, indentation));
consoleLog(`Changes were written to '${packageJsonPath}'.`); consoleLog(`Changes were written to '${packageJsonPath}'.`);
} }
@@ -114,4 +115,4 @@ if (suggestOverwrite) {
npm run check-configuration -- -r`); npm run check-configuration -- -r`);
} }
consoleLog(`Done checking the configuration in '${path}'.`); consoleLog(`Done checking the configuration in '${projectPath}'.`);

View File

@@ -1,3 +1,4 @@
/* eslint-disable unicorn/prefer-module */
/* /*
* Copyright (C) 2018, 2019 StApps * Copyright (C) 2018, 2019 StApps
* This program is free software: you can redistribute it and/or modify it * This program is free software: you can redistribute it and/or modify it
@@ -15,7 +16,7 @@
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';
import {join, resolve, sep} from 'path'; import path from 'path';
import {satisfies, valid} from 'semver'; import {satisfies, valid} from 'semver';
import {isDeepStrictEqual} from 'util'; import {isDeepStrictEqual} from 'util';
import {parse, stringify} from 'yaml'; import {parse, stringify} from 'yaml';
@@ -128,7 +129,7 @@ export interface Rules {
/** /**
* Expected CI config * Expected CI config
*/ */
ciConfig: { [k: string]: string | object; }; ciConfig: {[k: string]: string | object};
/** /**
* Expected dependencies * Expected dependencies
*/ */
@@ -152,48 +153,48 @@ export interface Rules {
/** /**
* Expected scripts * Expected scripts
*/ */
scripts: { [k: string]: string; }; scripts: {[k: string]: string};
} }
/** /**
* Wrapper for console.info that outputs every argument in cyan * Wrapper for console.info that outputs every argument in cyan
* *
* @param args Arguments to output * @param arguments_ Arguments to output
*/ */
export function consoleInfo(...args: string[]): void { export function consoleInfo(...arguments_: string[]): void {
args.forEach((arg) => { for (const argument of arguments_) {
/* tslint:disable-next-line:no-console */ /* tslint:disable-next-line:no-console */
console.info(`\n${chalk.cyan(arg)}`); console.info(`\n${chalk.cyan(argument)}`);
}); }
} }
/** /**
* Wrapper for console.warn that outputs every argument in red * Wrapper for console.warn that outputs every argument in red
* *
* @param args Arguments to output * @param arguments_ Arguments to output
*/ */
export function consoleWarn(...args: string[]): void { export function consoleWarn(...arguments_: string[]): void {
args.forEach((arg) => { for (const argument of arguments_) {
const lines = arg.split('\n'); const lines = argument.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);
} }
}); }
} }
/** /**
* Wrapper for console.log that outputs every argument in green * Wrapper for console.log that outputs every argument in green
* *
* @param args Arguments to output * @param arguments_ Arguments to output
*/ */
export function consoleLog(...args: string[]): void { export function consoleLog(...arguments_: string[]): void {
args.forEach((arg) => { for (const argument of arguments_) {
/* 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(argument)}`);
}); }
} }
/** /**
@@ -208,13 +209,16 @@ export function checkDependencies(rules: Rules, packageJson: PackageJSON): void
const [name, version] = dependency.split(':'); const [name, version] = dependency.split(':');
const installedVersion = packageJson.dependencies[name]; 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' &&
&& typeof valid(version) === 'string' 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}'.`,
@@ -224,32 +228,31 @@ Please install with 'npm install --save-exact ${name}'.`);
} }
if (typeof packageJson.devDependencies === 'object') { if (typeof packageJson.devDependencies === 'object') {
for (const devDependency of rules.devDependencies) { for (const developmentDependency of rules.devDependencies) {
const [devName, devVersion] = devDependency.split(':'); const [developmentName, developmentVersion] = developmentDependency.split(':');
let installedVersion = packageJson.devDependencies[devName]; let installedVersion = packageJson.devDependencies[developmentName];
if (typeof packageJson.dependencies === 'object') { if (typeof packageJson.dependencies === 'object') {
const [name] = devDependency.split(':'); const [name] = developmentDependency.split(':');
if (typeof packageJson.devDependencies[name] === 'string') { if (typeof packageJson.devDependencies[name] === 'string') {
installedVersion = packageJson.dependencies[name]; installedVersion = packageJson.dependencies[name];
} }
} }
if ( if (
(typeof packageJson.dependencies === 'undefined' || typeof packageJson.dependencies[devName] === 'undefined') (typeof packageJson.dependencies === 'undefined' ||
&& ( typeof packageJson.dependencies[developmentName] === 'undefined') &&
typeof packageJson.devDependencies === 'undefined' (typeof packageJson.devDependencies === 'undefined' ||
|| typeof packageJson.devDependencies[devName] === 'undefined' typeof packageJson.devDependencies[developmentName] === 'undefined')
)
) { ) {
consoleWarn(`Dev dependency '${devName}' is missing. consoleWarn(`Dev dependency '${developmentName}' is missing.
Please install with 'npm install --save-exact --save-dev ${devName}'.`); Please install with 'npm install --save-exact --save-dev ${developmentName}'.`);
} else if ( } else if (
typeof devVersion !== 'undefined' typeof developmentVersion !== 'undefined' &&
&& typeof valid(devVersion) === 'string' typeof valid(developmentVersion) === 'string' &&
&& !satisfies(installedVersion, devVersion) !satisfies(installedVersion, developmentVersion)
) { ) {
consoleWarn( consoleWarn(
`Version '${installedVersion}' of dev dependency '${devName} does not satisfy constraint '${devVersion}'.`, `Version '${installedVersion}' of dev dependency '${developmentName} does not satisfy constraint '${developmentVersion}'.`,
); );
} }
} }
@@ -259,47 +262,42 @@ Please install with 'npm install --save-exact --save-dev ${devName}'.`);
/** /**
* Check that configuration files are extended * Check that configuration files are extended
* *
* @param path Path, where configuration files are located * @param configPath Path, where configuration files are located
*/ */
export function checkConfigurationFilesAreExtended(path: string): void { export function checkConfigurationFilesAreExtended(configPath: string): void {
// check if configuration files are extended // check if configuration files are extended
['tsconfig.json', 'tslint.json'].forEach((file) => { for (const file of ['tsconfig.json', 'eslintrc.json']) {
const fileToCheck = resolve(path, file); const fileToCheck = path.resolve(configPath, file);
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) const configFile = JSON.parse(readFileSync(fileToCheck).toString());
.toString());
const configFileExtended = ( const configFileExtended =
typeof configFile.extends === 'string' (typeof configFile.extends === 'string' && configFile.extends === expectedPath) ||
&& configFile.extends === expectedPath (file === 'eslintrc.json' &&
) Array.isArray(configFile.extends) &&
|| ( configFile.extends.includes('@openstapps'));
file === 'tslint.json'
&& Array.isArray(configFile.extends)
&& configFile.extends.indexOf(expectedPath) >= 0
);
if (!configFileExtended) { if (!configFileExtended) {
consoleWarn(`File '${fileToCheck}' should extend '${expectedPath}'! consoleWarn(`File '${fileToCheck}' should extend '${expectedPath}'!
Example: Example:
${readFileSync(resolve(__dirname, '..', 'templates', `template-${file}`))}`); ${readFileSync(path.resolve(__dirname, '..', 'templates', `template-${file}`))}`);
} }
} }
}); }
} }
/** /**
* Check needed files * Check needed files
* *
* @param rules Rules for check * @param rules Rules for check
* @param path Path to files to check * @param filePath Path to files to check
* @param replaceFlag Whether or not to replace files * @param replaceFlag Whether or not to replace files
* @return Whether or not overwrite is suggested * @returns Whether or not overwrite is suggested
*/ */
export function checkNeededFiles(rules: Rules, path: string, replaceFlag: boolean): boolean { export function checkNeededFiles(rules: Rules, filePath: string, replaceFlag: boolean): boolean {
let suggestOverwrite = false; let suggestOverwrite = false;
// copy needed files // copy needed files
@@ -308,27 +306,25 @@ export function checkNeededFiles(rules: Rules, path: string, replaceFlag: boolea
// 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) destinationFile = destinationFile.split(path.sep).slice(1).join(path.sep);
.slice(1) file = path.join('templates', `template-${destinationFile}`);
.join(sep);
file = join('templates', `template-${destinationFile}`);
} }
const source = resolve(__dirname, '..', file); const source = path.resolve(__dirname, '..', file);
const destination = resolve(path, destinationFile); const destination = path.resolve(filePath, destinationFile);
// check if file exists or replace flag is set // check if file exists or replace flag is set
if (!existsSync(destination) || replaceFlag) { if (!existsSync(destination) || replaceFlag) {
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) const npmIgnore = readFileSync(destination).toString();
.toString();
const ignoredPatterns = npmIgnore.split('\n'); const ignoredPatterns = npmIgnore.split('\n');
let ignoresEverything = false; let ignoresEverything = false;
let unignoresDocs = false; // eslint-disable-next-line unicorn/prevent-abbreviations
let includeDocs = false;
for (const ignoredPattern of ignoredPatterns) { for (const ignoredPattern of ignoredPatterns) {
if (ignoredPattern === '/*') { if (ignoredPattern === '/*') {
@@ -336,7 +332,7 @@ export function checkNeededFiles(rules: Rules, path: string, replaceFlag: boolea
} }
if (ignoredPattern === '!docs') { if (ignoredPattern === '!docs') {
unignoresDocs = true; includeDocs = true;
} }
} }
@@ -346,7 +342,7 @@ export function checkNeededFiles(rules: Rules, path: string, replaceFlag: boolea
suggestOverwrite = true; suggestOverwrite = true;
} }
if (unignoresDocs) { if (includeDocs) {
consoleWarn(`'.npmignore' contains '!docs' and thus the package will contain the documentation. consoleWarn(`'.npmignore' contains '!docs' and thus the package will contain the documentation.
Consider creating a CI job to publish those files, rather than committing this folder to the npm repo. Consider creating a CI job to publish those files, rather than committing this folder to the npm repo.
Please double check that this is desired behavior since the docs can become huge: Please double check that this is desired behavior since the docs can become huge:
@@ -368,7 +364,7 @@ https://gitlab.com/openstapps/configuration/issues/11`);
*/ */
export function checkLicenses(rules: Rules, packageJson: PackageJSON): 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.includes(packageJson.license)) {
consoleWarn(`License should be one of '${rules.licenses.join(', ')}'!`); consoleWarn(`License should be one of '${rules.licenses.join(', ')}'!`);
} }
} }
@@ -379,16 +375,21 @@ export function checkLicenses(rules: Rules, packageJson: PackageJSON): void {
* @param rules Rules for check * @param rules Rules for check
* @param packageJson package.json to check NYC configuration in * @param packageJson package.json to check NYC configuration in
* @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 * @returns Whether or not package.json was changed and if overwrite is suggested
*/ */
export function checkNYCConfiguration(rules: Rules, packageJson: PackageJSON, replaceFlag: boolean) export function checkNYCConfiguration(
: [boolean, boolean] { 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) if (
.indexOf('nyc') >= 0) { typeof packageJson.devDependencies === 'object' &&
Object.keys(packageJson.devDependencies).includes('nyc')
) {
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;
@@ -397,7 +398,9 @@ export function checkNYCConfiguration(rules: Rules, packageJson: PackageJSON, re
consoleLog(`Added NYC configuration in to 'package.json'.`); consoleLog(`Added NYC configuration in to 'package.json'.`);
} else if (!isDeepStrictEqual(packageJson.nyc, rules.nycConfiguration)) { } else if (!isDeepStrictEqual(packageJson.nyc, rules.nycConfiguration)) {
consoleInfo(`NYC configuration in 'package.json' differs from the proposed one. Please check manually.`); consoleInfo(
`NYC configuration in 'package.json' differs from the proposed one. Please check manually.`,
);
suggestOverwrite = true; suggestOverwrite = true;
} }
@@ -412,7 +415,7 @@ export function checkNYCConfiguration(rules: Rules, packageJson: PackageJSON, re
* @param rules Rules for check * @param rules Rules for check
* @param packageJson package.json to check scripts in * @param packageJson package.json to check scripts in
* @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 * @returns Whether or not the package.json was changed
*/ */
export function checkScripts(rules: Rules, packageJson: PackageJSON, replaceFlag: boolean): boolean { export function checkScripts(rules: Rules, packageJson: PackageJSON, replaceFlag: boolean): boolean {
let packageJsonChanged = false; let packageJsonChanged = false;
@@ -454,9 +457,10 @@ export function checkScripts(rules: Rules, packageJson: PackageJSON, replaceFlag
* @param packageJson package.json to check contributors in * @param packageJson package.json to check contributors in
*/ */
export function checkContributors(path: PathLike, packageJson: PackageJSON): 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(
for (let person of execBuffer.toString() `git --git-dir=${path}/.git --work-tree=${path} log --format=\'%aN\' | sort -u`,
.split('\n')) { );
for (let person of execBuffer.toString().split('\n')) {
person = person.trim(); person = person.trim();
if (person === '') { if (person === '') {
@@ -465,11 +469,13 @@ export function checkContributors(path: PathLike, packageJson: PackageJSON): voi
let authorIsAttributed = false; let authorIsAttributed = false;
authorIsAttributed = authorIsAttributed authorIsAttributed =
|| (typeof packageJson.author === 'string' && packageJson.author.indexOf(person) >= 0) authorIsAttributed ||
|| (Array.isArray(packageJson.contributors) && packageJson.contributors.findIndex((contributor) => { (typeof packageJson.author === 'string' && packageJson.author.includes(person)) ||
return typeof contributor === 'string' && contributor.indexOf(person) >= 0; (Array.isArray(packageJson.contributors) &&
}) >= 0); packageJson.contributors.findIndex(contributor => {
return typeof contributor === 'string' && contributor.includes(person);
}) >= 0);
if (!authorIsAttributed) { if (!authorIsAttributed) {
consoleWarn(`'${person}' should be attributed as author or contributor.`); consoleWarn(`'${person}' should be attributed as author or contributor.`);
@@ -481,23 +487,20 @@ export function checkContributors(path: PathLike, packageJson: PackageJSON): voi
* Check CI config * Check CI config
* *
* @param rules Rules for check * @param rules Rules for check
* @param path Path to CI config * @param configPath Path to CI config
*/ */
export function checkCIConfig(rules: Rules, path: string): void { export function checkCIConfig(rules: Rules, configPath: string): void {
const pathToCiConfig = resolve(path, '.gitlab-ci.yml'); const pathToCiConfig = path.resolve(configPath, '.gitlab-ci.yml');
// check CI config if it exists // check CI config if it exists
if (existsSync(pathToCiConfig)) { if (existsSync(pathToCiConfig)) {
// read CI config // read CI config
const content = readFileSync(pathToCiConfig) const content = readFileSync(pathToCiConfig).toString();
.toString();
let ciConfigWithoutTemplates = ''; let ciConfigWithoutTemplates = '';
for (const line of content.split('\n')) { for (const line of content.split('\n')) {
const match = line const match = line.trim().match(/^<</);
.trim()
.match(/^<</);
if (match === null) { if (match === null) {
ciConfigWithoutTemplates += `${line}\n`; ciConfigWithoutTemplates += `${line}\n`;
@@ -514,7 +517,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: { [k: string]: string | object; } = {}; 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)}`);
@@ -531,26 +534,32 @@ ${stringify(rules.ciConfig)}`);
/** /**
* Check copyright years in files * Check copyright years in files
* *
* @param path Path to project root * @param projectPath Path to project root
* @param subDir Subordinated directory to examine * @param checkPathFragment Subordinated directory to examine
*/ */
export function checkCopyrightYears(path: PathLike, subDir: PathLike): void { export function checkCopyrightYears(projectPath: PathLike, checkPathFragment: PathLike): void {
const fileSystemObjects = readdirSync(resolve(path.toString(), subDir.toString())); const fileSystemObjects = readdirSync(path.resolve(projectPath.toString(), checkPathFragment.toString()));
for (const fileSystemObject of fileSystemObjects) { for (const fileSystemObject of fileSystemObjects) {
const fileSystemObjectPath = resolve(path.toString(), subDir.toString(), fileSystemObject); const fileSystemObjectPath = path.resolve(
projectPath.toString(),
checkPathFragment.toString(),
fileSystemObject,
);
// tslint:disable-next-line:max-line-length // tslint:disable-next-line:max-line-length
const execBuffer = execSync(`git --git-dir=${path}/.git --work-tree=${path} log --oneline --format='%cI' -- ${fileSystemObjectPath}`); const execBuffer = execSync(
`git --git-dir=${projectPath}/.git --work-tree=${projectPath} log --oneline --format='%cI' -- ${fileSystemObjectPath}`,
);
const seen: number[] = []; const seen: number[] = [];
let changedYears = execBuffer let changedYears = execBuffer
.toString() .toString()
.split('\n') .split('\n')
.map((date) => parseInt(date.split('-')[0], 10)) .map(date => Number.parseInt(date.split('-')[0], 10))
.filter((year) => { .filter(year => {
const stringYear = year.toString(); const stringYear = year.toString();
if (seen.indexOf(year) >= 0 || stringYear.match(/[0-9]{4}/) === null) { if (seen.includes(year) || stringYear.match(/[0-9]{4}/) === null) {
return false; return false;
} }
@@ -559,7 +568,7 @@ export function checkCopyrightYears(path: PathLike, subDir: PathLike): void {
return true; return true;
}) })
.sort(); .sort();
changedYears = [changedYears[0], changedYears[changedYears.length -1]]; changedYears = [changedYears[0], changedYears[changedYears.length - 1]];
changedYears = [...new Set(changedYears)].sort(); changedYears = [...new Set(changedYears)].sort();
const fileStats = lstatSync(fileSystemObjectPath); const fileStats = lstatSync(fileSystemObjectPath);
@@ -569,9 +578,7 @@ export function checkCopyrightYears(path: PathLike, subDir: PathLike): void {
continue; continue;
} }
const content = readFileSync(fileSystemObjectPath) const content = readFileSync(fileSystemObjectPath).toString().split('\n');
.toString()
.split('\n');
let copyrightYearsString = ''; let copyrightYearsString = '';
for (const line of content) { for (const line of content) {
@@ -587,16 +594,20 @@ 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('-') const copyrightYearsWithIntervals = copyrightYearsString.split('-').map(year => year.trim());
.map((year) => year.trim()); const copyrightYears: number[] = copyrightYearsWithIntervals
const copyrightYears: number[] = copyrightYearsWithIntervals.map(year => parseInt(year, 10)) .map(year => Number.parseInt(year, 10))
.sort(); .sort();
let copyrightYearNeedsUpdate = false; let copyrightYearNeedsUpdate = false;
if (typeof copyrightYears[0] !== 'undefined' && typeof changedYears[0] !== 'undefined') { if (typeof copyrightYears[0] !== 'undefined' && typeof changedYears[0] !== 'undefined') {
copyrightYearNeedsUpdate = copyrightYears[0] !== changedYears[0]; copyrightYearNeedsUpdate = copyrightYears[0] !== changedYears[0];
} }
if (typeof copyrightYears[1] !== 'undefined' && typeof changedYears[1] !== 'undefined' && !copyrightYearNeedsUpdate) { if (
typeof copyrightYears[1] !== 'undefined' &&
typeof changedYears[1] !== 'undefined' &&
!copyrightYearNeedsUpdate
) {
copyrightYearNeedsUpdate = copyrightYears[1] !== changedYears[1]; copyrightYearNeedsUpdate = copyrightYears[1] !== changedYears[1];
} }
if (copyrightYears.length !== changedYears.length) { if (copyrightYears.length !== changedYears.length) {
@@ -605,11 +616,16 @@ export function checkCopyrightYears(path: PathLike, subDir: PathLike): void {
if (copyrightYearNeedsUpdate) { if (copyrightYearNeedsUpdate) {
// tslint:disable-next-line:max-line-length // tslint:disable-next-line:max-line-length
consoleWarn(`File '${join(subDir.toString(), fileSystemObject)}' has to specify '${changedYears.join('-')}' as year(s) in the copyright line.`); consoleWarn(
`File '${path.join(
checkPathFragment.toString(),
fileSystemObject,
)}' has to specify '${changedYears.join('-')}' as year(s) in the copyright line.`,
);
} }
} }
} else if (fileStats.isDirectory()) { } else if (fileStats.isDirectory()) {
checkCopyrightYears(path, join(subDir.toString(), fileSystemObject)); checkCopyrightYears(projectPath, path.join(checkPathFragment.toString(), fileSystemObject));
} }
} }
} }
@@ -651,152 +667,114 @@ export function getRules(configuration: Configuration): Rules {
// expected dev dependencies // expected dev dependencies
const devDependencies = [ const devDependencies = [
'@typescript-eslint/eslint-plugin',
'@typescript-eslint/parser',
'@openstapps/eslint-config',
'conventional-changelog-cli', 'conventional-changelog-cli',
'tslint', 'eslint',
'typescript:3.8.3', 'eslint-config-prettier',
'eslint-plugin-jsdoc',
'eslint-plugin-prettier',
'eslint-plugin-unicorn',
'prettier',
'typescript:4.4.4',
]; ];
// files that need to be copied // files that need to be copied
const files = [ const files = [
'.editorconfig', '.editorconfig',
join('templates', '.gitignore'), '.eslintrc.json',
join('templates', 'tsconfig.json'), '.eslintignore',
join('templates', 'tslint.json'), path.join('templates', '.gitignore'),
path.join('templates', 'tsconfig.json'),
]; ];
// configuration for nyc to add to package.json // configuration for nyc to add to package.json
const nycConfiguration: NYCConfiguration = { const nycConfiguration: NYCConfiguration = {
all: true, 'all': true,
branches: 95, 'branches': 95,
'check-coverage': true, 'check-coverage': true,
exclude: [ 'exclude': ['src/cli.ts'],
'src/cli.ts', 'extension': ['.ts'],
], 'functions': 95,
extension: [ 'include': ['src'],
'.ts', 'lines': 95,
],
functions: 95,
include: [
'src',
],
lines: 95,
'per-file': true, 'per-file': true,
reporter: [ 'reporter': ['html', 'text-summary'],
'html', 'require': ['ts-node/register'],
'text-summary', 'statements': 95,
],
require: [
'ts-node/register',
],
statements: 95,
}; };
// 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',
postversion: 'npm run changelog', 'postversion': 'npm run changelog',
preversion: 'npm run prepublishOnly', 'preversion': 'npm run prepublishOnly',
push: 'git push && git push origin "v$npm_package_version"', 'push': 'git push && git push origin "v$npm_package_version"',
tslint: 'tslint -p tsconfig.json -c tslint.json \'src/**/*.ts\'', 'lint': 'eslint --ext .ts src/',
}; };
// list of expected licenses // list of expected licenses
const licenses = [ const licenses = ['AGPL-3.0-only', 'GPL-3.0-only'];
'AGPL-3.0-only',
'GPL-3.0-only',
];
// expected values in CI config // expected values in CI config
const ciConfig = { 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',
before_script: 'npm ci', 'before_script': 'npm ci',
build: { 'build': {
stage: 'build', stage: 'build',
script: [ script: ['npm run build'],
'npm run build',
],
artifacts: { artifacts: {
paths: [ paths: ['lib'],
'lib',
],
}, },
}, },
audit: { 'audit': {
allow_failure: true, allow_failure: true,
except: [ except: ['schedules'],
'schedules', script: ['npm audit'],
],
script: [
'npm audit',
],
stage: 'audit', stage: 'audit',
}, },
'scheduled-audit': { 'scheduled-audit': {
only: [ only: ['schedules'],
'schedules', script: ['npm audit --audit-level=high'],
],
script: [
'npm audit --audit-level=high',
],
stage: 'audit', stage: 'audit',
}, },
package: { 'package': {
dependencies: [ dependencies: ['build'],
'build', tags: ['secrecy'],
],
tags: [
'secrecy',
],
stage: 'publish', stage: 'publish',
script: [ script: ['echo "//registry.npmjs.org/:_authToken=$NPM_AUTH_TOKEN" > ~/.npmrc', 'npm publish'],
'echo "//registry.npmjs.org/:_authToken=$NPM_AUTH_TOKEN" > ~/.npmrc', only: ['/^v[0-9]+.[0-9]+.[0-9]+$/'],
'npm publish',
],
only: [
'/^v[0-9]+\.[0-9]+\.[0-9]+$/',
],
artifacts: { artifacts: {
paths: [ paths: ['lib'],
'lib',
],
}, },
}, },
pages: { 'pages': {
artifacts: { artifacts: {
'paths': [ paths: ['public'],
'public',
],
}, },
only: [ only: ['/^v[0-9]+\\.[0-9]+\\.[0-9]+$/'],
'/^v[0-9]+\\.[0-9]+\\.[0-9]+$/', script: ['npm run documentation', 'mv docs public'],
],
script: [
'npm run documentation',
'mv docs public',
],
stage: 'deploy', stage: 'deploy',
}, },
/* tslint:enable */ /* tslint:enable */
}; };
for (const ignoreCiEntry of configuration.ignoreCiEntries) { for (const ignoreCiEntry of configuration.ignoreCiEntries) {
// tslint:disable-next-line:ban-ts-ignore // @ts-expect-error can't be used to index
// @ts-ignore
delete ciConfig[ignoreCiEntry]; delete ciConfig[ignoreCiEntry];
} }
if (configuration.forPackaging) { if (configuration.forPackaging) {
scripts.prepublishOnly = 'npm ci && npm run build'; scripts.prepublishOnly = 'npm ci && npm run build';
files.push( files.push(path.join('templates', '.npmignore'));
join('templates', '.npmignore'),
);
} else { } else {
// tslint:disable-next-line:ban-ts-ignore // @ts-expect-error can't be used to index
// @ts-ignore
delete ciConfig[`package`]; delete ciConfig[`package`];
} }
@@ -805,20 +783,21 @@ export function getRules(configuration: Configuration): Rules {
} }
if (configuration.standardBuild || configuration.hasCli) { if (configuration.standardBuild || configuration.hasCli) {
scripts.build = 'npm run tslint && npm run compile'; scripts.build = 'npm run lint && npm run compile';
scripts.compile = 'rimraf lib && tsc'; scripts.compile = 'rimraf lib && tsc';
devDependencies.push('rimraf'); devDependencies.push('rimraf');
if (configuration.hasCli) { if (configuration.hasCli) {
devDependencies.push('prepend-file-cli'); devDependencies.push('prepend-file-cli');
scripts.compile += ' && prepend lib/cli.js \'#!/usr/bin/env node\n\''; scripts.compile += " && prepend lib/cli.js '#!/usr/bin/env node\n'";
} }
} }
if (configuration.standardDocumentation) { if (configuration.standardDocumentation) {
devDependencies.push('typedoc'); devDependencies.push('typedoc');
/* tslint:disable-next-line:max-line-length */ /* tslint:disable-next-line:max-line-length */
scripts.documentation = 'typedoc --includeDeclarations --mode modules --out docs --readme README.md --listInvalidSymbolLinks src'; scripts.documentation =
'typedoc --includeDeclarations --mode modules --out docs --readme README.md --listInvalidSymbolLinks --entryPointStrategy expand src';
} }
for (const ignoreScript of configuration.ignoreScripts) { for (const ignoreScript of configuration.ignoreScripts) {

View File

@@ -1,3 +0,0 @@
{
"extends": "./node_modules/@openstapps/configuration/tslint.json"
}

View File

@@ -1,143 +0,0 @@
{
"defaultSeverity": "error",
"extends": [
"tslint:recommended",
"tslint-eslint-rules"
],
"rules": {
"array-type": [
true,
"array-simple"
],
"arrow-return-shorthand": true,
"await-promise": true,
"ban-comma-operator": true,
"ban-ts-ignore": true,
"completed-docs": true,
"curly": true,
"encoding": true,
"file-header": true,
"file-name-casing": [
true,
"kebab-case"
],
"forin": true,
"indent": [
true,
"spaces",
2
],
"interface-name": [
true,
"never-prefix"
],
"linebreak-style": [
true,
"LF"
],
"max-classes-per-file": false,
"member-access": false,
"member-ordering": [
true,
{
"alphabetize": true,
"order": [
"private-static-field",
"protected-static-field",
"public-static-field",
"private-instance-field",
"protected-instance-field",
"public-instance-field",
"private-static-method",
"protected-static-method",
"public-static-method",
"constructor",
"private-instance-method",
"protected-instance-method",
"public-instance-method"
]
}
],
"newline-before-return": true,
"newline-per-chained-call": true,
"no-angle-bracket-type-assertion": true,
"no-any": true,
"no-boolean-literal-compare": true,
"no-conditional-assignment": true,
"no-construct": true,
"no-default-export": true,
"no-default-import": true,
"no-duplicate-super": true,
"no-floating-promises": true,
"no-implicit-dependencies": true,
"no-inferrable-types": true,
"no-magic-numbers": true,
"no-parameter-reassignment": true,
"no-redundant-jsdoc": true,
"no-reference": true,
"no-return-await": true,
"no-shadowed-variable": true,
"no-sparse-arrays": true,
"no-string-throw": true,
"no-trailing-whitespace": [
true,
"ignore-comments",
"ignore-jsdoc"
],
"object-curly-spacing": [
true,
"never"
],
"object-literal-key-quotes": false,
"object-literal-shorthand": false,
"one-variable-per-declaration": true,
"ordered-imports": [
true,
{
"import-sources-order": "case-insensitive",
"named-imports-order": "case-insensitive"
}
],
"prefer-const": true,
"prefer-for-of": true,
"prefer-function-over-method": true,
"prefer-object-spread": true,
"prefer-readonly": true,
"prefer-template": true,
"promise-function-async": true,
"quotemark": [
true,
"single",
"avoid-escape"
],
"semicolon": true,
"static-this": true,
"strict-boolean-expressions": true,
"trailing-comma": [
true,
{
"multiline": "always",
"singleline": "never"
}
],
"type-literal-delimiter": [
true,
{
"singleLine": "always"
}
],
"unnecessary-bind": true,
"unnecessary-else": true,
"variable-name": [
true,
"ban-keywords",
"check-format",
"allow-leading-underscore"
]
},
"linterOptions": {
"exclude": [
"../../../test/**"
]
}
}