├── .editorconfig ├── .eslintignore ├── .gitattributes ├── .github └── workflows │ ├── ci.yml │ ├── codeql.yml │ ├── dependency-review.yml │ └── scorecards.yml ├── .gitignore ├── .prettierrc.yml ├── LICENSE ├── README.md ├── __tests__ ├── app.js ├── boilerplate.js ├── cli.js ├── editorconfig.js ├── eslint.js ├── git.js └── readme.js ├── generators ├── app │ └── index.js ├── boilerplate │ ├── index.js │ └── templates │ │ └── index.js ├── cli │ ├── index.js │ └── templates │ │ └── cli.js ├── editorconfig │ ├── index.js │ └── templates │ │ └── editorconfig ├── eslint │ ├── index.js │ └── templates │ │ └── eslintignore ├── git │ ├── index.js │ └── templates │ │ ├── gitattributes │ │ └── gitignore └── readme │ ├── index.js │ └── templates │ └── README.md ├── index.js ├── package-lock.json └── package.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.md] 11 | trim_trailing_whitespace = false 12 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | coverage 2 | generators/*/templates 3 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | permissions: 12 | contents: read 13 | 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | 18 | strategy: 19 | matrix: 20 | # TODO: We need to re-introduce Node 6 and 7 once we have a way to run tests on them. 21 | node-version: [8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23] 22 | continue-on-error: true 23 | steps: 24 | - name: Checkout repository 25 | uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 26 | 27 | - name: Set up Node.js ${{ matrix.node-version }} 28 | uses: actions/setup-node@1a4442cacd436585916779262731d5b162bc6ec7 # v3.8.2 29 | with: 30 | node-version: ${{ matrix.node-version }} 31 | 32 | - name: Install dependencies 33 | run: npm install 34 | 35 | - name: Run lint 36 | run: npm run pretest 37 | 38 | - name: Run tests 39 | run: npm test -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: ["main"] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: ["main"] 20 | schedule: 21 | - cron: "0 0 * * 1" 22 | 23 | permissions: 24 | contents: read 25 | 26 | jobs: 27 | analyze: 28 | name: Analyze 29 | runs-on: ubuntu-latest 30 | permissions: 31 | actions: read 32 | contents: read 33 | security-events: write 34 | 35 | strategy: 36 | fail-fast: false 37 | matrix: 38 | language: ["javascript"] 39 | # CodeQL supports [ $supported-codeql-languages ] 40 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 41 | 42 | steps: 43 | - name: Checkout repository 44 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 45 | 46 | # Initializes the CodeQL tools for scanning. 47 | - name: Initialize CodeQL 48 | uses: github/codeql-action/init@9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0 # v3.28.9 49 | with: 50 | languages: ${{ matrix.language }} 51 | # If you wish to specify custom queries, you can do so here or in a config file. 52 | # By default, queries listed here will override any specified in a config file. 53 | # Prefix the list here with "+" to use these queries and those in the config file. 54 | 55 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 56 | # If this step fails, then you should remove it and run the build manually (see below) 57 | - name: Autobuild 58 | uses: github/codeql-action/autobuild@9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0 # v3.28.9 59 | 60 | # ℹ️ Command-line programs to run using the OS shell. 61 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 62 | 63 | # If the Autobuild fails above, remove it and uncomment the following three lines. 64 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 65 | 66 | # - run: | 67 | # echo "Run, Build Application using script" 68 | # ./location_of_script_within_repo/buildscript.sh 69 | 70 | - name: Perform CodeQL Analysis 71 | uses: github/codeql-action/analyze@9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0 # v3.28.9 72 | with: 73 | category: "/language:${{matrix.language}}" 74 | -------------------------------------------------------------------------------- /.github/workflows/dependency-review.yml: -------------------------------------------------------------------------------- 1 | # Dependency Review Action 2 | # 3 | # This Action will scan dependency manifest files that change as part of a Pull Request, 4 | # surfacing known-vulnerable versions of the packages declared or updated in the PR. 5 | # Once installed, if the workflow run is marked as required, 6 | # PRs introducing known-vulnerable packages will be blocked from merging. 7 | # 8 | # Source repository: https://github.com/actions/dependency-review-action 9 | name: 'Dependency Review' 10 | on: [pull_request] 11 | 12 | permissions: 13 | contents: read 14 | 15 | jobs: 16 | dependency-review: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: 'Checkout Repository' 20 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 21 | - name: 'Dependency Review' 22 | uses: actions/dependency-review-action@3b139cfc5fae8b618d3eae3675e383bb1769c019 # v4.5.0 23 | -------------------------------------------------------------------------------- /.github/workflows/scorecards.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. They are provided 2 | # by a third-party and are governed by separate terms of service, privacy 3 | # policy, and support documentation. 4 | 5 | name: Scorecard supply-chain security 6 | on: 7 | # For Branch-Protection check. Only the default branch is supported. See 8 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection 9 | branch_protection_rule: 10 | # To guarantee Maintained check is occasionally updated. See 11 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained 12 | schedule: 13 | - cron: '20 7 * * 2' 14 | push: 15 | branches: ["main"] 16 | 17 | # Declare default permissions as read only. 18 | permissions: read-all 19 | 20 | jobs: 21 | analysis: 22 | name: Scorecard analysis 23 | runs-on: ubuntu-latest 24 | permissions: 25 | # Needed to upload the results to code-scanning dashboard. 26 | security-events: write 27 | # Needed to publish results and get a badge (see publish_results below). 28 | id-token: write 29 | contents: read 30 | actions: read 31 | # To allow GraphQL ListCommits to work 32 | issues: read 33 | pull-requests: read 34 | # To detect SAST tools 35 | checks: read 36 | 37 | steps: 38 | - name: "Checkout code" 39 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 40 | with: 41 | persist-credentials: false 42 | 43 | - name: "Run analysis" 44 | uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0 45 | with: 46 | results_file: results.sarif 47 | results_format: sarif 48 | # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: 49 | # - you want to enable the Branch-Protection check on a *public* repository, or 50 | # - you are installing Scorecards on a *private* repository 51 | # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat. 52 | # repo_token: ${{ secrets.SCORECARD_TOKEN }} 53 | 54 | # Public repositories: 55 | # - Publish results to OpenSSF REST API for easy access by consumers 56 | # - Allows the repository to include the Scorecard badge. 57 | # - See https://github.com/ossf/scorecard-action#publishing-results. 58 | # For private repositories: 59 | # - `publish_results` will always be set to `false`, regardless 60 | # of the value entered here. 61 | publish_results: true 62 | 63 | # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF 64 | # format to the repository Actions tab. 65 | - name: "Upload artifact" 66 | uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 67 | with: 68 | name: SARIF file 69 | path: results.sarif 70 | retention-days: 5 71 | 72 | # Upload the results to GitHub's code scanning dashboard. 73 | - name: "Upload to code-scanning" 74 | uses: github/codeql-action/upload-sarif@9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0 # v3.28.9 75 | with: 76 | sarif_file: results.sarif 77 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | -------------------------------------------------------------------------------- /.prettierrc.yml: -------------------------------------------------------------------------------- 1 | singleQuote: true 2 | printWidth: 90 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2015 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Node Generator [![CI](https://github.com/yeoman/generator-node/actions/workflows/ci.yml/badge.svg)](https://github.com/yeoman/generator-node/actions/workflows/ci.yml) [![Gitter](https://img.shields.io/badge/Gitter-Join_the_Yeoman_chat_%E2%86%92-00d06f.svg)](https://gitter.im/yeoman/yeoman) [![OpenCollective](https://opencollective.com/yeoman/backers/badge.svg)](https://opencollective.com/yeoman#support) 2 | 3 | `generator-node` creates a base template to start a new Node.js module. 4 | 5 | It is also easily composed into your own generators so you can only target your efforts at your generator's specific features. 6 | 7 | 8 | ## Install 9 | 10 | ``` 11 | $ npm install --global generator-node 12 | ``` 13 | 14 | 15 | ## Usage 16 | 17 | ``` 18 | $ yo node 19 | ``` 20 | 21 | *Note that this template will generate files in the current directory, so be sure to change to a new directory first if you don't want to overwrite existing files.* 22 | 23 | That'll generate a project with all the common tools setup. This includes: 24 | 25 | - Filled `package.json` file 26 | - [jest](https://facebook.github.io/jest/) unit test and code coverage (optionally tracked on [Coveralls](https://coveralls.io/)) 27 | - [ESLint](http://eslint.org/) linting and code style checking 28 | - [Travis CI](https://travis-ci.com/) continuous integration (optional) 29 | - [License](https://spdx.org/licenses/) 30 | 31 | 32 | ### Running tests 33 | 34 | Once the project is scaffolded, inside the project folder run: 35 | 36 | ``` 37 | $ npm test 38 | ``` 39 | 40 | You can also directly use jest to run test on single files: 41 | 42 | ``` 43 | $ npm -g install jest-cli 44 | $ jest --watch 45 | ``` 46 | 47 | 48 | ### Publishing your code 49 | 50 | Once your tests are passing (ideally with a Travis CI green run), you might be ready to publish your code to npm. We recommend you using [npm version](https://docs.npmjs.com/cli/version) to tag release correctly. 51 | 52 | ``` 53 | $ npm version major 54 | $ git push --follow-tags 55 | # ATTENTION: There is no turning back here. 56 | $ npm publish 57 | ``` 58 | 59 | 60 | ## Extend this generator 61 | 62 | First of all, make sure you're comfortable with [Yeoman composability](http://yeoman.io/authoring/composability.html) feature. Then in your own generator: 63 | 64 | ```js 65 | var Generator = require('yeoman-generator'); 66 | 67 | module.exports = class extends Generator({ 68 | default() { 69 | this.composeWith(require.resolve('generator-node/generators/app'), { 70 | /* provide the options you want */ 71 | }); 72 | } 73 | }); 74 | ``` 75 | 76 | 77 | ### Options 78 | 79 | Here's a list of our supported options: 80 | 81 | - `boilerplate` (Boolean, default true) include or not the boilerplate files (`lib/index.js`, `test/index.js`). 82 | - `cli` (Boolean, default false) include or not a `lib/cli.js` file. 83 | - `editorconfig` (Boolean, default true) include or not a `.editorconfig` file. 84 | - `git` (Boolean, default true) include or not the git files (`.gitattributes`, `.gitignore`). 85 | - `license` (Boolean, default true) include or not a `LICENSE` file. 86 | - `travis` (Boolean, default true) include or not a `.travis.yml` file. 87 | - `githubAccount` (String) Account name for GitHub repo location. 88 | - `readme` (String) content of the `README.md` file. Given this option, generator-node will still generate the title (with badges) and the license section. 89 | 90 | 91 | ### Sub generators 92 | 93 | If you don't need all the features provided by the main generator, you can still use a limited set of features by composing with our sub generators directly. 94 | 95 | Remember you can see the options of each sub generators by running `yo node:sub --help`. 96 | 97 | - `node:boilerplate` 98 | - `node:cli` 99 | - `node:editorconfig` 100 | - `node:eslint` 101 | - `node:git` 102 | - `node:readme` 103 | 104 | 105 | ## Backers 106 | Love Yeoman work and community? Help us keep it alive by donating funds to cover project expenses!
107 | [[Become a backer](https://opencollective.com/yeoman#support)] 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | ## License 201 | 202 | MIT © Yeoman team (http://yeoman.io) 203 | -------------------------------------------------------------------------------- /__tests__/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const _ = require('lodash'); 3 | const assert = require('yeoman-assert'); 4 | const helpers = require('yeoman-test'); 5 | 6 | jest.mock('npm-name', () => { 7 | return () => Promise.resolve(true); 8 | }); 9 | 10 | jest.mock('github-username', () => { 11 | return () => Promise.resolve('unicornUser'); 12 | }); 13 | 14 | jest.mock('generator-license/app', () => { 15 | const helpers = require('yeoman-test'); 16 | return helpers.createDummyGenerator(); 17 | }); 18 | 19 | describe('node:app', () => { 20 | describe('running on new project', () => { 21 | it('scaffold a full project', () => { 22 | const answers = { 23 | name: 'generator-node', 24 | description: 'A node generator', 25 | homepage: 'http://yeoman.io', 26 | githubAccount: 'yeoman', 27 | authorName: 'The Yeoman Team', 28 | authorEmail: 'hi@yeoman.io', 29 | authorUrl: 'http://yeoman.io', 30 | keywords: ['foo', 'bar'], 31 | includeCoveralls: true, 32 | node: 'v10.4.1,v10' 33 | }; 34 | return helpers 35 | .run(require.resolve('../generators/app')) 36 | .withPrompts(answers) 37 | .then(() => { 38 | assert.file([ 39 | '.travis.yml', 40 | '.editorconfig', 41 | '.gitignore', 42 | '.gitattributes', 43 | 'README.md', 44 | 'lib/index.js', 45 | 'lib/__tests__/generatorNode.test.js' 46 | ]); 47 | 48 | assert.file('package.json'); 49 | assert.jsonFileContent('package.json', { 50 | name: 'generator-node', 51 | version: '0.0.0', 52 | description: answers.description, 53 | homepage: answers.homepage, 54 | repository: 'yeoman/generator-node', 55 | author: { 56 | name: answers.authorName, 57 | email: answers.authorEmail, 58 | url: answers.authorUrl 59 | }, 60 | files: ['lib'], 61 | keywords: answers.keywords, 62 | main: 'lib/index.js' 63 | }); 64 | 65 | assert.file('README.md'); 66 | assert.fileContent( 67 | 'README.md', 68 | "const generatorNode = require('generator-node');" 69 | ); 70 | assert.fileContent('README.md', '> A node generator'); 71 | assert.fileContent('README.md', '$ npm install --save generator-node'); 72 | assert.fileContent('README.md', '© [The Yeoman Team](http://yeoman.io)'); 73 | assert.fileContent( 74 | 'README.md', 75 | '[travis-image]: https://travis-ci.com/yeoman/generator-node.svg?branch=master' 76 | ); 77 | assert.fileContent('README.md', 'coveralls'); 78 | 79 | assert.fileContent('.travis.yml', '| coveralls'); 80 | assert.fileContent('.travis.yml', '- v10.4.1'); 81 | assert.fileContent('.travis.yml', '- v10'); 82 | }); 83 | }); 84 | }); 85 | 86 | describe('running on existing project', () => { 87 | it('Keeps current Readme and extend package.json fields', () => { 88 | const pkg = { 89 | version: '1.0.34', 90 | description: 'lots of fun', 91 | homepage: 'http://yeoman.io', 92 | repository: 'yeoman/generator-node', 93 | author: 'The Yeoman Team', 94 | files: ['lib'], 95 | keywords: ['bar'] 96 | }; 97 | return helpers 98 | .run(require.resolve('../generators/app')) 99 | .withPrompts({ name: 'generator-node' }) 100 | .on('ready', gen => { 101 | gen.fs.writeJSON(gen.destinationPath('package.json'), pkg); 102 | gen.fs.write(gen.destinationPath('README.md'), 'foo'); 103 | }) 104 | .then(() => { 105 | const newPkg = _.extend({ name: 'generator-node' }, pkg); 106 | assert.jsonFileContent('package.json', newPkg); 107 | assert.fileContent('README.md', 'foo'); 108 | }); 109 | }); 110 | }); 111 | 112 | describe('--name', () => { 113 | it('allows scopes in names', () => { 114 | return helpers 115 | .run(require.resolve('../generators/app')) 116 | .withOptions({ 117 | name: '@some-scope/generator-node', 118 | githubAccount: 'yeoman' 119 | }) 120 | .then(() => { 121 | assert.file('lib/__tests__/someScopeGeneratorNode.test.js'); 122 | 123 | assert.file('package.json'); 124 | assert.jsonFileContent('package.json', { 125 | name: '@some-scope/generator-node', 126 | repository: 'yeoman/generator-node' 127 | }); 128 | 129 | assert.file('README.md'); 130 | assert.fileContent( 131 | 'README.md', 132 | "const someScopeGeneratorNode = require('@some-scope/generator-node');" 133 | ); 134 | assert.fileContent( 135 | 'README.md', 136 | '$ npm install --save @some-scope/generator-node' 137 | ); 138 | assert.fileContent( 139 | 'README.md', 140 | '[travis-image]: https://travis-ci.com/yeoman/generator-node.svg?branch=master' 141 | ); 142 | assert.fileContent( 143 | '.git/config', 144 | '[remote "origin"]\n url = git@github.com:yeoman/generator-node.git' 145 | ); 146 | }); 147 | }); 148 | 149 | it('throws when an invalid name is supplied', async () => { 150 | await expect( 151 | helpers.run(require.resolve('../generators/app')).withOptions({ 152 | name: '@/invalid-name', 153 | githubAccount: 'yeoman' 154 | }) 155 | ).rejects.toMatchInlineSnapshot( 156 | `[Error: name can only contain URL-friendly characters]` 157 | ); 158 | 159 | await expect( 160 | helpers.run(require.resolve('../generators/app')).withOptions({ 161 | name: 'invalid@name', 162 | githubAccount: 'yeoman' 163 | }) 164 | ).rejects.toMatchInlineSnapshot( 165 | `[Error: name can only contain URL-friendly characters]` 166 | ); 167 | }); 168 | }); 169 | 170 | describe('--repository-name', () => { 171 | it('can be set separately from --name', () => { 172 | return helpers 173 | .run(require.resolve('../generators/app')) 174 | .withOptions({ 175 | name: 'generator-node', 176 | githubAccount: 'yeoman', 177 | repositoryName: 'not-generator-node' 178 | }) 179 | .then(() => { 180 | assert.file('package.json'); 181 | assert.jsonFileContent('package.json', { 182 | repository: 'yeoman/not-generator-node' 183 | }); 184 | 185 | assert.file('README.md'); 186 | assert.fileContent( 187 | 'README.md', 188 | '[travis-image]: https://travis-ci.com/yeoman/not-generator-node.svg?branch=master' 189 | ); 190 | assert.fileContent( 191 | '.git/config', 192 | '[remote "origin"]\n url = git@github.com:yeoman/not-generator-node.git' 193 | ); 194 | }); 195 | }); 196 | }); 197 | 198 | describe('--no-travis', () => { 199 | it('skip .travis.yml', () => { 200 | return helpers 201 | .run(require.resolve('../generators/app')) 202 | .withOptions({ travis: false }) 203 | .then(() => assert.noFile('.travis.yml')); 204 | }); 205 | }); 206 | 207 | describe('--projectRoot', () => { 208 | it('include the raw files', () => { 209 | return helpers 210 | .run(require.resolve('../generators/app')) 211 | .withOptions({ projectRoot: 'generators' }) 212 | .then(() => { 213 | assert.jsonFileContent('package.json', { 214 | files: ['generators'], 215 | main: 'generators/index.js' 216 | }); 217 | }); 218 | }); 219 | }); 220 | 221 | describe('--no-editorconfig', () => { 222 | it('include the raw files', () => { 223 | return helpers 224 | .run(require.resolve('../generators/app')) 225 | .withOptions({ editorconfig: false }) 226 | .then(() => assert.noFile('.editorconfig')); 227 | }); 228 | }); 229 | }); 230 | -------------------------------------------------------------------------------- /__tests__/boilerplate.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const assert = require('yeoman-assert'); 3 | const helpers = require('yeoman-test'); 4 | 5 | describe('node:boilerplate', () => { 6 | beforeEach(() => { 7 | return helpers 8 | .run(require.resolve('../generators/boilerplate')) 9 | .withOptions({ name: 'my-module' }); 10 | }); 11 | 12 | it('creates boilerplate files', () => { 13 | assert.file('lib/index.js'); 14 | assert.file('lib/__tests__/myModule.test.js'); 15 | assert.fileContent('lib/index.js', 'module.exports = {};'); 16 | assert.fileContent('lib/__tests__/myModule.test.js', 'const myModule'); 17 | assert.fileContent('lib/__tests__/myModule.test.js', "describe('myModule'"); 18 | }); 19 | }); 20 | 21 | describe('node:boilerplate', () => { 22 | beforeEach(() => { 23 | return helpers 24 | .run(require.resolve('../generators/boilerplate')) 25 | .withOptions({ name: 'my-module', generateInto: 'other/' }); 26 | }); 27 | 28 | it('creates boilerplate files using another path', () => { 29 | assert.file('other/lib/index.js'); 30 | assert.file('other/lib/__tests__/myModule.test.js'); 31 | assert.fileContent('other/lib/index.js', 'module.exports = {};'); 32 | assert.fileContent('other/lib/__tests__/myModule.test.js', 'const myModule'); 33 | assert.fileContent('other/lib/__tests__/myModule.test.js', "describe('myModule'"); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /__tests__/cli.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const assert = require('yeoman-assert'); 3 | const helpers = require('yeoman-test'); 4 | 5 | describe('node:cli', () => { 6 | beforeEach(() => { 7 | return helpers.run(require.resolve('../generators/cli')).on('ready', generator => { 8 | generator.fs.write(generator.destinationPath('package.json'), '{"name": "my-lib"}'); 9 | }); 10 | }); 11 | 12 | it('creates cli.js', () => { 13 | assert.file('lib/cli.js'); 14 | assert.fileContent('lib/cli.js', "const meow = require('meow')"); 15 | assert.fileContent('lib/cli.js', "const myLib = require('./')"); 16 | }); 17 | 18 | it('Extends package.json', () => { 19 | assert.fileContent('package.json', '"bin": "lib/cli.js"'); 20 | assert.fileContent('package.json', '"meow"'); 21 | assert.fileContent('package.json', /"lec": "\^/); 22 | assert.fileContent('package.json', '"prepare": "lec lib/cli.js -c LF"'); 23 | }); 24 | }); 25 | 26 | describe('node:cli', () => { 27 | beforeEach(() => { 28 | return helpers 29 | .run(require.resolve('../generators/cli')) 30 | .withOptions({ generateInto: 'other/' }) 31 | .on('ready', generator => { 32 | generator.fs.write( 33 | generator.destinationPath('other/package.json'), 34 | '{"name": "my-lib"}' 35 | ); 36 | }); 37 | }); 38 | 39 | it('creates cli.js with path option', () => { 40 | assert.file('other/lib/cli.js'); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /__tests__/editorconfig.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const assert = require('yeoman-assert'); 3 | const helpers = require('yeoman-test'); 4 | 5 | describe('node:editorconfig', () => { 6 | it('creates .editorconfig', () => { 7 | return helpers 8 | .run(require.resolve('../generators/editorconfig')) 9 | .then(() => assert.file('.editorconfig')); 10 | }); 11 | 12 | it('respect --generate-into option', () => { 13 | return helpers 14 | .run(require.resolve('../generators/editorconfig')) 15 | .withOptions({ generateInto: 'other/' }) 16 | .then(() => assert.file('other/.editorconfig')); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /__tests__/eslint.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const assert = require('yeoman-assert'); 3 | const helpers = require('yeoman-test'); 4 | 5 | describe('node:eslint', () => { 6 | it('fill package.json', () => { 7 | return helpers.run(require.resolve('../generators/eslint')).then(() => { 8 | assert.fileContent('package.json', /"eslint-config-xo":/); 9 | assert.jsonFileContent('package.json', { 10 | eslintConfig: { 11 | extends: ['xo', 'prettier'], 12 | env: { 13 | jest: true 14 | } 15 | }, 16 | scripts: { 17 | pretest: 'eslint .' 18 | } 19 | }); 20 | assert.file('.eslintignore'); 21 | }); 22 | }); 23 | 24 | it('respect --generate-into option as the root of the scaffolding', () => { 25 | return helpers 26 | .run(require.resolve('../generators/eslint')) 27 | .withOptions({ generateInto: 'other/' }) 28 | .then(() => { 29 | assert.fileContent('other/package.json', /"eslint-config-xo":/); 30 | assert.jsonFileContent('other/package.json', { 31 | eslintConfig: { 32 | extends: ['xo', 'prettier'] 33 | } 34 | }); 35 | assert.file('other/.eslintignore'); 36 | }); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /__tests__/git.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const assert = require('yeoman-assert'); 3 | const helpers = require('yeoman-test'); 4 | 5 | describe('node:git', () => { 6 | it('creates the git config files and init the repository', () => { 7 | return helpers 8 | .run(require.resolve('../generators/git')) 9 | .withOptions({ 10 | githubAccount: 'yeoman', 11 | repositoryName: 'generator-node' 12 | }) 13 | .then(() => { 14 | assert.file('.gitignore'); 15 | assert.file('.gitattributes'); 16 | assert.file('.git'); 17 | 18 | assert.file('package.json'); 19 | assert.jsonFileContent('package.json', { 20 | repository: 'yeoman/generator-node' 21 | }); 22 | 23 | assert.fileContent( 24 | '.git/config', 25 | '[remote "origin"]\n url = git@github.com:yeoman/generator-node.git' 26 | ); 27 | }); 28 | }); 29 | 30 | it('respects --generate-into option', () => { 31 | return helpers 32 | .run(require.resolve('../generators/git')) 33 | .withOptions({ 34 | githubAccount: 'other-account', 35 | repositoryName: 'other-name', 36 | generateInto: 'other/' 37 | }) 38 | .then(() => { 39 | assert.file('other/.gitignore'); 40 | assert.file('other/.gitattributes'); 41 | assert.file('other/.git'); 42 | 43 | assert.file('other/package.json'); 44 | assert.jsonFileContent('other/package.json', { 45 | repository: 'other-account/other-name' 46 | }); 47 | 48 | assert.fileContent( 49 | 'other/.git/config', 50 | '[remote "origin"]\n url = git@github.com:other-account/other-name.git' 51 | ); 52 | }); 53 | }); 54 | 55 | it("doesn't add remote `origin` when `githubAccount` isn't passed", () => { 56 | return helpers 57 | .run(require.resolve('../generators/git')) 58 | .withOptions({ 59 | repositoryName: 'other-name' 60 | }) 61 | .then(() => { 62 | assert.file('.gitignore'); 63 | assert.file('.gitattributes'); 64 | assert.file('.git'); 65 | assert.file('package.json'); 66 | 67 | assert.noFileContent('package.json', '"repository"'); 68 | assert.noFileContent('.git/config', '[remote "origin"]'); 69 | }); 70 | }); 71 | 72 | it("doesn't add remote `origin` when `repositoryName` isn't passed", () => { 73 | return helpers 74 | .run(require.resolve('../generators/git')) 75 | .withOptions({ 76 | githubAccount: 'other-account' 77 | }) 78 | .then(() => { 79 | assert.file('.gitignore'); 80 | assert.file('.gitattributes'); 81 | assert.file('.git'); 82 | assert.file('package.json'); 83 | 84 | assert.noFileContent('package.json', '"repository"'); 85 | assert.noFileContent('.git/config', '[remote "origin"]'); 86 | }); 87 | }); 88 | }); 89 | -------------------------------------------------------------------------------- /__tests__/readme.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const assert = require('yeoman-assert'); 3 | const helpers = require('yeoman-test'); 4 | 5 | describe('node:readme', () => { 6 | beforeEach(() => { 7 | return helpers 8 | .run(require.resolve('../generators/readme')) 9 | .withOptions({ 10 | name: 'my-project', 11 | description: 'a cool project', 12 | githubAccount: 'yeoman', 13 | authorName: 'Yeoman', 14 | authorUrl: 'http://yeoman.io', 15 | coveralls: true 16 | }) 17 | .on('ready', gen => { 18 | gen.fs.writeJSON(gen.destinationPath('package.json'), { 19 | license: 'MIT' 20 | }); 21 | }); 22 | }); 23 | 24 | it('creates and fill contents in README.md', () => { 25 | assert.file('README.md'); 26 | assert.fileContent('README.md', "const myProject = require('my-project');"); 27 | assert.fileContent('README.md', '> a cool project'); 28 | assert.fileContent('README.md', '$ npm install --save my-project'); 29 | assert.fileContent('README.md', 'MIT © [Yeoman](http://yeoman.io)'); 30 | assert.fileContent( 31 | 'README.md', 32 | '[travis-image]: https://travis-ci.com/yeoman/my-project.svg?branch=master' 33 | ); 34 | assert.fileContent('README.md', 'coveralls'); 35 | }); 36 | }); 37 | 38 | describe('node:readme --content', () => { 39 | beforeEach(() => { 40 | return helpers 41 | .run(require.resolve('../generators/readme')) 42 | .withOptions({ 43 | name: 'my-project', 44 | description: 'a cool project', 45 | githubAccount: 'yeoman', 46 | authorName: 'Yeoman', 47 | authorUrl: 'http://yeoman.io', 48 | coveralls: true, 49 | content: 'My custom content' 50 | }) 51 | .on('ready', gen => { 52 | gen.fs.writeJSON(gen.destinationPath('package.json'), { 53 | license: 'MIT' 54 | }); 55 | }); 56 | }); 57 | 58 | it('fill custom contents in README.md', () => { 59 | assert.file('README.md'); 60 | assert.fileContent('README.md', 'My custom content'); 61 | assert.fileContent('README.md', 'MIT © [Yeoman](http://yeoman.io)'); 62 | assert.fileContent( 63 | 'README.md', 64 | '[travis-image]: https://travis-ci.com/yeoman/my-project.svg?branch=master' 65 | ); 66 | assert.fileContent('README.md', 'coveralls'); 67 | }); 68 | }); 69 | 70 | describe('node:readme --no-coveralls', () => { 71 | beforeEach(() => { 72 | return helpers 73 | .run(require.resolve('../generators/readme')) 74 | .withOptions({ 75 | name: 'my-project', 76 | description: 'a cool project', 77 | githubAccount: 'yeoman', 78 | authorName: 'Yeoman', 79 | authorUrl: 'http://yeoman.io', 80 | coveralls: false 81 | }) 82 | .on('ready', gen => { 83 | gen.fs.writeJSON(gen.destinationPath('package.json'), { 84 | license: 'MIT' 85 | }); 86 | }); 87 | }); 88 | 89 | it('does not include coveralls badge README.md', () => { 90 | assert.noFileContent('README.md', 'coveralls'); 91 | }); 92 | }); 93 | 94 | describe('node:readme --generate-into', () => { 95 | beforeEach(() => { 96 | return helpers 97 | .run(require.resolve('../generators/readme')) 98 | .withOptions({ 99 | name: 'my-project', 100 | description: 'a cool project', 101 | githubAccount: 'yeoman', 102 | authorName: 'Yeoman', 103 | authorUrl: 'http://yeoman.io', 104 | coveralls: true, 105 | generateInto: 'other/' 106 | }) 107 | .on('ready', gen => { 108 | gen.fs.writeJSON(gen.destinationPath('other/package.json'), { 109 | license: 'MIT' 110 | }); 111 | }); 112 | }); 113 | 114 | it('creates and fill contents in README.md', () => { 115 | assert.file('other/README.md'); 116 | assert.fileContent('other/README.md', "const myProject = require('my-project');"); 117 | assert.fileContent('other/README.md', '> a cool project'); 118 | assert.fileContent('other/README.md', '$ npm install --save my-project'); 119 | assert.fileContent('other/README.md', 'MIT © [Yeoman](http://yeoman.io)'); 120 | assert.fileContent( 121 | 'other/README.md', 122 | '[travis-image]: https://travis-ci.com/yeoman/my-project.svg?branch=master' 123 | ); 124 | assert.fileContent('other/README.md', 'coveralls'); 125 | }); 126 | }); 127 | 128 | describe('node:readme --content and --generate-into', () => { 129 | beforeEach(() => { 130 | return helpers 131 | .run(require.resolve('../generators/readme')) 132 | .withOptions({ 133 | name: 'my-project', 134 | description: 'a cool project', 135 | githubAccount: 'yeoman', 136 | authorName: 'Yeoman', 137 | authorUrl: 'http://yeoman.io', 138 | coveralls: true, 139 | content: 'My custom content', 140 | generateInto: 'other/' 141 | }) 142 | .on('ready', gen => { 143 | gen.fs.writeJSON(gen.destinationPath('other/package.json'), { 144 | license: 'MIT' 145 | }); 146 | }); 147 | }); 148 | 149 | it('fill custom contents in README.md', () => { 150 | assert.file('other/README.md'); 151 | assert.fileContent('other/README.md', 'My custom content'); 152 | assert.fileContent('other/README.md', 'MIT © [Yeoman](http://yeoman.io)'); 153 | assert.fileContent( 154 | 'other/README.md', 155 | '[travis-image]: https://travis-ci.com/yeoman/my-project.svg?branch=master' 156 | ); 157 | assert.fileContent('other/README.md', 'coveralls'); 158 | }); 159 | }); 160 | 161 | describe('node:readme --no-coveralls and --generate-into', () => { 162 | beforeEach(() => { 163 | return helpers 164 | .run(require.resolve('../generators/readme')) 165 | .withOptions({ 166 | name: 'my-project', 167 | description: 'a cool project', 168 | githubAccount: 'yeoman', 169 | authorName: 'Yeoman', 170 | authorUrl: 'http://yeoman.io', 171 | coveralls: false, 172 | generateInto: 'other/' 173 | }) 174 | .on('ready', gen => { 175 | gen.fs.writeJSON(gen.destinationPath('other/package.json'), { 176 | license: 'MIT' 177 | }); 178 | }); 179 | }); 180 | 181 | it('does not include coveralls badge README.md', () => { 182 | assert.noFileContent('other/README.md', 'coveralls'); 183 | }); 184 | }); 185 | 186 | describe('node:readme --yarn', () => { 187 | beforeEach(() => { 188 | return helpers 189 | .run(require.resolve('../generators/readme')) 190 | .withOptions({ 191 | name: 'my-project', 192 | description: 'a cool project', 193 | githubAccount: 'yeoman', 194 | authorName: 'Yeoman', 195 | authorUrl: 'http://yeoman.io', 196 | yarn: true, 197 | coveralls: false 198 | }) 199 | .on('ready', gen => { 200 | gen.fs.writeJSON(gen.destinationPath('package.json'), { 201 | license: 'MIT' 202 | }); 203 | }); 204 | }); 205 | 206 | it('creates and fills contents in README.md', () => { 207 | assert.file('README.md'); 208 | assert.fileContent('README.md', '$ yarn add my-project'); 209 | }); 210 | }); 211 | -------------------------------------------------------------------------------- /generators/app/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const _ = require('lodash'); 3 | const extend = _.merge; 4 | const Generator = require('yeoman-generator'); 5 | const parseAuthor = require('parse-author'); 6 | const githubUsername = require('github-username'); 7 | const path = require('path'); 8 | const askName = require('inquirer-npm-name'); 9 | const chalk = require('chalk'); 10 | const validatePackageName = require('validate-npm-package-name'); 11 | const pkgJson = require('../../package.json'); 12 | 13 | module.exports = class extends Generator { 14 | constructor(args, options) { 15 | super(args, options); 16 | 17 | this.option('travis', { 18 | type: Boolean, 19 | required: false, 20 | default: true, 21 | desc: 'Include travis config' 22 | }); 23 | 24 | this.option('boilerplate', { 25 | type: Boolean, 26 | required: false, 27 | default: true, 28 | desc: 'Include boilerplate files' 29 | }); 30 | 31 | this.option('cli', { 32 | type: Boolean, 33 | required: false, 34 | default: false, 35 | desc: 'Add a CLI' 36 | }); 37 | 38 | this.option('coveralls', { 39 | type: Boolean, 40 | required: false, 41 | desc: 'Include coveralls config' 42 | }); 43 | 44 | this.option('editorconfig', { 45 | type: Boolean, 46 | required: false, 47 | default: true, 48 | desc: 'Include a .editorconfig file' 49 | }); 50 | 51 | this.option('license', { 52 | type: Boolean, 53 | required: false, 54 | default: true, 55 | desc: 'Include a license' 56 | }); 57 | 58 | this.option('name', { 59 | type: String, 60 | required: false, 61 | desc: 'Project name' 62 | }); 63 | 64 | this.option('githubAccount', { 65 | type: String, 66 | required: false, 67 | desc: 'GitHub username or organization' 68 | }); 69 | 70 | this.option('repositoryName', { 71 | type: String, 72 | required: false, 73 | desc: 'Name of the GitHub repository' 74 | }); 75 | 76 | this.option('projectRoot', { 77 | type: String, 78 | required: false, 79 | default: 'lib', 80 | desc: 'Relative path to the project code root' 81 | }); 82 | 83 | this.option('readme', { 84 | type: String, 85 | required: false, 86 | desc: 'Content to insert in the README.md file' 87 | }); 88 | } 89 | 90 | initializing() { 91 | this.pkg = this.fs.readJSON(this.destinationPath('package.json'), {}); 92 | 93 | // Pre set the default props from the information we have at this point 94 | this.props = { 95 | name: this.pkg.name, 96 | description: this.pkg.description, 97 | version: this.pkg.version, 98 | homepage: this.pkg.homepage, 99 | repositoryName: this.options.repositoryName 100 | }; 101 | 102 | if (this.options.name) { 103 | const name = this.options.name; 104 | const packageNameValidity = validatePackageName(name); 105 | 106 | if (packageNameValidity.validForNewPackages) { 107 | this.props.name = name; 108 | } else { 109 | this.emit( 110 | 'error', 111 | new Error( 112 | _.get(packageNameValidity, 'errors.0') || 113 | 'The name option is not a valid npm package name.' 114 | ) 115 | ); 116 | } 117 | } 118 | 119 | if (_.isObject(this.pkg.author)) { 120 | this.props.authorName = this.pkg.author.name; 121 | this.props.authorEmail = this.pkg.author.email; 122 | this.props.authorUrl = this.pkg.author.url; 123 | } else if (_.isString(this.pkg.author)) { 124 | const info = parseAuthor(this.pkg.author); 125 | this.props.authorName = info.name; 126 | this.props.authorEmail = info.email; 127 | this.props.authorUrl = info.url; 128 | } 129 | } 130 | 131 | _getModuleNameParts(name) { 132 | const moduleName = { 133 | name, 134 | repositoryName: this.props.repositoryName 135 | }; 136 | 137 | if (moduleName.name.startsWith('@')) { 138 | const nameParts = moduleName.name.slice(1).split('/'); 139 | 140 | Object.assign(moduleName, { 141 | scopeName: nameParts[0], 142 | localName: nameParts[1] 143 | }); 144 | } else { 145 | moduleName.localName = moduleName.name; 146 | } 147 | 148 | if (!moduleName.repositoryName) { 149 | moduleName.repositoryName = moduleName.localName; 150 | } 151 | 152 | return moduleName; 153 | } 154 | 155 | _askForModuleName() { 156 | let askedName; 157 | 158 | if (this.props.name) { 159 | askedName = Promise.resolve({ 160 | name: this.props.name 161 | }); 162 | } else { 163 | askedName = askName( 164 | { 165 | name: 'name', 166 | default: path.basename(process.cwd()) 167 | }, 168 | this 169 | ); 170 | } 171 | 172 | return askedName.then(answer => { 173 | const moduleNameParts = this._getModuleNameParts(answer.name); 174 | 175 | Object.assign(this.props, moduleNameParts); 176 | }); 177 | } 178 | 179 | _askFor() { 180 | const prompts = [ 181 | { 182 | name: 'description', 183 | message: 'Description', 184 | when: !this.props.description 185 | }, 186 | { 187 | name: 'homepage', 188 | message: 'Project homepage url', 189 | when: !this.props.homepage 190 | }, 191 | { 192 | name: 'authorName', 193 | message: "Author's Name", 194 | when: !this.props.authorName, 195 | default: this.user.git.name(), 196 | store: true 197 | }, 198 | { 199 | name: 'authorEmail', 200 | message: "Author's Email", 201 | when: !this.props.authorEmail, 202 | default: this.user.git.email(), 203 | store: true 204 | }, 205 | { 206 | name: 'authorUrl', 207 | message: "Author's Homepage", 208 | when: !this.props.authorUrl, 209 | store: true 210 | }, 211 | { 212 | name: 'keywords', 213 | message: 'Package keywords (comma to split)', 214 | when: !this.pkg.keywords, 215 | filter(words) { 216 | return words.split(/\s*,\s*/g); 217 | } 218 | }, 219 | { 220 | name: 'includeCoveralls', 221 | type: 'confirm', 222 | message: 'Send coverage reports to coveralls', 223 | when: this.options.coveralls === undefined 224 | } 225 | ]; 226 | 227 | return this.prompt(prompts).then(props => { 228 | this.props = extend(this.props, props); 229 | }); 230 | } 231 | 232 | _askForTravis() { 233 | const prompts = [ 234 | { 235 | name: 'node', 236 | message: 'Enter Node versions (comma separated)', 237 | when: this.options.travis 238 | } 239 | ]; 240 | 241 | return this.prompt(prompts).then(props => { 242 | this.props = extend(this.props, props); 243 | }); 244 | } 245 | 246 | _askForGithubAccount() { 247 | if (this.options.githubAccount) { 248 | this.props.githubAccount = this.options.githubAccount; 249 | return Promise.resolve(); 250 | } 251 | 252 | let usernamePromise; 253 | if (this.props.scopeName) { 254 | usernamePromise = Promise.resolve(this.props.scopeName); 255 | } else { 256 | usernamePromise = githubUsername(this.props.authorEmail).then( 257 | username => username, 258 | () => '' 259 | ); 260 | } 261 | 262 | return usernamePromise.then(username => { 263 | return this.prompt({ 264 | name: 'githubAccount', 265 | message: 'GitHub username or organization', 266 | default: username 267 | }).then(prompt => { 268 | this.props.githubAccount = prompt.githubAccount; 269 | }); 270 | }); 271 | } 272 | 273 | prompting() { 274 | return this._askForModuleName() 275 | .then(this._askFor.bind(this)) 276 | .then(this._askForTravis.bind(this)) 277 | .then(this._askForGithubAccount.bind(this)); 278 | } 279 | 280 | writing() { 281 | // Re-read the content at this point because a composed generator might modify it. 282 | const currentPkg = this.fs.readJSON(this.destinationPath('package.json'), {}); 283 | 284 | const pkg = extend( 285 | { 286 | name: this.props.name, 287 | version: '0.0.0', 288 | description: this.props.description, 289 | homepage: this.props.homepage, 290 | author: { 291 | name: this.props.authorName, 292 | email: this.props.authorEmail, 293 | url: this.props.authorUrl 294 | }, 295 | files: [this.options.projectRoot], 296 | main: path.join(this.options.projectRoot, 'index.js').replace(/\\/g, '/'), 297 | keywords: [], 298 | devDependencies: {}, 299 | engines: { 300 | npm: '>= 4.0.0' 301 | } 302 | }, 303 | currentPkg 304 | ); 305 | 306 | if (this.props.includeCoveralls) { 307 | pkg.devDependencies.coveralls = pkgJson.devDependencies.coveralls; 308 | } 309 | 310 | // Combine the keywords 311 | if (this.props.keywords && this.props.keywords.length) { 312 | pkg.keywords = _.uniq(this.props.keywords.concat(pkg.keywords)); 313 | } 314 | 315 | // Let's extend package.json so we're not overwriting user previous fields 316 | this.fs.writeJSON(this.destinationPath('package.json'), pkg); 317 | } 318 | 319 | default() { 320 | if (this.options.travis) { 321 | let options = { config: {} }; 322 | 323 | if (this.props.node) { 324 | // eslint-disable-next-line camelcase 325 | options.config.node_js = this.props.node.split(','); 326 | } 327 | 328 | if (this.props.includeCoveralls) { 329 | options.config.after_script = 'cat ./coverage/lcov.info | coveralls'; // eslint-disable-line camelcase 330 | } 331 | 332 | this.composeWith(require.resolve('generator-travis/generators/app'), options); 333 | } 334 | 335 | if (this.options.editorconfig) { 336 | this.composeWith(require.resolve('../editorconfig')); 337 | } 338 | 339 | this.composeWith(require.resolve('../eslint')); 340 | 341 | this.composeWith(require.resolve('../git'), { 342 | name: this.props.name, 343 | githubAccount: this.props.githubAccount, 344 | repositoryName: this.props.repositoryName 345 | }); 346 | 347 | this.composeWith(require.resolve('generator-jest/generators/app'), { 348 | testEnvironment: 'node', 349 | coveralls: false 350 | }); 351 | 352 | if (this.options.boilerplate) { 353 | this.composeWith(require.resolve('../boilerplate'), { 354 | name: this.props.name 355 | }); 356 | } 357 | 358 | if (this.options.cli) { 359 | this.composeWith(require.resolve('../cli')); 360 | } 361 | 362 | if (this.options.license && !this.pkg.license) { 363 | this.composeWith(require.resolve('generator-license/app'), { 364 | name: this.props.authorName, 365 | email: this.props.authorEmail, 366 | website: this.props.authorUrl 367 | }); 368 | } 369 | 370 | if (!this.fs.exists(this.destinationPath('README.md'))) { 371 | this.composeWith(require.resolve('../readme'), { 372 | name: this.props.name, 373 | description: this.props.description, 374 | githubAccount: this.props.githubAccount, 375 | repositoryName: this.props.repositoryName, 376 | authorName: this.props.authorName, 377 | authorUrl: this.props.authorUrl, 378 | coveralls: this.props.includeCoveralls, 379 | content: this.options.readme 380 | }); 381 | } 382 | } 383 | 384 | installing() { 385 | this.npmInstall(); 386 | } 387 | 388 | end() { 389 | this.log('Thanks for using Yeoman.'); 390 | 391 | if (this.options.travis) { 392 | let travisUrl = chalk.cyan( 393 | `https://travis-ci.com/profile/${this.props.githubAccount || ''}` 394 | ); 395 | this.log(`- Enable Travis integration at ${travisUrl}`); 396 | } 397 | 398 | if (this.props.includeCoveralls) { 399 | let coverallsUrl = chalk.cyan('https://coveralls.io/repos/new'); 400 | this.log(`- Enable Coveralls integration at ${coverallsUrl}`); 401 | } 402 | } 403 | }; 404 | -------------------------------------------------------------------------------- /generators/boilerplate/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const _ = require('lodash'); 3 | const Generator = require('yeoman-generator'); 4 | 5 | module.exports = class extends Generator { 6 | constructor(args, options) { 7 | super(args, options); 8 | 9 | this.option('generateInto', { 10 | type: String, 11 | required: false, 12 | default: '', 13 | desc: 'Relocate the location of the generated files.' 14 | }); 15 | 16 | this.option('name', { 17 | type: String, 18 | required: true, 19 | desc: 'The new module name.' 20 | }); 21 | } 22 | 23 | writing() { 24 | const filepath = this.destinationPath(this.options.generateInto, 'lib/index.js'); 25 | 26 | this.fs.copyTpl(this.templatePath('index.js'), filepath); 27 | 28 | this.composeWith(require.resolve('generator-jest/generators/test'), { 29 | arguments: [filepath], 30 | componentName: _.camelCase(this.options.name) 31 | }); 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /generators/boilerplate/templates/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = {}; 4 | -------------------------------------------------------------------------------- /generators/cli/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const _ = require('lodash'); 3 | const extend = _.merge; 4 | const Generator = require('yeoman-generator'); 5 | 6 | module.exports = class extends Generator { 7 | constructor(args, options) { 8 | super(args, options); 9 | 10 | this.option('generateInto', { 11 | type: String, 12 | required: false, 13 | defaults: '', 14 | desc: 'Relocate the location of the generated files.' 15 | }); 16 | } 17 | 18 | writing() { 19 | const pkg = this.fs.readJSON( 20 | this.destinationPath(this.options.generateInto, 'package.json'), 21 | {} 22 | ); 23 | 24 | extend(pkg, { 25 | bin: 'lib/cli.js', 26 | dependencies: { 27 | meow: '^3.7.0' 28 | }, 29 | devDependencies: { 30 | lec: '^1.0.1' 31 | }, 32 | scripts: { 33 | prepare: 'lec lib/cli.js -c LF' 34 | } 35 | }); 36 | 37 | this.fs.writeJSON( 38 | this.destinationPath(this.options.generateInto, 'package.json'), 39 | pkg 40 | ); 41 | 42 | this.fs.copyTpl( 43 | this.templatePath('cli.js'), 44 | this.destinationPath(this.options.generateInto, 'lib/cli.js'), 45 | { 46 | pkgName: pkg.name, 47 | pkgSafeName: _.camelCase(pkg.name) 48 | } 49 | ); 50 | } 51 | }; 52 | -------------------------------------------------------------------------------- /generators/cli/templates/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | const meow = require('meow'); 4 | const <%= pkgSafeName %> = require('./'); 5 | 6 | const cli = meow(` 7 | Usage 8 | $ <%= pkgName %> [input] 9 | 10 | Options 11 | --foo Lorem ipsum. [Default: false] 12 | 13 | Examples 14 | $ <%= pkgName %> 15 | unicorns 16 | $ <%= pkgName %> rainbows 17 | unicorns & rainbows 18 | `); 19 | -------------------------------------------------------------------------------- /generators/editorconfig/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const Generator = require('yeoman-generator'); 3 | 4 | module.exports = class extends Generator { 5 | constructor(args, options) { 6 | super(args, options); 7 | 8 | this.option('generateInto', { 9 | type: String, 10 | required: false, 11 | defaults: '', 12 | desc: 'Relocate the location of the generated files.' 13 | }); 14 | } 15 | 16 | initializing() { 17 | this.fs.copy( 18 | this.templatePath('editorconfig'), 19 | this.destinationPath(this.options.generateInto, '.editorconfig') 20 | ); 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /generators/editorconfig/templates/editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.md] 11 | trim_trailing_whitespace = false 12 | -------------------------------------------------------------------------------- /generators/eslint/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const Generator = require('yeoman-generator'); 3 | const rootPkg = require('../../package.json'); 4 | 5 | module.exports = class extends Generator { 6 | constructor(args, options) { 7 | super(args, options); 8 | 9 | this.option('generateInto', { 10 | type: String, 11 | required: false, 12 | default: '', 13 | desc: 'Relocate the location of the generated files.' 14 | }); 15 | } 16 | 17 | writing() { 18 | const pkgJson = { 19 | devDependencies: { 20 | eslint: rootPkg.devDependencies.eslint, 21 | prettier: rootPkg.devDependencies.prettier, 22 | husky: rootPkg.devDependencies.husky, 23 | 'lint-staged': rootPkg.devDependencies['lint-staged'], 24 | 'eslint-config-prettier': rootPkg.devDependencies['eslint-config-prettier'], 25 | 'eslint-plugin-prettier': rootPkg.devDependencies['eslint-plugin-prettier'], 26 | 'eslint-config-xo': rootPkg.devDependencies['eslint-config-xo'] 27 | }, 28 | 'lint-staged': rootPkg['lint-staged'], 29 | husky: rootPkg.husky, 30 | eslintConfig: rootPkg.eslintConfig, 31 | scripts: { 32 | pretest: rootPkg.scripts.pretest, 33 | precommit: rootPkg.scripts.precommit 34 | } 35 | }; 36 | 37 | this.fs.extendJSON( 38 | this.destinationPath(this.options.generateInto, 'package.json'), 39 | pkgJson 40 | ); 41 | 42 | this.fs.copy( 43 | this.templatePath('eslintignore'), 44 | this.destinationPath(this.options.generateInto, '.eslintignore') 45 | ); 46 | } 47 | }; 48 | -------------------------------------------------------------------------------- /generators/eslint/templates/eslintignore: -------------------------------------------------------------------------------- 1 | coverage 2 | -------------------------------------------------------------------------------- /generators/git/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const Generator = require('yeoman-generator'); 3 | const originUrl = require('git-remote-origin-url'); 4 | 5 | module.exports = class extends Generator { 6 | constructor(args, opts) { 7 | super(args, opts); 8 | this.option('generateInto', { 9 | type: String, 10 | required: false, 11 | defaults: '', 12 | desc: 'Relocate the location of the generated files.' 13 | }); 14 | 15 | this.option('githubAccount', { 16 | type: String, 17 | required: true, 18 | desc: 'GitHub username or organization' 19 | }); 20 | 21 | this.option('repositoryName', { 22 | type: String, 23 | required: true, 24 | desc: 'Name of the GitHub repository' 25 | }); 26 | } 27 | 28 | initializing() { 29 | this.fs.copy( 30 | this.templatePath('gitattributes'), 31 | this.destinationPath(this.options.generateInto, '.gitattributes') 32 | ); 33 | 34 | this.fs.copy( 35 | this.templatePath('gitignore'), 36 | this.destinationPath(this.options.generateInto, '.gitignore') 37 | ); 38 | 39 | return originUrl(this.destinationPath(this.options.generateInto)).then( 40 | function(url) { 41 | this.originUrl = url; 42 | }.bind(this), 43 | function() { 44 | this.originUrl = ''; 45 | }.bind(this) 46 | ); 47 | } 48 | 49 | _readPkg() { 50 | return this.fs.readJSON( 51 | this.destinationPath(this.options.generateInto, 'package.json'), 52 | {} 53 | ); 54 | } 55 | 56 | writing() { 57 | const pkg = this._readPkg(); 58 | 59 | let repository; 60 | if (this.originUrl) { 61 | repository = this.originUrl; 62 | } else if (this.options.githubAccount && this.options.repositoryName) { 63 | repository = this.options.githubAccount + '/' + this.options.repositoryName; 64 | } 65 | 66 | pkg.repository = pkg.repository || repository; 67 | 68 | this.fs.writeJSON( 69 | this.destinationPath(this.options.generateInto, 'package.json'), 70 | pkg 71 | ); 72 | } 73 | 74 | end() { 75 | const pkg = this._readPkg(); 76 | 77 | this.spawnCommandSync('git', ['init', '--quiet'], { 78 | cwd: this.destinationPath(this.options.generateInto) 79 | }); 80 | 81 | if (pkg.repository && !this.originUrl) { 82 | let repoSSH = pkg.repository; 83 | if (pkg.repository && pkg.repository.indexOf('.git') === -1) { 84 | repoSSH = 'git@github.com:' + pkg.repository + '.git'; 85 | } 86 | 87 | this.spawnCommandSync('git', ['remote', 'add', 'origin', repoSSH], { 88 | cwd: this.destinationPath(this.options.generateInto) 89 | }); 90 | } 91 | } 92 | }; 93 | -------------------------------------------------------------------------------- /generators/git/templates/gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /generators/git/templates/gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | -------------------------------------------------------------------------------- /generators/readme/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const _ = require('lodash'); 3 | const Generator = require('yeoman-generator'); 4 | const querystring = require('querystring'); 5 | 6 | module.exports = class extends Generator { 7 | constructor(args, options) { 8 | super(args, options); 9 | 10 | this.option('generateInto', { 11 | type: String, 12 | required: false, 13 | defaults: '', 14 | desc: 'Relocate the location of the generated files.' 15 | }); 16 | 17 | this.option('name', { 18 | type: String, 19 | required: true, 20 | desc: 'Project name' 21 | }); 22 | 23 | this.option('description', { 24 | type: String, 25 | required: true, 26 | desc: 'Project description' 27 | }); 28 | 29 | this.option('githubAccount', { 30 | type: String, 31 | required: true, 32 | desc: 'GitHub username or organization' 33 | }); 34 | 35 | this.option('repositoryName', { 36 | type: String, 37 | required: true, 38 | desc: 'Name of the GitHub repository' 39 | }); 40 | 41 | this.option('authorName', { 42 | type: String, 43 | required: true, 44 | desc: 'Author name' 45 | }); 46 | 47 | this.option('authorUrl', { 48 | type: String, 49 | required: true, 50 | desc: 'Author url' 51 | }); 52 | 53 | this.option('coveralls', { 54 | type: Boolean, 55 | required: true, 56 | desc: 'Include coveralls badge' 57 | }); 58 | 59 | this.option('content', { 60 | type: String, 61 | required: false, 62 | desc: 'Readme content' 63 | }); 64 | } 65 | 66 | writing() { 67 | const pkg = this.fs.readJSON( 68 | this.destinationPath(this.options.generateInto, 'package.json'), 69 | {} 70 | ); 71 | this.fs.copyTpl( 72 | this.templatePath('README.md'), 73 | this.destinationPath(this.options.generateInto, 'README.md'), 74 | { 75 | projectName: this.options.name, 76 | safeProjectName: _.camelCase(this.options.name), 77 | escapedProjectName: querystring.escape(this.options.name), 78 | repositoryName: this.options.repositoryName || this.options.name, 79 | description: this.options.description, 80 | githubAccount: this.options.githubAccount, 81 | author: { 82 | name: this.options.authorName, 83 | url: this.options.authorUrl 84 | }, 85 | license: pkg.license, 86 | includeCoveralls: this.options.coveralls, 87 | yarn: this.options.yarn, 88 | content: this.options.content 89 | } 90 | ); 91 | } 92 | }; 93 | -------------------------------------------------------------------------------- /generators/readme/templates/README.md: -------------------------------------------------------------------------------- 1 | # <%= projectName %> [![NPM version][npm-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Dependency Status][daviddm-image]][daviddm-url]<% 2 | if (includeCoveralls) { %> [![Coverage percentage][coveralls-image]][coveralls-url]<% } -%> 3 | 4 | > <%= description %> 5 | 6 | <% if (!content) { -%> 7 | ## Installation 8 | 9 | ```sh 10 | <% if (yarn) { -%> 11 | $ yarn add <%= projectName %> 12 | <% } else { -%> 13 | $ npm install --save <%= projectName %> 14 | <% } -%> 15 | ``` 16 | 17 | ## Usage 18 | 19 | ```js 20 | const <%= safeProjectName %> = require('<%= projectName %>'); 21 | 22 | <%= safeProjectName %>('Rainbow'); 23 | ``` 24 | <% } else { -%> 25 | <%= content %> 26 | <% } -%> 27 | ## License 28 | 29 | <%= license %> © [<%= author.name %>](<%= author.url %>) 30 | 31 | 32 | [npm-image]: https://badge.fury.io/js/<%= escapedProjectName %>.svg 33 | [npm-url]: https://npmjs.org/package/<%= projectName %> 34 | [travis-image]: https://travis-ci.com/<%= githubAccount %>/<%= repositoryName %>.svg?branch=master 35 | [travis-url]: https://travis-ci.com/<%= githubAccount %>/<%= repositoryName %> 36 | [daviddm-image]: https://david-dm.org/<%= githubAccount %>/<%= repositoryName %>.svg?theme=shields.io 37 | [daviddm-url]: https://david-dm.org/<%= githubAccount %>/<%= repositoryName %> 38 | <% if (includeCoveralls) { -%> 39 | [coveralls-image]: https://coveralls.io/repos/<%= githubAccount %>/<%= repositoryName %>/badge.svg 40 | [coveralls-url]: https://coveralls.io/r/<%= githubAccount %>/<%= repositoryName %> 41 | <% } -%> 42 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | app: require.resolve('./generators/app'), 5 | boilerplate: require.resolve('./generators/boilerplate'), 6 | cli: require.resolve('./generators/cli'), 7 | editorconfig: require.resolve('./generators/editorconfig'), 8 | eslint: require.resolve('./generators/eslint'), 9 | git: require.resolve('./generators/git'), 10 | readme: require.resolve('./generators/readme') 11 | }; 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "generator-node", 3 | "version": "2.8.0", 4 | "description": "Create a Node.js module", 5 | "homepage": "https://github.com/yeoman/generator-node", 6 | "author": "Yeoman team", 7 | "files": [ 8 | "index.js", 9 | "generators" 10 | ], 11 | "main": "index.js", 12 | "keywords": [ 13 | "yeoman-generator", 14 | "scaffold", 15 | "node", 16 | "module", 17 | "cli" 18 | ], 19 | "devDependencies": { 20 | "coveralls": "^3.0.7", 21 | "eslint": "^6.6.0", 22 | "eslint-config-prettier": "^6.6.0", 23 | "eslint-config-xo": "^0.27.2", 24 | "eslint-plugin-prettier": "^3.1.1", 25 | "husky": "^3.0.9", 26 | "jest": "^24.9.0", 27 | "lint-staged": "^9.4.3", 28 | "prettier": "^1.19.1", 29 | "yeoman-assert": "^3.1.1", 30 | "yeoman-test": "^2.0.0" 31 | }, 32 | "repository": "yeoman/generator-node", 33 | "scripts": { 34 | "pretest": "eslint .", 35 | "test": "jest" 36 | }, 37 | "dependencies": { 38 | "chalk": "^3.0.0", 39 | "generator-jest": "^1.7.0", 40 | "generator-license": "^5.2.0", 41 | "generator-travis": "^1.9.0", 42 | "git-remote-origin-url": "^3.0.0", 43 | "github-username": "^5.0.1", 44 | "inquirer-npm-name": "^3.0.0", 45 | "lodash": "^4.17.15", 46 | "parse-author": "^2.0.0", 47 | "validate-npm-package-name": "^3.0.0", 48 | "yeoman-generator": "^4.2.0" 49 | }, 50 | "eslintConfig": { 51 | "extends": [ 52 | "xo", 53 | "prettier" 54 | ], 55 | "env": { 56 | "jest": true, 57 | "node": true 58 | }, 59 | "rules": { 60 | "prettier/prettier": "error" 61 | }, 62 | "plugins": [ 63 | "prettier" 64 | ] 65 | }, 66 | "lint-staged": { 67 | "*.js": [ 68 | "eslint --fix", 69 | "git add" 70 | ], 71 | "*.json": [ 72 | "prettier --write", 73 | "git add" 74 | ] 75 | }, 76 | "license": "MIT", 77 | "jest": { 78 | "testEnvironment": "node", 79 | "collectCoverage": true, 80 | "coverageReporters": [ 81 | "lcov", 82 | "text", 83 | "text-summary" 84 | ] 85 | }, 86 | "engines": { 87 | "npm": ">= 6.0.0" 88 | }, 89 | "husky": { 90 | "hooks": { 91 | "pre-commit": "lint-staged" 92 | } 93 | } 94 | } 95 | --------------------------------------------------------------------------------