├── src ├── tests │ └── index.spec.ts ├── core │ ├── actions │ │ ├── tests │ │ │ ├── index.spec.ts │ │ │ └── actionUtils.spec.ts │ │ ├── checkActions │ │ │ ├── tests │ │ │ │ ├── mock │ │ │ │ │ └── index.mock.ts │ │ │ │ └── index.spec.ts │ │ │ └── index.ts │ │ ├── listActions │ │ │ ├── tests │ │ │ │ ├── mock │ │ │ │ │ └── index.mock.ts │ │ │ │ └── index.spec.ts │ │ │ └── index.ts │ │ ├── createActions │ │ │ ├── tests │ │ │ │ ├── index.spec.ts │ │ │ │ └── mock │ │ │ │ │ └── index.mock.ts │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── removeActions │ │ │ ├── tests │ │ │ │ ├── index.spec.ts │ │ │ │ └── mock │ │ │ │ │ └── index.mock.ts │ │ │ └── index.ts │ │ └── actionsUtils.ts │ ├── questions │ │ ├── setupQuestions │ │ │ ├── tests │ │ │ │ └── index.spec.ts │ │ │ ├── projectQuestions │ │ │ │ ├── author-patreon.ts │ │ │ │ ├── install-command.ts │ │ │ │ ├── author-name.ts │ │ │ │ ├── project-name.ts │ │ │ │ ├── contributing.ts │ │ │ │ ├── usage.ts │ │ │ │ ├── author-email.ts │ │ │ │ ├── test-command.ts │ │ │ │ ├── project-description.ts │ │ │ │ ├── project-version.ts │ │ │ │ ├── project-homepage.ts │ │ │ │ ├── project-documentation-url.ts │ │ │ │ ├── tests │ │ │ │ │ ├── author-email.spec.ts │ │ │ │ │ ├── license-name.spec.ts │ │ │ │ │ ├── contributing.spec.ts │ │ │ │ │ ├── install-command.spec.ts │ │ │ │ │ ├── author-patreon.spec.ts │ │ │ │ │ ├── author-twitter.spec.ts │ │ │ │ │ ├── author-name.spec.ts │ │ │ │ │ ├── project-name.spec.ts │ │ │ │ │ ├── license-url.spec.ts │ │ │ │ │ ├── test-command.spec.ts │ │ │ │ │ ├── usage.spec.ts │ │ │ │ │ ├── choose-template.spec.ts │ │ │ │ │ ├── project-version.spec.ts │ │ │ │ │ ├── project-description.spec.ts │ │ │ │ │ ├── author-github.spec.ts │ │ │ │ │ ├── project-homepage.spec.ts │ │ │ │ │ ├── project-documentation-url.spec.ts │ │ │ │ │ ├── project-prerequisites.spec.ts │ │ │ │ │ ├── spec.ts │ │ │ │ │ └── mock │ │ │ │ │ │ └── index.mock.ts │ │ │ │ ├── author-twitter.ts │ │ │ │ ├── author-github.ts │ │ │ │ ├── license-url.ts │ │ │ │ ├── choose-template.ts │ │ │ │ ├── project-prerequisites.ts │ │ │ │ └── license-name.ts │ │ │ ├── removeCommand │ │ │ │ ├── tests │ │ │ │ │ └── removeInit.spec.ts │ │ │ │ └── removeInit.ts │ │ │ ├── createCommand │ │ │ │ ├── tests │ │ │ │ │ ├── createInit.spec.ts │ │ │ │ │ └── mock │ │ │ │ │ │ └── createInit.mock.ts │ │ │ │ └── createInit.ts │ │ │ └── index.ts │ │ ├── tests │ │ │ └── askQuestions.spec.ts │ │ └── askQuestions.ts │ ├── coreUtils │ │ ├── tests │ │ │ ├── mock │ │ │ │ └── index.mock.ts │ │ │ └── index.spec.ts │ │ └── index.ts │ └── validations │ │ ├── tests │ │ ├── spec.ts │ │ ├── mock │ │ │ └── index.mock.ts │ │ ├── ValidateOptions.spec.ts │ │ └── IsInvalidArgs.spec.ts │ │ ├── ValidateOptions.ts │ │ └── IsInvalidArgs.ts ├── templates │ ├── files │ │ ├── optional │ │ │ ├── template-AUTHORS.md │ │ │ ├── template-CONTRIBUTORS.md │ │ │ ├── template-ACKNOWLEDGMENTS.md │ │ │ ├── template-SUPPORT.md │ │ │ ├── template-CHANGELOG.md │ │ │ └── template-CODEOWNERS.md │ │ ├── footer.md │ │ └── required │ │ │ ├── template-PULL_REQUEST_TEMPLATE.md │ │ │ ├── template-APACHE-LICENSE.md │ │ │ ├── template-FEATURE_REQUEST.md │ │ │ ├── template-ISC-LICENSE.md │ │ │ ├── template-GNU-LICENSE.md │ │ │ ├── template-BUG_REPORT.md │ │ │ ├── template-MIT-LICENSE.md │ │ │ ├── template-CONTRIBUTING.md │ │ │ ├── template-CODE_OF_CONDUCT.md │ │ │ ├── template-noHtml-README.md │ │ │ ├── template-html-README.md │ │ │ └── template-MOZILLA-LICENSE.md │ └── fileWriter │ │ ├── tests │ │ ├── index.spec.ts │ │ └── mock │ │ │ └── index.mock.ts │ │ └── index.ts ├── common │ ├── tests │ │ ├── mock │ │ │ ├── alert.mock.ts │ │ │ └── index.mock.ts │ │ ├── spec.ts │ │ ├── index.spec.ts │ │ └── alert.spec.ts │ ├── index.ts │ └── alerts.ts ├── tests.spec.ts ├── projectEnv │ ├── tests │ │ ├── mock │ │ │ └── index.mock.ts │ │ ├── projectInfo.spec.ts │ │ └── utils.spec.ts │ ├── utils.ts │ └── projectInfo.ts └── index.ts ├── .coveralls.yml ├── .prettierrc ├── .babelrc ├── .travis.yml ├── .editorconfig ├── .github ├── PULL_REQUEST_TEMPLATE.md └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── .nycrc ├── .all-contributorsrc ├── .eslintrc.js ├── tsconfig.json ├── LICENSE ├── .gitignore ├── tslint.json ├── CONTRIBUTING.md ├── package.json ├── CODE_OF_CONDUCT.md ├── types └── typeDeclarations.interface.ts └── README.md /src/tests/index.spec.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/core/actions/tests/index.spec.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/core/actions/tests/actionUtils.spec.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/templates/files/optional/template-AUTHORS.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/core/questions/setupQuestions/tests/index.spec.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/templates/files/optional/template-CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/templates/files/optional/template-ACKNOWLEDGMENTS.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.coveralls.yml: -------------------------------------------------------------------------------- 1 | repo_token: HxxK3LI28CYP0kJsg5FMO90N4OJDG9JLM -------------------------------------------------------------------------------- /src/common/tests/mock/alert.mock.ts: -------------------------------------------------------------------------------- 1 | const elements: string[] = ['readme', 'licence']; 2 | 3 | export default elements; 4 | -------------------------------------------------------------------------------- /src/templates/files/footer.md: -------------------------------------------------------------------------------- 1 | _This File was generated by [md-generator](https://github.com/oluwasegun-AA/md-generator)_ 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "trailingComma": "es5", 4 | "tabWidth": 2, 5 | "semi": true, 6 | "singleQuote": true 7 | } -------------------------------------------------------------------------------- /src/common/tests/spec.ts: -------------------------------------------------------------------------------- 1 | import alertTest from './alert.spec'; 2 | import fileNameTest from './index.spec'; 3 | 4 | export default { 5 | alertTest, 6 | fileNameTest 7 | }; 8 | -------------------------------------------------------------------------------- /src/core/coreUtils/tests/mock/index.mock.ts: -------------------------------------------------------------------------------- 1 | export const args: any = { 2 | optional: false, 3 | required: false, 4 | all: false, 5 | file: false, 6 | empty: false 7 | }; 8 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env" 4 | ], 5 | "plugins": [ 6 | "@babel/plugin-proposal-object-rest-spread", 7 | "@babel/plugin-transform-runtime" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /src/core/validations/tests/spec.ts: -------------------------------------------------------------------------------- 1 | import isInvalidArgsTest from './IsInvalidArgs.spec'; 2 | import validateOptionsTests from './ValidateOptions.spec'; 3 | 4 | export default { 5 | isInvalidArgsTest, 6 | validateOptionsTests 7 | }; 8 | -------------------------------------------------------------------------------- /src/core/actions/checkActions/tests/mock/index.mock.ts: -------------------------------------------------------------------------------- 1 | const required = { 2 | required: true, 3 | optional: false 4 | }; 5 | 6 | const optional = { 7 | required: false, 8 | optional: true 9 | }; 10 | 11 | export { 12 | required, 13 | optional 14 | }; 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "v16.15.0" 4 | cache: 5 | directories: 6 | - node_modules 7 | install: 8 | - yarn 9 | script: 10 | - yarn test 11 | after_success: 12 | - nyc report --reporter=text-lcov | coveralls 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /src/core/actions/listActions/tests/mock/index.mock.ts: -------------------------------------------------------------------------------- 1 | const requiredAndAll = { 2 | required: true, 3 | optional: false, 4 | all: ['readme'], 5 | } 6 | 7 | const optionalAndAll = { 8 | required: false, 9 | optional: true, 10 | all: ['readme'], 11 | } 12 | 13 | export { 14 | requiredAndAll, 15 | optionalAndAll 16 | }; 17 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | #### What does this PR do? 2 | 3 | #### Description of Task to be completed? 4 | 5 | #### How should this be manually tested? 6 | 7 | #### Any background context you want to provide? 8 | 9 | #### What are the relevant pivotal tracker stories? 10 | 11 | #### Screenshots (if appropriate) 12 | 13 | #### Questions: 14 | 15 | -------------------------------------------------------------------------------- /src/templates/files/optional/template-SUPPORT.md: -------------------------------------------------------------------------------- 1 | Where can you get support and help? 2 | ==================== 3 | * [Documentation](https://); 4 | * [Frequently Asked Questions](https://) (FAQ); 5 | * Find the [information you need](https://); 6 | * Find [help and other users](https://); 7 | * Post questions at [our forums](https://); 8 | * [Resources Directory](https://) (JRD). 9 | -------------------------------------------------------------------------------- /src/templates/files/required/template-PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | #### What does this PR do? 2 | 3 | #### Description of Task to be completed? 4 | 5 | #### How should this be manually tested? 6 | 7 | #### Any background context you want to provide? 8 | 9 | #### What are the relevant pivotal tracker stories? 10 | 11 | #### Screenshots (if appropriate) 12 | 13 | #### Questions: 14 | -------------------------------------------------------------------------------- /src/core/questions/setupQuestions/projectQuestions/author-patreon.ts: -------------------------------------------------------------------------------- 1 | import { IQuestionResponse } from "../../../../../types/typeDeclarations.interface"; 2 | 3 | const patreonUsername = (): IQuestionResponse => ({ 4 | type: 'input', 5 | message: ' Patreon username (use empty value to skip)', 6 | name: 'authorPatreonUsername', 7 | }); 8 | 9 | export default patreonUsername; 10 | -------------------------------------------------------------------------------- /src/core/questions/setupQuestions/projectQuestions/install-command.ts: -------------------------------------------------------------------------------- 1 | import { IQuestionResponse } from "../../../../../types/typeDeclarations.interface"; 2 | 3 | const installScript = (): IQuestionResponse => ({ 4 | type: 'input', 5 | message: ' Install Script (use empty value to skip)', 6 | name: 'installCommand', 7 | default: 'npm install', 8 | }); 9 | 10 | export default installScript; 11 | -------------------------------------------------------------------------------- /src/templates/files/optional/template-CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](http://keepachangelog.com/) 5 | and this project adheres to [Semantic Versioning](http://semver.org/). 6 | 7 | 8 | ## [Unreleased] 9 | 10 | 11 | ### Added 12 | 13 | 14 | ### Changed 15 | 16 | 17 | ### Removed 18 | -------------------------------------------------------------------------------- /src/core/questions/setupQuestions/projectQuestions/author-name.ts: -------------------------------------------------------------------------------- 1 | import { IProjectInfos, IQuestionResponse } from "../../../../../types/typeDeclarations.interface"; 2 | 3 | const authorName = (projectInfos: IProjectInfos): IQuestionResponse => ({ 4 | type: 'input', 5 | message: ' Author\'s name', 6 | name: 'authorName', 7 | default: projectInfos.author, 8 | }); 9 | 10 | export default authorName; 11 | -------------------------------------------------------------------------------- /src/core/questions/setupQuestions/projectQuestions/project-name.ts: -------------------------------------------------------------------------------- 1 | import { IQuestionResponse, IProjectInfos } from "../../../../../types/typeDeclarations.interface"; 2 | 3 | const projectName = (projectInfos: IProjectInfos): IQuestionResponse => ({ 4 | type: 'input', 5 | message: ' Project name', 6 | name: 'projectName', 7 | default: projectInfos.name, 8 | }); 9 | 10 | export default projectName; 11 | -------------------------------------------------------------------------------- /src/core/questions/setupQuestions/projectQuestions/contributing.ts: -------------------------------------------------------------------------------- 1 | import { IQuestionResponse } from "../../../../../types/typeDeclarations.interface"; 2 | 3 | const contributingUrl = (packageJson: any): IQuestionResponse => ({ 4 | type: 'input', 5 | message: ' Issues page URL (use empty value to skip)', 6 | name: 'contributingUrl', 7 | default: packageJson.contributingUrl, 8 | }); 9 | 10 | export default contributingUrl; 11 | -------------------------------------------------------------------------------- /src/core/questions/setupQuestions/projectQuestions/usage.ts: -------------------------------------------------------------------------------- 1 | import { IProjectInfos, IQuestionResponse } from "../../../../../types/typeDeclarations.interface"; 2 | 3 | const usageInfo = (projectInfos: IProjectInfos): IQuestionResponse => ({ 4 | type: 'input', 5 | message: ' Usage command or instruction (use empty value to skip)', 6 | name: 'usage', 7 | default: projectInfos.usage, 8 | }); 9 | 10 | export default usageInfo; 11 | -------------------------------------------------------------------------------- /src/core/questions/setupQuestions/projectQuestions/author-email.ts: -------------------------------------------------------------------------------- 1 | import { IProjectInfos, IQuestionResponse } from '../../../../../types/typeDeclarations.interface'; 2 | 3 | const authorEmail = (projectInfos: IProjectInfos): IQuestionResponse => ({ 4 | type: 'input', 5 | message: ' Author\'s email or Team\'s mail address', 6 | name: 'authorEmail', 7 | default: projectInfos.author, 8 | }); 9 | 10 | export default authorEmail; 11 | -------------------------------------------------------------------------------- /src/core/questions/setupQuestions/projectQuestions/test-command.ts: -------------------------------------------------------------------------------- 1 | import { IProjectInfos, IQuestionResponse } from "../../../../../types/typeDeclarations.interface"; 2 | 3 | const testScript = (projectInfos: IProjectInfos): IQuestionResponse => ({ 4 | type: 'input', 5 | message: ' Test Script (use empty value to skip)', 6 | name: 'testCommand', 7 | default: projectInfos.testCommand, 8 | }); 9 | 10 | export default testScript; 11 | -------------------------------------------------------------------------------- /src/common/tests/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { getFullFileNames } from '../index'; 2 | import filesArray from './mock/index.mock'; 3 | import { expect } from 'chai'; 4 | 5 | export default describe('test functions in common/index', () => { 6 | it('should return file name', () => { 7 | const names = getFullFileNames(filesArray); 8 | expect(names[0]).to.equal('README.md'); 9 | expect(names[1]).to.equal('CODE_OF_CONDUCT.md'); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /src/core/questions/setupQuestions/projectQuestions/project-description.ts: -------------------------------------------------------------------------------- 1 | import { IQuestionResponse, IProjectInfos } from "../../../../../types/typeDeclarations.interface"; 2 | 3 | const projectDescription = (projectInfos: IProjectInfos): IQuestionResponse => ({ 4 | type: 'input', 5 | message: ' Project description', 6 | name: 'projectDescription', 7 | default: projectInfos.description, 8 | }); 9 | 10 | export default projectDescription; 11 | -------------------------------------------------------------------------------- /src/core/questions/setupQuestions/projectQuestions/project-version.ts: -------------------------------------------------------------------------------- 1 | import { IProjectInfos, IQuestionResponse } from "../../../../../types/typeDeclarations.interface"; 2 | 3 | const projectVersion = (projectInfos: IProjectInfos): IQuestionResponse => ({ 4 | type: 'input', 5 | message: ' Project version (use empty value to skip)', 6 | name: 'projectVersion', 7 | default: projectInfos.version, 8 | }); 9 | 10 | export default projectVersion; 11 | -------------------------------------------------------------------------------- /src/core/questions/setupQuestions/projectQuestions/project-homepage.ts: -------------------------------------------------------------------------------- 1 | import { IProjectInfos, IQuestionResponse } from "../../../../../types/typeDeclarations.interface"; 2 | 3 | const projectHomepage = (projectInfos: IProjectInfos): IQuestionResponse => ({ 4 | type: 'input', 5 | message: ' Project homepage (use empty value to skip)', 6 | name: 'projectHomepage', 7 | default: projectInfos.homepage, 8 | }); 9 | 10 | export default projectHomepage; 11 | -------------------------------------------------------------------------------- /src/common/tests/mock/index.mock.ts: -------------------------------------------------------------------------------- 1 | import { ICurrentFile } from 'types/typeDeclarations.interface'; 2 | 3 | const filesArray: ICurrentFile[] = [ 4 | { 5 | name: 'README.md', 6 | exists: false, 7 | path: './README.md', 8 | }, 9 | { 10 | name: 'CODE_OF_CONDUCT.md', 11 | exists: false, 12 | path: './CODE_OF_CONDUCT.md', 13 | templatePath: '../../templates/files/required/template-CODE_OF_CONDUCT.md', 14 | } 15 | ]; 16 | 17 | export default filesArray; 18 | -------------------------------------------------------------------------------- /src/core/questions/setupQuestions/projectQuestions/project-documentation-url.ts: -------------------------------------------------------------------------------- 1 | import { IProjectInfos, IQuestionResponse } from "../../../../../types/typeDeclarations.interface"; 2 | 3 | const documentationUrl = (projectInfos: IProjectInfos): IQuestionResponse => ({ 4 | type: 'input', 5 | message: ' Project documentation URL (use empty value to skip)', 6 | name: 'projectDocumentationUrl', 7 | default: projectInfos.documentationUrl, 8 | }); 9 | 10 | export default documentationUrl; 11 | -------------------------------------------------------------------------------- /src/core/validations/tests/mock/index.mock.ts: -------------------------------------------------------------------------------- 1 | export const validArguments: any = { 2 | listAndCheck: [['required'], ['optional'], []], 3 | create: [ 4 | ['required', 'empty'], 5 | ['optional', 'empty'], 6 | ['all', 'empty'], 7 | ['file', 'empty'], 8 | ['all'], 9 | ['file'], 10 | ['optional'], 11 | ['required'], 12 | [] 13 | ], 14 | remove: [ 15 | ['all'], 16 | ['file'], 17 | ['optional'], 18 | ['required'], 19 | [] 20 | ] 21 | }; 22 | -------------------------------------------------------------------------------- /src/core/questions/setupQuestions/projectQuestions/tests/author-email.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import {projectInfo} from './mock/index.mock'; 3 | 4 | import authorEmail from '../author-email'; 5 | 6 | export default describe('test create questions', () => { 7 | it('should ask for author\'s email', () => { 8 | const data: any = authorEmail(projectInfo); 9 | expect(data.type).to.be.equal('input'); 10 | expect(data.name).to.be.equal('authorEmail'); 11 | }) 12 | }); 13 | -------------------------------------------------------------------------------- /.nycrc: -------------------------------------------------------------------------------- 1 | { 2 | "cache": false, 3 | "check-coverage": false, 4 | "require": ["ts-node/register"], 5 | "extension": [ 6 | ".ts" 7 | ], 8 | "include": [ 9 | "src/**/*.ts", 10 | "src/**/*.js" 11 | ], 12 | "exclude": [ 13 | "coverage/**", 14 | "node_modules/**", 15 | "**/*.d.ts", 16 | "**/*.spec.ts" 17 | ], 18 | "sourceMap": true, 19 | "reporter": [ 20 | "html", 21 | "text", 22 | "text-summary" 23 | ], 24 | "all": true, 25 | "instrument": true 26 | } -------------------------------------------------------------------------------- /src/core/questions/setupQuestions/projectQuestions/author-twitter.ts: -------------------------------------------------------------------------------- 1 | import { cleanSocialNetworkUsername } from '../../../../projectEnv/utils'; 2 | import { IQuestionResponse } from '../../../../../types/typeDeclarations.interface'; 3 | 4 | const twitterUsername = (): IQuestionResponse => ({ 5 | type: 'input', 6 | message: ' Twitter username (use empty value to skip)', 7 | name: 'authorTwitterUsername', 8 | filter: cleanSocialNetworkUsername, 9 | }); 10 | 11 | export default twitterUsername; 12 | -------------------------------------------------------------------------------- /src/core/questions/setupQuestions/projectQuestions/tests/license-name.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import licenseName from '../license-name'; 4 | 5 | export default describe('test create questions', () => { 6 | it('should ask for the lisence name', () => { 7 | const data: any = licenseName(); 8 | expect(data.type).to.be.equal('list'); 9 | expect(data.name).to.be.equal('licenseName'); 10 | expect(data.message.trim()).to.be.equal('License name'); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/core/questions/setupQuestions/projectQuestions/tests/contributing.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import contributingUrl from '../contributing'; 4 | 5 | export default describe('test create questions', () => { 6 | it('should ask for the contributing URL', () => { 7 | const data: any = contributingUrl({}); 8 | expect(data.type).to.be.equal('input'); 9 | expect(data.name).to.be.equal('contributingUrl'); 10 | expect(data.message.trim()).to.be.equal('Issues page URL (use empty value to skip)'); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/core/questions/setupQuestions/projectQuestions/tests/install-command.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import installScript from '../install-command'; 4 | 5 | export default describe('test create questions', () => { 6 | it('should ask for the install command', () => { 7 | const data: any = installScript(); 8 | expect(data.type).to.be.equal('input'); 9 | expect(data.name).to.be.equal('installCommand'); 10 | expect(data.message.trim()).to.be.equal('Install Script (use empty value to skip)'); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/core/questions/setupQuestions/projectQuestions/tests/author-patreon.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import patreonUsername from '../author-patreon'; 4 | 5 | export default describe('test create questions', () => { 6 | it('should ask for author\'s Patreon username', () => { 7 | const data: any = patreonUsername(); 8 | expect(data.type).to.be.equal('input'); 9 | expect(data.name).to.be.equal('authorPatreonUsername'); 10 | expect(data.message.trim()).to.be.equal('Patreon username (use empty value to skip)'); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/core/questions/setupQuestions/projectQuestions/tests/author-twitter.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import twitterUsername from '../author-twitter'; 4 | 5 | export default describe('test create questions', () => { 6 | it('should ask for author\'s twitter usernmae', () => { 7 | const data: any = twitterUsername(); 8 | expect(data.type).to.be.equal('input'); 9 | expect(data.name).to.be.equal('authorTwitterUsername'); 10 | expect(data.message.trim()).to.be.equal('Twitter username (use empty value to skip)'); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/core/questions/setupQuestions/projectQuestions/tests/author-name.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { projectInfo } from './mock/index.mock'; 3 | 4 | import authorName from '../author-name'; 5 | 6 | export default describe('test create questions', () => { 7 | it('should ask for the author\'s name', () => { 8 | const data: any = authorName(projectInfo); 9 | expect(data.type).to.be.equal('input'); 10 | expect(data.name).to.be.equal('authorName'); 11 | expect(data.message.trim()).to.be.equal('Author\'s name'); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/core/questions/setupQuestions/projectQuestions/tests/project-name.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import projectName from '../project-name'; 4 | import { projectInfo } from './mock/index.mock'; 5 | 6 | export default describe('test create questions', () => { 7 | it('should ask for project\'s name', () => { 8 | const data: any = projectName(projectInfo); 9 | expect(data.type).to.be.equal('input'); 10 | expect(data.name).to.be.equal('projectName'); 11 | expect(data.message.trim()).to.be.equal('Project name'); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/core/actions/checkActions/tests/index.spec.ts: -------------------------------------------------------------------------------- 1 | import checkHandler from '../index'; 2 | import { expect } from 'chai'; 3 | 4 | import { 5 | required, 6 | optional 7 | } from './mock/index.mock'; 8 | 9 | export default describe('test functions in core/actions/createActions', async() => { 10 | it('should call the checkHandler function', async() => { 11 | const infos = await checkHandler(required); 12 | const info = await checkHandler(optional); 13 | expect(infos).to.be.equal(undefined); 14 | expect(info).to.be.equal(undefined); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/core/actions/listActions/tests/index.spec.ts: -------------------------------------------------------------------------------- 1 | import listHandler from '../index'; 2 | import { expect } from 'chai'; 3 | 4 | import { 5 | requiredAndAll, 6 | optionalAndAll 7 | } from './mock/index.mock'; 8 | 9 | export default describe('test functions in core/actions/createActions', async() => { 10 | it('should call the listHandler function', async() => { 11 | const infos = await listHandler(requiredAndAll); 12 | const info = await listHandler(optionalAndAll); 13 | expect(infos).to.be.equal(undefined); 14 | expect(info).to.be.equal(undefined); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/core/questions/setupQuestions/projectQuestions/tests/license-url.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import licenseUrl from '../license-url'; 4 | import { projectInfo } from './mock/index.mock'; 5 | 6 | export default describe('test create questions', () => { 7 | it('should ask for the license URL', () => { 8 | const data: any = licenseUrl(projectInfo); 9 | expect(data.type).to.be.equal('input'); 10 | expect(data.name).to.be.equal('licenseUrl'); 11 | expect(data.message.trim()).to.be.equal('License URL (use empty value to skip)'); 12 | }); 13 | }); 14 | 15 | -------------------------------------------------------------------------------- /src/core/questions/setupQuestions/projectQuestions/tests/test-command.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import testScript from '../test-command'; 4 | import { projectInfo } from './mock/index.mock'; 5 | 6 | export default describe('test create questions', () => { 7 | it('should ask for the test command', () => { 8 | const data: any = testScript(projectInfo); 9 | expect(data.type).to.be.equal('input'); 10 | expect(data.name).to.be.equal('testCommand'); 11 | expect(data.message.trim()).to.be.equal('Test Script (use empty value to skip)'); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/core/questions/setupQuestions/projectQuestions/tests/usage.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import usageInfo from '../usage'; 4 | import { projectInfo } from './mock/index.mock'; 5 | 6 | export default describe('test create questions', () => { 7 | it('should ask for the usage description', () => { 8 | const data: any = usageInfo(projectInfo); 9 | expect(data.type).to.be.equal('input'); 10 | expect(data.name).to.be.equal('usage'); 11 | expect(data.message.trim()).to.be.equal('Usage command or instruction (use empty value to skip)'); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/core/questions/setupQuestions/projectQuestions/author-github.ts: -------------------------------------------------------------------------------- 1 | import { cleanSocialNetworkUsername } from '../../../../projectEnv/utils'; 2 | import { IProjectInfos, IQuestionResponse } from '../../../../../types/typeDeclarations.interface'; 3 | 4 | const authorGithubUsername = (projectInfos: IProjectInfos): IQuestionResponse => ({ 5 | type: 'input', 6 | message: ' GitHub username (use empty value to skip)', 7 | name: 'authorGithubUsername', 8 | default: projectInfos.githubUsername, 9 | filter: cleanSocialNetworkUsername, 10 | }); 11 | 12 | export default authorGithubUsername; 13 | -------------------------------------------------------------------------------- /src/core/questions/setupQuestions/projectQuestions/tests/choose-template.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import chooseTemplate from '../choose-template'; 4 | 5 | export default describe('test create questions', () => { 6 | it('should ask to choose readme template', () => { 7 | const data: any = chooseTemplate(); 8 | expect(data.type).to.be.equal('list'); 9 | expect(data.name).to.be.equal('templatePath'); 10 | expect(data.message.trim()).to.be.equal('Use HTML in your README.md for a nicer rendering? (not supported everywhere. ex: Bitbucket)'); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/core/questions/setupQuestions/projectQuestions/tests/project-version.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import projectVersion from '../project-version'; 4 | import { projectInfo } from './mock/index.mock'; 5 | 6 | export default describe('test create questions', () => { 7 | it('should ask for project\'s version', () => { 8 | const data: any = projectVersion(projectInfo); 9 | expect(data.type).to.be.equal('input'); 10 | expect(data.name).to.be.equal('projectVersion'); 11 | expect(data.message.trim()).to.be.equal('Project version (use empty value to skip)'); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/core/questions/setupQuestions/projectQuestions/license-url.ts: -------------------------------------------------------------------------------- 1 | import isEmpty from 'lodash/isEmpty'; 2 | import { IProjectInfos, IQuestionResponse, IProjectInfosWithMissingFields } from '../../../../../types/typeDeclarations.interface'; 3 | 4 | const licenseUrl = (projectInfos: IProjectInfos | IProjectInfosWithMissingFields): IQuestionResponse => ({ 5 | type: 'input', 6 | message: ' License URL (use empty value to skip)', 7 | name: 'licenseUrl', 8 | default: projectInfos.licenseUrl, 9 | when: (answersContext: any) => !isEmpty(answersContext.licenseName), 10 | }); 11 | 12 | export default licenseUrl; 13 | -------------------------------------------------------------------------------- /src/core/questions/setupQuestions/projectQuestions/tests/project-description.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import projectDescription from '../project-description'; 4 | import {projectInfo} from './mock/index.mock'; 5 | 6 | export default describe('test create questions', () => { 7 | it('should ask for the project\'s description', () => { 8 | const data: any = projectDescription(projectInfo); 9 | expect(data.type).to.be.equal('input'); 10 | expect(data.name).to.be.equal('projectDescription'); 11 | expect(data.message.trim()).to.be.equal('Project description'); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/core/questions/setupQuestions/projectQuestions/tests/author-github.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import {projectInfo} from './mock/index.mock'; 3 | 4 | import authorGithubUsername from '../author-github'; 5 | 6 | export default describe('test create questions', () => { 7 | it('should ask for github username', () => { 8 | const data: any = authorGithubUsername(projectInfo); 9 | expect(data.type).to.be.equal('input'); 10 | expect(data.name).to.be.equal('authorGithubUsername'); 11 | expect(data.message.trim()).to.be.equal('GitHub username (use empty value to skip)'); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/core/questions/setupQuestions/projectQuestions/tests/project-homepage.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import projectHomepage from '../project-homepage'; 4 | import { projectInfo } from './mock/index.mock'; 5 | 6 | export default describe('test create questions', () => { 7 | it('should ask for project\'s homepage', () => { 8 | const data: any = projectHomepage(projectInfo); 9 | expect(data.type).to.be.equal('input'); 10 | expect(data.name).to.be.equal('projectHomepage'); 11 | expect(data.message.trim()).to.be.equal('Project homepage (use empty value to skip)'); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/core/questions/setupQuestions/projectQuestions/tests/project-documentation-url.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import documentationUrl from '../project-documentation-url'; 4 | import { projectInfo } from './mock/index.mock'; 5 | 6 | export default describe('test create questions', () => { 7 | it('should ask for the documentation URL', () => { 8 | const data: any = documentationUrl(projectInfo); 9 | expect(data.type).to.be.equal('input'); 10 | expect(data.name).to.be.equal('projectDocumentationUrl'); 11 | expect(data.message.trim()).to.be.equal('Project documentation URL (use empty value to skip)'); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/templates/files/required/template-APACHE-LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) <%= currentYear %> <%= authorName %> 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /src/templates/fileWriter/tests/index.spec.ts: -------------------------------------------------------------------------------- 1 | import chai, { expect } from 'chai'; 2 | import { writeFile, buildFileContent } from '../index'; 3 | import { projectInfos } from './mock/index.mock'; 4 | import spies from 'chai-spies'; 5 | 6 | chai.use(spies); 7 | 8 | export default describe('test functions in templates/fileWriter/index.ts', async () => { 9 | it('should call writeFile', async () => { 10 | const response = writeFile('hello world', '/demo/path'); 11 | expect(response).to.be.equal(undefined); 12 | }); 13 | it('should call buildFileContent', async () => { 14 | const response = buildFileContent(projectInfos, '/demo/path'); 15 | expect(JSON.stringify(response)).to.be.equal('{}'); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /src/templates/files/required/template-FEATURE_REQUEST.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /src/templates/fileWriter/tests/mock/index.mock.ts: -------------------------------------------------------------------------------- 1 | export const projectInfos = { 2 | name: 'name', 3 | description: 'description', 4 | version: 'version', 5 | author: 'author', 6 | homepage: 'homepage', 7 | repositoryUrl: 'repositoryUrl', 8 | contributingUrl: 'contributingUrl', 9 | githubUsername: 'githubUsername', 10 | engines: 'demo string', 11 | licenseName: { 12 | name: 'demo string', 13 | }, 14 | licenseUrl: 'demo string', 15 | documentationUrl: 'demo string', 16 | isGithubRepo: true, 17 | usage: 'demo string', 18 | testCommand: 'demo string', 19 | isGithubRepos: true, 20 | projectName: 'demo string', 21 | projectPrerequisites: 'demo string', 22 | isProjectOnNpm: 'demo string' 23 | }; 24 | -------------------------------------------------------------------------------- /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "md-generator", 3 | "projectOwner": "Adépòjù Olúwáségun", 4 | "repoType": "github", 5 | "repoHost": "https://github.com", 6 | "files": [ 7 | "README.md" 8 | ], 9 | "imageSize": 100, 10 | "commit": true, 11 | "commitConvention": "eslint", 12 | "contributors": [ 13 | { 14 | "login": "Oluwasegun-AA", 15 | "name": "Adépòjù Olúwáségun", 16 | "avatar_url": "https://avatars0.githubusercontent.com/u/25525765?v=4", 17 | "profile": "https://github.com/Oluwasegun-AA", 18 | "contributions": [ 19 | "code", 20 | "doc", 21 | "maintenance", 22 | "test" 23 | ] 24 | } 25 | ], 26 | "contributorsPerLine": 7 27 | } 28 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es6: true, 5 | node: true, 6 | }, 7 | extends: [ 8 | 'airbnb-base', 9 | ], 10 | globals: { 11 | Atomics: 'readonly', 12 | SharedArrayBuffer: 'readonly', 13 | }, 14 | parserOptions: { 15 | ecmaVersion: 2018, 16 | sourceType: 'module', 17 | }, 18 | rules: { 19 | "no-shadow": 0, 20 | "arrow-parens": 0, 21 | "no-param-reassign": 0, 22 | "comma-dangle":0, 23 | "dot-notation":0, 24 | "no-constant-condition":0, 25 | "consistent-return":0, 26 | "operator-linebreak":0, 27 | "implicit-arrow-linebreak": 0, 28 | "no-underscore-dangle":0, 29 | "no-return-assign": 0, 30 | }, 31 | globals:{ 32 | chrome: true 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /src/templates/files/required/template-ISC-LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) <%= currentYear %> <%= authorName %> 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. 4 | 5 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 6 | -------------------------------------------------------------------------------- /src/templates/files/required/template-GNU-LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) <%= currentYear %> <%= authorName %> 2 | 3 | This program is free software: you can redistribute it and/or modify 4 | it under the terms of the GNU General Public License as published by 5 | the Free Software Foundation, either version 3 of the License, or 6 | (at your option) any later version. 7 | 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | GNU General Public License for more details. 12 | 13 | You should have received a copy of the GNU General Public License 14 | along with this program. If not, see . 15 | -------------------------------------------------------------------------------- /src/tests.spec.ts: -------------------------------------------------------------------------------- 1 | import './projectEnv/tests/projectInfo.spec'; 2 | import './projectEnv/tests/utils.spec'; 3 | import './templates/fileWriter/tests/index.spec'; 4 | import './common/tests/spec'; 5 | import './core/questions/tests/askQuestions.spec'; 6 | import './core/actions/checkActions/tests/index.spec'; 7 | import './core/actions/createActions/tests/index.spec'; 8 | import './core/actions/listActions/tests/index.spec'; 9 | import './core/actions/removeActions/tests/index.spec'; 10 | import './core/questions/setupQuestions/createCommand/tests/createInit.spec'; 11 | import './core/questions/setupQuestions/projectQuestions/tests/spec'; 12 | import './core/questions/setupQuestions/removeCommand/tests/removeInit.spec'; 13 | import './core/coreUtils/tests/index.spec'; 14 | import './core/validations/tests/spec'; 15 | -------------------------------------------------------------------------------- /src/core/questions/setupQuestions/removeCommand/tests/removeInit.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import {validateRemove, removeOptional} from '../removeInit'; 4 | 5 | export default describe('test create questions', () => { 6 | it('should verify file removal', () => { 7 | const data: any = validateRemove([]); 8 | expect(data.type).to.be.equal('confirm'); 9 | expect(data.name).to.be.equal('removeFiles'); 10 | expect(data.default).to.be.equal(false); 11 | }); 12 | it('should verify file removal', () => { 13 | const data: any = removeOptional([]); 14 | expect(data.type).to.be.equal('checkbox'); 15 | expect(data.name).to.be.equal('removeFiles'); 16 | expect(data.message.trim()).to.be.equal('Which of the following OPTIONAL .md files(s) would you like to delete?'); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/core/questions/setupQuestions/projectQuestions/tests/project-prerequisites.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import { askProjectPrerequisites, hasProjectInfosEngines } from '../project-prerequisites'; 4 | import { projectInfo } from './mock/index.mock'; 5 | 6 | export default describe('test create questions', () => { 7 | it('should ask for project\'s prerequisites', () => { 8 | const data: any = askProjectPrerequisites(projectInfo); 9 | expect(data.type).to.be.equal('checkbox'); 10 | expect(data.name).to.be.equal('projectPrerequisites'); 11 | expect(data.message.trim()).to.be.equal('Project prerequisites'); 12 | }); 13 | it('should return true if projectInfos.engines is valid', () => { 14 | const data: any = hasProjectInfosEngines(projectInfo); 15 | expect(data).to.be.equal(true); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /src/core/coreUtils/tests/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import { ExtractOptions } from '../index'; 4 | import { args } from './mock/index.mock'; 5 | 6 | export default describe('Tests for coreUtils', () => { 7 | it('should return the valid options for list', () => { 8 | const data: any = ExtractOptions.list(args); 9 | expect(data.optional).to.be.equal(false); 10 | }); 11 | it('should return the valid options for create', () => { 12 | const data: any = ExtractOptions.create(args); 13 | expect(data.optional).to.be.equal(false); 14 | }); 15 | it('should return the valid options for check', () => { 16 | const data: any = ExtractOptions.check(args); 17 | expect(data.optional).to.be.equal(false); 18 | }); 19 | it('should return the valid options for remove', () => { 20 | const data: any = ExtractOptions.remove(args); 21 | expect(data.optional).to.be.equal(false); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /src/core/questions/setupQuestions/createCommand/tests/createInit.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import files from './mock/createInit.mock'; 3 | 4 | import { 5 | createFiles, 6 | overrideFiles, 7 | createEmptyFiles 8 | } from '../createInit'; 9 | 10 | export default describe('test create questions', () => { 11 | it('should ask the create question', () => { 12 | const data: any = createFiles(files); 13 | expect(data.type).to.be.equal('confirm'); 14 | expect(data.name).to.be.equal('createFiles'); 15 | }); 16 | it('should ask override question', () => { 17 | const data: any = overrideFiles(['readme']); 18 | expect(data.type).to.be.equal('confirm'); 19 | expect(data.name).to.be.equal('override'); 20 | }); 21 | it('should ask to create empty files', () => { 22 | const data: any = createEmptyFiles(); 23 | expect(data.type).to.be.equal('confirm'); 24 | expect(data.name).to.be.equal('empty'); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /src/core/questions/setupQuestions/projectQuestions/choose-template.ts: -------------------------------------------------------------------------------- 1 | 2 | import path from 'path'; 3 | import { IQuestionResponse } from '../../../../../types/typeDeclarations.interface'; 4 | 5 | const getResolvedPath = (relativePath: string) => path.resolve(__dirname, relativePath); 6 | 7 | const defaultTemplate = getResolvedPath('../../../../templates/files/required/template-html-README.md'); 8 | const defaultNoHtmlTemplate = getResolvedPath('../../../../templates/files/required/template-noHtml-README.md'); 9 | 10 | const chooseTemplate = (): IQuestionResponse => ({ 11 | type: 'list', 12 | message: 13 | ' Use HTML in your README.md for a nicer rendering? (not supported everywhere. ex: Bitbucket)', 14 | name: 'templatePath', 15 | choices: [ 16 | { 17 | name: 'Yes', 18 | value: defaultTemplate, 19 | }, 20 | { 21 | name: 'No', 22 | value: defaultNoHtmlTemplate, 23 | }, 24 | ], 25 | }); 26 | 27 | export default chooseTemplate; 28 | -------------------------------------------------------------------------------- /src/core/validations/tests/ValidateOptions.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import handleCommand, { filterValidArgs } from '../ValidateOptions'; 4 | 5 | export default describe('Test ValidateOptions.ts', () => { 6 | it('Should filter args', () => { 7 | const response: any = filterValidArgs({}); 8 | expect(response.args).to.be.equal(undefined); 9 | }); 10 | 11 | it('Should list optional files', () => { 12 | const response: any = handleCommand({ 13 | _name: 'list', optional: true, 14 | required: undefined, 15 | all: false, 16 | file: false, 17 | empty: false 18 | }); 19 | expect(response).to.be.equal(undefined); 20 | }); 21 | 22 | it('Should handle commands', () => { 23 | const response: any = handleCommand({ 24 | _name: 'list', optional: true, 25 | required: false, 26 | all: false, 27 | file: false, 28 | empty: false 29 | }); 30 | expect(response).to.be.equal(undefined); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /src/core/actions/createActions/tests/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import createHandler, { 3 | getValidFiles, 4 | } from '../index'; 5 | 6 | import { 7 | none, 8 | required, 9 | optional, 10 | currentFile, 11 | } from './mock/index.mock'; 12 | 13 | export default describe('test functions in core/actions/createActions', async () => { 14 | it('should call the createHandler function', async () => { 15 | const resp = await createHandler(none); 16 | const info = await createHandler(required); 17 | const data = await createHandler(optional); 18 | expect(info).to.be.equal(undefined); 19 | expect(data).to.be.equal(undefined); 20 | expect(resp).to.be.equal(undefined); 21 | }); 22 | 23 | it('should inquire if the supplied filename is valid/supported', () => { 24 | const data = getValidFiles([currentFile], ['file']); 25 | expect(data.validFileNames[0]).to.be.equal(undefined); 26 | expect(data.inValidFileNames[0]).to.be.equal('FILE'); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /src/core/validations/ValidateOptions.ts: -------------------------------------------------------------------------------- 1 | import { showHelp, ExtractOptions } from '../coreUtils/index'; 2 | import IsValidArgs from './IsInvalidArgs'; 3 | import Actions from '../actions/index'; 4 | 5 | /** 6 | * extract on ly valid options 7 | */ 8 | export const filterValidArgs = (args: any): any => 9 | Object.keys(args).filter((item: string) => args[item] !== undefined); 10 | 11 | const validateOptions = (values: any): any => { 12 | const command: keyof typeof ExtractOptions = values._name; 13 | const args: any = ExtractOptions[command](values); 14 | const activeArgs: any = filterValidArgs(args); 15 | if (!IsValidArgs[command](activeArgs)) { 16 | showHelp('Invalid argument combination\n'); 17 | } 18 | return { activeArgs, values }; 19 | }; 20 | 21 | const handleCommand = (argValues: any): void => { 22 | const command: keyof typeof Actions = argValues._name; 23 | const { activeArgs, values }: any = validateOptions(argValues); 24 | Actions[command](activeArgs, values); 25 | }; 26 | 27 | export default handleCommand; 28 | -------------------------------------------------------------------------------- /src/templates/files/required/template-BUG_REPORT.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist", 6 | "resolveJsonModule": true, 7 | "declaration": false, 8 | "moduleResolution": "node", 9 | "emitDecoratorMetadata": true, 10 | "experimentalDecorators": true, 11 | "importHelpers": true, 12 | "target": "es5", 13 | "forceConsistentCasingInFileNames": true, 14 | "esModuleInterop": true, 15 | "strict": true, 16 | "noUnusedParameters": true, 17 | "checkJs": true, 18 | "allowJs": true, 19 | "strictNullChecks": true, 20 | "alwaysStrict": true, 21 | "noImplicitAny": true, 22 | "noUnusedLocals": true, 23 | "noFallthroughCasesInSwitch": true, 24 | "noImplicitReturns": true, 25 | "typeRoots": [ 26 | "node_modules/@types" 27 | ], 28 | "lib": [ 29 | "es2018", 30 | "dom", 31 | "esnext.asynciterable" 32 | ], 33 | "removeComments": true, 34 | }, 35 | "include": [ 36 | "src/**/*" 37 | ], 38 | "exclude": [ 39 | "types", 40 | "node_modules", 41 | "**/*.spec.ts", 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /src/core/actions/index.ts: -------------------------------------------------------------------------------- 1 | import { getValues } from './actionsUtils'; 2 | import listHandler from './listActions'; 3 | import createHandler from './createActions'; 4 | import checkHandler from './checkActions'; 5 | import removeHandler from './removeActions'; 6 | import { ICurrentFile } from '../../../types/typeDeclarations.interface'; 7 | 8 | /** 9 | * Handle all actions with respect to the supplied arguments 10 | */ 11 | class Actions { 12 | public static list: any = (args: any, resp: ICurrentFile): void => { 13 | const values = getValues(args, resp); 14 | listHandler(values); 15 | } 16 | 17 | public static create: any = (args: any, resp: ICurrentFile): void => { 18 | const values = getValues(args, resp); 19 | createHandler(values); 20 | } 21 | 22 | public static check: any = (args: any, resp: ICurrentFile): void => { 23 | const values = getValues(args, resp); 24 | checkHandler(values); 25 | } 26 | 27 | public static remove: any = (args: any, resp: ICurrentFile): void => { 28 | const values = getValues(args, resp); 29 | removeHandler(values); 30 | } 31 | } 32 | 33 | export default Actions; 34 | -------------------------------------------------------------------------------- /src/core/questions/setupQuestions/createCommand/tests/mock/createInit.mock.ts: -------------------------------------------------------------------------------- 1 | import { ICurrentFile } from 'types/typeDeclarations.interface'; 2 | 3 | const files: ICurrentFile[] = [{ 4 | name: 'README.md', 5 | exists: true, 6 | path: './README.md', 7 | }, { 8 | name: 'LICENSE', 9 | exists: false, 10 | path: './LICENSE', 11 | }, { 12 | name: 'CODE_OF_CONDUCT.md', 13 | exists: false, 14 | path: './CODE_OF_CONDUCT.md', 15 | templatePath: '../../templates/files/required/template-CODE_OF_CONDUCT.md' 16 | }, { 17 | name: 'PULL_REQUEST_TEMPLATE.md', 18 | exists: false, 19 | path: './.github/PULL_REQUEST_TEMPLATE.md', 20 | templatePath: '../../templates/files/required/template-PULL_REQUEST_TEMPLATE.md' 21 | }, { 22 | name: 'bug_report.md', 23 | exists: false, 24 | path: './.github/ISSUE_TEMPLATE/bug_report.md', 25 | templatePath: '../../templates/files/required/template-BUG_REPORT.md' 26 | }, { 27 | name: 'feature_request.md', 28 | exists: true, 29 | path: './.github/ISSUE_TEMPLATE/feature_request.md', 30 | templatePath: '../../templates/files/required/template-FEATURE_REQUEST.md' 31 | }]; 32 | 33 | export default files; 34 | -------------------------------------------------------------------------------- /src/projectEnv/tests/mock/index.mock.ts: -------------------------------------------------------------------------------- 1 | export const packageJson = { 2 | author: { 3 | name: 'testAuthorName' 4 | } 5 | }; 6 | 7 | export const packageJsonWithStringAuthor = { 8 | author: 'testAuthor' 9 | }; 10 | 11 | export const packageJsonWithNullAuthor = { 12 | author: null 13 | }; 14 | 15 | export const questionResponse1 = { 16 | type: 'input', 17 | name: 'string', 18 | message: 'string', 19 | default: undefined, 20 | filter: 'filter', 21 | choices: 'choices', 22 | when: () => true 23 | }; 24 | export const questionResponse2 = { 25 | type: 'checkbox', 26 | name: 'string', 27 | message: 'string', 28 | default: undefined, 29 | filter: 'filter', 30 | choices: [{ checked: true, value: true }, { checked: false }], 31 | when: () => true 32 | }; 33 | export const questionResponse3 = { 34 | type: 'type', 35 | name: 'string', 36 | message: 'string', 37 | default: 'true', 38 | filter: 'filter', 39 | choices: 'choices', 40 | when: () => true 41 | }; 42 | export const questionResponse4 = { 43 | type: 'type', 44 | name: 'string', 45 | message: 'string', 46 | default: 'true', 47 | filter: 'filter', 48 | choices: 'choices', 49 | when: () => false 50 | }; 51 | -------------------------------------------------------------------------------- /src/templates/files/required/template-MIT-LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) <%= currentYear %> <%= authorName %> 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Adépòjù Olúwásêgun (https://oluwasegun-aa.github.io/) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | # ignore custom files 64 | /dist 65 | /public 66 | 67 | # others 68 | .DS_Store 69 | .vscode 70 | -------------------------------------------------------------------------------- /src/common/index.ts: -------------------------------------------------------------------------------- 1 | import { flatMap } from 'lodash'; 2 | import { 3 | log, 4 | useBox, 5 | green, 6 | customHelp, 7 | whiteUnderline, 8 | red, 9 | gray, 10 | dimWhite, 11 | cyan, 12 | wrongCommandAlert, 13 | noCommandAlert, 14 | spinner, 15 | useHelpAlert, 16 | showEndMessage, 17 | fileNotDetectedAlert, 18 | unrecognizedFileAlert, 19 | checkCommunityStandardMet, 20 | castElementsToFormatedString, 21 | } from './alerts'; 22 | 23 | import { ICurrentFile } from '../../types/typeDeclarations.interface'; 24 | 25 | /** 26 | * @description 27 | * Takes an array of objects and return an array of the value in the name key of each object 28 | * 29 | * @param filesArray array of objects with a name key 30 | */ 31 | const getFullFileNames = (filesArray: ICurrentFile[]): string[] => 32 | flatMap(filesArray, (currentFile: ICurrentFile) => currentFile.name); 33 | 34 | export { 35 | log, 36 | useBox, 37 | green, 38 | customHelp, 39 | whiteUnderline, 40 | red, 41 | gray, 42 | dimWhite, 43 | cyan, 44 | wrongCommandAlert, 45 | noCommandAlert, 46 | spinner, 47 | useHelpAlert, 48 | showEndMessage, 49 | getFullFileNames, 50 | fileNotDetectedAlert, 51 | unrecognizedFileAlert, 52 | checkCommunityStandardMet, 53 | castElementsToFormatedString, 54 | }; 55 | -------------------------------------------------------------------------------- /src/core/questions/setupQuestions/projectQuestions/project-prerequisites.ts: -------------------------------------------------------------------------------- 1 | import isEmpty from 'lodash/isEmpty'; 2 | import isNil from 'lodash/isNil'; 3 | import { IProjectInfos, IQuestionResponse } from '../../../../../types/typeDeclarations.interface'; 4 | 5 | /** 6 | * @description Return engines as formatted choices 7 | */ 8 | const buildFormattedChoices = (engines: any) => { 9 | const choices = isNil(engines) 10 | ? null 11 | : Object.keys(engines).map((key: string) => ({ 12 | name: `${key} ${engines[key]}`, 13 | value: { 14 | name: key, 15 | value: engines[key], 16 | }, 17 | checked: true, 18 | })); 19 | return choices; 20 | }; 21 | 22 | /** 23 | * Check if projectInfos has engines properties 24 | */ 25 | const hasProjectInfosEngines = (projectInfos: IProjectInfos) => 26 | !isNil(projectInfos.engines) && !isEmpty(projectInfos.engines); 27 | 28 | const askProjectPrerequisites = (projectInfos: IProjectInfos): IQuestionResponse => ({ 29 | type: 'checkbox', 30 | message: ' Project prerequisites', 31 | name: 'projectPrerequisites', 32 | choices: buildFormattedChoices(projectInfos.engines), 33 | when: () => hasProjectInfosEngines(projectInfos), 34 | }); 35 | 36 | export { 37 | askProjectPrerequisites, 38 | hasProjectInfosEngines 39 | } 40 | -------------------------------------------------------------------------------- /src/projectEnv/tests/projectInfo.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { getAuthorName, getRepoIssuesUrl, getRepoUrlFromGit } from '../projectInfo'; 3 | import { packageJson, packageJsonWithStringAuthor, packageJsonWithNullAuthor } from './mock/index.mock' 4 | 5 | export default describe('test functions in projectEnv/projectInfo', async () => { 6 | it('should return undefined fi author name is null', async () => { 7 | const response = getAuthorName(packageJsonWithNullAuthor); 8 | expect(response).to.be.equal(undefined); 9 | }); 10 | 11 | it('should call getRepoIssuesUrl function', async () => { 12 | const response = getRepoIssuesUrl(packageJson); 13 | expect(response).to.be.equal(undefined); 14 | }); 15 | 16 | it('should return author name from package.json file', async () => { 17 | const response = getAuthorName(packageJsonWithStringAuthor); 18 | expect(response).to.be.equal('testAuthor'); 19 | }); 20 | 21 | it('should call getRepoUrlFromGit function', async () => { 22 | const response = getRepoUrlFromGit(); 23 | expect(response).to.be.equal(undefined); 24 | }); 25 | 26 | it('should return author name from package.json file', async () => { 27 | const response = getAuthorName(packageJson); 28 | expect(response).to.be.equal('testAuthorName'); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /src/core/actions/removeActions/tests/index.spec.ts: -------------------------------------------------------------------------------- 1 | import removeHandler, { 2 | deleteFromCodebase, 3 | deleteFiles, 4 | removeSpecificFiles 5 | } from '../index'; 6 | import { expect } from 'chai'; 7 | 8 | import { 9 | file, 10 | none, 11 | optional, 12 | required, 13 | currentFile 14 | } from './mock/index.mock'; 15 | 16 | export default describe('test functions in core/actions/createActions', () => { 17 | it('should call the createHandler function', () => { 18 | const resp = removeHandler(none); 19 | const info = removeHandler(required); 20 | const data = removeHandler(optional); 21 | expect(info).to.be.equal(undefined); 22 | expect(data).to.be.equal(undefined); 23 | expect(resp).to.be.equal(undefined); 24 | }); 25 | it('should log no files selected', () => { 26 | const data = deleteFromCodebase(['file'], { file: {path:''} }); 27 | expect(data).to.be.equal(undefined); 28 | }); 29 | it('should delete form codebase', () => { 30 | const data = deleteFiles(['file'], file); 31 | expect(data).to.be.equal(undefined); 32 | }); 33 | it('should prompt to delete files', () => { 34 | const data = deleteFiles([], file); 35 | expect(data).to.be.equal(undefined); 36 | }); 37 | it('should remove a specific file', () => { 38 | const data = removeSpecificFiles([currentFile]); 39 | expect(data).to.be.equal(undefined); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /src/core/questions/tests/askQuestions.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { 3 | find, 4 | getAppropriateQuestion, 5 | askQuestions, 6 | getInfos 7 | } from '../askQuestions'; 8 | import { projectInfos } from '../../../templates/fileWriter/tests/mock/index.mock'; 9 | export default describe('test questions/askQuestions.ts', async () => { 10 | it('should return valid option', () => { 11 | const data: any = find(['find', 'this'], 'this'); 12 | expect(data).to.be.equal('this'); 13 | }); 14 | it('should return null when files are invalid', () => { 15 | const data: any = getAppropriateQuestion(['find', 'this']); 16 | expect(data).to.be.equal(null); 17 | }); 18 | it('should return appropriate questions for valid files', () => { 19 | const data: any = getAppropriateQuestion(['README', 'LICENSE', 'CODE_OF_CONDUCT']); 20 | expect(data.chooseTemplate).exist; 21 | }); 22 | it('should ask for author\'s email', async () => { 23 | const data: any = await askQuestions(projectInfos, true, ['find', 'this']); 24 | expect(data.isGithubRepos).to.be.equal(true); 25 | }); 26 | it('should ask for author\'s email', async () => { 27 | const data: any = await getInfos(true, ['find', 'this']); 28 | expect(data.repositoryUrl).to.be.equal('https://github.com/Oluwasegun-AA/md-generator'); 29 | expect(data.isProjectOnNpm).to.be.equal(true); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /src/core/questions/setupQuestions/projectQuestions/license-name.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { IQuestionResponse } from '../../../../../types/typeDeclarations.interface'; 3 | 4 | const getResolvedPath = (relativePath: string) => path.resolve(__dirname, relativePath); 5 | 6 | const MIT = getResolvedPath( 7 | '../../../../templates/files/required/template-MIT-LICENSE.md' 8 | ); 9 | const ISC = getResolvedPath( 10 | '../../../../templates/files/required/template-ISC-LICENSE.md' 11 | ); 12 | const GNU = getResolvedPath( 13 | '../../../../templates/files/required/template-GNU-LICENSE.md' 14 | ); 15 | const APACHE = getResolvedPath( 16 | '../../../../templates/files/required/template-APACHE-LICENSE.md' 17 | ); 18 | const MOZILLA = getResolvedPath( 19 | '../../../../templates/files/required/template-MOZILLA-LICENSE.md' 20 | ); 21 | 22 | const licenseName = (): IQuestionResponse => ({ 23 | type: 'list', 24 | message: ' License name', 25 | name: 'licenseName', 26 | choices: [ 27 | { 28 | name: 'MIT', 29 | value: { name: 'MIT', path: MIT }, 30 | }, 31 | { 32 | name: 'ISC', 33 | value: { name: 'ISC', path: ISC }, 34 | }, 35 | { 36 | name: 'GNU', 37 | value: { name: 'GNU', path: GNU }, 38 | }, 39 | { 40 | name: 'APACHE', 41 | value: { name: 'APACHE', path: APACHE }, 42 | }, 43 | { 44 | name: 'MOZILLA', 45 | value: { name: 'MOZILLA', path: MOZILLA }, 46 | }, 47 | ], 48 | }); 49 | 50 | export default licenseName; 51 | -------------------------------------------------------------------------------- /src/core/questions/setupQuestions/projectQuestions/tests/spec.ts: -------------------------------------------------------------------------------- 1 | import askProjectName from './project-name.spec'; 2 | import askProjectVersion from './project-version.spec'; 3 | import askProjectDescription from './project-description.spec'; 4 | import askProjectHomepage from './project-homepage.spec'; 5 | import askProjectDocumentationUrl from './project-documentation-url.spec'; 6 | import askAuthorName from './author-name.spec'; 7 | import askAuthorEmail from './author-email.spec'; 8 | import askAuthorGithub from './author-github.spec'; 9 | import askAuthorTwitter from './author-twitter.spec'; 10 | import askAuthorPatreon from './author-patreon.spec'; 11 | import askProjectPrerequisites from './project-prerequisites.spec'; 12 | import askLicenseName from './license-name.spec'; 13 | import askLicenseUrl from './license-url.spec'; 14 | import askContributing from './contributing.spec'; 15 | import askInstallCommand from './install-command.spec'; 16 | import askUsage from './usage.spec'; 17 | import askTestCommand from './test-command.spec'; 18 | import chooseTemplate from './choose-template.spec'; 19 | 20 | export { 21 | chooseTemplate, 22 | askProjectName, 23 | askProjectVersion, 24 | askProjectHomepage, 25 | askProjectDescription, 26 | askLicenseName, 27 | askLicenseUrl, 28 | askInstallCommand, 29 | askTestCommand, 30 | askProjectDocumentationUrl, 31 | askAuthorName, 32 | askAuthorGithub, 33 | askAuthorTwitter, 34 | askAuthorPatreon, 35 | askProjectPrerequisites, 36 | askContributing, 37 | askUsage, 38 | askAuthorEmail 39 | }; 40 | -------------------------------------------------------------------------------- /src/core/validations/IsInvalidArgs.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 3 | * converts array to string 4 | * 5 | * @param data array of information 6 | */ 7 | const str = (data: string[] | []) => JSON.stringify(data); 8 | 9 | /** 10 | * @description 11 | * validates strict equality between tow items 12 | * @param args raw arguments 13 | * @param validOptions valid options 14 | */ 15 | export const isValidOption = (args: string[], validOptions: string[][]) => 16 | !validOptions.every((option: string[] | []) => str(args) !== str(option)); 17 | 18 | /** 19 | * checks the validity of supplied arguments 20 | */ 21 | class IsValidArgs { 22 | public static list: any = (args: string[]): boolean => { 23 | const validArgs: string[][] = [['required'], ['optional'], []]; 24 | return isValidOption(args, validArgs); 25 | } 26 | 27 | public static create: any = (args: string[]): boolean => { 28 | const validArgs = [ 29 | ['required', 'empty'], 30 | ['optional', 'empty'], 31 | ['all', 'empty'], 32 | ['file', 'empty'], 33 | ['all'], 34 | ['file'], 35 | ['optional'], 36 | ['required'], 37 | [] 38 | ]; 39 | return isValidOption(args, validArgs); 40 | } 41 | 42 | public static check: any = (args: string[]): boolean => { 43 | const validArgs = [['required'], ['optional'], []]; 44 | return isValidOption(args, validArgs); 45 | } 46 | 47 | public static remove: any = (args: string[]): boolean => { 48 | const validArgs = [ 49 | ['all'], 50 | ['file'], 51 | ['optional'], 52 | ['required'], 53 | []]; 54 | return isValidOption(args, validArgs); 55 | } 56 | } 57 | 58 | export default IsValidArgs; 59 | -------------------------------------------------------------------------------- /src/core/questions/setupQuestions/projectQuestions/tests/mock/index.mock.ts: -------------------------------------------------------------------------------- 1 | import { IProjectInfos, IQuestionResponse, IProjectInfosWithMissingFields } from "types/typeDeclarations.interface"; 2 | 3 | const projectInfo: IProjectInfos = { 4 | name: 'hello', 5 | description: 'hello', 6 | version: 'hello', 7 | author: 'hello', 8 | homepage: 'hello', 9 | repositoryUrl: 'hello', 10 | contributingUrl: 'hello', 11 | githubUsername: 'hello', 12 | engines: 'hello', 13 | licenseName: { 14 | name: 'hello' 15 | }, 16 | licenseUrl: 'hello', 17 | documentationUrl: 'hello', 18 | isGithubRepo: false, 19 | usage: 'hello', 20 | testCommand: 'hello', 21 | isGithubRepos: false, 22 | projectName: 'hello', 23 | projectPrerequisites: 'hello', 24 | isProjectOnNpm: 'hello' 25 | }; 26 | 27 | const questionResponse: IQuestionResponse = { 28 | type: 'hello', 29 | name: 'hello', 30 | message: 'hello', 31 | default: 'hello', 32 | filter: 'hello', 33 | choices: 'hello', 34 | when: 'hello', 35 | }; 36 | 37 | const projectInfoEmptyLicense: IProjectInfosWithMissingFields = { 38 | name: 'hello', 39 | description: 'hello', 40 | version: 'hello', 41 | author: 'hello', 42 | homepage: 'hello', 43 | repositoryUrl: 'hello', 44 | contributingUrl: 'hello', 45 | githubUsername: 'hello', 46 | engines: 'hello', 47 | licenseName: null, 48 | licenseUrl: 'hello', 49 | documentationUrl: 'hello', 50 | isGithubRepo: false, 51 | usage: 'hello', 52 | testCommand: 'hello', 53 | isGithubRepos: false, 54 | projectName: 'hello', 55 | projectPrerequisites: 'hello', 56 | isProjectOnNpm: 'hello' 57 | }; 58 | 59 | export { 60 | projectInfo, 61 | questionResponse, 62 | projectInfoEmptyLicense 63 | }; 64 | -------------------------------------------------------------------------------- /src/core/actions/createActions/tests/mock/index.mock.ts: -------------------------------------------------------------------------------- 1 | const required = { 2 | required: true, 3 | optional: false 4 | }; 5 | 6 | const optional = { 7 | required: false, 8 | optional: true 9 | }; 10 | 11 | export const currentFile = { 12 | name: 'file', 13 | exists: true, 14 | path: '/path/to/file', 15 | templatePath: '/path/to/template' 16 | }; 17 | 18 | const file = { 19 | required: false, 20 | optional: false, 21 | isEmpty: true, 22 | file: ['readme'], 23 | resp: { 24 | name: 'README.md', 25 | exists: true, 26 | path: './README.md', 27 | }, 28 | LICENSE: { 29 | name: 'LICENSE', 30 | exists: false, 31 | path: './LICENSE', 32 | }, 33 | CODE_OF_CONDUCT: { 34 | name: 'CODE_OF_CONDUCT.md', 35 | exists: false, 36 | path: './CODE_OF_CONDUCT.md', 37 | templatePath: '../../templates/files/required/template-CODE_OF_CONDUCT.md' 38 | }, 39 | PULL_REQUEST_TEMPLATE: { 40 | name: 'PULL_REQUEST_TEMPLATE.md', 41 | exists: false, 42 | path: './.github/PULL_REQUEST_TEMPLATE.md', 43 | templatePath: '../../templates/files/required/template-PULL_REQUEST_TEMPLATE.md' 44 | }, 45 | BUG_REPORT: { 46 | name: 'bug_report.md', 47 | exists: false, 48 | path: './.github/ISSUE_TEMPLATE/bug_report.md', 49 | templatePath: '../../templates/files/required/template-BUG_REPORT.md' 50 | }, 51 | FEATURE_REQUEST: { 52 | name: 'feature_request.md', 53 | exists: true, 54 | path: './.github/ISSUE_TEMPLATE/feature_request.md', 55 | templatePath: '../../templates/files/required/template-FEATURE_REQUEST.md' 56 | }, 57 | }; 58 | 59 | const none = { 60 | required: false, 61 | optional: false, 62 | }; 63 | 64 | export { 65 | required, 66 | optional, 67 | file, 68 | none 69 | }; 70 | -------------------------------------------------------------------------------- /src/core/actions/removeActions/tests/mock/index.mock.ts: -------------------------------------------------------------------------------- 1 | const required = { 2 | required: true, 3 | optional: false 4 | }; 5 | 6 | const optional = { 7 | required: false, 8 | optional: true 9 | }; 10 | 11 | export const currentFile = { 12 | name: 'file', 13 | exists: true, 14 | path: '/path/to/file', 15 | templatePath: '/path/to/template' 16 | }; 17 | 18 | const file = { 19 | required: false, 20 | optional: false, 21 | isEmpty: true, 22 | file: ['readme'], 23 | resp: { 24 | name: 'README.md', 25 | exists: true, 26 | path: './README.md', 27 | }, 28 | LICENSE: { 29 | name: 'LICENSE', 30 | exists: false, 31 | path: './LICENSE', 32 | }, 33 | CODE_OF_CONDUCT: { 34 | name: 'CODE_OF_CONDUCT.md', 35 | exists: false, 36 | path: './CODE_OF_CONDUCT.md', 37 | templatePath: '../../templates/files/required/template-CODE_OF_CONDUCT.md' 38 | }, 39 | PULL_REQUEST_TEMPLATE: { 40 | name: 'PULL_REQUEST_TEMPLATE.md', 41 | exists: false, 42 | path: './.github/PULL_REQUEST_TEMPLATE.md', 43 | templatePath: '../../templates/files/required/template-PULL_REQUEST_TEMPLATE.md' 44 | }, 45 | BUG_REPORT: { 46 | name: 'bug_report.md', 47 | exists: false, 48 | path: './.github/ISSUE_TEMPLATE/bug_report.md', 49 | templatePath: '../../templates/files/required/template-BUG_REPORT.md' 50 | }, 51 | FEATURE_REQUEST: { 52 | name: 'feature_request.md', 53 | exists: true, 54 | path: './.github/ISSUE_TEMPLATE/feature_request.md', 55 | templatePath: '../../templates/files/required/template-FEATURE_REQUEST.md' 56 | }, 57 | }; 58 | 59 | const none = { 60 | required: false, 61 | optional: false, 62 | }; 63 | 64 | export { 65 | required, 66 | optional, 67 | file, 68 | none 69 | }; 70 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint-config-airbnb", 3 | "jsRules": { 4 | "eofline": true 5 | }, 6 | "rules": { 7 | "import-name": [ 8 | false 9 | ], 10 | "align": [ 11 | true 12 | ], 13 | "ter-arrow-parens": [ 14 | true 15 | ], 16 | "deprecation": { 17 | "severity": "warn" 18 | }, 19 | "interface-name": true, 20 | "max-classes-per-file": true, 21 | "max-line-length": [ 22 | true, 23 | 120 24 | ], 25 | "member-ordering": [ 26 | true, 27 | { 28 | "order": [ 29 | "static-field", 30 | "instance-field", 31 | "static-method", 32 | "instance-method" 33 | ] 34 | } 35 | ], 36 | "no-consecutive-blank-lines": true, 37 | "no-console": [ 38 | true, 39 | "debug", 40 | "info", 41 | "time", 42 | "timeEnd", 43 | "trace" 44 | ], 45 | "no-empty": true, 46 | "no-inferrable-types": [ 47 | false, 48 | "ignore-params" 49 | ], 50 | "no-non-null-assertion": true, 51 | "no-redundant-jsdoc": true, 52 | "no-switch-case-fall-through": true, 53 | "no-use-before-declare": true, 54 | "no-var-requires": false, 55 | "object-literal-key-quotes": [ 56 | true, 57 | "as-needed" 58 | ], 59 | "trailing-comma": false, 60 | "no-output-on-prefix": true, 61 | "use-input-property-decorator": true, 62 | "use-output-property-decorator": true, 63 | "use-host-property-decorator": true, 64 | "no-input-rename": true, 65 | "no-output-rename": true, 66 | "use-life-cycle-interface": true, 67 | "use-pipe-transform-interface": true, 68 | "component-class-suffix": true, 69 | "directive-class-suffix": true, 70 | "no-conditional-assignment": true, 71 | "object-shorthand-properties-first": false, 72 | "quotemark": [true, "single", "avoid-escape"] 73 | } 74 | } -------------------------------------------------------------------------------- /src/templates/fileWriter/index.ts: -------------------------------------------------------------------------------- 1 | import ejs from 'ejs'; 2 | import fs from 'fs'; 3 | import { unescape } from 'lodash'; 4 | import { promisify } from 'util'; 5 | import { dirname } from 'path'; 6 | import { log } from '../../common/index'; 7 | import { IProjectInfos } from '../../../types/typeDeclarations.interface'; 8 | 9 | 10 | /** 11 | * @description writes file and makes parent directories if required 12 | * 13 | * @param path path to file 14 | * @param text content to be written 15 | */ 16 | const writeFile = (text: string, path: string): Promise => { 17 | const errMsg = () => log(`${path.split('/').pop()} creation unsuccessful`); 18 | // @ts-ignore 19 | return fs.mkdir(dirname(path), (err: any): any => { 20 | if (err && err.code !== 'EEXIST') return errMsg(); 21 | return fs.writeFile(path, unescape(text), (e: any): void => { 22 | if (e) errMsg(); 23 | }); 24 | }); 25 | }; 26 | 27 | /** 28 | * @description Get file template content from the given templatePath 29 | * 30 | * @param templatePath path to template 31 | */ 32 | const getFileTemplate = async (templatePath: string): Promise => { 33 | try { 34 | const template = await promisify(fs.readFile)(templatePath, 'utf8'); 35 | return template; 36 | } catch (err) { 37 | throw err; 38 | } 39 | }; 40 | 41 | /** 42 | * @description create file content with the given context and templatePath 43 | * @param context Project information (project / user information) 44 | * @param templatePath path to template 45 | */ 46 | const buildFileContent = async (context: IProjectInfos, templatePath: string) => { 47 | const currentYear: number = new Date().getFullYear(); 48 | const template: string = await getFileTemplate(templatePath); 49 | 50 | return ejs.render(template, { 51 | filename: templatePath, 52 | currentYear, 53 | ...context, 54 | }); 55 | }; 56 | 57 | export { writeFile, getFileTemplate, buildFileContent }; 58 | -------------------------------------------------------------------------------- /src/core/questions/setupQuestions/removeCommand/removeInit.ts: -------------------------------------------------------------------------------- 1 | import { getFullFileNames } from '../../../../common/index'; 2 | import { ICurrentFile, IQuestionResponse } from '../../../../../types/typeDeclarations.interface'; 3 | 4 | /** 5 | * @description 6 | * Return question for files removal 7 | * 8 | * @param files optional files names array 9 | */ 10 | const removeFiles = (files: ICurrentFile[]): any => ({ 11 | type: 'checkbox', 12 | name: 'removeFiles', 13 | message: 'Which of the following files(s) would you like to delete?\n', 14 | choices: [...getFullFileNames(files)], 15 | }); 16 | 17 | /** 18 | * @description 19 | * Return validation for files removal; 20 | * 21 | * @param files optional files names array 22 | */ 23 | const validateRemove = (files: ICurrentFile[]): any => ({ 24 | type: 'confirm', 25 | name: 'removeFiles', 26 | message: `Are you sure you would like to delete the following .md file(s)? \n\n${files}`, 27 | default: false, 28 | }); 29 | 30 | /** 31 | * @description 32 | * Return question for required files removal 33 | * 34 | * @param files optional files names array 35 | */ 36 | const removeRequired = (files: ICurrentFile[]): IQuestionResponse => ({ 37 | type: 'checkbox', 38 | name: 'removeFiles', 39 | message: 40 | 'Which of the following REQUIRED .md files(s) would you like to delete?\n', 41 | choices: [...getFullFileNames(files)], 42 | }); 43 | 44 | /** 45 | * @description 46 | * Return question for optional files removal 47 | * 48 | * @param files optional files names array 49 | */ 50 | const removeOptional = (files: ICurrentFile[]): IQuestionResponse => ({ 51 | type: 'checkbox', 52 | name: 'removeFiles', 53 | message: 54 | 'Which of the following OPTIONAL .md files(s) would you like to delete?\n', 55 | choices: [...getFullFileNames(files)], 56 | }); 57 | 58 | export { 59 | removeFiles, 60 | validateRemove, 61 | removeRequired, 62 | removeOptional 63 | }; 64 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import program from 'commander'; 3 | import { customHelp, wrongCommandAlert, noCommandAlert } from './common/index'; 4 | import handleCommand from './core/validations/ValidateOptions'; 5 | 6 | program 7 | .name('md-generator') 8 | .version('0.6.1') 9 | .action((command: string) => wrongCommandAlert(command)) 10 | .on('--help', () => { 11 | customHelp(); 12 | }); 13 | 14 | program 15 | .command('list [env]') 16 | .description('list All Required/optional .md files') 17 | .option('-O --optional', 'list all optional files') 18 | .option('-R --required', 'list all required files') 19 | .action((_type: any, args: any) => { 20 | handleCommand(args); 21 | }); 22 | 23 | program 24 | .command('create [env]') 25 | .description('create All/specific files') 26 | .option('-O, --optional', 'create all optional files') 27 | .option('-R, --required', 'create all required files') 28 | .option('-A, --all [value]', 'create all required/optional .md files') 29 | .option('-F, --file [value]', 'Create specific .md files') 30 | .option('-E, --empty', 'make created files empty') 31 | .action((_type: any, args: any) => { 32 | handleCommand(args); 33 | }); 34 | 35 | program 36 | .command('check [env]') 37 | .description('check for missing .md files') 38 | .option('-O, --optional', 'check all optional files') 39 | .option('-R, --required', 'check all required files') 40 | .action((_type: any, args: any) => { 41 | handleCommand(args); 42 | }); 43 | 44 | program 45 | .command('remove [env]') 46 | .description('remove All/specific .md files') 47 | .option('-A, --all', 'remove all .md files') 48 | .option('-O, --optional', 'remove all optional files') 49 | .option('-R, --required', 'remove all required files') 50 | .option('-F, --file [value]', 'remove specific .md files') 51 | .action((_type: any, args: any) => { 52 | handleCommand(args); 53 | }); 54 | 55 | if (!process.argv[2]) noCommandAlert(); 56 | 57 | program.parse(process.argv); 58 | -------------------------------------------------------------------------------- /src/core/coreUtils/index.ts: -------------------------------------------------------------------------------- 1 | import program from 'commander'; 2 | import { log } from '../../common'; 3 | import { IArguments } from '../../../types/typeDeclarations.interface'; 4 | 5 | /** 6 | * @description 7 | * log help text to the terminal 8 | * 9 | * @param text custom preliminary string to be logged 10 | */ 11 | const showHelp = (text: string): void => { 12 | log(text); 13 | program.help(); 14 | process.exit(1); 15 | }; 16 | 17 | /** 18 | * @description 19 | * logs help message on wrong arguments 20 | * 21 | * @param type response payload 22 | */ 23 | const showHelpOnError = (type: any): void => { 24 | const option = type.parent.rawArgs[3]; 25 | const NO_COMMAND_SPECIFIED = Object.keys(program.opts()).every( 26 | (key: string) => program.opts()[`${key}`] === undefined || key === 'version' 27 | ); 28 | if (NO_COMMAND_SPECIFIED) { 29 | showHelp(`Invalid Option: ${option}`); 30 | } 31 | }; 32 | 33 | /** 34 | * Extract all needed options in each mode 35 | */ 36 | class ExtractOptions { 37 | public static list: any = (args: any): IArguments => { 38 | const { optional, required }: any = args; 39 | return { optional, required }; 40 | }; 41 | 42 | public static create: any = (args: any): IArguments => { 43 | const { 44 | optional, 45 | required, 46 | all, 47 | file, 48 | empty 49 | }: any = args; 50 | return { 51 | optional, 52 | required, 53 | all, 54 | file, 55 | empty, 56 | }; 57 | }; 58 | 59 | public static check = (args: any): IArguments => { 60 | const { optional, required }: any = args; 61 | return { optional, required }; 62 | }; 63 | 64 | public static remove = (args: any): IArguments => { 65 | const { 66 | all, 67 | file, 68 | required, 69 | optional 70 | }: any = args; 71 | return { 72 | all, 73 | file, 74 | required, 75 | optional 76 | }; 77 | }; 78 | } 79 | 80 | export { showHelpOnError, showHelp, ExtractOptions }; 81 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ### Contributing 2 | When contributing to this repository, Kindly first discuss the change you wish to make via issue, or any other method with the owners of this repository before making a change. 3 | 4 | Also, kindly note read through the code of conduct as described in the `code_of_conduct.md` file, as it better guides your interactions with the project. 5 | 6 | ### Pull Request Process 7 | - Ensure any install or build dependencies are removed before the end of the layer when doing a build. 8 | - Ensure you follow strictly the pre-populated `pull_Request template`. 9 | - Update the `README.md` with details of changes to the interface, this includes new environment variables, exposed ports, useful file locations and container parameters. 10 | - Increase the version numbers in any examples files and the `README.md` to the new version that this Pull Request would represent. The versioning scheme we use is semantic versioning ( `SemVer` ). 11 | - You may merge the Pull Request in once you have the sign-off of three other developers, or if you do not have permission to do that, you may request the second reviewer to merge it for you. 12 | 13 | ### Code of Conduct 14 | #### Our Pledge 15 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 16 | 17 | #### Our Standards 18 | Examples of behavior that contributes to creating a positive environment include: 19 | 20 | - Using welcoming and inclusive language 21 | - Being respectful of differing viewpoints and experiences 22 | - Gracefully accepting constructive criticism 23 | - Focusing on what is best for the community 24 | - Showing empathy towards other community members 25 | - Examples of unacceptable behavior by participants include: 26 | -------------------------------------------------------------------------------- /src/templates/files/required/template-CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ### Contributing 2 | When contributing to this repository, Kindly first discuss the change you wish to make via issue, or any other method with the owners of this repository before making a change. 3 | 4 | Also, kindly note read through the code of conduct as described in the `code_of_conduct.md` file, as it better guides your interactions with the project. 5 | 6 | ### Pull Request Process 7 | - Ensure any install or build dependencies are removed before the end of the layer when doing a build. 8 | - Ensure you follow strictly the pre-populated `pull_Request template`. 9 | - Update the `README.md` with details of changes to the interface, this includes new environment variables, exposed ports, useful file locations and container parameters. 10 | - Increase the version numbers in any examples files and the `README.md` to the new version that this Pull Request would represent. The versioning scheme we use is semantic versioning ( `SemVer` ). 11 | - You may merge the Pull Request in once you have the sign-off of three other developers, or if you do not have permission to do that, you may request the second reviewer to merge it for you. 12 | 13 | ### Code of Conduct 14 | #### Our Pledge 15 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 16 | 17 | #### Our Standards 18 | Examples of behavior that contributes to creating a positive environment include: 19 | 20 | - Using welcoming and inclusive language 21 | - Being respectful of differing viewpoints and experiences 22 | - Gracefully accepting constructive criticism 23 | - Focusing on what is best for the community 24 | - Showing empathy towards other community members 25 | - Examples of unacceptable behavior by participants include: 26 | -------------------------------------------------------------------------------- /src/core/validations/tests/IsInvalidArgs.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import IsValidArgs, { isValidOption } from '../IsInvalidArgs'; 4 | import { validArguments } from './mock/index.mock'; 5 | 6 | export default describe('Validation tests', () => { 7 | it('should return false for invalid arguments', () => { 8 | const response: boolean = isValidOption(validArguments.listAndCheck, [['empty']]); 9 | expect(response).to.be.equal(false); 10 | }); 11 | it('should return false for invalid argument on the list option', () => { 12 | const response: boolean = IsValidArgs.list(['empty']); 13 | expect(response).to.be.equal(false); 14 | }); 15 | it('should return false for invalid argument on the create option', () => { 16 | const response: boolean = IsValidArgs.create(['empty']); 17 | expect(response).to.be.equal(false); 18 | }); 19 | it('should return false for invalid argument on the check option', () => { 20 | const response: boolean = IsValidArgs.check(['empty']); 21 | expect(response).to.be.equal(false); 22 | }); 23 | it('should return false for invalid argument on the remove option', () => { 24 | const response: boolean = IsValidArgs.remove(['empty']); 25 | expect(response).to.be.equal(false); 26 | }); 27 | it('should return true for valid arguments on the list option', () => { 28 | const response: boolean = IsValidArgs.list(['required']); 29 | expect(response).to.be.equal(true); 30 | }); 31 | it('should return true for valid arguments on the create option', () => { 32 | const response: boolean = IsValidArgs.create(['required']); 33 | expect(response).to.be.equal(true); 34 | }); 35 | it('should return true for valid arguments on the check option', () => { 36 | const response: boolean = IsValidArgs.check(['required']); 37 | expect(response).to.be.equal(true); 38 | }); 39 | it('should return true for valid arguments on the remove option', () => { 40 | const response: boolean = IsValidArgs.remove(['required']); 41 | expect(response).to.be.equal(true); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /src/core/actions/checkActions/index.ts: -------------------------------------------------------------------------------- 1 | import pad from 'pad'; 2 | import getInfos from '../../../projectEnv/projectInfo'; 3 | import { 4 | log, 5 | red, 6 | gray, 7 | green, 8 | dimWhite, 9 | spinner, 10 | whiteUnderline, 11 | checkCommunityStandardMet 12 | } from '../../../common/index'; 13 | import { 14 | requiredFiles, 15 | optionalFiles 16 | } from '../actionsUtils'; 17 | 18 | import {IProjectInfos, IFilesCategories } from '../../../../types/typeDeclarations.interface'; 19 | 20 | /** 21 | * @description 22 | * Check if .md file exists in the codebase 23 | * 24 | * @param file file name 25 | */ 26 | const check = (file: any): void => { 27 | Object.keys(file).forEach((key: string): void => { 28 | if (!file[key].exists) { 29 | return log( 30 | pad(red(' X '), 12), 31 | gray(pad(`${file[key].name}`, 27), 'Not found') 32 | ); 33 | } 34 | return log( 35 | pad(green(' √ '), 12), 36 | dimWhite(pad(`${file[key].name}`, 28)), 37 | 'exists' 38 | ); 39 | }); 40 | log('\n'); 41 | }; 42 | 43 | /** 44 | * @description 45 | * List all required/optional .md files present in the codebase 46 | * 47 | * @param values arguments i.e command and command options 48 | */ 49 | const checkHandler: any = async (values: IFilesCategories): Promise => { 50 | const spin: any = spinner('Checking for all Required / Optional .md files . . .'); 51 | const { required, optional }: IFilesCategories = values; 52 | const all: boolean = !optional && !required; 53 | // const USE_DEFAULT: boolean = true; 54 | await getInfos().then((projectInfos: IProjectInfos) => { 55 | spin.succeed('Done'); 56 | const { githubUsername, name } = projectInfos; 57 | if (required || all) { 58 | log(whiteUnderline('Required Files :\n')); 59 | check(requiredFiles); 60 | } 61 | if (optional || all) { 62 | log(whiteUnderline('Optional Files :\n')); 63 | check(optionalFiles); 64 | } 65 | checkCommunityStandardMet(githubUsername, name); 66 | }); 67 | }; 68 | 69 | export default checkHandler; 70 | -------------------------------------------------------------------------------- /src/core/actions/listActions/index.ts: -------------------------------------------------------------------------------- 1 | import pad from 'pad'; 2 | import getInfos from '../../../projectEnv/projectInfo'; 3 | import { 4 | log, 5 | cyan, 6 | spinner, 7 | whiteUnderline, 8 | checkCommunityStandardMet 9 | } from '../../../common/index'; 10 | import {IArguments, IProjectInfos} from '../../../../types/typeDeclarations.interface'; 11 | 12 | /** 13 | * @description 14 | * List available .m files i.e required and/or optional 15 | * 16 | * @param values arguments i.e command and command options 17 | */ 18 | const listHandler = async (values: IArguments): Promise => { 19 | const spin: any = spinner('Generating Requested List . . .'); 20 | const { required, optional }: IArguments = values; 21 | const all: boolean = !optional && !required; 22 | await getInfos().then((projectInfos: IProjectInfos) => { 23 | spin.succeed('Done'); 24 | const { githubUsername, name }: IProjectInfos = projectInfos; 25 | if (required || all) { 26 | log( 27 | whiteUnderline( 28 | 'Required .md files needed to meet community standards :\n' 29 | ) 30 | ); 31 | log(pad('📘', 5), cyan('README.md')); 32 | log(pad('📘', 5), cyan('LICENSE')); 33 | log(pad('📘', 5), cyan('CODE_OF_CONDUCT.md')); 34 | log(pad('📘', 5), cyan('PULL_REQUEST_TEMPLATE.md')); 35 | log(pad('📘', 5), cyan('CONTRIBUTING.md')); 36 | log(pad('📘', 5), cyan('bug_report.md')); 37 | log(pad('📘', 5), cyan('feature_request.md')); 38 | } 39 | if (optional || all) { 40 | log(whiteUnderline('\n Optional .md files :\n')); 41 | log(pad('📘', 5), cyan('CHANGELOG.md')); 42 | log(pad('📘', 5), cyan('SUPPORT.md')); 43 | log(pad('📘', 5), cyan('CONTRIBUTORS.md')); 44 | log(pad('📘', 5), cyan('AUTHORS.md')); 45 | log(pad('📘', 5), cyan('ACKNOWLEDGMENTS.md')); 46 | log(pad('📘', 5), cyan('CODEOWNERS.md')); 47 | } 48 | log('\nDo remember to add a description to your repository.'); 49 | checkCommunityStandardMet(githubUsername, name); 50 | }); 51 | }; 52 | 53 | export default listHandler; 54 | -------------------------------------------------------------------------------- /src/templates/files/optional/template-CODEOWNERS.md: -------------------------------------------------------------------------------- 1 | # Custom Fields 2 | administrator/components/com_fields/* @ 3 | components/com_fields/* @ 4 | plugins/content/fields/* @ 5 | plugins/editors-xtd/fields/* @ 6 | plugins/fields/* @ 7 | plugins/systems/fields/* @ 8 | 9 | # Smart Search 10 | administrator/components/com_finder/* @ 11 | components/com_finder/* @ 12 | modules/mod_finder/* @ 13 | plugins/content/finder/* @ 14 | plugins/finder/* @ 15 | 16 | # Language strings 17 | administrator/language/en-GB/* @ 18 | installation/language/en-GB/* @ 19 | language/en-GB/* @ 20 | README.md @ 21 | README.txt @ 22 | 23 | # CodeMirror 24 | media/editors/codemirror/* @ 25 | plugins/editors/codemirror/* @ 26 | 27 | # Statistics Server 28 | plugins/system/stats/* @ 29 | 30 | # Release Tools 31 | build.xml @ 32 | build/build.php @ 33 | build/bump.php @ 34 | build/deleted_file_check.php @ 35 | 36 | # Core/Extension Install/Update Tools 37 | administrator/components/com_joomlaupdate/* @ 38 | libraries/src/Installer/* @ 39 | libraries/src/Updater/* @ 40 | 41 | # Automated Testing 42 | build/jenkins/* @ 43 | build/travis/* @ 44 | tests/codeception/* @ 45 | tests/javascript/* @ 46 | tests/unit/* @ 47 | .appveyor.yml @ 48 | .drone.yml @ 49 | .hound.yml @ 50 | .travis.yml @ 51 | appveyor-phpunit.xml @ 52 | codeception.yml @ 53 | karma.conf.js @ 54 | phpunit.xml.dist @ 55 | RoboFile.dist.ini @ 56 | RoboFile.php @ 57 | travis-phpunit.xml @ 58 | 59 | # Core JS 60 | media/*/js/* @ 61 | 62 | # CSP Tooling 63 | plugins/system/httpheaders/* @ 64 | administrator/components/com_csp/* @ 65 | components/com_csp/* @ 66 | -------------------------------------------------------------------------------- /src/common/tests/alert.spec.ts: -------------------------------------------------------------------------------- 1 | import { 2 | log, 3 | useBox, 4 | spinner, 5 | useHelpAlert, 6 | showEndMessage, 7 | customHelp, 8 | noCommandAlert, 9 | wrongCommandAlert, 10 | castElementsToFormatedString, 11 | checkCommunityStandardMet, 12 | fileNotDetectedAlert, 13 | unrecognizedFileAlert 14 | } from '../alerts'; 15 | import { expect } from 'chai'; 16 | import elements from './mock/alert.mock'; 17 | 18 | export default describe('test functions in common/alert', () => { 19 | it('should log arguments to the console', () => { 20 | const logData: any = log('print', 'hello', 'world'); 21 | expect(logData).to.equal(undefined); 22 | }); 23 | it('should call the useBox', () => { 24 | const logData: any = useBox('print'); 25 | expect(logData).to.equal(undefined); 26 | }); 27 | it('should call showEndMessage', () => { 28 | const logData: any = showEndMessage('print', 'hello', 'world'); 29 | expect(logData).to.equal(undefined); 30 | }); 31 | it('should call customHelp', () => { 32 | const logData: any = customHelp(); 33 | expect(logData).to.equal(undefined); 34 | }); 35 | it('should call wrongCommandAlert', () => { 36 | const logData: any = wrongCommandAlert(); 37 | expect(logData).to.equal(undefined); 38 | }); 39 | it('should call noCommandAlert', () => { 40 | const logData: any = noCommandAlert(); 41 | expect(logData).to.equal(undefined); 42 | }); 43 | it('should trigger spinner', () => { 44 | const logData: any = spinner('hello'); 45 | expect(logData.options.text).to.equal('hello'); 46 | }); 47 | it('should cast Elements ToFormated String', () => { 48 | const logData: any = castElementsToFormatedString(elements); 49 | expect(logData[5]).to.equal('-'); 50 | }); 51 | it('should trigger fileNotDetectedAlert', () => { 52 | const logData: any = fileNotDetectedAlert(); 53 | expect(logData).to.equal(undefined); 54 | }); 55 | 56 | it('should trigger unrecognizedFileAlert', () => { 57 | const logData: any = unrecognizedFileAlert(elements); 58 | expect(logData).to.equal(undefined); 59 | }); 60 | it('should trigger useHelpAlert', () => { 61 | const logData: any = useHelpAlert(); 62 | expect(logData).to.equal(undefined); 63 | }); 64 | it('should log link to github', () => { 65 | const messageWithParameters: any = checkCommunityStandardMet('username', 'project'); 66 | expect(messageWithParameters).to.equal(undefined); 67 | const messageWithoutParameters: any = checkCommunityStandardMet(); 68 | expect(messageWithoutParameters).to.equal(undefined); 69 | }); 70 | }); 71 | 72 | -------------------------------------------------------------------------------- /src/core/questions/setupQuestions/index.ts: -------------------------------------------------------------------------------- 1 | import askProjectName from './projectQuestions/project-name'; 2 | import askProjectVersion from './projectQuestions/project-version'; 3 | import askProjectDescription from './projectQuestions/project-description'; 4 | import askProjectHomepage from './projectQuestions/project-homepage'; 5 | import askProjectDocumentationUrl from './projectQuestions/project-documentation-url'; 6 | import askAuthorName from './projectQuestions/author-name'; 7 | import askAuthorEmail from './projectQuestions/author-email'; 8 | import askAuthorGithub from './projectQuestions/author-github'; 9 | import askAuthorTwitter from './projectQuestions/author-twitter'; 10 | import askAuthorPatreon from './projectQuestions/author-patreon'; 11 | import {askProjectPrerequisites} from './projectQuestions/project-prerequisites'; 12 | import askLicenseName from './projectQuestions/license-name'; 13 | import askLicenseUrl from './projectQuestions/license-url'; 14 | import askContributing from './projectQuestions/contributing'; 15 | import askInstallCommand from './projectQuestions/install-command'; 16 | import askUsage from './projectQuestions/usage'; 17 | import askTestCommand from './projectQuestions/test-command'; 18 | import chooseTemplate from './projectQuestions/choose-template'; 19 | import { 20 | removeFiles, 21 | validateRemove, 22 | removeRequired, 23 | removeOptional, 24 | } from './removeCommand/removeInit'; 25 | import { 26 | createFiles, 27 | createRequired, 28 | createOptional, 29 | selectFileToCreate, 30 | createEmptyFiles, 31 | overrideFiles 32 | } from './createCommand/createInit'; 33 | 34 | const licenseQuestions = { 35 | askLicenseName, 36 | askLicenseUrl, 37 | askAuthorName, 38 | askAuthorEmail 39 | }; 40 | 41 | const codeOfConductQuestions = { 42 | askAuthorName, 43 | askAuthorEmail 44 | }; 45 | 46 | const readmeQuestions = { 47 | chooseTemplate, 48 | askProjectName, 49 | askProjectVersion, 50 | askProjectHomepage, 51 | askProjectDescription, 52 | askLicenseName, 53 | askLicenseUrl, 54 | askInstallCommand, 55 | askTestCommand, 56 | askProjectDocumentationUrl, 57 | askAuthorName, 58 | askAuthorGithub, 59 | askAuthorTwitter, 60 | askAuthorPatreon, 61 | askProjectPrerequisites, 62 | askContributing, 63 | askUsage, 64 | askAuthorEmail 65 | }; 66 | 67 | export { 68 | licenseQuestions, 69 | readmeQuestions, 70 | removeFiles, 71 | validateRemove, 72 | removeRequired, 73 | removeOptional, 74 | createFiles, 75 | createRequired, 76 | createOptional, 77 | selectFileToCreate, 78 | createEmptyFiles, 79 | overrideFiles, 80 | codeOfConductQuestions 81 | }; 82 | -------------------------------------------------------------------------------- /src/projectEnv/tests/utils.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { 3 | getProjectName, 4 | getPackageJson, 5 | getDefaultAnswer, 6 | getDefaultAnswers, 7 | getGitRepositoryName, 8 | isProjectAvailableOnNpm, 9 | cleanSocialNetworkUsername 10 | } from '../utils'; 11 | import { 12 | questionResponse1, 13 | questionResponse2, 14 | questionResponse3, 15 | questionResponse4 16 | } from './mock/index.mock'; 17 | export default describe('test functions in projectEnv/utils', async () => { 18 | it('should return repository name', async () => { 19 | const response = getGitRepositoryName(process.cwd()); 20 | expect(response).to.be.equal('md-generator'); 21 | }); 22 | it('should return undefined if repo name cannot be retrieved', async () => { 23 | const response = getGitRepositoryName({}); 24 | expect(response).to.be.equal(undefined); 25 | }); 26 | it('should return project name', async () => { 27 | const response = getProjectName({}); 28 | expect(response).to.be.equal('md-generator'); 29 | }); 30 | it('should return Package.json', async () => { 31 | const response = await getPackageJson(); 32 | expect(response.name).to.be.equal('md-generator'); 33 | expect(response.main).to.be.equal('index.js'); 34 | }); 35 | it('should return true if project is available on npm', async () => { 36 | const response = isProjectAvailableOnNpm('md-generator'); 37 | expect(response).to.be.equal(true); 38 | }); 39 | it('should return "" question type = input and default answer undefined', async () => { 40 | const response = getDefaultAnswer(questionResponse1, ''); 41 | expect(response).to.be.equal(''); 42 | }); 43 | it('should return true when value of selected choice is true', async () => { 44 | const response = getDefaultAnswer(questionResponse2, ''); 45 | expect(response[0]).to.be.equal(true); 46 | }); 47 | it('should return undefined when question type is neither input or checkbox', async () => { 48 | const response = getDefaultAnswer(questionResponse3, ''); 49 | expect(response).to.be.equal(undefined); 50 | }); 51 | it('should return undefined with falsy when condition', async () => { 52 | const response = getDefaultAnswer(questionResponse4, ''); 53 | expect(response).to.be.equal(undefined); 54 | }); 55 | it('should get answers for multiple questions', async () => { 56 | const response = getDefaultAnswers(['']); 57 | expect(response).to.be.equal(undefined); 58 | }); 59 | it('should clean social network username removing an "@" character', async () => { 60 | const response = cleanSocialNetworkUsername('@username'); 61 | expect(response).to.be.equal('username'); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /src/core/questions/askQuestions.ts: -------------------------------------------------------------------------------- 1 | import inquirer from 'inquirer'; 2 | import { flatMap } from 'lodash'; 3 | 4 | import { 5 | readmeQuestions, 6 | licenseQuestions, 7 | codeOfConductQuestions, 8 | } from './setupQuestions/index'; 9 | import * as utils from '../../projectEnv/utils'; 10 | import getProjectInfos from '../../projectEnv/projectInfo'; 11 | import { IProjectInfos, IQuestions } from '../../../types/typeDeclarations.interface'; 12 | 13 | export const find = (files: string[], item: string): any => files.find((element: string) => element === item); 14 | export const getAppropriateQuestion = (files: string[]): IQuestions| null => { 15 | const readmeQstn = find(files, 'README') ? readmeQuestions : null; 16 | const licenseQstn = find(files, 'LICENSE') ? licenseQuestions : null; 17 | const codeOfConductQstn = find(files, 'CODE_OF_CONDUCT') 18 | ? codeOfConductQuestions 19 | : null; 20 | return readmeQstn || licenseQstn || codeOfConductQstn; 21 | }; 22 | 23 | /** 24 | * @description Ask user questions on the terminal and returns selected answers 25 | * @param projectInfos project information retrieved from the codebase / supplied by user 26 | * @param useDefaultAnswers boolean 27 | */ 28 | 29 | const askQuestions = async ( 30 | projectInfos: IProjectInfos, 31 | useDefaultAnswers: boolean, 32 | filesToBeCreated: string[] 33 | ): Promise => { 34 | const filteredQuestions = getAppropriateQuestion(filesToBeCreated); 35 | const getQuestions = (questions: any) => 36 | flatMap(Object.values(questions), (questionBuilder: any) => questionBuilder(projectInfos)); 37 | const ask = async (questions: any) => { 38 | if (questions) { 39 | const data = await inquirer.prompt(questions); 40 | return data; 41 | } 42 | return utils.getDefaultAnswers(getQuestions(readmeQuestions)); 43 | }; 44 | const question: any = filteredQuestions ? getQuestions(filteredQuestions) : null; 45 | 46 | const answersContext: IProjectInfos = useDefaultAnswers 47 | ? utils.getDefaultAnswers(getQuestions(readmeQuestions)) 48 | : await ask(question); 49 | const { isGithubRepos, repositoryUrl } = projectInfos; 50 | return { 51 | isGithubRepos, 52 | repositoryUrl, 53 | projectPrerequisites: undefined, 54 | isProjectOnNpm: utils.isProjectAvailableOnNpm(answersContext.projectName), 55 | ...answersContext, 56 | }; 57 | }; 58 | 59 | /** 60 | * @description 61 | * 1) Gather project infos 62 | * 2) Ask user questions 63 | * 3) return all answers 64 | */ 65 | const getInfos = async (useDefaultAnswers: boolean, filesToBeCreated: string[]) : Promise => { 66 | const projectInformations: IProjectInfos = await getProjectInfos(); 67 | const answersContext: IProjectInfos = await askQuestions( 68 | projectInformations, 69 | useDefaultAnswers, 70 | filesToBeCreated 71 | ); 72 | return answersContext; 73 | }; 74 | 75 | export { askQuestions, getInfos }; 76 | -------------------------------------------------------------------------------- /src/core/questions/setupQuestions/createCommand/createInit.ts: -------------------------------------------------------------------------------- 1 | import { 2 | castElementsToFormatedString, 3 | getFullFileNames, 4 | } from '../../../../common/index'; 5 | import { ICurrentFile, IQuestionResponse } from '../../../../../types/typeDeclarations.interface'; 6 | 7 | /** 8 | * @description 9 | * Question to validate files creation 10 | * 11 | * @param files files names array 12 | */ 13 | const createFiles = (files: ICurrentFile[]): IQuestionResponse => { 14 | const filesAsString = castElementsToFormatedString(getFullFileNames(files)); 15 | return { 16 | type: 'confirm', 17 | name: 'createFiles', 18 | message: `Confirm you would like to create the following .md file(s)\n\n${filesAsString}`, 19 | default: false, 20 | }; 21 | }; 22 | 23 | /** 24 | * @description 25 | * create files Question 26 | * 27 | * @param files files names array 28 | */ 29 | const selectFileToCreate = (files: ICurrentFile[]): IQuestionResponse => ({ 30 | type: 'checkbox', 31 | name: 'createFiles', 32 | message: 'Which of the following .md files would you like to create?\n', 33 | choices: [...getFullFileNames(files)], 34 | }); 35 | 36 | /** 37 | * @description 38 | * create files Question 39 | * 40 | * @param files files names array 41 | */ 42 | const createEmptyFiles = (): any => ({ 43 | type: 'confirm', 44 | name: 'empty', 45 | message: 'Would you like the created file(s) to be empty?\n', 46 | default: false, 47 | }); 48 | 49 | /** 50 | * @description 51 | * create required files Question 52 | * 53 | * @param files required files names array 54 | */ 55 | const createRequired = (files: ICurrentFile[]): IQuestionResponse => ({ 56 | type: 'checkbox', 57 | name: 'createFiles', 58 | message: 59 | 'Which of the following REQUIRED .md files would you like to create?\n', 60 | choices: [...getFullFileNames(files)], 61 | }); 62 | 63 | /** 64 | * @description 65 | * create optional files Question 66 | * 67 | * @param files optional files names array 68 | */ 69 | const createOptional = (files: ICurrentFile[]) => ({ 70 | type: 'checkbox', 71 | name: 'createFiles', 72 | message: 73 | 'Which of the following OPTIONAL .md files would you like to create?\n', 74 | choices: [...getFullFileNames(files)], 75 | }); 76 | 77 | /** 78 | * @description 79 | * create optional files Question 80 | * 81 | * @param files optional files names array 82 | */ 83 | const overrideFiles = (files: string[]): any => { 84 | const filesAsString = castElementsToFormatedString(files); 85 | return { 86 | type: 'confirm', 87 | name: 'override', 88 | message: `The following file(s) exists,\n${filesAsString}\nwould you like to override?\n`, 89 | choices: false, 90 | }; 91 | }; 92 | 93 | export { 94 | createFiles, 95 | createRequired, 96 | createOptional, 97 | selectFileToCreate, 98 | createEmptyFiles, 99 | overrideFiles, 100 | }; 101 | -------------------------------------------------------------------------------- /src/projectEnv/utils.ts: -------------------------------------------------------------------------------- 1 | import loadJsonFile from 'load-json-file'; 2 | import path from 'path'; 3 | import getRepoName from 'git-repo-name'; 4 | import { execSync } from 'child_process'; 5 | import { IQuestionResponse } from '../../types/typeDeclarations.interface'; 6 | 7 | /** 8 | * @description Get package json name property 9 | * 10 | * @param packageJson package.json file 11 | */ 12 | const getPackageJsonName = (packageJson: any = {}): string => packageJson.name || undefined; 13 | 14 | /** 15 | * @description Get git repository name 16 | * @param cwd process.cwd() 17 | */ 18 | const getGitRepositoryName = (cwd: any): any => { 19 | try { 20 | return getRepoName.sync({ cwd }); 21 | } catch (err) { 22 | return undefined; 23 | } 24 | }; 25 | 26 | /** 27 | * @description Get project name 28 | */ 29 | const getProjectName = (packageJson: any): string => { 30 | const cwd = process.cwd(); 31 | return ( 32 | getPackageJsonName(packageJson) || 33 | getGitRepositoryName(cwd) || 34 | path.basename(cwd) 35 | ); 36 | }; 37 | 38 | /** 39 | * @description Get package.json content 40 | */ 41 | const getPackageJson = async (): Promise => { 42 | try { 43 | return await loadJsonFile('package.json'); 44 | } catch (err) { 45 | return undefined; 46 | } 47 | }; 48 | 49 | /** 50 | * @description Return true if the project is available on NPM, return false otherwise. 51 | * @param projectName project name 52 | * @returns boolean 53 | */ 54 | const isProjectAvailableOnNpm = (projectName: string): boolean => { 55 | try { 56 | execSync(`npm view ${projectName}`, { stdio: 'ignore' }); 57 | return true; 58 | } catch (err) { 59 | return false; 60 | } 61 | }; 62 | 63 | /** 64 | * @description Get the default answer depending on the question type 65 | * @param question single question 66 | */ 67 | const getDefaultAnswer = (question: IQuestionResponse, answersContext: any): any => { 68 | if (question.when && !question.when(answersContext)) return undefined; 69 | 70 | switch (question.type) { 71 | case 'input': 72 | return question.default || ''; 73 | case 'checkbox': 74 | return question.choices 75 | .filter((choice: any) => choice.checked) 76 | .map((choice: any) => choice.value); 77 | default: 78 | return undefined; 79 | } 80 | }; 81 | 82 | /** 83 | * @description Get default question's answers 84 | * @param questions all questions 85 | */ 86 | const getDefaultAnswers = (questions: any): any => 87 | questions.reduce( 88 | (answersContext: any, question: any) => ({ 89 | ...answersContext, 90 | [question.name]: getDefaultAnswer(question, answersContext), 91 | }), 92 | {} 93 | ); 94 | 95 | /** 96 | * @description Clean social network username by removing the @ prefix 97 | * @param input social network username input 98 | * @returns input without the prefix 99 | */ 100 | const cleanSocialNetworkUsername = (input: string): string => input.replace(/^@/, ''); 101 | 102 | export { 103 | getPackageJson, 104 | getProjectName, 105 | getDefaultAnswers, 106 | getDefaultAnswer, 107 | getGitRepositoryName, 108 | isProjectAvailableOnNpm, 109 | cleanSocialNetworkUsername 110 | }; 111 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "md-generator", 3 | "version": "0.6.1", 4 | "description": "NPM Package which bootstraps Development by creating all required .md files to meet community standards.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "nyc cross-env TS_NODE_FILES=true mocha --exit --require ts-node/register --colors src/tests.spec.ts", 8 | "coverage": "cat ./coverage/lcov.info | ./node_modules/.bin/coveralls", 9 | "compile": "yarn clear && yarn build", 10 | "copy-files": "babel ./src/templates/files -d dist/src/templates/files --ignore --copy-files", 11 | "build": "tsc -b && yarn copy-files", 12 | "clear": "rm -rf dist", 13 | "pretty": "./node_modules/.bin/prettier --single-quote --write \"src/**/*.{js,jsx,ts,tsx,json,css,scss,md}\"", 14 | "lint": "eslint src/**/*.ts --fix", 15 | "release": "standard-version", 16 | "contributors:init": "all-contributors init", 17 | "contributors:add": "all-contributors add", 18 | "contributors:generate": "all-contributors generate" 19 | }, 20 | "bin": { 21 | "md-generator": "./dist/src/index.js" 22 | }, 23 | "files": [ 24 | "dist", 25 | "LICENSE", 26 | "README.md" 27 | ], 28 | "repository": { 29 | "type": "git", 30 | "url": "git+https://github.com/Oluwasegun-AA/md-generator.git" 31 | }, 32 | "keywords": [ 33 | "files", 34 | ".md", 35 | "files template", 36 | "cli", 37 | "generator", 38 | "md-generator", 39 | "template", 40 | "automate" 41 | ], 42 | "author": "Adépòjù Olúwásêgun (https://oluwasegun-aa.github.io/)", 43 | "license": "MIT", 44 | "bugs": { 45 | "url": "https://github.com/Oluwasegun-AA/md-generator/issues" 46 | }, 47 | "homepage": "https://github.com/Oluwasegun-AA/md-generator#readme", 48 | "husky": { 49 | "hooks": { 50 | "pre-push": "lint-staged" 51 | } 52 | }, 53 | "lint-staged": { 54 | "src/**/*.{js,jsx,ts,tsx,json,css,scss,md}": [ 55 | "prettier --write", 56 | "git add" 57 | ] 58 | }, 59 | "dependencies": { 60 | "boxen": "4.1.0", 61 | "chalk": "2.4.2", 62 | "commander": "3.0.1", 63 | "ejs": "^3.1.8", 64 | "git-repo-name": "1.0.1", 65 | "inquirer": "6.4.1", 66 | "load-json-file": "6.2.0", 67 | "ora": "4.0.1", 68 | "pad": "3.2.0" 69 | }, 70 | "devDependencies": { 71 | "@babel/cli": "^7.19.3", 72 | "@babel/core": "7.4.5", 73 | "@babel/plugin-transform-runtime": "^7.19.6", 74 | "@babel/preset-env": "7.4.5", 75 | "@babel/runtime": "7.4.5", 76 | "@types/chai": "4.2.6", 77 | "@types/chai-spies": "1.0.1", 78 | "@types/ejs": "2.7.0", 79 | "@types/git-repo-name": "1.0.0", 80 | "@types/inquirer": "6.5.0", 81 | "@types/lodash": "4.14.149", 82 | "@types/mocha": "5.2.7", 83 | "@types/node": "12.12.14", 84 | "babel-node": "^0.0.1-security", 85 | "chai": "4.2.0", 86 | "chai-spies": "1.0.0", 87 | "coveralls": "3.0.9", 88 | "cross-env": "6.0.3", 89 | "eslint": "5.3.0", 90 | "eslint-config-airbnb": "17.1.0", 91 | "eslint-config-airbnb-base": "13.1.0", 92 | "eslint-plugin-import": "2.18.0", 93 | "husky": "3.0.4", 94 | "lint-staged": "9.2.3", 95 | "lodash": "4.17.21", 96 | "mocha": "10.1.0", 97 | "nyc": "14.1.1", 98 | "prettier": "1.18.2", 99 | "ts-node": "8.5.4", 100 | "tslint-config-airbnb": "5.11.2", 101 | "typescript": "3.7.2" 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at oluwasegunadepoju@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq -------------------------------------------------------------------------------- /src/templates/files/required/template-CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at <%= authorEmail %>. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /src/templates/files/required/template-noHtml-README.md: -------------------------------------------------------------------------------- 1 | # Welcome to <%= projectName %> 👋 2 | <% if (isProjectOnNpm) { -%> 3 | [![Version](https://img.shields.io/npm/v/<%= projectName %>.svg)](https://www.npmjs.com/package/<%= projectName %>) 4 | <% } -%> 5 | <% if (projectVersion && !isProjectOnNpm) { -%> 6 | ![Version](https://img.shields.io/badge/version-<%= projectVersion %>-blue.svg?cacheSeconds=2592000) 7 | <% } -%> 8 | <% if (projectPrerequisites) { -%> 9 | <% projectPrerequisites.map(({ name, value }) => { -%> 10 | ![Prerequisite](https://img.shields.io/badge/<%= name %>-<%= encodeURIComponent(value) %>-blue.svg) 11 | <% }) -%> 12 | <% } -%> 13 | <% if (projectDocumentationUrl) { -%> 14 | [![Documentation](https://img.shields.io/badge/documentation-yes-brightgreen.svg)](<%= projectDocumentationUrl %>) 15 | <% } -%> 16 | <% if (isGithubRepos) { -%> 17 | [![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg)](<%= repositoryUrl %>/graphs/commit-activity) 18 | <% } -%> 19 | <% if (licenseName.name && licenseUrl) { -%> 20 | [![License: <%= licenseName.name %>](https://img.shields.io/badge/License-<%= licenseName %>-yellow.svg)](<%= licenseUrl %>) 21 | <% } -%> 22 | <% if (authorTwitterUsername) { -%> 23 | [![Twitter: <%= authorTwitterUsername %>](https://img.shields.io/twitter/follow/<%= authorTwitterUsername %>.svg?style=social)](https://twitter.com/<%= authorTwitterUsername %>) 24 | <% } -%> 25 | <% if (projectDescription) { -%> 26 | 27 | ## Table of Contents 28 | * [Project Overview](#Project-Overview) 29 | * [Prerequisites](#Prerequisites) 30 | * [Features](#Features) 31 | * [Demo](#demo) 32 | * [Usage](#Usage) 33 | * [Installation](#Installation) 34 | * [Known Issues](#Known-issues) 35 | * [Contributing](#contributing) 36 | * [support](#support) 37 | * [License](#License) 38 | 39 | > <%= projectDescription %> 40 | <% } -%> 41 | <% if (projectHomepage) { -%> 42 | 43 | ## Project-Overview 44 | 45 | ### 🏠 [Homepage](<%= projectHomepage %>) 46 | <% } -%> 47 | <% if (projectPrerequisites && projectPrerequisites.length) { -%> 48 | 49 | ## Prerequisites 50 | 51 | <% projectPrerequisites.map(({ name, value }) => { -%> 52 | - <%= name %> <%= value %> 53 | <% }) -%> 54 | <% } -%> 55 | <% if (installCommand) { -%> 56 | 57 | ## Install 58 | 59 | ```sh 60 | <%= installCommand %> 61 | ``` 62 | <% } -%> 63 | <% if (usage) { -%> 64 | 65 | ## Usage 66 | 67 | ```sh 68 | <%= usage %> 69 | ``` 70 | <% } -%> 71 | <% if (testCommand) { -%> 72 | 73 | ## Run tests 74 | 75 | ```sh 76 | <%= testCommand %> 77 | ``` 78 | <% } -%> 79 | <% if (authorName || authorTwitterUsername || authorGithubUsername) { -%> 80 | 81 | ## Known Issues 82 | No known [issues](<%= contributingUrl %>) at the moment. 83 | 84 | ## Author 85 | <% if (authorName) { %> 86 | 👤 **<%= authorName %>** 87 | <% } %> 88 | <% if (authorTwitterUsername) { -%> 89 | * Twitter: [@<%= authorTwitterUsername %>](https://twitter.com/<%= authorTwitterUsername %>) 90 | <% } -%> 91 | <% if (authorGithubUsername) { -%> 92 | * Github: [@<%= authorGithubUsername %>](https://github.com/<%= authorGithubUsername %>) 93 | <% } -%> 94 | <% } -%> 95 | <% if (contributingUrl) { -%> 96 | 97 | ## 🤝 Contributing 98 | 99 | Contributions, issues and feature requests are welcome! 100 | 101 | Feel free to check [issues page](<%= contributingUrl %>). 102 | <% } -%> 103 | 104 | ## Show your support 105 | 106 | Give a ⭐️ if this project helped you! 107 | <% if (authorPatreonUsername) { -%> 108 | 109 | [![support us](https://img.shields.io/badge/become-a patreon%20us-orange.svg?cacheSeconds=2592000)](https://www.patreon.com/<%= authorPatreonUsername %>) 110 | <% } -%> 111 | 112 | <% if (licenseName && licenseUrl) { -%> 113 | 114 | ## 📝 License 115 | 116 | <% if (authorName && authorGithubUsername) { -%> 117 | Copyright © <%= currentYear %> [<%= authorName %>](https://github.com/<%= authorGithubUsername %>). 118 | 119 | <% } -%> 120 | This project is [<%= licenseName.name %>](<%= licenseUrl %>) licensed. 121 | <% } -%> 122 | 123 | *** 124 | <%- include('../footer.md'); -%> 125 | -------------------------------------------------------------------------------- /src/templates/files/required/template-html-README.md: -------------------------------------------------------------------------------- 1 |

Welcome to <%= projectName %> 👋

2 |

3 | <% if (isProjectOnNpm) { -%> 4 | 5 | Version 6 | 7 | <% } -%> 8 | <% if (projectVersion && !isProjectOnNpm) { -%> 9 | Version 10 | <% } -%> 11 | <% if (projectPrerequisites) { -%> 12 | <% projectPrerequisites.map(({ name, value }) => { -%> 13 | 14 | <% }) -%> 15 | <% } -%> 16 | <% if (projectDocumentationUrl) { -%> 17 | 18 | Documentation 19 | 20 | <% } -%> 21 | <% if (isGithubRepos) { -%> 22 | 23 | Maintenance 24 | 25 | <% } -%> 26 | <% if (licenseName.name && licenseUrl) { -%> 27 | 28 | License: <%= licenseName.name %> 29 | 30 | <% } -%> 31 | <% if (authorTwitterUsername) { -%> 32 | 33 | Twitter: <%= authorTwitterUsername %> 34 | 35 | <% } -%> 36 |

37 | 38 | ## Table of Contents 39 | * [Project Overview](#Project-Overview) 40 | * [Prerequisites](#Prerequisites) 41 | * [Features](#Features) 42 | * [Demo](#demo) 43 | * [Usage](#Usage) 44 | * [Installation](#Installation) 45 | * [Known Issues](#Known-issues) 46 | * [Contributing](#contributing) 47 | * [support](#support) 48 | * [License](#License) 49 | 50 | 51 | ## Project-Overview 52 | 53 | <% if (projectDescription) { -%> 54 | 55 | > <%= projectDescription %> 56 | <% } -%> 57 | <% if (projectHomepage) { -%> 58 | 59 | ### 🏠 [Homepage](<%= projectHomepage %>) 60 | <% } -%> 61 | <% if (projectPrerequisites && projectPrerequisites.length) { -%> 62 | 63 | ## Prerequisites 64 | 65 | <% projectPrerequisites.map(({ name, value }) => { -%> 66 | - <%= name %> <%= value %> 67 | <% }) -%> 68 | <% } -%> 69 | <% if (installCommand) { -%> 70 | 71 | ## Installation 72 | 73 | ```sh 74 | <%= installCommand %> 75 | ``` 76 | <% } -%> 77 | <% if (usage) { -%> 78 | 79 | ## Features 80 | 81 | - [x] 82 | - [x] 83 | - [x] 84 | 85 | ## Usage 86 | 87 | ```sh 88 | <%= usage %> 89 | ``` 90 | <% } -%> 91 | <% if (testCommand) { -%> 92 | 93 | ## Run tests 94 | 95 | ```sh 96 | <%= testCommand %> 97 | ``` 98 | <% } -%> 99 | <% if (authorName || authorTwitterUsername || authorGithubUsername) { -%> 100 | 101 | ## Known Issues 102 | No known [issues](<%= contributingUrl %>) at the moment. 103 | 104 | ## Author 105 | <% if (authorName) { %> 106 | 👤 **<%= authorName %>** 107 | <% } %> 108 | <% if (authorTwitterUsername) { -%> 109 | * Twitter: [@<%= authorTwitterUsername %>](https://twitter.com/<%= authorTwitterUsername %>) 110 | <% } -%> 111 | <% if (authorGithubUsername) { -%> 112 | * Github: [@<%= authorGithubUsername %>](https://github.com/<%= authorGithubUsername %>) 113 | <% } -%> 114 | <% } -%> 115 | <% if (contributingUrl) { -%> 116 | 117 | ## 🤝 Contributing 118 | 119 | Contributions, issues and feature requests are welcome!
Feel free to check [issues page](<%= contributingUrl %>). 120 | <% } -%> 121 | 122 | ## support 123 | 124 | Give a ⭐️ if this project helped you! 125 | <% if (authorPatreonUsername) { -%> 126 | 127 | 128 | 129 | 130 | <% } -%> 131 | <% if (licenseName.name && licenseUrl) { -%> 132 | 133 | ## 📝 License 134 | 135 | <% if (authorName && authorGithubUsername) { -%> 136 | Copyright © <%= currentYear %> [<%= authorName %>](https://github.com/<%= authorGithubUsername %>).
137 | <% } -%> 138 | This project is [<%= licenseName.name %>](<%= licenseUrl %>) licensed. 139 | <% } -%> 140 | 141 | *** 142 | <%- include('../footer.md'); -%> 143 | 144 | -------------------------------------------------------------------------------- /src/core/actions/removeActions/index.ts: -------------------------------------------------------------------------------- 1 | import inquirer from 'inquirer'; 2 | import fs from 'fs'; 3 | import { 4 | allExistingFiles, 5 | removeDotMdAttribute, 6 | allFiles, 7 | requiredFiles, 8 | optionalFiles, 9 | queryFilesExistence, 10 | } from '../actionsUtils'; 11 | import { 12 | removeFiles, 13 | validateRemove, 14 | removeRequired, 15 | removeOptional, 16 | } from '../../questions/setupQuestions/index'; 17 | import { 18 | log, 19 | useBox, 20 | castElementsToFormatedString, 21 | } from '../../../common/index'; 22 | import { IAllFiles, ICurrentFile } from '../../../../types/typeDeclarations.interface'; 23 | 24 | /** 25 | * @description 26 | * Delete file from the codebase 27 | * 28 | * @param filesArray name of files to be deleted (array of strings) 29 | * @param allFiles All files Available files 30 | */ 31 | export const deleteFromCodebase = (filesArray: string[], allFiles: any) => { 32 | filesArray.forEach((file: string) => { 33 | const { path }: ICurrentFile = allFiles[file]; 34 | fs.unlink(path, (err: any) => { 35 | if (err) log(`Could not delete ${file}`); 36 | }); 37 | }); 38 | useBox('File(s) removed successfully \nThanks for using md-generator'); 39 | }; 40 | 41 | /** 42 | * @description 43 | * Handles the delete process, validating if no file is supplied 44 | * 45 | * @param validFilesArray name of Valid files to be deleted (array of strings) 46 | * @param allFiles All files Available files 47 | */ 48 | export const deleteFiles = (validFilesArray: string[], allFiles: IAllFiles): void => { 49 | if (validFilesArray.length === 0) { 50 | return log('Error: No file selected, Please select a file\n'); 51 | } 52 | inquirer 53 | .prompt(validateRemove(castElementsToFormatedString(validFilesArray))) 54 | .then((answer: any) => { 55 | const { removeFiles }: {removeFiles: boolean} = answer; 56 | if (removeFiles) { 57 | deleteFromCodebase(validFilesArray, allFiles); 58 | } 59 | }); 60 | }; 61 | 62 | /** 63 | * @description 64 | * Handles the delete process, validating if the filename is valid and if it exists 65 | * 66 | * @param selectedFiles All files parsed to be deleted 67 | * @param filesInfo All available files 68 | * @param mode Question mode 69 | */ 70 | const processRemoval = (selectedFiles: string[], filesInfo: IAllFiles, mode: any): void => { 71 | if (selectedFiles.length === 0) { 72 | return log('Error: .md file(s) not found in the codebase\n'); 73 | } 74 | inquirer.prompt(mode(selectedFiles)).then((answer: any) => { 75 | const selectedFiles = removeDotMdAttribute(answer.removeFiles); 76 | deleteFiles(selectedFiles, filesInfo); 77 | }); 78 | }; 79 | 80 | /** 81 | * @description handles item removal when command is passed without an option 82 | */ 83 | const removeNonSpecific = (): void => { 84 | processRemoval(allExistingFiles(), allFiles, removeFiles); 85 | }; 86 | 87 | /** 88 | * @description 89 | * Handles removal when command is passed with option -F or --file 90 | * @param values Array of file names supplied 91 | */ 92 | export const removeSpecificFiles = (values: ICurrentFile[]): void => { 93 | const { foundFiles, filesNotFound } = queryFilesExistence(values); 94 | if (filesNotFound.length > 0) { 95 | const filesList: string = castElementsToFormatedString(filesNotFound); 96 | log('The following file(s) were not found :\n', `${filesList} \n`); 97 | } 98 | if (foundFiles.length > 0) { 99 | deleteFiles(foundFiles, allFiles); 100 | } 101 | }; 102 | 103 | /** 104 | * @description 105 | * handles files removals when the option -R or --required is used 106 | */ 107 | const removeRequiredFiles = (): void => { 108 | processRemoval( 109 | allExistingFiles(requiredFiles), 110 | requiredFiles, 111 | removeRequired 112 | ); 113 | }; 114 | 115 | /** 116 | * @description 117 | * handles files removals when the option -O or --optional is used 118 | */ 119 | const removeOptionalFiles = (): void => { 120 | processRemoval( 121 | allExistingFiles(optionalFiles), 122 | optionalFiles, 123 | removeOptional 124 | ); 125 | }; 126 | 127 | /** 128 | * @description 129 | * removes file in the codebase based on the arguments passed 130 | * 131 | * @param values arguments i.e command, payload and command options 132 | */ 133 | const removeHandler = (values: any) => { 134 | const { 135 | file, 136 | required, 137 | optional, 138 | resp 139 | } = values; 140 | if (required) return removeRequiredFiles(); 141 | if (optional) return removeOptionalFiles(); 142 | if (file) return removeSpecificFiles(resp); 143 | removeNonSpecific(); 144 | }; 145 | 146 | export default removeHandler; 147 | -------------------------------------------------------------------------------- /src/projectEnv/projectInfo.ts: -------------------------------------------------------------------------------- 1 | import { isNil, get, has } from 'lodash'; 2 | import { execSync } from 'child_process'; 3 | import { getPackageJson, getProjectName } from './utils'; 4 | import { IProjectInfos } from '../../types/typeDeclarations.interface'; 5 | 6 | const GITHUB_URL = 'https://github.com/'; 7 | 8 | /** 9 | * @description Clean repository url by removing '.git' and 'git+' 10 | * 11 | * @param repoUrl repository url 12 | */ 13 | const cleanRepoUrl = (repoUrl: any): string => 14 | repoUrl 15 | .replace('\n', '') 16 | .replace('git+', '') 17 | .replace('.git', ''); 18 | 19 | /** 20 | * @description Get repository url from package.json 21 | */ 22 | const getRepoUrlFromPackageJson = async (packageJson: any): Promise => { 23 | const repoUrl = get(packageJson, 'repository.url', undefined); 24 | return isNil(repoUrl) ? undefined : cleanRepoUrl(repoUrl); 25 | }; 26 | 27 | /** 28 | * @description Get repository url from git 29 | */ 30 | export const getRepoUrlFromGit = (): any => { 31 | try { 32 | const stdout = execSync('git config --get remote.origin.url'); 33 | return cleanRepoUrl(stdout); 34 | } catch (err) { 35 | return undefined; 36 | } 37 | }; 38 | 39 | /** 40 | * @description Get repository url from package.json or git 41 | * 42 | * @param packageJson package.json file 43 | */ 44 | const getRepoUrl = async (packageJson?: any): Promise => 45 | (await getRepoUrlFromPackageJson(packageJson)) || getRepoUrlFromGit(); 46 | 47 | /** 48 | * @description Get repository issues url from package.json or git 49 | * 50 | * @param packageJson package.json file 51 | */ 52 | export const getRepoIssuesUrl = async (packageJson: any): Promise => { 53 | let repoIssuesUrl = get(packageJson, 'bugs.url', undefined); 54 | 55 | if (isNil(repoIssuesUrl)) { 56 | const repoUrl = await getRepoUrl(); 57 | 58 | if (!isNil(repoUrl)) { 59 | repoIssuesUrl = `${repoUrl}/issues`; 60 | } 61 | } 62 | return repoIssuesUrl; 63 | }; 64 | 65 | /** 66 | * @description Check if repository is a Github repository 67 | * 68 | * @param repositoryUrl repository URL 69 | */ 70 | const isGithubRepository = (repositoryUrl: string): boolean => 71 | !isNil(repositoryUrl) && repositoryUrl.includes(GITHUB_URL); 72 | 73 | /** 74 | * @description Get github username from repository url 75 | * 76 | * @param repositoryUrl repository URL 77 | */ 78 | const getGithubUsernameFromRepositoryUrl = (repositoryUrl: string): string => 79 | repositoryUrl.replace(GITHUB_URL, '').split('/')[0]; 80 | 81 | /** 82 | * @description Get license url from github repository url 83 | * 84 | * @param repositoryUrl repository URL 85 | */ 86 | const getLicenseUrlFromGithubRepositoryUrl = (repositoryUrl: string): string => 87 | `${repositoryUrl}/blob/master/LICENSE`; 88 | 89 | const getReadmeUrlFromGithubRepositoryUrl = (repositoryUrl: string): string => 90 | `${repositoryUrl}#readme`; 91 | 92 | /** 93 | * @description Get project author name from package.json 94 | * 95 | * @param packageJson package.json file 96 | * @returns authorName 97 | */ 98 | export const getAuthorName = (packageJson: any): any => { 99 | if (has(packageJson, 'author.name')) { 100 | return get(packageJson, 'author.name', undefined); 101 | } 102 | if (has(packageJson, 'author') && typeof packageJson.author === 'string') { 103 | return get(packageJson, 'author', undefined); 104 | } 105 | return undefined; 106 | }; 107 | 108 | /** 109 | * @description Get project information from git and package.json 110 | */ 111 | const getProjectInfos = async (): Promise => { 112 | const packageJson = await getPackageJson(); 113 | const name = getProjectName(packageJson); 114 | const description = get(packageJson, 'description', undefined); 115 | const engines = get(packageJson, 'engines', undefined); 116 | const author = getAuthorName(packageJson); 117 | const version = get(packageJson, 'version', undefined); 118 | const licenseName = { name: get(packageJson, 'license', undefined) }; 119 | const homepage = get(packageJson, 'homepage', undefined); 120 | const usage = has(packageJson, 'scripts.start') ? 'npm run start' : undefined; 121 | const testCommand = has(packageJson, 'scripts.test') 122 | ? 'npm run test' 123 | : undefined; 124 | const repositoryUrl = await getRepoUrl(packageJson); 125 | const contributingUrl = await getRepoIssuesUrl(packageJson); 126 | const isGithubRepo = isGithubRepository(repositoryUrl); 127 | const documentationUrl = isGithubRepo 128 | ? getReadmeUrlFromGithubRepositoryUrl(repositoryUrl) 129 | : undefined; 130 | const githubUsername = isGithubRepo 131 | ? getGithubUsernameFromRepositoryUrl(repositoryUrl) 132 | : undefined; 133 | const licenseUrl = isGithubRepo 134 | ? getLicenseUrlFromGithubRepositoryUrl(repositoryUrl) 135 | : undefined; 136 | 137 | return { 138 | name, 139 | description, 140 | version, 141 | author, 142 | homepage, 143 | repositoryUrl, 144 | contributingUrl, 145 | githubUsername, 146 | engines, 147 | licenseName, 148 | licenseUrl, 149 | documentationUrl, 150 | isGithubRepo, 151 | usage, 152 | testCommand, 153 | }; 154 | }; 155 | 156 | export default getProjectInfos; 157 | -------------------------------------------------------------------------------- /types/typeDeclarations.interface.ts: -------------------------------------------------------------------------------- 1 | export interface IBOX_CONFIG { 2 | padding: number; 3 | margin: { top: number; bottom: number }; 4 | borderColor: string; 5 | align: string; 6 | borderStyle: string; 7 | }; 8 | 9 | export interface ICurrentFile { 10 | name: string; 11 | exists: boolean; 12 | path: string; 13 | templatePath?: string; 14 | }; 15 | 16 | export interface IProjectInfos { 17 | name: string | any; 18 | description: string | any; 19 | version: string | any; 20 | author: string | any; 21 | homepage: string | any; 22 | repositoryUrl?: string | any; 23 | contributingUrl: string | any; 24 | githubUsername: string | any; 25 | engines: string | any; 26 | licenseName: { 27 | name: string | any; 28 | }; 29 | licenseUrl: string | any; 30 | documentationUrl: string | any; 31 | isGithubRepo: boolean | any; 32 | usage: string | any; 33 | testCommand: string | any; 34 | isGithubRepos?: boolean | any; 35 | projectName?: string | any; 36 | projectPrerequisites?: any | any; 37 | isProjectOnNpm?: any | any; 38 | }; 39 | 40 | export interface IProjectInfosWithMissingFields { 41 | name?: string | any; 42 | description?: string | any; 43 | version?: string | any; 44 | author?: string | any; 45 | homepage?: string | any; 46 | repositoryUrl?: string | any; 47 | contributingUrl?: string | any; 48 | githubUsername?: string | any; 49 | engines?: string | any; 50 | licenseName?: { 51 | name?: string | any; 52 | } | any; 53 | licenseUrl?: string | any; 54 | documentationUrl?: string | any; 55 | isGithubRepo?: boolean | any; 56 | usage?: string | any; 57 | testCommand?: string | any; 58 | isGithubRepos?: boolean | any; 59 | projectName?: string | any; 60 | projectPrerequisites?: any | any; 61 | isProjectOnNpm?: any | any; 62 | }; 63 | 64 | export interface IRequiredFiles { 65 | README?: { 66 | name: string; 67 | exists: boolean; 68 | path: string; 69 | }; 70 | LICENSE?: { 71 | name: string; 72 | exists: boolean; 73 | path: string; 74 | }; 75 | CODE_OF_CONDUCT?: { 76 | name: string; 77 | exists: boolean; 78 | path: string; 79 | templatePath: string; 80 | }; 81 | PULL_REQUEST_TEMPLATE?: { 82 | name: string; 83 | exists: boolean; 84 | path: string; 85 | templatePath: string 86 | }; 87 | BUG_REPORT?: { 88 | name: string; 89 | exists: boolean; 90 | path: string; 91 | templatePath: string; 92 | }; 93 | FEATURE_REQUEST?: { 94 | name: string; 95 | exists: boolean; 96 | path: string; 97 | templatePath: string 98 | }; 99 | }; 100 | 101 | // optional files Objects and their details 102 | export interface IOptionalFiles { 103 | CHANGELOG?: { 104 | name: string; 105 | exists: boolean; 106 | path: string; 107 | templatePath: string; 108 | }; 109 | SUPPORT?: { 110 | name: string; 111 | exists: boolean; 112 | path: string; 113 | templatePath: string; 114 | }; 115 | CONTRIBUTORS?: { 116 | name: string; 117 | exists: boolean; 118 | path: string; 119 | templatePath: string; 120 | }; 121 | AUTHORS?: { 122 | name: string; 123 | exists: boolean; 124 | path: string; 125 | templatePath: string; 126 | }; 127 | ACKNOWLEDGMENTS?: { 128 | name: string; 129 | exists: boolean; 130 | path: string; 131 | templatePath: string; 132 | }; 133 | CODEOWNERS?: { 134 | name: string; 135 | exists: boolean; 136 | path: string; 137 | templatePath: string; 138 | }; 139 | }; 140 | 141 | export interface IAllFiles extends IRequiredFiles, IOptionalFiles { 142 | }; 143 | 144 | export interface ISortedFiles { 145 | validFileNames: string[]; 146 | inValidFileNames: string[]; 147 | foundFiles: string[]; 148 | filesNotFound: string[]; 149 | }; 150 | 151 | export interface IArguments { 152 | optional?: boolean; 153 | required?: boolean; 154 | all?: string[]; 155 | file?: string[]; 156 | empty?: boolean; 157 | resp?: ICurrentFile[]; 158 | isEmpty?: boolean; 159 | }; 160 | 161 | export interface IQuestionResponse { 162 | type?: string; 163 | name?: string; 164 | message?: string; 165 | default?: boolean | string; 166 | filter?: any; 167 | choices?: boolean | string[] | any; 168 | when?: any; 169 | }; 170 | 171 | export interface IQuestions { 172 | chooseTemplate?: IQuestionResponse | any; 173 | askProjectName?: IQuestionResponse | any; 174 | askProjectVersion?: IQuestionResponse | any; 175 | askProjectHomepage?: IQuestionResponse | any; 176 | askProjectDescription?: IQuestionResponse | any; 177 | askLicenseName?: IQuestionResponse | any; 178 | askLicenseUrl?: IQuestionResponse | any; 179 | askInstallCommand?: IQuestionResponse | any; 180 | askTestCommand?: IQuestionResponse | any; 181 | askProjectDocumentationUrl?: IQuestionResponse | any; 182 | askAuthorName?: IQuestionResponse | any; 183 | askAuthorGithub?: IQuestionResponse | any; 184 | askAuthorTwitter?: IQuestionResponse | any; 185 | askAuthorPatreon?: IQuestionResponse | any; 186 | askProjectPrerequisites?: IQuestionResponse | any; 187 | askContributing?: IQuestionResponse | any; 188 | askUsage?: IQuestionResponse | any; 189 | askAuthorEmail?: IQuestionResponse | any; 190 | }; 191 | 192 | export interface IInputValue { 193 | file: string[]; 194 | required: string[]; 195 | optional: string[]; 196 | isEmpty?: string[]; 197 | resp: ICurrentFile[]; 198 | all?: string[]; 199 | }; 200 | 201 | export interface IFilesCategories { 202 | required: boolean; 203 | optional: boolean; 204 | } 205 | -------------------------------------------------------------------------------- /src/common/alerts.ts: -------------------------------------------------------------------------------- 1 | import boxen from 'boxen'; 2 | import pad from 'pad'; 3 | import chalk from 'chalk'; 4 | import ora from 'ora'; 5 | import { IBOX_CONFIG } from '../../types/typeDeclarations.interface'; 6 | 7 | // get chalk colors for terminal 8 | const { 9 | red, 10 | gray, 11 | green, 12 | cyan 13 | }: any = chalk; 14 | const whiteUnderline: any = chalk.underline.rgb(174, 174, 174); 15 | const dimWhite: any = chalk.rgb(174, 174, 174); 16 | 17 | /** 18 | * @description 19 | * Logs to the terminal 20 | * 21 | * @param data1 data to be logged 22 | * @param data2 data to be logged 23 | * @param data3 data to be logged 24 | */ 25 | const log = (data1: any, data2: any = '', data3: any = ''): void => { 26 | process.stdout.write(`\n${data1}`); 27 | process.stdout.write(`${data2}`); 28 | process.stdout.write(`${data3}`); 29 | }; 30 | 31 | // configuration for boxen 32 | const BOX_CONFIG: IBOX_CONFIG| any = { 33 | padding: 1, 34 | margin: { top: 2, bottom: 2 }, 35 | borderColor: 'cyan', 36 | align: 'center', 37 | borderStyle: 'double', 38 | }; 39 | 40 | /** 41 | * @description 42 | * Encapsulates Items logged in the console within a styled box 43 | * @param text text to be printed on the terminal 44 | */ 45 | const useBox: any = (text: string): void => { 46 | log(boxen(text, BOX_CONFIG)); 47 | }; 48 | 49 | /** 50 | * @description Prints Success message after file creation 51 | */ 52 | const showEndMessage: any = (): void => 53 | useBox('File(s) Created Successfully\nThank you for using md-generator'); 54 | 55 | /** 56 | * @description 57 | * Prints the custom help to the terminal 58 | */ 59 | const customHelp: any = (): void => { 60 | log('\nCommand-Options :'); 61 | log('Usage: md-generator [commands] [command-options]\n'); 62 | log(pad('-A, --all', 26), 'Operate on all required/optional .md files'); 63 | log(pad('-F, --file', 26), 'Operate on specific .md files'); 64 | log(pad('-E, --empty', 26), 'make added files empty'); 65 | log(pad('-R --required', 26), 'Operate on required files'); 66 | log(pad('-O --optional', 26), 'Operate on optional files\n'); 67 | }; 68 | 69 | /** 70 | * @description 71 | * prints to the wrong Command Alert to the terminal 72 | * 73 | * @param command the command parsed 74 | */ 75 | const wrongCommandAlert: any = (command: string): void => 76 | log( 77 | `Command "${command}" Does not Exist,\nPlease use --help to get the available commands\n` 78 | ); 79 | 80 | /** 81 | * @description 82 | * prints to the no Command Alert to the terminal 83 | */ 84 | const noCommandAlert: any = (): void => { 85 | log( 86 | 'Error: No Command Supplied. Please use --help to view the available Commands and Options\n' 87 | ); 88 | }; 89 | 90 | /** 91 | * @description 92 | * Shows spinner on the terminal 93 | * 94 | * @param text text that accompany the spinner 95 | */ 96 | const spinner: any = (text: string): any => ora(text).start(); 97 | 98 | /** 99 | * @description 100 | * Takes array of strings and returns a formatted list with the array elements 101 | * 102 | * @param filesArray Array of Strings 103 | */ 104 | const castElementsToFormatedString: any = (filesArray: string[]): string => { 105 | let files:string = ''; 106 | filesArray.forEach( 107 | (file: string):string => (files += `${cyan(pad('-', 2))} ${dimWhite(`${file}`)}\n`) 108 | ); 109 | return files; 110 | }; 111 | 112 | /** 113 | * @description 114 | * logs link to github's community page if username and project name is available 115 | * 116 | * @param authorGithubUsername GitHub username 117 | * @param projectName project name 118 | */ 119 | const checkCommunityStandardMet: any = (authorGithubUsername: string, projectName: string): void => { 120 | if (authorGithubUsername && projectName) { 121 | log( 122 | `You can check community standards met via https://github.com/${authorGithubUsername}/${projectName}/community \n` 123 | ); 124 | } else { 125 | log( 126 | 'You can check community standards met via https://github.com/""/""/community \n' 127 | ); 128 | } 129 | }; 130 | 131 | /** 132 | * alert showing no file names supplied to the --files option 133 | */ 134 | const fileNotDetectedAlert: any = (): void => { 135 | log( 136 | 'Error: File names not detected, please supply file names i.e --file "README.md CONTRIBUTING.md" \n' 137 | ); 138 | }; 139 | 140 | /** 141 | * Alert showing list of unsupported file names 142 | * @param inValidFileNamesArray invalid file name supplied 143 | */ 144 | const unrecognizedFileAlert: any = (inValidFileNamesArray: string[]): void => { 145 | log( 146 | `The following file name(s) is/are not recognized as one of the required/optional .md files\n${castElementsToFormatedString( 147 | inValidFileNamesArray 148 | )}` 149 | ); 150 | }; 151 | 152 | interface consolePayload { 153 | errorText: string; 154 | override?: boolean; 155 | } 156 | /** 157 | * Alert directing users to the --help command 158 | */ 159 | const useHelpAlert: any = ({ errorText, override }: consolePayload): void => { 160 | const genericText = override ? '\n' : ' please use --help to check all supported md Files\n'; 161 | log(`${errorText} ${genericText}`); 162 | }; 163 | 164 | export { 165 | log, 166 | useBox, 167 | green, 168 | customHelp, 169 | whiteUnderline, 170 | red, 171 | gray, 172 | dimWhite, 173 | cyan, 174 | wrongCommandAlert, 175 | noCommandAlert, 176 | spinner, 177 | useHelpAlert, 178 | showEndMessage, 179 | fileNotDetectedAlert, 180 | unrecognizedFileAlert, 181 | checkCommunityStandardMet, 182 | castElementsToFormatedString, 183 | }; 184 | -------------------------------------------------------------------------------- /src/core/actions/actionsUtils.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import { fileNotDetectedAlert } from '../../common/index'; 4 | import { IAllFiles, IRequiredFiles, IOptionalFiles, ISortedFiles } from '../../../types/typeDeclarations.interface'; 5 | 6 | /** 7 | * @description 8 | * gets the absolute path a file 9 | * 10 | * @param relativePath relative path to the file 11 | */ 12 | const getResolvedPath = (relativePath: string) => path.resolve(__dirname, relativePath); 13 | 14 | /** 15 | * @description 16 | * Returns the activated command line option 17 | * 18 | * @param args all arguments available 19 | * @param option option activated 20 | */ 21 | const getArgs = (args: [], option:string) => args.find((item: string) => item === option); 22 | 23 | /** 24 | * @description 25 | * returns an Object showing which option has been activated and the payload supplied 26 | * 27 | * @param args all arguments available 28 | * @param resp option activated 29 | */ 30 | const getValues = (args: [], resp: any) => ({ 31 | file: getArgs(args, 'file'), 32 | required: getArgs(args, 'required'), 33 | optional: getArgs(args, 'optional'), 34 | all: getArgs(args, 'all'), 35 | isEmpty: getArgs(args, 'empty'), 36 | resp, 37 | }); 38 | 39 | /** 40 | * @description 41 | * Checks if file exists in the code base 42 | * 43 | * @param relativePath relative path to file 44 | */ 45 | const checkFileExist = (relativePath: string) => !!fs.existsSync(relativePath); 46 | 47 | // required files Objects and their details 48 | const requiredFiles: IRequiredFiles = { 49 | README: { 50 | name: 'README.md', 51 | exists: checkFileExist('./README.md'), 52 | path: './README.md', 53 | }, 54 | LICENSE: { 55 | name: 'LICENSE', 56 | exists: checkFileExist('./LICENSE'), 57 | path: './LICENSE', 58 | }, 59 | CODE_OF_CONDUCT: { 60 | name: 'CODE_OF_CONDUCT.md', 61 | exists: checkFileExist('./CODE_OF_CONDUCT.md'), 62 | path: './CODE_OF_CONDUCT.md', 63 | templatePath: getResolvedPath( 64 | '../../templates/files/required/template-CODE_OF_CONDUCT.md' 65 | ), 66 | }, 67 | PULL_REQUEST_TEMPLATE: { 68 | name: 'PULL_REQUEST_TEMPLATE.md', 69 | exists: checkFileExist('./.github/PULL_REQUEST_TEMPLATE.md'), 70 | path: './.github/PULL_REQUEST_TEMPLATE.md', 71 | templatePath: getResolvedPath( 72 | '../../templates/files/required/template-PULL_REQUEST_TEMPLATE.md' 73 | ), 74 | }, 75 | BUG_REPORT: { 76 | name: 'bug_report.md', 77 | exists: checkFileExist('./.github/ISSUE_TEMPLATE/bug_report.md'), 78 | path: './.github/ISSUE_TEMPLATE/bug_report.md', 79 | templatePath: getResolvedPath( 80 | '../../templates/files/required/template-BUG_REPORT.md' 81 | ), 82 | }, 83 | FEATURE_REQUEST: { 84 | name: 'feature_request.md', 85 | exists: checkFileExist('./.github/ISSUE_TEMPLATE/feature_request.md'), 86 | path: './.github/ISSUE_TEMPLATE/feature_request.md', 87 | templatePath: getResolvedPath( 88 | '../../templates/files/required/template-FEATURE_REQUEST.md' 89 | ), 90 | }, 91 | }; 92 | 93 | // optional files Objects and their details 94 | const optionalFiles: IOptionalFiles = { 95 | CHANGELOG: { 96 | name: 'CHANGELOG.md', 97 | exists: checkFileExist('./CHANGELOG.md'), 98 | path: './CHANGELOG.md', 99 | templatePath: getResolvedPath( 100 | '../../templates/files/optional/template-CHANGELOG.md' 101 | ), 102 | }, 103 | SUPPORT: { 104 | name: 'SUPPORT.md', 105 | exists: checkFileExist('./SUPPORT.md'), 106 | path: './SUPPORT.md', 107 | templatePath: getResolvedPath( 108 | '../../templates/files/optional/template-SUPPORT.md' 109 | ), 110 | }, 111 | CONTRIBUTORS: { 112 | name: 'CONTRIBUTORS.md', 113 | exists: checkFileExist('./CONTRIBUTORS.md'), 114 | path: './CONTRIBUTORS.md', 115 | templatePath: getResolvedPath( 116 | '../../templates/files/optional/template-CONTRIBUTORS.md' 117 | ), 118 | }, 119 | AUTHORS: { 120 | name: 'AUTHORS.md', 121 | exists: checkFileExist('./AUTHORS.md'), 122 | path: './AUTHORS.md', 123 | templatePath: getResolvedPath( 124 | '../../templates/files/optional/template-AUTHORS.md' 125 | ), 126 | }, 127 | ACKNOWLEDGMENTS: { 128 | name: 'ACKNOWLEDGMENTS.md', 129 | exists: checkFileExist('./ACKNOWLEDGMENTS.md'), 130 | path: './ACKNOWLEDGMENTS.md', 131 | templatePath: getResolvedPath( 132 | '../../templates/files/optional/template-ACKNOWLEDGMENTS.md' 133 | ), 134 | }, 135 | CODEOWNERS: { 136 | name: 'CODEOWNERS.md', 137 | exists: checkFileExist('./CODEOWNERS.md'), 138 | path: './CODEOWNERS.md', 139 | templatePath: getResolvedPath( 140 | '../../templates/files/optional/template-CODEOWNERS.md' 141 | ), 142 | }, 143 | }; 144 | 145 | // all files Objects and their details 146 | const allFiles: IAllFiles = { ...requiredFiles, ...optionalFiles }; 147 | 148 | /** 149 | * @description 150 | * checks if file exists in the code base 151 | 152 | * @param data array of files names 153 | * @param allMdFiles Object containing all files available 154 | */ 155 | const checkFilesExist = (data: string[], allMdFiles: any): any => { 156 | const foundFiles: any[] = []; 157 | const filesNotFound: any[] = []; 158 | data.forEach((item: string) => { 159 | const newItem: string = item.split('.')[0].toUpperCase(); 160 | const file: any = Object.keys(allMdFiles).find( 161 | (key: string) => key === newItem && allMdFiles[newItem].exists 162 | ); 163 | if (file) { 164 | foundFiles.push(file); 165 | } else { 166 | filesNotFound.push(newItem); 167 | } 168 | }); 169 | return { foundFiles, filesNotFound }; 170 | }; 171 | 172 | /** 173 | * @description 174 | * remove the .md attribute of supplied in the terminal with the file name 175 | * 176 | * @param item Array of file names supplied 177 | */ 178 | const removeDotMdAttribute = (item: string[]) => { 179 | const newItem: any[] = []; 180 | item.forEach((key: string) => newItem.push(key.split('.')[0].toUpperCase())); 181 | return newItem; 182 | }; 183 | 184 | /** 185 | * @description 186 | * check if file name supplied is available in the npm module 187 | * 188 | * @param files Array of all file names supplied 189 | */ 190 | const allExistingFiles = (files: IAllFiles = allFiles) => 191 | Object.values(files).filter((item: any) => !!item.exists); 192 | 193 | const getArrayOfValues = (files: IAllFiles = allFiles) => Object.values(files); 194 | 195 | /** 196 | * @description 197 | * validate files names with respect to the files supported by the npm module 198 | * 199 | * @param values Array of all file names supplied 200 | */ 201 | const queryFilesExistence = (values: any): ISortedFiles => { 202 | const args = !values.parent ? values.join(' ') : values.parent.rawArgs[4]; 203 | if ((!args || args.length === 0) && values.parent) { 204 | fileNotDetectedAlert(); 205 | process.exit(1); 206 | } 207 | const data = args.split(' ').filter((item: string)=> item.length > 0 ); 208 | return checkFilesExist(data, allFiles); 209 | }; 210 | 211 | const getItemFromFileName = (fileName: string) => 212 | Object.values(allFiles).find((value: any) => value.name.toUpperCase().includes(fileName)); 213 | 214 | export { 215 | allFiles, 216 | getValues, 217 | requiredFiles, 218 | optionalFiles, 219 | checkFilesExist, 220 | getArrayOfValues, 221 | queryFilesExistence, 222 | allExistingFiles, 223 | removeDotMdAttribute, 224 | getItemFromFileName, 225 | }; 226 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

md-generator

2 |

3 | 4 | Version 5 | 6 | 7 | Coverage Status 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | Version 16 | 17 | 18 | 19 | Version 20 | 21 | 22 | 23 | Version 24 | 25 |

26 | 27 | CLI tool which bootstraps Development by creating "ALL" required .md files to meet "community standards". 28 | 29 | ## Table of Contents 30 | 31 | - [Project Overview](#Project-Overview) 32 | - [Features](#Features) 33 | - [Installation](#Installation) 34 | - [Supported Files](#Supported-Files) 35 | - [Usage](#Usage) 36 | - [Demo](#Demo) 37 | - [Known Issues](#Known-Issues) 38 | - [Contributing](#contributing) 39 | - [Contributors](#Contributors) 40 | - [License](#License) 41 | 42 | ## Project Overview 43 | 44 | md-generator was created to reduce the time spent creating .md files while trying to set up projects. Hence, bootstrapping development with `ALL` desired md files in line with proven community standards. 45 | 46 | ## Features 47 | 48 | - [x] provides interactive file creation 49 | - [x] provides default templates in all created files (which could be further customized) 50 | - [x] checks if the minimum community standard is met and recommends the minimum required .md files 51 | - [x] provides bulk removal of specified .md files 52 | - [x] provides bulk creation of .md files 53 | - [x] optional `--empty` argument to make created files empty if preferred so 54 | - [x] optional `--required` to `create`/`check`/`list` only all files needed to meet the minimum community standard 55 | 56 | ## Installation 57 | 58 | `npm install md-generator` or globally using `npm install -g md-generator` 59 | 60 | - you can interact with the package from the npm registry 61 | using `npx md-generator [command] [options]` 62 | 63 | ## Supported Files 64 | `Required` 65 | - README.md 66 | - LICENSE 67 | - CODE_OF_CONDUCT.md 68 | - PULL_REQUEST_TEMPLATE.md 69 | - CONTRIBUTING.md 70 | - bug_report.md 71 | - feature_request.md 72 | 73 | `Optional` 74 | - CHANGELOG.md 75 | - SUPPORT.md 76 | - CONTRIBUTORS.md 77 | - AUTHORS.md 78 | - ACKNOWLEDGMENTS.md 79 | - CODEOWNERS.md 80 | 81 | ## Usage 82 | 83 | install globally using 84 | 85 | - npm : npm i -g md-generator 86 | - yarn : yarn add -g md-generator 87 | 88 | #### Trigger md-generator using 89 | 90 | - md-generator `[parent-options]` 91 | - md-generator `[commands]` `[command-options] [file names]` 92 | 93 | #### Parent Options: 94 | 95 | | Option | Function | 96 | | :-----------: | :-----------------------: | 97 | | -V, --version | Output the version number | 98 | | -h, --help | Output usage information | 99 | 100 | #### Commands: 101 | 102 | | Command | Function | 103 | | :-------------------------------: | :------------------------------------------------------------: | 104 | | list `[options]` `[File Names]` | List All Required/optional .md files | 105 | | create `[options]` `[File Names]` | Create All/specific files | 106 | | check `[options]` `[File Names]` | Checks codebase for the availability of All/Specific .md files | 107 | | remove `[options]` `[File Names]` | Remove All/specific .md files | 108 | 109 | #### Command-Options : 110 | 111 | | Command Option | Function | 112 | | :------------: | :----------------------------------------: | 113 | | -A, --all | Operate on all required/optional .md files | 114 | | -F, --file | Operate on specific .md files | 115 | | -E, --empty | make added files empty | 116 | | -R --required | Operate on required files | 117 | | -O --optional | Operate on optional files | 118 | 119 | > Note: 120 | 121 | - `File Names` can be with/without the file extension 122 | - multiples `File Names` should be separated with spaces ie `--file "README.md CONTRIBUTING.md"`. 123 | 124 | ## Demo 125 | 126 |

127 | 128 | Demo 129 | 130 | 131 | Demo 132 | 133 |

134 | 135 | | Cli Command | Function | 136 | | :--------------------------------------------------: | :-------------------------------------------------------: | 137 | | md-generator --help | displays help (all available command and option) | 138 | | md-generator list | lists all supported .md files | 139 | | md-generator list --optional | lists all supported optional .md files | 140 | | md-generator list --required | lists all required .md files to meet community standard | 141 | | md-generator check | checks code base for existing/missing supported .md files | 142 | | md-generator check --required | checks code base for existing/missing required .md files | 143 | | md-generator check --optional | checks code base for existing/missing optional .md files | 144 | | md-generator create | Interactively Generates all desired files | 145 | | md-generator create --file "file1.md file2.md ..." | Creates the supplied files | 146 | | md-generator create --file "file1 file2 ..." --empty | creates supplied files with no template | 147 | | md-generator create --required | Interactively create required .md files | 148 | | md-generator create --optional | Interactively create optional .md files | 149 | | md-generator remove | Interactively deletes all desired .md files | 150 | | md-generator remove --file "file1 file2 ..." | Interactively deletes all specified .md files | 151 | 152 | ## Known issues 153 | 154 | No known [issues](https://github.com/Oluwasegun-AA/md-generator/issues) at the moment. However, [issues](https://github.com/Oluwasegun-AA/md-generator/issues) can be raised when such is noticed 155 | 156 | ## Contributing 157 | 158 | > Feel free to contribute and kindly go through the Pull Request guide, and contributing.md file 159 | 160 | ## Contributors 161 | 162 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): 163 | 164 | 165 | 166 |
Adépòjù Olúwáségun
Adépòjù Olúwáségun

💻 📖 🚧 ⚠️
167 | 168 | 169 | 170 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind are welcome! 171 | 172 | ## License 173 | 174 | ![GitHub](https://img.shields.io/github/license/mashape/apistatus.svg) 175 | 176 | - **[MIT license]()** 177 | - With ❤️ from Olúwáségun. 178 | 179 | --- 180 | 181 | _This File was generated by [md-generator](https://github.com/oluwasegun-AA/md-generator)_ 182 | -------------------------------------------------------------------------------- /src/core/actions/createActions/index.ts: -------------------------------------------------------------------------------- 1 | import inquirer from 'inquirer'; 2 | 3 | import { 4 | createFiles, 5 | createRequired, 6 | createOptional, 7 | selectFileToCreate, 8 | createEmptyFiles, 9 | overrideFiles, 10 | } from '../../questions/setupQuestions/index'; 11 | import { 12 | spinner, 13 | fileNotDetectedAlert, 14 | unrecognizedFileAlert, 15 | getFullFileNames, 16 | showEndMessage, 17 | useHelpAlert, 18 | } from '../../../common/index'; 19 | import { getInfos } from '../../questions/askQuestions'; 20 | import { buildFileContent, writeFile } from '../../../templates/fileWriter'; 21 | import { 22 | allFiles, 23 | requiredFiles, 24 | optionalFiles, 25 | getArrayOfValues, 26 | queryFilesExistence, 27 | getItemFromFileName, 28 | } from '../actionsUtils'; 29 | import { isEmpty } from 'lodash'; 30 | 31 | import { ICurrentFile, ISortedFiles, IAllFiles } from '../../../../types/typeDeclarations.interface'; 32 | 33 | /** 34 | * @description 35 | * return all files to be created based on the choice of the user to override the existing file 36 | * 37 | * @param existingFiles - files existing in the code base 38 | * @param noneExistingFiles - files not found in the codebase 39 | */ 40 | export const shouldOverride: any = async (existingFiles: string[], noneExistingFiles: string[]): Promise => { 41 | const files: string[] = await inquirer 42 | .prompt(overrideFiles(existingFiles)) 43 | .then((item: any) => { 44 | const { override } = item; 45 | if (!override && isEmpty(noneExistingFiles[0])) return process.exit(1); 46 | if (override) return noneExistingFiles.concat(existingFiles); 47 | return noneExistingFiles; 48 | }); 49 | return files; 50 | }; 51 | 52 | export const getValidFiles: any = (values: ICurrentFile[] | string[]): any => { 53 | const list: string[] = 54 | typeof values[0] === 'object' ? getFullFileNames(values as ICurrentFile[]) : values as string[]; 55 | const { foundFiles, filesNotFound }: ISortedFiles = queryFilesExistence(list); 56 | const validFileNames: string[] | ICurrentFile[] = filesNotFound.filter((key: string) => 57 | Object.keys(allFiles).includes(key)); 58 | const inValidFileNames: string[] | ICurrentFile[] = filesNotFound.filter( 59 | (key: string) => !Object.keys(allFiles).includes(key) 60 | ); 61 | return { 62 | validFileNames, 63 | inValidFileNames, 64 | foundFiles, 65 | }; 66 | }; 67 | 68 | /** 69 | * @description 70 | * handles files override for existing files based on the user's choice 71 | * 72 | * @param values - files to be overridden 73 | */ 74 | export const handleOverride: any = async (values: string[] | ICurrentFile[]): Promise => { 75 | let { validFileNames }: ISortedFiles = getValidFiles(values); 76 | const { inValidFileNames, foundFiles }: ISortedFiles = getValidFiles(values); 77 | if (foundFiles.length > 0) { 78 | validFileNames = await shouldOverride(foundFiles, validFileNames); 79 | } 80 | return { validFileNames, inValidFileNames }; 81 | }; 82 | 83 | /** 84 | * @description 85 | * Creates Files 86 | * 87 | * @param USE_DEFAULT determines if questions are asked or default values are used 88 | * @param filesToBeCreated file names (Array of strings) 89 | * @param isEmpty determines if file to be created would be empty 90 | */ 91 | // tslint:disable-next-line: max-line-length 92 | export const createMdFiles = async (USE_DEFAULT: boolean, filesToBeCreated: string[] | ICurrentFile[], isEmpty: boolean): Promise => { 93 | const { validFileNames, inValidFileNames }: ISortedFiles = await handleOverride(filesToBeCreated); 94 | if (validFileNames.length === 0 && inValidFileNames.length === 0) { 95 | useHelpAlert({ 96 | errorText: 'Error: No option selected, Press to select, to toggle all, to invert selection', 97 | override: true 98 | }); 99 | process.exit(1); 100 | } 101 | if (validFileNames.length === 0 && inValidFileNames.length > 0) { 102 | useHelpAlert({ 103 | errorText:`${inValidFileNames} not supported,`, 104 | override: false 105 | }); 106 | process.exit(1); 107 | } 108 | if (inValidFileNames.length > 0 && validFileNames.length > 0) { 109 | useHelpAlert({ 110 | errorText: `The following files are not supported and will not be created ${inValidFileNames}`, 111 | override: true 112 | }); 113 | } 114 | // tslint:disable: no-parameter-reassignment 115 | const files: any = validFileNames; 116 | if (validFileNames.includes('README') || validFileNames.includes('LICENSE')) USE_DEFAULT = false; 117 | await getInfos(USE_DEFAULT, files).then((projectInfos: any) => { 118 | files.forEach(async (file: string | ICurrentFile) => { 119 | let pathToTemplate: string; 120 | const { path, templatePath }: ICurrentFile = getItemFromFileName(file as string); 121 | if (file === 'LICENSE') { 122 | pathToTemplate = projectInfos.licenseName.path; 123 | } else { 124 | pathToTemplate = templatePath || projectInfos.templatePath; 125 | } 126 | const fileContent: string = isEmpty 127 | ? '' 128 | : await buildFileContent(projectInfos, pathToTemplate); 129 | await writeFile(fileContent, path); 130 | }); 131 | }); 132 | spinner().succeed('File(s) created Successfully\n'); 133 | showEndMessage(); 134 | }; 135 | 136 | /** 137 | * @description 138 | * determine which file creation mode is activated with respect to the type of files 139 | * 140 | * @param allItems all files array 141 | * @param mode Question modes 142 | * @param CREATE_EMPTY_FILE determines if questions are asked or default values are used 143 | */ 144 | // tslint:disable-next-line: max-line-length 145 | const processCreation = async (allItems: IAllFiles | ICurrentFile[], mode: any, IS_EMPTY_FILE?: boolean): Promise => { 146 | const USE_DEFAULT_VALUES: boolean = true; 147 | let CREATE_EMPTY_FILE: boolean = false; 148 | let filesToBeCreated: ICurrentFile[]; 149 | return inquirer.prompt(mode(getArrayOfValues(allItems as IAllFiles))) 150 | .then((answer: any): any => { 151 | const { createFiles } = answer; 152 | if (createFiles === false) return process.exit(1); 153 | inquirer.prompt(createEmptyFiles()).then((res: any) => { 154 | CREATE_EMPTY_FILE = IS_EMPTY_FILE || res.empty; 155 | if (typeof createFiles === 'object') { 156 | filesToBeCreated = Object.values(allItems).filter((file: ICurrentFile) => 157 | createFiles.includes(file.name)); 158 | if ( 159 | createFiles.includes('README.md') || 160 | createFiles.includes('CODE_OF_CONDUCT.md') || 161 | createFiles.includes('LICENSE') 162 | ) { 163 | return createMdFiles( 164 | !USE_DEFAULT_VALUES, 165 | filesToBeCreated, 166 | CREATE_EMPTY_FILE 167 | ); 168 | } 169 | } 170 | if (createFiles === true) { 171 | return createMdFiles(!USE_DEFAULT_VALUES, allItems as ICurrentFile[], CREATE_EMPTY_FILE); 172 | } 173 | return createMdFiles(USE_DEFAULT_VALUES, createFiles, CREATE_EMPTY_FILE); 174 | }); 175 | }); 176 | }; 177 | 178 | /** 179 | * @description 180 | * Check if files to be created is a valid .md file, exists in codebase, or doesn't exist. 181 | * 182 | * @param values Array of files names 183 | */ 184 | export const checkCreatableFiles = async (values: ICurrentFile[]): Promise => { 185 | const { validFileNames, inValidFileNames, foundFiles }: ISortedFiles = getValidFiles( 186 | values 187 | ); 188 | let validFiles: ICurrentFile[] | string[] = validFileNames.concat(foundFiles); 189 | if (inValidFileNames.length > 0) { 190 | unrecognizedFileAlert(inValidFileNames); 191 | } 192 | if (validFiles.length > 0) { 193 | validFiles = Object.values(allFiles).filter((item: any) => 194 | validFiles.includes(item.name.split('.')[0])); 195 | } else { 196 | fileNotDetectedAlert(); 197 | process.exit(1); 198 | } 199 | return validFiles; 200 | }; 201 | 202 | /** 203 | * @description creates file when command is passed with out options 204 | */ 205 | const createNonSpecificFiles = (): void => { 206 | processCreation(allFiles, selectFileToCreate); 207 | }; 208 | 209 | /** 210 | * @description creates files when command is passed with option -F or --file 211 | * @param isEmpty determines if questions are asked or default values are used 212 | * @param values Files names 213 | */ 214 | export const createSpecificFiles = async (isEmpty: boolean, values: ICurrentFile[]): Promise => { 215 | const files = await checkCreatableFiles(values); 216 | processCreation(files, createFiles, isEmpty); 217 | }; 218 | 219 | /** 220 | * @description 221 | * Creates files when command is passed with option -R or --required 222 | * 223 | * @param isEmpty determines if questions are asked or default values are used 224 | */ 225 | const createRequiredFiles = (isEmpty: boolean): void => { 226 | processCreation(requiredFiles, createRequired, isEmpty); 227 | }; 228 | 229 | /** 230 | * @description 231 | * Creates files when command is passed with option -O or --optional 232 | * 233 | * @param isEmpty determines if questions are asked or default values are used 234 | */ 235 | const createOptionalFiles = (isEmpty: boolean): void => { 236 | processCreation(optionalFiles, createOptional, isEmpty); 237 | }; 238 | 239 | /** 240 | * @description 241 | * handles file creation 242 | * 243 | * @param values arguments i.e command, payload and command options 244 | */ 245 | const createHandler = (values: any): any => { 246 | const { 247 | file, 248 | required, 249 | optional, 250 | isEmpty, 251 | resp 252 | } = values; 253 | if (file) return createSpecificFiles(isEmpty, resp); 254 | if (required) return createRequiredFiles(isEmpty); 255 | if (optional) return createOptionalFiles(isEmpty); 256 | createNonSpecificFiles(); 257 | }; 258 | 259 | export default createHandler; 260 | -------------------------------------------------------------------------------- /src/templates/files/required/template-MOZILLA-LICENSE.md: -------------------------------------------------------------------------------- 1 | 1. #### Definitions 2 | 1.1. "Contributor" 3 | means each individual or legal entity that creates, contributes to the creation of, or owns Covered Software. 4 | 5 | 1.2. “Contributor Version” 6 | means the combination of the Contributions of others (if any) used by a Contributor and that particular Contributor’s Contribution. 7 | 8 | 1.3. “Contribution” 9 | means Covered Software of a particular Contributor. 10 | 11 | 1.4. “Covered Software” 12 | means Source Code Form to which the initial Contributor has attached the notice in Exhibit A, the Executable Form of such Source Code Form, and Modifications of such Source Code Form, in each case including portions thereof. 13 | 14 | 1.5. “Incompatible With Secondary Licenses” 15 | means 16 | 17 | that the initial Contributor has attached the notice described in Exhibit B to the Covered Software; or 18 | 19 | that the Covered Software was made available under the terms of version 1.1 or earlier of the License, but not also under the terms of a Secondary License. 20 | 21 | 1.6. “Executable Form” 22 | means any form of the work other than Source Code Form. 23 | 24 | 1.7. “Larger Work” 25 | means a work that combines Covered Software with other material, in a separate file or files, that is not Covered Software. 26 | 27 | 1.8. “License” 28 | means this document. 29 | 30 | 1.9. “Licensable” 31 | means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently, any and all of the rights conveyed by this License. 32 | 33 | 1.10. “Modifications” 34 | means any of the following: 35 | 36 | any file in Source Code Form that results from an addition to, deletion from, or modification of the contents of Covered Software; or 37 | 38 | any new file in Source Code Form that contains any Covered Software. 39 | 40 | 1.11. “Patent Claims” of a Contributor 41 | means any patent claim(s), including without limitation, method, process, and apparatus claims, in any patent Licensable by such Contributor that would be infringed, but for the grant of the License, by the making, using, selling, offering for sale, having made, import, or transfer of either its Contributions or its Contributor Version. 42 | 43 | 1.12. “Secondary License” 44 | means either the GNU General Public License, Version 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General Public License, Version 3.0, or any later versions of those licenses. 45 | 46 | 1.13. “Source Code Form” 47 | means the form of the work preferred for making modifications. 48 | 49 | 1.14. “You” (or “Your”) 50 | means an individual or a legal entity exercising rights under this License. For legal entities, “You” includes any entity that controls, is controlled by, or is under common control with You. For purposes of this definition, “control” means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity. 51 | 52 | 2. #### License Grants and Conditions 53 | 2.1. Grants 54 | Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license: 55 | 56 | under intellectual property rights (other than patent or trademark) Licensable by such Contributor to use, reproduce, make available, modify, display, perform, distribute, and otherwise exploit its Contributions, either on an unmodified basis, with Modifications, or as part of a Larger Work; and 57 | 58 | under Patent Claims of such Contributor to make, use, sell, offer for sale, have made, import, and otherwise transfer either its Contributions or its Contributor Version. 59 | 60 | 2.2. Effective Date 61 | The licenses granted in Section 2.1 with respect to any Contribution become effective for each Contribution on the date the Contributor first distributes such Contribution. 62 | 63 | 2.3. Limitations on Grant Scope 64 | The licenses granted in this Section 2 are the only rights granted under this License. No additional rights or licenses will be implied from the distribution or licensing of Covered Software under this License. Notwithstanding Section 2.1(b) above, no patent license is granted by a Contributor: 65 | 66 | for any code that a Contributor has removed from Covered Software; or 67 | 68 | for infringements caused by: (i) Your and any other third party’s modifications of Covered Software, or (ii) the combination of its Contributions with other software (except as part of its Contributor Version); or 69 | 70 | under Patent Claims infringed by Covered Software in the absence of its Contributions. 71 | 72 | This License does not grant any rights in the trademarks, service marks, or logos of any Contributor (except as may be necessary to comply with the notice requirements in Section 3.4). 73 | 74 | 2.4. Subsequent Licenses 75 | No Contributor makes additional grants as a result of Your choice to distribute the Covered Software under a subsequent version of this License (see Section 10.2) or under the terms of a Secondary License (if permitted under the terms of Section 3.3). 76 | 77 | 2.5. Representation 78 | Each Contributor represents that the Contributor believes its Contributions are its original creation(s) or it has sufficient rights to grant the rights to its Contributions conveyed by this License. 79 | 80 | 2.6. Fair Use 81 | This License is not intended to limit any rights You have under applicable copyright doctrines of fair use, fair dealing, or other equivalents. 82 | 83 | 2.7. Conditions 84 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in Section 2.1. 85 | 86 | 3. #### Responsibilities 87 | 3.1. Distribution of Source Form 88 | All distribution of Covered Software in Source Code Form, including any Modifications that You create or to which You contribute, must be under the terms of this License. You must inform recipients that the Source Code Form of the Covered Software is governed by the terms of this License, and how they can obtain a copy of this License. You may not attempt to alter or restrict the recipients’ rights in the Source Code Form. 89 | 90 | 3.2. Distribution of Executable Form 91 | If You distribute Covered Software in Executable Form then: 92 | 93 | such Covered Software must also be made available in Source Code Form, as described in Section 3.1, and You must inform recipients of the Executable Form how they can obtain a copy of such Source Code Form by reasonable means in a timely manner, at a charge no more than the cost of distribution to the recipient; and 94 | 95 | You may distribute such Executable Form under the terms of this License, or sublicense it under different terms, provided that the license for the Executable Form does not attempt to limit or alter the recipients’ rights in the Source Code Form under this License. 96 | 97 | 3.3. Distribution of a Larger Work 98 | You may create and distribute a Larger Work under terms of Your choice, provided that You also comply with the requirements of this License for the Covered Software. If the Larger Work is a combination of Covered Software with a work governed by one or more Secondary Licenses, and the Covered Software is not Incompatible With Secondary Licenses, this License permits You to additionally distribute such Covered Software under the terms of such Secondary License(s), so that the recipient of the Larger Work may, at their option, further distribute the Covered Software under the terms of either this License or such Secondary License(s). 99 | 100 | 3.4. Notices 101 | You may not remove or alter the substance of any license notices (including copyright notices, patent notices, disclaimers of warranty, or limitations of liability) contained within the Source Code Form of the Covered Software, except that You may alter any license notices to the extent required to remedy known factual inaccuracies. 102 | 103 | 3.5. Application of Additional Terms 104 | You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, You may do so only on Your own behalf, and not on behalf of any Contributor. You must make it absolutely clear that any such warranty, support, indemnity, or liability obligation is offered by You alone, and You hereby agree to indemnify every Contributor for any liability incurred by such Contributor as a result of warranty, support, indemnity or liability terms You offer. You may include additional disclaimers of warranty and limitations of liability specific to any jurisdiction. 105 | 106 | 4. #### Inability to Comply Due to Statute or Regulation 107 | If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Software due to statute, judicial order, or regulation then You must: (a) comply with the terms of this License to the maximum extent possible; and (b) describe the limitations and the code they affect. Such description must be placed in a text file included with all distributions of the Covered Software under this License. Except to the extent prohibited by statute or regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be able to understand it. 108 | 109 | 5. #### Termination 110 | 5.1. The rights granted under this License will terminate automatically if You fail to comply with any of its terms. However, if You become compliant, then the rights granted under this License from a particular Contributor are reinstated (a) provisionally, unless and until such Contributor explicitly and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor fails to notify You of the non-compliance by some reasonable means prior to 60 days after You have come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an ongoing basis if such Contributor notifies You of the non-compliance by some reasonable means, this is the first time You have received notice of non-compliance with this License from such Contributor, and You become compliant prior to 30 days after Your receipt of the notice. 111 | 112 | 5.2. If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate. 113 | 114 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user license agreements (excluding distributors and resellers) which have been validly granted by You or Your distributors under this License prior to termination shall survive termination. 115 | 116 | 6. #### Disclaimer of Warranty 117 | Covered Software is provided under this License on an “as is” basis, without warranty of any kind, either expressed, implied, or statutory, including, without limitation, warranties that the Covered Software is free of defects, merchantable, fit for a particular purpose or non-infringing. The entire risk as to the quality and performance of the Covered Software is with You. Should any Covered Software prove defective in any respect, You (not any Contributor) assume the cost of any necessary servicing, repair, or correction. This disclaimer of warranty constitutes an essential part of this License. No use of any Covered Software is authorized under this License except under this disclaimer. 118 | 119 | 7. #### Limitation of Liability 120 | Under no circumstances and under no legal theory, whether tort (including negligence), contract, or otherwise, shall any Contributor, or anyone who distributes Covered Software as permitted above, be liable to You for any direct, indirect, special, incidental, or consequential damages of any character including, without limitation, damages for lost profits, loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses, even if such party shall have been informed of the possibility of such damages. This limitation of liability shall not apply to liability for death or personal injury resulting from such party’s negligence to the extent applicable law prohibits such limitation. Some jurisdictions do not allow the exclusion or limitation of incidental or consequential damages, so this exclusion and limitation may not apply to You. 121 | 122 | 8. #### Litigation 123 | Any litigation relating to this License may be brought only in the courts of a jurisdiction where the defendant maintains its principal place of business and such litigation shall be governed by laws of that jurisdiction, without reference to its conflict-of-law provisions. Nothing in this Section shall prevent a party’s ability to bring cross-claims or counter-claims. 124 | 125 | 9. #### Miscellaneous 126 | This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not be used to construe this License against a Contributor. 127 | 128 | 10. #### Versions of the License 129 | 10.1. New Versions 130 | Mozilla Foundation is the license steward. Except as provided in Section 10.3, no one other than the license steward has the right to modify or publish new versions of this License. Each version will be given a distinguishing version number. 131 | 132 | 10.2. Effect of New Versions 133 | You may distribute the Covered Software under the terms of the version of the License under which You originally received the Covered Software, or under the terms of any subsequent version published by the license steward. 134 | 135 | 10.3. Modified Versions 136 | If you create software not governed by this License, and you want to create a new license for such software, you may create and use a modified version of this License if you rename the license and remove any references to the name of the license steward (except to note that such modified license differs from this License). 137 | 138 | 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses 139 | If You choose to distribute Source Code Form that is Incompatible With Secondary Licenses under the terms of this version of the License, the notice described in Exhibit B of this License must be attached. 140 | 141 | Exhibit A - Source Code Form License Notice 142 | This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/. 143 | 144 | If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. 145 | 146 | You may add additional accurate notices of copyright ownership. 147 | 148 | Exhibit B - “Incompatible With Secondary Licenses” Notice 149 | This Source Code Form is “Incompatible With Secondary Licenses”, as defined by the Mozilla Public License, v. 2.0. 150 | --------------------------------------------------------------------------------