From da2d7f6f91ccfc91d3560c1e380daa4282f1aeb5 Mon Sep 17 00:00:00 2001 From: Karl-Philipp Wulfert Date: Thu, 18 Apr 2019 13:05:46 +0200 Subject: [PATCH] feat: add configuration for configuration checks Fixes #13 --- README.md | 28 +--- package-lock.json | 182 +++++++++++----------- package.json | 10 +- src/cli.ts | 35 +++-- src/common.ts | 356 +++++++++++++++++++++++++++++++++++++++---- src/configuration.ts | 124 --------------- 6 files changed, 447 insertions(+), 288 deletions(-) delete mode 100644 src/configuration.ts diff --git a/README.md b/README.md index 3a21594d..1e83d287 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,11 @@ [![license)](https://img.shields.io/npm/l/@openstapps/configuration.svg?style=flat-square)](https://www.gnu.org/licenses/gpl-3.0.en.html) [![documentation](https://img.shields.io/badge/documentation-online-blue.svg?style=flat-square)](https://openstapps.gitlab.io/configuration) -A collection of configuration base files for StApps projects. +Checks your `@openstapps` project's configuration and automatically adjusts it to adhere to the suggested defaults. -Install it as a dev dependency in your project and it will automatically add a script in your `package.json` to execute it. +## Installation + +Install it as a dev dependency in your project and it will check your configuration and add a script in your `package.json` to check the configuration again easily afterwards. ```shell npm install --save-dev @openstapps/configuration @@ -21,24 +23,6 @@ npm install -g @openstapps/configuration openstapps-configuration --help ``` -## Checked files +## Configuration -If your project's root contains a `tsconfig.json` or a `tslint.json` they will be checked if they contain the extension of the default configurations. - -## Copied files - -The following files are automatically copied to your project's root directory if they do not exist yet. Use `-r, --replace` to replace existing files. - -| File | Purpose | -| --- | --- | -| `.editorconfig` | Configuration for your editor/IDE about basic settings for indentation and formatting | -| `.gitignore` | Configuration for Git about which files to ignore from versioning | -| `.npmignore` | Configuration for NPM about which files to exclude from packaging | -| `tsconfig.json` | Configuration for [TypeScript compiler](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html) | -| `tslint.json` | Configuration for [TSLint](https://palantir.github.io/tslint/usage/configuration/) | - -## Adjusted files - -Your project's `package.json` will be examined to determine whether or not you have `nyc` installed in your `devDependencies`. If it is installed the recommended configuration for `nyc` will be added to your `package.json` if it does not exist. - -A script called `check-configuration` will be added to your `package.json` to execute the configuration script manually again. +To configure how your project's configuration is checked add a property `"openstappsConfiguration"` to your `package.json` with all or some properties of the [configuration](https://openstapps.gitlab.io/configuration/interfaces/_src_common_.configuration.html); diff --git a/package-lock.json b/package-lock.json index 742059da..765b1da0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,24 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@babel/code-frame": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", + "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", + "requires": { + "@babel/highlight": "^7.0.0" + } + }, + "@babel/highlight": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", + "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + } + }, "@babel/runtime": { "version": "7.4.3", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.4.3.tgz", @@ -76,6 +94,11 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-10.14.4.tgz", "integrity": "sha512-DT25xX/YgyPKiHFOpNuANIQIVvYEwCWXgK2jYYwqgaMrYE6+tq+DtmMwlD3drl6DJbUwtlIDnn0d7tIn/EbXBg==" }, + "@types/semver": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-6.0.0.tgz", + "integrity": "sha512-OO0srjOGH99a4LUN2its3+r6CBYcplhJ466yLqs+zvAWgphCpS8hYZEZ797tRDP/QKcqTdb/YCN6ifASoAWkrQ==" + }, "@types/shelljs": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/@types/shelljs/-/shelljs-0.8.3.tgz", @@ -107,16 +130,6 @@ "integrity": "sha1-anmQQ3ynNtXhKI25K9MmbV9csqo=", "dev": true }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" - }, "arg": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.0.tgz", @@ -149,30 +162,6 @@ "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", "dev": true }, - "babel-code-frame": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", - "requires": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" - }, - "dependencies": { - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - } - } - }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -424,6 +413,14 @@ "semver": "^5.5.0", "split": "^1.0.0", "through2": "^2.0.0" + }, + "dependencies": { + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + } } }, "conventional-commits-filter": { @@ -817,6 +814,14 @@ "requires": { "meow": "^4.0.0", "semver": "^5.5.0" + }, + "dependencies": { + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + } } }, "gitconfiglocal": { @@ -848,9 +853,9 @@ "dev": true }, "handlebars": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.1.tgz", - "integrity": "sha512-3Zhi6C0euYZL5sM0Zcy7lInLXKQ+YLcF/olbN010mzGQ4XVm50JeyBnMqofHh696GrciGruC7kCcApPDJvVgwA==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.2.tgz", + "integrity": "sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw==", "dev": true, "requires": { "neo-async": "^2.6.0", @@ -859,14 +864,6 @@ "uglify-js": "^3.1.4" } }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "requires": { - "ansi-regex": "^2.0.0" - } - }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -971,14 +968,14 @@ "dev": true }, "js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "js-yaml": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.0.tgz", - "integrity": "sha512-pZZoSxcCYco+DIKBTimr67J6Hy+EYGZDY/HCWC+iAEA9h1ByhMXAIVUXMcMFpOCxQ/xjXmPI2MkDL5HRm5eFrQ==", + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", "requires": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -1178,6 +1175,14 @@ "resolve": "^1.10.0", "semver": "2 || 3 || 4 || 5", "validate-npm-package-license": "^3.0.1" + }, + "dependencies": { + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + } } }, "number-is-nan": { @@ -1446,9 +1451,9 @@ "dev": true }, "semver": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", - "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==" + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.0.0.tgz", + "integrity": "sha512-0UewU+9rFapKFnlbirLi3byoOuhrSsli/z/ihNnvM24vgF+8sNBiI1LZPBSH9wJKUwaUbw+s3hToDLCXkrghrQ==" }, "shelljs": { "version": "0.8.3", @@ -1474,9 +1479,9 @@ "dev": true }, "source-map-support": { - "version": "0.5.11", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.11.tgz", - "integrity": "sha512-//sajEx/fGL3iw6fltKMdPvy8kL3kJ2O3iuYlRoT3k9Kb4BjOoZ+BZzaNHeuaruSt+Kf3Zk9tnfAQg9/AJqUVQ==", + "version": "0.5.12", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.12.tgz", + "integrity": "sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ==", "dev": true, "requires": { "buffer-from": "^1.0.0", @@ -1547,14 +1552,6 @@ "safe-buffer": "~5.1.0" } }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "^2.0.0" - } - }, "strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -1567,11 +1564,6 @@ "integrity": "sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=", "dev": true }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" - }, "tempfile": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/tempfile/-/tempfile-1.1.1.tgz", @@ -1626,9 +1618,9 @@ "dev": true }, "ts-node": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.0.3.tgz", - "integrity": "sha512-2qayBA4vdtVRuDo11DEFSsD/SFsBXQBRZZhbRGSIkmYmVkWjULn/GGMdG10KVqkaGndljfaTD8dKjWgcejO8YA==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.1.0.tgz", + "integrity": "sha512-34jpuOrxDuf+O6iW1JpgTRDFynUZ1iEqtYruBqh35gICNjN8x+LpVcPAcwzLPi9VU6mdA3ym+x233nZmZp445A==", "dev": true, "requires": { "arg": "^4.1.0", @@ -1644,11 +1636,11 @@ "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==" }, "tslint": { - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.15.0.tgz", - "integrity": "sha512-6bIEujKR21/3nyeoX2uBnE8s+tMXCQXhqMmaIPJpHmXJoBJPTLcI7/VHRtUwMhnLVdwLqqY3zmd8Dxqa5CVdJA==", + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.16.0.tgz", + "integrity": "sha512-UxG2yNxJ5pgGwmMzPMYh/CCnCnh0HfPgtlVRDs1ykZklufFBL1ZoTlWFRz2NQjcoEiDoRp+JyT0lhBbbH/obyA==", "requires": { - "babel-code-frame": "^6.22.0", + "@babel/code-frame": "^7.0.0", "builtin-modules": "^1.1.1", "chalk": "^2.3.0", "commander": "^2.12.1", @@ -1661,6 +1653,13 @@ "semver": "^5.3.0", "tslib": "^1.8.0", "tsutils": "^2.29.0" + }, + "dependencies": { + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" + } } }, "tslint-eslint-rules": { @@ -1736,29 +1735,20 @@ "dev": true }, "typescript": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.4.2.tgz", - "integrity": "sha512-Og2Vn6Mk7JAuWA1hQdDQN/Ekm/SchX80VzLhjKN9ETYrIepBFAd8PkOdOTK2nKt0FCkmMZKBJvQ1dV1gIxPu/A==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.4.3.tgz", + "integrity": "sha512-FFgHdPt4T/duxx6Ndf7hwgMZZjZpB+U0nMNGVCYPq0rEzWKjEDobm4J6yb3CS7naZ0yURFqdw9Gwc7UOh/P9oQ==", "dev": true }, "uglify-js": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.5.2.tgz", - "integrity": "sha512-imog1WIsi9Yb56yRt5TfYVxGmnWs3WSGU73ieSOlMVFwhJCA9W8fqFFMMj4kgDqiS/80LGdsYnWL7O9UcjEBlg==", + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.5.4.tgz", + "integrity": "sha512-GpKo28q/7Bm5BcX9vOu4S46FwisbPbAmkkqPnGIpKvKTM96I85N6XHQV+k4I6FA2wxgLhcsSyHoNhzucwCflvA==", "dev": true, "optional": true, "requires": { - "commander": "~2.19.0", + "commander": "~2.20.0", "source-map": "~0.6.1" - }, - "dependencies": { - "commander": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", - "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==", - "dev": true, - "optional": true - } } }, "universalify": { @@ -1815,9 +1805,9 @@ } }, "yn": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.0.0.tgz", - "integrity": "sha512-+Wo/p5VRfxUgBUGy2j/6KX2mj9AYJWOHuhMjMcbBFc3y54o9/4buK1ksBvuiK01C3kby8DH9lSmJdSxw+4G/2Q==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.0.tgz", + "integrity": "sha512-kKfnnYkbTfrAdd0xICNFw7Atm8nKpLcLv9AZGEt+kczL/WQVai4e2V6ZN8U/O+iI6WrNuJjNNOyu4zfhl9D3Hg==", "dev": true } } diff --git a/package.json b/package.json index ce69acc5..bb6e82f9 100644 --- a/package.json +++ b/package.json @@ -24,10 +24,12 @@ "license": "GPL-3.0-only", "dependencies": { "@types/node": "10.14.4", + "@types/semver": "6.0.0", "@types/yaml": "1.0.2", "chalk": "2.4.2", "commander": "2.20.0", - "tslint": "5.15.0", + "semver": "6.0.0", + "tslint": "5.16.0", "tslint-eslint-rules": "5.4.0", "yaml": "1.5.0" }, @@ -35,12 +37,12 @@ "conventional-changelog-cli": "2.0.12", "prepend-file-cli": "1.0.6", "rimraf": "2.6.3", - "ts-node": "8.0.3", + "ts-node": "8.1.0", "typedoc": "0.14.2", - "typescript": "3.4.2" + "typescript": "3.4.3" }, "peerDependencies": { - "typescript": "^3.4.2" + "typescript": "^3.4.0" }, "bin": { "openstapps-configuration": "lib/cli.js" diff --git a/src/cli.ts b/src/cli.ts index f4372501..14373664 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -20,12 +20,15 @@ import { checkCIConfig, checkConfigurationFilesAreExtended, checkContributors, + checkDependencies, checkLicenses, checkNeededFiles, checkNYCConfiguration, checkScripts, consoleInfo, consoleLog, + getConfiguration, + getRules, } from './common'; // current working directory @@ -43,18 +46,12 @@ const path = resolve(commander.path); // check for existing package.json in provided path if (!existsSync(resolve(path, 'package.json'))) { - throw new Error(`No package.json in "${path}".`); + throw new Error(`No 'package.json' in '${path}'.`); } // path to examined package.json const packageJsonPath = resolve(path, 'package.json'); -// 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()); @@ -64,26 +61,38 @@ if (packageJson.name === '@openstapps/configuration') { process.exit(0); } -checkLicenses(packageJson); +// whether or not the contents of the package.json were changed +let packageJsonChanged = false; + +// whether or not to suggest an overwrite +let suggestOverwrite = false; + +const configuration = getConfiguration(packageJson); + +const rules = getRules(configuration); + +checkDependencies(rules, packageJson); + +checkLicenses(rules, packageJson); checkConfigurationFilesAreExtended(path); -suggestOverwrite = suggestOverwrite || checkNeededFiles(path, commander.replace); +suggestOverwrite = suggestOverwrite || checkNeededFiles(rules, path, commander.replace); -const checkedNYCConfiguration = checkNYCConfiguration(packageJson, commander.replace); +const checkedNYCConfiguration = checkNYCConfiguration(rules, packageJson, commander.replace); packageJsonChanged = packageJsonChanged || checkedNYCConfiguration[0]; suggestOverwrite = suggestOverwrite || checkedNYCConfiguration[1]; -packageJsonChanged = packageJsonChanged || checkScripts(packageJson, commander.replace); +packageJsonChanged = packageJsonChanged || checkScripts(rules, packageJson, commander.replace); checkContributors(packageJson); -checkCIConfig(path); +checkCIConfig(rules, path); if (packageJsonChanged) { writeFileSync(resolve(path, 'package.json'), JSON.stringify(packageJson, null, 2)); - consoleLog(`Changes were written to "${packageJsonPath}".`); + consoleLog(`Changes were written to '${packageJsonPath}'.`); } if (suggestOverwrite) { diff --git a/src/common.ts b/src/common.ts index 276ea620..a2ea2173 100644 --- a/src/common.ts +++ b/src/common.ts @@ -2,9 +2,73 @@ import chalk from 'chalk'; import {execSync} from 'child_process'; import {copyFileSync, existsSync, readFileSync} from 'fs'; import {join, resolve, sep} from 'path'; +import {satisfies, valid} from 'semver'; import {isDeepStrictEqual} from 'util'; import {parse, stringify} from 'yaml'; -import {EXPECTED_CI_CONFIG, EXPECTED_LICENSES, NEEDED_FILES, NYC_CONFIGURATION, SCRIPTS} from './configuration'; + +/** + * Configuration for the configuration check + */ +export interface Configuration { + /** + * Whether or not the project is meant to be packaged + */ + forPackaging: boolean; + /** + * Whether or not the project has a CLI + */ + hasCli: boolean; + /** + * A list of script names to ignore while checking + */ + ignoreScripts: string[]; + /** + * Whether or not the project is meant to be executed server side + */ + serverSide: boolean; + /** + * Whether or not the standard build procedure is meant to be used + */ + standardBuild: boolean; + /** + * Whether or not the standard documentation procedure is meant to be used + */ + standardDocumentation: boolean; +} + +/** + * Rules for the configuration check + */ +export interface Rules { + /** + * Expected CI config + */ + ciConfig: any; + /** + * Expected dependencies + */ + dependencies: string[]; + /** + * Expected dev dependencies + */ + devDependencies: string[]; + /** + * Expected files + */ + files: string[]; + /** + * Expected licenses + */ + licenses: string[]; + /** + * Expected NYC configuration + */ + nycConfiguration: any; + /** + * Expected scripts + */ + scripts: { [k: string]: string; }; +} /** * Wrapper for console.info that outputs every argument in cyan @@ -25,8 +89,13 @@ export function consoleInfo(...args: string[]): void { */ 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(arg)); + console.warn('\n' + chalk.red.bold(lines[0])); + for (const line of lines.slice(1)) { + /* tslint:disable-next-line:no-console */ + console.info(line); + } }); } @@ -42,6 +111,53 @@ export function consoleLog(...args: string[]): void { }); } +/** + * Check dependencies are installed + * + * @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]; + + 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}'.`, + ); + } + } + + for (const devDependency of rules.devDependencies) { + const [name, version] = devDependency.split(':'); + const 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}'.`, + ); + } + } +} + /** * Check that configuration files are extended * @@ -67,7 +183,7 @@ export function checkConfigurationFilesAreExtended(path: string): void { ); if (!configFileExtended) { - consoleWarn(`File "${fileToCheck}" should extend "${expectedPath}"! + consoleWarn(`File '${fileToCheck}' should extend '${expectedPath}'! Example: ${readFileSync(resolve(__dirname, '..', 'templates', 'template-' + file))}`); @@ -79,15 +195,16 @@ ${readFileSync(resolve(__dirname, '..', 'templates', 'template-' + file))}`); /** * Check needed files * + * @param rules Rules for check * @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 { +export function checkNeededFiles(rules: Rules, path: string, replaceFlag: boolean): boolean { let suggestOverwrite = false; // copy needed files - NEEDED_FILES.forEach((file) => { + rules.files.forEach((file) => { let destinationFile = file; // remove templates directory for destination files @@ -102,7 +219,7 @@ export function checkNeededFiles(path: string, replaceFlag: boolean): boolean { // check if file exists or replace flag is set if (!existsSync(destination) || replaceFlag) { copyFileSync(source, destination); - consoleInfo(`Copied file "${source}" to "${destination}".`); + consoleInfo(`Copied file '${source}' to '${destination}'.`); } else if (destinationFile === '.npmignore') { const npmIgnore = readFileSync(destination).toString(); @@ -143,23 +260,25 @@ https://gitlab.com/openstapps/configuration/issues/11`); /** * Check licenses * + * @param rules Rules for check * @param packageJson package.json to check license in */ -export function checkLicenses(packageJson: any): void { +export function checkLicenses(rules: Rules, 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(', ')}"!`); + if (rules.licenses.indexOf(packageJson.license) === -1) { + consoleWarn(`License should be one of '${rules.licenses.join(', ')}'!`); } } /** * Check NYC configuration * + * @param rules Rules for check * @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] { +export function checkNYCConfiguration(rules: Rules, packageJson: any, replaceFlag: boolean): [boolean, boolean] { let packageJsonChanged = false; let suggestOverwrite = false; @@ -167,12 +286,12 @@ export function checkNYCConfiguration(packageJson: any, replaceFlag: boolean): [ 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; + packageJson.nyc = rules.nycConfiguration; packageJsonChanged = true; consoleLog(`Added NYC configuration in to 'package.json'.`); - } else if (!isDeepStrictEqual(packageJson.nyc, NYC_CONFIGURATION)) { + } else if (!isDeepStrictEqual(packageJson.nyc, rules.nycConfiguration)) { consoleInfo(`NYC configuration in 'package.json' differs from the proposed one. Please check manually.`); suggestOverwrite = true; @@ -185,11 +304,12 @@ export function checkNYCConfiguration(packageJson: any, replaceFlag: boolean): [ /** * Check scripts * + * @param rules Rules for check * @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 { +export function checkScripts(rules: Rules, packageJson: any, replaceFlag: boolean): boolean { let packageJsonChanged = false; // check if scripts is a map @@ -199,19 +319,19 @@ export function checkScripts(packageJson: any, replaceFlag: boolean): boolean { packageJsonChanged = true; } - Object.keys(SCRIPTS).forEach((scriptName) => { + Object.keys(rules.scripts).forEach((scriptName) => { const scriptToCheck = packageJson.scripts[scriptName]; // check if script exists if (typeof scriptToCheck === 'undefined' || replaceFlag) { - packageJson.scripts[scriptName] = SCRIPTS[scriptName]; + packageJson.scripts[scriptName] = rules.scripts[scriptName]; packageJsonChanged = true; consoleInfo(`Added '${scriptName}' script to 'package.json'.`); - } else if (typeof scriptToCheck === 'string' && scriptToCheck !== SCRIPTS[scriptName]) { + } else if (typeof scriptToCheck === 'string' && scriptToCheck !== rules.scripts[scriptName]) { consoleWarn(`Script '${scriptName}' in 'package.json' should be: -"${SCRIPTS[scriptName].replace('\n', '\\n')}".`); +'${rules.scripts[scriptName].replace('\n', '\\n')}'.`); } }); @@ -249,9 +369,10 @@ export function checkContributors(packageJson: any): void { /** * Check CI config * + * @param rules Rules for check * @param path Path to CI config */ -export function checkCIConfig(path: string): void { +export function checkCIConfig(rules: Rules, path: string): void { // check CI config if it exists const pathToCiConfig = resolve(path, '.gitlab-ci.yml'); if (existsSync(pathToCiConfig)) { @@ -261,24 +382,201 @@ export function checkCIConfig(path: string): void { const ciConfig = parse(buffer.toString()); // check entries - for (const entry in EXPECTED_CI_CONFIG) { - if (!EXPECTED_CI_CONFIG.hasOwnProperty(entry)) { + for (const entry in rules.ciConfig) { + if (!rules.ciConfig.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; - })())); + if (!isDeepStrictEqual(rules.ciConfig[entry], ciConfig[entry])) { + const completeEntry: any = {}; + completeEntry[entry] = rules.ciConfig[entry]; + consoleWarn(`Entry '${entry}' in '${pathToCiConfig}' is incorrect. Expected value is: +${stringify(completeEntry)}`); } } } catch (error) { consoleWarn(`Could not parse ${pathToCiConfig} because of '${error.message}'. -Please ensure consistency of CI config manually.`); - consoleInfo(stringify(EXPECTED_CI_CONFIG)); +Please ensure consistency of CI config manually. +${stringify(rules.ciConfig)}`); } } } + +/** + * Get configuration + * + * @param packageJson package.json to get configuration from + */ +export function getConfiguration(packageJson: any): Configuration { + const defaultConfiguration: Configuration = { + forPackaging: true, + hasCli: true, + ignoreScripts: [], + serverSide: true, + standardBuild: true, + standardDocumentation: true, + }; + + if (typeof packageJson.openstappsConfiguration !== 'undefined') { + return { + ...defaultConfiguration, + ...packageJson.openstappsConfiguration, + }; + } + + return defaultConfiguration; +} + +/** + * Get rules for check + * + * @param configuration Configuration for check + */ +export function getRules(configuration: Configuration): Rules { + // expected dependencies + const dependencies: string[] = []; + + // expected dev dependencies + const devDependencies = [ + 'conventional-changelog-cli', + 'tslint', + 'typescript:^3.4.0', + ]; + + // files that need to be copied + const files = [ + '.editorconfig', + join('templates', '.gitignore'), + join('templates', 'tsconfig.json'), + join('templates', 'tslint.json'), + ]; + + // configuration for nyc to add to package.json + const nycConfiguration = { + all: true, + branches: 95, + 'check-coverage': true, + exclude: [ + 'src/test/**/*.spec.ts', + 'src/cli.ts', + ], + extension: [ + '.ts', + ], + functions: 95, + include: [ + 'src', + ], + lines: 95, + 'per-file': true, + reporter: [ + 'html', + 'text-summary', + ], + statements: 95, + }; + + // 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\'', + 'check-configuration': 'openstapps-configuration', + }; + + // list of expected licenses + const licenses = [ + 'AGPL-3.0-only', + 'GPL-3.0-only', + ]; + + // expected values in CI config + const ciConfig = { + /* tslint:disable:object-literal-sort-keys */ + image: 'registry.gitlab.com/openstapps/projectmanagement/node', + cache: { + key: '${CI_COMMIT_REF_SLUG}', + paths: [ + 'node_modules', + ], + }, + audit: { + allow_failure: true, + except: [ + 'schedules', + ], + script: [ + 'npm audit', + ], + stage: 'test', + }, + 'scheduled-audit': { + only: [ + 'schedules', + ], + script: [ + 'npm audit', + ], + stage: 'test', + }, + pages: { + artifacts: { + 'paths': [ + 'public', + ], + }, + only: [ + '/^v[0-9]+\\.[0-9]+\\.[0-9]+$/', + ], + script: [ + 'npm run documentation', + 'mv docs public', + ], + stage: 'deploy', + }, + /* tslint:enable */ + }; + + if (configuration.forPackaging) { + scripts.prepublishOnly = 'npm ci && npm run build'; + + files.push( + join('templates', '.npmignore'), + ); + } + + if (configuration.serverSide) { + dependencies.push('@types/node:^10.0.0'); + } + + if (configuration.standardBuild || configuration.hasCli) { + scripts.build = 'npm run tslint && npm run compile'; + scripts.compile = 'rimraf lib && tsc'; + devDependencies.push('rimraf'); + + if (configuration.hasCli) { + devDependencies.push('prepend-file-cli'); + scripts.compile += ' && prepend lib/cli.js \'#!/usr/bin/env node\n\''; + } + } + + if (configuration.standardDocumentation) { + devDependencies.push('typedoc'); + /* tslint:disable-next-line:max-line-length */ + scripts.documentation = 'typedoc --includeDeclarations --mode modules --out docs --readme README.md --listInvalidSymbolLinks src'; + } + + for (const ignoreScript of configuration.ignoreScripts) { + consoleInfo(`Ignoring script '${ignoreScript}'.`); + delete scripts[ignoreScript]; + } + + return { + ciConfig, + dependencies, + devDependencies, + files, + licenses, + nycConfiguration, + scripts, + }; +} diff --git a/src/configuration.ts b/src/configuration.ts deleted file mode 100644 index e6156483..00000000 --- a/src/configuration.ts +++ /dev/null @@ -1,124 +0,0 @@ -/* - * 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 . - */ -import {join} from 'path'; - -/** - * Files that need to be copied - */ -export const NEEDED_FILES = [ - '.editorconfig', - join('templates', '.gitignore'), - join('templates', '.npmignore'), - join('templates', 'tsconfig.json'), - join('templates', 'tslint.json'), -]; - -/** - * Configuration for nyc to add to package.json - */ -export const NYC_CONFIGURATION = { - all: true, - branches: 95, - 'check-coverage': true, - exclude: [ - 'src/test/**/*.spec.ts', - 'src/cli.ts', - ], - extension: [ - '.ts', - ], - functions: 95, - include: [ - 'src', - ], - lines: 95, - 'per-file': true, - reporter: [ - 'html', - 'text-summary', - ], - statements: 95, -}; - -/** - * Map of expected scripts - */ -export const SCRIPTS: { [k: string]: string } = { - 'build': 'npm run tslint && npm run compile', - /* 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\'', - 'check-configuration': 'openstapps-configuration', - 'compile': 'rimraf lib && tsc && prepend lib/cli.js \'#!/usr/bin/env node\n\'', - /* tslint:disable-next-line:max-line-length */ - 'documentation': 'typedoc --includeDeclarations --mode modules --out docs --readme README.md --listInvalidSymbolLinks src', - 'prepublishOnly': 'npm ci && npm run build', -}; - -/** - * List of expected licenses - */ -export const EXPECTED_LICENSES = [ - 'AGPL-3.0-only', - 'GPL-3.0-only', -]; - -/* tslint:disable:object-literal-sort-keys */ -/** - * Expected values in CI config - */ -export const EXPECTED_CI_CONFIG: any = { - image: 'registry.gitlab.com/openstapps/projectmanagement/node', - cache: { - key: '${CI_COMMIT_REF_SLUG}', - paths: [ - 'node_modules', - ], - }, - audit: { - allow_failure: true, - except: [ - 'schedules', - ], - script: [ - 'npm audit', - ], - stage: 'test', - }, - 'scheduled-audit': { - only: [ - 'schedules', - ], - script: [ - 'npm audit', - ], - stage: 'test', - }, - pages: { - artifacts: { - 'paths': [ - 'public', - ], - }, - only: [ - '/^v[0-9]+\\.[0-9]+\\.[0-9]+$/', - ], - script: [ - 'npm run documentation', - 'mv docs public', - ], - stage: 'deploy', - }, -}; -/* tslint:enable */