├── .codeclimate.yml ├── .github └── workflows │ └── test.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE.txt ├── README.md ├── cli.js ├── example ├── message.txt ├── node_modules │ └── .bin │ │ ├── scripty │ │ └── scripty.cmd ├── package.json ├── scripts-win │ └── test.cmd └── scripts │ └── test ├── lib ├── derive-log-level.js ├── derive-log-level.test.js ├── load-option.js ├── load-option.test.js ├── log.js ├── log.test.js ├── optionify.js ├── optionify.test.js ├── resolve-script │ ├── find-executables.js │ ├── find-executables.test.js │ ├── glob-first.js │ ├── glob-first.test.js │ ├── glob-patterns.js │ ├── glob-patterns.test.js │ ├── index.js │ ├── index.test.js │ ├── script-dirs.js │ └── script-dirs.test.js ├── run │ ├── all.js │ ├── dry-run.js │ ├── index.js │ ├── print-script.js │ ├── print-script.test.js │ └── spawn-script.js └── scripty.js ├── package-lock.json ├── package.json ├── scripts-win └── noop.cmd ├── scripts ├── noop └── test │ └── debug └── test ├── decorate-assertions.js ├── fixtures ├── baseball │ ├── batter.rb │ └── pitcher.rb ├── built-in-scripts-win │ └── hello │ │ └── world.cmd ├── built-in-scripts │ └── hello │ │ └── world ├── custom-user-scripts-win │ └── secret.cmd ├── custom-user-scripts │ └── secret ├── modules │ ├── node_modules │ │ └── foo │ │ │ ├── package.json │ │ │ ├── scripts-win │ │ │ ├── foo.cmd │ │ │ └── user.cmd │ │ │ └── scripts │ │ │ ├── foo │ │ │ └── user │ ├── scripts-win │ │ └── user.cmd │ └── scripts │ │ └── user ├── relative-path-loading │ └── package.json ├── unit │ ├── find-executables │ │ ├── exec.rb │ │ ├── exec.sh │ │ ├── file.executable │ │ ├── file.not.executable │ │ └── is-executable │ └── glob-first │ │ ├── test │ │ ├── index.sh │ │ ├── index2 │ │ └── other │ │ ├── test2 │ │ └── test3 │ │ ├── bar.foo │ │ └── index2 ├── user-scripts-win │ ├── args │ │ └── echoer.cmd │ ├── baz │ │ └── .keep │ ├── car │ │ └── index │ ├── dog │ │ └── index │ │ │ └── .keep │ ├── fail.cmd │ ├── foo │ │ └── bar │ ├── parallel │ │ ├── batter.cmd │ │ └── pitcher.cmd │ ├── parent │ │ ├── a.cmd │ │ ├── b.cmd │ │ └── c.cmd │ └── top │ │ └── index.cmd └── user-scripts │ ├── args │ └── echoer │ ├── baz │ └── .keep │ ├── car │ └── index │ ├── dog │ └── index │ │ └── .keep │ ├── exec-dir-wat │ └── .keep │ ├── fail │ ├── foo │ └── bar │ ├── parallel │ ├── batter │ └── pitcher │ ├── parent │ ├── a │ ├── b │ └── c │ ├── stats │ ├── all-x │ ├── group-x │ ├── no-x │ ├── other-x │ └── owner-x │ ├── top │ └── index.rb │ └── train │ ├── explode.sh │ └── index.sh ├── grab-stdio.js ├── is-old-node.js ├── run-scripty.js ├── safe-helper.js ├── safe ├── basic-test.js ├── custom-script-dir-test.js ├── dir-test.js ├── dry-run-test.js ├── example-test.js ├── modules-test.js ├── no-opts-spot-check.js ├── parallel-test.js ├── relative-path-loading.js └── silent-test.js └── unit-helper.js /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | exclude_patterns: 4 | - example/ 5 | - node_modules/ 6 | - test/ 7 | - "**/*.test.js" 8 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: push 3 | 4 | jobs: 5 | test: 6 | runs-on: ${{ matrix.os }}-latest 7 | strategy: 8 | matrix: 9 | os: [ubuntu, macOS, windows] 10 | node: [8, 10, 12, 14, 16, latest] 11 | steps: 12 | - uses: actions/checkout@v3 13 | - uses: actions/setup-node@v3 14 | with: 15 | node-version: ${{ matrix.node }} 16 | cache: 'npm' 17 | - name: Run tests for Node ${{ matrix.node }} on ${{ matrix.os }} 18 | run: npm cit 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | npm-debug.log 3 | .node-version 4 | /coverage 5 | /scripty-*.tgz 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [v3.0.0](https://github.com/testdouble/scripty/tree/v3.0.0) (2025-02-18) 4 | [Full Changelog](https://github.com/testdouble/scripty/compare/v2.1.1...v3.0.0) 5 | 6 | - Deprecate scripty 7 | 8 | ## [v2.0.0](https://github.com/testdouble/scripty/tree/v2.0.0) (2020-03-29) 9 | [Full Changelog](https://github.com/testdouble/scripty/compare/v1.9.1...v2.0.0) 10 | 11 | **Closed issues:** 12 | 13 | - Script name and content is always printed [\#84](https://github.com/testdouble/scripty/issues/84) 14 | - Default to silent mode; allow verbose option [\#70](https://github.com/testdouble/scripty/issues/70) 15 | 16 | **Merged pull requests:** 17 | 18 | - Adds the Test Double code of conduct [\#81](https://github.com/testdouble/scripty/pull/81) ([ericqweinstein](https://github.com/ericqweinstein)) 19 | - Infer log level from npm's own logLevel [\#78](https://github.com/testdouble/scripty/pull/78) ([jasonkarns](https://github.com/jasonkarns)) 20 | - Tweak travis/appveyor configs [\#77](https://github.com/testdouble/scripty/pull/77) ([jasonkarns](https://github.com/jasonkarns)) 21 | - Report errors by console-logging and exit code [\#76](https://github.com/testdouble/scripty/pull/76) ([jasonkarns](https://github.com/jasonkarns)) 22 | - Let istanbul generate combined coverage report [\#75](https://github.com/testdouble/scripty/pull/75) ([jasonkarns](https://github.com/jasonkarns)) 23 | - Shore up logging behavior [\#74](https://github.com/testdouble/scripty/pull/74) ([jasonkarns](https://github.com/jasonkarns)) 24 | 25 | 26 | ## [v1.9.1](https://github.com/testdouble/scripty/tree/v1.9.1) (2019-02-19) 27 | [Full Changelog](https://github.com/testdouble/scripty/compare/v1.9.0...v1.9.1) 28 | 29 | **Closed issues:** 30 | 31 | - Multiple script locations [\#79](https://github.com/testdouble/scripty/issues/79) 32 | 33 | **Merged pull requests:** 34 | 35 | - Bumps lodash to latest [\#80](https://github.com/testdouble/scripty/pull/80) ([jasonkarns](https://github.com/jasonkarns)) 36 | 37 | ## [v1.9.0](https://github.com/testdouble/scripty/tree/v1.9.0) (2018-12-18) 38 | [Full Changelog](https://github.com/testdouble/scripty/compare/v1.8.0...v1.9.0) 39 | 40 | **Closed issues:** 41 | 42 | - Sharable scripts via a npm package [\#68](https://github.com/testdouble/scripty/issues/68) 43 | - Support script/ along with scripts/ by default [\#52](https://github.com/testdouble/scripty/issues/52) 44 | 45 | **Merged pull requests:** 46 | 47 | - Omit test and misc files from npm tarball [\#73](https://github.com/testdouble/scripty/pull/73) ([jasonkarns](https://github.com/jasonkarns)) 48 | - Support singular 'script' dirname convention [\#72](https://github.com/testdouble/scripty/pull/72) ([jasonkarns](https://github.com/jasonkarns)) 49 | - DevDep bumps [\#71](https://github.com/testdouble/scripty/pull/71) ([jasonkarns](https://github.com/jasonkarns)) 50 | 51 | ## [v1.8.0](https://github.com/testdouble/scripty/tree/v1.8.0) (2018-10-10) 52 | [Full Changelog](https://github.com/testdouble/scripty/compare/v1.7.2...v1.8.0) 53 | 54 | **Closed issues:** 55 | 56 | - Batch scripts skip directories [\#59](https://github.com/testdouble/scripty/issues/59) 57 | - Revive builtIn scripts resolver [\#58](https://github.com/testdouble/scripty/issues/58) 58 | - Spawn another lifecycle event [\#57](https://github.com/testdouble/scripty/issues/57) 59 | - Custom path for certain scripts? [\#55](https://github.com/testdouble/scripty/issues/55) 60 | - Customize "sub-scripts" separation char [\#40](https://github.com/testdouble/scripty/issues/40) 61 | 62 | **Merged pull requests:** 63 | 64 | - Add support for shareable modules [\#69](https://github.com/testdouble/scripty/pull/69) ([thoov](https://github.com/thoov)) 65 | - Upgrade dependencies and node versions [\#66](https://github.com/testdouble/scripty/pull/66) ([rosston](https://github.com/rosston)) 66 | - Add "permissions will also be stored in git" note to README [\#65](https://github.com/testdouble/scripty/pull/65) ([micimize](https://github.com/micimize)) 67 | 68 | ## [v1.7.2](https://github.com/testdouble/scripty/tree/v1.7.2) (2017-08-29) 69 | [Full Changelog](https://github.com/testdouble/scripty/compare/v1.7.1...v1.7.2) 70 | 71 | **Merged pull requests:** 72 | 73 | - Clean up exits. [\#50](https://github.com/testdouble/scripty/pull/50) ([Schoonology](https://github.com/Schoonology)) 74 | - chore: update travis.yml with node 6lts + @stable [\#49](https://github.com/testdouble/scripty/pull/49) ([JaKXz](https://github.com/JaKXz)) 75 | - chore: update appveyor with node 6lts [\#48](https://github.com/testdouble/scripty/pull/48) ([JaKXz](https://github.com/JaKXz)) 76 | 77 | ## [v1.7.1](https://github.com/testdouble/scripty/tree/v1.7.1) (2016-12-21) 78 | [Full Changelog](https://github.com/testdouble/scripty/compare/v1.7.0...v1.7.1) 79 | 80 | **Closed issues:** 81 | 82 | - v1.7.0 doesn't use custom path [\#45](https://github.com/testdouble/scripty/issues/45) 83 | 84 | **Merged pull requests:** 85 | 86 | - Fix loading custom scripts. [\#46](https://github.com/testdouble/scripty/pull/46) ([Schoonology](https://github.com/Schoonology)) 87 | 88 | ## [v1.7.0](https://github.com/testdouble/scripty/tree/v1.7.0) (2016-10-18) 89 | [Full Changelog](https://github.com/testdouble/scripty/compare/v1.6.0...v1.7.0) 90 | 91 | **Closed issues:** 92 | 93 | - Always running child scripts in parallel on Windows [\#42](https://github.com/testdouble/scripty/issues/42) 94 | - Scripty scripts are missing from the output of `npm run` [\#41](https://github.com/testdouble/scripty/issues/41) 95 | 96 | **Merged pull requests:** 97 | 98 | - Allow all configuration via package.json. [\#39](https://github.com/testdouble/scripty/pull/39) ([Schoonology](https://github.com/Schoonology)) 99 | 100 | ## [v1.6.0](https://github.com/testdouble/scripty/tree/v1.6.0) (2016-05-10) 101 | [Full Changelog](https://github.com/testdouble/scripty/compare/v1.5.0...v1.6.0) 102 | 103 | **Implemented enhancements:** 104 | 105 | - Add a dry run option [\#13](https://github.com/testdouble/scripty/issues/13) 106 | 107 | **Closed issues:** 108 | 109 | - How well does scripty work with preinstall scripts? [\#33](https://github.com/testdouble/scripty/issues/33) 110 | - quiet output [\#28](https://github.com/testdouble/scripty/issues/28) 111 | 112 | **Merged pull requests:** 113 | 114 | - Lowercase Scripty in Readme [\#37](https://github.com/testdouble/scripty/pull/37) ([hanneskaeufler](https://github.com/hanneskaeufler)) 115 | - Adds an env var to trigger a silent mode. [\#36](https://github.com/testdouble/scripty/pull/36) ([hanneskaeufler](https://github.com/hanneskaeufler)) 116 | 117 | ## [v1.5.0](https://github.com/testdouble/scripty/tree/v1.5.0) (2016-04-23) 118 | [Full Changelog](https://github.com/testdouble/scripty/compare/v1.4.0...v1.5.0) 119 | 120 | **Closed issues:** 121 | 122 | - Performance [\#34](https://github.com/testdouble/scripty/issues/34) 123 | - magic [\#31](https://github.com/testdouble/scripty/issues/31) 124 | - alternative syntax [\#30](https://github.com/testdouble/scripty/issues/30) 125 | - options [\#29](https://github.com/testdouble/scripty/issues/29) 126 | 127 | **Merged pull requests:** 128 | 129 | - add suggested documentation to FAQ section of README [\#32](https://github.com/testdouble/scripty/pull/32) ([ashleygwilliams](https://github.com/ashleygwilliams)) 130 | - Fix typo in Readme [\#27](https://github.com/testdouble/scripty/pull/27) ([hanneskaeufler](https://github.com/hanneskaeufler)) 131 | - Add an option to perform a dry run [\#26](https://github.com/testdouble/scripty/pull/26) ([hanneskaeufler](https://github.com/hanneskaeufler)) 132 | - Add a trivial example project [\#25](https://github.com/testdouble/scripty/pull/25) ([searls](https://github.com/searls)) 133 | 134 | ## [v1.4.0](https://github.com/testdouble/scripty/tree/v1.4.0) (2016-04-17) 135 | [Full Changelog](https://github.com/testdouble/scripty/compare/v1.3.0...v1.4.0) 136 | 137 | **Implemented enhancements:** 138 | 139 | - Way to specify a folder for scripts [\#8](https://github.com/testdouble/scripty/issues/8) 140 | 141 | **Closed issues:** 142 | 143 | - Separating scripts into one file? [\#23](https://github.com/testdouble/scripty/issues/23) 144 | 145 | **Merged pull requests:** 146 | 147 | - Allow users to configure custom script directories [\#24](https://github.com/testdouble/scripty/pull/24) ([searls](https://github.com/searls)) 148 | 149 | ## [v1.3.0](https://github.com/testdouble/scripty/tree/v1.3.0) (2016-04-16) 150 | [Full Changelog](https://github.com/testdouble/scripty/compare/v1.2.3...v1.3.0) 151 | 152 | **Implemented enhancements:** 153 | 154 | - Running tasks in parallel [\#10](https://github.com/testdouble/scripty/issues/10) 155 | 156 | **Merged pull requests:** 157 | 158 | - Implements SCRIPTY\_PARALLEL [\#21](https://github.com/testdouble/scripty/pull/21) ([searls](https://github.com/searls)) 159 | 160 | ## [v1.2.3](https://github.com/testdouble/scripty/tree/v1.2.3) (2016-04-16) 161 | [Full Changelog](https://github.com/testdouble/scripty/compare/v1.2.2...v1.2.3) 162 | 163 | **Fixed bugs:** 164 | 165 | - "explicit parent script" not working [\#20](https://github.com/testdouble/scripty/issues/20) 166 | 167 | **Merged pull requests:** 168 | 169 | - Fix explicit parent script for index files with an extension [\#22](https://github.com/testdouble/scripty/pull/22) ([searls](https://github.com/searls)) 170 | - Simplify find-executables test [\#19](https://github.com/testdouble/scripty/pull/19) ([searls](https://github.com/searls)) 171 | 172 | ## [v1.2.2](https://github.com/testdouble/scripty/tree/v1.2.2) (2016-04-16) 173 | [Full Changelog](https://github.com/testdouble/scripty/compare/v1.2.1...v1.2.2) 174 | 175 | **Closed issues:** 176 | 177 | - more info [\#15](https://github.com/testdouble/scripty/issues/15) 178 | 179 | **Merged pull requests:** 180 | 181 | - don't mistake directories for executables [\#16](https://github.com/testdouble/scripty/pull/16) ([boneskull](https://github.com/boneskull)) 182 | 183 | ## [v1.2.1](https://github.com/testdouble/scripty/tree/v1.2.1) (2016-04-15) 184 | [Full Changelog](https://github.com/testdouble/scripty/compare/v1.2.0...v1.2.1) 185 | 186 | **Implemented enhancements:** 187 | 188 | - Handle when `scripty` is run outside `npm run` [\#9](https://github.com/testdouble/scripty/issues/9) 189 | 190 | **Merged pull requests:** 191 | 192 | - When run outside of npm, print a warning [\#14](https://github.com/testdouble/scripty/pull/14) ([searls](https://github.com/searls)) 193 | 194 | ## [v1.2.0](https://github.com/testdouble/scripty/tree/v1.2.0) (2016-04-15) 195 | [Full Changelog](https://github.com/testdouble/scripty/compare/v1.1.0...v1.2.0) 196 | 197 | **Closed issues:** 198 | 199 | - Does scripty allow `.sh` file extension? [\#7](https://github.com/testdouble/scripty/issues/7) 200 | - Pass arguments after `--` through to the script [\#5](https://github.com/testdouble/scripty/issues/5) 201 | 202 | **Merged pull requests:** 203 | 204 | - Allow user-provided args [\#12](https://github.com/testdouble/scripty/pull/12) ([searls](https://github.com/searls)) 205 | - use stricter stubbing [\#11](https://github.com/testdouble/scripty/pull/11) ([searls](https://github.com/searls)) 206 | 207 | ## [v1.1.0](https://github.com/testdouble/scripty/tree/v1.1.0) (2016-04-12) 208 | [Full Changelog](https://github.com/testdouble/scripty/compare/v1.0.2...v1.1.0) 209 | 210 | **Merged pull requests:** 211 | 212 | - Fix windows [\#4](https://github.com/testdouble/scripty/pull/4) ([searls](https://github.com/searls)) 213 | 214 | ## [v1.0.2](https://github.com/testdouble/scripty/tree/v1.0.2) (2016-04-10) 215 | [Full Changelog](https://github.com/testdouble/scripty/compare/v1.0.1...v1.0.2) 216 | 217 | **Fixed bugs:** 218 | 219 | - Fix travis [\#3](https://github.com/testdouble/scripty/issues/3) 220 | 221 | ## [v1.0.1](https://github.com/testdouble/scripty/tree/v1.0.1) (2016-04-10) 222 | [Full Changelog](https://github.com/testdouble/scripty/compare/v1.0.0...v1.0.1) 223 | 224 | ## [v1.0.0](https://github.com/testdouble/scripty/tree/v1.0.0) (2016-04-09) 225 | [Full Changelog](https://github.com/testdouble/scripty/compare/v0.0.3...v1.0.0) 226 | 227 | ## [v0.0.3](https://github.com/testdouble/scripty/tree/v0.0.3) (2016-04-09) 228 | [Full Changelog](https://github.com/testdouble/scripty/compare/v0.0.2...v0.0.3) 229 | 230 | ## [v0.0.2](https://github.com/testdouble/scripty/tree/v0.0.2) (2016-04-09) 231 | [Full Changelog](https://github.com/testdouble/scripty/compare/v0.0.1...v0.0.2) 232 | 233 | ## [v0.0.1](https://github.com/testdouble/scripty/tree/v0.0.1) (2016-04-09) 234 | 235 | 236 | \* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* 237 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Test Double, LLC. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # scripty 2 | 3 | ## ⛔ DEPRECATED ⛔ 4 | 5 | scripty is deprecated and unmaintained. 6 | 7 | Why? The JavaScript package ecosystem has gotten more complicated since April 2016, with scripty predating Bun — and even yarn and pnpm by a few months. Additionally, certain lifecycle scripts (like `preinstall`, `install`, `prepare`, `prepublish`, and `prepack`) may trigger before scripty is available and can't reliably use scripty anyway. Most people will generally be better off explicitly referencing their script files from the `package.json` rather than relying on scripty's convention. 8 | 9 | We have no plans to hand off the package or resurrect it, but you are of course welcome to fork it. 10 | 11 | ## What is? 12 | 13 | Using [npm-scripts](https://docs.npmjs.com/misc/scripts) has become a popular 14 | way of maintaining the various build tasks needed to develop Node.js modules. 15 | People like npm-scripts because it's simple! This is a common refrain: 16 | 17 | > Don't bother with grunt, gulp, or broccoli, just add a little script to your 18 | package.json and run it with `npm run name:of:script` 19 | 20 | Indeed, this _is_ much simpler, but it can quickly become a mess. Take a look at 21 | what happened to our 22 | [testdouble.js](https://github.com/testdouble/testdouble.js) library's 23 | [package.json](https://github.com/testdouble/testdouble.js/blob/30e27f54de0e84fe99a9c33340a0474c3a21369b/package.json#L16-L42). 24 | Using npm-scripts for everything is simple to start with, but it can't hope to 25 | guard against the complexity that naturally accumulates over the life of a 26 | project. 27 | 28 | We wrote scripty to help us extract our npm scripts—particularly the gnarly 29 | ones—into their own files without changing the command we use to run 30 | them. To see how to do this yourself, read on! 31 | 32 | ## Install 33 | 34 | ``` 35 | $ npm install --save-dev scripty 36 | ``` 37 | 38 | ## Usage 39 | 40 | 1. From your module's root, create a `scripts` directory 41 | 2. If you want to define an npm script named "foo:bar", write an executable 42 | file at `scripts/foo/bar` 43 | 3. Feel a liberating breeze roll over your knuckles as 44 | your script is free to roam within its own file, beyond the stuffy confines of a 45 | quote-escaped string inside a pile of JSON 46 | 4. Declare your `"foo:bar"` script in `"scripts"` in your `package.json`: 47 | 48 | ``` json 49 | "scripts": { 50 | "foo:bar": "scripty" 51 | } 52 | ``` 53 | 54 | From this point on, you can run `npm run foo:bar` and scripty will use npm's 55 | built-in `npm_lifecycle_event` environment variable to look up 56 | `scripts/foo/bar` and execute it for you. 57 | 58 | This pattern is great for extracting 59 | scripts that are starting to become unwieldy inside your `package.json`, while 60 | still explicitly calling out the scripts that your package supports (though 61 | where to take that aspect from here is [up for 62 | debate](https://github.com/testdouble/scripty/issues/1)). 63 | 64 | ## Advanced Usage 65 | 66 | Ready to take things to the next level? Check this stuff out: 67 | 68 | ### Passing command-line args 69 | 70 | To pass command-line args when you're running an npm script, set them after 71 | `--` and npm will forward them to your script (and scripty will do its part by 72 | forwarding them along). 73 | 74 | For example, if you had a script in `scripts/echo/hello`: 75 | 76 | ``` sh 77 | #!/usr/bin/env sh 78 | 79 | echo Hello, "$1"! 80 | ``` 81 | 82 | Then you can run `npm run echo:hello -- WORLD` and see your script print 83 | `"Hello, WORLD!"`. 84 | 85 | ### Batching "sub-scripts" 86 | 87 | Let's say you have two test tasks in `scripts/test/unit` and 88 | `scripts/test/integration`: 89 | 90 | ``` json 91 | "scripts": { 92 | "test:unit": "scripty", 93 | "test:integration": "scripty" 94 | } 95 | ``` 96 | 97 | And you want `npm test` to simply run all of them, regardless of order. In that 98 | case, just add a `"test"` entry to your `package.json` like so: 99 | 100 | ``` json 101 | "scripts": { 102 | "test:unit": "scripty", 103 | "test:integration": "scripty", 104 | "test": "scripty" 105 | } 106 | ``` 107 | 108 | And from then on, running `npm test` will result in scripty running all the 109 | executable files it can find in `scripts/test/*`. 110 | 111 | ### Defining an explicit parent script 112 | 113 | Suppose in the example above, it becomes important for us to run our scripts in 114 | a particular order. Or, perhaps, when running `npm test` we need to do some other 115 | custom scripting as well. Fear, not! 116 | 117 | Without changing the JSON from the previous example: 118 | 119 | ``` json 120 | "scripts": { 121 | "test:unit": "scripty", 122 | "test:integration": "scripty", 123 | "test": "scripty" 124 | } 125 | ``` 126 | 127 | Defining a script named `scripts/test/index` will cause scripty to only run that 128 | `index` script, as opposed to globbing for all the scripts it finds in 129 | `scripts/test/*`. 130 | 131 | ### Running scripts in parallel 132 | 133 | If you have a certain command that will match mutiple child scripts (for 134 | instance, if `npm run watch` matches `scripts/watch/js` and 135 | `scripts/watch/css`), then you can tell scripty to run the sub-scripts in 136 | parallel by setting a `SCRIPTY_PARALLEL` env variable to `'true'`. This may 137 | be used to similar effect as the 138 | [npm-run-all](https://www.npmjs.com/package/npm-run-all) module. 139 | 140 | To illustrate, to run a scripty script in parallel, you might: 141 | 142 | ``` 143 | $ SCRIPTY_PARALLEL=true npm run watch 144 | ``` 145 | 146 | Or, if that particular script should always be run in parallel, you can set the 147 | variable in your package.json: 148 | 149 | ``` json 150 | "scripts": { 151 | "watch": "SCRIPTY_PARALLEL=true scripty" 152 | } 153 | ``` 154 | 155 | Which will run any sub-scripts in parallel whenever you run `npm run watch`. 156 | 157 | Finally, if you **always** want to run scripts in parallel, any option can be 158 | set in your package.json under a `"scripty"` entry: 159 | 160 | ```json 161 | "config": { 162 | "scripty": { 163 | "parallel": true 164 | } 165 | } 166 | ``` 167 | 168 | ### Windows support 169 | 170 | Windows support is provided by scripty in two ways: 171 | 172 | 1. If everything in your `scripts` directory can be safely executed by Windows, 173 | no action is needed (this is only likely if you don't have collaborators on 174 | Unix-like platforms) 175 | 2. If your project needs to run scripts in both Windows & Unix, then you may 176 | define a `scripts-win/` directory with a symmetrical set of scripts to whatever 177 | Unix scripts might be found in `scripts/` 178 | 179 | To illustrate the above, suppose you have this bash script configured as 180 | ``"test/unit"`` in your package.json file and this bash script defined in 181 | `scripts/test/unit`: 182 | 183 | ``` bash 184 | #!/usr/bin/env bash 185 | 186 | teenytest --helper test/unit-helper.js "lib/**/*.test.js" 187 | ``` 188 | 189 | In order to add Windows support, you could define `scripts-win/test/unit.cmd` 190 | with this script: 191 | 192 | ``` bat 193 | @ECHO OFF 194 | 195 | teenytest --helper test\unit-helper.js "lib\**\*.test.js" 196 | ``` 197 | 198 | With a configuration like the above, if `npm run test:unit` is run from a Unix 199 | platform, the initial bash script in `scripts/` will run. If the same CLI 200 | command is run from Windows, however, the batch script in `scripts-win/` will be 201 | run. 202 | 203 | ### Specifying custom script directories 204 | 205 | By default, scripty will search for scripts in `scripts/` relative to your 206 | module root (and if you're running windows, it'll check `scripts-win/` first). 207 | If you'd like to customize the base directories scripty uses to search for your 208 | scripts, add a `"scripty"` object property to your package.json like so: 209 | 210 | ``` json 211 | "config": { 212 | "scripty": { 213 | "path": "../core/scripts", 214 | "windowsPath": "../core/scripts-win" 215 | } 216 | } 217 | ``` 218 | 219 | You can configure either or both of `"path"` and `"windowsPath"` to custom 220 | locations of your choosing. This may be handy in situations where multiple 221 | projects share the same set of scripts. 222 | 223 | ### Sharing scripts via node modules 224 | 225 | You can configure scripty to include certain node modules into its executable 226 | search space. This is beneficial if you would like to create a centralized place 227 | for your scripts and then share them across multiple projects. To include modules 228 | add a `"scripty"` object property, `modules`, to your package.json like so: 229 | 230 | ``` json 231 | "config": { 232 | "scripty": { 233 | "modules": ["packageA", "packageB"] 234 | } 235 | } 236 | ``` 237 | 238 | Each node module must contain a `scripts` directory. Below is an example directory 239 | structure: 240 | 241 | ``` 242 | root/ 243 | scripts/ 244 | foo 245 | node_modules/ 246 | packageA/ 247 | scripts/ 248 | foo 249 | bar 250 | packageB/ 251 | scripts/ 252 | bar 253 | baz 254 | ``` 255 | 256 | In the above example the resolution of `foo` would resolve to `root.scripts.foo`. Local scripts 257 | take priority over ones defined in modules. The resolution of `bar` would resolve to 258 | `root.node_modules.packageA.scripts.bar` as packageA was the first module defined 259 | in the `scripty.modules` config. 260 | 261 | ### Dry runs 262 | 263 | To perform a dry run of your scripts—something that's handy to check which 264 | scripts will run from a particular command without actually executing potentially 265 | destructive scripts, you can set an environment variable like so: 266 | 267 | ``` 268 | $ SCRIPTY_DRY_RUN=true npm run publish:danger:stuff 269 | ``` 270 | 271 | This will print the path and contents of each script the command would execute in 272 | the order they would be executed if you were to run the command normally. 273 | 274 | Worth mentioning, like all options this can be set in package.json under a 275 | `"scripty"` entry: 276 | 277 | ```json 278 | "config": { 279 | "scripty": { 280 | "dryRun": true 281 | } 282 | } 283 | ``` 284 | 285 | ### Log output 286 | 287 | Scripty is now quieter by default. 288 | The output can be configured to a level of `verbose`, `info`, `warn`, or `error`. 289 | Any logs equal to or higher than the setting are shown. 290 | All logs are printed to STDERR (to aid in redirection and piping). 291 | 292 | ``` 293 | $ SCRIPTY_LOG_LEVEL=verbose npm run publish:danger:stuff 294 | ``` 295 | 296 | This will print the path and contents of each script the command executes. 297 | 298 | If you always want scripty to run your scripts at a certain level, 299 | you can set it in your package.json under a `"scripty"` entry: 300 | 301 | ```json 302 | "config": { 303 | "scripty": { 304 | "logLevel": "warn" 305 | } 306 | } 307 | ``` 308 | 309 | `SCRIPTY_SILENT` and `SCRIPTY_QUIET` are aliases for `SCRIPTY_LOG_LEVEL=silent` 310 | `SCRIPTY_VERBOSE` is an alias for `SCRIPTY_LOG_LEVEL=verbose` 311 | (also `"silent": true`, etc in package.json#scripty) 312 | 313 | `SCRIPTY_DRY_RUN=true` implies log level `info` 314 | 315 | Explicit setting from logLevel takes precedence; otherwise, 316 | conflicting values between silent/verbose/dryRun will respect the highest level. 317 | If no setting is provided, scripty will infer its log level from npm's log level. 318 | 319 | ## Likely questions 320 | 321 | * **Is this pure magic?** - Nope! For once, instilling some convention didn't 322 | require any clever metaprogramming, just environment variables npm already sets; 323 | try running `printenv` from a script some time! 324 | * **Why isn't my script executing?** - If your script isn't executing, make sure 325 | it's **executable**! In UNIX, this can be accomplished by running 326 | `chmod +x scripts/path/to/my/script` (permissions will also be stored in git) 327 | * **How can I expect my users to understand what this does?** Documenting your 328 | project's use of `scripty` in the `README` is probably a good idea. Here's 329 | some copy pasta if you don't feel like writing it up yourself: 330 | 331 | > ## npm scripts 332 | > MyProject uses [`scripty`](https://github.com/testdouble/scripty) to organize 333 | > npm scripts. The scripts are defined in the [`scripts`](/scripts) directory. 334 | > In `package.json` you'll see the word `scripty` as opposed to the script 335 | > content you'd expect. For more info, see 336 | > [scripty's GitHub](https://github.com/testdouble/scripty). 337 | 338 | > {{ insert table containing script names and what they do, e.g. 339 | > [this](https://github.com/ashleygwilliams/relational-node#scripts) }} 340 | 341 | ## Code of Conduct 342 | 343 | This project follows Test Double's [code of conduct](https://testdouble.com/code-of-conduct) for all community interactions, including (but not limited to) one-on-one communications, public posts/comments, code reviews, pull requests, and GitHub issues. If violations occur, Test Double will take any action they deem appropriate for the infraction, up to and including blocking a user from the organization's repositories. 344 | -------------------------------------------------------------------------------- /cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var lifecycleEvent = process.env.npm_lifecycle_event 4 | 5 | if (!lifecycleEvent) { 6 | console.error( 7 | 'scripty ERR! It seems you may be running scripty from the command-line directly.\n' + 8 | 'At this time, scripty can only be run within an npm script specified in your package.json.\n\n' + 9 | 'Example package.json entry:\n\n' + 10 | ' "scripts": {\n' + 11 | ' "foo:bar": "scripty"\n' + 12 | ' }\n\n' + 13 | 'And then run via `npm run foo:bar`.\n\n' + 14 | 'For more documentation, see:\n' + 15 | ' https://github.com/testdouble/scripty\n\n' + 16 | 'Exiting.' 17 | ) 18 | process.exit(1) 19 | } else { 20 | var scripty = require('./lib/scripty') 21 | var loadOption = require('./lib/load-option') 22 | var log = require('./lib/log') 23 | 24 | scripty(lifecycleEvent, { 25 | userArgs: process.argv.slice(2), 26 | parallel: loadOption('parallel'), 27 | dryRun: loadOption('dryRun'), 28 | logLevel: loadOption('logLevel'), 29 | quiet: loadOption('quiet'), 30 | silent: loadOption('silent'), 31 | verbose: loadOption('verbose'), 32 | spawn: { 33 | stdio: 'inherit' 34 | }, 35 | resolve: { 36 | modules: loadOption('modules'), 37 | scripts: loadOption('path'), 38 | scriptsWin: loadOption('windowsPath') 39 | } 40 | }, function (er, code) { 41 | if (er) { 42 | log.error(er) 43 | code = code || er.code || 1 44 | } 45 | process.exitCode = code 46 | }) 47 | } 48 | -------------------------------------------------------------------------------- /example/message.txt: -------------------------------------------------------------------------------- 1 | Some Example Project 2 | -------------------------------------------------------------------------------- /example/node_modules/.bin/scripty: -------------------------------------------------------------------------------- 1 | ../../../cli.js -------------------------------------------------------------------------------- /example/node_modules/.bin/scripty.cmd: -------------------------------------------------------------------------------- 1 | @IF EXIST "%~dp0\node.exe" ( 2 | "%~dp0\node.exe" "%~dp0\..\..\..\cli.js" %* 3 | ) ELSE ( 4 | @SETLOCAL 5 | @SET PATHEXT=%PATHEXT:;.JS;=;% 6 | node "%~dp0\..\..\..\cli.js" %* 7 | ) 8 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-scripty-project", 3 | "version": "1.0.0", 4 | "description": "Just an example of how to use scripty", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "scripty" 8 | }, 9 | "author": "Justin Searls ", 10 | "license": "MIT" 11 | } 12 | -------------------------------------------------------------------------------- /example/scripts-win/test.cmd: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | type message.txt 4 | -------------------------------------------------------------------------------- /example/scripts/test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | cat message.txt 4 | -------------------------------------------------------------------------------- /lib/derive-log-level.js: -------------------------------------------------------------------------------- 1 | const log = require('./log') 2 | 3 | const npmLevel = { 4 | silent: log.silent, 5 | error: log.error, 6 | warn: log.warn, 7 | notice: log.info, 8 | http: log.info, 9 | timing: log.info, 10 | info: log.info, 11 | verbose: log.verbose, 12 | silly: log.verbose 13 | } 14 | 15 | module.exports = function deriveLogLevel (userOptions = {}) { 16 | if (userOptions.logLevel) return userOptions.logLevel 17 | 18 | if (userOptions.verbose) return log.verbose 19 | 20 | if (userOptions.dryRun) return log.info 21 | 22 | if (userOptions.silent || userOptions.quiet) return log.silent 23 | 24 | return npmLevel[process.env.npm_config_loglevel] 25 | } 26 | -------------------------------------------------------------------------------- /lib/derive-log-level.test.js: -------------------------------------------------------------------------------- 1 | var subject = require('./derive-log-level') 2 | var log = require('./log') 3 | 4 | module.exports = { 5 | 'defaults to inferring from npm logLevel': function () { 6 | process.env.npm_config_loglevel = 'silly' 7 | assert.equal(subject(), log.verbose) 8 | 9 | process.env.npm_config_loglevel = 'notice' 10 | assert.equal(subject(), log.info) 11 | 12 | process.env.npm_config_loglevel = 'silent' 13 | assert.equal(subject(), log.silent) 14 | }, 15 | 16 | 'explicit logLevel takes precedence': function () { 17 | assert.equal(subject({ 18 | silent: true, 19 | dryRun: true, 20 | verbose: true, 21 | logLevel: 'warn' }), 'warn') 22 | }, 23 | 24 | 'passes through unrecognized values': function () { 25 | assert.equal(subject({ logLevel: 'worn' }), 'worn') 26 | }, 27 | 28 | 'verbose preempts dry-run and silent': function () { 29 | assert.equal(subject({ silent: true, dryRun: true, verbose: true }), log.verbose) 30 | }, 31 | 32 | 'dry-run preempts silent': function () { 33 | assert.equal(subject({ silent: true, dryRun: true }), log.info) 34 | }, 35 | 36 | 'silent is read last': function () { 37 | assert.equal(subject({ silent: true }), log.silent) 38 | }, 39 | 40 | 'quiet is alias for silent': function () { 41 | assert.equal(subject({ quiet: true }), log.silent) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/load-option.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash') 2 | 3 | module.exports = function loadOption (name) { 4 | if (envVarSet(posixEnvVarName(name))) { 5 | return boolEnvVarValue(posixEnvVarName(name)) 6 | } else if (envVarSet(packageEnvVarName(name))) { 7 | return boolEnvVarValue(packageEnvVarName(name)) 8 | } else if (envVarSet(packageEnvConfigVarName(name))) { 9 | return boolEnvVarValue(packageEnvConfigVarName(name)) 10 | } else if (envVarSet(packageArrayEnvVarName(name))) { 11 | return arrayEnvVarValue(packageEnvVarName(name)) 12 | } 13 | } 14 | 15 | function boolEnvVarValue (envVarName) { 16 | var value = process.env[envVarName] 17 | 18 | if (value === 'true') { 19 | return true 20 | } else if (value === 'false') { 21 | return false 22 | } else { 23 | return value 24 | } 25 | } 26 | 27 | function arrayEnvVarValue (envVarName) { 28 | var count = 0 29 | var result = [] 30 | 31 | while (envVarSet(envVarName + '_' + count)) { 32 | result.push(process.env[envVarName + '_' + count]) 33 | count++ 34 | } 35 | 36 | return result 37 | } 38 | 39 | function envVarSet (envVarName) { 40 | return !!process.env[envVarName] 41 | } 42 | 43 | function posixEnvVarName (optionName) { 44 | return 'SCRIPTY_' + _.snakeCase(optionName).toUpperCase() 45 | } 46 | 47 | function packageEnvVarName (optionName) { // Backwards compatible for npm v6 48 | return 'npm_package_scripty_' + optionName 49 | } 50 | 51 | function packageEnvConfigVarName (optionName) { 52 | return 'npm_package_config_scripty_' + optionName 53 | } 54 | 55 | function packageArrayEnvVarName (optionName) { 56 | return packageEnvVarName(optionName) + '_0' 57 | } 58 | -------------------------------------------------------------------------------- /lib/load-option.test.js: -------------------------------------------------------------------------------- 1 | var subject = require('./load-option') 2 | 3 | module.exports = { 4 | beforeEach: function () { 5 | delete process.env.SCRIPTY_TEST_KEY 6 | delete process.env.npm_package_scripty_testKey 7 | }, 8 | envTrue: function () { 9 | process.env.SCRIPTY_TEST_KEY = 'true' 10 | 11 | assert.equal(subject('testKey'), true) 12 | }, 13 | envFalse: function () { 14 | process.env.SCRIPTY_TEST_KEY = 'false' 15 | 16 | assert.equal(subject('testKey'), false) 17 | }, 18 | packageTrue: function () { 19 | process.env.npm_package_scripty_testKey = true 20 | 21 | assert.equal(subject('testKey'), true) 22 | }, 23 | packageFalse: function () { 24 | process.env.npm_package_scripty_testKey = false 25 | 26 | assert.equal(subject('testKey'), false) 27 | }, 28 | envOverrideTrue: function () { 29 | process.env.SCRIPTY_TEST_KEY = 'true' 30 | process.env.npm_package_scripty_testKey = false 31 | 32 | assert.equal(subject('testKey'), true) 33 | }, 34 | envOverrideFalse: function () { 35 | process.env.SCRIPTY_TEST_KEY = 'false' 36 | process.env.npm_package_scripty_testKey = true 37 | 38 | assert.equal(subject('testKey'), false) 39 | }, 40 | packageString: function () { 41 | process.env.npm_package_scripty_testKey = 'some value' 42 | 43 | assert.equal(subject('testKey'), 'some value') 44 | }, 45 | envString: function () { 46 | process.env.SCRIPTY_TEST_KEY = 'some value' 47 | 48 | assert.equal(subject('testKey'), 'some value') 49 | }, 50 | envOverrideString: function () { 51 | process.env.SCRIPTY_TEST_KEY = 'right value' 52 | process.env.npm_package_scripty_testKey = 'wrong value' 53 | 54 | assert.equal(subject('testKey'), 'right value') 55 | }, 56 | packageArray: function () { 57 | process.env.npm_package_scripty_testKey_0 = 'value 1' 58 | process.env.npm_package_scripty_testKey_1 = 'value 2' 59 | process.env.npm_package_scripty_testKey_2 = 'value 3' 60 | 61 | assert.deepEqual(subject('testKey'), ['value 1', 'value 2', 'value 3']) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /lib/log.js: -------------------------------------------------------------------------------- 1 | const util = require('util') 2 | 3 | const constant = val => () => val 4 | 5 | const formatError = args => 6 | args.map(arg => arg instanceof Error ? arg.message : arg) 7 | 8 | const loggerWithPrefix = (prefix, writer) => (...args) => 9 | writer()(prefix, util.format(...formatError(args)) 10 | .replace(/(\r?\n)(?=[\s\S]+)/g, `$1${prefix} `)) 11 | 12 | const silentLogger = (...args) => { 13 | output += util.format(...args) + '\n' 14 | } 15 | 16 | let level 17 | let output 18 | 19 | module.exports = { 20 | get level () { 21 | return level.toString() 22 | }, 23 | 24 | set level (l) { 25 | level = module.exports[String(l).toLowerCase()] 26 | }, 27 | 28 | read: () => output, 29 | 30 | reset: () => { 31 | output = '' 32 | module.exports.level = 'info' 33 | } 34 | } 35 | 36 | ;[ 37 | ['verbose', '>'], 38 | ['info', '>'], 39 | ['warn', 'WARN'], 40 | ['error', 'ERR!'], 41 | ['silent'] 42 | ].forEach(([name, prefix], index) => { 43 | const logger = loggerWithPrefix(`scripty ${prefix}`, () => 44 | level <= logger ? console.error : silentLogger) 45 | 46 | logger.valueOf = constant(index + 1) 47 | logger.toString = constant(name) 48 | 49 | module.exports[name] = logger 50 | }) 51 | 52 | module.exports.reset() 53 | -------------------------------------------------------------------------------- /lib/log.test.js: -------------------------------------------------------------------------------- 1 | var subject = require('./log') 2 | 3 | module.exports = { 4 | beforeEach: function () { 5 | td.replace(console, 'error') 6 | subject.reset() 7 | }, 8 | writesToStderr: function () { 9 | subject.info('foo') 10 | 11 | td.verify(console.error('scripty >', 'foo')) 12 | }, 13 | setTheLogLevel: { 14 | verbose: function () { 15 | subject.level = subject.verbose 16 | subject.verbose('ity') 17 | td.verify(console.error('scripty >', 'ity')) 18 | }, 19 | info: function () { 20 | subject.level = subject.info 21 | td.when(console.error('ity')).thenThrow(new Error('Should not log verbose calls at INFO level')) 22 | subject.verbose('ity') 23 | subject.info('mation') 24 | td.verify(console.error('scripty >', 'mation')) 25 | }, 26 | warn: function () { 27 | subject.level = subject.warn 28 | td.when(console.error('mation')).thenThrow(new Error('Should not log info calls at WARN level')) 29 | subject.info('mation') 30 | subject.warn('ing') 31 | td.verify(console.error('scripty WARN', 'ing')) 32 | }, 33 | error: function () { 34 | subject.level = subject.error 35 | td.when(console.error('ing')).thenThrow(new Error('Should not log warn calls at ERROR level')) 36 | subject.warn('ing') 37 | subject.error('fail') 38 | td.verify(console.error('scripty ERR!', 'fail')) 39 | }, 40 | silent: function () { 41 | subject.level = subject.silent 42 | td.when(console.error('fail')).thenThrow(new Error('Should not log error calls at SILENT level')) 43 | subject.error('fail') 44 | } 45 | }, 46 | modeSwitchCapturesLogs: function () { 47 | subject.level = subject.silent 48 | 49 | subject.info('bar') 50 | subject.info('baz', 'noz') 51 | 52 | td.verify(console.error(), { ignoreExtraArgs: true, times: 0 }) 53 | assert.equal(subject.read(), 'scripty > bar\nscripty > baz noz\n') 54 | }, 55 | resetResetsMode: function () { 56 | subject.level = subject.silent 57 | 58 | subject.reset() 59 | 60 | subject.info('biz') 61 | td.verify(console.error('scripty >', 'biz')) 62 | }, 63 | resetResetsLog: function () { 64 | subject.level = subject.silent 65 | 66 | subject.info('lalalal') 67 | 68 | subject.reset() 69 | 70 | assert.equal(subject.read(), '') 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /lib/optionify.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash') 2 | var deriveLogLevel = require('./derive-log-level') 3 | 4 | var NULL_CB = function () {} 5 | module.exports = function (rawFunc, defaultOptions) { 6 | return function (mainArg, userOptions, cb) { 7 | if (typeof userOptions === 'function') { 8 | cb = userOptions 9 | } 10 | if (!cb) { 11 | cb = NULL_CB 12 | } 13 | 14 | var logLevel = deriveLogLevel(userOptions) 15 | if (logLevel) userOptions.logLevel = logLevel 16 | 17 | var fullOptions = _.defaultsDeep({}, userOptions, defaultOptions) 18 | return rawFunc(mainArg, fullOptions, cb) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/optionify.test.js: -------------------------------------------------------------------------------- 1 | var subject = require('./optionify') 2 | 3 | module.exports = { 4 | beforeEach: function () { 5 | this.dummy = function (main, options, cb) { 6 | return { main: main, options: options, cb: cb } 7 | } 8 | this.defaults = { deep: { a: 2 } } 9 | 10 | // prevent inferring log level 11 | delete process.env.npm_config_loglevel 12 | }, 13 | deepMergeOptions: function () { 14 | var cb = function () { return 'bar' } 15 | 16 | var result = subject(this.dummy, this.defaults)('foo', { deep: { b: 3 } }, cb) 17 | 18 | assert.equal(result.main, 'foo') 19 | assert.deepEqual(result.options, { deep: { a: 2, b: 3 } }) 20 | assert.equal(result.cb(), 'bar') 21 | }, 22 | nothingProvidedFine: function () { 23 | var result = subject(this.dummy, this.defaults)() 24 | 25 | assert.equal(result.main, undefined) 26 | assert.deepEqual(result.options, this.defaults) 27 | assert.equal(result.cb(), undefined) 28 | }, 29 | noOptionsProvided: function () { 30 | var cb = function () { return 'bar' } 31 | 32 | var result = subject(this.dummy, this.defaults)('foo', cb) 33 | 34 | assert.equal(result.main, 'foo') 35 | assert.deepEqual(result.options, this.defaults) 36 | assert.equal(result.cb(), 'bar') 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/resolve-script/find-executables.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash') 2 | var async = require('async') 3 | var fs = require('fs') 4 | var path = require('path') 5 | var globFirst = require('./glob-first') 6 | var log = require('../log') 7 | 8 | module.exports = (patterns, cb) => 9 | globFirst(patterns, (er, results) => { 10 | if (er) return cb(er) 11 | 12 | async.map(results, 13 | (result, cb) => fs.access(result, fs.constants.R_OK | fs.constants.X_OK, 14 | er => cb(null, er 15 | ? log.warn(`Ignoring script '${result}' because it was not readable/executable.\n` + 16 | `Run \`chmod u+rx '${result}'\` if you want scripty to run it.`) 17 | : path.resolve(result)) 18 | ), 19 | (er, results) => cb(er, _.compact(results)) 20 | ) 21 | }) 22 | -------------------------------------------------------------------------------- /lib/resolve-script/find-executables.test.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash') 2 | var path = require('path') 3 | var log = require('../log') 4 | 5 | var base = function (glob) { 6 | return path.resolve('test/fixtures/unit/find-executables', glob) 7 | } 8 | var subject = require('./find-executables') 9 | 10 | module.exports = { 11 | beforeEach: function () { 12 | log.level = 'silent' 13 | }, 14 | noFilesFound: function (done) { 15 | subject([base('does-not-exist*')], function (er, result) { 16 | assert.deepEqual(result, []) 17 | done(er) 18 | }) 19 | }, 20 | oneFileFound: function (done) { 21 | subject([base('is-executable*')], function (er, result) { 22 | assert.deepEqual(result, [base('is-executable')]) 23 | done(er) 24 | }) 25 | }, 26 | oneFileFoundWithOneNonExecutable: function (done) { 27 | if (process.platform === 'win32') return done() 28 | subject([base('file.*')], function (er, result) { 29 | assert.deepEqual(result, [base('file.executable')]) 30 | assert.includes(log.read(), 31 | `scripty WARN Ignoring script '${base('file.not.executable')}' because it was not readable/executable.\n` + 32 | `scripty WARN Run \`chmod u+rx '${base('file.not.executable')}'\` if you want scripty to run it.` 33 | ) 34 | done(er) 35 | }) 36 | }, 37 | twoFilesFound: function (done) { 38 | subject([base('exec.*')], function (er, result) { 39 | assert.deepEqual(_.sortBy(result), [base('exec.rb'), base('exec.sh')]) 40 | done(er) 41 | }) 42 | }, 43 | dirFound: function (done) { 44 | subject([base('exec-dir-wat*')], function (er, result) { 45 | assert.deepEqual(result, []) 46 | done(er) 47 | }) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/resolve-script/glob-first.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var _ = require('lodash') 3 | var glob = require('glob') 4 | var async = require('async') 5 | 6 | module.exports = function (globPatterns, cb) { 7 | async.map(globPatterns, function (globPattern, cb) { 8 | glob(globPattern, { nodir: true }, cb) 9 | }, function (er, result) { 10 | if (er) return cb(er) 11 | var firstMatches = _(result).reject(_.isEmpty).first() 12 | cb(er, _.map(firstMatches, function (f) { 13 | return path.resolve(f) 14 | })) 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /lib/resolve-script/glob-first.test.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash') 2 | var path = require('path') 3 | 4 | var base = function (segment) { 5 | return path.resolve('test/fixtures/unit/glob-first', segment) 6 | } 7 | var subject = require('./glob-first') 8 | 9 | module.exports = { 10 | hitMissMiss: function (done) { 11 | var patterns = [base('test2+(|.*)'), base('test2/index+(|.*)'), base('test2/*')] 12 | 13 | subject(patterns, function (er, result) { 14 | assert.equal(result, base('test2')) 15 | done(er) 16 | }) 17 | }, 18 | missHitHit: function (done) { 19 | var patterns = [base('test+(|.*)'), base('test/index+(|.*)'), base('test/*')] 20 | 21 | subject(patterns, function (er, result) { 22 | assert.equal(result, base('test/index.sh')) 23 | done(er) 24 | }) 25 | }, 26 | missMissHit: function (done) { 27 | var patterns = [base('(test3+(|.*)'), base('test3/index+(|.*)'), base('test3/*')] 28 | 29 | subject(patterns, function (er, result) { 30 | assert.deepEqual(_.sortBy(result), [base('test3/bar.foo'), base('test3/index2')]) 31 | done(er) 32 | }) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/resolve-script/glob-patterns.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | 3 | module.exports = function (dir1, dir2, moduleDirs) { 4 | var globs = [] 5 | var scriptPath = path.join.apply(this, dir2.split(':')) 6 | var searchSpaceDirs = [].concat(dir1, moduleDirs) 7 | 8 | searchSpaceDirs.forEach(function (dir) { 9 | var scriptDir = path.resolve(dir, scriptPath) 10 | 11 | // exact file match (+ any same-named extensions) 12 | globs.push(scriptDir + '+(|.*)') 13 | 14 | // a nested index file match (+ any same-named extensions) 15 | globs.push(path.join(scriptDir, 'index+(|.*)')) 16 | 17 | // any nested files at all 18 | globs.push(path.join(scriptDir, '*')) 19 | }) 20 | 21 | return globs 22 | } 23 | -------------------------------------------------------------------------------- /lib/resolve-script/glob-patterns.test.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var subject = require('./glob-patterns') 3 | 4 | module.exports = { 5 | simpleCase: function () { 6 | var result = subject(__dirname, 'foo:bar', []) 7 | 8 | assert.equal(result.length, 3) 9 | assert.equal(result[0], path.resolve(__dirname, 'foo/bar') + '+(|.*)') 10 | assert.equal(result[1], path.resolve(__dirname, 'foo/bar/index') + '+(|.*)') 11 | assert.equal(result[2], path.resolve(__dirname, 'foo/bar/*')) 12 | }, 13 | modulesCase: function () { 14 | var result = subject( 15 | __dirname, 16 | 'foo:bar', 17 | [ 18 | path.join(process.cwd(), 'node_modules/bar/scripts'), 19 | path.join(process.cwd(), 'node_modules/baz/scripts') 20 | ] 21 | ) 22 | 23 | assert.equal(result.length, 9) 24 | assert.equal(result[0], path.resolve(__dirname, 'foo/bar') + '+(|.*)') 25 | assert.equal(result[1], path.resolve(__dirname, 'foo/bar/index') + '+(|.*)') 26 | assert.equal(result[2], path.resolve(__dirname, 'foo/bar/*')) 27 | 28 | assert.equal(result[3], path.resolve(process.cwd(), 'node_modules/bar/scripts/foo/bar') + '+(|.*)') 29 | assert.equal(result[4], path.resolve(process.cwd(), 'node_modules/bar/scripts/foo/bar/index') + '+(|.*)') 30 | assert.equal(result[5], path.resolve(process.cwd(), 'node_modules/bar/scripts/foo/bar/*')) 31 | 32 | assert.equal(result[6], path.resolve(process.cwd(), 'node_modules/baz/scripts/foo/bar') + '+(|.*)') 33 | assert.equal(result[7], path.resolve(process.cwd(), 'node_modules/baz/scripts/foo/bar/index') + '+(|.*)') 34 | assert.equal(result[8], path.resolve(process.cwd(), 'node_modules/baz/scripts/foo/bar/*')) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/resolve-script/index.js: -------------------------------------------------------------------------------- 1 | var globPatterns = require('./glob-patterns') 2 | var findExecutables = require('./find-executables') 3 | var scriptDirs = require('./script-dirs') 4 | 5 | module.exports = function (name, options, cb) { 6 | var dirs = scriptDirs(options) 7 | var userGlob = globPatterns(dirs.userDir, name, dirs.moduleDirs) 8 | findExecutables(userGlob, function (er, userPaths) { 9 | if (userPaths.length > 0) { 10 | cb(er, userPaths) 11 | } else { 12 | var ourGlob = globPatterns(dirs.ourDir, name, []) 13 | findExecutables(ourGlob, function (er, ourPaths) { 14 | if (ourPaths.length > 0) { 15 | cb(er, ourPaths) 16 | } else { 17 | cb(new Error(`No script found for npm lifecycle '${name}' matching any of:\n` + 18 | ` ${String(userGlob).replace(/,/g, '\n ')}\n` + 19 | ` ${String(ourGlob).replace(/,/g, '\n ')}\n` + 20 | `Either define a script or remove "scripty" from 'scripts.${name}' in your package.json.` 21 | ), null) 22 | } 23 | }) 24 | } 25 | }) 26 | } 27 | -------------------------------------------------------------------------------- /lib/resolve-script/index.test.js: -------------------------------------------------------------------------------- 1 | var OPTIONS = { panda: true } 2 | 3 | module.exports = { 4 | beforeEach: function () { 5 | td.when(td.replace(process, 'cwd')()).thenReturn('/user-dir') 6 | this.globPatterns = td.replace('./glob-patterns') 7 | this.findExecutables = td.replace('./find-executables') 8 | this.scriptDirs = td.replace('./script-dirs') 9 | this.subject = require('./index') 10 | 11 | td.when(this.scriptDirs(OPTIONS)).thenReturn({ userDir: 'A', ourDir: 'B', moduleDirs: [] }) 12 | td.when(this.globPatterns('A', 'fake', [])).thenReturn(['glob1']) 13 | td.when(this.globPatterns('B', 'fake', [])).thenReturn(['glob2']) 14 | 15 | td.when(this.globPatterns('A', 'fake', [])).thenReturn(['glob1']) 16 | td.when(this.globPatterns('B', 'fake', [])).thenReturn(['glob2']) 17 | }, 18 | bothUserAndBuiltInScriptsExist: function (done) { 19 | td.when(this.findExecutables(['glob1'])).thenCallback(null, ['user-path']) 20 | td.when(this.findExecutables(['glob2'])).thenCallback(null, ['pathA']) 21 | 22 | this.subject('fake', OPTIONS, function (er, result) { 23 | assert.deepEqual(result, ['user-path']) 24 | done(er) 25 | }) 26 | }, 27 | oneBuiltInScriptExists: function (done) { 28 | td.when(this.findExecutables(['glob1'])).thenCallback(null, []) 29 | td.when(this.findExecutables(['glob2'])).thenCallback(null, ['pathA']) 30 | 31 | this.subject('fake', OPTIONS, function (er, result) { 32 | assert.deepEqual(result, ['pathA']) 33 | done(er) 34 | }) 35 | }, 36 | noScriptExists: function (done) { 37 | td.when(this.findExecutables(['glob1'])).thenCallback(null, []) 38 | td.when(this.findExecutables(['glob2'])).thenCallback(null, []) 39 | 40 | this.subject('fake', OPTIONS, function (er, result) { 41 | assert.equal(er.message, 42 | `No script found for npm lifecycle 'fake' matching any of:\n` + 43 | ` glob1\n glob2\n` + 44 | `Either define a script or remove "scripty" from 'scripts.fake' in your package.json.`) 45 | assert.equal(result, null) 46 | done(null) 47 | }) 48 | } 49 | } 50 | 51 | if (UNSUPPORTED_TDD) module.exports = {} 52 | -------------------------------------------------------------------------------- /lib/resolve-script/script-dirs.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var fs = require('fs') 3 | var lodash = require('lodash') 4 | var resolvePkg = require('resolve-pkg') 5 | 6 | module.exports = function (options, platform) { 7 | platform = platform || process.platform 8 | var modules = lodash.get(options, 'modules', []) 9 | return { 10 | userDir: find(process.cwd(), options, 'scripts', platform), 11 | moduleDirs: findModulePaths(modules).map(function (path) { return find(path, options, 'scripts', platform) }), 12 | ourDir: find(path.resolve(__dirname, '../..'), options, 'builtIn', platform) 13 | } 14 | } 15 | 16 | function find (base, options, key, platform) { 17 | if (platform === 'win32' && options[key + 'Win']) { 18 | return options[key + 'Win'] 19 | } else if (platform === 'win32' && fs.existsSync(path.resolve(base, 'scripts-win'))) { 20 | return path.resolve(base, 'scripts-win') 21 | } else if (platform === 'win32' && fs.existsSync(path.resolve(base, 'script-win'))) { 22 | return path.resolve(base, 'script-win') 23 | } else if (options[key]) { 24 | return options[key] 25 | } else if (fs.existsSync(path.resolve(base, 'scripts'))) { 26 | return path.resolve(base, 'scripts') 27 | } else { 28 | return path.resolve(base, 'script') 29 | } 30 | } 31 | 32 | function findModulePaths (modules) { 33 | var modulePaths = [] 34 | 35 | modules.forEach(function (moduleName) { 36 | var modulePath = resolvePkg(moduleName) 37 | 38 | if (modulePath) { 39 | modulePaths.push(modulePath) 40 | } 41 | }) 42 | 43 | return modulePaths 44 | } 45 | -------------------------------------------------------------------------------- /lib/resolve-script/script-dirs.test.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | 3 | module.exports = { 4 | beforeEach: function () { 5 | this.resolvePkg = td.replace('resolve-pkg') 6 | this.fsExistsSync = td.replace('fs').existsSync 7 | this.subject = require('./script-dirs') 8 | }, 9 | unixUserScripts: { 10 | 'prefer scripts key if set': function () { 11 | assert.equal(this.subject({ scripts: 'A', scriptsWin: 'B' }, 'lolnix').userDir, 'A') 12 | }, 13 | 14 | 'first fallback to scripts if it exists': function () { 15 | td.when(this.fsExistsSync(td.matchers.contains(/scripts$/))).thenReturn(true) 16 | assert.equal(this.subject({ scripts: null, scriptsWin: 'B' }, 'lolnix').userDir, 17 | path.resolve(process.cwd(), 'scripts')) 18 | }, 19 | 20 | 'final fallback to script': function () { 21 | td.when(this.fsExistsSync(td.matchers.contains(/scripts$/))).thenReturn(false) 22 | assert.equal(this.subject({ scripts: null, scriptsWin: null }, 'lolnix').userDir, 23 | path.resolve(process.cwd(), 'script')) 24 | } 25 | }, 26 | unixBuiltInScripts: function () { 27 | td.when(this.fsExistsSync(td.matchers.contains(/scripts$/))).thenReturn(true) 28 | assert.equal(this.subject({ builtIn: 'A', builtInWin: 'B' }, 'lolnix').ourDir, 'A') 29 | assert.equal(this.subject({ builtIn: null, builtInWin: 'B' }, 'lolnix').ourDir, 30 | path.resolve(__dirname, '../../scripts')) 31 | }, 32 | windowsUserScripts: { 33 | 'prefer scriptsWin value if set': function () { 34 | assert.equal(this.subject({ scripts: 'A', scriptsWin: 'B' }, 'win32').userDir, 'B') 35 | }, 36 | 37 | 'first fallback to scripts-win if exists': function () { 38 | td.when(this.fsExistsSync(td.matchers.contains(/scripts-win$/))).thenReturn(true) 39 | assert.equal(this.subject({ scripts: 'A', scriptsWin: null }, 'win32').userDir, 40 | path.resolve(process.cwd(), 'scripts-win')) 41 | }, 42 | 43 | 'second fallback to script-win if it exists': function () { 44 | td.when(this.fsExistsSync(td.matchers.contains(/scripts-win$/))).thenReturn(false) 45 | td.when(this.fsExistsSync(td.matchers.contains(/script-win$/))).thenReturn(true) 46 | assert.equal(this.subject({ scripts: 'A', scriptsWin: null }, 'win32').userDir, 47 | path.resolve(process.cwd(), 'script-win')) 48 | }, 49 | 50 | 'third fallback to scripts key if set': function () { 51 | td.when(this.fsExistsSync(td.matchers.contains(/scripts-win$/))).thenReturn(false) 52 | td.when(this.fsExistsSync(td.matchers.contains(/script-win$/))).thenReturn(false) 53 | assert.equal(this.subject({ scripts: 'A', scriptsWin: null }, 'win32').userDir, 'A') 54 | }, 55 | 56 | 'fourth fallback to scripts if it exists': function () { 57 | td.when(this.fsExistsSync(td.matchers.contains(/scripts-win$/))).thenReturn(false) 58 | td.when(this.fsExistsSync(td.matchers.contains(/script-win$/))).thenReturn(false) 59 | td.when(this.fsExistsSync(td.matchers.contains(/scripts$/))).thenReturn(true) 60 | assert.equal(this.subject({ scripts: null, scriptsWin: null }, 'win32').userDir, 61 | path.resolve(process.cwd(), 'scripts')) 62 | }, 63 | 64 | 'final fallback to script': function () { 65 | td.when(this.fsExistsSync(td.matchers.contains(/scripts-win$/))).thenReturn(false) 66 | td.when(this.fsExistsSync(td.matchers.contains(/script-win$/))).thenReturn(false) 67 | td.when(this.fsExistsSync(td.matchers.contains(/scripts$/))).thenReturn(false) 68 | assert.equal(this.subject({ scripts: null, scriptsWin: null }, 'win32').userDir, 69 | path.resolve(process.cwd(), 'script')) 70 | } 71 | }, 72 | windowsBuiltIn: function () { 73 | td.when(this.fsExistsSync(td.matchers.contains(/scripts-win$/))).thenReturn(true) 74 | assert.equal(this.subject({ builtIn: 'A', builtInWin: 'B' }, 'win32').ourDir, 'B') 75 | assert.equal(this.subject({ builtIn: 'A', builtInWin: null }, 'win32').ourDir, 76 | path.resolve(__dirname, '../../scripts-win')) 77 | }, 78 | moduleDirs: { 79 | 'prefer scripts if it exists': function () { 80 | td.when(this.resolvePkg('bar')).thenReturn('node_modules/bar') 81 | td.when(this.fsExistsSync(td.matchers.contains(/scripts$/))).thenReturn(true) 82 | 83 | assert.deepEqual( 84 | this.subject({ 85 | modules: ['bar'] 86 | }, 87 | 'lolnix' 88 | ).moduleDirs, [path.resolve(process.cwd(), 'node_modules/bar/scripts')] 89 | ) 90 | }, 91 | 92 | 'fallback to script': function () { 93 | td.when(this.resolvePkg('bar')).thenReturn('node_modules/bar') 94 | td.when(this.fsExistsSync(td.matchers.contains(/scripts$/))).thenReturn(false) 95 | assert.deepEqual( 96 | this.subject({ 97 | modules: ['bar'] 98 | }, 99 | 'lolnix' 100 | ).moduleDirs, [path.resolve(process.cwd(), 'node_modules/bar/script')] 101 | ) 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /lib/run/all.js: -------------------------------------------------------------------------------- 1 | var async = require('async') 2 | 3 | module.exports = function (commands, parallel, cb) { 4 | var runner = async[parallel ? 'parallel' : 'series'] 5 | runner(commands, cb) 6 | } 7 | -------------------------------------------------------------------------------- /lib/run/dry-run.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash') 2 | 3 | var printScript = require('./print-script') 4 | var log = require('../log') 5 | 6 | module.exports = function (scriptFiles, cb) { 7 | log.info('This is a dry run. Executed scripts would be:') 8 | _.map(scriptFiles, printScript) 9 | cb(null, 0) 10 | } 11 | -------------------------------------------------------------------------------- /lib/run/index.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash') 2 | 3 | const commandify = (func, items, options) => 4 | items.map(item => cb => func(item, options, cb)) 5 | var dryRun = require('./dry-run') 6 | var spawnScript = require('./spawn-script') 7 | var all = require('./all') 8 | 9 | module.exports = function (scriptFiles, options, cb) { 10 | if (options.dryRun) return dryRun(scriptFiles, cb) 11 | 12 | var commands = commandify(spawnScript, scriptFiles, options) 13 | all(commands, options.parallel, function (er, codes) { 14 | cb(er, _.last(codes)) 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /lib/run/print-script.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | var log = require('../log') 3 | 4 | module.exports = function (scriptFile) { 5 | log.info(`Executing "${scriptFile}":\n`) 6 | log.verbose(`${read(scriptFile)}`) 7 | } 8 | 9 | function read (scriptFile) { 10 | try { 11 | return fs.readFileSync(scriptFile).toString() 12 | } catch (e) { 13 | log.warn(`Failed to read '${scriptFile}':\n${e.message}`) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/run/print-script.test.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | 3 | var log = require('../log') 4 | 5 | module.exports = { 6 | beforeEach: function () { 7 | log.level = 'silent' 8 | this.subject = require('./print-script') 9 | }, 10 | happyPath: function () { 11 | var script = path.resolve('scripts/test/debug') 12 | 13 | this.subject(script) 14 | 15 | assert.includes(log.read(), 'scripty > Executing "' + script + '":\n') 16 | assert.includes(log.read(), 'scripty > #!/usr/bin/env sh') 17 | assert.includes(log.read(), 'scripty > npm test -- --debug-brk') 18 | }, 19 | sadPath: function () { 20 | var script = '/silly/nonsense' 21 | 22 | this.subject(script) 23 | 24 | assert.includes(log.read(), `scripty WARN Failed to read '/silly/nonsense':`) 25 | assert.includes(log.read(), `scripty WARN ENOENT`) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/run/spawn-script.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash') 2 | var printScript = require('./print-script') 3 | var spawn = require('child_process').spawn 4 | 5 | module.exports = function (scriptFile, options, cb) { 6 | printScript(scriptFile) 7 | 8 | const child = spawn(scriptFile, options.userArgs, options.spawn) 9 | 10 | child.on('close', code => cb(code !== 0 11 | ? new Error(`script failed: '${scriptFile}'\nexit status: ${code}`) 12 | : null, code) 13 | ) 14 | 15 | _.invoke(options, 'spawn.tap', child) 16 | } 17 | -------------------------------------------------------------------------------- /lib/scripty.js: -------------------------------------------------------------------------------- 1 | var optionify = require('./optionify') 2 | var resolveScript = require('./resolve-script') 3 | var run = require('./run') 4 | var log = require('./log') 5 | 6 | module.exports = optionify(function scripty (npmLifecycle, options, cb) { 7 | log.level = options.logLevel 8 | 9 | resolveScript(npmLifecycle, options.resolve, function (er, scriptFiles) { 10 | if (er) return cb(er) 11 | run(scriptFiles, options, cb) 12 | }) 13 | }, { 14 | userArgs: [], 15 | parallel: false, 16 | dryRun: false, 17 | logLevel: 'info', 18 | spawn: {}, 19 | resolve: {} 20 | }) 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scripty", 3 | "version": "3.0.0", 4 | "homepage": "https://github.com/testdouble/scripty#readme", 5 | "description": "Because no one should be shell-scripting inside a JSON file.", 6 | "license": "MIT", 7 | "author": "Justin Searls ", 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/testdouble/scripty.git" 11 | }, 12 | "bugs": { 13 | "url": "https://github.com/testdouble/scripty/issues" 14 | }, 15 | "main": "lib/scripty.js", 16 | "bin": "cli.js", 17 | "files": [ 18 | "cli.js", 19 | "lib", 20 | "scripts", 21 | "scripts-win" 22 | ], 23 | "scripts": { 24 | "test:unit": "teenytest 'lib/**/*.test.js' --helper test/unit-helper.js", 25 | "test:safe": "teenytest 'test/safe/**/*.js' --helper test/safe-helper.js", 26 | "test:style": "standard", 27 | "test": "npm run test:unit && npm run test:style && npm run test:safe", 28 | "test:cover": "npm run test:cover:unit && npm run test:cover:safe", 29 | "posttest:cover": "istanbul report", 30 | "test:cover:unit": "istanbul cover --dir coverage/unit teenytest -- 'lib/**/*.test.js' --helper test/unit-helper.js", 31 | "test:cover:safe": "istanbul cover --dir coverage/safe teenytest -- 'test/safe/**/*.js' --helper test/safe-helper.js", 32 | "postversion": "npm publish && git push --follow-tags" 33 | }, 34 | "dependencies": { 35 | "async": "^2.6.4", 36 | "glob": "^7.0.3", 37 | "lodash": "^4.17.11", 38 | "resolve-pkg": "^1.0.0" 39 | }, 40 | "devDependencies": { 41 | "assert": "^1.3.0", 42 | "intercept-stdout": "^0.1.2", 43 | "istanbul": "^0.4.3", 44 | "semver": "^5.1.0", 45 | "standard": "^12.0.1", 46 | "teenytest": "^5.1.1", 47 | "testdouble": "^3.9.1" 48 | }, 49 | "keywords": [ 50 | "npm", 51 | "scripts", 52 | "dry", 53 | "scripts", 54 | "shell", 55 | "script", 56 | "management" 57 | ], 58 | "standard": { 59 | "globals": [ 60 | "td", 61 | "assert", 62 | "UNSUPPORTED_TDD" 63 | ] 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /scripts-win/noop.cmd: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /scripts/noop: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | : 4 | -------------------------------------------------------------------------------- /scripts/test/debug: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | npm test -- --debug-brk 4 | -------------------------------------------------------------------------------- /test/decorate-assertions.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash') 2 | 3 | module.exports = function (assert) { 4 | return _.extend({}, assert, { 5 | includes: function (actual, expected) { 6 | if (!_.includes(actual, expected)) { 7 | throw new Error( 8 | 'AssertionError: expected:\n\n"' + actual + 9 | '"\n\n to contain:\n\n"' + expected + '"\n\n' 10 | ) 11 | } 12 | } 13 | }) 14 | } 15 | -------------------------------------------------------------------------------- /test/fixtures/baseball/batter.rb: -------------------------------------------------------------------------------- 1 | def wait_for_file(name) 2 | while !File.exist?(File.join(File.dirname(__FILE__), name)) 3 | sleep 0.1 4 | end 5 | end 6 | 7 | def write_file(name) 8 | File.open(File.join(File.dirname(__FILE__), name), 'w') {|f| f.write('stuff') } 9 | end 10 | 11 | puts "Batter waits for ball" 12 | wait_for_file('ball') 13 | puts "Batter sees ball" 14 | write_file('bat') 15 | puts "Batter writes bat" 16 | -------------------------------------------------------------------------------- /test/fixtures/baseball/pitcher.rb: -------------------------------------------------------------------------------- 1 | def wait_for_file(name) 2 | while !File.exist?(File.join(File.dirname(__FILE__), name)) 3 | sleep 0.1 4 | end 5 | end 6 | 7 | def write_file(name) 8 | File.open(File.join(File.dirname(__FILE__), name), 'w') {|f| f.write('stuff') } 9 | end 10 | 11 | puts "Pitcher writes ball" 12 | write_file('ball') 13 | puts "Pitcher waits for bat" 14 | wait_for_file('bat') 15 | puts "Pitcher sees bat" 16 | 17 | -------------------------------------------------------------------------------- /test/fixtures/built-in-scripts-win/hello/world.cmd: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | SET WORLD=World 4 | 5 | ECHO Hello, %WORLD%! 6 | -------------------------------------------------------------------------------- /test/fixtures/built-in-scripts/hello/world: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | WORLD="World" 4 | 5 | echo "Hello, $WORLD!" 6 | -------------------------------------------------------------------------------- /test/fixtures/custom-user-scripts-win/secret.cmd: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | ECHO SSHHH 4 | -------------------------------------------------------------------------------- /test/fixtures/custom-user-scripts/secret: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | echo SSHHH 4 | -------------------------------------------------------------------------------- /test/fixtures/modules/node_modules/foo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "foo" 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/modules/node_modules/foo/scripts-win/foo.cmd: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | echo Hello, World! from foo win 4 | 5 | -------------------------------------------------------------------------------- /test/fixtures/modules/node_modules/foo/scripts-win/user.cmd: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | echo Hello, World! from foo win 4 | 5 | -------------------------------------------------------------------------------- /test/fixtures/modules/node_modules/foo/scripts/foo: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | WORLD="World" 4 | 5 | echo "Hello, $WORLD! from foo" 6 | -------------------------------------------------------------------------------- /test/fixtures/modules/node_modules/foo/scripts/user: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | WORLD="World" 4 | 5 | echo "Hello, $WORLD! from foo" 6 | 7 | -------------------------------------------------------------------------------- /test/fixtures/modules/scripts-win/user.cmd: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | echo Hello, World! from user win 4 | 5 | -------------------------------------------------------------------------------- /test/fixtures/modules/scripts/user: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | WORLD="World" 4 | 5 | echo "Hello, $WORLD! from user" 6 | -------------------------------------------------------------------------------- /test/fixtures/relative-path-loading/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "relative-path-loading", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "secret": "scripty" 6 | }, 7 | "scripty": { 8 | "path": "../custom-user-scripts" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/fixtures/unit/find-executables/exec.rb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdouble/scripty/a54c27c854f6e5132336983f2bfae8a81dcbbe57/test/fixtures/unit/find-executables/exec.rb -------------------------------------------------------------------------------- /test/fixtures/unit/find-executables/exec.sh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdouble/scripty/a54c27c854f6e5132336983f2bfae8a81dcbbe57/test/fixtures/unit/find-executables/exec.sh -------------------------------------------------------------------------------- /test/fixtures/unit/find-executables/file.executable: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdouble/scripty/a54c27c854f6e5132336983f2bfae8a81dcbbe57/test/fixtures/unit/find-executables/file.executable -------------------------------------------------------------------------------- /test/fixtures/unit/find-executables/file.not.executable: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdouble/scripty/a54c27c854f6e5132336983f2bfae8a81dcbbe57/test/fixtures/unit/find-executables/file.not.executable -------------------------------------------------------------------------------- /test/fixtures/unit/find-executables/is-executable: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdouble/scripty/a54c27c854f6e5132336983f2bfae8a81dcbbe57/test/fixtures/unit/find-executables/is-executable -------------------------------------------------------------------------------- /test/fixtures/unit/glob-first/test/index.sh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdouble/scripty/a54c27c854f6e5132336983f2bfae8a81dcbbe57/test/fixtures/unit/glob-first/test/index.sh -------------------------------------------------------------------------------- /test/fixtures/unit/glob-first/test/index2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdouble/scripty/a54c27c854f6e5132336983f2bfae8a81dcbbe57/test/fixtures/unit/glob-first/test/index2 -------------------------------------------------------------------------------- /test/fixtures/unit/glob-first/test/other: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdouble/scripty/a54c27c854f6e5132336983f2bfae8a81dcbbe57/test/fixtures/unit/glob-first/test/other -------------------------------------------------------------------------------- /test/fixtures/unit/glob-first/test2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdouble/scripty/a54c27c854f6e5132336983f2bfae8a81dcbbe57/test/fixtures/unit/glob-first/test2 -------------------------------------------------------------------------------- /test/fixtures/unit/glob-first/test3/bar.foo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdouble/scripty/a54c27c854f6e5132336983f2bfae8a81dcbbe57/test/fixtures/unit/glob-first/test3/bar.foo -------------------------------------------------------------------------------- /test/fixtures/unit/glob-first/test3/index2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdouble/scripty/a54c27c854f6e5132336983f2bfae8a81dcbbe57/test/fixtures/unit/glob-first/test3/index2 -------------------------------------------------------------------------------- /test/fixtures/user-scripts-win/args/echoer.cmd: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | echo Your args were: %1 %2 4 | -------------------------------------------------------------------------------- /test/fixtures/user-scripts-win/baz/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdouble/scripty/a54c27c854f6e5132336983f2bfae8a81dcbbe57/test/fixtures/user-scripts-win/baz/.keep -------------------------------------------------------------------------------- /test/fixtures/user-scripts-win/car/index: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdouble/scripty/a54c27c854f6e5132336983f2bfae8a81dcbbe57/test/fixtures/user-scripts-win/car/index -------------------------------------------------------------------------------- /test/fixtures/user-scripts-win/dog/index/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdouble/scripty/a54c27c854f6e5132336983f2bfae8a81dcbbe57/test/fixtures/user-scripts-win/dog/index/.keep -------------------------------------------------------------------------------- /test/fixtures/user-scripts-win/fail.cmd: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | type C:\silly\nonsense 4 | -------------------------------------------------------------------------------- /test/fixtures/user-scripts-win/foo/bar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdouble/scripty/a54c27c854f6e5132336983f2bfae8a81dcbbe57/test/fixtures/user-scripts-win/foo/bar -------------------------------------------------------------------------------- /test/fixtures/user-scripts-win/parallel/batter.cmd: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | ruby test/fixtures/baseball/batter.rb 4 | 5 | -------------------------------------------------------------------------------- /test/fixtures/user-scripts-win/parallel/pitcher.cmd: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | ruby test/fixtures/baseball/pitcher.rb 4 | 5 | 6 | -------------------------------------------------------------------------------- /test/fixtures/user-scripts-win/parent/a.cmd: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | ECHO AAA 4 | -------------------------------------------------------------------------------- /test/fixtures/user-scripts-win/parent/b.cmd: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | ECHO BBB 4 | -------------------------------------------------------------------------------- /test/fixtures/user-scripts-win/parent/c.cmd: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | ECHO CCC 4 | -------------------------------------------------------------------------------- /test/fixtures/user-scripts-win/top/index.cmd: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | ECHO rubby 4 | -------------------------------------------------------------------------------- /test/fixtures/user-scripts/args/echoer: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | echo "Your args were: $1 \"$2\"" 4 | -------------------------------------------------------------------------------- /test/fixtures/user-scripts/baz/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdouble/scripty/a54c27c854f6e5132336983f2bfae8a81dcbbe57/test/fixtures/user-scripts/baz/.keep -------------------------------------------------------------------------------- /test/fixtures/user-scripts/car/index: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdouble/scripty/a54c27c854f6e5132336983f2bfae8a81dcbbe57/test/fixtures/user-scripts/car/index -------------------------------------------------------------------------------- /test/fixtures/user-scripts/dog/index/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdouble/scripty/a54c27c854f6e5132336983f2bfae8a81dcbbe57/test/fixtures/user-scripts/dog/index/.keep -------------------------------------------------------------------------------- /test/fixtures/user-scripts/exec-dir-wat/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdouble/scripty/a54c27c854f6e5132336983f2bfae8a81dcbbe57/test/fixtures/user-scripts/exec-dir-wat/.keep -------------------------------------------------------------------------------- /test/fixtures/user-scripts/fail: -------------------------------------------------------------------------------- 1 | cat /silly/nonsense 2 | -------------------------------------------------------------------------------- /test/fixtures/user-scripts/foo/bar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdouble/scripty/a54c27c854f6e5132336983f2bfae8a81dcbbe57/test/fixtures/user-scripts/foo/bar -------------------------------------------------------------------------------- /test/fixtures/user-scripts/parallel/batter: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ruby test/fixtures/baseball/batter.rb 4 | 5 | -------------------------------------------------------------------------------- /test/fixtures/user-scripts/parallel/pitcher: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ruby test/fixtures/baseball/pitcher.rb 4 | 5 | 6 | -------------------------------------------------------------------------------- /test/fixtures/user-scripts/parent/a: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | echo "AAA" 4 | -------------------------------------------------------------------------------- /test/fixtures/user-scripts/parent/b: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | echo "BBB" 4 | 5 | -------------------------------------------------------------------------------- /test/fixtures/user-scripts/parent/c: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | echo "CCC" 4 | 5 | -------------------------------------------------------------------------------- /test/fixtures/user-scripts/stats/all-x: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdouble/scripty/a54c27c854f6e5132336983f2bfae8a81dcbbe57/test/fixtures/user-scripts/stats/all-x -------------------------------------------------------------------------------- /test/fixtures/user-scripts/stats/group-x: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdouble/scripty/a54c27c854f6e5132336983f2bfae8a81dcbbe57/test/fixtures/user-scripts/stats/group-x -------------------------------------------------------------------------------- /test/fixtures/user-scripts/stats/no-x: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdouble/scripty/a54c27c854f6e5132336983f2bfae8a81dcbbe57/test/fixtures/user-scripts/stats/no-x -------------------------------------------------------------------------------- /test/fixtures/user-scripts/stats/other-x: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdouble/scripty/a54c27c854f6e5132336983f2bfae8a81dcbbe57/test/fixtures/user-scripts/stats/other-x -------------------------------------------------------------------------------- /test/fixtures/user-scripts/stats/owner-x: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdouble/scripty/a54c27c854f6e5132336983f2bfae8a81dcbbe57/test/fixtures/user-scripts/stats/owner-x -------------------------------------------------------------------------------- /test/fixtures/user-scripts/top/index.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | puts 'rubby' 4 | -------------------------------------------------------------------------------- /test/fixtures/user-scripts/train/explode.sh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdouble/scripty/a54c27c854f6e5132336983f2bfae8a81dcbbe57/test/fixtures/user-scripts/train/explode.sh -------------------------------------------------------------------------------- /test/fixtures/user-scripts/train/index.sh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdouble/scripty/a54c27c854f6e5132336983f2bfae8a81dcbbe57/test/fixtures/user-scripts/train/index.sh -------------------------------------------------------------------------------- /test/grab-stdio.js: -------------------------------------------------------------------------------- 1 | module.exports = function (result) { 2 | result.stdout = '' 3 | result.stderr = '' 4 | return function (childProcess) { 5 | childProcess.stdout.on('data', function (text) { 6 | result.stdout += text.toString() 7 | }) 8 | childProcess.stderr.on('data', function (text) { 9 | result.stderr += text.toString() 10 | }) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/is-old-node.js: -------------------------------------------------------------------------------- 1 | var version = require('semver')(process.version) 2 | 3 | module.exports = version.major === 0 && version.minor < 11 4 | -------------------------------------------------------------------------------- /test/run-scripty.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash') 2 | var path = require('path') 3 | 4 | var scripty = require('../lib/scripty') 5 | var grabStdio = require('./grab-stdio') 6 | 7 | module.exports = function (name, opts, cb) { 8 | var stdio = {} 9 | 10 | scripty(name, _.defaultsDeep({}, opts, { 11 | logLevel: 'silent', 12 | resolve: { 13 | builtIn: path.resolve('test/fixtures/built-in-scripts'), 14 | builtInWin: path.resolve('test/fixtures/built-in-scripts-win'), 15 | scripts: path.resolve('test/fixtures/user-scripts'), 16 | scriptsWin: path.resolve('test/fixtures/user-scripts-win') 17 | }, 18 | spawn: { 19 | tap: grabStdio(stdio) 20 | } 21 | }), function (er, code) { 22 | cb(er, code, stdio) 23 | }) 24 | } 25 | -------------------------------------------------------------------------------- /test/safe-helper.js: -------------------------------------------------------------------------------- 1 | var decorateAssertions = require('./decorate-assertions') 2 | global.assert = decorateAssertions(require('assert')) 3 | 4 | var log = require('../lib/log') 5 | 6 | module.exports = { 7 | afterEach: function () { 8 | log.reset() 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/safe/basic-test.js: -------------------------------------------------------------------------------- 1 | var runScripty = require('../run-scripty') 2 | var log = require('../../lib/log') 3 | const path = require('path') 4 | 5 | module.exports = { 6 | outputAndRunScript: function (done) { 7 | runScripty('hello:world', {}, function (er, code, stdio) { 8 | assert.equal(0, code) 9 | if (process.platform === 'win32') { 10 | assert.includes(log.read(), '> ECHO Hello, %WORLD%!') 11 | } else { 12 | assert.includes(log.read(), '> echo "Hello, $WORLD!') 13 | } 14 | assert.includes(stdio.stdout, 'Hello, World!') 15 | 16 | done(er) 17 | }) 18 | }, 19 | noScriptFound: function (done) { 20 | runScripty('not:a:real:thing', {}, function (er, code, stdio) { 21 | assert.notEqual(0, code) 22 | assert.includes(er.message, 23 | `No script found for npm lifecycle 'not:a:real:thing'` 24 | ) 25 | 26 | done(null) 27 | }) 28 | }, 29 | scriptFoundButFailed: function (done) { 30 | runScripty('fail', {}, function (er, code, stdio) { 31 | assert.notEqual(0, code) 32 | assert.includes(er.message, `script failed: '${path.resolve('test/fixtures/user-scripts/fail')}'\nexit status: ${code}`) 33 | if (process.platform === 'win32') { 34 | assert.includes(stdio.stderr, 'The system cannot find the path specified.') 35 | } else { 36 | assert.includes(stdio.stderr, 'cat: /silly/nonsense: No such file or directory') 37 | } 38 | done(null) 39 | }) 40 | }, 41 | passArgsToScript: function (done) { 42 | var options = { userArgs: ['--test', 'arg passed by user'] } 43 | 44 | runScripty('args:echoer', options, function (er, code, stdio) { 45 | assert.includes(stdio.stdout, 'Your args were: --test "arg passed by user"') 46 | 47 | done(er) 48 | }) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /test/safe/custom-script-dir-test.js: -------------------------------------------------------------------------------- 1 | var runScripty = require('../run-scripty') 2 | 3 | module.exports = { 4 | basic: function (done) { 5 | runScripty('secret', { 6 | resolve: { 7 | scripts: 'test/fixtures/custom-user-scripts', 8 | scriptsWin: 'test/fixtures/custom-user-scripts-win' 9 | } 10 | }, function (er, code, stdio) { 11 | assert.equal(code, 0) 12 | assert.includes(stdio.stdout, 'SSHHH') 13 | done(er) 14 | }) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/safe/dir-test.js: -------------------------------------------------------------------------------- 1 | var runScripty = require('../run-scripty') 2 | 3 | module.exports = { 4 | runsIndexWhenDirIsFound: function (done) { 5 | runScripty('top', {}, function (er, code, stdio) { 6 | assert.equal(code, 0) 7 | assert.includes(stdio.stdout, 'rubby') 8 | done(er) 9 | }) 10 | }, 11 | runAllWhenDirHasNoIndex: function (done) { 12 | runScripty('parent', {}, function (er, code, stdio) { 13 | assert.equal(code, 0) 14 | assert.includes(stdio.stdout, 'AAA') 15 | assert.includes(stdio.stdout, 'BBB') 16 | assert.includes(stdio.stdout, 'CCC') 17 | done(er) 18 | }) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /test/safe/dry-run-test.js: -------------------------------------------------------------------------------- 1 | var runScripty = require('../run-scripty') 2 | var log = require('../../lib/log') 3 | 4 | module.exports = function doesNotRunButPrintResolvedScripts (done) { 5 | runScripty('hello:world', { dryRun: true }, function (er, code, stdio) { 6 | assert.includes(log.read(), 'This is a dry run. Executed scripts would be:') 7 | if (process.platform === 'win32') { 8 | assert.includes(log.read(), 'built-in-scripts-win\\hello\\world') 9 | assert.includes(log.read(), 'Hello, %WORLD%') 10 | } else { 11 | assert.includes(log.read(), 'built-in-scripts/hello/world') 12 | assert.includes(log.read(), 'Hello, $WORLD') 13 | } 14 | assert.equal(stdio.stdout, '', 'There should be no script output on stdout') 15 | done(er) 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /test/safe/example-test.js: -------------------------------------------------------------------------------- 1 | var exec = require('child_process').exec 2 | var path = require('path') 3 | 4 | module.exports = function (done) { 5 | var exampleTestRun = exec('npm test', { 6 | cwd: path.resolve(__dirname, '../../example') 7 | }) 8 | 9 | var stdout = '' 10 | exampleTestRun.stdout.on('data', function (text) { 11 | stdout += text 12 | }) 13 | 14 | exampleTestRun.on('close', function (code) { 15 | assert.equal(code, 0) 16 | assert.includes(stdout, 'Some Example Project') 17 | done() 18 | }) 19 | } 20 | -------------------------------------------------------------------------------- /test/safe/modules-test.js: -------------------------------------------------------------------------------- 1 | var fork = require('child_process').fork 2 | var path = require('path') 3 | var grabStdio = require('../grab-stdio') 4 | 5 | module.exports = { 6 | runScriptFromModule: function (done) { 7 | var stdio = {} 8 | var child = fork('../../../cli', [], { 9 | cwd: path.join(__dirname, '..', 'fixtures', 'modules'), 10 | silent: true, 11 | env: { 12 | npm_lifecycle_event: 'foo', 13 | npm_package_scripty_modules_0: 'foo', 14 | SCRIPTY_SILENT: true 15 | } 16 | }) 17 | 18 | grabStdio(stdio)(child) 19 | 20 | child.on('exit', function (code) { 21 | assert.equal(code, 0) 22 | if (process.platform === 'win32') { 23 | assert.includes(stdio.stdout, 'Hello, World! from foo win') 24 | } else { 25 | assert.includes(stdio.stdout, 'Hello, World! from foo') 26 | } 27 | 28 | done() 29 | }) 30 | }, 31 | userScriptTakesPriorityOverModule: function (done) { 32 | var stdio = {} 33 | var child = fork('../../../cli', [], { 34 | cwd: path.join(__dirname, '..', 'fixtures', 'modules'), 35 | silent: true, 36 | env: { 37 | npm_lifecycle_event: 'user', 38 | npm_package_scripty_modules_0: 'foo', 39 | SCRIPTY_SILENT: true 40 | } 41 | }) 42 | 43 | grabStdio(stdio)(child) 44 | 45 | child.on('exit', function (code) { 46 | assert.equal(code, 0) 47 | if (process.platform === 'win32') { 48 | assert.includes(stdio.stdout, 'Hello, World! from user win') 49 | } else { 50 | assert.includes(stdio.stdout, 'Hello, World! from user') 51 | } 52 | 53 | done() 54 | }) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /test/safe/no-opts-spot-check.js: -------------------------------------------------------------------------------- 1 | const scripty = require('../../lib/scripty') 2 | 3 | module.exports = function worksWithNoOpts (done) { 4 | process.env.npm_config_loglevel = 'silent' 5 | 6 | scripty('noop', function (er, code) { 7 | assert.equal(0, code) 8 | done(er) 9 | }) 10 | } 11 | -------------------------------------------------------------------------------- /test/safe/parallel-test.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | var path = require('path') 3 | var _ = require('lodash') 4 | 5 | var runScripty = require('../run-scripty') 6 | 7 | module.exports = { 8 | beforeEach: teardown, 9 | afterEach: teardown, 10 | works: function (done) { 11 | runScripty('parallel', { parallel: true }, function (er, code, stdio) { 12 | assert.equal(0, code) 13 | assert.includes(stdio.stdout, 'Batter waits for ball') 14 | assert.includes(stdio.stdout, 'Pitcher writes ball') 15 | assert.includes(stdio.stdout, 'Batter sees ball') 16 | assert.includes(stdio.stdout, 'Pitcher waits for bat') 17 | assert.includes(stdio.stdout, 'Batter writes bat') 18 | assert.includes(stdio.stdout, 'Pitcher sees bat') 19 | done(er) 20 | }) 21 | } 22 | } 23 | 24 | function teardown () { 25 | _(['bat', 'ball']).each(function (f) { 26 | try { 27 | fs.unlinkSync(path.resolve('test/fixtures/baseball', f)) 28 | } catch (e) {} 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /test/safe/relative-path-loading.js: -------------------------------------------------------------------------------- 1 | var fork = require('child_process').fork 2 | var path = require('path') 3 | var grabStdio = require('../grab-stdio') 4 | 5 | module.exports = { 6 | 'loads scripts from a relative path': function (done) { 7 | var stdio = {} 8 | var windowsSuffix = process.platform === 'win32' ? '-win' : '' 9 | var child = fork('../../../cli', [], { 10 | cwd: path.join(__dirname, '..', 'fixtures', 'relative-path-loading'), 11 | silent: true, 12 | env: { 13 | npm_lifecycle_event: 'secret', 14 | npm_package_scripty_path: '../custom-user-scripts' + windowsSuffix, 15 | SCRIPTY_SILENT: true 16 | } 17 | }) 18 | 19 | grabStdio(stdio)(child) 20 | 21 | child.on('exit', function (code) { 22 | assert.equal(code, 0) 23 | assert.includes(stdio.stdout, 'SSHHH') 24 | 25 | done() 26 | }) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /test/safe/silent-test.js: -------------------------------------------------------------------------------- 1 | var runScripty = require('../run-scripty') 2 | 3 | module.exports = function doesNotEchoScriptContentInSilentMode (done) { 4 | var oldConsole = global.console 5 | global.console = {} // blow up if a console method is invoked 6 | 7 | runScripty('hello:world', { logLevel: 'silent' }, function (er, code, stdio) { 8 | global.console = oldConsole 9 | done(er) 10 | }) 11 | } 12 | -------------------------------------------------------------------------------- /test/unit-helper.js: -------------------------------------------------------------------------------- 1 | global.td = require('testdouble') 2 | 3 | var decorateAssertions = require('./decorate-assertions') 4 | global.assert = decorateAssertions(require('assert')) 5 | 6 | var log = require('../lib/log') 7 | 8 | global.UNSUPPORTED_TDD = require('./is-old-node') 9 | if (UNSUPPORTED_TDD) { 10 | console.warn('Warning: skipping isolated tests because td.js ' + 11 | 'doesn\'t support ' + process.version) 12 | } 13 | 14 | module.exports = { 15 | afterEach: function () { 16 | td.reset() 17 | log.reset() 18 | } 19 | } 20 | --------------------------------------------------------------------------------