readme-md-generator
Advanced tools
Comparing version 0.4.1 to 0.5.0
@@ -9,4 +9,14 @@ module.exports = { | ||
rules: { | ||
semi: ['error', 'never'], | ||
'no-use-before-define': ['error', { functions: false }], | ||
'comma-dangle': 0, | ||
'no-var': 2, | ||
'prefer-const': 2 | ||
'prefer-const': 2, | ||
'operator-linebreak': 0, | ||
'no-confusing-arrow': 0, | ||
'implicit-arrow-linebreak': 0, | ||
indent: 0, | ||
'no-param-reassign': 0, | ||
'function-paren-newline': 0, | ||
'arrow-parens': 0 | ||
}, | ||
@@ -13,0 +23,0 @@ parserOptions: { |
# Changelog | ||
<a name="0.5.0"></a> | ||
## 0.5.0 (2019-06-27) | ||
### Added | ||
- ✨ Get author name from package.json even if author prop is an object ([#75](https://github.com/kefranabg/readme-md-generator/issues/75)) [[688c338](https://github.com/kefranabg/readme-md-generator/commit/688c33833188a5487ff6df024d4993404ee0406c)] | ||
- ✨ Allow user to specify path to custom README template ([#68](https://github.com/kefranabg/readme-md-generator/issues/68)) [[e0d66c0](https://github.com/kefranabg/readme-md-generator/commit/e0d66c002c8108ff3ae142979a5c8003a28a8107)] | ||
- ✨ Allow user to choose a non HTML README template ([#80](https://github.com/kefranabg/readme-md-generator/issues/80)) [[6d5c884](https://github.com/kefranabg/readme-md-generator/commit/6d5c8848c476fc2770204f215ddd6f48d539b4e0)] | ||
### Changed | ||
- ⬆️ Update inquirer to the latest version ([#67](https://github.com/kefranabg/readme-md-generator/issues/67)) [[59f69e5](https://github.com/kefranabg/readme-md-generator/commit/59f69e51ec1caae17230d9331a2c14b04bd2825e)] | ||
- ⬆️ Update eslint to the latest version ([#70](https://github.com/kefranabg/readme-md-generator/issues/70)) [[88c96ac](https://github.com/kefranabg/readme-md-generator/commit/88c96ac31acfa12381a33d39a2953f3405053870)] | ||
- ♻️ Refactoring inquirer code ([#69](https://github.com/kefranabg/readme-md-generator/issues/69)) [[802d57d](https://github.com/kefranabg/readme-md-generator/commit/802d57d8af2e2cdcdbddea86c2fa2225db6d4516)] | ||
<a name="0.4.1"></a> | ||
@@ -24,3 +40,3 @@ | ||
- 📝 Update README [[0dff1e5](https://github.com/kefranabg/readme-md-generator/commit/0dff1e5562404559b2ad64ccf0c8fc3d1df73f92)] | ||
- docs: add kefranabg as a contributor ([#57](https://github.com/kefranabg/readme-md-generator/issues/57)) [[e5d9d53](https://github.com/kefranabg/readme-md-generator/commit/e5d9d5376b8341e06005e497728935c2874631b6)] | ||
- 📝 add kefranabg as a contributor ([#57](https://github.com/kefranabg/readme-md-generator/issues/57)) [[e5d9d53](https://github.com/kefranabg/readme-md-generator/commit/e5d9d5376b8341e06005e497728935c2874631b6)] | ||
- 👥 Add contributors [[2533cd8](https://github.com/kefranabg/readme-md-generator/commit/2533cd8f2c8c78a043e67ca2bead9b4606606121)] | ||
@@ -27,0 +43,0 @@ |
{ | ||
"name": "readme-md-generator", | ||
"version": "0.4.1", | ||
"version": "0.5.0", | ||
"description": "CLI that generates beautiful README.md files.", | ||
@@ -14,3 +14,3 @@ "main": "src/index.js", | ||
"git-repo-name": "^1.0.1", | ||
"inquirer": "~6.3.1", | ||
"inquirer": "~6.4.1", | ||
"load-json-file": "^6.0.0", | ||
@@ -24,3 +24,3 @@ "lodash": "^4.17.11", | ||
"codecov": "^3.5.0", | ||
"eslint": "^5.3.0", | ||
"eslint": "^6.0.0", | ||
"eslint-config-airbnb-base": "^13.1.0", | ||
@@ -27,0 +27,0 @@ "eslint-plugin-import": "^2.17.3", |
@@ -8,7 +8,10 @@ <h1 align="center">Welcome to readme-md-generator 👋</h1> | ||
<a href="https://github.com/kefranabg/readme-md-generator/blob/master/LICENSE"> | ||
<img alt="License: MIT" src="https://img.shields.io/badge/License-MIT-yellow.svg" target="_blank" /> | ||
<img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-yellow.svg" target="_blank" /> | ||
</a> | ||
<a href="https://codecov.io/gh/kefranabg/readme-md-generator"> | ||
<img src="https://codecov.io/gh/kefranabg/readme-md-generator/branch/master/graph/badge.svg" /> | ||
</a> | ||
</a> | ||
<a href="https://github.com/frinyvonnick/gitmoji-changelog"> | ||
<img src="https://img.shields.io/badge/changelog-gitmoji-brightgreen.svg" alt="gitmoji-changelog"> | ||
</a> | ||
<a href="https://twitter.com/FranckAbgrall"> | ||
@@ -26,3 +29,3 @@ <img alt="Twitter: FranckAbgrall" src="https://img.shields.io/twitter/follow/FranckAbgrall.svg?style=social" target="_blank" /> | ||
<p align="center"> | ||
<img width="700" align="center" src="https://user-images.githubusercontent.com/9840435/59459416-07f1e580-8e1d-11e9-89e7-6a5e6b373e93.gif" alt="demo"/> | ||
<img width="700" align="center" src="https://user-images.githubusercontent.com/9840435/60266022-72a82400-98e7-11e9-9958-f9004c2f97e1.gif" alt="demo"/> | ||
</p> | ||
@@ -33,3 +36,3 @@ | ||
<p align="center"> | ||
<img width="700" src="https://user-images.githubusercontent.com/9840435/59458494-d7a94780-8e1a-11e9-9103-42639c347c38.jpg" alt="cli output"/> | ||
<img width="700" src="https://user-images.githubusercontent.com/9840435/60266090-9cf9e180-98e7-11e9-9cac-3afeec349bbc.jpg" alt="cli output"/> | ||
</p> | ||
@@ -72,3 +75,3 @@ | ||
OR use default values for all questions (`-y, --yes`): | ||
Or use default values for all questions (`-y`): | ||
@@ -79,2 +82,10 @@ ```sh | ||
Use your own `ejs` README template (`-p`): | ||
```sh | ||
npx readme-md-generator -p path/to/my/own/template.md | ||
``` | ||
You can find [ejs README template examples here](https://github.com/kefranabg/readme-md-generator/tree/master/templates). | ||
## 🤝 Contributing | ||
@@ -97,2 +108,4 @@ | ||
<td align="center"><a href="https://github.com/apatrascu"><img src="https://avatars3.githubusercontent.com/u/1193770?v=4" width="75px;" alt="Alecsandru Patrascu"/><br /><sub><b>Alecsandru Patrascu</b></sub></a><br /><a href="https://github.com/kefranabg/readme-md-generator/commits?author=apatrascu" title="Code">💻</a></td> | ||
<td align="center"><a href="http://milad.nekofar.com"><img src="https://avatars3.githubusercontent.com/u/147401?v=4" width="75px;" alt="Milad Nekofar"/><br /><sub><b>Milad Nekofar</b></sub></a><br /><a href="https://github.com/kefranabg/readme-md-generator/commits?author=nekofar" title="Code">💻</a> <a href="https://github.com/kefranabg/readme-md-generator/commits?author=nekofar" title="Tests">⚠️</a> <a href="#ideas-nekofar" title="Ideas, Planning, & Feedback">🤔</a></td> | ||
<td align="center"><a href="https://github.com/hgb123"><img src="https://avatars0.githubusercontent.com/u/18468577?v=4" width="75px;" alt="Bao Ho"/><br /><sub><b>Bao Ho</b></sub></a><br /><a href="https://github.com/kefranabg/readme-md-generator/commits?author=hgb123" title="Code">💻</a> <a href="https://github.com/kefranabg/readme-md-generator/commits?author=hgb123" title="Tests">⚠️</a></td> | ||
</tr> | ||
@@ -99,0 +112,0 @@ </table> |
@@ -1,72 +0,29 @@ | ||
const inquirer = require('inquirer') | ||
const { isNil } = require('lodash') | ||
const readme = require('./readme') | ||
const projectInfos = require('./project-infos') | ||
const questionsBuilders = require('./questions') | ||
const infos = require('./project-infos') | ||
const utils = require('./utils') | ||
const askQuestions = require('./ask-questions') | ||
/** | ||
* Ask user questions and return context to generate a README | ||
* | ||
* @param {Object} projectInfos | ||
*/ | ||
const askQuestions = async (projectInfos, skipQuestions) => { | ||
let answersContext = { | ||
isGithubRepos: projectInfos.isGithubRepos, | ||
repositoryUrl: projectInfos.repositoryUrl, | ||
projectPrerequisites: undefined | ||
} | ||
for (const questionBuilder of Object.values(questionsBuilders)) { | ||
const question = questionBuilder(projectInfos, answersContext) | ||
if (!isNil(question)) { | ||
const currentAnswerContext = skipQuestions | ||
? { [question.name]: getDefaultAnswer(question) } | ||
: await inquirer.prompt([question]) | ||
answersContext = { | ||
...answersContext, | ||
...currentAnswerContext | ||
} | ||
} | ||
} | ||
return answersContext | ||
} | ||
/** | ||
* Get the default answer depending on the question type | ||
* | ||
* @param {Object} question | ||
*/ | ||
const getDefaultAnswer = question => { | ||
switch (question.type) { | ||
case 'input': | ||
return question.default || '' | ||
case 'checkbox': | ||
return question.choices | ||
.filter(choice => choice.checked) | ||
.map(choice => choice.value) | ||
default: | ||
return undefined | ||
} | ||
} | ||
/** | ||
* Main process: | ||
* 1) Gather project infos | ||
* 2) Ask user questions | ||
* 3) Build README content | ||
* 4) Create README.md file | ||
* 1) Get README template path | ||
* 2) Gather project infos | ||
* 3) Ask user questions | ||
* 4) Build README content | ||
* 5) Create README.md file | ||
* | ||
* @param {Object} args | ||
*/ | ||
const mainProcess = async ({ template, yes }) => { | ||
const projectInformations = await projectInfos.getProjectInfos() | ||
const answersContext = await cli.askQuestions(projectInformations, yes) | ||
module.exports = async ({ customTemplatePath, useDefaultAnswers }) => { | ||
const templatePath = await readme.getReadmeTemplatePath( | ||
customTemplatePath, | ||
useDefaultAnswers | ||
) | ||
const projectInformations = await infos.getProjectInfos() | ||
const answersContext = await askQuestions( | ||
projectInformations, | ||
useDefaultAnswers | ||
) | ||
const readmeContent = await readme.buildReadmeContent( | ||
answersContext, | ||
template | ||
templatePath | ||
) | ||
@@ -78,9 +35,1 @@ | ||
} | ||
const cli = { | ||
mainProcess, | ||
getDefaultAnswer, | ||
askQuestions | ||
} | ||
module.exports = cli |
const inquirer = require('inquirer') | ||
const cli = require('./cli') | ||
const projectInfos = require('./project-infos') | ||
const mainProcess = require('./cli') | ||
const infos = require('./project-infos') | ||
const readme = require('./readme') | ||
const utils = require('./utils') | ||
const questions = require('./questions') | ||
const askQuestions = require('./ask-questions') | ||
const realAskQuestions = cli.askQuestions | ||
inquirer.prompt = jest.fn(items => | ||
Promise.resolve( | ||
items.reduce((result, item) => { | ||
result[item.name] = 'value' | ||
return result | ||
}, {}) | ||
) | ||
) | ||
inquirer.prompt = jest.fn(([question]) => | ||
Promise.resolve({ [question.name]: 'value' }) | ||
jest.mock('./ask-questions', () => | ||
jest.fn(() => Promise.resolve({ projectName: 'readme-md-generator' })) | ||
) | ||
@@ -34,141 +41,40 @@ | ||
describe('cli', () => { | ||
beforeEach(() => { | ||
inquirer.prompt.mockClear() | ||
describe('mainProcess', () => { | ||
afterEach(() => { | ||
askQuestions.mockClear() | ||
}) | ||
describe('mainProcess', () => { | ||
const answersContext = { projectName: 'readme-md-generator' } | ||
it('should call main functions with correct args', async () => { | ||
const customTemplatePath = undefined | ||
const useDefaultAnswers = true | ||
const projectInformations = { name: 'readme-md-generator' } | ||
const readmeContent = 'content' | ||
const templatePath = 'path/to/template' | ||
infos.getProjectInfos = jest.fn(() => Promise.resolve(projectInformations)) | ||
readme.buildReadmeContent = jest.fn(() => Promise.resolve(readmeContent)) | ||
readme.getReadmeTemplatePath = jest.fn(() => Promise.resolve(templatePath)) | ||
readme.writeReadme = jest.fn() | ||
utils.showEndMessage = jest.fn() | ||
beforeAll(() => { | ||
cli.askQuestions = jest.fn(() => Promise.resolve(answersContext)) | ||
}) | ||
await mainProcess({ customTemplatePath, useDefaultAnswers }) | ||
afterEach(() => { | ||
cli.askQuestions.mockClear() | ||
}) | ||
afterAll(() => { | ||
cli.askQuestions = realAskQuestions | ||
}) | ||
it('should call main functions with correct args', async () => { | ||
const template = 'default' | ||
const projectInformations = { name: 'readme-md-generator' } | ||
const readmeContent = 'content' | ||
projectInfos.getProjectInfos = jest.fn(() => | ||
Promise.resolve(projectInformations) | ||
) | ||
readme.buildReadmeContent = jest.fn(() => Promise.resolve(readmeContent)) | ||
readme.writeReadme = jest.fn() | ||
utils.showEndMessage = jest.fn() | ||
await cli.mainProcess({ template }) | ||
expect(projectInfos.getProjectInfos).toHaveBeenCalledTimes(1) | ||
expect(cli.askQuestions).toHaveBeenNthCalledWith( | ||
1, | ||
projectInformations, | ||
undefined | ||
) | ||
expect(readme.buildReadmeContent).toHaveBeenNthCalledWith( | ||
1, | ||
answersContext, | ||
template | ||
) | ||
expect(readme.writeReadme).toHaveBeenNthCalledWith(1, readmeContent) | ||
expect(utils.showEndMessage).toHaveBeenCalledTimes(1) | ||
}) | ||
it('should forward --yes option to askQuestions', async () => { | ||
const template = 'default' | ||
const projectInformations = { name: 'readme-md-generator' } | ||
const skipQuestions = true | ||
utils.showEndMessage = jest.fn() | ||
await cli.mainProcess({ template, yes: skipQuestions }) | ||
expect(cli.askQuestions).toHaveBeenNthCalledWith( | ||
1, | ||
projectInformations, | ||
skipQuestions | ||
) | ||
}) | ||
expect(readme.getReadmeTemplatePath).toHaveBeenNthCalledWith( | ||
1, | ||
customTemplatePath, | ||
useDefaultAnswers | ||
) | ||
expect(infos.getProjectInfos).toHaveBeenCalledTimes(1) | ||
expect(askQuestions).toHaveBeenNthCalledWith( | ||
1, | ||
projectInformations, | ||
useDefaultAnswers | ||
) | ||
expect(readme.buildReadmeContent).toHaveBeenNthCalledWith( | ||
1, | ||
{ projectName: 'readme-md-generator' }, | ||
templatePath | ||
) | ||
expect(readme.writeReadme).toHaveBeenNthCalledWith(1, readmeContent) | ||
expect(utils.showEndMessage).toHaveBeenCalledTimes(1) | ||
}) | ||
describe('getDefaultAnswer', () => { | ||
it('should handle input prompts correctly', () => { | ||
const question = { type: 'input', default: 'default' } | ||
const result = cli.getDefaultAnswer(question) | ||
expect(result).toEqual(question.default) | ||
}) | ||
it('should handle choices prompts correctly', () => { | ||
const value = { name: 'name', value: 'value' } | ||
const question = { | ||
type: 'checkbox', | ||
choices: [{ value, checked: true }, { checked: false }] | ||
} | ||
const result = cli.getDefaultAnswer(question) | ||
expect(result).toEqual([value]) | ||
}) | ||
it('should return empty string for non-defaulted fields', () => { | ||
const question = { type: 'input' } | ||
const result = cli.getDefaultAnswer(question) | ||
expect(result).toEqual('') | ||
}) | ||
it('should return undefined for invalid types', () => { | ||
const question = { type: 'invalid' } | ||
const result = cli.getDefaultAnswer(question) | ||
expect(result).toEqual(undefined) | ||
}) | ||
}) | ||
describe('askQuestions', () => { | ||
it('should call all builder functions exported by questions', async () => { | ||
const projectInfos = { name: 'readme-md-generator' } | ||
await cli.askQuestions(projectInfos) | ||
expect(questions.askProjectName).toHaveBeenCalledTimes(1) | ||
expect(questions.askProjectVersion).toHaveBeenCalledTimes(1) | ||
expect(questions.askProjectDescription).toHaveBeenCalledTimes(1) | ||
}) | ||
it('should use default values with --yes option', async () => { | ||
const projectInfos = { name: 'readme-md-generator' } | ||
const result = await cli.askQuestions(projectInfos, true) | ||
expect(inquirer.prompt).not.toHaveBeenCalled() | ||
expect(result).toEqual({ | ||
projectName: 'defaultProjectName', | ||
projectVersion: '', | ||
projectDescription: [{ name: 'choiceOne', value: 1 }], | ||
isGithubRepos: undefined, | ||
repositoryUrl: undefined, | ||
projectPrerequisites: undefined | ||
}) | ||
}) | ||
it('should return merged contexts', async () => { | ||
const projectInfos = { name: 'readme-md-generator' } | ||
const context = await cli.askQuestions(projectInfos) | ||
expect(context).toEqual({ | ||
projectName: 'value', | ||
projectVersion: 'value', | ||
projectDescription: 'value', | ||
isGithubRepos: undefined, | ||
repositoryUrl: undefined, | ||
projectPrerequisites: undefined | ||
}) | ||
}) | ||
}) | ||
}) |
#!/usr/bin/env node | ||
const yargs = require('yargs') | ||
const { noop } = require('lodash') | ||
const { mainProcess } = require('./cli') | ||
const mainProcess = require('./cli') | ||
yargs | ||
.usage('Usage: $0 <command> [options]') | ||
.command( | ||
'$0 [template]', | ||
'Generate README.md from a template', | ||
command => | ||
command.positional('template', { | ||
desc: 'The name of template you want to use', | ||
default: 'default' | ||
}), | ||
args => mainProcess(args) | ||
) | ||
.command('$0', 'Generate README.md', noop, args => { | ||
const { path: customTemplatePath, yes: useDefaultAnswers } = args | ||
mainProcess({ customTemplatePath, useDefaultAnswers }) | ||
}) | ||
.string('p') | ||
.alias('p', 'path') | ||
.describe('path', 'Path to your own template') | ||
.boolean('yes') | ||
@@ -20,0 +18,0 @@ .alias('y', 'yes') |
@@ -99,2 +99,20 @@ const isNil = require('lodash/isNil') | ||
/** | ||
* Get project author name from package.json | ||
* | ||
* @param packageJson | ||
* @returns {string} authorName | ||
*/ | ||
const getAuthorName = packageJson => { | ||
if (has(packageJson, 'author.name')) { | ||
return get(packageJson, 'author.name', undefined) | ||
} | ||
if (has(packageJson, 'author') && typeof packageJson.author === 'string') { | ||
return get(packageJson, 'author', undefined) | ||
} | ||
return undefined | ||
} | ||
/** | ||
* Get project informations from git and package.json | ||
@@ -109,3 +127,3 @@ */ | ||
const engines = get(packageJson, 'engines', undefined) | ||
const author = get(packageJson, 'author', undefined) | ||
const author = getAuthorName(packageJson) | ||
const version = get(packageJson, 'version', undefined) | ||
@@ -112,0 +130,0 @@ const licenseName = get(packageJson, 'license', undefined) |
@@ -275,3 +275,59 @@ const ora = require('ora') | ||
}) | ||
it('should return correct infos when author is defined as an object', async () => { | ||
const packgeJsonInfos = { | ||
name: 'readme-md-generator', | ||
version: '0.1.3', | ||
description: 'CLI that generates beautiful README.md files.', | ||
author: { | ||
name: 'Franck Abgrall', | ||
email: 'abgrallkefran@gmail.com', | ||
url: '' | ||
}, | ||
license: 'MIT', | ||
homepage: 'https://github.com/kefranabg/readme-md-generator', | ||
repository: { | ||
type: 'git', | ||
url: 'git+https://github.com/kefranabg/readme-md-generator.git' | ||
}, | ||
bugs: { | ||
url: 'https://github.com/kefranabg/readme-md-generator/issues' | ||
}, | ||
engines: { | ||
npm: '>=5.5.0', | ||
node: '>=9.3.0' | ||
} | ||
} | ||
utils.getPackageJson.mockReturnValueOnce(Promise.resolve(packgeJsonInfos)) | ||
childProcess.execSync.mockReturnValue( | ||
'https://github.com/kefranabg/readme-md-generator.git' | ||
) | ||
const projectInfos = await getProjectInfos() | ||
expect(projectInfos).toEqual({ | ||
name: 'readme-md-generator', | ||
description: 'CLI that generates beautiful README.md files.', | ||
version: '0.1.3', | ||
author: 'Franck Abgrall', | ||
repositoryUrl: 'https://github.com/kefranabg/readme-md-generator', | ||
homepage: 'https://github.com/kefranabg/readme-md-generator', | ||
contributingUrl: | ||
'https://github.com/kefranabg/readme-md-generator/issues', | ||
githubUsername: 'kefranabg', | ||
engines: { | ||
npm: '>=5.5.0', | ||
node: '>=9.3.0' | ||
}, | ||
licenseName: 'MIT', | ||
licenseUrl: | ||
'https://github.com/kefranabg/readme-md-generator/blob/master/LICENSE', | ||
documentationUrl: | ||
'https://github.com/kefranabg/readme-md-generator#readme', | ||
isGithubRepos: true, | ||
usage: undefined, | ||
testCommand: undefined | ||
}) | ||
}) | ||
}) | ||
}) |
@@ -0,1 +1,2 @@ | ||
/* eslint-disable global-require */ | ||
module.exports = { | ||
@@ -2,0 +3,0 @@ askProjectName: require('./project-name'), |
const isEmpty = require('lodash/isEmpty') | ||
module.exports = (projectInfos, answersContext) => | ||
isEmpty(answersContext.licenseName) | ||
? undefined | ||
: { | ||
type: 'input', | ||
message: '📝 License url (use empty value to skip)', | ||
name: 'licenseUrl', | ||
default: projectInfos.licenseUrl | ||
} | ||
module.exports = projectInfos => ({ | ||
type: 'input', | ||
message: '📝 License url (use empty value to skip)', | ||
name: 'licenseUrl', | ||
default: projectInfos.licenseUrl, | ||
when: answersContext => !isEmpty(answersContext.licenseName) | ||
}) |
@@ -8,24 +8,40 @@ const askLicenseUrl = require('./license-url') | ||
const projectInfos = { licenseUrl } | ||
const result = askLicenseUrl(projectInfos) | ||
expect(result).toEqual( | ||
expect.objectContaining({ | ||
type: 'input', | ||
message: '📝 License url (use empty value to skip)', | ||
name: 'licenseUrl', | ||
default: licenseUrl | ||
}) | ||
) | ||
}) | ||
it('should show this question if licenseName is defined', () => { | ||
const projectInfos = { | ||
licenseUrl: | ||
'https://github.com/kefranabg/readme-md-generator/blob/master/LICENSE' | ||
} | ||
const answersContext = { licenseName: 'MIT' } | ||
const result = askLicenseUrl(projectInfos, answersContext) | ||
const question = askLicenseUrl(projectInfos) | ||
const result = question.when(answersContext) | ||
expect(result).toEqual({ | ||
type: 'input', | ||
message: '📝 License url (use empty value to skip)', | ||
name: 'licenseUrl', | ||
default: licenseUrl | ||
}) | ||
expect(result).toBe(true) | ||
}) | ||
it('should return undefined', () => { | ||
const licenseUrl = | ||
'https://github.com/kefranabg/readme-md-generator/blob/master/LICENSE' | ||
const projectInfos = { licenseUrl } | ||
const answersContext = { licenseName: '' } | ||
it('should not show this question if licenseName is not defined', () => { | ||
const projectInfos = { | ||
licenseUrl: | ||
'https://github.com/kefranabg/readme-md-generator/blob/master/LICENSE' | ||
} | ||
const answersContext = {} | ||
const result = askLicenseUrl(projectInfos, answersContext) | ||
const question = askLicenseUrl(projectInfos) | ||
const result = question.when(answersContext) | ||
expect(result).toBe(undefined) | ||
expect(result).toBe(false) | ||
}) | ||
}) |
@@ -10,10 +10,12 @@ const isEmpty = require('lodash/isEmpty') | ||
const buildFormattedChoices = engines => | ||
Object.keys(engines).map(key => ({ | ||
name: `${key} ${engines[key]}`, | ||
value: { | ||
name: key, | ||
value: engines[key] | ||
}, | ||
checked: true | ||
})) | ||
isNil(engines) | ||
? null | ||
: Object.keys(engines).map(key => ({ | ||
name: `${key} ${engines[key]}`, | ||
value: { | ||
name: key, | ||
value: engines[key] | ||
}, | ||
checked: true | ||
})) | ||
@@ -28,10 +30,8 @@ /** | ||
module.exports = projectInfos => | ||
hasProjectInfosEngines(projectInfos) | ||
? { | ||
type: 'checkbox', | ||
message: '⚠️ Project prerequisites', | ||
name: 'projectPrerequisites', | ||
choices: buildFormattedChoices(projectInfos.engines) | ||
} | ||
: undefined | ||
module.exports = projectInfos => ({ | ||
type: 'checkbox', | ||
message: '⚠️ Project prerequisites', | ||
name: 'projectPrerequisites', | ||
choices: buildFormattedChoices(projectInfos.engines), | ||
when: () => hasProjectInfosEngines(projectInfos) | ||
}) |
@@ -13,37 +13,53 @@ const askProjectPrerequisites = require('./project-prerequisites') | ||
expect(result).toEqual({ | ||
type: 'checkbox', | ||
message: '⚠️ Project prerequisites', | ||
name: 'projectPrerequisites', | ||
choices: [ | ||
{ | ||
checked: true, | ||
name: 'npm >=5.5.0', | ||
value: { name: 'npm', value: '>=5.5.0' } | ||
}, | ||
{ | ||
checked: true, | ||
name: 'node >= 9.3.0', | ||
value: { name: 'node', value: '>= 9.3.0' } | ||
} | ||
] | ||
}) | ||
expect(result).toEqual( | ||
expect.objectContaining({ | ||
type: 'checkbox', | ||
message: '⚠️ Project prerequisites', | ||
name: 'projectPrerequisites', | ||
choices: [ | ||
{ | ||
checked: true, | ||
name: 'npm >=5.5.0', | ||
value: { name: 'npm', value: '>=5.5.0' } | ||
}, | ||
{ | ||
checked: true, | ||
name: 'node >= 9.3.0', | ||
value: { name: 'node', value: '>= 9.3.0' } | ||
} | ||
] | ||
}) | ||
) | ||
}) | ||
it('should return undefined when engines property is empty object', () => { | ||
const engines = {} | ||
const projectInfos = { engines } | ||
it('should not show the question when engines property is empty object', () => { | ||
const projectInfos = { engines: {} } | ||
const result = askProjectPrerequisites(projectInfos) | ||
const question = askProjectPrerequisites(projectInfos) | ||
const result = question.when() | ||
expect(result).toEqual(undefined) | ||
expect(result).toEqual(false) | ||
}) | ||
it('should return undefined when engines property is not defined', () => { | ||
it('should not show the question when engines property is not defined', () => { | ||
const projectInfos = {} | ||
const result = askProjectPrerequisites(projectInfos) | ||
const question = askProjectPrerequisites(projectInfos) | ||
const result = question.when() | ||
expect(result).toEqual(undefined) | ||
expect(result).toEqual(false) | ||
}) | ||
it('should show the question when engines property is defined and not empty', () => { | ||
const projectInfos = { | ||
engines: { | ||
node: '>=10' | ||
} | ||
} | ||
const question = askProjectPrerequisites(projectInfos) | ||
const result = question.when() | ||
expect(result).toEqual(true) | ||
}) | ||
}) |
const ejs = require('ejs') | ||
const path = require('path') | ||
const ora = require('ora') | ||
@@ -7,3 +6,6 @@ const { promisify } = require('util') | ||
const fs = require('fs') | ||
const { isNil } = require('lodash') | ||
const chooseTemplate = require('./choose-template') | ||
const README_PATH = 'README.md' | ||
@@ -47,13 +49,9 @@ | ||
/** | ||
* Build README content with the given answersContext and templateName | ||
* Build README content with the given context and templatePath | ||
* | ||
* @param {Object} context | ||
* @param {string} templateName | ||
* @param {string} templatePath | ||
*/ | ||
const buildReadmeContent = async (context, templateName) => { | ||
const buildReadmeContent = async (context, templatePath) => { | ||
const currentYear = getYear(new Date()) | ||
const templatePath = path.resolve( | ||
__dirname, | ||
`../templates/${templateName}.md` | ||
) | ||
const template = await getReadmeTemplate(templatePath) | ||
@@ -68,6 +66,41 @@ | ||
/** | ||
* Validate template path | ||
* | ||
* @param {string} templatePath | ||
*/ | ||
const validateReadmeTemplatePath = templatePath => { | ||
const spinner = ora('Resolving README template path').start() | ||
try { | ||
fs.lstatSync(templatePath).isFile() | ||
} catch (err) { | ||
spinner.fail(`The template path '${templatePath}' is not valid.`) | ||
throw err | ||
} | ||
spinner.succeed('README template path resolved') | ||
} | ||
/** | ||
* Get readme template path | ||
* (either a custom template, or a template that user will choose from prompt) | ||
* | ||
* @param {String} customTemplate | ||
*/ | ||
const getReadmeTemplatePath = async (customTemplate, useDefaultAnswers) => { | ||
const templatePath = isNil(customTemplate) | ||
? await chooseTemplate(useDefaultAnswers) | ||
: customTemplate | ||
validateReadmeTemplatePath(templatePath) | ||
return templatePath | ||
} | ||
module.exports = { | ||
writeReadme, | ||
buildReadmeContent, | ||
README_PATH | ||
README_PATH, | ||
getReadmeTemplatePath | ||
} |
const fs = require('fs') | ||
const ora = require('ora') | ||
const path = require('path') | ||
const chooseTemplate = require('./choose-template') | ||
jest.mock('ora') | ||
const defaultTemplatePath = path.resolve(__dirname, '../templates/default.md') | ||
const defaultNoHtmlTemplatePath = path.resolve( | ||
__dirname, | ||
'../templates/default-no-html.md' | ||
) | ||
chooseTemplate.mockReturnValue(defaultTemplatePath) | ||
const { writeReadme, buildReadmeContent, README_PATH } = require('./readme') | ||
const { | ||
writeReadme, | ||
buildReadmeContent, | ||
README_PATH, | ||
getReadmeTemplatePath | ||
} = require('./readme') | ||
@@ -26,3 +38,3 @@ describe('readme', () => { | ||
const readmeContent = 'content' | ||
fs.writeFile = jest.fn((path, content, cb) => cb(null, 'done')) | ||
fs.writeFile = jest.fn((_, __, cb) => cb(null, 'done')) | ||
@@ -56,3 +68,3 @@ await writeReadme(readmeContent) | ||
const readmeContent = 'content' | ||
fs.writeFile = jest.fn((path, content, cb) => cb(null, 'done')) | ||
fs.writeFile = jest.fn((_, __, cb) => cb(null, 'done')) | ||
@@ -68,3 +80,2 @@ await writeReadme(readmeContent) | ||
describe('buildReadmeContent', () => { | ||
const templateName = 'default' | ||
const context = { | ||
@@ -104,3 +115,3 @@ isGithubRepos: true, | ||
it('should call ora with correct parameters in success case', async () => { | ||
await buildReadmeContent(context, templateName) | ||
await buildReadmeContent(context, defaultTemplatePath) | ||
@@ -113,4 +124,4 @@ expect(ora).toHaveBeenCalledTimes(1) | ||
it('should return readme template content', async () => { | ||
const result = await buildReadmeContent(context, templateName) | ||
it('should return readme default template content', async () => { | ||
const result = await buildReadmeContent(context, defaultTemplatePath) | ||
@@ -120,2 +131,11 @@ expect(result).toMatchSnapshot() | ||
it('should return readme default template no html content', async () => { | ||
const result = await buildReadmeContent( | ||
context, | ||
defaultNoHtmlTemplatePath | ||
) | ||
expect(result).toMatchSnapshot() | ||
}) | ||
it('should call ora with correct parameters in fail case', async () => { | ||
@@ -127,3 +147,3 @@ fs.readFile = jest.fn(() => { | ||
try { | ||
await buildReadmeContent(context, templateName) | ||
await buildReadmeContent(context, defaultTemplatePath) | ||
// eslint-disable-next-line no-empty | ||
@@ -138,2 +158,61 @@ } catch (err) {} | ||
}) | ||
describe('getReadmeTemplatePath', () => { | ||
it('should return template that user has selected', async () => { | ||
const useDefaultAnswers = false | ||
const actualResult = await getReadmeTemplatePath( | ||
undefined, | ||
useDefaultAnswers | ||
) | ||
expect(actualResult).toEqual(defaultTemplatePath) | ||
expect(chooseTemplate).toHaveBeenNthCalledWith(1, useDefaultAnswers) | ||
}) | ||
it('should return custom template path if customTemplatePath is defined', async () => { | ||
const customTemplatePath = defaultTemplatePath | ||
const actualResult = await getReadmeTemplatePath( | ||
customTemplatePath, | ||
false | ||
) | ||
expect(actualResult).toEqual(customTemplatePath) | ||
expect(chooseTemplate).not.toHaveBeenCalled() | ||
}) | ||
it('should throw an error if customTemplate is defined but invalid', () => { | ||
const wrongPath = 'wrong path' | ||
expect(getReadmeTemplatePath(wrongPath, false)).rejects.toThrow() | ||
}) | ||
it('should call ora with correct parameters in fail case', async () => { | ||
const wrongPath = 'wrong path' | ||
try { | ||
await getReadmeTemplatePath(wrongPath, false) | ||
// eslint-disable-next-line no-empty | ||
} catch (err) {} | ||
expect(ora).toHaveBeenNthCalledWith(1, 'Resolving README template path') | ||
expect(fail).toHaveBeenNthCalledWith( | ||
1, | ||
"The template path 'wrong path' is not valid." | ||
) | ||
}) | ||
it('should call ora with correct parameters in success case', async () => { | ||
await getReadmeTemplatePath(defaultTemplatePath, false) | ||
expect(ora).toHaveBeenNthCalledWith(1, 'Resolving README template path') | ||
expect(succeed).toHaveBeenNthCalledWith( | ||
1, | ||
'README template path resolved' | ||
) | ||
}) | ||
}) | ||
}) | ||
jest.mock('ora') | ||
jest.mock('./choose-template') |
@@ -27,6 +27,3 @@ const loadJsonFile = require('load-json-file') | ||
*/ | ||
const getPackageJsonName = (packageJson = {}) => { | ||
return packageJson.name || undefined | ||
} | ||
const getPackageJsonName = (packageJson = {}) => packageJson.name || undefined | ||
/** | ||
@@ -69,2 +66,36 @@ * Get git repository name | ||
/** | ||
* Get the default answer depending on the question type | ||
* | ||
* @param {Object} question | ||
*/ | ||
const getDefaultAnswer = (question, answersContext) => { | ||
if (question.when && !question.when(answersContext)) return undefined | ||
switch (question.type) { | ||
case 'input': | ||
return question.default || '' | ||
case 'checkbox': | ||
return question.choices | ||
.filter(choice => choice.checked) | ||
.map(choice => choice.value) | ||
default: | ||
return undefined | ||
} | ||
} | ||
/** | ||
* Get default question's answers | ||
* | ||
* @param {Array} questions | ||
*/ | ||
const getDefaultAnswers = questions => | ||
questions.reduce( | ||
(answersContext, question) => ({ | ||
...answersContext, | ||
[question.name]: getDefaultAnswer(question, answersContext) | ||
}), | ||
{} | ||
) | ||
module.exports = { | ||
@@ -75,3 +106,5 @@ getPackageJson, | ||
END_MSG, | ||
BOXEN_CONFIG | ||
BOXEN_CONFIG, | ||
getDefaultAnswers, | ||
getDefaultAnswer | ||
} |
@@ -5,2 +5,3 @@ const loadJsonFile = require('load-json-file') | ||
const getReposName = require('git-repo-name') | ||
const { isNil } = require('lodash') | ||
@@ -15,3 +16,5 @@ const realPathBasename = path.basename | ||
END_MSG, | ||
BOXEN_CONFIG | ||
BOXEN_CONFIG, | ||
getDefaultAnswer, | ||
getDefaultAnswers | ||
} = require('./utils') | ||
@@ -115,2 +118,84 @@ | ||
}) | ||
describe('getDefaultAnswer', () => { | ||
it('should handle input prompts correctly', () => { | ||
const question = { type: 'input', default: 'default' } | ||
const result = getDefaultAnswer(question) | ||
expect(result).toEqual(question.default) | ||
}) | ||
it('should handle choices prompts correctly', () => { | ||
const value = { name: 'name', value: 'value' } | ||
const question = { | ||
type: 'checkbox', | ||
choices: [{ value, checked: true }, { checked: false }] | ||
} | ||
const result = getDefaultAnswer(question) | ||
expect(result).toEqual([value]) | ||
}) | ||
it('should return empty string for non-defaulted fields', () => { | ||
const question = { type: 'input' } | ||
const result = getDefaultAnswer(question) | ||
expect(result).toEqual('') | ||
}) | ||
it('should return undefined for invalid types', () => { | ||
const question = { type: 'invalid' } | ||
const result = getDefaultAnswer(question) | ||
expect(result).toEqual(undefined) | ||
}) | ||
it('should return undefined if when function is defined and return false', () => { | ||
const answersContext = {} | ||
const question = { | ||
type: 'input', | ||
when: ansewersContext => !isNil(ansewersContext.licenseUrl) | ||
} | ||
const result = getDefaultAnswer(question, answersContext) | ||
expect(result).toEqual(undefined) | ||
}) | ||
it('should return correct value if when function is defined and return true', () => { | ||
const answersContext = { licenseUrl: 'licenseUrl' } | ||
const question = { | ||
type: 'input', | ||
default: 'default', | ||
when: ansewersContext => !isNil(ansewersContext.licenseUrl) | ||
} | ||
const result = getDefaultAnswer(question, answersContext) | ||
expect(result).toEqual('default') | ||
}) | ||
}) | ||
describe('getDefaultAnswers', () => { | ||
it('should return default answers from questions', () => { | ||
const questions = [ | ||
{ | ||
type: 'input', | ||
name: 'questionOne', | ||
default: 'answer 1' | ||
}, | ||
{ | ||
type: 'input', | ||
name: 'questionTwo', | ||
default: 'answer 2' | ||
} | ||
] | ||
const result = getDefaultAnswers(questions) | ||
expect(result).toEqual({ | ||
questionOne: 'answer 1', | ||
questionTwo: 'answer 2' | ||
}) | ||
}) | ||
}) | ||
}) |
@@ -40,3 +40,3 @@ <h1 align="center">Welcome to <%= projectName %> 👋</h1> | ||
<% } -%> | ||
<% if (projectPrerequisites) { -%> | ||
<% if (projectPrerequisites && projectPrerequisites.length) { -%> | ||
@@ -43,0 +43,0 @@ ## Prerequisites |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Copyleft License
License(Experimental) Copyleft license information was found
Found 1 instance in 1 package
SPDX disjunction
LicenseSPDX disjunction for an artifact's license information
Found 1 instance in 1 package
Mixed license
License(Experimental) Package contains multiple licenses.
Found 1 instance in 1 package
Non-permissive License
License(Experimental) A license not known to be considered permissive was found
Found 1 instance in 1 package
Copyleft License
License(Experimental) Copyleft license information was found
Found 1 instance in 1 package
SPDX disjunction
LicenseSPDX disjunction for an artifact's license information
Found 1 instance in 1 package
Mixed license
License(Experimental) Package contains multiple licenses.
Found 1 instance in 1 package
Non-permissive License
License(Experimental) A license not known to be considered permissive was found
Found 1 instance in 1 package
87843
65
1730
133
+ Addedinquirer@6.4.1(transitive)
- Removedinquirer@6.3.1(transitive)
Updatedinquirer@~6.4.1