readme-md-generator
Advanced tools
Comparing version
@@ -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
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
87843
28.39%65
12.07%1730
24.37%133
10.83%+ Added
- Removed
Updated