diff --git a/README.md b/README.md index 712d34cb..dffda480 100644 --- a/README.md +++ b/README.md @@ -1 +1 @@ -🚧 This package is in progres.. :) 🚧 \ No newline at end of file +🚧 This package is in progres... Use it once released with version 1.x.x :) 🚧 \ No newline at end of file diff --git a/package.json b/package.json index b48dfe3d..9f755ac0 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,9 @@ "setdev": "run-p husky:install git:config", "onboarding": "node ./scripts/on-boarding.js", "prepare": "is-ci || run-s setdev onboarding", - "build": "tsc --project ./tsconfig.build.json && tsc-alias --project ./tsconfig.build.json", + "build:remove": "rimraf dist", + "build:create": "tsc --project ./tsconfig.build.json && tsc-alias --project ./tsconfig.build.json", + "build": "run-s build:remove build:create", "lint": "eslint {src,tests}/**/*", "lint:fix": "eslint {src,tests}/**/* --fix", "format": "prettier --write \"**/*.{ts,js,json}\"", @@ -47,10 +49,13 @@ "depcheck": "depcheck" }, "dependencies": { - "minimist": "1.2.6" + "inquirer": "8.2.2", + "minimist": "1.2.6", + "yaml": "2.0.1" }, "devDependencies": { "@commitlint/cli": "16.2.3", + "@types/inquirer": "8.2.1", "@types/minimist": "1.2.2", "@typescript-eslint/eslint-plugin": "5.19.0", "@typescript-eslint/parser": "5.19.0", @@ -70,6 +75,7 @@ "lint-staged": "12.3.7", "npm-run-all": "4.1.5", "prettier": "2.6.2", + "rimraf": "3.0.2", "tsc-alias": "1.6.6", "typescript": "4.6.3" } diff --git a/src/index.ts b/src/index.ts index 2a4e55ae..bdbe4b69 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,13 @@ -import { getCLIArgv } from '@/utils/cli-argv'; -import { startCLIPhase } from '@/utils/cli-phase'; +import { getCLIArgv } from '@/utils/argv'; +import StartCLI from '@/modules/cli'; +import StartConfiguration from '@/modules/configuration'; (() => { const argv = getCLIArgv(); - startCLIPhase(argv); + const configFromCLI = StartCLI(argv); + + if (configFromCLI) { + StartConfiguration(); + } })(); diff --git a/src/models/configuration.ts b/src/models/configuration.ts deleted file mode 100644 index 4d68cb54..00000000 --- a/src/models/configuration.ts +++ /dev/null @@ -1,38 +0,0 @@ -type IRuleEnforcement = 0 | 1 | 2 | 'off' | 'warn' | 'error'; - -type IRules = Record; - -type IFormat = - | 'checkstyle' - | 'compact' - | 'html' - | 'jslint-xml' - | 'json' - | 'junit' - | 'stylish' - | 'tap' - | 'unix' - | 'visualstudio'; - -export interface IConfiguration { - readonly noInflintrc: boolean; - readonly config: string | null; - readonly rules: IRules; - readonly ignoreFile: string; - readonly noIgnoreFile: boolean; - readonly ignorePattern: ReadonlyArray; - readonly quiet: boolean; - readonly maxWarnings: number; - readonly outputFile: string; - readonly format: IFormat; - readonly color: boolean; - readonly noColor: boolean; - readonly printRule: string; - readonly version: boolean; - readonly bail: boolean; - readonly help: boolean; - readonly extends: string; - readonly files: ReadonlyArray; - readonly warn: boolean; - readonly debug: boolean; -} diff --git a/src/utils/cli-help.ts b/src/modules/cli/functions/help.ts similarity index 50% rename from src/utils/cli-help.ts rename to src/modules/cli/functions/help.ts index 3199a17c..e7a12216 100644 --- a/src/utils/cli-help.ts +++ b/src/modules/cli/functions/help.ts @@ -1,88 +1,44 @@ import CLILoggerService from '@/services/cli-logger'; +import { calcSpaceBias } from '@/utils/cli-logger'; -type IOptionsKeys = - | 'noInflintrc' - | 'config' - | 'rule' - | 'ignorePath' - | 'noIgnore' - | 'ignorePattern' - | 'quiet' - | 'maxWarnings' - | 'outputFile' - | 'format' - | 'color' - | 'init' - | 'debug' - | 'help' - | 'version' - | 'printRule'; +import { options } from '../models/help'; /** * The function logs the help document to the console * @returns void */ export const printHelp = () => { - const options: Record = { - noInflintrc: [ - '--no-inflintrc', - 'Disable use of configuration from configuration file or package.json. Default: false', - ], - config: ['-c, --config (path::String)', 'Inflint will use the provided configuration file'], - rule: ['--rule (String)', 'Specify rules'], - ignorePath: ['--ignore-path (path::String)', 'Specify path of ignore file'], - noIgnore: ['-"-no-ignore', 'Disable use of ignore files and patterns. Default: false'], - ignorePattern: [ - '--ignore-pattern (String)', - 'Pattern of files to ignore (in addition to those in .inflintignore)', - ], - quiet: ['--quiet', 'Report errors only. Default: false'], - maxWarnings: [ - '--max-warnings (Int)', - 'Number of warnings to trigger non-zero exit code. Default: -1', - ], - outputFile: ['-o, --output-file (path::String)', 'Specify file to write report to'], - format: ['-f, --format (String)', 'Use a specific output format. Default: "stylish"'], - color: ['--color, --no-color', 'Force enabling/disabling of color'], - init: ['--init', 'Run configuration initialization wizard. Default: false'], - debug: ['--debug', 'Output debugging information'], - help: ['-h, --help', 'Show help'], - version: ['-v, --version', 'Output the version number'], - printRule: ['--print-rule (path::String)', 'Print the expected rule to be enforced on a given file'], - }; - - const spaceBias = - Object.values(options).reduce((final, value) => { - return Math.max(final, value[0]!.length); - }, 0) + 3; + const spaceBias = calcSpaceBias(options); CLILoggerService.logDefault('inflint [options] [files]'); + CLILoggerService.logEmptyBlock(); CLILoggerService.logDefault('Basic configuration:'); CLILoggerService.logSpaced(options.noInflintrc, 2, spaceBias - options.noInflintrc[0].length); CLILoggerService.logSpaced(options.config, 2, spaceBias - options.config[0].length); - CLILoggerService.logEmptyLine(); + CLILoggerService.logEmptyBlock(); CLILoggerService.logDefault('Specifying rules:'); CLILoggerService.logSpaced(options.rule, 2, spaceBias - options.rule[0].length); - CLILoggerService.logEmptyLine(); + CLILoggerService.logSpaced(options.alias, 2, spaceBias - options.alias[0].length); + CLILoggerService.logEmptyBlock(); CLILoggerService.logDefault('Ignoring files:'); CLILoggerService.logSpaced(options.ignorePath, 2, spaceBias - options.ignorePath[0].length); CLILoggerService.logSpaced(options.noIgnore, 2, spaceBias - options.noIgnore[0].length); CLILoggerService.logSpaced(options.ignorePattern, 2, spaceBias - options.ignorePattern[0].length); - CLILoggerService.logEmptyLine(); + CLILoggerService.logEmptyBlock(); CLILoggerService.logDefault('Handling warnings:'); CLILoggerService.logSpaced(options.quiet, 2, spaceBias - options.quiet[0].length); CLILoggerService.logSpaced(options.maxWarnings, 2, spaceBias - options.maxWarnings[0].length); - CLILoggerService.logEmptyLine(); + CLILoggerService.logEmptyBlock(); CLILoggerService.logDefault('Output:'); CLILoggerService.logSpaced(options.outputFile, 2, spaceBias - options.outputFile[0].length); CLILoggerService.logSpaced(options.format, 2, spaceBias - options.format[0].length); CLILoggerService.logSpaced(options.color, 2, spaceBias - options.color[0].length); - CLILoggerService.logEmptyLine(); + CLILoggerService.logEmptyBlock(); CLILoggerService.logDefault('Miscellaneous:'); CLILoggerService.logSpaced(options.init, 2, spaceBias - options.init[0].length); diff --git a/src/modules/cli/functions/init.ts b/src/modules/cli/functions/init.ts new file mode 100644 index 00000000..c9a6e765 --- /dev/null +++ b/src/modules/cli/functions/init.ts @@ -0,0 +1,80 @@ +import inquirer from 'inquirer'; + +import { convertAliasesAnswers, exportFilesFromInit } from '../utils/init'; +import type { IAliasAnswer, IInitialAnswers, IAliasAnswers } from '../models/init'; + +/** + * The function initialize the project with basic Inflint configuration + * @returns void + */ +export const initConfiguration = async () => { + const initialQuestions: inquirer.QuestionCollection = [ + { + type: 'list', + name: 'file_format', + message: 'Please choose the configuration file format:', + default: 'Javascript', + choices: ['Javascript', 'JSON', 'YAML'], + }, + { + type: 'list', + name: 'with_ignore_file', + message: 'Do you want to create an ignore file?', + default: 'Yes', + choices: ['Yes', 'No'], + }, + { + type: 'list', + name: 'with_aliases', + message: 'Do you want to set some aliases to your configuration?', + default: 'No', + choices: ['Yes', 'No'], + }, + ]; + + const initialAnswers = await inquirer.prompt(initialQuestions); + const aliasesAnswers: IAliasAnswers = []; + + if (initialAnswers.with_aliases === 'Yes') { + let stopAliases = false; + + do { + const aliasQuestions: inquirer.QuestionCollection = [ + { + type: 'input', + name: 'name', + message: 'Enter name of the alias:', + validate: (input: string) => (input ? true : 'Please enter an alias name'), + }, + { + type: 'input', + name: 'regex', + message: 'Enter regex the alias will match:', + validate: (input: string) => (input ? true : 'Please enter a regex for the alias'), + }, + { + type: 'list', + name: 'with_resume', + message: 'Do you want to set an another alias?', + default: 'Yes', + choices: ['Yes', 'No'], + }, + ]; + + const aliasAnswers = await inquirer.prompt(aliasQuestions); + + aliasesAnswers.push({ + name: aliasAnswers.name, + regex: aliasAnswers.regex, + }); + + if (aliasAnswers.with_resume === 'No') { + stopAliases = true; + } + } while (!stopAliases); + } + + const convertedAliases = convertAliasesAnswers(aliasesAnswers); + + await exportFilesFromInit(initialAnswers.file_format, initialAnswers.with_ignore_file, convertedAliases); +}; diff --git a/src/utils/cli-version.ts b/src/modules/cli/functions/version.ts similarity index 81% rename from src/utils/cli-version.ts rename to src/modules/cli/functions/version.ts index ee93ca58..713b2daf 100644 --- a/src/utils/cli-version.ts +++ b/src/modules/cli/functions/version.ts @@ -1,5 +1,5 @@ import CLILoggerService from '@/services/cli-logger'; -import packageJson from '../../package.json'; +import packageJson from '../../../../package.json'; /** * The function logs the version to the console diff --git a/src/modules/cli/index.ts b/src/modules/cli/index.ts new file mode 100644 index 00000000..61c39ccf --- /dev/null +++ b/src/modules/cli/index.ts @@ -0,0 +1,29 @@ +import { ParsedArgs } from 'minimist'; + +import { printHelp } from './functions/help'; +import { printVersion } from './functions/version'; +import { initConfiguration } from './functions/init'; + +const StartCLI = (argv: ParsedArgs) => { + if (argv['h'] === true || argv['help'] === true) { + printHelp(); + + return null; + } + + if (argv['v'] === true || argv['version'] === true) { + printVersion(); + + return null; + } + + if (argv['init'] === true) { + initConfiguration(); + + return null; + } + + return null; +}; + +export default StartCLI; diff --git a/src/modules/cli/models/help.ts b/src/modules/cli/models/help.ts new file mode 100644 index 00000000..e0e7fe95 --- /dev/null +++ b/src/modules/cli/models/help.ts @@ -0,0 +1,44 @@ +type IOptionsKeys = + | 'noInflintrc' + | 'config' + | 'rule' + | 'alias' + | 'ignorePath' + | 'noIgnore' + | 'ignorePattern' + | 'quiet' + | 'maxWarnings' + | 'outputFile' + | 'format' + | 'color' + | 'init' + | 'debug' + | 'help' + | 'version' + | 'printRule'; + +export const options: Record = { + noInflintrc: [ + '--no-inflintrc', + 'Disable use of configuration from configuration file or package.json. Default: false', + ], + config: ['-c, --config (path::String)', 'Inflint will use the provided configuration file'], + rule: ['--rule (String)', 'Specify rules'], + alias: ['--alias (String)', 'Specify custom aliases'], + ignorePath: ['--ignore-path (path::String)', 'Specify path of ignore file'], + noIgnore: ['--no-ignore', 'Disable use of ignore files and patterns. Default: false'], + ignorePattern: [ + '--ignore-pattern (String)', + 'Pattern of files to ignore (in addition to those in .inflintignore)', + ], + quiet: ['--quiet', 'Report errors only. Default: false'], + maxWarnings: ['--max-warnings (Int)', 'Number of warnings to trigger non-zero exit code. Default: -1'], + outputFile: ['-o, --output-file (path::String)', 'Specify file to write report to'], + format: ['-f, --format (String)', 'Use a specific output format. Default: "stylish"'], + color: ['--color, --no-color', 'Force enabling/disabling of color'], + init: ['--init', 'Run configuration initialization wizard. Default: false'], + debug: ['--debug', 'Output debugging information'], + help: ['-h, --help', 'Show help'], + version: ['-v, --version', 'Output the version number'], + printRule: ['--print-rule (path::String)', 'Print the expected rule to be enforced on a given file'], +}; diff --git a/src/modules/cli/models/init.ts b/src/modules/cli/models/init.ts new file mode 100644 index 00000000..f35fd41f --- /dev/null +++ b/src/modules/cli/models/init.ts @@ -0,0 +1,17 @@ +export type IBooleanQuestion = 'Yes' | 'No'; + +export type IFileFormat = 'Javascript' | 'JSON' | 'YAML'; + +export interface IAliasAnswer { + readonly name: string; + readonly regex: string; + readonly with_resume: IBooleanQuestion; +} + +export interface IInitialAnswers { + readonly file_format: IFileFormat; + readonly with_ignore_file: IBooleanQuestion; + readonly with_aliases: IBooleanQuestion; +} + +export type IAliasAnswers = Omit[]; diff --git a/src/modules/cli/utils/init.ts b/src/modules/cli/utils/init.ts new file mode 100644 index 00000000..39f569ae --- /dev/null +++ b/src/modules/cli/utils/init.ts @@ -0,0 +1,61 @@ +import fs from 'fs/promises'; +import path from 'path'; + +import YAML from 'yaml'; + +import { cleanObject } from '@/utils/object'; + +import type { IFileFormat, IBooleanQuestion, IAliasAnswers } from '../models/init'; + +/** + * The function converts an array of aliases to an object from answers + * @param aliasesAnswers the aliases to convert from answers + * @returns the converted object + */ +export const convertAliasesAnswers = (aliasesAnswers: IAliasAnswers) => { + return aliasesAnswers.reduce>((final, value) => { + return { + ...final, + [value.name]: value.regex, + }; + }, {}); +}; + +export const exportFilesFromInit = async ( + fileFormat: IFileFormat, + withIgnoreFile: IBooleanQuestion, + aliases: Record, +) => { + const contentObject = { + aliases: Object.keys(aliases).length > 0 ? aliases : undefined, + }; + + cleanObject(contentObject); + + let contentString: string; + let filename: string; + + if (fileFormat === 'YAML') { + contentString = Object.keys(contentObject).length > 0 ? YAML.stringify(contentObject) : ''; + filename = '.inflintrc.yml'; + } else if (fileFormat === 'Javascript') { + contentString = `module.exports = ${JSON.stringify(contentObject, null, 2)};`; + filename = '.inflintrc.js'; + } else { + contentString = JSON.stringify(contentObject, null, 2); + filename = '.inflintrc.json'; + } + + const writeConfigFilePromise = fs.writeFile(path.join(process.cwd(), filename), contentString); + + if (withIgnoreFile === 'No') { + await writeConfigFilePromise; + } else { + const ignoreFilename = '.inflintignore'; + + await Promise.all([ + writeConfigFilePromise, + fs.writeFile(path.join(process.cwd(), ignoreFilename), ''), + ]); + } +}; diff --git a/src/modules/configuration/index.ts b/src/modules/configuration/index.ts new file mode 100644 index 00000000..058b61e1 --- /dev/null +++ b/src/modules/configuration/index.ts @@ -0,0 +1,5 @@ +const StartConfiguration = () => { + console.log('Configuration'); +}; + +export default StartConfiguration; diff --git a/src/services/cli-logger.ts b/src/services/cli-logger.ts index 25ed6026..648fb98f 100644 --- a/src/services/cli-logger.ts +++ b/src/services/cli-logger.ts @@ -1,7 +1,7 @@ /* eslint-disable no-console */ export default class CLILoggerService { - public static logEmptyLine() { + public static logEmptyBlock() { console.log('\n'); } diff --git a/src/utils/cli-argv.ts b/src/utils/argv.ts similarity index 100% rename from src/utils/cli-argv.ts rename to src/utils/argv.ts diff --git a/src/utils/cli-logger.ts b/src/utils/cli-logger.ts new file mode 100644 index 00000000..b74c8b72 --- /dev/null +++ b/src/utils/cli-logger.ts @@ -0,0 +1,12 @@ +/** + * The function receives object representing CLI prints and calculates the spaces bias to get stylish print + * @param input the prints object + * @returns the space bias + */ +export const calcSpaceBias = (input: Record) => { + return ( + Object.values(input).reduce((final, value) => { + return Math.max(final, value[0]!.length); + }, 0) + 3 + ); +}; diff --git a/src/utils/cli-phase.ts b/src/utils/cli-phase.ts deleted file mode 100644 index be2c2b9b..00000000 --- a/src/utils/cli-phase.ts +++ /dev/null @@ -1,25 +0,0 @@ -import minimist from 'minimist'; - -import { printHelp } from './cli-help'; -import { printVersion } from './cli-version'; - -/** - * The function stars the CLI phase. If no need to continue to other phase, will return null. Otherwise returns configuration for other phases - * @param argv the argv from CLI - * @returns null if need to finish, othewise configuration to work with - */ -export const startCLIPhase = (argv: minimist.ParsedArgs) => { - if (argv['h'] === true || argv['help'] === true) { - printHelp(); - - return null; - } - - if (argv['v'] === true || argv['version'] === true) { - printVersion(); - - return null; - } - - return null; -}; diff --git a/src/utils/object.ts b/src/utils/object.ts new file mode 100644 index 00000000..1281023b --- /dev/null +++ b/src/utils/object.ts @@ -0,0 +1,8 @@ +/** + * The function remove "undefined" fields from the object + * @param input the object to clean + * @returns void + */ +export const cleanObject = (input: Record) => { + Object.keys(input).forEach((key) => input[key] === undefined && delete input[key]); +}; diff --git a/tsconfig.base.json b/tsconfig.base.json index c26a3628..2f74509d 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -28,7 +28,8 @@ "paths": { "@/utils/*": ["src/utils/*"], "@/models/*": ["src/models/*"], - "@/services/*": ["src/services/*"] + "@/services/*": ["src/services/*"], + "@/modules/*": ["src/modules/*"] }, "typeRoots": ["./node_modules/@types", "./@types"] } diff --git a/yarn.lock b/yarn.lock index e4e4d555..9b1c5a93 100644 --- a/yarn.lock +++ b/yarn.lock @@ -791,6 +791,14 @@ dependencies: "@types/node" "*" +"@types/inquirer@8.2.1": + version "8.2.1" + resolved "https://registry.yarnpkg.com/@types/inquirer/-/inquirer-8.2.1.tgz#28a139be3105a1175e205537e8ac10830e38dbf4" + integrity sha512-wKW3SKIUMmltbykg4I5JzCVzUhkuD9trD6efAmYgN2MrSntY0SMRQzEnD3mkyJ/rv9NLbTC7g3hKKE86YwEDLw== + dependencies: + "@types/through" "*" + rxjs "^7.2.0" + "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": version "2.0.4" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44" @@ -855,6 +863,13 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== +"@types/through@*": + version "0.0.30" + resolved "https://registry.yarnpkg.com/@types/through/-/through-0.0.30.tgz#e0e42ce77e897bd6aead6f6ea62aeb135b8a3895" + integrity sha512-FvnCJljyxhPM3gkRgWmxmDZyAQSiBQQWLI0A0VFL0K7W1oRUrPJSqNO0NvTnLkBcotdlp3lKvaT0JrnyRDkzOg== + dependencies: + "@types/node" "*" + "@types/yargs-parser@*": version "21.0.0" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b" @@ -4533,7 +4548,7 @@ rfdc@^1.3.0: resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== -rimraf@^3.0.0, rimraf@^3.0.2: +rimraf@3.0.2, rimraf@^3.0.0, rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== @@ -4566,7 +4581,7 @@ rxjs@^6.4.0: dependencies: tslib "^1.9.0" -rxjs@^7.5.4, rxjs@^7.5.5: +rxjs@^7.2.0, rxjs@^7.5.4, rxjs@^7.5.5: version "7.5.5" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.5.5.tgz#2ebad89af0f560f460ad5cc4213219e1f7dd4e9f" integrity sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw== @@ -5399,6 +5414,11 @@ yallist@^4.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== +yaml@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.0.1.tgz#71886d6021f3da28169dbefde78d4dd0f8d83650" + integrity sha512-1NpAYQ3wjzIlMs0mgdBmYzLkFgWBIWrzYVDYfrixhoFNNgJ444/jT2kUT2sicRbJES3oQYRZugjB6Ro8SjKeFg== + yaml@^1.10.0, yaml@^1.10.2: version "1.10.2" resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"