├── .all-contributorsrc ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .npmrc ├── .prettierrc ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── jest.config.js ├── other ├── CODE_OF_CONDUCT.md ├── EXAMPLES.md ├── ROADMAP.md └── manual-release.md ├── package.json ├── run-setup.js ├── src ├── __mocks__ │ ├── child_process.js │ └── semver.js ├── __tests__ │ └── index.js ├── index.js ├── install-deps │ ├── __tests__ │ │ └── index.js │ └── index.js ├── run-setup.js ├── setup.js ├── utils │ ├── __tests__ │ │ └── one-line.js │ └── one-line.js └── verify-system │ ├── __tests__ │ ├── __snapshots__ │ │ ├── index.js.snap │ │ ├── node.js.snap │ │ └── npm.js.snap │ ├── exec-validator.js │ ├── index.js │ ├── node.js │ └── npm.js │ ├── exec-validator.js │ ├── index.js │ ├── node.js │ ├── npm.js │ └── yarn.js ├── webpack.config.js └── yarn.lock /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "workshop-setup", 3 | "projectOwner": "kentcdodds", 4 | "files": [ 5 | "README.md" 6 | ], 7 | "imageSize": 100, 8 | "commit": false, 9 | "contributors": [ 10 | { 11 | "login": "kentcdodds", 12 | "name": "Kent C. Dodds", 13 | "avatar_url": "https://avatars.githubusercontent.com/u/1500684?v=3", 14 | "profile": "https://kentcdodds.com", 15 | "contributions": [ 16 | "code", 17 | "doc", 18 | "infra", 19 | "test" 20 | ] 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.js text eol=lf 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 13 | 14 | - `workshop-setup` version: 15 | - `node` version: 16 | - `npm` (or `yarn`) version: 17 | 18 | Relevant code or config 19 | 20 | ```javascript 21 | 22 | ``` 23 | 24 | What you did: 25 | 26 | 27 | 28 | What happened: 29 | 30 | 31 | 32 | Reproduction repository: 33 | 34 | 38 | 39 | Problem description: 40 | 41 | 42 | 43 | Suggested solution: 44 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | **What**: 19 | 20 | 21 | **Why**: 22 | 23 | 24 | **How**: 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | dist 4 | .opt-in 5 | .opt-out 6 | .eslintcache 7 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=http://registry.npmjs.org/ 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": false, 6 | "singleQuote": true, 7 | "trailingComma": "all", 8 | "bracketSpacing": false, 9 | "jsxBracketSameLine": false, 10 | "proseWrap": "always" 11 | } 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | cache: 4 | yarn: true 5 | directories: 6 | - ~/.npm 7 | - node_modules 8 | notifications: 9 | email: false 10 | node_js: '8' 11 | script: npm run validate 12 | after_success: kcd-scripts travis-after-success 13 | branches: 14 | only: master 15 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | The changelog is automatically updated using [semantic-release](https://github.com/semantic-release/semantic-release). 4 | You can see it on the [releases page](../releases). 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thanks for being willing to contribute! 4 | 5 | **Working on your first Pull Request?** You can learn how from this _free_ 6 | series [How to Contribute to an Open Source Project on GitHub][egghead] 7 | 8 | ## Project setup 9 | 10 | 1. Fork and clone the repo 11 | 2. `$ npm install` to install dependencies 12 | 3. `$ npm run validate` to validate you've got it working 13 | 4. Create a branch for your PR 14 | 15 | > Tip: Keep your `master` branch pointing at the original repository and make 16 | > pull requests from branches on your fork. To do this, run: 17 | > 18 | > ``` 19 | > git remote add upstream https://github.com/kentcdodds/workshop-setup.git 20 | > git fetch upstream 21 | > git branch --set-upstream-to=upstream/master master 22 | > ``` 23 | > 24 | > This will add the original repository as a "remote" called "upstream," Then 25 | > fetch the git information from that remote, then set your local `master` 26 | > branch to use the upstream master branch whenever you run `git pull`. Then you 27 | > can make all of your pull request branches based on this `master` branch. 28 | > Whenever you want to update The installed version of `master`, do a regular 29 | > `git pull`. 30 | 31 | ## Add yourself as a contributor 32 | 33 | This project follows the [all contributors][all-contributors] specification. To 34 | add yourself to the table of contributors on the `README.md`, please use the 35 | automated script as part of your PR: 36 | 37 | ```console 38 | npm run add-contributor 39 | ``` 40 | 41 | Follow the prompt and commit `.all-contributorsrc` and `README.md` in the PR. If 42 | you've already added yourself to the list and are making a new type of 43 | contribution, you can run it again and select the added contribution type. 44 | 45 | ## Committing and Pushing changes 46 | 47 | Please make sure to run the tests before you commit your changes. You can run 48 | `npm run test:update` which will update any snapshots that need updating. Make 49 | sure to include those changes (if they exist) in your commit. 50 | 51 | ### opt into git hooks 52 | 53 | There are git hooks set up with this project that are automatically installed 54 | when you install dependencies. They're really handy, but are turned off by 55 | default (so as to not hinder new contributors). You can opt into these by 56 | creating a file called `.opt-in` at the root of the project and putting this 57 | inside: 58 | 59 | ``` 60 | pre-commit 61 | ``` 62 | 63 | ## Help needed 64 | 65 | Please checkout the [the open issues][issues] 66 | 67 | Also, please watch the repo and respond to questions/bug reports/feature 68 | requests! Thanks! 69 | 70 | [egghead]: 71 | https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github 72 | [all-contributors]: https://github.com/kentcdodds/all-contributors 73 | [issues]: https://github.com/kentcdodds/workshop-setup/issues 74 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2016 Kent C. Dodds 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # workshop-setup 2 | 3 | Verify and setup a repository for workshop attendees 4 | 5 | [![Build Status][build-badge]][build] 6 | [![Code Coverage][coverage-badge]][coverage] 7 | [![Dependencies][dependencyci-badge]][dependencyci] 8 | [![version][version-badge]][package] [![downloads][downloads-badge]][npm-stat] 9 | [![MIT License][license-badge]][license] 10 | 11 | [![All Contributors](https://img.shields.io/badge/all_contributors-1-orange.svg?style=flat-square)](#contributors) 12 | [![PRs Welcome][prs-badge]][prs] [![Donate][donate-badge]][donate] 13 | [![Code of Conduct][coc-badge]][coc] [![Roadmap][roadmap-badge]][roadmap] 14 | [![Examples][examples-badge]][examples] 15 | 16 | [![Watch on GitHub][github-watch-badge]][github-watch] 17 | [![Star on GitHub][github-star-badge]][github-star] 18 | [![Tweet][twitter-badge]][twitter] 19 | 20 | ## The problem 21 | 22 | I make quite a few [workshops][workshops] and one of the biggest challenges I 23 | have is making sure that people have set things up correctly so the workshop has 24 | as few surprises as possible. So I want to have a script validate things on 25 | attendees machines before they start on the workshop and give them helpful info 26 | to fix problems early and on their own. 27 | 28 | The problem is further complicated by the fact that I can't use any modules to 29 | do this because I can pretty much only guarantee that attendees have some 30 | version of node and npm, but not which version. So I need something that exists 31 | when they clone the repository right from the start. 32 | 33 | ## This solution 34 | 35 | This exposes a simple function that takes an array of validators which return 36 | strings of helpful text (or a promise that resolves to a string of helpful text) 37 | if the system is not valid (or `null` if it is valid). To overcome the issue of 38 | not being able to install things, there is a bundled version of this module that 39 | you can download from the registry and commit directly to your project. 40 | 41 | ## Table of Contents 42 | 43 | 44 | 45 | 46 | - [Installation](#installation) 47 | - [Usage](#usage) 48 | - [Alternative usage](#alternative-usage) 49 | - [verifySystem](#verifysystem) 50 | - [installDeps](#installdeps) 51 | - [Inspiration](#inspiration) 52 | - [Other Solutions](#other-solutions) 53 | - [Contributors](#contributors) 54 | - [LICENSE](#license) 55 | 56 | 57 | 58 | ## Installation 59 | 60 | The way I expect people to use this module is by downloading the UMD build and 61 | committing it directly into their project. You can download the UMD build via 62 | npm if you like (then just copy/paste the file from `node_modules`) or download 63 | it from `unpkg.com` here: https://unpkg.com/workshop-setup/dist/index.js 64 | 65 | ``` 66 | curl -o scripts/workshop-setup.js -L https://unpkg.com/workshop-setup/dist/index.js 67 | ``` 68 | 69 | This module is distributed via [npm][npm] which is bundled with [node][node] and 70 | can be installed as one of your project's `devDependencies`: 71 | 72 | ``` 73 | npm install --save-dev workshop-setup 74 | ``` 75 | 76 | ## Usage 77 | 78 | Here's what I recommend: 79 | 80 | 1. Download the workshop-setup script into `scripts/workshop-setup.js` 81 | 2. Add `engines` config to your `packge.json` with `node`, `npm`, and `yarn` 82 | listed 83 | 3. Add a `script` to your `package.json` called `setup` with: 84 | `node ./scripts/setup` 85 | 4. Create the `scripts/setup.js` file 86 | 5. And put this in it: 87 | 88 | ```javascript 89 | var path = require('path') 90 | var pkg = require(path.join(process.cwd(), 'package.json')) 91 | 92 | // if you install it then this should be require('workshop-setup') 93 | // but that... doesn't really make sense. 94 | require('./workshop-setup') 95 | .setup(pkg.engines) 96 | .then( 97 | () => { 98 | console.log(`💯 You're all set up! 👏`) 99 | }, 100 | error => { 101 | console.error(`🚨 There was a problem:`) 102 | console.error(error) 103 | console.error( 104 | `\nIf you would like to just ignore this error, then feel free to do so and install dependencies as you normally would in "${process.cwd()}". Just know that things may not work properly if you do...`, 105 | ) 106 | }, 107 | ) 108 | ``` 109 | 110 | ### Alternative usage 111 | 112 | Whether you install it or download it, usage is basically the same. The 113 | difference is how you require it. 114 | 115 | ```javascript 116 | // if you install it, you'd do 117 | var workshopSetup = require('workshop-setup') 118 | // if you download it, you'd do something like: 119 | var workshopSetup = require('./workshop-setup') 120 | ``` 121 | 122 | ### verifySystem 123 | 124 | This allows you to verify the user's system is correct: 125 | 126 | ```javascript 127 | var verifySystem = require('./workshop-setup').verifySystem 128 | 129 | var verifyPromise = verifySystem([ 130 | verifySystem.validators.node('^8.4.0'), 131 | verifySystem.validators.npm('^5.4.1'), 132 | ]) 133 | 134 | verifyPromise.then( 135 | function() { 136 | // resolves if there are no errors 137 | console.log('🎉 Congrats! Your system is setup properly') 138 | console.log('You should be good to install and run things.') 139 | }, 140 | function(error) { 141 | // rejects if there are errors 142 | console.error(error) 143 | console.info( 144 | "\nIf you don't care about these warnings, go " + 145 | 'ahead and install dependencies with `node ./scripts/install`', 146 | ) 147 | process.exitCode = 1 148 | }, 149 | ) 150 | ``` 151 | 152 | You can also specify custom validators. There are several utilities exposed by 153 | `workshop-setup` as well which can be quite helpful. 154 | 155 | ```javascript 156 | verifySystem([ 157 | function promiseVerify() { 158 | return new Promise(resolve => { 159 | // note the exclusion of reject here. We expect all validator promises to 160 | // resolve with `null` or the error message. 161 | resolve(null) // there were no errors 162 | }) 163 | }, 164 | function syncVerify() { 165 | if ('cats' > 'dogs') { 166 | return 'dogs are way better than cats' 167 | } 168 | return null 169 | }, 170 | // here's a practical example that uses some utilities 171 | function validateYeoman() { 172 | return verifySystem.utils.execValidator('^1.8.5', 'yo --version', function( 173 | actual, 174 | desired, 175 | ) { 176 | return verifySystem.utils.commonTags.oneLine` 177 | You have version ${actual} of yeoman, but 178 | should have a version in the range: ${desired} 179 | ` 180 | }) 181 | }, 182 | ]).then(/* handle success/failure */) 183 | ``` 184 | 185 | #### validators 186 | 187 | The built-in validators available on `workshopSetup.verifySystem.validators` 188 | are: 189 | 190 | - `node(desiredVersionRange)` 191 | - `yarn(desiredVersionRange)` 192 | - `npm(desiredNpmVersionRange)` 193 | 194 | #### utils 195 | 196 | Most of the utils are simply exposing other modules which are bundled with 197 | `workshop-setup`. These are available on `workshopSetup.verifySystem.utils`: 198 | 199 | - `execValidator(desiredVersionRange, commandToGetVersion, messageFn)` - 200 | `messageFn` is given `actual, desired` 201 | - `oneLine`: a tag that allows you to have multiple lines for a message and 202 | it'll put it all on one line 203 | - [`semver`][semver] (really useful `satisfies` method on this one) 204 | 205 | ### installDeps 206 | 207 | This will install dependencies in the given directory/directories (defaults to 208 | `process.cwd()`) using `npm`. 209 | 210 | ```javascript 211 | var path = require('path') 212 | var installDeps = require('./workshop-setup').installDeps 213 | 214 | var main = path.resolve(__dirname, '..') 215 | var api = path.resolve(__dirname, '../api') 216 | var client = path.resolve(__dirname, '../client') 217 | installDeps([main, api, client]).then( 218 | () => { 219 | console.log('👍 all dependencies installed') 220 | }, 221 | () => { 222 | // ignore, workshop-setup will log for us... 223 | }, 224 | ) 225 | 226 | // you can also do: 227 | installDeps() 228 | // which is effectively 229 | installDeps(process.cwd()) 230 | 231 | // or, to be more specific: 232 | installDeps(path.resolve('..')) 233 | ``` 234 | 235 | ## Inspiration 236 | 237 | This project was inspired by all of the people who have ever struggled to set up 238 | one of my workshops before. Hopefully it's easier now! 239 | 240 | ## Other Solutions 241 | 242 | I'm unaware of any other solutions for this problem. Feel free to link them here 243 | if you find any. 244 | 245 | ## Contributors 246 | 247 | Thanks goes to these people ([emoji key][emojis]): 248 | 249 | 250 | 251 | | [
Kent C. Dodds](https://kentcdodds.com)
[💻](https://github.com/kentcdodds/workshop-setup/commits?author=kentcdodds) [📖](https://github.com/kentcdodds/workshop-setup/commits?author=kentcdodds) 🚇 [⚠️](https://github.com/kentcdodds/workshop-setup/commits?author=kentcdodds) | 252 | | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | 253 | 254 | 255 | 256 | 257 | This project follows the [all-contributors][all-contributors] specification. 258 | Contributions of any kind welcome! 259 | 260 | ## LICENSE 261 | 262 | MIT 263 | 264 | [npm]: https://www.npmjs.com/ 265 | [node]: https://nodejs.org 266 | [build-badge]: 267 | https://img.shields.io/travis/kentcdodds/workshop-setup.svg?style=flat-square 268 | [build]: https://travis-ci.org/kentcdodds/workshop-setup 269 | [coverage-badge]: 270 | https://img.shields.io/codecov/c/github/kentcdodds/workshop-setup.svg?style=flat-square 271 | [coverage]: https://codecov.io/github/kentcdodds/workshop-setup 272 | [dependencyci-badge]: 273 | https://dependencyci.com/github/kentcdodds/workshop-setup/badge?style=flat-square 274 | [dependencyci]: https://dependencyci.com/github/kentcdodds/workshop-setup 275 | [version-badge]: 276 | https://img.shields.io/npm/v/workshop-setup.svg?style=flat-square 277 | [package]: https://www.npmjs.com/package/workshop-setup 278 | [downloads-badge]: 279 | https://img.shields.io/npm/dm/workshop-setup.svg?style=flat-square 280 | [npm-stat]: 281 | http://npm-stat.com/charts.html?package=workshop-setup&from=2016-04-01 282 | [license-badge]: 283 | https://img.shields.io/npm/l/workshop-setup.svg?style=flat-square 284 | [license]: 285 | https://github.com/kentcdodds/workshop-setup/blob/master/other/LICENSE 286 | [prs-badge]: 287 | https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square 288 | [prs]: http://makeapullrequest.com 289 | [donate-badge]: 290 | https://img.shields.io/badge/$-support-green.svg?style=flat-square 291 | [donate]: http://kcd.im/donate 292 | [coc-badge]: 293 | https://img.shields.io/badge/code%20of-conduct-ff69b4.svg?style=flat-square 294 | [coc]: 295 | https://github.com/kentcdodds/workshop-setup/blob/master/other/CODE_OF_CONDUCT.md 296 | [roadmap-badge]: 297 | https://img.shields.io/badge/%F0%9F%93%94-roadmap-CD9523.svg?style=flat-square 298 | [roadmap]: 299 | https://github.com/kentcdodds/workshop-setup/blob/master/other/ROADMAP.md 300 | [examples-badge]: 301 | https://img.shields.io/badge/%F0%9F%92%A1-examples-8C8E93.svg?style=flat-square 302 | [examples]: 303 | https://github.com/kentcdodds/workshop-setup/blob/master/other/EXAMPLES.md 304 | [github-watch-badge]: 305 | https://img.shields.io/github/watchers/kentcdodds/workshop-setup.svg?style=social 306 | [github-watch]: https://github.com/kentcdodds/workshop-setup/watchers 307 | [github-star-badge]: 308 | https://img.shields.io/github/stars/kentcdodds/workshop-setup.svg?style=social 309 | [github-star]: https://github.com/kentcdodds/workshop-setup/stargazers 310 | [twitter]: 311 | https://twitter.com/intent/tweet?text=Check%20out%20workshop-setup!%20https://github.com/kentcdodds/workshop-setup%20%F0%9F%91%8D 312 | [twitter-badge]: 313 | https://img.shields.io/twitter/url/https/github.com/kentcdodds/workshop-setup.svg?style=social 314 | [emojis]: https://github.com/kentcdodds/all-contributors#emoji-key 315 | [all-contributors]: https://github.com/kentcdodds/all-contributors 316 | [workshops]: https://kentcdodds.com/workshops 317 | [semver]: https://www.npmjs.com/package/semver 318 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | const config = require('kcd-scripts/jest') 2 | 3 | config.coverageThreshold.global = { 4 | branches: 50, 5 | statements: 60, 6 | functions: 60, 7 | lines: 50, 8 | } 9 | -------------------------------------------------------------------------------- /other/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at kent+coc@doddsfamily.us. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /other/EXAMPLES.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | There aren't any examples yet! Want to add one? See `CONTRIBUTING.md` 4 | -------------------------------------------------------------------------------- /other/ROADMAP.md: -------------------------------------------------------------------------------- 1 | # Project Roadmap 2 | 3 | This is where we'll define a few things about the library's goals. 4 | 5 | ## Want to do 6 | 7 | - Add more validators for common things 8 | 9 | ## Might do 10 | 11 | ## Wont do 12 | -------------------------------------------------------------------------------- /other/manual-release.md: -------------------------------------------------------------------------------- 1 | # manual release 2 | 3 | This file is just here to be used when there's a mistake in the auto-release. 4 | Just increment the number below and commit your change with the commit type 5 | that should have been released which will trigger a release. Then you can 6 | update the changelog if necessary. 7 | 8 | **Manual Release Count: 1** 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "workshop-setup", 3 | "version": "0.0.0-semantically-released", 4 | "description": "Verify and setup a repository for workshop attendees", 5 | "main": "dist/index.js", 6 | "engines": { 7 | "node": ">=8", 8 | "npm": ">=6", 9 | "yarn": ">=1" 10 | }, 11 | "scripts": { 12 | "build": "webpack", 13 | "lint": "kcd-scripts lint", 14 | "test": "kcd-scripts test", 15 | "test:update": "npm test -- --updateSnapshot --coverage", 16 | "validate": "kcd-scripts validate" 17 | }, 18 | "husky": { 19 | "hooks": { 20 | "pre-commit": "kcd-scripts pre-commit" 21 | } 22 | }, 23 | "files": [ 24 | "dist", 25 | "run-setup.js" 26 | ], 27 | "bin": { 28 | "workshop-setup": "run-setup.js" 29 | }, 30 | "keywords": [ 31 | "workshop", 32 | "tool", 33 | "utility", 34 | "repository" 35 | ], 36 | "author": "Kent C. Dodds (http://kentcdodds.com/)", 37 | "license": "MIT", 38 | "devDependencies": { 39 | "@babel/core": "^7.4.3", 40 | "@babel/preset-env": "^7.4.3", 41 | "babel-loader": "^8.0.5", 42 | "indent-string": "^3.2.0", 43 | "jest-in-case": "^1.0.2", 44 | "kcd-scripts": "^1.2.1", 45 | "semver": "^6.0.0", 46 | "webpack": "^4.30.0", 47 | "webpack-cli": "^3.3.0" 48 | }, 49 | "eslintConfig": { 50 | "extends": [ 51 | "./node_modules/kcd-scripts/eslint.js" 52 | ], 53 | "rules": { 54 | "no-console": "off" 55 | } 56 | }, 57 | "eslintIgnore": [ 58 | "node_modules", 59 | "coverage", 60 | "dist" 61 | ], 62 | "babel": { 63 | "presets": [ 64 | [ 65 | "@babel/preset-env", 66 | { 67 | "targets": { 68 | "node": "8.0.0" 69 | } 70 | } 71 | ] 72 | ] 73 | }, 74 | "repository": { 75 | "type": "git", 76 | "url": "https://github.com/kentcdodds/workshop-setup.git" 77 | }, 78 | "bugs": { 79 | "url": "https://github.com/kentcdodds/workshop-setup/issues" 80 | }, 81 | "homepage": "https://github.com/kentcdodds/workshop-setup#readme" 82 | } 83 | -------------------------------------------------------------------------------- /run-setup.js: -------------------------------------------------------------------------------- 1 | // lol, I realized after writing this that it doesn't really make sense because 2 | // folks who need to run this wont have the package installed anyway, so this 3 | // file is basically useless, but I'll leave it here as something that can be 4 | // copy/pasted in each individual project :) 5 | 6 | var path = require('path') 7 | var setup = require('./dist').setup 8 | 9 | setup(require(path.join(process.cwd(), 'package.json')).engines).catch( 10 | error => { 11 | console.error(`🚨 There was a problem:`) 12 | console.error(error) 13 | console.error( 14 | `\nIf you would like to just ignore this error, then feel free to do so and install dependencies as you normally would in "${process.cwd()}". Just know that things may not work properly if you do...`, 15 | ) 16 | }, 17 | ) 18 | 19 | /* eslint no-var:0 */ 20 | -------------------------------------------------------------------------------- /src/__mocks__/child_process.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | execSync: jest.fn(), 3 | spawn: jest.fn(), 4 | } 5 | -------------------------------------------------------------------------------- /src/__mocks__/semver.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | satisfies: jest.fn(), 3 | } 4 | -------------------------------------------------------------------------------- /src/__tests__/index.js: -------------------------------------------------------------------------------- 1 | /* eslint import/default:0 */ 2 | import * as workshopSetup from '../' 3 | import installDeps from '../install-deps' 4 | import verifySystem from '../verify-system' 5 | import setup from '../setup' 6 | 7 | test('exposes the right stuff', () => { 8 | expect(workshopSetup).toEqual({ 9 | installDeps, 10 | verifySystem, 11 | setup, 12 | }) 13 | }) 14 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import verifySystem from './verify-system' 2 | import installDeps from './install-deps' 3 | import setup from './setup' 4 | 5 | export {verifySystem, installDeps, setup} 6 | -------------------------------------------------------------------------------- /src/install-deps/__tests__/index.js: -------------------------------------------------------------------------------- 1 | import cpMock from 'child_process' 2 | import installDeps from '../' // eslint-disable-line import/default 3 | 4 | jest.mock('child_process') 5 | 6 | beforeEach(() => { 7 | jest.spyOn(console, 'log').mockImplementation(() => {}) 8 | jest.spyOn(console, 'warn').mockImplementation(() => {}) 9 | jest.spyOn(console, 'error').mockImplementation(() => {}) 10 | }) 11 | 12 | afterEach(() => { 13 | console.log.mockRestore() 14 | console.warn.mockRestore() 15 | console.error.mockRestore() 16 | }) 17 | 18 | test('installs via npm', async () => { 19 | await testInstallDeps() 20 | const installer = 'npm' 21 | const args = ['install', '--no-package-lock'] 22 | expect(cpMock.spawn).toHaveBeenCalledWith(installer, args, { 23 | stdio: 'inherit', 24 | shell: true, 25 | cwd: process.cwd(), 26 | }) 27 | }) 28 | 29 | test('installs in the given directory', async () => { 30 | const mockCwd = './dir' 31 | await testInstallDeps({installDepsArgs: mockCwd}) 32 | expect(cpMock.spawn).toHaveBeenCalledWith( 33 | expect.any(String), 34 | expect.any(Array), 35 | { 36 | stdio: 'inherit', 37 | shell: true, 38 | cwd: mockCwd, 39 | }, 40 | ) 41 | }) 42 | 43 | test('installs in all given directories', async () => { 44 | const mockCwds = ['./dir1', './dir2', './dir3'] 45 | await testInstallDeps({installDepsArgs: mockCwds}) 46 | mockCwds.forEach(cwd => { 47 | expect(cpMock.spawn).toHaveBeenCalledWith( 48 | expect.any(String), 49 | expect.any(Array), 50 | { 51 | stdio: 'inherit', 52 | shell: true, 53 | cwd, 54 | }, 55 | ) 56 | }) 57 | }) 58 | 59 | test('rejects if an exit code is non-zero', async () => { 60 | const onMock = jest.fn((event, callback) => { 61 | const exitCode = 1 62 | callback(exitCode) 63 | }) 64 | let error 65 | try { 66 | await testInstallDeps({onMock}) 67 | } catch (e) { 68 | error = e 69 | } 70 | expect(error).toEqual(process.cwd()) 71 | expect(console.error).toHaveBeenCalledTimes(1) 72 | }) 73 | 74 | async function testInstallDeps({onMock, installDepsArgs} = {}) { 75 | // not sure why, but default args for onMock messed things up... 76 | const onSpy = jest.fn(onMock || defaultOnMock) 77 | cpMock.spawn.mockClear() 78 | cpMock.spawn.mockImplementation(() => ({on: onSpy})) 79 | await installDeps(installDepsArgs) 80 | expect(cpMock.spawn).toHaveBeenCalledTimes(getSpawnCalls()) 81 | expect(onSpy).toHaveBeenCalledWith('exit', expect.any(Function)) 82 | 83 | function getSpawnCalls() { 84 | if (!installDepsArgs || !Array.isArray(installDepsArgs)) { 85 | return 1 86 | } else { 87 | return installDepsArgs.length 88 | } 89 | } 90 | 91 | function defaultOnMock(event, callback) { 92 | if (event === 'exit') { 93 | const exitCode = 0 94 | callback(exitCode) 95 | } else { 96 | throw new Error('should not call on with anything but `exit`') 97 | } 98 | } 99 | } 100 | 101 | /* eslint no-console:0 */ 102 | -------------------------------------------------------------------------------- /src/install-deps/index.js: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import fs from 'fs' 3 | import cp from 'child_process' 4 | 5 | export default installDeps 6 | 7 | function installDeps(directories = [process.cwd()], {yarnOk} = {}) { 8 | if (!Array.isArray(directories)) { 9 | directories = [directories] 10 | } 11 | 12 | let promise = Promise.resolve() 13 | directories.forEach(dir => { 14 | promise = promise.then(() => spawnInstall(dir)) 15 | }) 16 | return promise 17 | 18 | function spawnInstall(cwd) { 19 | return new Promise((resolve, reject) => { 20 | const hasPkgLock = fs.existsSync( 21 | path.join(process.cwd(), 'package-lock.json'), 22 | ) 23 | const hasYarnLock = fs.existsSync(path.join(process.cwd(), 'yarn.lock')) 24 | const useYarn = yarnOk && (hasYarnLock || !hasPkgLock) 25 | let installer = 'npm' 26 | let installerArgs = [ 27 | hasPkgLock ? 'ci' : 'install', 28 | hasPkgLock ? null : '--no-package-lock', 29 | ].filter(Boolean) 30 | 31 | if (useYarn) { 32 | installer = 'yarn' 33 | installerArgs = [hasYarnLock ? null : '--no-lockfile'].filter(Boolean) 34 | } else if (!yarnOk && (hasYarnLock && !hasPkgLock)) { 35 | console.warn( 36 | `⚠️ "${cwd}" has a yarn.lock file, but this system does not have the right version of yarn installed.`, 37 | `We'll install using npm instead, but you may experience issues. Install the correct version of yarn to get rid of this warning.`, 38 | ) 39 | } 40 | 41 | const command = [installer, ...installerArgs].join(' ') 42 | console.log(`📦 starting \`${command}\` in "${cwd}"`) 43 | 44 | const child = cp.spawn(installer, installerArgs, { 45 | stdio: 'inherit', 46 | shell: true, 47 | cwd, 48 | }) 49 | child.on('exit', onExit) 50 | 51 | function onExit(code) { 52 | if (code === 0) { 53 | console.log(`🎉 finished installing dependencies in "${cwd}"`) 54 | resolve() 55 | } else { 56 | console.error(`💀 error installing dependencies in "${cwd}"`) 57 | reject(cwd) 58 | } 59 | } 60 | }) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/run-setup.js: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import setup from './setup' 3 | 4 | const {engines} = require(path.join(process.cwd(), 'package.json')) 5 | 6 | setup(engines) 7 | -------------------------------------------------------------------------------- /src/setup.js: -------------------------------------------------------------------------------- 1 | import verifySystem from './verify-system' 2 | import installDeps from './install-deps' 3 | 4 | export default setup 5 | 6 | async function setup({directories, node, yarn, npm}) { 7 | const nodeError = await verifySystem.validators.node(node)() 8 | if (nodeError) { 9 | return Promise.reject(nodeError) 10 | } 11 | const npmError = await verifySystem.validators.npm(npm)() 12 | const yarnError = await verifySystem.validators.yarn(yarn)() 13 | if (yarnError && npmError) { 14 | const errorMessage = [ 15 | verifySystem.utils.oneLine` 16 | We tried to validate The installed version of npm and yarn and both failed. 17 | We recommend you fix yarn, but fixing either will resolve this issue. 18 | Here are the validation error messages for each: 19 | `, 20 | '', 21 | yarnError, 22 | '', 23 | npmError, 24 | ].join('\n') 25 | return Promise.reject(errorMessage) 26 | } 27 | return installDeps(directories, {yarnOk: !yarnError}) 28 | } 29 | -------------------------------------------------------------------------------- /src/utils/__tests__/one-line.js: -------------------------------------------------------------------------------- 1 | import cases from 'jest-in-case' 2 | import oneLine from '../one-line' 3 | 4 | cases( 5 | 'one-line', 6 | output => { 7 | expect(output).not.toContain('\n') 8 | expect(output).not.toContain(' ') 9 | }, 10 | [ 11 | oneLine` 12 | foo 13 | 14 | bar 15 | `, 16 | oneLine`foo 17 | baz 18 | buz 19 | `, 20 | ], 21 | ) 22 | -------------------------------------------------------------------------------- /src/utils/one-line.js: -------------------------------------------------------------------------------- 1 | function oneLine(strings, ...interpolations) { 2 | const string = strings 3 | .map( 4 | (s, i) => 5 | `${s}${interpolations[i] === undefined ? '' : interpolations[i]}`, 6 | ) 7 | .join('') 8 | .split('\n') 9 | .join(' ') 10 | .split(' ') 11 | .filter(Boolean) 12 | .join(' ') 13 | 14 | return string 15 | } 16 | 17 | export default oneLine 18 | -------------------------------------------------------------------------------- /src/verify-system/__tests__/__snapshots__/index.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`returns an error message if a validator returns one 1`] = ` 4 | "There are some issues with your system. 5 | - error 1 6 | - error 2" 7 | `; 8 | -------------------------------------------------------------------------------- /src/verify-system/__tests__/__snapshots__/node.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`returns error if conditions are not satisfied 1`] = `"The installed version of node (4.7.0) does not satisfy the desired range of 6.9.5. Please install a version within the range. You can use http://git.io/nvm or https://github.com/coreybutler/nvm-windows to make changing The installed version of node easier."`; 4 | -------------------------------------------------------------------------------- /src/verify-system/__tests__/__snapshots__/npm.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`returns npm version mismatch 1`] = `"The installed version of npm (3.11.3) does not satisfy the desired range of ^4.0.3. Please at least have a version within the specified version range. You can install the latest version by running \`npm install --global npm@^4.0.3\`."`; 4 | -------------------------------------------------------------------------------- /src/verify-system/__tests__/exec-validator.js: -------------------------------------------------------------------------------- 1 | import cpMock from 'child_process' 2 | import {satisfies as satisfiesMock} from 'semver' 3 | import execValidator from '../exec-validator' 4 | 5 | jest.mock('child_process') 6 | 7 | test('returns null if the version is satisfied', () => { 8 | const mockActual = '1.2.3' 9 | setup({mockActual}) 10 | const desired = '1.2.3' 11 | const command = 'split-guide --version' 12 | expect(execValidator(desired, command, () => 'error')).toBe(null) 13 | expect(cpMock.execSync).toHaveBeenCalledTimes(1) 14 | expect(cpMock.execSync).toHaveBeenCalledWith(command) 15 | expect(satisfiesMock).toHaveBeenCalledTimes(1) 16 | expect(satisfiesMock).toHaveBeenCalledWith(mockActual, desired) 17 | }) 18 | 19 | test('returns call to message if the version is not satisfied', () => { 20 | const mockActual = '1.3.4' 21 | setup({mockActual, satisfies: false}) 22 | const desired = '1.2.3' 23 | const command = 'split-guide --version' 24 | const errorMessage = 'error' 25 | const messageCallback = jest.fn(() => errorMessage) 26 | expect(execValidator(desired, command, messageCallback)).toBe(errorMessage) 27 | expect(messageCallback).toHaveBeenCalledTimes(1) 28 | expect(messageCallback).toHaveBeenCalledWith(mockActual, desired) 29 | }) 30 | 31 | function setup({mockActual = '1.2.3', satisfies = true} = {}) { 32 | satisfiesMock.mockClear() 33 | cpMock.execSync.mockClear() 34 | satisfiesMock.mockImplementation(() => satisfies) 35 | cpMock.execSync.mockImplementation(() => mockActual) 36 | } 37 | -------------------------------------------------------------------------------- /src/verify-system/__tests__/index.js: -------------------------------------------------------------------------------- 1 | import verifySystem from '../' 2 | 3 | test('resolve null with no errors', async () => { 4 | const result = await verifySystem() 5 | expect(result).toBe(null) 6 | }) 7 | 8 | test(`filters out falsy values (you're quite welcome)`, async () => { 9 | const result = await verifySystem([undefined, null]) 10 | expect(result).toBe(null) 11 | }) 12 | 13 | test('calls validators and returns null if no messages returned', async () => { 14 | const v1 = jest.fn() 15 | const v2 = jest.fn(() => null) 16 | const result = await verifySystem([v1, v2]) 17 | expect(result).toBe(null) 18 | expect(v1).toHaveBeenCalledTimes(1) 19 | expect(v2).toHaveBeenCalledTimes(1) 20 | }) 21 | 22 | test('returns an error message if a validator returns one', async () => { 23 | const result = await verifySystem([() => 'error 1', () => 'error 2']).catch( 24 | error => error, 25 | ) 26 | expect(result).toMatchSnapshot() 27 | }) 28 | 29 | test('properly pluarlizes a single error message', async () => { 30 | const result = await verifySystem([() => 'error 1']).catch(error => error) 31 | expect(result).toContain('is an issue') 32 | }) 33 | 34 | test('properly pluarlizes multiple error messages', async () => { 35 | const result = await verifySystem([() => 'error 1', () => 'error 2']).catch( 36 | error => error, 37 | ) 38 | expect(result).toContain('are some issues') 39 | }) 40 | -------------------------------------------------------------------------------- /src/verify-system/__tests__/node.js: -------------------------------------------------------------------------------- 1 | import mockProcess from 'process' 2 | import {satisfies as satisfiesMock} from 'semver' 3 | import validateNode from '../node' 4 | 5 | jest.mock('process', () => ({ 6 | versions: { 7 | node: '6.9.5', 8 | }, 9 | })) 10 | 11 | test('returns null if conditions are satisfied', () => { 12 | setup({version: '6.9.5'}) 13 | const desired = '6.9.5' 14 | const result = validateNode(desired)() 15 | expect(result).toBe(null) 16 | }) 17 | 18 | test('returns error if conditions are not satisfied', () => { 19 | const mockActual = '4.7.0' 20 | setup({version: mockActual, satisfies: false}) 21 | const desired = '6.9.5' 22 | const result = validateNode(desired)() 23 | expect(result).toContain(desired) 24 | expect(result).toContain(mockActual) 25 | expect(result).toMatchSnapshot() 26 | }) 27 | 28 | function setup({version = '6.9.5', satisfies = true} = {}) { 29 | mockProcess.versions.node = version 30 | satisfiesMock.mockClear() 31 | satisfiesMock.mockImplementation(() => satisfies) 32 | } 33 | -------------------------------------------------------------------------------- /src/verify-system/__tests__/npm.js: -------------------------------------------------------------------------------- 1 | import cpMock from 'child_process' 2 | import {satisfies as satisfiesMock} from 'semver' 3 | import oneLine from '../../utils/one-line' 4 | import validateNpm from '../npm' 5 | 6 | jest.mock('child_process') 7 | 8 | test('returns npm version mismatch', () => { 9 | const mockNpmVersion = '3.11.3' 10 | setup({ 11 | mockNpmVersion, 12 | satisfiesNpm: false, 13 | mockYarnVersion: () => { 14 | throw new Error('blah') 15 | }, 16 | }) 17 | const desiredNpm = '^4.0.3' 18 | const result = validateNpm(desiredNpm)() 19 | expect(result).toContain(desiredNpm) 20 | expect(result).toContain(mockNpmVersion) 21 | expect(result).toMatchSnapshot() 22 | }) 23 | 24 | function setup({mockNpmVersion = '4.0.3', satisfiesNpm = true} = {}) { 25 | satisfiesMock.mockClear() 26 | cpMock.execSync.mockClear() 27 | satisfiesMock.mockImplementation(satisfiesMockImplementation) 28 | cpMock.execSync.mockImplementation(execSyncMock) 29 | 30 | function satisfiesMockImplementation(actual, desired) { 31 | if (actual === mockNpmVersion) { 32 | return satisfiesNpm 33 | } else { 34 | console.error( 35 | oneLine` 36 | called satisifies with "${desired}, ${actual}". 37 | ${actual} does not match npm@${mockNpmVersion} 38 | `, 39 | ) 40 | throw new Error('satisfies mock implementation insufficient') 41 | } 42 | } 43 | 44 | function execSyncMock(command) { 45 | if (command.includes('npm')) { 46 | return callAndReturn(mockNpmVersion) 47 | } else { 48 | console.error( 49 | oneLine` 50 | called execSync with "${command}". 51 | This does not include npm 52 | `, 53 | ) 54 | throw new Error('execSync mock implementation insufficient') 55 | } 56 | } 57 | 58 | function callAndReturn(thing) { 59 | if (typeof thing === 'function') { 60 | thing = thing() 61 | } 62 | return thing 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/verify-system/exec-validator.js: -------------------------------------------------------------------------------- 1 | import {execSync} from 'child_process' 2 | import semver from 'semver' 3 | import oneLine from '../utils/one-line' 4 | 5 | export default execValidator 6 | 7 | function execValidator(desired, command, message) { 8 | let actual = '0.0.0' 9 | try { 10 | actual = execSync(command) 11 | .toString() 12 | .trim() 13 | } catch (error) { 14 | return oneLine` 15 | There was an error running the command \`${command}\`: 16 | ${error.message} 17 | ` 18 | } 19 | return semver.satisfies(actual, desired) ? null : message(actual, desired) 20 | } 21 | -------------------------------------------------------------------------------- /src/verify-system/index.js: -------------------------------------------------------------------------------- 1 | import indentString from 'indent-string' 2 | import semver from 'semver' 3 | import oneLine from '../utils/one-line' 4 | import node from './node' 5 | import npm from './npm' 6 | import yarn from './yarn' 7 | import execValidator from './exec-validator' 8 | 9 | export default validateAll 10 | 11 | Object.assign(validateAll, { 12 | utils: { 13 | execValidator, 14 | oneLine, 15 | semver, 16 | }, 17 | validators: { 18 | node, 19 | npm, 20 | yarn, 21 | }, 22 | }) 23 | 24 | function validateAll(validators = []) { 25 | const promises = validators.filter(Boolean).map(v => Promise.resolve(v())) 26 | return Promise.all(promises).then(results => { 27 | const errors = results.filter(Boolean) 28 | const errorCount = errors.length 29 | 30 | if (errorCount) { 31 | const errorMessages = errors.map(error => `- ${error}`).join('\n') 32 | const one = errorCount === 1 33 | 34 | return Promise.reject( 35 | [ 36 | oneLine` 37 | There ${one ? 'is an issue' : 'are some issues'} with your system. 38 | `, 39 | indentString(errorMessages, 2), 40 | ].join('\n'), 41 | ) 42 | } else { 43 | return null 44 | } 45 | }) 46 | } 47 | -------------------------------------------------------------------------------- /src/verify-system/node.js: -------------------------------------------------------------------------------- 1 | import process from 'process' 2 | import semver from 'semver' 3 | import oneLine from '../utils/one-line' 4 | 5 | export default getNodeValidator 6 | 7 | function getNodeValidator(desired) { 8 | return validateNode 9 | 10 | function validateNode() { 11 | const actual = process.versions.node 12 | if (!semver.satisfies(actual, desired)) { 13 | return oneLine` 14 | The installed version of node (${actual}) does not satisfy 15 | the desired range of ${desired}. 16 | Please install a version within the range. You can use 17 | http://git.io/nvm or https://github.com/coreybutler/nvm-windows 18 | to make changing The installed version of node easier. 19 | ` 20 | } 21 | return null 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/verify-system/npm.js: -------------------------------------------------------------------------------- 1 | import oneLine from '../utils/one-line' 2 | import execValidator from './exec-validator' 3 | 4 | export default getNPMValidator 5 | 6 | function getNPMValidator(desired) { 7 | return validateNpm 8 | 9 | function validateNpm() { 10 | return execValidator(desired, 'npm --version', actual => { 11 | return oneLine` 12 | The installed version of npm (${actual}) does not satisfy 13 | the desired range of ${desired}. 14 | Please at least have a version within the specified version range. 15 | You can install the latest version by running 16 | \`npm install --global npm@${desired}\`. 17 | ` 18 | }) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/verify-system/yarn.js: -------------------------------------------------------------------------------- 1 | import oneLine from '../utils/one-line' 2 | import execValidator from './exec-validator' 3 | 4 | export default getYarnValidator 5 | 6 | function getYarnValidator(desired) { 7 | return validateYarn 8 | 9 | function validateYarn() { 10 | return execValidator(desired, 'yarn --version', actual => { 11 | return oneLine` 12 | The installed version of yarn (${actual}) does not satisfy 13 | the desired range of ${desired}. 14 | Please at least have a version within the specified version range. 15 | Updating to the latest version of yarn depends on how you installed it 16 | in the first place. Please see more information here: 17 | https://yarnpkg.com/en/docs/install 18 | ` 19 | }) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const join = require('path').join 2 | const webpack = require('webpack') 3 | 4 | const include = join(__dirname, 'src') 5 | 6 | module.exports = { 7 | entry: './src/index', 8 | target: 'node', 9 | mode: 'development', 10 | output: { 11 | path: join(__dirname, 'dist'), 12 | libraryTarget: 'commonjs2', 13 | filename: 'index.js', 14 | }, 15 | devtool: 'source-map', 16 | module: { 17 | rules: [{test: /\.js$/, use: [{loader: 'babel-loader'}], include}], 18 | }, 19 | plugins: [new webpack.optimize.ModuleConcatenationPlugin()], 20 | } 21 | 22 | /* eslint babel/camelcase:0 */ 23 | --------------------------------------------------------------------------------