diff --git a/src/project-infos.js b/src/project-infos.js index 9bc3850..69c0368 100644 --- a/src/project-infos.js +++ b/src/project-infos.js @@ -7,7 +7,8 @@ const { execSync } = require('child_process') const { getPackageJson, getProjectName, - getAuthorWebsiteFromGithubAPI + getAuthorWebsiteFromGithubAPI, + getPackageManagerFromLockFile } = require('./utils') const GITHUB_URL = 'https://github.com/' @@ -128,6 +129,9 @@ const getProjectInfos = async () => { const packageJson = await getPackageJson() const isJSProject = !!packageJson + const packageManager = isJSProject + ? getPackageManagerFromLockFile() + : undefined const name = getProjectName(packageJson) const description = get(packageJson, 'description', undefined) const engines = get(packageJson, 'engines', undefined) @@ -135,10 +139,8 @@ const getProjectInfos = async () => { const version = get(packageJson, 'version', undefined) const licenseName = get(packageJson, 'license', undefined) const homepage = get(packageJson, 'homepage', undefined) - const usage = has(packageJson, 'scripts.start') ? 'npm run start' : undefined - const testCommand = has(packageJson, 'scripts.test') - ? 'npm run test' - : undefined + const hasStartCommand = has(packageJson, 'scripts.start') + const hasTestCommand = has(packageJson, 'scripts.test') const repositoryUrl = await getReposUrl(packageJson) const issuesUrl = await getReposIssuesUrl(packageJson) const isGithubRepos = isGithubRepository(repositoryUrl) @@ -176,9 +178,10 @@ const getProjectInfos = async () => { licenseUrl, documentationUrl, isGithubRepos, - usage, - testCommand, - isJSProject + hasStartCommand, + hasTestCommand, + isJSProject, + packageManager } } diff --git a/src/project-infos.spec.js b/src/project-infos.spec.js index 8ff2b6c..2a3b205 100644 --- a/src/project-infos.spec.js +++ b/src/project-infos.spec.js @@ -11,7 +11,10 @@ jest.mock('child_process', () => ({ jest.mock('./utils', () => ({ getPackageJson: jest.fn(), getProjectName: jest.fn(() => 'readme-md-generator'), - getAuthorWebsiteFromGithubAPI: jest.fn(() => 'https://www.franck-abgrall.me/') + getAuthorWebsiteFromGithubAPI: jest.fn( + () => 'https://www.franck-abgrall.me/' + ), + getPackageManagerFromLockFile: jest.fn(() => 'yarn') })) const succeed = jest.fn() @@ -85,8 +88,9 @@ describe('projectInfos', () => { isGithubRepos: true, isJSProject: true, issuesUrl: 'https://github.com/kefranabg/readme-md-generator/issues', - usage: undefined, - testCommand: undefined + hasStartCommand: false, + hasTestCommand: false, + packageManager: 'yarn' }) }) @@ -138,8 +142,9 @@ describe('projectInfos', () => { isGithubRepos: false, isJSProject: true, issuesUrl: 'https://gitlab.com/kefranabg/readme-md-generator/issues', - usage: undefined, - testCommand: undefined + hasStartCommand: false, + hasTestCommand: false, + packageManager: 'yarn' }) }) @@ -171,8 +176,8 @@ describe('projectInfos', () => { isGithubRepos: true, isJSProject: false, issuesUrl: 'https://github.com/kefranabg/readme-md-generator/issues', - usage: undefined, - testCommand: undefined + hasStartCommand: false, + hasTestCommand: false }) }) @@ -202,8 +207,8 @@ describe('projectInfos', () => { isGithubRepos: false, isJSProject: false, issuesUrl: 'https://gitlab.com/kefranabg/readme-md-generator/issues', - usage: undefined, - testCommand: undefined + hasStartCommand: false, + hasTestCommand: false }) }) @@ -231,8 +236,9 @@ describe('projectInfos', () => { documentationUrl: undefined, isGithubRepos: false, isJSProject: false, - usage: undefined, - testCommand: undefined + testCommand: undefined, + hasStartCommand: false, + hasTestCommand: false }) }) @@ -286,8 +292,9 @@ describe('projectInfos', () => { isGithubRepos: true, isJSProject: true, issuesUrl: 'https://github.com/kefranabg/readme-md-generator/issues', - usage: undefined, - testCommand: undefined + hasStartCommand: false, + hasTestCommand: false, + packageManager: 'yarn' }) }) @@ -345,8 +352,70 @@ describe('projectInfos', () => { isGithubRepos: true, isJSProject: true, issuesUrl: 'https://github.com/kefranabg/readme-md-generator/issues', - usage: undefined, - testCommand: undefined + hasStartCommand: false, + hasTestCommand: false, + packageManager: 'yarn' + }) + }) + + it('should return correct infos when lock file is found', async () => { + const packgeJsonInfos = { + name: 'readme-md-generator', + version: '0.1.3', + description: 'CLI that generates beautiful README.md files.', + author: 'Franck Abgrall', + 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' + }, + scripts: { + start: 'node src/index.js', + test: 'jest' + } + } + utils.getPackageJson.mockReturnValueOnce(Promise.resolve(packgeJsonInfos)) + utils.getPackageManagerFromLockFile.mockReturnValueOnce('yarn') + 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/blob/master/CONTRIBUTING.md', + authorWebsite: 'https://www.franck-abgrall.me/', + 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, + isJSProject: true, + issuesUrl: 'https://github.com/kefranabg/readme-md-generator/issues', + hasStartCommand: true, + hasTestCommand: true, + packageManager: 'yarn' }) }) }) diff --git a/src/questions/index.js b/src/questions/index.js index cf539ad..65af034 100644 --- a/src/questions/index.js +++ b/src/questions/index.js @@ -3,6 +3,7 @@ module.exports = { askProjectName: require('./project-name'), askProjectVersion: require('./project-version'), askProjectDescription: require('./project-description'), + askPackageManager: require('./package-manager'), askProjectHomepage: require('./project-homepage'), askProjectDemoUrl: require('./project-demo-url'), askProjectDocumentationUrl: require('./project-documentation-url'), diff --git a/src/questions/index.spec.js b/src/questions/index.spec.js index 3b533d0..266af23 100644 --- a/src/questions/index.spec.js +++ b/src/questions/index.spec.js @@ -8,6 +8,7 @@ describe('questions', () => { 'askProjectName', 'askProjectVersion', 'askProjectDescription', + 'askPackageManager', 'askProjectHomepage', 'askProjectDemoUrl', 'askProjectDocumentationUrl', diff --git a/src/questions/install-command.js b/src/questions/install-command.js index 4e0ce7a..0eeb1d9 100644 --- a/src/questions/install-command.js +++ b/src/questions/install-command.js @@ -1,6 +1,11 @@ +const isNil = require('lodash/isNil') + module.exports = projectInfos => ({ type: 'input', message: '📦 Install command (use empty value to skip)', name: 'installCommand', - default: projectInfos.isJSProject ? 'npm install' : undefined + default: answers => { + const packageManager = answers.packageManager || projectInfos.packageManager + return isNil(packageManager) ? undefined : `${packageManager} install` + } }) diff --git a/src/questions/install-command.spec.js b/src/questions/install-command.spec.js index b8b5507..4e3f16e 100644 --- a/src/questions/install-command.spec.js +++ b/src/questions/install-command.spec.js @@ -1,27 +1,34 @@ const askInstallCommand = require('./install-command') describe('askInstallCommand', () => { - it('should return correct question format when project lang is js', () => { - const projectInfos = { isJSProject: true } - const result = askInstallCommand(projectInfos) + it('should return correct question format', () => { + const result = askInstallCommand() + expect(result).toEqual( + expect.objectContaining({ + type: 'input', + message: '📦 Install command (use empty value to skip)', + name: 'installCommand' + }) + ) + }) + + it('should return undefined default answer when package manager is not defined', () => { + const projectInfos = {} - expect(result).toEqual({ - type: 'input', - message: '📦 Install command (use empty value to skip)', - name: 'installCommand', - default: 'npm install' + const result = askInstallCommand(projectInfos).default({ + packageManager: undefined }) + + expect(result).toBeUndefined() }) - it('should return correct question format when project lang is not js', () => { - const projectInfos = { isJSProject: false } - const result = askInstallCommand(projectInfos) + it('should return correct default answer when package manager is defined', () => { + const projectInfos = {} - expect(result).toEqual({ - type: 'input', - message: '📦 Install command (use empty value to skip)', - name: 'installCommand', - default: undefined + const result = askInstallCommand(projectInfos).default({ + packageManager: 'yarn' }) + + expect(result).toEqual('yarn install') }) }) diff --git a/src/questions/package-manager.js b/src/questions/package-manager.js new file mode 100644 index 0000000..2466243 --- /dev/null +++ b/src/questions/package-manager.js @@ -0,0 +1,18 @@ +const isEmpty = require('lodash/isEmpty') + +module.exports = projectInfos => ({ + type: 'list', + message: '📦 Choose Package Manager ', + name: 'packageManager', + choices: [ + { + name: 'npm', + value: 'npm' + }, + { + name: 'yarn', + value: 'yarn' + } + ], + when: () => projectInfos.isJSProject && isEmpty(projectInfos.packageManager) +}) diff --git a/src/questions/package-manager.spec.js b/src/questions/package-manager.spec.js new file mode 100644 index 0000000..6b0c677 --- /dev/null +++ b/src/questions/package-manager.spec.js @@ -0,0 +1,47 @@ +const askPackageManager = require('./package-manager') + +const expectedQuestion = { + type: 'list', + message: '📦 Choose Package Manager ', + name: 'packageManager', + choices: [ + { + name: 'npm', + value: 'npm' + }, + { + name: 'yarn', + value: 'yarn' + } + ] +} + +describe('askPackageManager', () => { + it('should return correct question format when package manager is undefined', () => { + const projectInfos = { packageManager: undefined } + const result = askPackageManager(projectInfos) + + expect(result).toEqual(expect.objectContaining(expectedQuestion)) + }) + + it('should not show question for a non JS Project', () => { + const projectInfos = { isJSProject: false, packageManager: undefined } + const result = askPackageManager(projectInfos).when(projectInfos) + + expect(result).toBe(false) + }) + + it('should not show question when package manager has already been detected', () => { + const projectInfos = { isJSProject: true, packageManager: 'yarn' } + const result = askPackageManager(projectInfos).when(projectInfos) + + expect(result).toBe(false) + }) + + it('should show question when package manager is undefined and if project is JS', () => { + const projectInfos = { isJSProject: true, packageManager: undefined } + const result = askPackageManager(projectInfos).when(projectInfos) + + expect(result).toBe(true) + }) +}) diff --git a/src/questions/test-command.js b/src/questions/test-command.js index e49b83b..59c3755 100644 --- a/src/questions/test-command.js +++ b/src/questions/test-command.js @@ -1,6 +1,13 @@ +const isNil = require('lodash/isNil') + module.exports = projectInfos => ({ type: 'input', message: '✅ Test command (use empty value to skip)', name: 'testCommand', - default: projectInfos.testCommand + default: answers => { + const packageManager = answers.packageManager || projectInfos.packageManager + return projectInfos.hasTestCommand && !isNil(packageManager) + ? `${packageManager} run test` + : undefined + } }) diff --git a/src/questions/test-command.spec.js b/src/questions/test-command.spec.js index ed749a8..308c26e 100644 --- a/src/questions/test-command.spec.js +++ b/src/questions/test-command.spec.js @@ -2,16 +2,43 @@ const askTestCommand = require('./test-command') describe('askTestCommand', () => { it('should return correct question format', () => { - const testCommand = 'npm run test' - const projectInfos = { testCommand } + const result = askTestCommand() + expect(result).toEqual( + expect.objectContaining({ + type: 'input', + message: '✅ Test command (use empty value to skip)', + name: 'testCommand' + }) + ) + }) + + it('should return undefined default answer when package manager does not exists', () => { + const projectInfos = { hasTestCommand: true } + + const result = askTestCommand(projectInfos).default({ + packageManager: undefined + }) + + expect(result).toBeUndefined() + }) - const result = askTestCommand(projectInfos) + it('should return undefined default answer when test command does not exists', () => { + const projectInfos = { hasTestCommand: false } - expect(result).toEqual({ - type: 'input', - message: '✅ Test command (use empty value to skip)', - name: 'testCommand', - default: testCommand + const result = askTestCommand(projectInfos).default({ + packageManager: 'yarn' }) + + expect(result).toBeUndefined() + }) + + it('should return correct default answer when start command and package manager exists', () => { + const projectInfos = { hasTestCommand: true } + + const result = askTestCommand(projectInfos).default({ + packageManager: 'yarn' + }) + + expect(result).toEqual('yarn run test') }) }) diff --git a/src/questions/usage.js b/src/questions/usage.js index 374bcb1..8fb615e 100644 --- a/src/questions/usage.js +++ b/src/questions/usage.js @@ -1,6 +1,13 @@ +const isNil = require('lodash/isNil') + module.exports = projectInfos => ({ type: 'input', message: '🚀 Usage command or instruction (use empty value to skip)', name: 'usage', - default: projectInfos.usage + default: answers => { + const packageManager = answers.packageManager || projectInfos.packageManager + return projectInfos.hasStartCommand && !isNil(packageManager) + ? `${packageManager} run start` + : undefined + } }) diff --git a/src/questions/usage.spec.js b/src/questions/usage.spec.js index b88c025..12a6fa3 100644 --- a/src/questions/usage.spec.js +++ b/src/questions/usage.spec.js @@ -2,16 +2,44 @@ const askUsage = require('./usage') describe('askUsage', () => { it('should return correct question format', () => { - const usage = 'npm start' - const projectInfos = { usage } + const result = askUsage() - const result = askUsage(projectInfos) + expect(result).toEqual( + expect.objectContaining({ + type: 'input', + message: '🚀 Usage command or instruction (use empty value to skip)', + name: 'usage' + }) + ) + }) + + it('should return undefined default answer when package manager does not exists', () => { + const projectInfos = { hasStartCommand: true } + + const result = askUsage(projectInfos).default({ + packageManager: undefined + }) + + expect(result).toBeUndefined() + }) + + it('should return undefined default answer when start command does not exists', () => { + const projectInfos = { hasStartCommand: false } - expect(result).toEqual({ - type: 'input', - message: '🚀 Usage command or instruction (use empty value to skip)', - name: 'usage', - default: usage + const result = askUsage(projectInfos).default({ + packageManager: 'yarn' }) + + expect(result).toBeUndefined() + }) + + it('should return correct default answer when start command and packageManager exists', () => { + const projectInfos = { hasStartCommand: true } + + const result = askUsage(projectInfos).default({ + packageManager: 'yarn' + }) + + expect(result).toEqual('yarn run start') }) }) diff --git a/src/utils.js b/src/utils.js index 6b733b2..f98fbe8 100644 --- a/src/utils.js +++ b/src/utils.js @@ -5,6 +5,7 @@ const boxen = require('boxen') const path = require('path') const getReposName = require('git-repo-name') const fetch = require('node-fetch') +const fs = require('fs') const escapeMarkdown = require('markdown-escape') const { execSync } = require('child_process') @@ -151,6 +152,35 @@ const getAuthorWebsiteFromGithubAPI = async githubUsername => { } } +/** + * Returns a boolean whether a file exists or not + * + * @param {String} filepath + * @returns {Boolean} + */ +const doesFileExist = filepath => { + try { + return fs.existsSync(filepath) + } catch (err) { + return false + } +} + +/** + * Returns the package manager from the lock file + * + * @returns {String} packageManger or undefined + */ +const getPackageManagerFromLockFile = () => { + const packageLockExists = doesFileExist('package-lock.json') + const yarnLockExists = doesFileExist('yarn.lock') + + if (packageLockExists && yarnLockExists) return undefined + if (packageLockExists) return 'npm' + if (yarnLockExists) return 'yarn' + return undefined +} + module.exports = { getPackageJson, showEndMessage, @@ -161,5 +191,7 @@ module.exports = { getDefaultAnswer, cleanSocialNetworkUsername, isProjectAvailableOnNpm, - getAuthorWebsiteFromGithubAPI + getAuthorWebsiteFromGithubAPI, + getPackageManagerFromLockFile, + doesFileExist } diff --git a/src/utils.spec.js b/src/utils.spec.js index 1e5e9bd..2ceb187 100644 --- a/src/utils.spec.js +++ b/src/utils.spec.js @@ -3,6 +3,7 @@ const boxen = require('boxen') const path = require('path') const getReposName = require('git-repo-name') const fetch = require('node-fetch') +const fs = require('fs') const { isNil } = require('lodash') const realPathBasename = path.basename @@ -18,12 +19,15 @@ const { getDefaultAnswers, cleanSocialNetworkUsername, isProjectAvailableOnNpm, - getAuthorWebsiteFromGithubAPI + getAuthorWebsiteFromGithubAPI, + doesFileExist, + getPackageManagerFromLockFile } = require('./utils') jest.mock('load-json-file') jest.mock('boxen') jest.mock('node-fetch') +jest.mock('fs') describe('utils', () => { describe('getPackageJson', () => { @@ -46,7 +50,7 @@ describe('utils', () => { const result = await getPackageJson() - expect(result).toBe(undefined) + expect(result).toBeUndefined() }) }) @@ -85,7 +89,7 @@ describe('utils', () => { it('should return package.json name prop when defined', () => { const packageJson = { name: projectName } - getReposName.sync.mockReturnValue('readme-md-generator') + getReposName.sync.mockReturnValueOnce('readme-md-generator') const result = getProjectName(packageJson) @@ -96,7 +100,7 @@ describe('utils', () => { it('should return git repos when package.json it is not defined', () => { const packageJson = undefined - getReposName.sync.mockReturnValue('readme-md-generator') + getReposName.sync.mockReturnValueOnce('readme-md-generator') const result = getProjectName(packageJson) @@ -264,4 +268,61 @@ describe('utils', () => { expect(authorWebsite).toEqual(undefined) }) }) + + describe('doesFileExist', () => { + it('should return true when file exists for a given path', () => { + fs.existsSync.mockReturnValueOnce(true) + expect(doesFileExist('./file-path')).toBe(true) + }) + + it('should return false when file does not exist for a given path', () => { + fs.existsSync.mockReturnValueOnce(false) + expect(doesFileExist('./file-path')).toBe(false) + }) + + it('should return false if fs.existsSync throws an error', () => { + fs.existsSync.mockImplementationOnce(() => { + throw new Error('ERROR') + }) + expect(doesFileExist('./file-path')).toBe(false) + }) + }) + + describe('getPackageManagerFromLockFile', () => { + it('should return npm if only package-lock.json exists', () => { + fs.existsSync.mockImplementation( + filePath => filePath === 'package-lock.json' + ) + + const result = getPackageManagerFromLockFile() + + expect(result).toEqual('npm') + }) + + it('should return yarn if only yarn.lock exists', () => { + fs.existsSync.mockImplementation(filePath => filePath === 'yarn.lock') + + const result = getPackageManagerFromLockFile() + + expect(result).toEqual('yarn') + }) + + it('should return undefined if only yarn.lock and package-lock.json exists', () => { + fs.existsSync.mockImplementation( + filePath => filePath === 'yarn.lock' || filePath === 'package-lock.json' + ) + + const result = getPackageManagerFromLockFile() + + expect(result).toBeUndefined() + }) + + it('should return undefined if only no lock file exists', () => { + fs.existsSync.mockImplementation(() => false) + + const result = getPackageManagerFromLockFile() + + expect(result).toBeUndefined() + }) + }) })