├── .all-contributorsrc ├── .babelrc ├── .doclets.yml ├── .eslintignore ├── .gitattributes ├── .gitignore ├── .npmrc ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── esdoc.json ├── other ├── CODE_OF_CONDUCT.md ├── EXAMPLES.md ├── MAINTAINING.md ├── ROADMAP.md └── nps-utils-demo.gif ├── package-scripts.js ├── package.json ├── src ├── __snapshots__ │ ├── includePackage.test.js.snap │ └── index.test.js.snap ├── includePackage.test.js ├── index.js └── index.test.js └── yarn.lock /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "nps-utils", 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 | "login": "huy-nguyen", 24 | "name": "Huy Nguyen", 25 | "avatar_url": "https://avatars2.githubusercontent.com/u/7352279?v=3", 26 | "profile": "https://www.huy-nguyen.com/", 27 | "contributions": [ 28 | "doc", 29 | "infra" 30 | ] 31 | }, 32 | { 33 | "login": "gunnx", 34 | "name": "Keith Gunn", 35 | "avatar_url": "https://avatars1.githubusercontent.com/u/1970063?v=4", 36 | "profile": "https://github.com/gunnx", 37 | "contributions": [ 38 | "bug", 39 | "code", 40 | "doc", 41 | "test" 42 | ] 43 | }, 44 | { 45 | "login": "mikecann", 46 | "name": "Mike Cann", 47 | "avatar_url": "https://avatars3.githubusercontent.com/u/215033?v=4", 48 | "profile": "http://www.mikecann.co.uk", 49 | "contributions": [ 50 | "code", 51 | "test" 52 | ] 53 | } 54 | ] 55 | } 56 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "targets": { 5 | "node": 4.5 6 | } 7 | }], 8 | "stage-2" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /.doclets.yml: -------------------------------------------------------------------------------- 1 | dir: src 2 | articles: 3 | - Overview: README.md 4 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .nyc_output 3 | coverage 4 | dist 5 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.js text eol=lf 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | dist 4 | .opt-in 5 | .opt-out 6 | npm-debug.log 7 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=http://registry.npmjs.org/ 2 | save-exact=true 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | cache: 4 | directories: 5 | - node_modules 6 | notifications: 7 | email: false 8 | node_js: 9 | - '6' 10 | script: 11 | - yarn start validate 12 | after_success: 13 | - yarn start report-coverage 14 | - yarn start release 15 | branches: 16 | only: 17 | - master 18 | -------------------------------------------------------------------------------- /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](https://github.com/kentcdodds/nps-utils/releases). 5 | 6 | -------------------------------------------------------------------------------- /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* series 6 | [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 start validate` to validate you've got it working 13 | 4. Create a branch for your PR 14 | 15 | This project uses [`p-s`][p-s] and you can run `npm start` to see what scripts are available. 16 | 17 | ## Add yourself as a contributor 18 | 19 | This project follows the [all contributors][all-contributors] specification. To add yourself to the table of 20 | contributors on the README.md, please use the automated script as part of your PR: 21 | 22 | ```console 23 | npm start addContributor 24 | ``` 25 | 26 | Follow the prompt. If you've already added yourself to the list and are making a new type of contribution, you can run 27 | it again and select the added contribution type. 28 | 29 | ## Committing and Pushing changes 30 | 31 | This project uses [`semantic-release`][semantic-release] to do automatic releases and generate a changelog based on the 32 | commit history. So we follow [a convention][convention] for commit messages. Please follow this convention for your 33 | commit messages. 34 | 35 | You can use `commitizen` to help you to follow [the convention][convention] 36 | 37 | Once you are ready to commit the changes, please use the below commands 38 | 39 | 1. `git add ` 40 | 2. `$ npm start commit` 41 | 42 | ... and follow the instruction of the interactive prompt. 43 | 44 | ### opt into git hooks 45 | 46 | There are git hooks set up with this project that are automatically installed when you install dependencies. They're 47 | really handy, but are turned off by default (so as to not hinder new contributors). You can opt into these by creating 48 | a file called `.opt-in` at the root of the project and putting this inside: 49 | 50 | ``` 51 | commit-msg 52 | pre-commit 53 | ``` 54 | 55 | ## Help needed 56 | 57 | Please checkout the [ROADMAP.md][ROADMAP] and raise an issue to discuss 58 | any of the items in the want to do or might do list. 59 | 60 | Also, please watch the repo and respond to questions/bug reports/feature requests! Thanks! 61 | 62 | [egghead]: https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github 63 | [semantic-release]: https://npmjs.com/package/semantic-release 64 | [convention]: https://github.com/conventional-changelog/conventional-changelog-angular/blob/ed32559941719a130bb0327f886d6a32a8cbc2ba/convention.md 65 | [all-contributors]: https://github.com/kentcdodds/all-contributors 66 | [ROADMAP]: ./ROADMAP.md 67 | [p-s]: https://npmjs.com/package/p-s 68 | -------------------------------------------------------------------------------- /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 | # nps-utils 2 | 3 | Utilities for [nps][nps] (npm-package-scripts) 4 | 5 | [![Build Status][build-badge]][build] 6 | [![Code Coverage][coverage-badge]][coverage] 7 | [![Dependencies][dependencyci-badge]][dependencyci] 8 | [![version][version-badge]][package] 9 | [![downloads][downloads-badge]][npm-stat] 10 | [![MIT License][license-badge]][LICENSE] 11 | 12 | [![All Contributors](https://img.shields.io/badge/all_contributors-4-orange.svg?style=flat-square)](#contributors) 13 | [![PRs Welcome][prs-badge]][prs] 14 | [![Donate][donate-badge]][donate] 15 | [![Code of Conduct][coc-badge]][coc] 16 | [![Roadmap][roadmap-badge]][roadmap] 17 | [![Examples][examples-badge]][examples] 18 | 19 | [![Watch on GitHub][github-watch-badge]][github-watch] 20 | [![Star on GitHub][github-star-badge]][github-star] 21 | [![Tweet][twitter-badge]][twitter] 22 | 23 | Sponsor 24 | 25 | ## The problem 26 | 27 | [nps][nps] is a great package to empower your scripts and there are some common 28 | things you wind up doing to keep your `package-scripts.js` file clean, useful, 29 | and maintainable. So you wind up duplicating utility functions across projects. 30 | 31 | ## This solution 32 | 33 | This has several utility functions you'll often want when using `nps`. 34 | 35 | Check out what the `concurrent` and `runInNewWindow` methods can do: 36 | 37 | 38 | concurrent gif 39 | 40 | 41 | ## Installation 42 | 43 | This module is distributed via [npm][npm] which is bundled with [node][node] and 44 | should be installed as one of your project's `devDependencies`: 45 | 46 | ``` 47 | npm install --save-dev nps-utils 48 | ``` 49 | 50 | ## Usage 51 | 52 | You'll most likely use this in your `package-scripts.js` file: 53 | 54 | ```javascript 55 | const npsUtils = require('nps-utils') 56 | 57 | module.exports = { 58 | scripts: { 59 | validate: npsUtils.concurrent.nps('lint', 'build', 'test --coverage'), 60 | lint: 'eslint .', 61 | build: 'webpack --env.production', 62 | test: 'jest' 63 | } 64 | } 65 | ``` 66 | 67 | ### Available methods: 68 | 69 | API docs can be found [here](https://doc.esdoc.org/github.com/kentcdodds/nps-utils/) 70 | 71 | - [concurrent](https://doc.esdoc.org/github.com/kentcdodds/nps-utils/function/index.html#static-function-concurrent) 72 | - [series](https://doc.esdoc.org/github.com/kentcdodds/nps-utils/function/index.html#static-function-series) 73 | - [runInNewWindow](https://doc.esdoc.org/github.com/kentcdodds/nps-utils/function/index.html#static-function-runInNewWindow) 74 | - [rimraf](https://doc.esdoc.org/github.com/kentcdodds/nps-utils/function/index.html#static-function-rimraf) 75 | - [ifWindows](https://doc.esdoc.org/github.com/kentcdodds/nps-utils/function/index.html#static-function-ifWindows) 76 | - [ifNotWindows](https://doc.esdoc.org/github.com/kentcdodds/nps-utils/function/index.html#static-function-ifNotWindows) 77 | - [copy](https://doc.esdoc.org/github.com/kentcdodds/nps-utils/function/index.html#static-function-copy) 78 | - [ncp](https://doc.esdoc.org/github.com/kentcdodds/nps-utils/function/index.html#static-function-ncp) 79 | - [mkdirp](https://doc.esdoc.org/github.com/kentcdodds/nps-utils/function/index.html#static-function-mkdirp) 80 | - [open](https://doc.esdoc.org/github.com/kentcdodds/nps-utils/function/index.html#static-function-open) 81 | - [crossEnv](https://doc.esdoc.org/github.com/kentcdodds/nps-utils/function/index.html#static-function-crossEnv) 82 | - [includePackage](https://doc.esdoc.org/github.com/kentcdodds/nps-utils/function/index.html#static-function-includePackage) 83 | 84 | `nps` also exports [`common-tags`][common-tags] as `commonTags` which can be 85 | really helpful for long scripts or descriptions. 86 | 87 | Or, see the JSDoc right in 88 | [the source code](https://github.com/kentcdodds/nps-utils/blob/master/src/index.js) 😎 89 | 90 | ## Inspiration 91 | 92 | This package was inspired by the removal of `--parallel` from `p-s` 93 | [here](https://github.com/kentcdodds/p-s/pull/94). 94 | 95 | ## Other Solutions 96 | 97 | I am unaware of other solutions, but if you come across any, please add a PR to 98 | list them here! 99 | 100 | ## Contributors 101 | 102 | Thanks goes to these people ([emoji key][emojis]): 103 | 104 | 105 | | [
Kent C. Dodds](https://kentcdodds.com)
[💻](https://github.com/kentcdodds/nps-utils/commits?author=kentcdodds) [📖](https://github.com/kentcdodds/nps-utils/commits?author=kentcdodds) 🚇 [⚠️](https://github.com/kentcdodds/nps-utils/commits?author=kentcdodds) | [
Huy Nguyen](https://www.huy-nguyen.com/)
[📖](https://github.com/kentcdodds/nps-utils/commits?author=huy-nguyen) 🚇 | [
Keith Gunn](https://github.com/gunnx)
[🐛](https://github.com/kentcdodds/nps-utils/issues?q=author%3Agunnx) [💻](https://github.com/kentcdodds/nps-utils/commits?author=gunnx) [📖](https://github.com/kentcdodds/nps-utils/commits?author=gunnx) [⚠️](https://github.com/kentcdodds/nps-utils/commits?author=gunnx) | [
Mike Cann](http://www.mikecann.co.uk)
[💻](https://github.com/kentcdodds/nps-utils/commits?author=mikecann) [⚠️](https://github.com/kentcdodds/nps-utils/commits?author=mikecann) | 106 | | :---: | :---: | :---: | :---: | 107 | 108 | 109 | This project follows the [all-contributors][all-contributors] specification. Contributions of any kind welcome! 110 | 111 | ## LICENSE 112 | 113 | MIT 114 | 115 | [npm]: https://www.npmjs.com/ 116 | [node]: https://nodejs.org 117 | [build-badge]: https://img.shields.io/travis/kentcdodds/nps-utils.svg?style=flat-square 118 | [build]: https://travis-ci.org/kentcdodds/nps-utils 119 | [coverage-badge]: https://img.shields.io/codecov/c/github/kentcdodds/nps-utils.svg?style=flat-square 120 | [coverage]: https://codecov.io/github/kentcdodds/nps-utils 121 | [dependencyci-badge]: https://dependencyci.com/github/kentcdodds/nps-utils/badge?style=flat-square 122 | [dependencyci]: https://dependencyci.com/github/kentcdodds/nps-utils 123 | [version-badge]: https://img.shields.io/npm/v/nps-utils.svg?style=flat-square 124 | [package]: https://www.npmjs.com/package/nps-utils 125 | [downloads-badge]: https://img.shields.io/npm/dm/nps-utils.svg?style=flat-square 126 | [npm-stat]: http://npm-stat.com/charts.html?package=nps-utils&from=2016-04-01 127 | [license-badge]: https://img.shields.io/npm/l/nps-utils.svg?style=flat-square 128 | [license]: https://github.com/kentcdodds/nps-utils/blob/master/other/LICENSE 129 | [prs-badge]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square 130 | [prs]: http://makeapullrequest.com 131 | [donate-badge]: https://img.shields.io/badge/$-support-green.svg?style=flat-square 132 | [donate]: http://kcd.im/donate 133 | [coc-badge]: https://img.shields.io/badge/code%20of-conduct-ff69b4.svg?style=flat-square 134 | [coc]: https://github.com/kentcdodds/nps-utils/blob/master/other/CODE_OF_CONDUCT.md 135 | [roadmap-badge]: https://img.shields.io/badge/%F0%9F%93%94-roadmap-CD9523.svg?style=flat-square 136 | [roadmap]: https://github.com/kentcdodds/nps-utils/blob/master/other/ROADMAP.md 137 | [examples-badge]: https://img.shields.io/badge/%F0%9F%92%A1-examples-8C8E93.svg?style=flat-square 138 | [examples]: https://github.com/kentcdodds/nps-utils/blob/master/other/EXAMPLES.md 139 | [github-watch-badge]: https://img.shields.io/github/watchers/kentcdodds/nps-utils.svg?style=social 140 | [github-watch]: https://github.com/kentcdodds/nps-utils/watchers 141 | [github-star-badge]: https://img.shields.io/github/stars/kentcdodds/nps-utils.svg?style=social 142 | [github-star]: https://github.com/kentcdodds/nps-utils/stargazers 143 | [twitter]: https://twitter.com/intent/tweet?text=Check%20out%20nps-utils!%20https://github.com/kentcdodds/nps-utils%20%F0%9F%91%8D 144 | [twitter-badge]: https://img.shields.io/twitter/url/https/github.com/kentcdodds/nps-utils.svg?style=social 145 | [emojis]: https://github.com/kentcdodds/all-contributors#emoji-key 146 | [all-contributors]: https://github.com/kentcdodds/all-contributors 147 | [nps]: https://npmjs.com/package/nps 148 | [doclet]: https://doclets.io/kentcdodds/nps-utils/master 149 | [common-tags]: https://npmjs.com/package/common-tags 150 | -------------------------------------------------------------------------------- /esdoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "source": "./src", 3 | "destination": "./docs" 4 | } 5 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /other/MAINTAINING.md: -------------------------------------------------------------------------------- 1 | # Maintaining 2 | 3 | This is documentation for maintainers of this project. 4 | 5 | ## Code of Conduct 6 | 7 | Please review, understand, and be an example of it. Violations of the code of conduct are 8 | taken seriously, even (especially) for maintainers. 9 | 10 | ## Issues 11 | 12 | We want to support and build the community. We do that best by helping people learn to solve 13 | their own problems. We have an issue template and hopefully most folks follow it. If it's 14 | not clear what the issue is, invite them to create a minimal reproduction of what they're trying 15 | to accomplish or the bug they think they've found. 16 | 17 | Once it's determined that a code change is necessary, point people to 18 | [makeapullrequest.com](http://makeapullrequest.com) and invite them to make a pull request. 19 | If they're the one who needs the feature, they're the one who can build it. If they need 20 | some hand holding and you have time to lend a hand, please do so. It's an investment into 21 | another human being, and an investment into a potential maintainer. 22 | 23 | Remember that this is open source, so the code is not yours, it's ours. If someone needs a change 24 | in the codebase, you don't have to make it happen yourself. Commit as much time to the project 25 | as you want/need to. Nobody can ask any more of you than that. 26 | 27 | ## Pull Requests 28 | 29 | As a maintainer, you're fine to make your branches on the main repo or on your own fork. Either 30 | way is fine. 31 | 32 | When we receive a pull request, a travis build is kicked off automatically (see the `.travis.yml` 33 | for what runs in the travis build). We avoid merging anything that breaks the travis build. 34 | 35 | Please review PRs and focus on the code rather than the individual. You never know when this is 36 | someone's first ever PR and we want their experience to be as positive as possible, so be 37 | uplifting and constructive. 38 | 39 | When you merge the pull request, 99% of the time you should use the 40 | [Squash and merge](https://help.github.com/articles/merging-a-pull-request/) feature. This keeps 41 | our git history clean, but more importantly, this allows us to make any necessary changes to the 42 | commit message so we release what we want to release. See the next section on Releases for more 43 | about that. 44 | 45 | ## Release 46 | 47 | Our releases are automatic. They happen whenever code lands into `master`. A travis build gets 48 | kicked off and if it's successful, a tool called 49 | [`semantic-release`](https://github.com/semantic-release/semantic-release) is used to 50 | automatically publish a new release to npm as well as a changelog to GitHub. It is only able to 51 | determine the version and whether a release is necessary by the git commit messages. With this 52 | in mind, **please brush up on [the commit message convention][commit] which drives our releases.** 53 | 54 | > One important note about this: Please make sure that commit messages do NOT contain the words 55 | > "BREAKING CHANGE" in them unless we want to push a major version. I've been burned by this 56 | > more than once where someone will include "BREAKING CHANGE: None" and it will end up releasing 57 | > a new major version. Not a huge deal honestly, but kind of annoying... 58 | 59 | ## Thanks! 60 | 61 | Thank you so much for helping to maintain this project! 62 | 63 | [commit]: https://github.com/conventional-changelog-archived-repos/conventional-changelog-angular/blob/ed32559941719a130bb0327f886d6a32a8cbc2ba/convention.md 64 | -------------------------------------------------------------------------------- /other/ROADMAP.md: -------------------------------------------------------------------------------- 1 | # Project Roadmap 2 | 3 | ## Want to do 4 | 5 | ## Might do 6 | 7 | ## Wont do 8 | -------------------------------------------------------------------------------- /other/nps-utils-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kentcdodds/nps-utils/b20c1db9dd79945ee255eff27897d26f763dfd98/other/nps-utils-demo.gif -------------------------------------------------------------------------------- /package-scripts.js: -------------------------------------------------------------------------------- 1 | require('babel-register') // eslint-disable-line import/no-unassigned-import 2 | const path = require('path') 3 | const {oneLine} = require('common-tags') 4 | const {concurrent, series, open} = require('./src') 5 | 6 | module.exports = { 7 | scripts: { 8 | commit: { 9 | description: oneLine` 10 | This uses commitizen to help us generate 11 | well formatted commit messages 12 | `, 13 | script: 'git-cz', 14 | }, 15 | test: { 16 | default: `jest --coverage`, 17 | watch: 'jest --watch', 18 | }, 19 | openCoverage: open( 20 | `file://${path.resolve('coverage/lcov-report/index.html')}` 21 | ), 22 | build: { 23 | description: 'delete the dist directory and run babel to build the files', 24 | script: oneLine` 25 | rimraf dist && babel --copy-files 26 | --out-dir dist --ignore *.test.js src 27 | `, 28 | }, 29 | lint: { 30 | description: 'lint the entire project', 31 | script: 'eslint .', 32 | }, 33 | reportCoverage: { 34 | description: oneLine` 35 | Report coverage stats to codecov. 36 | This should be run after the \`test\` script 37 | `, 38 | script: 'codecov', 39 | }, 40 | release: { 41 | description: oneLine` 42 | We automate releases with semantic-release. 43 | This should only be run on travis 44 | `, 45 | script: series( 46 | 'semantic-release pre', 47 | 'npm publish', 48 | 'semantic-release post' 49 | ), 50 | }, 51 | validate: { 52 | description: oneLine` 53 | This runs several scripts to make sure things look 54 | good before committing or on clean install 55 | `, 56 | script: concurrent.nps('lint', 'build', 'test'), 57 | }, 58 | addContributor: { 59 | description: 'When new people contribute to the project, run this', 60 | script: 'all-contributors add', 61 | }, 62 | generateContributors: { 63 | description: 'Update the badge and contributors table', 64 | script: 'all-contributors generate', 65 | }, 66 | }, 67 | options: { 68 | silent: false, 69 | }, 70 | } 71 | // this is not transpiled 72 | /* 73 | eslint 74 | max-len: 0, 75 | comma-dangle: [ 76 | 2, 77 | { 78 | arrays: 'always-multiline', 79 | objects: 'always-multiline', 80 | functions: 'never' 81 | } 82 | ] 83 | */ 84 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nps-utils", 3 | "version": "0.0.0-semantically-released", 4 | "description": "Utilities for nps (npm-package-scripts)", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "start": "nps", 8 | "test": "nps test", 9 | "commitmsg": "opt --in commit-msg --exec \"validate-commit-msg\"", 10 | "precommit": "opt --in pre-commit --exec \"lint-staged && npm start validate\"" 11 | }, 12 | "files": [ 13 | "dist" 14 | ], 15 | "keywords": [], 16 | "author": "Kent C. Dodds (http://kentcdodds.com/)", 17 | "license": "MIT", 18 | "dependencies": { 19 | "any-shell-escape": "^0.1.1", 20 | "common-tags": "^1.4.0", 21 | "concurrently": "^3.4.0", 22 | "cpy-cli": "^1.0.1", 23 | "cross-env": "^3.1.4", 24 | "is-windows": "^1.0.0", 25 | "mkdirp": "^0.5.1", 26 | "ncp": "2.0.0", 27 | "opn-cli": "^3.1.0", 28 | "rimraf": "^2.6.1" 29 | }, 30 | "devDependencies": { 31 | "all-contributors-cli": "^4.0.1", 32 | "babel-cli": "^6.18.0", 33 | "babel-jest": "^19.0.0", 34 | "babel-preset-env": "^1.1.11", 35 | "babel-preset-stage-2": "^6.18.0", 36 | "babel-register": "^6.23.0", 37 | "codecov": "^1.0.1", 38 | "commitizen": "^2.8.6", 39 | "cz-conventional-changelog": "^2.0.0", 40 | "esdoc": "^0.5.2", 41 | "eslint": "^3.16.1", 42 | "eslint-config-kentcdodds": "^12.0.0", 43 | "husky": "^0.13.1", 44 | "jest-cli": "^19.0.2", 45 | "lint-staged": "^3.3.1", 46 | "nps": "^5.0.3", 47 | "opt-cli": "^1.5.1", 48 | "prettier-eslint-cli": "^3.1.2", 49 | "semantic-release": "^6.3.6", 50 | "validate-commit-msg": "^2.8.2" 51 | }, 52 | "eslintConfig": { 53 | "extends": [ 54 | "kentcdodds", 55 | "kentcdodds/jest" 56 | ], 57 | "rules": { 58 | "max-len": [ 59 | 2, 60 | 80 61 | ] 62 | } 63 | }, 64 | "jest": { 65 | "testEnvironment": "node", 66 | "coverageThreshold": { 67 | "global": { 68 | "branches": 100, 69 | "functions": 100, 70 | "lines": 100, 71 | "statements": 100 72 | } 73 | } 74 | }, 75 | "config": { 76 | "commitizen": { 77 | "path": "node_modules/cz-conventional-changelog" 78 | } 79 | }, 80 | "lint-staged": { 81 | "*.js": [ 82 | "prettier-eslint --write", 83 | "git add" 84 | ] 85 | }, 86 | "repository": "https://github.com/kentcdodds/nps-utils.git", 87 | "bugs": { 88 | "url": "https://github.com/kentcdodds/nps-utils/issues" 89 | }, 90 | "homepage": "https://github.com/kentcdodds/nps-utils#readme" 91 | } 92 | -------------------------------------------------------------------------------- /src/__snapshots__/includePackage.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`description nodes respected 1`] = ` 4 | Object { 5 | "foo": "cd scripts/subdir && npm start foo", 6 | "nested": Object { 7 | "description": "this is nested", 8 | "script": "cd scripts/subdir && npm start nested && cd \\"../..\\"", 9 | }, 10 | } 11 | `; 12 | 13 | exports[`given complex package, correct includes all scripts 1`] = ` 14 | Object { 15 | "bar": "cd scripts && npm start bar", 16 | "default": "cd scripts && npm start default", 17 | "foo": "cd scripts && npm start foo", 18 | "nested": Object { 19 | "aaa": "cd scripts && npm start nested.aaa", 20 | "bbb": "cd scripts && npm start nested.bbb", 21 | "default": "cd scripts && npm start nested.default", 22 | }, 23 | } 24 | `; 25 | -------------------------------------------------------------------------------- /src/__snapshots__/index.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`commonTags as darwin 1`] = `"helpful stuff my good friend"`; 4 | 5 | exports[`commonTags as win32 1`] = `"helpful stuff my good friend"`; 6 | 7 | exports[`concurrent as darwin 1`] = `"node node_modules/concurrently/src/main.js --kill-others-on-fail --prefix-colors \\"bgBlue.bold,bgWhite.black.dim\\" --prefix \\"[{name}]\\" --names \\"test,lint\\" 'echo test' 'echo lint'"`; 8 | 9 | exports[`concurrent as win32 1`] = `"node node_modules/concurrently/src/main.js --kill-others-on-fail --prefix-colors \\"bgBlue.bold,bgWhite.black.dim\\" --prefix \\"[{name}]\\" --names \\"test,lint\\" \\"echo test\\" \\"echo lint\\""`; 10 | 11 | exports[`concurrent.nps as darwin 1`] = `"node node_modules/concurrently/src/main.js --kill-others-on-fail --prefix-colors \\"bgMagenta.bold,bgBlack.bold,bgCyan.bold,bgGreen.dim\\" --prefix \\"[{name}]\\" --names \\"test,lint,build.app,validate\\" 'nps test' 'nps lint' 'nps \\"build.app --silent\\"' 'nps validate'"`; 12 | 13 | exports[`concurrent.nps as win32 1`] = `"node node_modules/concurrently/src/main.js --kill-others-on-fail --prefix-colors \\"bgMagenta.bold,bgBlack.bold,bgCyan.bold,bgGreen.dim\\" --prefix \\"[{name}]\\" --names \\"test,lint,build.app,validate\\" \\"nps test\\" \\"nps lint\\" \\"nps \\"\\"build.app --silent\\"\\"\\" \\"nps validate\\""`; 14 | 15 | exports[`copy as darwin 1`] = `"node node_modules/cpy-cli/cli.js \\"**/*.html\\" \\"../dist/\\" --cwd=src --parents"`; 16 | 17 | exports[`copy as win32 1`] = `"node node_modules/cpy-cli/cli.js \\"**/*.html\\" \\"../dist/\\" --cwd=src --parents"`; 18 | 19 | exports[`crossEnv as darwin 1`] = `"node node_modules/cross-env/bin/cross-env.js NODE_ENV=test jest"`; 20 | 21 | exports[`crossEnv as win32 1`] = `"node node_modules/cross-env/bin/cross-env.js NODE_ENV=test jest"`; 22 | 23 | exports[`getBin as darwin 1`] = `"node_modules/rimraf/bin.js"`; 24 | 25 | exports[`getBin as win32 1`] = `"node_modules/rimraf/bin.js"`; 26 | 27 | exports[`ifNotWindows as darwin 1`] = `"echo main"`; 28 | 29 | exports[`ifNotWindows as win32 1`] = `"echo alternate"`; 30 | 31 | exports[`ifWindows as darwin 1`] = `"echo alternate"`; 32 | 33 | exports[`ifWindows as win32 1`] = `"echo main"`; 34 | 35 | exports[`mkdirp as darwin 1`] = `"node node_modules/mkdirp/bin/cmd.js /tmp/foo/bar/baz"`; 36 | 37 | exports[`mkdirp as win32 1`] = `"node node_modules/mkdirp/bin/cmd.js /tmp/foo/bar/baz"`; 38 | 39 | exports[`ncp as darwin 1`] = `"node node_modules/ncp/bin/ncp src dist"`; 40 | 41 | exports[`ncp as win32 1`] = `"node node_modules/ncp/bin/ncp src dist"`; 42 | 43 | exports[`open as darwin 1`] = `"node node_modules/opn-cli/cli.js http://kentcdodds.com -- \\"google chrome\\" --incognito"`; 44 | 45 | exports[`open as win32 1`] = `"node node_modules/opn-cli/cli.js http://kentcdodds.com -- \\"google chrome\\" --incognito"`; 46 | 47 | exports[`rimraf as darwin 1`] = `"node node_modules/rimraf/bin.js build"`; 48 | 49 | exports[`rimraf as win32 1`] = `"node node_modules/rimraf/bin.js build"`; 50 | 51 | exports[`runInNewWindow as darwin 1`] = `"osascript -e 'tell application \\"Terminal\\"' -e 'tell application \\"System Events\\" to keystroke \\"t\\" using {command down}' -e 'do script \\"cd && echo hi\\" in front window' -e 'end tell'"`; 52 | 53 | exports[`runInNewWindow as win32 1`] = `"start cmd /k \\"cd && echo hi\\""`; 54 | 55 | exports[`runInNewWindow.nps as darwin 1`] = `"osascript -e 'tell application \\"Terminal\\"' -e 'tell application \\"System Events\\" to keystroke \\"t\\" using {command down}' -e 'do script \\"cd && node node_modules/.bin/nps /\\"lint --cache/\\"\\" in front window' -e 'end tell'"`; 56 | 57 | exports[`runInNewWindow.nps as win32 1`] = `"start cmd /k \\"cd && node node_modules/.bin/nps /\\"lint --cache/\\"\\""`; 58 | 59 | exports[`series as darwin 1`] = `"echo hey && echo hi && echo there"`; 60 | 61 | exports[`series as win32 1`] = `"echo hey && echo hi && echo there"`; 62 | 63 | exports[`series.nps as darwin 1`] = `"nps test && nps lint.src && nps \\"lint.scripts --cache\\" && nps \\"build --fast\\""`; 64 | 65 | exports[`series.nps as win32 1`] = `"nps test && nps lint.src && nps \\"lint.scripts --cache\\" && nps \\"build --fast\\""`; 66 | 67 | exports[`setColors as darwin 1`] = `"node node_modules/concurrently/src/main.js --kill-others-on-fail --prefix-colors \\"white.bgBlue.bold,black.bgYellow.dim\\" --prefix \\"[{name}]\\" --names \\"lint,build\\" 'nps lint' 'nps build'"`; 68 | 69 | exports[`setColors as win32 1`] = `"node node_modules/concurrently/src/main.js --kill-others-on-fail --prefix-colors \\"white.bgBlue.bold,black.bgYellow.dim\\" --prefix \\"[{name}]\\" --names \\"lint,build\\" \\"nps lint\\" \\"nps build\\""`; 70 | 71 | exports[`shellEscape as darwin 1`] = `"'testing $:{}()[]ą^ęńł'\\"'\\"'÷//'\\"'\\"'\`\\" '"`; 72 | 73 | exports[`shellEscape as win32 1`] = `"\\"testing $:{}()[]ą^ęńł'÷//'\`\\"\\" \\""`; 74 | -------------------------------------------------------------------------------- /src/includePackage.test.js: -------------------------------------------------------------------------------- 1 | import {includePackage} from './index' 2 | 3 | test('given just a string, it looks for scripts at a default location', () => { 4 | 5 | jest.mock(`./packages/foo/package-scripts.js`, () => ({ 6 | scripts: { 7 | default: 'echo foo', 8 | }, 9 | }), {virtual: true}) 10 | 11 | jest.mock(`./packages/bar/package-scripts.js`, () => ({ 12 | scripts: { 13 | default: 'echo bar', 14 | }, 15 | }), {virtual: true}) 16 | 17 | expect(includePackage('foo')) 18 | .toEqual({default: 'cd packages/foo && npm start default'}) 19 | 20 | expect(includePackage('bar')) 21 | .toEqual({default: 'cd packages/bar && npm start default'}) 22 | 23 | }) 24 | 25 | test('given an explicit path, it uses that', () => { 26 | 27 | jest.mock(`./hello/explicit-foo.js`, () => ({ 28 | scripts: { 29 | default: 'echo foo', 30 | }, 31 | }), {virtual: true}) 32 | 33 | jest.mock(`./explicit-bar.js`, () => ({ 34 | scripts: { 35 | default: 'echo bar', 36 | }, 37 | }), {virtual: true}) 38 | 39 | expect(includePackage({path: `./hello/explicit-foo.js`})) 40 | .toEqual({default: 'cd hello && npm start default'}) 41 | 42 | expect(includePackage({path: `./explicit-bar.js`})) 43 | .toEqual({default: 'cd && npm start default'}) 44 | 45 | }) 46 | 47 | test('given complex package, correct includes all scripts', () => { 48 | 49 | jest.mock(`./scripts/complex-package.js`, () => ({ 50 | scripts: { 51 | default: 'nps foo', 52 | foo: 'echo foo', 53 | bar: 'echo foo', 54 | nested: { 55 | default: 'nps nested.bbb', 56 | aaa: 'echo aaa', 57 | bbb: 'echo bbb', 58 | }, 59 | }, 60 | }), {virtual: true}) 61 | 62 | expect(includePackage({path: `./scripts/complex-package.js`})) 63 | .toMatchSnapshot() 64 | 65 | }) 66 | 67 | test('description nodes respected', () => { 68 | 69 | jest.mock(`./scripts/subdir/complex-package-description.js`, () => ({ 70 | scripts: { 71 | foo: 'echo foo', 72 | nested: { 73 | script: 'echo hia', 74 | description: 'this is nested', 75 | }, 76 | }, 77 | }), {virtual: true}) 78 | 79 | 80 | expect(includePackage({path: './scripts/subdir/' + 81 | 'complex-package-description.js'})) 82 | .toMatchSnapshot() 83 | 84 | }) 85 | 86 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import * as commonTags from 'common-tags' 3 | 4 | let defaultColors = [ 5 | 'bgBlue.bold', 6 | 'bgMagenta.bold', 7 | 'bgGreen.bold', 8 | 'bgBlack.bold', 9 | 'bgCyan.bold', 10 | 'bgRed.bold', 11 | 'bgWhite.bold', 12 | 'bgYellow.bold', 13 | // TODO: add more colors that look good? 14 | ] 15 | 16 | export { 17 | concurrent, 18 | series, 19 | runInNewWindow, 20 | rimraf, 21 | ifWindows, 22 | ifNotWindows, 23 | copy, 24 | ncp, 25 | mkdirp, 26 | open, 27 | crossEnv, 28 | commonTags, 29 | setColors, 30 | includePackage, 31 | shellEscape, 32 | getBin, 33 | } 34 | 35 | /** 36 | * Set your own colours used by nps scripts 37 | * @param {string[]} colors - Array of color strings supported by concurrent 38 | * @example 39 | * setColors(['white.bgblue.bold', 'black.bgYellow.dim', 'white.bgGreen']) 40 | */ 41 | function setColors(colors) { 42 | defaultColors = colors 43 | } 44 | /** 45 | * Accepts any number of scripts, filters out any 46 | * falsy ones and joins them with ' && ' 47 | * @param {...string} scripts - Any number of strings representing commands 48 | * @example 49 | * // returns 'eslint && jest && webpack --env.production' 50 | * series('eslint', 'jest', 'webpack --env.production') 51 | * @return {string} - the command that will execute the given scripts in series 52 | */ 53 | function series(...scripts) { 54 | return scripts.filter(Boolean).join(' && ') 55 | } 56 | 57 | /** 58 | * Accepts any number of nps script names, filters out 59 | * any falsy ones, prepends `nps` to them, and passes 60 | * the that to `series` 61 | * @param {...string} scriptNames - the script names to run 62 | * // returns 'nps lint && nps "test --coverage" && nps build' 63 | * series.nps('lint', 'test --coverage', 'build') 64 | * @return {string} - the command that will execute the nps scripts in series 65 | */ 66 | series.nps = function seriesNPS(...scriptNames) { 67 | return series( 68 | ...scriptNames 69 | .filter(Boolean) 70 | .map(scriptName => scriptName.trim()) 71 | .filter(Boolean) 72 | .map(scriptName => `nps ${quoteScript(scriptName)}`), 73 | ) 74 | } 75 | 76 | /** 77 | * A concurrent script object 78 | * @typedef {Object|string} ConcurrentScript 79 | * @property {string} script - the command to run 80 | * @property {string} color - the color to use 81 | * (see concurrently's docs for valid values) 82 | */ 83 | /** 84 | * An object of concurrent script objects 85 | * @typedef {Object.} ConcurrentScripts 86 | */ 87 | 88 | /** 89 | * Generates a command that uses `concurrently` to run 90 | * scripts concurrently. Adds a few flags to make it 91 | * behave as you probably want (like --kill-others-on-fail). 92 | * In addition, it adds color and labels where the color 93 | * can be specified or is defaulted and the label is based 94 | * on the key for the script. 95 | * @param {ConcurrentScripts} scripts - the scripts to run 96 | * note: this function filters out falsy values :) 97 | * @example 98 | * // returns a bit of a long script that can vary slightly 99 | * // based on your environment... :) 100 | * concurrent({ 101 | * lint: { 102 | * script: 'eslint .', 103 | * color: 'bgGreen.white.dim', 104 | * }, 105 | * test: 'jest', 106 | * build: { 107 | * script: 'webpack' 108 | * } 109 | * }) 110 | * @return {string} - the command to run 111 | */ 112 | function concurrent(scripts) { 113 | const {colors, scripts: quotedScripts, names} = Object.keys(scripts) 114 | .reduce(reduceScripts, { 115 | colors: [], 116 | scripts: [], 117 | names: [], 118 | }) 119 | const flags = [ 120 | '--kill-others-on-fail', 121 | `--prefix-colors "${colors.join(',')}"`, 122 | '--prefix "[{name}]"', 123 | `--names "${names.join(',')}"`, 124 | shellEscape(quotedScripts), 125 | ] 126 | const concurrently = runBin('concurrently') 127 | return `${concurrently} ${flags.join(' ')}` 128 | 129 | function reduceScripts(accumulator, scriptName, index) { 130 | let scriptObj = scripts[scriptName] 131 | if (!scriptObj) { 132 | return accumulator 133 | } else if (typeof scriptObj === 'string') { 134 | scriptObj = {script: scriptObj} 135 | } 136 | const { 137 | script, 138 | color = defaultColors[index % defaultColors.length], 139 | } = scriptObj 140 | if (!script) { 141 | return accumulator 142 | } 143 | accumulator.names.push(scriptName) 144 | accumulator.colors.push(color) 145 | accumulator.scripts.push(script) 146 | return accumulator 147 | } 148 | } 149 | 150 | /** 151 | * Accepts any number of nps script names, filters out 152 | * any falsy ones, prepends `nps` to them, and passes 153 | * the that to `concurrent` 154 | * @param {...string} scriptNames - the script names to run 155 | * @example 156 | * // will basically return `nps lint & nps "test --coverage" & nps build` 157 | * // but with the concurrently command and relevant flags to make 158 | * // it super awesome with colors and whatnot. :) 159 | * concurrent.nps('lint', 'test --coverage', 'build') 160 | * @return {string} the command to run 161 | */ 162 | concurrent.nps = function concurrentNPS(...scriptNames) { 163 | return concurrent( 164 | scriptNames.map(mapNPSScripts).reduce(reduceNPSScripts, {}), 165 | ) 166 | 167 | function mapNPSScripts(scriptName, index) { 168 | const color = defaultColors[index] 169 | if (!Boolean(scriptName)) { 170 | return undefined 171 | } else if (typeof scriptName === 'string') { 172 | return {script: scriptName, color} 173 | } else { 174 | return Object.assign({color}, scriptName) 175 | } 176 | } 177 | 178 | function reduceNPSScripts(scripts, scriptObj) { 179 | if (!scriptObj) { 180 | return scripts 181 | } 182 | const {color, script} = scriptObj 183 | const [name] = script.split(' ') 184 | scripts[name] = { 185 | script: `nps ${quoteScript(script.trim())}`, 186 | color, 187 | } 188 | return scripts 189 | } 190 | } 191 | 192 | /** 193 | * EXPERIMENTAL: THIS DOES NOT CURRENTLY WORK FOR ALL TERMINALS 194 | * Takes a command and returns a version that should run in 195 | * another tab/window of your terminal. Currently only supports 196 | * Windows cmd (new window) and Terminal.app (new tab) 197 | * @param {string} command - the command to run in a new tab/window 198 | * @example 199 | * // returns some voodoo magic to make the terminal do what you want 200 | * runInNewWindow('echo hello') 201 | * @return {string} - the command to run 202 | */ 203 | function runInNewWindow(command) { 204 | return isWindows() ? 205 | `start cmd /k "cd ${process.cwd()} && ${command}"` : 206 | commonTags.oneLine` 207 | osascript 208 | -e 'tell application "Terminal"' 209 | -e 'tell application "System Events" 210 | to keystroke "t" using {command down}' 211 | -e 'do script "cd ${process.cwd()} && ${command}" in front window' 212 | -e 'end tell' 213 | ` 214 | } 215 | 216 | /** 217 | * EXPERIMENTAL: THIS DOES NOT CURRENTLY WORK FOR ALL TERMINALS 218 | * Takes an nps script name and prepends it with a call to nps 219 | * then forwards that to `runInNewWindow` properly escaped. 220 | * @param {string} scriptName - the name of the nps script to run 221 | * @example 222 | * // returns a script that runs 223 | * // `node node_modules/.bin/nps "lint --cache"` 224 | * // in a new tab/window 225 | * runInNewWindow.nps('lint --cache') 226 | * @return {string} - the command to run 227 | */ 228 | runInNewWindow.nps = function runInNewWindowNPS(scriptName) { 229 | const escaped = true 230 | return runInNewWindow( 231 | `node node_modules/.bin/nps ${quoteScript(scriptName, escaped)}`, 232 | ) 233 | } 234 | 235 | /** 236 | * Gets a script that uses the rimraf binary. rimraf 237 | * is a dependency of nps-utils, so you don't need to 238 | * install it yourself. 239 | * @param {string} args - args to pass to rimraf 240 | * learn more from http://npm.im/rimraf 241 | * @return {string} - the command with the rimraf binary 242 | */ 243 | function rimraf(args) { 244 | return `${runBin('rimraf')} ${args}` 245 | } 246 | 247 | /** 248 | * Takes two scripts and returns the first if the 249 | * current environment is windows, and the second 250 | * if the current environment is not windows 251 | * @param {string} script - the script to use for windows 252 | * @param {string} altScript - the script to use for non-windows 253 | * @return {string} - the command to run 254 | */ 255 | function ifWindows(script, altScript) { 256 | return isWindows() ? script : altScript 257 | } 258 | 259 | /** 260 | * Simply calls ifWindows(altScript, script) 261 | * @param {string} script - the script to use for non-windows 262 | * @param {string} altScript - the script to use for windows 263 | * @return {string} - the command to run 264 | */ 265 | function ifNotWindows(script, altScript) { 266 | return ifWindows(altScript, script) 267 | } 268 | 269 | /** 270 | * Gets a script that uses the cpy-cli binary. cpy-cli 271 | * is a dependency of nps-utils, so you don't need to 272 | * install it yourself. 273 | * @param {string} args - args to pass to cpy-cli 274 | * learn more from http://npm.im/cpy-cli 275 | * @return {string} - the command with the cpy-cli binary 276 | */ 277 | function copy(args) { 278 | return `${runBin('cpy-cli', 'cpy')} ${args}` 279 | } 280 | 281 | /** 282 | * Gets a script that uses the ncp binary. ncp 283 | * is a dependency of nps-utils, so you don't need to 284 | * install it yourself. 285 | * @param {string} args - args to pass to ncp 286 | * learn more from http://npm.im/ncp 287 | * @return {string} - the command with the ncp binary 288 | */ 289 | function ncp(args) { 290 | return `${runBin('ncp')} ${args}` 291 | } 292 | 293 | /** 294 | * Gets a script that uses the mkdirp binary. mkdirp 295 | * is a dependency of nps-utils, so you don't need to 296 | * install it yourself. 297 | * @param {string} args - args to pass to mkdirp 298 | * learn more from http://npm.im/mkdirp 299 | * @return {string} - the command with the mkdirp binary 300 | */ 301 | function mkdirp(args) { 302 | return `${runBin('mkdirp')} ${args}` 303 | } 304 | 305 | /** 306 | * Gets a script that uses the opn-cli binary. opn-cli 307 | * is a dependency of nps-utils, so you don't need to 308 | * install it yourself. 309 | * @param {string} args - args to pass to opn-cli 310 | * learn more from http://npm.im/opn-cli 311 | * @return {string} - the command with the opn-cli binary 312 | */ 313 | function open(args) { 314 | return `${runBin('opn-cli', 'opn')} ${args}` 315 | } 316 | 317 | /** 318 | * Gets a script that uses the cross-env binary. cross-env 319 | * is a dependency of nps-utils, so you don't need to 320 | * install it yourself. 321 | * @param {string} args - args to pass to cross-env 322 | * learn more from http://npm.im/cross-env 323 | * @return {string} - the command with the cross-env binary 324 | */ 325 | function crossEnv(args) { 326 | return `${runBin('cross-env')} ${args}` 327 | } 328 | 329 | /** 330 | * The options to pass to includePackage 331 | * @typedef {Object|string} IncludePackageOptions 332 | * @property {string} path - the path to the package scripts 333 | */ 334 | 335 | /** 336 | * Includes the scripts from a sub-package in your repo (for 337 | * yarn workspaces or lerna style projects). 338 | * @param {IncludePackageOptions} packageNameOrOptions - either a 339 | * simple name for the sub-package or an options object where you can 340 | * specify the exact path to the package to include. 341 | * If you just provide the name and not the options object, then the path 342 | * defaults to: ./packages/{package}/package-scripts.js 343 | * @return {any} will return an object of scripts loaded from that package 344 | */ 345 | function includePackage(packageNameOrOptions) { 346 | const packageScriptsPath = typeof packageNameOrOptions === 'string' ? 347 | `./packages/${packageNameOrOptions}/package-scripts.js` : 348 | packageNameOrOptions.path 349 | 350 | const startingDir = process.cwd().split('\\').join('/') 351 | 352 | const relativeDir = path 353 | .relative(startingDir, path.dirname(packageScriptsPath)) 354 | .split('\\') 355 | .join('/') 356 | 357 | const relativeReturn = path 358 | .relative(relativeDir, startingDir) 359 | .split('\\') 360 | .join('/') 361 | 362 | const scripts = require(packageScriptsPath) 363 | 364 | // eslint-disable-next-line 365 | function replace(obj, prefix) { 366 | const retObj = {} 367 | const dot = prefix ? '.' : '' 368 | for (const key in obj) { 369 | if (key === 'description') { 370 | retObj[key] = obj[key] 371 | } else if (key === 'script') { 372 | retObj[key] = series( 373 | `cd ${relativeDir}`, 374 | `npm start ${prefix}`, 375 | `cd "${relativeReturn}"`, 376 | ) 377 | } else if (typeof obj[key] === 'string') { 378 | retObj[key] = series( 379 | `cd ${relativeDir}`, 380 | `npm start ${prefix}${dot}${key}`, 381 | ) 382 | } else { 383 | retObj[key] = Object.assign( 384 | {}, 385 | replace(obj[key], `${prefix}${dot}${key}`, `cd "${startingDir}"`), 386 | ) 387 | } 388 | } 389 | return retObj 390 | } 391 | 392 | return replace(scripts.scripts, '') 393 | } 394 | 395 | // utils 396 | 397 | function quoteScript(script, escaped) { 398 | const quote = escaped ? '\\"' : '"' 399 | const shouldQuote = script.indexOf(' ') !== -1 400 | return shouldQuote ? `${quote}${script}${quote}` : script 401 | } 402 | 403 | /** 404 | * Get the path to one of the bin scripts exported by a package 405 | * @param {string} packageName - name of the npm package 406 | * @param {string} binName=packageName - name of the script 407 | * @returns {string} path, relative to process.cwd() 408 | */ 409 | function getBin(packageName, binName = packageName) { 410 | const packagePath = require.resolve(`${packageName}/package.json`) 411 | const concurrentlyDir = path.dirname(packagePath) 412 | let {bin: binRelativeToPackage} = require(packagePath) 413 | if (typeof binRelativeToPackage === 'object') { 414 | binRelativeToPackage = binRelativeToPackage[binName] 415 | } 416 | const fullBinPath = path.join(concurrentlyDir, binRelativeToPackage) 417 | return path.relative(process.cwd(), fullBinPath) 418 | } 419 | 420 | function runBin(...args) { 421 | return `node ${getBin(...args)}` 422 | } 423 | 424 | function isWindows() { 425 | // lazily require for perf :) 426 | return require('is-windows')() 427 | } 428 | 429 | /** 430 | * Escape a string so the shell expands it to the original. 431 | * @param {string|array} arg - as accepted by any-shell-escape; arrays will 432 | * yield multiple arguments in the shell 433 | * @returns {string} ready to pass to shell 434 | */ 435 | function shellEscape(arg) { 436 | // lazily require for perf :) 437 | return require('any-shell-escape')(arg) 438 | } 439 | 440 | /* 441 | eslint 442 | func-name-matching:0, 443 | global-require:0, 444 | import/no-dynamic-require:0 445 | */ 446 | -------------------------------------------------------------------------------- /src/index.test.js: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | 3 | // all of these tests are run both as darwin and win32 4 | // you provide the key (test name) and the value is a 5 | // function that accepts npsUtils which you can use to 6 | // create the result. 7 | const snapshotTests = { 8 | series: ({series}) => 9 | series('echo hey', null, 'echo hi', undefined, 'echo there', false), 10 | 'series.nps': ({series}) => 11 | series.nps( 12 | 'test', 13 | false, 14 | 'lint.src', 15 | null, 16 | 'lint.scripts --cache', 17 | undefined, 18 | ' ', 19 | 'build --fast', 20 | ), 21 | concurrent: ({concurrent}) => concurrent({ 22 | test: 'echo test', 23 | validate: { 24 | script: false, 25 | }, 26 | lint: {script: 'echo lint', color: 'bgWhite.black.dim'}, 27 | build: false, 28 | cover: undefined, 29 | }), 30 | 'concurrent.nps': ({concurrent}) => 31 | concurrent.nps( 32 | null, 33 | 'test', 34 | undefined, 35 | 'lint', 36 | {script: 'build.app --silent'}, 37 | false, 38 | {script: 'validate', color: 'bgGreen.dim'}, 39 | ), 40 | runInNewWindow: ({runInNewWindow}) => runInNewWindow('echo hi'), 41 | 'runInNewWindow.nps': ({runInNewWindow}) => 42 | runInNewWindow.nps('lint --cache'), 43 | rimraf: ({rimraf}) => rimraf('build'), 44 | ifWindows: ({ifWindows}) => ifWindows('echo main', 'echo alternate'), 45 | ifNotWindows: ({ifNotWindows}) => 46 | ifNotWindows('echo main', 'echo alternate'), 47 | copy: ({copy}) => copy('"**/*.html" "../dist/" --cwd=src --parents'), 48 | ncp: ({ncp}) => ncp('src dist'), 49 | mkdirp: ({mkdirp}) => mkdirp('/tmp/foo/bar/baz'), 50 | open: ({open}) => 51 | open('http://kentcdodds.com -- "google chrome" --incognito'), 52 | crossEnv: ({crossEnv}) => crossEnv('NODE_ENV=test jest'), 53 | commonTags: ({commonTags}) => 54 | commonTags.oneLine` helpful\n stuff\n my good\n friend`, 55 | setColors: ({concurrent, setColors}) => { 56 | setColors(['white.bgBlue.bold', 'black.bgYellow.dim']) 57 | return concurrent.nps('lint', 'build') 58 | }, 59 | shellEscape: ({shellEscape}) => 60 | shellEscape(`testing $:{}()[]ą^ęńł'÷/\\'\`" `), 61 | getBin: ({getBin}) => getBin('rimraf'), 62 | } 63 | 64 | Object.keys(snapshotTests).forEach(testName => { 65 | test(`${testName} as darwin`, () => { 66 | const result = withPlatform('darwin', snapshotTests[testName]) 67 | expect(relativeizePath(result)).toMatchSnapshot() 68 | }) 69 | test(`${testName} as win32`, () => { 70 | const result = withPlatform('win32', snapshotTests[testName]) 71 | expect(relativeizePath(result)).toMatchSnapshot() 72 | }) 73 | }) 74 | 75 | function withPlatform(platform, getResult) { 76 | const originalPlatform = process.platform 77 | process.platform = platform 78 | jest.resetModules() 79 | const freshUtils = require('.') 80 | const result = getResult(freshUtils) 81 | process.platform = originalPlatform 82 | return result 83 | } 84 | 85 | function relativeizePath(stringWithAbsolutePaths) { 86 | // escape string for regexp generation 87 | const escapedPath = path 88 | .resolve(__dirname, '../') 89 | .replace( 90 | new RegExp('[\\-\\[\\]\\/\\{\\}\\(\\)\\*\\+\\?\\.\\\\^\\$\\|]', 'g'), 91 | '\\$&', 92 | ) 93 | 94 | const relativePath = stringWithAbsolutePaths.replace( 95 | new RegExp(escapedPath, 'g'), 96 | '', 97 | ) 98 | 99 | return relativePath.replace(/\\/g, '/') 100 | } 101 | 102 | /* 103 | eslint 104 | global-require:0, 105 | import/no-dynamic-require:0 106 | */ 107 | --------------------------------------------------------------------------------