From 4245aceed5d9238cb4b2c8af930660ffb46b7283 Mon Sep 17 00:00:00 2001 From: ig1na Date: Fri, 5 Jul 2019 11:15:07 +0200 Subject: [PATCH 1/4] refactor(nodemon): Use installNpmDevDep function Use Globals.packagePath --- src/rules/nodemon/index.ts | 92 +++++++++++++++++--------------------- 1 file changed, 42 insertions(+), 50 deletions(-) diff --git a/src/rules/nodemon/index.ts b/src/rules/nodemon/index.ts index 0b1005e9..782437ac 100644 --- a/src/rules/nodemon/index.ts +++ b/src/rules/nodemon/index.ts @@ -9,16 +9,15 @@ import * as cp from 'child_process'; import Node from '../../stacks/node'; import { logger } from '../../logger/index'; import * as fs from 'fs-extra'; +import { installNpmDevDep } from '../../utils/commands'; @RuleRegister.register @StackRegister.registerRuleForStacks([Node]) export class Nodemon { private parsedJSON: any; - private packagePath: string; constructor() { - this.packagePath = Globals.rootPath + 'package.json'; - this.parsedJSON = require(this.packagePath); + this.parsedJSON = require(Globals.packageJSONPath); } async shouldBeApplied(): Promise { @@ -38,56 +37,49 @@ export class Nodemon { async apply(apply: boolean): Promise { if (apply) { - const exec = util.promisify(cp.exec); - - return exec('npm i -DE nodemon', { cwd: Globals.rootPath }) - .then(() => { - logger.info( - 'Nodemon: Succesfully installed nodemon as dev dependency. Checking for existing script.', - ); - - fs.readJSON(this.packagePath, { encoding: 'utf-8' }) - .then(packageJSON => { - if (!pathExistsInJSON(packageJSON, ['scripts', 'nodemon'])) { - if (!pathExistsInJSON(packageJSON, ['scripts'])) { - packageJSON.scripts = { nodemon: 'nodemon' }; - } else { - packageJSON.scripts.nodemon = 'nodemon'; - } + return installNpmDevDep('nodemon').then(() => { + logger.info( + 'Nodemon: Succesfully installed nodemon as dev dependency. Checking for existing script.', + ); - fs.writeJSON(this.packagePath, packageJSON, { spaces: '\t' }) - .then(() => { - logger.info( - 'Nodemon: Succesfully added nodemon script. Run "npm run nodemon" to run it.', - ); - }) - .catch(err => { - logger.error( - `Nodemon: Error trying to write to ${ - this.packagePath - }, please run in debug mode to know more.`, - ); - logger.debug(err); - }); + fs.readJSON(Globals.packageJSONPath, { encoding: 'utf-8' }) + .then(packageJSON => { + if (!pathExistsInJSON(packageJSON, ['scripts', 'nodemon'])) { + if (!pathExistsInJSON(packageJSON, ['scripts'])) { + packageJSON.scripts = { nodemon: 'nodemon' }; } else { - logger.info('Nodemon: Script already existing.'); + packageJSON.scripts.nodemon = 'nodemon'; } - }) - .catch(err => { - logger.error( - `Nodemon: Error trying to read ${ - this.packagePath - }, please run in debug mode to know more`, - ); - logger.debug(err); - }); - }) - .catch(err => { - logger.error( - 'Nodemon: Error trying to execute npm i -DE nodemon command, please run in debug mode to know more', - ); - logger.debug(err); - }); + + fs.writeJSON(Globals.packageJSONPath, packageJSON, { + spaces: '\t', + }) + .then(() => { + logger.info( + 'Nodemon: Succesfully added nodemon script. Run "npm run nodemon" to run it.', + ); + }) + .catch(err => { + logger.error( + `Nodemon: Error trying to write to ${ + Globals.packageJSONPath + }, please run in debug mode to know more.`, + ); + logger.debug(err); + }); + } else { + logger.info('Nodemon: Script already existing.'); + } + }) + .catch(err => { + logger.error( + `Nodemon: Error trying to read ${ + Globals.packageJSONPath + }, please run in debug mode to know more`, + ); + logger.debug(err); + }); + }); } } From caac6c95c4fa84c7277b5417d9c386f1f139dba0 Mon Sep 17 00:00:00 2001 From: ig1na Date: Fri, 5 Jul 2019 11:18:39 +0200 Subject: [PATCH 2/4] Revert "WIP: front-app-debug" --- src/index.ts | 17 +-- .../front-app-debug/__mocks__/constants.ts | 24 ---- src/rules/front-app-debug/apply.test.ts | 63 ---------- src/rules/front-app-debug/constants.ts | 112 ----------------- src/rules/front-app-debug/index.ts | 117 ------------------ .../front-app-debug/should-be-applied.test.ts | 93 -------------- src/rules/husky/apply.test.ts | 45 +++---- src/rules/husky/index.ts | 76 ++++++------ src/rules/linter/apply.test.ts | 12 +- src/rules/linter/index.ts | 114 ++++++++++------- src/rules/nodemon/index.ts | 27 ++-- src/rules/prettier/index.ts | 11 +- src/rules/vscode-extensions/constants.ts | 4 - src/stacks/github/index.ts | 3 - src/stacks/list-stacks/index.ts | 11 -- src/stacks/stack-register/index.ts | 1 - src/utils/commands/index.ts | 25 ---- src/utils/globals/index.ts | 4 +- src/utils/json/has-key-in-object.test.ts | 70 +++++++++++ src/utils/json/index.ts | 37 +++++- 20 files changed, 257 insertions(+), 609 deletions(-) delete mode 100644 src/rules/front-app-debug/__mocks__/constants.ts delete mode 100644 src/rules/front-app-debug/apply.test.ts delete mode 100644 src/rules/front-app-debug/constants.ts delete mode 100644 src/rules/front-app-debug/index.ts delete mode 100644 src/rules/front-app-debug/should-be-applied.test.ts delete mode 100644 src/utils/commands/index.ts create mode 100644 src/utils/json/has-key-in-object.test.ts diff --git a/src/index.ts b/src/index.ts index c2a2fe4b..499be00d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,6 +12,8 @@ import { YesNo, Ok } from './choice/index'; import { generateReport } from './templating'; import Globals from './utils/globals/index'; +init(); + class ProjectFillerCli extends Command { static description = 'describe the command here'; static path: string = './'; @@ -90,8 +92,6 @@ class ProjectFillerCli extends Command { return; } - init(); - if (runFlags.debug) { logger.level = 'debug'; } @@ -178,18 +178,7 @@ class ProjectFillerCli extends Command { Object.entries(answers).forEach(([_, answer], i) => { const apply = foundRules[i].apply; if (apply) { - cli.action.start('Applying rules, please wait'); - apply - .call(foundRules[i], answer) - .then(() => { - cli.action.stop('Rules applied ! Congratulations !'); - }) - .catch(err => { - logger.error( - 'An error occured while applying rules.', - ); - logger.debug(err); - }); + const applyResult = apply.call(foundRules[i], answer); } }); }) diff --git a/src/rules/front-app-debug/__mocks__/constants.ts b/src/rules/front-app-debug/__mocks__/constants.ts deleted file mode 100644 index b7c98258..00000000 --- a/src/rules/front-app-debug/__mocks__/constants.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { LaunchConf, LaunchConfFile } from '../constants'; -import { Constructor } from '../../../stacks/stack-register/index'; -import VueJS from '../../../stacks/vue-js/index'; -import Stack from '../../../stacks/stack'; -export const configs: { - [key: string]: { stack: Constructor; confs: LaunchConf[] }; -} = { - vuejs: { - stack: VueJS, - confs: [ - { - type: 'firefox', - }, - { - type: 'chrome', - }, - ], - }, -}; - -export const vscodeConfig: LaunchConfFile = { - version: '0.2.0', - configurations: [], -}; diff --git a/src/rules/front-app-debug/apply.test.ts b/src/rules/front-app-debug/apply.test.ts deleted file mode 100644 index 774f6875..00000000 --- a/src/rules/front-app-debug/apply.test.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { ListStacks } from '../../stacks/list-stacks/index'; -import { Constructor } from '../../stacks/stack-register/index'; -import VueJS from '../../stacks/vue-js/index'; -import { FrontAppDebug } from './index'; -import Globals from '../../utils/globals'; -import Stack from '../../stacks/stack'; - -Globals.rootPath = 'front-app-debug/'; - -const fs = require('fs-extra'); -const launchFilePath = `${Globals.rootPath}.vscode/launch.json`; - -jest.mock('./constants'); - -afterEach(() => { - jest.resetAllMocks(); - jest.resetModules(); -}); - -test('should add missing configurations', () => { - const mockLaunchFile = { - version: '0.2.0', - - configurations: [ - { - type: 'firefox', - }, - ], - }; - - const resultLaunchFile = { - version: '0.2.0', - configurations: [ - { - type: 'firefox', - }, - { - type: 'chrome', - }, - ], - }; - - ListStacks.stackIsAvailable = jest.fn( - async (stackCtor: Constructor) => { - if (stackCtor === VueJS) { - return true; - } - return false; - }, - ); - - jest.mock(launchFilePath, () => mockLaunchFile, { virtual: true }); - - fs.writeJSON = jest.fn(); - - const frontAppDebug = new FrontAppDebug(); - - return frontAppDebug.apply(true).then(() => { - expect(fs.writeJSON).toBeCalledWith(launchFilePath, resultLaunchFile, { - spaces: '\t', - }); - }); -}); diff --git a/src/rules/front-app-debug/constants.ts b/src/rules/front-app-debug/constants.ts deleted file mode 100644 index df4d7398..00000000 --- a/src/rules/front-app-debug/constants.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { Constructor } from './../../stacks/stack-register/index'; -import { LaunchConf } from './constants'; -import VueJS from '../../stacks/vue-js'; -import Stack from '../../stacks/stack'; -import Angular from '../../stacks/angular'; -import { React } from '../../stacks/react'; -export interface LaunchConfFile { - version: string; - configurations: LaunchConf[]; -} - -export interface LaunchConf { - type: string; - name?: string; - request?: string; - url?: string; - webRoot?: string; - pathMappings?: object[]; - breakOnLoad?: boolean; - sourceMapPathOverrides?: object; - reAttach?: boolean; - file?: string; - addonPath?: string; -} - -export const configs: { - [key: string]: { stack: Constructor; confs: LaunchConf[] }; -} = { - vuejs: { - stack: VueJS, - confs: [ - { - type: 'firefox', - request: 'launch', - name: 'vuejs: firefox', - url: 'http://localhost:8080', - webRoot: '${workspaceFolder}/src', - pathMappings: [{ url: 'webpack:///src/', path: '${webRoot}/' }], - }, - { - type: 'chrome', - request: 'launch', - name: 'vuejs: chrome', - url: 'http://localhost:8080', - webRoot: '${workspaceFolder}/src', - breakOnLoad: true, - sourceMapPathOverrides: { - 'webpack:///./src/*': '${webRoot}/*', - }, - }, - ], - }, - angular: { - stack: Angular, - confs: [ - { - name: 'Launch index.html', - type: 'firefox', - request: 'launch', - reAttach: true, - file: '${workspaceFolder}/index.html', - }, - { - name: 'Launch localhost', - type: 'firefox', - request: 'launch', - reAttach: true, - url: 'http://localhost/index.html', - webRoot: '${workspaceFolder}', - }, - { - name: 'Attach', - type: 'firefox', - request: 'attach', - }, - { - name: 'Launch WebExtension', - type: 'firefox', - request: 'launch', - reAttach: true, - addonPath: '${workspaceFolder}', - }, - { - type: 'chrome', - request: 'launch', - name: 'Launch Chrome against localhost', - url: 'http://localhost:8080', - webRoot: '${workspaceFolder}', - }, - ], - }, - react: { - stack: React, - confs: [ - { - name: 'Chrome', - type: 'chrome', - request: 'launch', - url: 'http://localhost:3000', - webRoot: '${workspaceFolder}/src', - sourceMapPathOverrides: { - 'webpack:///src/*': '${webRoot}/*', - }, - }, - ], - }, -}; - -export const vscodeConfig: LaunchConfFile = { - version: '0.2.0', - configurations: [], -}; diff --git a/src/rules/front-app-debug/index.ts b/src/rules/front-app-debug/index.ts deleted file mode 100644 index 6252469c..00000000 --- a/src/rules/front-app-debug/index.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { Constructor } from './../../stacks/stack-register/index'; -import { ListStacks } from './../../stacks/list-stacks/index'; -import { RuleRegister } from '../rule-register'; -import { StackRegister } from '../../stacks/stack-register'; -import VueJS from '../../stacks/vue-js'; -import Angular from '../../stacks/angular'; -import { React } from '../../stacks/react'; -import { YesNo } from '../../choice'; -import Globals from '../../utils/globals'; -import { vscodeConfig, LaunchConfFile, configs, LaunchConf } from './constants'; -import Stack from '../../stacks/stack'; -import { pathExistsInJSON } from '../../utils/json'; -import * as fs from 'fs-extra'; - -@RuleRegister.register -@StackRegister.registerRuleForStacks([VueJS, Angular, React]) -export class FrontAppDebug { - private parsedLaunchConf: LaunchConfFile; - private existingConfigs: LaunchConf[] = []; - private launchConfFilePath = `${Globals.rootPath}.vscode/launch.json`; - private missingConfigurations: LaunchConf[] = []; - private initialized: boolean = false; - private readonly stacksToCheck: Array> = [ - VueJS, - Angular, - React, - ]; - private foundStacks: Array> = []; - - constructor() { - try { - this.parsedLaunchConf = require(`${Globals.rootPath}.vscode/launch.json`); - } catch (err) { - this.parsedLaunchConf = vscodeConfig; - } - } - - async init() { - if (!this.initialized) { - this.foundStacks = this.stacksToCheck.filter(async stackToCheck => { - return await ListStacks.stackIsAvailable(stackToCheck); - }); - - if (pathExistsInJSON(this.parsedLaunchConf, ['configurations'])) { - this.existingConfigs = this.parsedLaunchConf.configurations; - } - - this.missingConfigurations = this.getMissingConfigurations(); - } - return Promise.resolve(); - } - - async shouldBeApplied(): Promise { - return this.init().then(() => { - return this.getMissingConfigurations().length !== 0; - }); - } - - async apply(apply: boolean): Promise { - return this.init().then(() => { - this.addMissingConfigurations(this.missingConfigurations); - return fs.writeJSON(this.launchConfFilePath, this.parsedLaunchConf, { - spaces: '\t', - }); - }); - } - - private addMissingConfigurations(missingConfigurations: LaunchConf[]): void { - missingConfigurations.forEach(missingConf => { - this.parsedLaunchConf.configurations.push(missingConf); - }); - } - - private getMissingConfigurations(): LaunchConf[] { - return this.foundStacks.reduce( - (prevMissingConfig, foundStack: Constructor, i) => { - const configsToAddStackName = Object.values(configs).find(obj => { - return obj.stack === foundStack; - }); - - let configsToAdd; - - if (configsToAddStackName !== undefined) { - configsToAdd = configsToAddStackName.confs.filter(configToAdd => { - return !this.existingConfigs.some(existingConfig => { - return configToAdd.type === existingConfig.type; - }); - }); - return [...prevMissingConfig, ...configsToAdd]; - } - - return [...prevMissingConfig]; - }, - [], - ); - } - - getName(): string { - return 'Front Application Debugging'; - } - - getShortDescription(): string { - return 'In order to debug front-end applications made with Vue, Angular or React, this rule adds the config file in .vscode folder.'; - } - - getLongDescription() { - return 'Laborum exercitation incididunt nulla veniam labore esse. Pariatur adipisicing sint aliqua adipisicing culpa consequat reprehenderit excepteur eiusmod. Est irure voluptate fugiat enim minim laborum. Magna anim eiusmod consectetur voluptate. Proident ad ex laborum in adipisicing sit minim aliquip duis. Do non voluptate mollit officia consequat proident ex mollit dolore qui esse sit reprehenderit.'; - } - - getPromptType() { - return 'list'; - } - - getChoices() { - return YesNo; - } -} diff --git a/src/rules/front-app-debug/should-be-applied.test.ts b/src/rules/front-app-debug/should-be-applied.test.ts deleted file mode 100644 index d565b4d7..00000000 --- a/src/rules/front-app-debug/should-be-applied.test.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { Constructor } from './../../stacks/stack-register/index'; -import { ListStacks } from './../../stacks/list-stacks/index'; -import { FrontAppDebug } from '.'; -import Globals from '../../utils/globals'; -import Stack from '../../stacks/stack'; -import { configs } from './__mocks__/constants'; -import VueJS from '../../stacks/vue-js/index'; - -Globals.rootPath = 'front-app-debug/'; - -const fs = require('fs-extra'); -const launchFilePath = `${Globals.rootPath}.vscode/launch.json`; - -jest.mock('./constants'); - -afterEach(() => { - jest.resetAllMocks(); - jest.resetModules(); -}); - -ListStacks.stackIsAvailable = jest.fn(async (stackCtor: Constructor) => { - if (stackCtor === VueJS) { - return true; - } - return false; -}); - -test('should return true if .vscode/launch.json does not exist', () => { - jest.mock( - launchFilePath, - () => { - throw new Error(); - }, - { virtual: true }, - ); - - const frontAppDebug = new FrontAppDebug(); - - return frontAppDebug.shouldBeApplied().then(result => { - expect(result).toBeTruthy(); - }); -}); - -test('should return true if .vscode/launch.json misses configurations', () => { - const mockLaunchFile = { - configurations: [ - { - type: 'firefox', - }, - ], - }; - - ListStacks.stackIsAvailable = jest.fn( - async (stackCtor: Constructor) => { - if (stackCtor === VueJS) { - return true; - } - return false; - }, - ); - - jest.mock(launchFilePath, () => mockLaunchFile, { virtual: true }); - - const frontAppDebug = new FrontAppDebug(); - - return frontAppDebug.shouldBeApplied().then(result => { - expect(result).toBeTruthy(); - }); -}); - -test('should return false if .vscode/launch.json has all configurations', () => { - const mockLaunchFile = { - configurations: configs.vuejs.confs, - }; - - ListStacks.stackIsAvailable = jest.fn( - async (stackCtor: Constructor) => { - if (stackCtor === VueJS) { - return true; - } - - return false; - }, - ); - - jest.mock(launchFilePath, () => mockLaunchFile, { virtual: true }); - - const frontAppDebug = new FrontAppDebug(); - - return frontAppDebug.shouldBeApplied().then(result => { - expect(result).toBeFalsy(); - }); -}); diff --git a/src/rules/husky/apply.test.ts b/src/rules/husky/apply.test.ts index 719f7695..d112bafe 100644 --- a/src/rules/husky/apply.test.ts +++ b/src/rules/husky/apply.test.ts @@ -1,60 +1,43 @@ import Node from '../../stacks/node/index'; import { Husky } from './index'; import Globals from '../../utils/globals/index'; -import { logger } from '../../logger'; -const commands = require('../../utils/commands/index'); Globals.rootPath = 'husky/'; +const util = require('util'); + +const cp = require('child_process'); +jest.mock('child_process'); + const fs = require('fs-extra'); jest.mock('fs-extra'); -logger.info = jest.fn(); +require('../../logger'); +jest.mock('../../logger'); afterEach(() => { jest.resetAllMocks(); jest.resetModules(); }); -test('Method apply() should add husky to devDependencies and pre-push hook', () => { +test('Method apply() should add husky to devDependencies', () => { jest.mock(`${Globals.rootPath}package.json`, () => ({}), { virtual: true }); const husky = new Husky(); const node = new Node(); - const finalPackage = { - husky: { - hooks: { - 'pre-push': 'exit 1', - }, - }, - }; - node.isAvailable = jest.fn(() => { return Promise.resolve(true); }); - fs.readJSON = jest.fn(() => { - return Promise.resolve({}); - }); - - fs.writeJSON = jest.fn(() => { - return Promise.resolve(); - }); - - commands.installNpmDevDep = jest.fn(() => { - return Promise.resolve(); + util.promisify = jest.fn((exec: (cmd: string) => {}) => { + return (cmd: string) => { + exec(cmd); + return Promise.resolve(); + }; }); return husky.apply(true).then(() => { - expect(commands.installNpmDevDep).toBeCalledWith('husky'); - expect(fs.writeJSON).toBeCalledWith(Globals.packageJSONPath, finalPackage, { - spaces: '\t', - }); - expect(logger.info).toBeCalledWith( - `Husky Rule: Succesfully written pre-push hook to ${ - Globals.packageJSONPath - }. You may update this hook with a npm script for it to launch before pushing to git.`, - ); + expect(cp.exec).toBeCalled(); }); }); diff --git a/src/rules/husky/index.ts b/src/rules/husky/index.ts index 90f4942d..1b1c9aaf 100644 --- a/src/rules/husky/index.ts +++ b/src/rules/husky/index.ts @@ -5,70 +5,64 @@ import { logger } from '../../logger'; import Node from '../../stacks/node'; import TypeScript from '../../stacks/typescript'; import * as fs from 'fs-extra'; -import { hasDevDependency, pathExistsInJSON } from '../../utils/json'; +import * as util from 'util'; +import * as cp from 'child_process'; +import { hasDevDependencies } from '../../utils/json'; import Globals from '../../utils/globals'; -import { installNpmDevDep } from '../../utils/commands'; @RuleRegister.register @StackRegister.registerRuleForStacks([Node, TypeScript]) export class Husky { + private packagePath: string; private parsedPackage: any; constructor() { - this.parsedPackage = require(Globals.packageJSONPath); + this.packagePath = `${Globals.rootPath}package.json`; + this.parsedPackage = require(this.packagePath); } async apply(apply: boolean): Promise { if (apply) { - return installNpmDevDep('husky').then(() => { - return this.writeHuskyHook(); - }); - } - } + const exec = util.promisify(cp.exec); - private async writeHuskyHook(): Promise { - if (!pathExistsInJSON(this.parsedPackage, ['husky', 'hooks', 'pre-push'])) { - return fs - .readJSON(Globals.packageJSONPath, { encoding: 'utf-8' }) - .then(parsed => { + return exec('npm i -DE husky', { cwd: Globals.rootPath }) + .then((out: { stdout: string; stderr: string }) => { + if (out !== undefined && out.stderr !== undefined) { + throw new Error(out.stderr); + } + return fs.readFile(this.packagePath, { encoding: 'utf-8' }); + }) + .then(data => { + const parsed = JSON.parse(data); parsed.husky = { hooks: { 'pre-push': 'exit 1', }, }; - return fs - .writeJSON(Globals.packageJSONPath, parsed, { - spaces: '\t', - }) - .then(() => { - logger.info( - `Husky Rule: Succesfully written pre-push hook to ${ - Globals.packageJSONPath - }. You may update this hook with a npm script for it to launch before pushing to git.`, - ); - }) - .catch(err => { - logger.error( - `Husky Rule: Error trying to write pre-push hook to ${ - Globals.packageJSONPath - }`, - ); - logger.debug(err); - }); - }) - .catch(err => { - logger.error( - `Husky Rule: Error trying to read ${Globals.packageJSONPath} file.`, + return fs.writeFile( + this.packagePath, + JSON.stringify(parsed, null, '\t'), + { + encoding: 'utf-8', + }, ); - logger.debug(err); + }) + .catch((err: Error) => { + logger.error(err); }); } - return Promise.resolve(); } - async shouldBeApplied(): Promise { - return !hasDevDependency(this.parsedPackage, 'husky'); + async shouldBeApplied() { + return !this.isInDevDep(); + } + + isInDevDep(): boolean { + return ( + hasDevDependencies(this.parsedPackage) && + this.parsedPackage.devDependencies.husky !== undefined + ); } getName(): string { @@ -84,7 +78,7 @@ export class Husky { } getPromptType() { - return 'list'; + return 'checkbox'; } getChoices() { diff --git a/src/rules/linter/apply.test.ts b/src/rules/linter/apply.test.ts index 60c1640f..176dc779 100644 --- a/src/rules/linter/apply.test.ts +++ b/src/rules/linter/apply.test.ts @@ -2,7 +2,10 @@ import { Linter } from './index'; import { ListStacks } from '../../stacks/list-stacks/index'; import Stack from '../../stacks/stack/index'; import Globals from '../../utils/globals/index'; -const commands = require('../../utils/commands/index'); +const util = require('util'); + +const cp = require('child_process'); +jest.mock('child_process'); const fs = require('fs-extra'); jest.mock('fs-extra'); @@ -40,8 +43,11 @@ test('should install tslint as devDependencies and create tslint.json', () => { const linterRule = new Linter(); - commands.installNpmDevDep = jest.fn(() => { - return Promise.resolve(); + util.promisify = jest.fn((exec: (cmd: string) => void) => { + return (cmd: string) => { + expect(cmd).toBe('npm i tslint typescript -DE'); + return Promise.resolve(); + }; }); return linterRule.apply(true); diff --git a/src/rules/linter/index.ts b/src/rules/linter/index.ts index c0d694bf..a47de41c 100644 --- a/src/rules/linter/index.ts +++ b/src/rules/linter/index.ts @@ -6,10 +6,11 @@ import { logger } from '../../logger/index'; import TypeScript from '../../stacks/typescript/index'; import Node from '../../stacks/node/index'; import * as fs from 'fs-extra'; -import { hasDevDependency } from '../../utils/json'; +import * as cp from 'child_process'; +import * as util from 'util'; +import { hasDevDependencies } from '../../utils/json'; import { YesNo } from '../../choice/index'; import Globals from '../../utils/globals'; -import { installNpmDevDep } from '../../utils/commands'; @RuleRegister.register @StackRegister.registerRuleForStacks([TypeScript, Node]) @@ -21,10 +22,6 @@ export class Linter { private linterChoice: string = ''; private linterhasConfigFile: boolean = false; private linterInDevDep: boolean = false; - private documentation: { [linter: string]: string } = { - tslint: 'https://palantir.github.io/tslint/usage/configuration/', - eslint: 'https://eslint.org/docs/user-guide/configuring', - }; constructor() { this.packageJSONPath = `${Globals.rootPath}package.json`; @@ -44,72 +41,88 @@ export class Linter { }, ); } + return; } async shouldBeApplied() { return this.init().then(async () => { this.linterhasConfigFile = await this.hasConfigFile(this.linterChoice); - this.linterInDevDep = hasDevDependency( - this.parsedPackageJSON, - this.linterChoice, - ); + this.linterInDevDep = this.isInDevDep(this.linterChoice); return !this.linterInDevDep || !this.linterhasConfigFile; }); } - async apply(apply: boolean): Promise { - if (apply) { - return this.init().then(() => { - if (this.linterInDevDep) { - logger.info(`${this.linterChoice} already installed.`); - } else { - const linterToInstall = - this.linterChoice === 'tslint' ? 'tslint typescript' : 'eslint'; + async apply(apply: boolean) { + return this.init().then(() => { + if (apply) { + const exec = util.promisify(cp.exec); + return this.init() + .then(() => { + if (!this.linterInDevDep) { + const installCmd = + this.linterChoice === 'tslint' + ? 'npm i tslint typescript -DE' + : 'npm i eslint -DE'; - return installNpmDevDep(linterToInstall).then(() => { + return exec(installCmd, { cwd: Globals.rootPath }) + .then(() => { + logger.info(`Installed ${this.linterChoice} succesfully`); + }) + .catch(() => { + logger.error( + `Could notCould not install ${ + this.linterChoice + }, try installing it using "${installCmd}" command.`, + ); + }); + } else { + logger.info(`${this.linterChoice} already installed.`); + } + }) + .then(() => { if (!this.linterhasConfigFile) { - return this.writeLinterFile(); + const documentation: { [linter: string]: string } = { + tslint: + 'https://palantir.github.io/tslint/usage/configuration/', + eslint: 'https://eslint.org/docs/user-guide/configuring', + }; + this.writeLinterFile() + .then(() => { + logger.info( + ` ${ + this.linterChoice + }.json succesfully written to root folder. You may add more rules if you like, find documentation at : ${ + documentation[this.linterChoice] + }`, + ); + }) + .catch(err => { + logger.error(`Error writing to ${this.linterChoice} file.`); + logger.debug(err); + }); } else { logger.info(`${this.linterChoice}.json file already existing.`); } }); - } - }); - } + } + }); } private writeLinterFile() { return fs .ensureFile(this.linterPaths[this.linterChoice]) .catch(err => { - logger.error(`Error creating ${this.linterPaths[this.linterChoice]}`); + logger.error(`Error creating ${this.linterChoice}.json`); logger.debug(err); - return Promise.reject(err); + return; }) .then(() => { - return fs - .writeJson( - this.linterPaths[this.linterChoice], - linterJSON[this.linterChoice], - { spaces: '\t' }, - ) - .then(() => { - logger.info( - `Succesfully written ${ - this.linterPaths[this.linterChoice] - }. You may add more rules if you like, find documentation at : ${ - this.documentation[this.linterChoice] - }`, - ); - }) - .catch(err => { - logger.error( - `Linter Rule: Error trying to write to ${ - this.linterPaths[this.linterChoice] - }`, - ); - }); + return fs.writeJson( + this.linterPaths[this.linterChoice], + linterJSON[this.linterChoice], + { spaces: '\t' }, + ); }); } @@ -117,6 +130,13 @@ export class Linter { return fs.pathExists(this.linterPaths[linter]); } + isInDevDep(linter: string): boolean { + return ( + hasDevDependencies(this.parsedPackageJSON) && + this.parsedPackageJSON.devDependencies[linter] !== undefined + ); + } + getName() { return 'Linter'; } diff --git a/src/rules/nodemon/index.ts b/src/rules/nodemon/index.ts index dd99b8a2..5a54a72d 100644 --- a/src/rules/nodemon/index.ts +++ b/src/rules/nodemon/index.ts @@ -9,15 +9,16 @@ import * as cp from 'child_process'; import Node from '../../stacks/node'; import { logger } from '../../logger/index'; import * as fs from 'fs-extra'; -import { installNpmDevDep } from '../../utils/commands'; @RuleRegister.register @StackRegister.registerRuleForStacks([Node]) export class Nodemon { private parsedJSON: any; + private packagePath: string; constructor() { - this.parsedJSON = require(Globals.packageJSONPath); + this.packagePath = Globals.rootPath + 'package.json'; + this.parsedJSON = require(this.packagePath); } async shouldBeApplied(): Promise { @@ -39,13 +40,19 @@ export class Nodemon { if (apply) { const exec = util.promisify(cp.exec); - return installNpmDevDep('nodemon') - .then(() => { + exec('npm i -DE nodemon', { cwd: Globals.rootPath }) + .then((out: { stdout: string; stderr: string }) => { + if (out !== undefined && out.stderr !== undefined) { + logger.error( + 'Nodemon: Error trying to install Nodemon as a devDependency, please run in debug mode to know more', + ); + logger.debug(out.stderr); + throw new Error(out.stderr); + } logger.info( 'Nodemon: Succesfully installed nodemon as dev dependency. Checking for existing script.', ); - - fs.readJSON(Globals.packageJSONPath, { encoding: 'utf-8' }) + fs.readJSON(this.packagePath, { encoding: 'utf-8' }) .then(packageJSON => { if (!pathExistsInJSON(packageJSON, ['scripts', 'nodemon'])) { if (!pathExistsInJSON(packageJSON, ['scripts'])) { @@ -54,9 +61,7 @@ export class Nodemon { packageJSON.scripts.nodemon = 'nodemon'; } - fs.writeJSON(Globals.packageJSONPath, packageJSON, { - spaces: '\t', - }) + fs.writeJSON(this.packagePath, packageJSON, { spaces: '\t' }) .then(() => { logger.info( 'Nodemon: Succesfully added nodemon script. Run "npm run nodemon" to run it.', @@ -65,7 +70,7 @@ export class Nodemon { .catch(err => { logger.error( `Nodemon: Error trying to write to ${ - Globals.packageJSONPath + this.packagePath }, please run in debug mode to know more.`, ); logger.debug(err); @@ -77,7 +82,7 @@ export class Nodemon { .catch(err => { logger.error( `Nodemon: Error trying to read ${ - Globals.packageJSONPath + this.packagePath }, please run in debug mode to know more`, ); logger.debug(err); diff --git a/src/rules/prettier/index.ts b/src/rules/prettier/index.ts index e39bbb64..e02f9879 100644 --- a/src/rules/prettier/index.ts +++ b/src/rules/prettier/index.ts @@ -5,7 +5,7 @@ import * as util from 'util'; import * as cp from 'child_process'; import TypeScript from '../../stacks/typescript'; import Node from '../../stacks/node'; -import { hasDevDependency } from '../../utils/json/index'; +import { hasDevDependencies } from '../../utils/json/index'; import Globals from '../../utils/globals'; /** * Looks for Prettier dependency in package.json, and add it if necessary. @@ -40,7 +40,14 @@ export class Prettier { } async shouldBeApplied() { - return !hasDevDependency(this.parsedPackage, 'prettier'); + return !this.isInDevDep(); + } + + isInDevDep() { + return ( + hasDevDependencies(this.parsedPackage) && + this.parsedPackage.devDependencies.prettier !== undefined + ); } getName() { diff --git a/src/rules/vscode-extensions/constants.ts b/src/rules/vscode-extensions/constants.ts index 1a971956..99cc8e42 100644 --- a/src/rules/vscode-extensions/constants.ts +++ b/src/rules/vscode-extensions/constants.ts @@ -11,15 +11,11 @@ export const possibleChoices: ChoiceList = { { name: 'TSLint', value: 'ms-vscode.vscode-typescript-tslint-plugin' }, { name: 'Angular Console', value: 'nrwl.angular-console' }, { name: 'Prettier', value: 'esbenp.prettier-vscode' }, - { name: 'Debugger for Chrome', value: 'msjsdiag.debugger-for-chrome' }, - { name: 'Debugger for Firefox', value: 'hbenl.vscode-firefox-debug' }, ], VueJS: [ { name: 'Vetur', value: 'octref.vetur' }, { name: 'ESLint', value: 'dbaeumer.vscode-eslint' }, { name: 'Prettier', value: 'esbenp.prettier-vscode' }, - { name: 'Debugger for Chrome', value: 'msjsdiag.debugger-for-chrome' }, - { name: 'Debugger for Firefox', value: 'hbenl.vscode-firefox-debug' }, ], TypeScript: [{ name: 'Prettier', value: 'esbenp.prettier-vscode' }], Node: [{ name: 'Prettier', value: 'esbenp.prettier-vscode' }], diff --git a/src/stacks/github/index.ts b/src/stacks/github/index.ts index 9416c3cf..acb71341 100644 --- a/src/stacks/github/index.ts +++ b/src/stacks/github/index.ts @@ -27,9 +27,6 @@ export default class GitHub { }); }) .catch(err => { - if (err.code === 'ENOENT') { - return false; - } logger.debug(err); return false; }); diff --git a/src/stacks/list-stacks/index.ts b/src/stacks/list-stacks/index.ts index 0fd1b6de..2ef4f062 100644 --- a/src/stacks/list-stacks/index.ts +++ b/src/stacks/list-stacks/index.ts @@ -1,6 +1,5 @@ import { StackRegister, Constructor } from '../stack-register'; import Stack from '../stack'; -import Globals from '../../utils/globals'; export class ListStacks { static stacks: Stack[]; @@ -47,14 +46,4 @@ export class ListStacks { }); }); } - - static findAvailableStack( - ctor: Constructor, - ): Promise { - return this.findAvailableStackIn(ctor, Globals.rootPath); - } - - static async stackIsAvailable(ctor: Constructor): Promise { - return this.findAvailableStack(ctor) !== undefined; - } } diff --git a/src/stacks/stack-register/index.ts b/src/stacks/stack-register/index.ts index b44259c7..5818fa2b 100644 --- a/src/stacks/stack-register/index.ts +++ b/src/stacks/stack-register/index.ts @@ -67,7 +67,6 @@ export class StackRegister { T extends Constructor >(stackCtor: T) { return (subStack: S) => { - StackRegister.rulesByStack[subStack.name] = []; if (StackRegister.subStacks[stackCtor.name] === undefined) { StackRegister.subStacks[stackCtor.name] = [subStack]; } else { diff --git a/src/utils/commands/index.ts b/src/utils/commands/index.ts deleted file mode 100644 index 24235feb..00000000 --- a/src/utils/commands/index.ts +++ /dev/null @@ -1,25 +0,0 @@ -import * as util from 'util'; -import * as cp from 'child_process'; -import { logger } from '../../logger'; -import Globals from '../globals'; - -const exec = util.promisify(cp.exec); - -export function installNpmDevDep(dependency: string) { - return execInRootpath(`npm i ${dependency} -DE`) - .then(() => { - logger.info(`Succesfully installed ${dependency}.`); - return Promise.resolve(); - }) - .catch(err => { - logger.error( - `Error trying to install ${dependency}, try to install it with 'npm i ${dependency} -DE' command.`, - ); - logger.debug(err); - return Promise.reject(err); - }); -} - -export function execInRootpath(cmd: string) { - return exec(cmd, { cwd: Globals.rootPath }); -} diff --git a/src/utils/globals/index.ts b/src/utils/globals/index.ts index 4a98ee43..b5aa8e93 100644 --- a/src/utils/globals/index.ts +++ b/src/utils/globals/index.ts @@ -1,7 +1,5 @@ import * as Path from 'path'; export default class Globals { static rootPath: string = Path.resolve('.') + '/'; - static get packageJSONPath(): string { - return `${Globals.rootPath}package.json`; - } + static readonly packageJSONPath: string = `${Globals.rootPath}package.json`; } diff --git a/src/utils/json/has-key-in-object.test.ts b/src/utils/json/has-key-in-object.test.ts new file mode 100644 index 00000000..5b4eb423 --- /dev/null +++ b/src/utils/json/has-key-in-object.test.ts @@ -0,0 +1,70 @@ +import { hasKeyInObject } from './index'; +const JSONFilePath = 'test/test.json'; + +afterEach(() => { + jest.resetModules(); +}); + +test('should return false if JSON file does not exist', () => { + expect(hasKeyInObject(JSONFilePath, 'testObj', 'testKey')).toBeFalsy(); +}); + +test('should return false if JSON file is empty', () => { + jest.mock(JSONFilePath, () => ({}), { virtual: true }); + + expect(hasKeyInObject(JSONFilePath, 'testObj', 'testKey')).toBeFalsy(); +}); + +test('should return false if key to find not direct child of given object', () => { + const mockJSON = { + obj: { + dep1: 'dep1Val', + dep2: 'dep2Val', + subObject: { + keyToFind: 'val', + }, + }, + keyToFind: 'val', + otherObject: { + keyTofind: 'val', + }, + }; + + jest.mock(JSONFilePath, () => mockJSON, { virtual: true }); + + expect(hasKeyInObject(JSONFilePath, 'obj', 'keyToFind')).toBeFalsy(); +}); + +test('should return false if obj does not exist', () => { + const mockJSON = { + notObj: { + dep1: 'dep1Val', + dep2: 'dep2Val', + subObject: { + keyToFind: 'val', + }, + }, + keyToFind: 'val', + otherObject: { + keyTofind: 'val', + }, + }; + + jest.mock(JSONFilePath, () => mockJSON, { virtual: true }); + + expect(hasKeyInObject(JSONFilePath, 'obj', 'keyToFind')).toBeFalsy(); +}); + +test('should return true if key to find is direct child of given object', () => { + const mockJSON = { + obj: { + dep1: 'dep1Val', + dep2: 'dep2Val', + keyToFind: 'val', + }, + }; + + jest.mock(JSONFilePath, () => mockJSON, { virtual: true }); + + expect(hasKeyInObject(JSONFilePath, 'obj', 'keyToFind')).toBeTruthy(); +}); diff --git a/src/utils/json/index.ts b/src/utils/json/index.ts index aadb6470..c1664d93 100644 --- a/src/utils/json/index.ts +++ b/src/utils/json/index.ts @@ -1,9 +1,32 @@ -export function hasDevDependency(parsedJSON: object, dependency: string) { - return pathExistsInJSON(parsedJSON, ['devDependencies', dependency]); +export function hasDevDependencies(packageJSON: any): boolean { + return packageJSON.devDependencies !== undefined; } -export function hasDependency(parsedJSON: object, dependency: string) { - return pathExistsInJSON(parsedJSON, ['dependencies', dependency]); +export function hasDevDependency(path: string, dependency: string) { + return hasKeyInObject(`${path}package.json`, 'devDependencies', dependency); +} + +export function hasDependency(path: string, dependency: string) { + return hasKeyInObject(`${path}package.json`, 'dependencies', dependency); +} + +export function hasKeyInObject( + JSONFilePath: string, + obj: string, + keyToFind: string, +): boolean { + try { + const parsedJSON = require(JSONFilePath); + if (parsedJSON[obj] !== undefined) { + return parsedJSON[obj][keyToFind] !== undefined; + } + return false; + } catch (err) { + if (err.code === 'MODULE_NOT_FOUND') { + return false; + } + throw err; + } } export function pathExistsInJSON(parsedJSON: any, objs: string[]): boolean { @@ -15,3 +38,9 @@ export function pathExistsInJSON(parsedJSON: any, objs: string[]): boolean { parsedJSON[head] !== undefined && pathExistsInJSON(parsedJSON[head], tail) ); } + +export function JSONhasObj(JSONPath: string, obj: string) { + const parsedJSON = require(JSONPath); + + return parsedJSON[obj] !== undefined; +} From 0e91a4bc9de48b7a66f6766b130c71a26a556664 Mon Sep 17 00:00:00 2001 From: ig1na Date: Fri, 5 Jul 2019 12:06:34 +0200 Subject: [PATCH 3/4] Revert "Merge branch 'master' into develop" This reverts commit 44a1f1e8bf55258e5afac8050090d4c5a84ad322. From 5c88c3de515fb67070592d6599bedc52dbf4831b Mon Sep 17 00:00:00 2001 From: ig1na Date: Fri, 5 Jul 2019 15:46:37 +0200 Subject: [PATCH 4/4] Revert "Merge branch 'master' into front-app-debug" This reverts commit 743253fec3c80cb4dcffb21de8e368e1dd6136ac. --- src/index.ts | 17 ++- .../front-app-debug/__mocks__/constants.ts | 24 ++++ src/rules/front-app-debug/apply.test.ts | 63 ++++++++++ src/rules/front-app-debug/constants.ts | 112 +++++++++++++++++ src/rules/front-app-debug/index.ts | 117 ++++++++++++++++++ .../front-app-debug/should-be-applied.test.ts | 93 ++++++++++++++ src/rules/husky/apply.test.ts | 45 ++++--- src/rules/husky/index.ts | 76 ++++++------ src/rules/linter/apply.test.ts | 12 +- src/rules/linter/index.ts | 114 +++++++---------- src/rules/nodemon/index.ts | 5 +- src/rules/prettier/index.ts | 11 +- src/rules/vscode-extensions/constants.ts | 4 + src/stacks/github/index.ts | 3 + src/stacks/list-stacks/index.ts | 11 ++ src/stacks/stack-register/index.ts | 1 + src/utils/commands/index.ts | 25 ++++ src/utils/globals/index.ts | 4 +- src/utils/json/has-key-in-object.test.ts | 70 ----------- src/utils/json/index.ts | 37 +----- 20 files changed, 600 insertions(+), 244 deletions(-) create mode 100644 src/rules/front-app-debug/__mocks__/constants.ts create mode 100644 src/rules/front-app-debug/apply.test.ts create mode 100644 src/rules/front-app-debug/constants.ts create mode 100644 src/rules/front-app-debug/index.ts create mode 100644 src/rules/front-app-debug/should-be-applied.test.ts create mode 100644 src/utils/commands/index.ts delete mode 100644 src/utils/json/has-key-in-object.test.ts diff --git a/src/index.ts b/src/index.ts index 499be00d..c2a2fe4b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,8 +12,6 @@ import { YesNo, Ok } from './choice/index'; import { generateReport } from './templating'; import Globals from './utils/globals/index'; -init(); - class ProjectFillerCli extends Command { static description = 'describe the command here'; static path: string = './'; @@ -92,6 +90,8 @@ class ProjectFillerCli extends Command { return; } + init(); + if (runFlags.debug) { logger.level = 'debug'; } @@ -178,7 +178,18 @@ class ProjectFillerCli extends Command { Object.entries(answers).forEach(([_, answer], i) => { const apply = foundRules[i].apply; if (apply) { - const applyResult = apply.call(foundRules[i], answer); + cli.action.start('Applying rules, please wait'); + apply + .call(foundRules[i], answer) + .then(() => { + cli.action.stop('Rules applied ! Congratulations !'); + }) + .catch(err => { + logger.error( + 'An error occured while applying rules.', + ); + logger.debug(err); + }); } }); }) diff --git a/src/rules/front-app-debug/__mocks__/constants.ts b/src/rules/front-app-debug/__mocks__/constants.ts new file mode 100644 index 00000000..b7c98258 --- /dev/null +++ b/src/rules/front-app-debug/__mocks__/constants.ts @@ -0,0 +1,24 @@ +import { LaunchConf, LaunchConfFile } from '../constants'; +import { Constructor } from '../../../stacks/stack-register/index'; +import VueJS from '../../../stacks/vue-js/index'; +import Stack from '../../../stacks/stack'; +export const configs: { + [key: string]: { stack: Constructor; confs: LaunchConf[] }; +} = { + vuejs: { + stack: VueJS, + confs: [ + { + type: 'firefox', + }, + { + type: 'chrome', + }, + ], + }, +}; + +export const vscodeConfig: LaunchConfFile = { + version: '0.2.0', + configurations: [], +}; diff --git a/src/rules/front-app-debug/apply.test.ts b/src/rules/front-app-debug/apply.test.ts new file mode 100644 index 00000000..774f6875 --- /dev/null +++ b/src/rules/front-app-debug/apply.test.ts @@ -0,0 +1,63 @@ +import { ListStacks } from '../../stacks/list-stacks/index'; +import { Constructor } from '../../stacks/stack-register/index'; +import VueJS from '../../stacks/vue-js/index'; +import { FrontAppDebug } from './index'; +import Globals from '../../utils/globals'; +import Stack from '../../stacks/stack'; + +Globals.rootPath = 'front-app-debug/'; + +const fs = require('fs-extra'); +const launchFilePath = `${Globals.rootPath}.vscode/launch.json`; + +jest.mock('./constants'); + +afterEach(() => { + jest.resetAllMocks(); + jest.resetModules(); +}); + +test('should add missing configurations', () => { + const mockLaunchFile = { + version: '0.2.0', + + configurations: [ + { + type: 'firefox', + }, + ], + }; + + const resultLaunchFile = { + version: '0.2.0', + configurations: [ + { + type: 'firefox', + }, + { + type: 'chrome', + }, + ], + }; + + ListStacks.stackIsAvailable = jest.fn( + async (stackCtor: Constructor) => { + if (stackCtor === VueJS) { + return true; + } + return false; + }, + ); + + jest.mock(launchFilePath, () => mockLaunchFile, { virtual: true }); + + fs.writeJSON = jest.fn(); + + const frontAppDebug = new FrontAppDebug(); + + return frontAppDebug.apply(true).then(() => { + expect(fs.writeJSON).toBeCalledWith(launchFilePath, resultLaunchFile, { + spaces: '\t', + }); + }); +}); diff --git a/src/rules/front-app-debug/constants.ts b/src/rules/front-app-debug/constants.ts new file mode 100644 index 00000000..df4d7398 --- /dev/null +++ b/src/rules/front-app-debug/constants.ts @@ -0,0 +1,112 @@ +import { Constructor } from './../../stacks/stack-register/index'; +import { LaunchConf } from './constants'; +import VueJS from '../../stacks/vue-js'; +import Stack from '../../stacks/stack'; +import Angular from '../../stacks/angular'; +import { React } from '../../stacks/react'; +export interface LaunchConfFile { + version: string; + configurations: LaunchConf[]; +} + +export interface LaunchConf { + type: string; + name?: string; + request?: string; + url?: string; + webRoot?: string; + pathMappings?: object[]; + breakOnLoad?: boolean; + sourceMapPathOverrides?: object; + reAttach?: boolean; + file?: string; + addonPath?: string; +} + +export const configs: { + [key: string]: { stack: Constructor; confs: LaunchConf[] }; +} = { + vuejs: { + stack: VueJS, + confs: [ + { + type: 'firefox', + request: 'launch', + name: 'vuejs: firefox', + url: 'http://localhost:8080', + webRoot: '${workspaceFolder}/src', + pathMappings: [{ url: 'webpack:///src/', path: '${webRoot}/' }], + }, + { + type: 'chrome', + request: 'launch', + name: 'vuejs: chrome', + url: 'http://localhost:8080', + webRoot: '${workspaceFolder}/src', + breakOnLoad: true, + sourceMapPathOverrides: { + 'webpack:///./src/*': '${webRoot}/*', + }, + }, + ], + }, + angular: { + stack: Angular, + confs: [ + { + name: 'Launch index.html', + type: 'firefox', + request: 'launch', + reAttach: true, + file: '${workspaceFolder}/index.html', + }, + { + name: 'Launch localhost', + type: 'firefox', + request: 'launch', + reAttach: true, + url: 'http://localhost/index.html', + webRoot: '${workspaceFolder}', + }, + { + name: 'Attach', + type: 'firefox', + request: 'attach', + }, + { + name: 'Launch WebExtension', + type: 'firefox', + request: 'launch', + reAttach: true, + addonPath: '${workspaceFolder}', + }, + { + type: 'chrome', + request: 'launch', + name: 'Launch Chrome against localhost', + url: 'http://localhost:8080', + webRoot: '${workspaceFolder}', + }, + ], + }, + react: { + stack: React, + confs: [ + { + name: 'Chrome', + type: 'chrome', + request: 'launch', + url: 'http://localhost:3000', + webRoot: '${workspaceFolder}/src', + sourceMapPathOverrides: { + 'webpack:///src/*': '${webRoot}/*', + }, + }, + ], + }, +}; + +export const vscodeConfig: LaunchConfFile = { + version: '0.2.0', + configurations: [], +}; diff --git a/src/rules/front-app-debug/index.ts b/src/rules/front-app-debug/index.ts new file mode 100644 index 00000000..6252469c --- /dev/null +++ b/src/rules/front-app-debug/index.ts @@ -0,0 +1,117 @@ +import { Constructor } from './../../stacks/stack-register/index'; +import { ListStacks } from './../../stacks/list-stacks/index'; +import { RuleRegister } from '../rule-register'; +import { StackRegister } from '../../stacks/stack-register'; +import VueJS from '../../stacks/vue-js'; +import Angular from '../../stacks/angular'; +import { React } from '../../stacks/react'; +import { YesNo } from '../../choice'; +import Globals from '../../utils/globals'; +import { vscodeConfig, LaunchConfFile, configs, LaunchConf } from './constants'; +import Stack from '../../stacks/stack'; +import { pathExistsInJSON } from '../../utils/json'; +import * as fs from 'fs-extra'; + +@RuleRegister.register +@StackRegister.registerRuleForStacks([VueJS, Angular, React]) +export class FrontAppDebug { + private parsedLaunchConf: LaunchConfFile; + private existingConfigs: LaunchConf[] = []; + private launchConfFilePath = `${Globals.rootPath}.vscode/launch.json`; + private missingConfigurations: LaunchConf[] = []; + private initialized: boolean = false; + private readonly stacksToCheck: Array> = [ + VueJS, + Angular, + React, + ]; + private foundStacks: Array> = []; + + constructor() { + try { + this.parsedLaunchConf = require(`${Globals.rootPath}.vscode/launch.json`); + } catch (err) { + this.parsedLaunchConf = vscodeConfig; + } + } + + async init() { + if (!this.initialized) { + this.foundStacks = this.stacksToCheck.filter(async stackToCheck => { + return await ListStacks.stackIsAvailable(stackToCheck); + }); + + if (pathExistsInJSON(this.parsedLaunchConf, ['configurations'])) { + this.existingConfigs = this.parsedLaunchConf.configurations; + } + + this.missingConfigurations = this.getMissingConfigurations(); + } + return Promise.resolve(); + } + + async shouldBeApplied(): Promise { + return this.init().then(() => { + return this.getMissingConfigurations().length !== 0; + }); + } + + async apply(apply: boolean): Promise { + return this.init().then(() => { + this.addMissingConfigurations(this.missingConfigurations); + return fs.writeJSON(this.launchConfFilePath, this.parsedLaunchConf, { + spaces: '\t', + }); + }); + } + + private addMissingConfigurations(missingConfigurations: LaunchConf[]): void { + missingConfigurations.forEach(missingConf => { + this.parsedLaunchConf.configurations.push(missingConf); + }); + } + + private getMissingConfigurations(): LaunchConf[] { + return this.foundStacks.reduce( + (prevMissingConfig, foundStack: Constructor, i) => { + const configsToAddStackName = Object.values(configs).find(obj => { + return obj.stack === foundStack; + }); + + let configsToAdd; + + if (configsToAddStackName !== undefined) { + configsToAdd = configsToAddStackName.confs.filter(configToAdd => { + return !this.existingConfigs.some(existingConfig => { + return configToAdd.type === existingConfig.type; + }); + }); + return [...prevMissingConfig, ...configsToAdd]; + } + + return [...prevMissingConfig]; + }, + [], + ); + } + + getName(): string { + return 'Front Application Debugging'; + } + + getShortDescription(): string { + return 'In order to debug front-end applications made with Vue, Angular or React, this rule adds the config file in .vscode folder.'; + } + + getLongDescription() { + return 'Laborum exercitation incididunt nulla veniam labore esse. Pariatur adipisicing sint aliqua adipisicing culpa consequat reprehenderit excepteur eiusmod. Est irure voluptate fugiat enim minim laborum. Magna anim eiusmod consectetur voluptate. Proident ad ex laborum in adipisicing sit minim aliquip duis. Do non voluptate mollit officia consequat proident ex mollit dolore qui esse sit reprehenderit.'; + } + + getPromptType() { + return 'list'; + } + + getChoices() { + return YesNo; + } +} diff --git a/src/rules/front-app-debug/should-be-applied.test.ts b/src/rules/front-app-debug/should-be-applied.test.ts new file mode 100644 index 00000000..d565b4d7 --- /dev/null +++ b/src/rules/front-app-debug/should-be-applied.test.ts @@ -0,0 +1,93 @@ +import { Constructor } from './../../stacks/stack-register/index'; +import { ListStacks } from './../../stacks/list-stacks/index'; +import { FrontAppDebug } from '.'; +import Globals from '../../utils/globals'; +import Stack from '../../stacks/stack'; +import { configs } from './__mocks__/constants'; +import VueJS from '../../stacks/vue-js/index'; + +Globals.rootPath = 'front-app-debug/'; + +const fs = require('fs-extra'); +const launchFilePath = `${Globals.rootPath}.vscode/launch.json`; + +jest.mock('./constants'); + +afterEach(() => { + jest.resetAllMocks(); + jest.resetModules(); +}); + +ListStacks.stackIsAvailable = jest.fn(async (stackCtor: Constructor) => { + if (stackCtor === VueJS) { + return true; + } + return false; +}); + +test('should return true if .vscode/launch.json does not exist', () => { + jest.mock( + launchFilePath, + () => { + throw new Error(); + }, + { virtual: true }, + ); + + const frontAppDebug = new FrontAppDebug(); + + return frontAppDebug.shouldBeApplied().then(result => { + expect(result).toBeTruthy(); + }); +}); + +test('should return true if .vscode/launch.json misses configurations', () => { + const mockLaunchFile = { + configurations: [ + { + type: 'firefox', + }, + ], + }; + + ListStacks.stackIsAvailable = jest.fn( + async (stackCtor: Constructor) => { + if (stackCtor === VueJS) { + return true; + } + return false; + }, + ); + + jest.mock(launchFilePath, () => mockLaunchFile, { virtual: true }); + + const frontAppDebug = new FrontAppDebug(); + + return frontAppDebug.shouldBeApplied().then(result => { + expect(result).toBeTruthy(); + }); +}); + +test('should return false if .vscode/launch.json has all configurations', () => { + const mockLaunchFile = { + configurations: configs.vuejs.confs, + }; + + ListStacks.stackIsAvailable = jest.fn( + async (stackCtor: Constructor) => { + if (stackCtor === VueJS) { + return true; + } + + return false; + }, + ); + + jest.mock(launchFilePath, () => mockLaunchFile, { virtual: true }); + + const frontAppDebug = new FrontAppDebug(); + + return frontAppDebug.shouldBeApplied().then(result => { + expect(result).toBeFalsy(); + }); +}); diff --git a/src/rules/husky/apply.test.ts b/src/rules/husky/apply.test.ts index d112bafe..719f7695 100644 --- a/src/rules/husky/apply.test.ts +++ b/src/rules/husky/apply.test.ts @@ -1,43 +1,60 @@ import Node from '../../stacks/node/index'; import { Husky } from './index'; import Globals from '../../utils/globals/index'; +import { logger } from '../../logger'; +const commands = require('../../utils/commands/index'); Globals.rootPath = 'husky/'; -const util = require('util'); - -const cp = require('child_process'); -jest.mock('child_process'); - const fs = require('fs-extra'); jest.mock('fs-extra'); -require('../../logger'); -jest.mock('../../logger'); +logger.info = jest.fn(); afterEach(() => { jest.resetAllMocks(); jest.resetModules(); }); -test('Method apply() should add husky to devDependencies', () => { +test('Method apply() should add husky to devDependencies and pre-push hook', () => { jest.mock(`${Globals.rootPath}package.json`, () => ({}), { virtual: true }); const husky = new Husky(); const node = new Node(); + const finalPackage = { + husky: { + hooks: { + 'pre-push': 'exit 1', + }, + }, + }; + node.isAvailable = jest.fn(() => { return Promise.resolve(true); }); - util.promisify = jest.fn((exec: (cmd: string) => {}) => { - return (cmd: string) => { - exec(cmd); - return Promise.resolve(); - }; + fs.readJSON = jest.fn(() => { + return Promise.resolve({}); + }); + + fs.writeJSON = jest.fn(() => { + return Promise.resolve(); + }); + + commands.installNpmDevDep = jest.fn(() => { + return Promise.resolve(); }); return husky.apply(true).then(() => { - expect(cp.exec).toBeCalled(); + expect(commands.installNpmDevDep).toBeCalledWith('husky'); + expect(fs.writeJSON).toBeCalledWith(Globals.packageJSONPath, finalPackage, { + spaces: '\t', + }); + expect(logger.info).toBeCalledWith( + `Husky Rule: Succesfully written pre-push hook to ${ + Globals.packageJSONPath + }. You may update this hook with a npm script for it to launch before pushing to git.`, + ); }); }); diff --git a/src/rules/husky/index.ts b/src/rules/husky/index.ts index 1b1c9aaf..90f4942d 100644 --- a/src/rules/husky/index.ts +++ b/src/rules/husky/index.ts @@ -5,64 +5,70 @@ import { logger } from '../../logger'; import Node from '../../stacks/node'; import TypeScript from '../../stacks/typescript'; import * as fs from 'fs-extra'; -import * as util from 'util'; -import * as cp from 'child_process'; -import { hasDevDependencies } from '../../utils/json'; +import { hasDevDependency, pathExistsInJSON } from '../../utils/json'; import Globals from '../../utils/globals'; +import { installNpmDevDep } from '../../utils/commands'; @RuleRegister.register @StackRegister.registerRuleForStacks([Node, TypeScript]) export class Husky { - private packagePath: string; private parsedPackage: any; constructor() { - this.packagePath = `${Globals.rootPath}package.json`; - this.parsedPackage = require(this.packagePath); + this.parsedPackage = require(Globals.packageJSONPath); } async apply(apply: boolean): Promise { if (apply) { - const exec = util.promisify(cp.exec); + return installNpmDevDep('husky').then(() => { + return this.writeHuskyHook(); + }); + } + } - return exec('npm i -DE husky', { cwd: Globals.rootPath }) - .then((out: { stdout: string; stderr: string }) => { - if (out !== undefined && out.stderr !== undefined) { - throw new Error(out.stderr); - } - return fs.readFile(this.packagePath, { encoding: 'utf-8' }); - }) - .then(data => { - const parsed = JSON.parse(data); + private async writeHuskyHook(): Promise { + if (!pathExistsInJSON(this.parsedPackage, ['husky', 'hooks', 'pre-push'])) { + return fs + .readJSON(Globals.packageJSONPath, { encoding: 'utf-8' }) + .then(parsed => { parsed.husky = { hooks: { 'pre-push': 'exit 1', }, }; - return fs.writeFile( - this.packagePath, - JSON.stringify(parsed, null, '\t'), - { - encoding: 'utf-8', - }, - ); + return fs + .writeJSON(Globals.packageJSONPath, parsed, { + spaces: '\t', + }) + .then(() => { + logger.info( + `Husky Rule: Succesfully written pre-push hook to ${ + Globals.packageJSONPath + }. You may update this hook with a npm script for it to launch before pushing to git.`, + ); + }) + .catch(err => { + logger.error( + `Husky Rule: Error trying to write pre-push hook to ${ + Globals.packageJSONPath + }`, + ); + logger.debug(err); + }); }) - .catch((err: Error) => { - logger.error(err); + .catch(err => { + logger.error( + `Husky Rule: Error trying to read ${Globals.packageJSONPath} file.`, + ); + logger.debug(err); }); } + return Promise.resolve(); } - async shouldBeApplied() { - return !this.isInDevDep(); - } - - isInDevDep(): boolean { - return ( - hasDevDependencies(this.parsedPackage) && - this.parsedPackage.devDependencies.husky !== undefined - ); + async shouldBeApplied(): Promise { + return !hasDevDependency(this.parsedPackage, 'husky'); } getName(): string { @@ -78,7 +84,7 @@ export class Husky { } getPromptType() { - return 'checkbox'; + return 'list'; } getChoices() { diff --git a/src/rules/linter/apply.test.ts b/src/rules/linter/apply.test.ts index 176dc779..60c1640f 100644 --- a/src/rules/linter/apply.test.ts +++ b/src/rules/linter/apply.test.ts @@ -2,10 +2,7 @@ import { Linter } from './index'; import { ListStacks } from '../../stacks/list-stacks/index'; import Stack from '../../stacks/stack/index'; import Globals from '../../utils/globals/index'; -const util = require('util'); - -const cp = require('child_process'); -jest.mock('child_process'); +const commands = require('../../utils/commands/index'); const fs = require('fs-extra'); jest.mock('fs-extra'); @@ -43,11 +40,8 @@ test('should install tslint as devDependencies and create tslint.json', () => { const linterRule = new Linter(); - util.promisify = jest.fn((exec: (cmd: string) => void) => { - return (cmd: string) => { - expect(cmd).toBe('npm i tslint typescript -DE'); - return Promise.resolve(); - }; + commands.installNpmDevDep = jest.fn(() => { + return Promise.resolve(); }); return linterRule.apply(true); diff --git a/src/rules/linter/index.ts b/src/rules/linter/index.ts index a47de41c..c0d694bf 100644 --- a/src/rules/linter/index.ts +++ b/src/rules/linter/index.ts @@ -6,11 +6,10 @@ import { logger } from '../../logger/index'; import TypeScript from '../../stacks/typescript/index'; import Node from '../../stacks/node/index'; import * as fs from 'fs-extra'; -import * as cp from 'child_process'; -import * as util from 'util'; -import { hasDevDependencies } from '../../utils/json'; +import { hasDevDependency } from '../../utils/json'; import { YesNo } from '../../choice/index'; import Globals from '../../utils/globals'; +import { installNpmDevDep } from '../../utils/commands'; @RuleRegister.register @StackRegister.registerRuleForStacks([TypeScript, Node]) @@ -22,6 +21,10 @@ export class Linter { private linterChoice: string = ''; private linterhasConfigFile: boolean = false; private linterInDevDep: boolean = false; + private documentation: { [linter: string]: string } = { + tslint: 'https://palantir.github.io/tslint/usage/configuration/', + eslint: 'https://eslint.org/docs/user-guide/configuring', + }; constructor() { this.packageJSONPath = `${Globals.rootPath}package.json`; @@ -41,88 +44,72 @@ export class Linter { }, ); } - return; } async shouldBeApplied() { return this.init().then(async () => { this.linterhasConfigFile = await this.hasConfigFile(this.linterChoice); - this.linterInDevDep = this.isInDevDep(this.linterChoice); + this.linterInDevDep = hasDevDependency( + this.parsedPackageJSON, + this.linterChoice, + ); return !this.linterInDevDep || !this.linterhasConfigFile; }); } - async apply(apply: boolean) { - return this.init().then(() => { - if (apply) { - const exec = util.promisify(cp.exec); - return this.init() - .then(() => { - if (!this.linterInDevDep) { - const installCmd = - this.linterChoice === 'tslint' - ? 'npm i tslint typescript -DE' - : 'npm i eslint -DE'; + async apply(apply: boolean): Promise { + if (apply) { + return this.init().then(() => { + if (this.linterInDevDep) { + logger.info(`${this.linterChoice} already installed.`); + } else { + const linterToInstall = + this.linterChoice === 'tslint' ? 'tslint typescript' : 'eslint'; - return exec(installCmd, { cwd: Globals.rootPath }) - .then(() => { - logger.info(`Installed ${this.linterChoice} succesfully`); - }) - .catch(() => { - logger.error( - `Could notCould not install ${ - this.linterChoice - }, try installing it using "${installCmd}" command.`, - ); - }); - } else { - logger.info(`${this.linterChoice} already installed.`); - } - }) - .then(() => { + return installNpmDevDep(linterToInstall).then(() => { if (!this.linterhasConfigFile) { - const documentation: { [linter: string]: string } = { - tslint: - 'https://palantir.github.io/tslint/usage/configuration/', - eslint: 'https://eslint.org/docs/user-guide/configuring', - }; - this.writeLinterFile() - .then(() => { - logger.info( - ` ${ - this.linterChoice - }.json succesfully written to root folder. You may add more rules if you like, find documentation at : ${ - documentation[this.linterChoice] - }`, - ); - }) - .catch(err => { - logger.error(`Error writing to ${this.linterChoice} file.`); - logger.debug(err); - }); + return this.writeLinterFile(); } else { logger.info(`${this.linterChoice}.json file already existing.`); } }); - } - }); + } + }); + } } private writeLinterFile() { return fs .ensureFile(this.linterPaths[this.linterChoice]) .catch(err => { - logger.error(`Error creating ${this.linterChoice}.json`); + logger.error(`Error creating ${this.linterPaths[this.linterChoice]}`); logger.debug(err); - return; + return Promise.reject(err); }) .then(() => { - return fs.writeJson( - this.linterPaths[this.linterChoice], - linterJSON[this.linterChoice], - { spaces: '\t' }, - ); + return fs + .writeJson( + this.linterPaths[this.linterChoice], + linterJSON[this.linterChoice], + { spaces: '\t' }, + ) + .then(() => { + logger.info( + `Succesfully written ${ + this.linterPaths[this.linterChoice] + }. You may add more rules if you like, find documentation at : ${ + this.documentation[this.linterChoice] + }`, + ); + }) + .catch(err => { + logger.error( + `Linter Rule: Error trying to write to ${ + this.linterPaths[this.linterChoice] + }`, + ); + }); }); } @@ -130,13 +117,6 @@ export class Linter { return fs.pathExists(this.linterPaths[linter]); } - isInDevDep(linter: string): boolean { - return ( - hasDevDependencies(this.parsedPackageJSON) && - this.parsedPackageJSON.devDependencies[linter] !== undefined - ); - } - getName() { return 'Linter'; } diff --git a/src/rules/nodemon/index.ts b/src/rules/nodemon/index.ts index 58b361f9..782437ac 100644 --- a/src/rules/nodemon/index.ts +++ b/src/rules/nodemon/index.ts @@ -9,16 +9,15 @@ import * as cp from 'child_process'; import Node from '../../stacks/node'; import { logger } from '../../logger/index'; import * as fs from 'fs-extra'; +import { installNpmDevDep } from '../../utils/commands'; @RuleRegister.register @StackRegister.registerRuleForStacks([Node]) export class Nodemon { private parsedJSON: any; - private packagePath: string; constructor() { - this.packagePath = Globals.rootPath + 'package.json'; - this.parsedJSON = require(this.packagePath); + this.parsedJSON = require(Globals.packageJSONPath); } async shouldBeApplied(): Promise { diff --git a/src/rules/prettier/index.ts b/src/rules/prettier/index.ts index e02f9879..e39bbb64 100644 --- a/src/rules/prettier/index.ts +++ b/src/rules/prettier/index.ts @@ -5,7 +5,7 @@ import * as util from 'util'; import * as cp from 'child_process'; import TypeScript from '../../stacks/typescript'; import Node from '../../stacks/node'; -import { hasDevDependencies } from '../../utils/json/index'; +import { hasDevDependency } from '../../utils/json/index'; import Globals from '../../utils/globals'; /** * Looks for Prettier dependency in package.json, and add it if necessary. @@ -40,14 +40,7 @@ export class Prettier { } async shouldBeApplied() { - return !this.isInDevDep(); - } - - isInDevDep() { - return ( - hasDevDependencies(this.parsedPackage) && - this.parsedPackage.devDependencies.prettier !== undefined - ); + return !hasDevDependency(this.parsedPackage, 'prettier'); } getName() { diff --git a/src/rules/vscode-extensions/constants.ts b/src/rules/vscode-extensions/constants.ts index 99cc8e42..1a971956 100644 --- a/src/rules/vscode-extensions/constants.ts +++ b/src/rules/vscode-extensions/constants.ts @@ -11,11 +11,15 @@ export const possibleChoices: ChoiceList = { { name: 'TSLint', value: 'ms-vscode.vscode-typescript-tslint-plugin' }, { name: 'Angular Console', value: 'nrwl.angular-console' }, { name: 'Prettier', value: 'esbenp.prettier-vscode' }, + { name: 'Debugger for Chrome', value: 'msjsdiag.debugger-for-chrome' }, + { name: 'Debugger for Firefox', value: 'hbenl.vscode-firefox-debug' }, ], VueJS: [ { name: 'Vetur', value: 'octref.vetur' }, { name: 'ESLint', value: 'dbaeumer.vscode-eslint' }, { name: 'Prettier', value: 'esbenp.prettier-vscode' }, + { name: 'Debugger for Chrome', value: 'msjsdiag.debugger-for-chrome' }, + { name: 'Debugger for Firefox', value: 'hbenl.vscode-firefox-debug' }, ], TypeScript: [{ name: 'Prettier', value: 'esbenp.prettier-vscode' }], Node: [{ name: 'Prettier', value: 'esbenp.prettier-vscode' }], diff --git a/src/stacks/github/index.ts b/src/stacks/github/index.ts index acb71341..9416c3cf 100644 --- a/src/stacks/github/index.ts +++ b/src/stacks/github/index.ts @@ -27,6 +27,9 @@ export default class GitHub { }); }) .catch(err => { + if (err.code === 'ENOENT') { + return false; + } logger.debug(err); return false; }); diff --git a/src/stacks/list-stacks/index.ts b/src/stacks/list-stacks/index.ts index 2ef4f062..0fd1b6de 100644 --- a/src/stacks/list-stacks/index.ts +++ b/src/stacks/list-stacks/index.ts @@ -1,5 +1,6 @@ import { StackRegister, Constructor } from '../stack-register'; import Stack from '../stack'; +import Globals from '../../utils/globals'; export class ListStacks { static stacks: Stack[]; @@ -46,4 +47,14 @@ export class ListStacks { }); }); } + + static findAvailableStack( + ctor: Constructor, + ): Promise { + return this.findAvailableStackIn(ctor, Globals.rootPath); + } + + static async stackIsAvailable(ctor: Constructor): Promise { + return this.findAvailableStack(ctor) !== undefined; + } } diff --git a/src/stacks/stack-register/index.ts b/src/stacks/stack-register/index.ts index 5818fa2b..b44259c7 100644 --- a/src/stacks/stack-register/index.ts +++ b/src/stacks/stack-register/index.ts @@ -67,6 +67,7 @@ export class StackRegister { T extends Constructor >(stackCtor: T) { return (subStack: S) => { + StackRegister.rulesByStack[subStack.name] = []; if (StackRegister.subStacks[stackCtor.name] === undefined) { StackRegister.subStacks[stackCtor.name] = [subStack]; } else { diff --git a/src/utils/commands/index.ts b/src/utils/commands/index.ts new file mode 100644 index 00000000..24235feb --- /dev/null +++ b/src/utils/commands/index.ts @@ -0,0 +1,25 @@ +import * as util from 'util'; +import * as cp from 'child_process'; +import { logger } from '../../logger'; +import Globals from '../globals'; + +const exec = util.promisify(cp.exec); + +export function installNpmDevDep(dependency: string) { + return execInRootpath(`npm i ${dependency} -DE`) + .then(() => { + logger.info(`Succesfully installed ${dependency}.`); + return Promise.resolve(); + }) + .catch(err => { + logger.error( + `Error trying to install ${dependency}, try to install it with 'npm i ${dependency} -DE' command.`, + ); + logger.debug(err); + return Promise.reject(err); + }); +} + +export function execInRootpath(cmd: string) { + return exec(cmd, { cwd: Globals.rootPath }); +} diff --git a/src/utils/globals/index.ts b/src/utils/globals/index.ts index b5aa8e93..4a98ee43 100644 --- a/src/utils/globals/index.ts +++ b/src/utils/globals/index.ts @@ -1,5 +1,7 @@ import * as Path from 'path'; export default class Globals { static rootPath: string = Path.resolve('.') + '/'; - static readonly packageJSONPath: string = `${Globals.rootPath}package.json`; + static get packageJSONPath(): string { + return `${Globals.rootPath}package.json`; + } } diff --git a/src/utils/json/has-key-in-object.test.ts b/src/utils/json/has-key-in-object.test.ts deleted file mode 100644 index 5b4eb423..00000000 --- a/src/utils/json/has-key-in-object.test.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { hasKeyInObject } from './index'; -const JSONFilePath = 'test/test.json'; - -afterEach(() => { - jest.resetModules(); -}); - -test('should return false if JSON file does not exist', () => { - expect(hasKeyInObject(JSONFilePath, 'testObj', 'testKey')).toBeFalsy(); -}); - -test('should return false if JSON file is empty', () => { - jest.mock(JSONFilePath, () => ({}), { virtual: true }); - - expect(hasKeyInObject(JSONFilePath, 'testObj', 'testKey')).toBeFalsy(); -}); - -test('should return false if key to find not direct child of given object', () => { - const mockJSON = { - obj: { - dep1: 'dep1Val', - dep2: 'dep2Val', - subObject: { - keyToFind: 'val', - }, - }, - keyToFind: 'val', - otherObject: { - keyTofind: 'val', - }, - }; - - jest.mock(JSONFilePath, () => mockJSON, { virtual: true }); - - expect(hasKeyInObject(JSONFilePath, 'obj', 'keyToFind')).toBeFalsy(); -}); - -test('should return false if obj does not exist', () => { - const mockJSON = { - notObj: { - dep1: 'dep1Val', - dep2: 'dep2Val', - subObject: { - keyToFind: 'val', - }, - }, - keyToFind: 'val', - otherObject: { - keyTofind: 'val', - }, - }; - - jest.mock(JSONFilePath, () => mockJSON, { virtual: true }); - - expect(hasKeyInObject(JSONFilePath, 'obj', 'keyToFind')).toBeFalsy(); -}); - -test('should return true if key to find is direct child of given object', () => { - const mockJSON = { - obj: { - dep1: 'dep1Val', - dep2: 'dep2Val', - keyToFind: 'val', - }, - }; - - jest.mock(JSONFilePath, () => mockJSON, { virtual: true }); - - expect(hasKeyInObject(JSONFilePath, 'obj', 'keyToFind')).toBeTruthy(); -}); diff --git a/src/utils/json/index.ts b/src/utils/json/index.ts index c1664d93..aadb6470 100644 --- a/src/utils/json/index.ts +++ b/src/utils/json/index.ts @@ -1,32 +1,9 @@ -export function hasDevDependencies(packageJSON: any): boolean { - return packageJSON.devDependencies !== undefined; +export function hasDevDependency(parsedJSON: object, dependency: string) { + return pathExistsInJSON(parsedJSON, ['devDependencies', dependency]); } -export function hasDevDependency(path: string, dependency: string) { - return hasKeyInObject(`${path}package.json`, 'devDependencies', dependency); -} - -export function hasDependency(path: string, dependency: string) { - return hasKeyInObject(`${path}package.json`, 'dependencies', dependency); -} - -export function hasKeyInObject( - JSONFilePath: string, - obj: string, - keyToFind: string, -): boolean { - try { - const parsedJSON = require(JSONFilePath); - if (parsedJSON[obj] !== undefined) { - return parsedJSON[obj][keyToFind] !== undefined; - } - return false; - } catch (err) { - if (err.code === 'MODULE_NOT_FOUND') { - return false; - } - throw err; - } +export function hasDependency(parsedJSON: object, dependency: string) { + return pathExistsInJSON(parsedJSON, ['dependencies', dependency]); } export function pathExistsInJSON(parsedJSON: any, objs: string[]): boolean { @@ -38,9 +15,3 @@ export function pathExistsInJSON(parsedJSON: any, objs: string[]): boolean { parsedJSON[head] !== undefined && pathExistsInJSON(parsedJSON[head], tail) ); } - -export function JSONhasObj(JSONPath: string, obj: string) { - const parsedJSON = require(JSONPath); - - return parsedJSON[obj] !== undefined; -}