├── .eslintrc.json ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── nodejs.yml ├── .gitignore ├── .madrun.js ├── .npmignore ├── .npmrc ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── ChangeLog ├── LICENSE ├── README.md ├── bin ├── redrun-completion.js └── redrun.js ├── help.json ├── lib ├── cli-parse.js ├── get-body.js ├── group-parse.js ├── quote-args.js ├── redrun.js ├── regexp.js ├── replace.js └── wildcard.js ├── package.json ├── redrun-big.png ├── redrun.ai ├── redrun.png ├── shell └── redrun-completion.sh └── test ├── cli-parse.js ├── get-body.js ├── group-parse.js ├── madrun.js ├── redrun.js └── replace.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "overrides": [{ 3 | "files": ["bin/**"], 4 | "rules": { 5 | "no-process-exit": "off" 6 | } 7 | }], 8 | "extends": [ 9 | "plugin:n/recommended", 10 | "plugin:putout/recommended" 11 | ], 12 | "plugins": [ 13 | "putout", 14 | "n" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: coderaiser 2 | patreon: coderaiser 3 | open_collective: cloudcmd 4 | ko_fi: coderaiser 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | - **Version** (`redrun -v`): 7 | - **Node Version** `node -v`: 8 | - **OS** (`uname -a` on Linux): 9 | - **Browser name/version**: 10 | - **Used Command Line Parameters**: 11 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | - [ ] commit message named according to [Contributing Guide](https://github.com/coderaiser/cloudcmd/blob/master/CONTRIBUTING.md "Contributting Guide") 7 | - [ ] `npm run codestyle` is OK 8 | - [ ] `npm test` is OK 9 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | name: Node CI 2 | on: 3 | - push 4 | - pull_request 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | env: 9 | NAME: redrun 10 | strategy: 11 | matrix: 12 | node-version: 13 | - 20.x 14 | - 22.x 15 | - 23.x 16 | steps: 17 | - uses: actions/checkout@v4 18 | - uses: oven-sh/setup-bun@v1 19 | with: 20 | bun-version: latest 21 | - name: Use Node.js ${{ matrix.node-version }} 22 | uses: actions/setup-node@v4 23 | with: 24 | node-version: ${{ matrix.node-version }} 25 | - name: Install Redrun 26 | run: bun i redrun -g --no-save 27 | - name: Install 28 | run: bun i --no-save 29 | - name: Lint 30 | run: redrun fix:lint 31 | - name: Commit fixes 32 | uses: EndBug/add-and-commit@v9 33 | continue-on-error: true 34 | with: 35 | message: "chore: ${{ env.NAME }}: lint using actions ☘️" 36 | - name: Coverage 37 | run: redrun coverage report 38 | - name: Coveralls 39 | uses: coverallsapp/github-action@v2 40 | continue-on-error: true 41 | with: 42 | github-token: ${{ secrets.GITHUB_TOKEN }} 43 | - name: typos-action 44 | uses: crate-ci/typos@v1.0.4 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | .*.swp 3 | node_modules 4 | package-lock.json 5 | npm-debug.log 6 | .nyc_output 7 | 8 | yarn-error.log 9 | 10 | coverage 11 | .idea 12 | -------------------------------------------------------------------------------- /.madrun.js: -------------------------------------------------------------------------------- 1 | import {run, cutEnv} from 'madrun'; 2 | 3 | const env = { 4 | SUPERTAPE_CHECK_ASSERTIONS_COUNT: 0, 5 | SUPERTAPE_CHECK_SCOPES: 0, 6 | }; 7 | 8 | export default { 9 | 'lint': () => 'putout .', 10 | 'fresh:lint': () => run('lint', '--fresh'), 11 | 'lint:fresh': () => run('lint', '--fresh'), 12 | 'fix:lint': () => run('lint', '--fix'), 13 | 'test': () => [env, 'tape test/**/*.js'], 14 | 'watch:test': async () => [env, await run('watcher', await cutEnv('test'))], 15 | 'watch:tape': () => 'nodemon -w test -w lib --exec tape', 16 | 'watch:coverage:base': async () => [env, await run('watcher', `nyc ${await cutEnv('test')}`)], 17 | 'watch:coverage:tape': () => run('watcher', 'nyc tape'), 18 | 'watch:coverage': async () => [env, await cutEnv('watch:coverage:base')], 19 | 'watcher': () => 'nodemon -w test -w lib --exec', 20 | 'coverage': async () => [env, `c8 ${await cutEnv('test')}`], 21 | 'report': () => 'c8 report --reporter=lcov', 22 | 'postpublish': () => 'npm i -g', 23 | }; 24 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .* 2 | test 3 | *.png 4 | redrun.ai 5 | *.md 6 | !README.md 7 | 8 | yarn-error.log 9 | 10 | coverage 11 | *.config.* 12 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | - Using welcoming and inclusive language 12 | - Being respectful of differing viewpoints and experiences 13 | - Gracefully accepting constructive criticism 14 | - Focusing on what is best for the community 15 | - Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | - The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | - Trolling, insulting/derogatory comments, and personal or political attacks 21 | - Public or private harassment 22 | - Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | - Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Commit 2 | 3 | Format of the commit message: **type(scope) subject** 4 | 5 | **Type**: 6 | 7 | - feature(scope) subject 8 | - fix(scope) subject 9 | - docs(scope) subject 10 | - refactor(scope) subject 11 | - test(scope) subject 12 | - chore(scope) subject 13 | 14 | **Scope**: 15 | Scope could be anything specifying place of the commit change. 16 | For example util, console, view, edit, style etc... 17 | 18 | **Subject text**: 19 | 20 | - use imperative, present tense: “change” not “changed” nor “changes” 21 | - don't capitalize first letter 22 | - no dot (.) at the end 23 | **Message body**: 24 | - just as in use imperative, present tense: “change” not “changed” nor “changes” 25 | - includes motivation for the change and contrasts with previous behavior 26 | 27 | **Examples**: 28 | 29 | - [fix(style) .name{width}: 37% -> 35%](https://github.com/coderaiser/cloudcmd/commit/94b0642e3990c17b3a0ee3efeb75f343e1e7c050) 30 | - [fix(console) dispatch: focus -> mouseup](https://github.com/coderaiser/cloudcmd/commit/f41ec5058d1411e86a881f8e8077e0572e0409ec) 31 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | 2025.03.22, v12.0.0 2 | 3 | feature: 4 | - 3e4b3e0 redrun: eslint-plugin-putout v26.0.2 5 | - 8f27dc9 redrun: putout v39.0.2 6 | - f936fd3 redrun: madrun v11.0.0 7 | - dcf4c45 redrun: drop support of node < 20 8 | 9 | 2025.02.18, v11.1.0 10 | 11 | feature: 12 | - 2add89f redrun: add ability to run scripts when node_modules installed upper in directory tree 13 | 14 | 2025.02.12, v11.0.7 15 | 16 | feature: 17 | - b35e422 redrun: eslint-plugin-putout v24.0.3 18 | - cac2c43 redrun: putout v38.1.2 19 | - fa7291e redrun: envir v3.0.1 20 | 21 | 2025.01.09, v11.0.6 22 | 23 | feature: 24 | - 48d3de2 redrun: eslint-plugin-n v17.15.1 25 | - a7c373e redrun: eslint v9.17.0 26 | - 54326f9 redrun: c8 v10.1.3 27 | - 0f6af06 redrun: eslint-plugin-putout v23.4.0 28 | - 55ae750 redrun: putout v37.12.0 29 | 30 | 2024.03.19, v11.0.5 31 | 32 | fix: 33 | - cd6711b redrun: mjs -> js 34 | 35 | 2024.03.19, v11.0.4 36 | 37 | feature: 38 | - aa0f963 redrun: coupole npm -s 39 | 40 | 2024.03.19, v11.0.3 41 | 42 | fix: 43 | - 69e90f3 redrun: c8: dependencies -> devDependencies 44 | 45 | 2024.03.19, v11.0.2 46 | 47 | feature: 48 | - 34ab881 convert to ESM 49 | - b4ba7c2 redrun: add support of `npm run -s` 50 | - b1a6fca redrun: string-replace-async v3.0.2 51 | 52 | 2024.02.01, v11.0.1 53 | 54 | feature: 55 | - 5a6bef8 redrun: supertape v10.0.0 56 | - 5fc0b7c redrun: madrun v10.0.1 57 | - ff23fa9 redrun: putout v35.0.3 58 | - 25d19df redrun: c8 v9.1.0 59 | 60 | 2023.12.12, v11.0.0 61 | 62 | feature: 63 | - 0333f52 redrun: drop support of node < 18 64 | - f35f965 redrun: madrun v10.0.0 65 | - 5a4235a redrun: parent-directories v3.0.0 66 | - e7ce372 redrun: eslint-plugin-putout v22.1.0 67 | - 49f7bde redrun: putout v34.0.8 68 | - 032c7d9 redrun: supertape v9.0.0 69 | 70 | 2023.08.13, v10.2.0 71 | 72 | feature: 73 | - f7fca52 package: eslint-plugin-putout v19.0.4 74 | - 76072f4 package: putout v31.1.0 75 | - f8d95e3 redrun: exit with 1 when script not found 76 | 77 | 2023.07.12, v10.1.0 78 | 79 | feature: 80 | - b5859d4 package: nodemon v3.0.1 81 | - dfaabe3 package: putout v30.3.1 82 | - 2c3a6a7 package: c8 v8.0.0 83 | - 75e21ee package: eslint-plugin-n v16.0.1 84 | - c1fa1d9 package: eslint-plugin-putout v18.1.0 85 | - 622bec9 package: putout v29.0.2 86 | - 89098e5 package: supertape v8.3.0 87 | - c7e8655 package: eslint-plugin-putout v16.2.1 88 | - 4b5a544 package: putout v27.7.0 89 | 90 | 2022.08.06, v10.0.2 91 | 92 | feature: 93 | - (package) eslint-plugin-n v15.2.4 94 | - (package) coveralls v3.1.1 95 | - (package) madrun v9.0.6 96 | - (package) eslint-plugin-putout v16.0.1 97 | - (package) supertape v7.6.0 98 | - (package) yargs-parser v21.1.1 99 | - (package) putout v27.0.1 100 | - (package) eslint v8.21.0 101 | - (package) eslint-plugin-putout v15.1.1 102 | 103 | 2022.04.06, v10.0.1 104 | 105 | fix: 106 | - (redrun) engines 107 | 108 | 109 | feature: 110 | - (package) eslint-plugin-putout v14.8.0 111 | 112 | 113 | 2022.02.17, v10.0.0 114 | 115 | feature: 116 | - (package) supertape v7.0.1 117 | - (package) putout v25.0.0 118 | - (package) madrun v9.0.0 119 | - (package) yargs-parser v21.0.0 120 | - (package) putout v24.1.0 121 | - (package) eslint-plugin-putout v13.0.1 122 | - (package) eslint-plugin-putout v11.6.0 123 | - (package) putout v22.0.0 124 | - (package) eslint v8.0.0-beta.1 125 | - (package) eslint-plugin-putout v9.3.0 126 | - (package) putout v19.7.1 127 | - (package) supertape v6.1.0 128 | 129 | 130 | 2021.05.09, v9.0.1 131 | 132 | feature: 133 | - (package) supertape v5.4.0 134 | - (package) putout v17.1.0 135 | - (package) parent-dirs v2.0.0 136 | - (package) putout v15.1.1 137 | - (package) eslint-plugin-putout v7.1.1 138 | - (package) putout v14.1.3 139 | - (package) supertape v4.6.0 140 | - (package) putout v13.3.0 141 | - (package) putout v12.1.0 142 | - (package) supertape v3.7.3 143 | 144 | 145 | 2020.11.27, v9.0.0 146 | 147 | feature: 148 | - (package) madrun v8.0.0 149 | - (redrun) asyncify 150 | - (package) eslint-plugin-putout v6.1.0 151 | - (package) putout v11.0.5 152 | 153 | 154 | 2020.09.11, v8.0.3 155 | 156 | feature: 157 | - (package) yargs-parser v20.0.0 158 | 159 | 160 | 2020.08.10, v8.0.2 161 | 162 | fix: 163 | - (redrun) replace: madrun: rm useless code 164 | 165 | feature: 166 | - (package) yargs-parser v19.0.1 167 | 168 | 169 | 2020.06.26, v8.0.1 170 | 171 | feature: 172 | - (package) putout v9.1.0 173 | - (package) supertape v2.0.1 174 | - (package) eslint v7.3.1 175 | - (package) eslint-plugin-putout v5.0.1 176 | - (package) @cloudcmd/stub v3.1.0 177 | - (package) madrun v7.0.0 178 | 179 | 180 | 2020.05.04, v8.0.0 181 | 182 | feature: 183 | - (package) madrun v6.0.0 184 | - (package) eslint-plugin-putout v4.0.0 185 | - (package) putout v8.0.0 186 | - (redrun) drop support of node < 10 187 | 188 | 189 | 2020.03.25, v7.1.20 190 | 191 | feature: 192 | - (package) fullstore v3.0.0 193 | 194 | 195 | 2020.03.08, v7.1.19 196 | 197 | feature: 198 | - (package) yargs-parser v18.1.0 199 | - (package) readjson v2.0.1 200 | 201 | 202 | 2020.02.24, v7.1.18 203 | 204 | feature: 205 | - (package) try-catch v3.0.0 206 | 207 | 208 | 2020.02.10, v7.1.17 209 | 210 | feature: 211 | - (package) yargs-parser v17.0.0 212 | - (package) eslint-plugin-node v11.0.0 213 | - (package) nyc v15.0.0 214 | 215 | 216 | 2019.12.21, v7.1.16 217 | 218 | feature: 219 | - (package) for-each-key v2.0.0 220 | 221 | 222 | 2019.12.21, v7.1.15 223 | 224 | feature: 225 | - (package) rm unused jessy 226 | 227 | 228 | 2019.11.21, v7.1.14 229 | 230 | feature: 231 | - (package) envir v2.0.0 232 | 233 | 234 | 2019.11.21, v7.1.13 235 | 236 | feature: 237 | - (package) rm unused all-object-keys 238 | - (package) nodemon v2.0.0 239 | 240 | 241 | 2019.11.06, v7.1.12 242 | 243 | feature: 244 | - (package) putout v7.0.0 245 | - (package) eslint-plugin-putout v3.0.0 246 | - (package) madrun v5.0.0 247 | 248 | 249 | 2019.10.27, v7.1.11 250 | 251 | feature: 252 | - (package) yargs-parser v16.0.0 253 | 254 | 255 | 2019.10.25, v7.1.10 256 | 257 | feature: 258 | - (package) madrun v4.0.0 259 | 260 | 261 | 2019.10.07, v7.1.9 262 | 263 | feature: 264 | - (package) putout v6.12.1 265 | - (package) yargs-parser v15.0.0 266 | - (package) currify v4.0.0 267 | 268 | 269 | 2019.09.12, v7.1.8 270 | 271 | feature: 272 | - (package) somefilter v3.0.0 273 | - (package) fullstore v2.0.2 274 | 275 | 276 | 2019.09.07, v7.1.7 277 | 278 | feature: 279 | - (package) yargs-parser v14.0.0 280 | - (package) eslint-plugin-node v10.0.0 281 | 282 | 283 | 2019.08.30, v7.1.6 284 | 285 | feature: 286 | - (package) madrun v3.0.1 287 | - (package) putout v5.10.2 288 | - (package) eslint-plugin-putout v2.0.0 289 | - (package) eslint v6.1.0 290 | - (package) eslint-plugin-node v9.0.1 291 | - (package) nyc v14.1.1 292 | - (package) putout v4.2.2 293 | 294 | 295 | 2019.02.15, v7.1.5 296 | 297 | feature: 298 | - (package) madrun v2.0.0 299 | 300 | 301 | 2019.02.04, v7.1.4 302 | 303 | feature: 304 | - (package) yargs-parser v13.0.0 305 | 306 | 307 | 2019.02.01, v7.1.3 308 | 309 | fix: 310 | - (redrun) madrun.js support 311 | 312 | 313 | 2019.02.01, v7.1.2 314 | 315 | feature: 316 | - (package) yargs-parser v12.0.0 317 | 318 | 319 | 2019.01.25, v7.1.1 320 | 321 | fix: 322 | - (replace) rm unused 323 | 324 | 325 | 2019.01.25, v7.1.0 326 | 327 | feature: 328 | - (redrun) add support of madrun 329 | 330 | 331 | 2018.11.12, v7.0.3 332 | 333 | feature: 334 | - (package) eslint-plugin-node v8.0.0 335 | - (package) yargs-parser v11.1.0 336 | 337 | 338 | 2018.10.09, v7.0.2 339 | 340 | fix: 341 | - (package) back to working yargs-parser v10 342 | - (redrun) rm console.log 343 | 344 | 345 | 2018.10.09, v7.0.1 346 | 347 | feature: 348 | - (package) currify v3.0.0 349 | 350 | 351 | 2018.09.27, v7.0.0 352 | 353 | feature: 354 | - (package) debug v4.0.1 355 | - (redrun) drop support of node < 8 356 | 357 | 358 | 2018.09.24, v6.0.7 359 | 360 | feature: 361 | - (package) squad v3.0.0 362 | - (package) nyc v13.0.1 363 | - (package) add eslint-plugin-node 364 | - (package) eslint v5.1.0 365 | 366 | 367 | 2018.06.07, v6.0.6 368 | 369 | feature: 370 | - (package) debug v3.1.0: regular expression ddos 371 | 372 | 373 | 2018.06.04, v6.0.5 374 | 375 | fix: 376 | - (redrun) one-line infinite loop detect 377 | 378 | feature: 379 | - (package) nyc v12.0.2 380 | 381 | 382 | 2018.04.18, v6.0.4 383 | 384 | fix: 385 | - (quote-args) do not add quotes when there is one of different type 386 | 387 | 388 | 2018.04.17, v6.0.3 389 | 390 | fix: 391 | - (redrun) parse: quotes after -- 392 | 393 | 394 | 2018.04.05, v6.0.2 395 | 396 | feature: 397 | - (package) yargs-parser v10.0.0 398 | - (package) for-each-key 399 | 400 | 401 | 2018.03.09, v6.0.1 402 | 403 | fix: 404 | - (redrun) infinite -> Infinite 405 | 406 | 407 | 2018.03.07, v6.0.0 408 | 409 | feature: 410 | - (redrun) drop support of node < 4, legacy 411 | - (redrun) add ability to determine infinite loop 412 | 413 | 414 | 2018.02.22, v5.10.5 415 | 416 | fix: 417 | - (redrun) somefilter: node v4 support 418 | 419 | 420 | 2018.02.22, v5.10.4 421 | 422 | fix: 423 | - (redrun) npm run script not found: null -> not found 424 | 425 | feature: 426 | - (package) somefilter v2.0.0 427 | 428 | 429 | 2018.02.16, v5.10.3 430 | 431 | feature: 432 | - (package) squad v2.0.0 433 | 434 | 435 | 2018.02.08, v5.10.2 436 | 437 | fix: 438 | - (package) babel-preset-env: dependencies -> devDependencies 439 | 440 | 441 | 2018.02.08, v5.10.1 442 | 443 | feature: 444 | - (package) yargs-parser v9.0.2 445 | - (package) babel-preset-env v1.6.1 446 | - (package) try-catch v2.0.0 447 | 448 | 449 | 2017.11.08, v5.10.0 450 | 451 | feature: 452 | - (redrun) add support of npx (#8) 453 | 454 | 455 | 2017.10.05, v5.9.18 456 | 457 | feature: 458 | - (package) yargs-parser v8.0.0 459 | - (package) coveralls v3.0.0 460 | 461 | 462 | 2017.09.01, v5.9.17 463 | 464 | feature: 465 | - (package) env -> envir 466 | 467 | 468 | 2017.08.09, v5.9.16 469 | 470 | feature: 471 | - (package) debug v3.0.0 472 | 473 | 474 | 2017.06.12, v5.9.15 475 | 476 | fix: 477 | - (redrun) exit before all scripts done in mac os when curl used (#7) 478 | 479 | feature: 480 | - (package) eslint v4.0.0 481 | - (package) scripts: prewisdom: rm package-lock.json 482 | - (package) nyc v11.0.1 483 | 484 | 485 | 2017.05.24, v5.9.14 486 | 487 | fix: 488 | - (redrun) inner args 489 | - (env) no config 490 | 491 | feature: 492 | - (get-body) rm unused code 493 | - (gitignore) add swp 494 | 495 | 496 | 2017.05.24, v5.9.13 497 | 498 | feature: 499 | - (redrun) storage -> fullstore 500 | 501 | 502 | 2017.05.10, v5.9.12 503 | 504 | feature: 505 | - (package) yargs-parser v7.0.0 506 | 507 | 508 | 2017.03.20, v5.9.11 509 | 510 | fix: 511 | - (redrun) currify -> currify/legacy 512 | 513 | 514 | 2017.03.07, v5.9.10 515 | 516 | fix: 517 | - (get-body) pre + post + symbols: regexp 518 | 519 | feature: 520 | - (package) watch:lint 521 | 522 | 523 | 2017.02.21, v5.9.9 524 | 525 | feature: 526 | - (package) includes -> indexOf 527 | - (travis) node: rm 4, 5 528 | 529 | 530 | 2017.02.20, v5.9.8 531 | 532 | fix: 533 | - (package) array.includes in node <= 6 534 | 535 | 536 | 2017.02.20, v5.9.7 537 | 538 | feature: 539 | - (package) yargs-parser v5.0.0 540 | 541 | 542 | 2017.02.01, v5.9.6 543 | 544 | feature: 545 | - (redrun) Object.assign polyfill -> babel transform 546 | 547 | 548 | 2017.01.12, v5.9.5 549 | 550 | feature: 551 | - (package) description 552 | - (package) npm-run-all v4.0.0 553 | - (package) nyc v10.0.0 554 | 555 | 556 | 2016.11.23, v5.9.4 557 | 558 | feature: 559 | - (package) currify v2.0.0 560 | - (package) nyc v9.0.1 561 | 562 | 563 | 2016.11.02, v5.9.3 564 | 565 | feature: 566 | - (package) jessy v2.0.0 567 | 568 | 569 | 2016.10.20, v5.9.2 570 | 571 | fix: 572 | - (env) $npm_package_config: name with "_" 573 | 574 | 575 | 2016.10.06, v5.9.1 576 | 577 | fix: 578 | - (bin) redrun: bring back node v4.0 support 579 | 580 | 581 | 2016.10.04, v5.9.0 582 | 583 | feature: 584 | - (redrun) add Object.assign polyfill for legacy node versions 585 | - (package) redrun-legacy -> redrun_ 586 | 587 | 588 | 2016.09.28, v5.8.4 589 | 590 | fix: 591 | - (redrun) mapsome -> mapsome/legacy 592 | 593 | feature: 594 | - (redrun) includes -> indexOf: add support of old versions of node 595 | - (package) parent-dirs: put in bin/redrun.js: es2015 only 596 | 597 | 598 | 2016.09.28, v5.8.3 599 | 600 | fix: 601 | - (redrun) drop every "--" 602 | - (redrun) "npm install" -> "null" 603 | 604 | 605 | 2016.09.26, v5.8.2 606 | 607 | feature: 608 | - (package) yargs-parser v4.0.0 609 | 610 | 611 | 2016.09.20, v5.8.1 612 | 613 | feature: 614 | - (quote-args) rm unused 615 | 616 | 617 | 2016.09.19, v5.8.0 618 | 619 | feature: 620 | - (keys) keys -> all-object-keys 621 | - (redrun) improve arguments quots support in deep npm run 622 | - (eslint) rules: indent: 4 623 | 624 | 625 | 2016.09.16, v5.7.0 626 | 627 | fix: 628 | - (redrun) prevent quotes duplication 629 | 630 | feature: 631 | - (redrun) scripts args: quotes to word sequence -> quotes to every word 632 | 633 | 634 | 2016.09.09, v5.6.1 635 | 636 | fix: 637 | - (redrun) args parsing: expand of inner redrun 638 | 639 | 640 | 2016.09.06, v5.6.0 641 | 642 | feature: 643 | - (redrun) quote arguments after "--" 644 | 645 | 646 | 2016.09.06, v5.5.2 647 | 648 | fix: 649 | - (package) redrun-completion-legacy 650 | - (redrun) support of scripts that contains "." in name 651 | 652 | feature: 653 | - (package) add redrun-completion-legacy 654 | 655 | 656 | 2016.09.02, v5.5.1 657 | 658 | feature: 659 | - (redrun-completion) add compdef support 660 | 661 | 662 | 2016.09.02, v5.5.0 663 | 664 | feature: 665 | - (redrun) add redrun-completion: tab-completion of npm-scripts 666 | 667 | 668 | 2016.09.01, v5.4.0 669 | 670 | feature: 671 | - (redrun) add $npm_package_version support 672 | - (package) npm-run-all v3.0.0 673 | 674 | 675 | 2016.08.15, v5.3.4 676 | 677 | fix: 678 | - (env) globally installed modules first then node_modules/.bin -> node_modules/.bin and then global 679 | 680 | feature: 681 | - (package) nyc v8.1.0 682 | 683 | 684 | 2016.08.12, v5.3.3 685 | 686 | feature: 687 | - (package) yargs-parser v3.1.0 688 | - (package) add postpublish 689 | 690 | 691 | 2016.08.11, v5.3.2 692 | 693 | fix: 694 | - (redrun) ignore empty deep scripts 695 | 696 | 697 | 2016.08.09, v5.3.1 698 | 699 | feature: 700 | - (cli-parse) parseArgs: simplify aliases 701 | 702 | 703 | 2016.08.01, v5.3.0 704 | 705 | feature: 706 | - (cli-parse) speed up: yargs -> yargs-parser 707 | 708 | 709 | 2016.07.27, v5.2.0 710 | 711 | feature: 712 | - (cli-parse) put commands without --parallel, --series to begin 713 | 714 | 715 | 2016.07.26, v5.1.2 716 | 717 | fix: 718 | - (redrun) crash when no scripts section in package.json 719 | 720 | 721 | 2016.07.19, v5.1.1 722 | 723 | fix: 724 | - (redrun) {message} -> message 725 | 726 | 727 | 2016.07.19, v5.1.0 728 | 729 | feature: 730 | - (redrun) add more information about json parsing errors 731 | - (package) nyc v7.0.0 732 | 733 | 734 | 2016.07.11, v5.0.1 735 | 736 | fix: 737 | - (redrun) prevent built-in behaviour of "npm publish" 738 | - (redrun) prevent built-in behaviour of "npm version" 739 | 740 | 741 | 2016.07.08, v5.0.0 742 | 743 | feature: 744 | - (redrun) --parallel, --series --: add params to every script, not only to last 745 | 746 | 747 | 2016.07.08, v4.2.1 748 | 749 | fix: 750 | - chore(test) fixlint 751 | - feature(package) add fixlint 752 | - (redrun) parsing unrecognized option from package.json 753 | 754 | feature: 755 | - (redrun) let -> const 756 | - (package) add fixlint 757 | - (jscs) add validateQuoteMarks, requireSemicolons 758 | 759 | 760 | 2016.07.06, v4.2.0 761 | 762 | feature: 763 | - (redrun) add support of node.js < 4 with 764 | - (redrun) let -> const 765 | - (redrun) getEnv: traverseForInfo -> Info 766 | 767 | 768 | 2016.07.05, v4.1.1 769 | 770 | fix: 771 | - (redrun) mapsome path 772 | 773 | 774 | 2016.07.05, v4.1.0 775 | 776 | feature: 777 | - (redrun) rm deprecation message 778 | - (redrun) add ability to run scripts with redrun from any directory 779 | - (package) eslint v3.0.0 780 | - (package) add tap-min 781 | - (redrun) add -q, --quiet 782 | 783 | 784 | 2016.06.22, v4.0.0 785 | 786 | feature: 787 | - (package) add tap-min 788 | - (redrun) add -q, --quiet 789 | 790 | 791 | 2016.05.16, v3.0.1 792 | 793 | fix: 794 | - (redrun) -v, -h when no package.json in curren directory 795 | 796 | feature: 797 | - (package) npm-run-all v2.0.0 798 | - (package) keywords: add 799 | - (package) scripts: rm "-" 800 | 801 | 802 | 2016.05.06, v3.0.0 803 | 804 | fix: 805 | - (redrun) parse redrun args with ENV set 806 | 807 | feature: 808 | - (redrun) add --calm, --series-calm, --parallel-calm 809 | - (cli-parse) help: add args 810 | 811 | 812 | 2016.05.05, v2.1.0 813 | 814 | fix: 815 | - (cli-parse) script-not-found: message 816 | - (redrun) throw on similar name 817 | 818 | feature: 819 | - (redrun) add script arguments support 820 | - (redrun) add tryOrExit 821 | - (redrun) preserve colors: spawnify -> child_process.execSync 822 | 823 | 824 | 2016.05.04, v2.0.0 825 | 826 | feature: 827 | - (redrun) add support of "&&" 828 | 829 | 830 | 2016.05.04, v1.12.0 831 | 832 | fix: 833 | - (regexp) (un?)install -> (un)?install 834 | 835 | feature: 836 | - (redrun) add support of multylevel $npm_package_config variables 837 | 838 | 839 | 2016.04.29, v1.11.1 840 | 841 | feature: 842 | - (package) rm npcp 843 | 844 | 845 | 2016.04.29, v1.11.0 846 | 847 | feature: 848 | - (redrun) add npm_package_config support 849 | 850 | 851 | 2016.04.28, v1.10.1 852 | 853 | fix: 854 | - (redrun) env: child process has no parent env 855 | 856 | feature: 857 | - (redrun) preserve colors 858 | 859 | 860 | 2016.04.28, v1.10.0 861 | 862 | feature: 863 | - (redrun) add $npm_package_config support 864 | 865 | 866 | 2016.04.27, v1.9.1 867 | 868 | fix: 869 | - (redrun) parse names with "-" 870 | 871 | 872 | 2016.04.27, v1.9.0 873 | 874 | feature: 875 | - (redrun) add node_modules/.bin to env PATH 876 | - (package) lint: rm --parallel 877 | 878 | 879 | 2016.04.27, v1.8.3 880 | 881 | fix: 882 | - (redrun) names with "." 883 | 884 | 885 | 2016.04.27, v1.8.2 886 | 887 | fix: 888 | - (redrun) --loud: undefined 889 | - (regexp) redrunName, scriptName: ":" 890 | 891 | 892 | 2016.04.27, v1.8.1 893 | 894 | fix: 895 | - (redrun) wildcards support in inner parse 896 | 897 | 898 | 2016.04.27, v1.8.0 899 | 900 | feature: 901 | - (redrun) add self-parsing 902 | 903 | 904 | 2016.04.27, v1.7.1 905 | 906 | fix: 907 | - (redrun) cli-parse: no args: no script found -> help 908 | 909 | feature: 910 | - (travis) add node v6 911 | 912 | 913 | 2016.04.26, v1.7.0 914 | 915 | feature: 916 | - (redrun) add cli-parse 917 | - (redrun) cli-parse -> group-parse 918 | - (redrun) add regexp 919 | - (redrun) regexp -> wildcard 920 | 921 | 922 | 2016.04.23, v1.6.0 923 | 924 | feature: 925 | - (redrun) add t, tst support 926 | - (redrun) add ai 927 | 928 | 929 | 2016.04.23, v1.5.0 930 | 931 | feature: 932 | - (redrun) scripts:{} -> {} 933 | 934 | 935 | 2016.04.22, v1.4.1 936 | 937 | feature: 938 | - (redrun) add node 4 support 939 | 940 | 941 | 2016.04.22, v1.4.0 942 | 943 | feature: 944 | - (redrun) loud: "redrun: cmd" -> "cmd" 945 | - (package) lint -> slowlint 946 | 947 | 948 | 2016.04.22, v1.3.0 949 | 950 | feature: 951 | - (redrun) add support of reserved script names (like "npm test") 952 | - (package) add watch:test/coverage 953 | - (cli-parse) add 954 | 955 | 956 | 2016.04.22, v1.2.1 957 | 958 | feature: 959 | - (redrun) script1 script2 -> tasks 960 | 961 | 962 | 2016.04.22, v1.2.0 963 | 964 | fix: 965 | - (redrun) process.stderr -> console.error 966 | 967 | feature: 968 | - (redrun) add logo 969 | - (redurn) add -l, --loud 970 | - (redrun) add parallel, series 971 | 972 | 973 | 2016.04.21, v1.1.4 974 | 975 | feature: 976 | - (redrun) series: rm console.log 977 | 978 | 979 | 2016.04.21, v1.1.3 980 | 981 | fix: 982 | - (redrun) array 983 | 984 | 985 | 2016.04.21, v1.1.2 986 | 987 | fix: 988 | - (redrun) seriesScripts 989 | 990 | 991 | 2016.04.21, v1.1.1 992 | 993 | fix: 994 | - (redrun) series, parallel 995 | 996 | 997 | 2016.04.21, v1.1.0 998 | 999 | fix: 1000 | - (travis) codestyle -> lint 1001 | 1002 | feature: 1003 | - (package) add multi args support 1004 | - (package) add coveralls 1005 | 1006 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-now coderaiser 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 | # Redrun [![License][LicenseIMGURL]][LicenseURL] [![NPM version][NPMIMGURL]][NPMURL] [![Build Status][BuildStatusIMGURL]][BuildStatusURL] [![Coverage Status][CoverageIMGURL]][CoverageURL] 2 | 3 | [NPMIMGURL]: https://img.shields.io/npm/v/redrun.svg?style=flat 4 | [BuildStatusURL]: https://github.com/coderaiser/redrun/actions?query=workflow%3A%22Node+CI%22 "Build Status" 5 | [BuildStatusIMGURL]: https://github.com/coderaiser/redrun/workflows/Node%20CI/badge.svg 6 | [LicenseIMGURL]: https://img.shields.io/badge/license-MIT-317BF9.svg?style=flat 7 | [NPMURL]: https://npmjs.org/package/redrun "npm" 8 | [LicenseURL]: https://tldrlegal.com/license/mit-license "MIT License" 9 | [CoverageURL]: https://coveralls.io/github/coderaiser/redrun?branch=master 10 | [CoverageIMGURL]: https://coveralls.io/repos/coderaiser/redrun/badge.svg?branch=master&service=github 11 | 12 | CLI tool to run multiple npm-scripts fast. Supports madly comfortable 🏎 [**Madrun**](https://github.com/coderaiser/madrun). 13 | 14 | ![Redrun](https://github.com/coderaiser/redrun/raw/master/redrun.png "Redrun") 15 | 16 | ## Install 17 | 18 | ``` 19 | npm i redrun -g 20 | ``` 21 | 22 | # Usage 23 | 24 | ``` 25 | Usage: redrun [...tasks] [options] [-- ...args] 26 | Options: 27 | -p, --parallel run scripts in parallel 28 | -s, --series run scripts in series 29 | -q, --quiet do not output result command before execution 30 | -c, --calm return zero exit code when command completed with error 31 | -P, --parallel-calm run scripts in parallel and return zero exit code 32 | -S, --series-calm run scripts in series and return zero exit code 33 | -h, --help display this help and exit 34 | -v, --version output version information and exit 35 | ``` 36 | 37 | # Completion 38 | 39 | You can enable tab-completion of npm scripts similar to [npm's completion](https://docs.npmjs.com/cli/completion) using: 40 | 41 | ```sh 42 | redrun-completion >> ~/.bashrc 43 | redrun-completion >> ~/.zshrc 44 | ``` 45 | 46 | You may also pipe the output of redrun-completion to a file such as `/usr/local/etc/bash_completion.d/redrun` if you have a system that will read that file for you. 47 | 48 | # How it works 49 | 50 | `package.json`: 51 | 52 | ```json 53 | { 54 | "scripts": { 55 | "one": "npm run two", 56 | "two": "npm run three", 57 | "three": "echo 'hello'" 58 | } 59 | } 60 | ``` 61 | 62 | Usually these expressions would be executed one after another: 63 | 64 | ```sh 65 | coderaiser@cloudcmd:~/redrun$ npm run one 66 | 67 | > redrun@1.0.0 one /home/coderaiser/redrun 68 | > npm run two 69 | 70 | 71 | > redrun@1.0.0 two /home/coderaiser/redrun 72 | > npm run three 73 | 74 | 75 | > redrun@1.0.0 three /home/coderaiser/redrun 76 | > echo 'hello' 77 | 78 | hello 79 | ``` 80 | 81 | All these `npm run` commands that are created are slow, because each time it creates a new process. 82 | 83 | `redrun` makes it faster: 84 | 85 | ``` 86 | coderaiser@cloudcmd:~/redrun$ redrun one 87 | > echo 'hello' 88 | hello 89 | ``` 90 | 91 | ## How to use? 92 | 93 | Redrun could be used via the command line, the scripts section of `package.json` or in a script: 94 | 95 | ```js 96 | import redrun from 'redrun'; 97 | 98 | await redrun('one', { 99 | one: 'npm run two', 100 | two: 'npm run three', 101 | three: `echo 'hello'`, 102 | }); 103 | 104 | // returns 105 | `echo 'hello'`; 106 | 107 | await redrun('one', { 108 | one: 'redrun -p two three', 109 | two: 'redrun four five', 110 | three: `echo 'hello'`, 111 | four: 'jshint lib', 112 | five: 'jscs test', 113 | }); 114 | 115 | // returns 116 | `jshint lib && jscs test & echo 'hello'`; 117 | ``` 118 | 119 | ## Speed comparison 120 | 121 | The less spend time is better: 122 | 123 | - `npm-run-all`: 1m12.570s 124 | - `npm run && npm run`: 1m10.727s 125 | - `redrun`: 0m38.312s 126 | 127 | Here are logs: 128 | 129 | ### npm-run-all: 130 | 131 | ```sh 132 | coderaiser@cloudcmd:~/redrun$ time npm run speed:npm-run-all 133 | 134 | > speed:npm-run-all /home/coderaiser/redrun 135 | > npm-run-all lint:* 136 | 137 | 138 | > redrun@5.3.0 lint:jshint /home/coderaiser/redrun 139 | > jshint bin lib test 140 | 141 | 142 | > redrun@5.3.0 lint:eslint-bin /home/coderaiser/redrun 143 | > eslint --rule 'no-console:0' bin 144 | 145 | 146 | > redrun@5.3.0 lint:eslint-lib /home/coderaiser/redrun 147 | > eslint lib test 148 | 149 | 150 | > redrun@5.3.0 lint:jscs /home/coderaiser/redrun 151 | > jscs --esnext bin lib test 152 | 153 | 154 | real 1m12.570s 155 | user 0m14.431s 156 | sys 0m17.147s 157 | ``` 158 | 159 | ### npm run && npm run 160 | 161 | ```sh 162 | coderaiserser@cloudcmd:~/redrun$ time npm run speed:npm-run 163 | 164 | redrun@5.3.0 speed:npm-run /home/coderaiser/redrun 165 | > npm run lint:jshint && npm run lint:eslint-bin && npm run lint:eslint-lib && npm run lint:jscs 166 | 167 | 168 | > redrun@5.3.0 lint:jshint /home/coderaiser/redrun 169 | > jshint bin lib test 170 | 171 | 172 | > redrun@5.3.0 lint:eslint-bin /home/coderaiser/redrun 173 | > eslint --rule 'no-console:0' bin 174 | 175 | 176 | > redrun@5.3.0 lint:eslint-lib /home/coderaiser/redrun 177 | > eslint lib test 178 | 179 | 180 | > redrun@5.3.0 lint:jscs /home/coderaiser/redrun 181 | > jscs --esnext bin lib test 182 | 183 | 184 | real 1m10.727s 185 | user 0m14.670s 186 | sys 0m16.663s 187 | ``` 188 | 189 | ### redrun 190 | 191 | ```sh 192 | coderaiser@cloudcmd:~/redrun$ redrun lint:* 193 | > jshint bin lib test && eslint --rule 'no-console:0' bin && eslint lib test && jscs --esnext bin lib test 194 | 195 | real 0m38.312s 196 | user 0m8.198s 197 | sys 0m9.113s 198 | ``` 199 | 200 | As you can see `redrun` is much faster and more DRY way of using `npm scripts` than regular solutions. 201 | 202 | ## Related 203 | 204 | - [madrun](https://github.com/coderaiser/madrun) - CLI tool to run multiple npm-scripts in a madly comfortable way. 205 | 206 | ## License 207 | 208 | MIT 209 | -------------------------------------------------------------------------------- /bin/redrun-completion.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import path, {dirname} from 'node:path'; 4 | import {fileURLToPath} from 'node:url'; 5 | import process from 'node:process'; 6 | import fs from 'node:fs'; 7 | 8 | const __filename = fileURLToPath(import.meta.url); 9 | const __dirname = dirname(__filename); 10 | const filename = path.join(__dirname, '..', 'shell/redrun-completion.sh'); 11 | const read = fs.createReadStream(filename); 12 | const write = process.stdout; 13 | 14 | read.pipe(write); 15 | -------------------------------------------------------------------------------- /bin/redrun.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import {statSync} from 'node:fs'; 4 | import path from 'node:path'; 5 | import process from 'node:process'; 6 | import {execSync} from 'node:child_process'; 7 | import tryCatch from 'try-catch'; 8 | import readjson from 'readjson'; 9 | import squad from 'squad'; 10 | import mapsome from 'mapsome'; 11 | import storage from 'fullstore'; 12 | import parentDirectories from 'parent-directories'; 13 | import envir from 'envir'; 14 | import cliParse from '../lib/cli-parse.js'; 15 | 16 | const cwd = process.cwd(); 17 | const argv = process.argv.slice(2); 18 | const [first] = argv; 19 | 20 | const Directory = storage(); 21 | const InfoDirectory = storage(); 22 | const Info = storage(); 23 | const pop = ([a]) => a; 24 | 25 | const tryOrExit = squad(exitIfError, pop, tryCatch); 26 | const exitIfNotEntry = squad(exitIfError, notEntryError); 27 | 28 | let arg; 29 | let ErrorCode = 1; 30 | 31 | if (!first || /^(-v|--version|-h|--help)$/.test(first)) { 32 | arg = await cliParse(argv, {}); 33 | } else { 34 | const info = traverseForInfo(cwd).scripts || {}; 35 | nodeModulesDir(cwd); 36 | arg = await cliParse(argv, info); 37 | } 38 | 39 | if (arg.name !== 'run') { 40 | console.log(arg.output); 41 | process.exit(ErrorCode); 42 | } else { 43 | if (!arg.quiet) 44 | console.log(`> ${arg.cmd}`); 45 | 46 | if (arg.calm) 47 | ErrorCode = 0; 48 | 49 | execute(arg.cmd); 50 | } 51 | 52 | function execute(cmd) { 53 | const env = getEnv(); 54 | 55 | tryOrExit(() => { 56 | execSync(cmd, { 57 | env, 58 | stdio: [ 59 | 0, 60 | 1, 61 | 2, 62 | 'pipe', 63 | ], 64 | cwd: InfoDirectory(), 65 | }); 66 | }); 67 | } 68 | 69 | function getEnv() { 70 | const dir = Directory(); 71 | const info = Info(); 72 | 73 | const {PATH} = process.env; 74 | const env = envir(PATH, dir, info); 75 | 76 | return { 77 | ...process.env, 78 | ...env, 79 | }; 80 | } 81 | 82 | function exitIfError(error) { 83 | if (error) { 84 | console.error(error.message); 85 | process.exit(ErrorCode); 86 | } 87 | 88 | return error; 89 | } 90 | 91 | function getInfo(dir) { 92 | const infoPath = path.join(dir, 'package.json'); 93 | const [error, info] = tryCatch(readjson.sync, infoPath); 94 | 95 | exitIfNotEntry(infoPath, error); 96 | 97 | Info(info); 98 | InfoDirectory(dir); 99 | 100 | return info; 101 | } 102 | 103 | function nodeModulesDir(cwd) { 104 | for (const dir of parentDirectories(cwd)) { 105 | const nodeModulesPath = path.join(dir, 'node_modules'); 106 | const [error] = tryCatch(statSync, nodeModulesPath); 107 | 108 | if (!error) { 109 | Directory(dir); 110 | return; 111 | } 112 | } 113 | } 114 | 115 | function traverseForInfo(cwd) { 116 | const result = mapsome(getInfo, parentDirectories(cwd)); 117 | 118 | exitIfEntryError(result); 119 | 120 | return result; 121 | } 122 | 123 | function notEntryError(path, error) { 124 | if (error && error.code !== 'ENOENT') { 125 | const {message} = error; 126 | 127 | error.message = `${path}: ${message}`; 128 | 129 | return error; 130 | } 131 | } 132 | 133 | function exitIfEntryError(data) { 134 | if (!data) { 135 | const infoPath = path.join(cwd, 'package.json'); 136 | const error = Error(`Cannot find module '${infoPath}'`); 137 | 138 | exitIfError(error); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /help.json: -------------------------------------------------------------------------------- 1 | { 2 | "-p, --parallel ": "run scripts in parallel", 3 | "-s, --series ": "run scripts in series", 4 | "-q, --quiet ": "do not output result command before execution", 5 | "-c, --calm ": "return zero exit code when command completed with error", 6 | "-P, --parallel-calm ": "run scripts in parallel and return zero exit code", 7 | "-S, --series-calm ": "run scripts in series and return zero exit code", 8 | "-h, --help ": "display this help and exit", 9 | "-v, --version ": "output version information and exit" 10 | } 11 | -------------------------------------------------------------------------------- /lib/cli-parse.js: -------------------------------------------------------------------------------- 1 | import {createRequire} from 'node:module'; 2 | import os from 'node:os'; 3 | import debug from 'debug'; 4 | import yargsParser from 'yargs-parser'; 5 | import forEachKey from 'for-each-key'; 6 | import * as regexp from './regexp.js'; 7 | 8 | const require = createRequire(import.meta.url); 9 | const pkg = require('../package.json'); 10 | const helpjson = require('../help.json'); 11 | const log = debug('redrun:cli-parse'); 12 | 13 | const {assign} = Object; 14 | 15 | export default async function cliParse(argv, scripts) { 16 | log(`argv: ${argv}`); 17 | 18 | check(argv, scripts); 19 | 20 | const getArguments = (index, argv) => !~index ? '' : ' ' + argv 21 | .slice(index + 1) 22 | .join(' '); 23 | 24 | const cutArguments = (index, argv) => !~index ? argv : argv.slice(0, index); 25 | 26 | const indexOfParams = argv.indexOf('--'); 27 | const params = getArguments(indexOfParams, argv); 28 | 29 | argv = cutArguments(indexOfParams, argv); 30 | 31 | const args = parseArgs(argv); 32 | 33 | let result; 34 | 35 | const unknownName = findUnknown(args); 36 | 37 | if (unknownName) { 38 | result = build('unknown', unknown, unknownName); 39 | } else if (args.version) { 40 | result = build('version', version); 41 | } else if (args.help || !args._.concat(args.parallel, args.series, args.parallelCalm, args.seriesCalm).length) { 42 | result = build('help', help); 43 | } else { 44 | const cmd = await parse(args, params, scripts); 45 | 46 | if (!cmd) 47 | result = build('script-not-found', scriptNotFound, args); 48 | else 49 | result = { 50 | name: 'run', 51 | quiet: args.quiet, 52 | calm: args.calm, 53 | cmd, 54 | }; 55 | } 56 | 57 | log(`result: ${result.cmd || result.output}`); 58 | 59 | return result; 60 | } 61 | 62 | const parseArgs = (args) => yargsParser(args, { 63 | array: [ 64 | 'series', 65 | 'parallel', 66 | 'series-calm', 67 | 'parallel-calm', 68 | ], 69 | boolean: [ 70 | 'version', 71 | 'help', 72 | 'quiet', 73 | 'calm', 74 | ], 75 | alias: { 76 | s: 'series', 77 | p: 'parallel', 78 | S: 'series-calm', 79 | P: 'parallel-calm', 80 | v: 'version', 81 | h: 'help', 82 | q: 'quiet', 83 | c: 'calm', 84 | }, 85 | default: { 86 | 'series': [], 87 | 'parallel': [], 88 | 'parallel-calm': [], 89 | 'series-calm': [], 90 | 'version': false, 91 | 'help': false, 92 | 'quiet': false, 93 | 'calm': false, 94 | }, 95 | }); 96 | 97 | const build = (name, fn, arg) => ({ 98 | name, 99 | output: fn(arg), 100 | }); 101 | 102 | function check(argv, scripts) { 103 | if (!argv) 104 | throw Error('argv should be an array!'); 105 | 106 | if (!scripts) 107 | throw Error('scripts should be object!'); 108 | } 109 | 110 | function findUnknown(args) { 111 | const argsRegExp = regexp.cli; 112 | const keys = Object.keys(args); 113 | const test = (a) => !argsRegExp.test(a); 114 | 115 | return keys 116 | .filter(test) 117 | .pop(); 118 | } 119 | 120 | export function version() { 121 | return 'v' + pkg.version; 122 | } 123 | 124 | export function help() { 125 | let result = 'Usage: ' + pkg.name + ' [...tasks] [options] [-- ...args]\n'; 126 | 127 | result += 'Options:\n'; 128 | 129 | const addStr = (key, value) => { 130 | result += ` ${key} ${value}\n`; 131 | }; 132 | 133 | forEachKey(addStr, helpjson); 134 | 135 | return result; 136 | } 137 | 138 | export function unknown(cmd) { 139 | if (cmd.length === 1) 140 | cmd = `-${cmd}`; 141 | else 142 | cmd = `--${cmd}`; 143 | 144 | const {name} = pkg; 145 | 146 | return `${cmd} is not a ${name} option. ` + `See '${name} --help'`; 147 | } 148 | 149 | assign(cliParse, { 150 | unknown, 151 | help, 152 | version, 153 | }); 154 | 155 | export function scriptNotFound(argv) { 156 | const {s, p} = argv; 157 | const argStr = argv 158 | ._ 159 | .concat(s, p) 160 | .join(' '); 161 | 162 | return `One of scripts not found: ${argStr}`; 163 | } 164 | 165 | async function parse(args, params, scripts) { 166 | let cmd = ''; 167 | const and = (cmd) => cmd && ` && ${cmd}`; 168 | const calmDown = (array) => array.map((name) => { 169 | return `${name} || ${calm()}`; 170 | }); 171 | 172 | const calmIf = (array) => !args.calm ? array : calmDown(array); 173 | 174 | if (args.parallel.length) 175 | cmd = await parallel(args.parallel, params, scripts); 176 | 177 | if (args.parallelCalm.length) { 178 | const parallelCalm = calmDown(args.parallelCalm); 179 | cmd = await parallel(parallelCalm, params, scripts) + and(cmd); 180 | } 181 | 182 | if (args.seriesCalm.length) { 183 | const seriesCalm = calmDown(args.seriesCalm); 184 | cmd = await series(seriesCalm, params, scripts) + and(cmd); 185 | } 186 | 187 | const names = calmIf(args._).concat(args.series); 188 | 189 | if (names.length) 190 | cmd = await series(names, params, scripts) + and(cmd); 191 | 192 | return cmd; 193 | } 194 | 195 | function calm() { 196 | const platform = os.platform(); 197 | const isWin = platform === 'win32'; 198 | 199 | return isWin ? '(exit 0)' : 'true'; 200 | } 201 | 202 | async function series(names, params, scripts) { 203 | const {default: groupParse} = await import('./group-parse.js'); 204 | return await groupParse(names, {params}, scripts); 205 | } 206 | 207 | async function parallel(names, params, scripts) { 208 | const {default: groupParse} = await import('./group-parse.js'); 209 | return await groupParse(names, {params, parallel: true}, scripts); 210 | } 211 | -------------------------------------------------------------------------------- /lib/get-body.js: -------------------------------------------------------------------------------- 1 | import currify from 'currify'; 2 | import debug from 'debug'; 3 | import wildcard from './wildcard.js'; 4 | import { 5 | name as nameRegExp, 6 | enter as regEnter, 7 | } from './regexp.js'; 8 | import quote from './quote-args.js'; 9 | 10 | const log = debug('redrun:get-body'); 11 | 12 | const bodies = currify(bodies_); 13 | 14 | export default (name, options, scripts) => { 15 | if (!scripts) { 16 | scripts = options; 17 | options = {}; 18 | } 19 | 20 | log(name, nameRegExp); 21 | 22 | const [script] = name.match(nameRegExp); 23 | const args = name.replace(script, '') || ''; 24 | 25 | const body = getScripts(script, scripts) 26 | .map(bodies(scripts, args)) 27 | .map(quote); 28 | 29 | const {parallel} = options; 30 | const joiner = ` ${parallel ? '&' : '&&'} `; 31 | const result = body.join(joiner); 32 | 33 | log(`${name} -> ${result}`); 34 | 35 | if (!body.length) 36 | return null; 37 | 38 | return result; 39 | }; 40 | 41 | function getScripts(query, scripts) { 42 | const test = (name) => { 43 | return wildcard(query).test(name); 44 | }; 45 | 46 | return Object 47 | .keys(scripts) 48 | .filter(test); 49 | } 50 | 51 | function bodies_(scripts, args, name) { 52 | let inner = scripts[name]; 53 | 54 | if (args && regEnter.test(inner)) 55 | inner += ' --'; 56 | 57 | if (args) 58 | inner += args; 59 | 60 | const pre = scripts[`pre${name}`] || ''; 61 | const post = scripts[`post${name}`] || ''; 62 | 63 | if (pre) 64 | inner = `${pre} && ${inner}`; 65 | 66 | if (post) 67 | inner = `${inner} && ${post}`; 68 | 69 | return inner; 70 | } 71 | -------------------------------------------------------------------------------- /lib/group-parse.js: -------------------------------------------------------------------------------- 1 | import debug from 'debug'; 2 | import redrun from './redrun.js'; 3 | 4 | const log = debug('redrun:group-parse'); 5 | const {stringify} = JSON; 6 | 7 | export default async (names, options, scripts) => { 8 | if (!scripts) { 9 | scripts = options; 10 | options = {}; 11 | } 12 | 13 | log(`names ${names}`); 14 | log(`scripts ${stringify(scripts)}`); 15 | log(`options ${stringify(options)}`); 16 | 17 | const {parallel, params = ''} = options; 18 | 19 | const all = []; 20 | 21 | for (const name of names) { 22 | const script = await redrun(`${name}${params}`, options, scripts); 23 | all.push(script); 24 | } 25 | 26 | const allFiltered = all.filter(Boolean); 27 | 28 | const {length} = allFiltered; 29 | 30 | if (!length || all.includes(null)) 31 | return ''; 32 | 33 | const symbol = parallel ? '&' : '&&'; 34 | 35 | return allFiltered.join(` ${symbol} `); 36 | }; 37 | -------------------------------------------------------------------------------- /lib/quote-args.js: -------------------------------------------------------------------------------- 1 | import {enter as regEnter} from './regexp.js'; 2 | 3 | const endQuote = quote(/"$/); 4 | const startQuote = quote(/^"/); 5 | 6 | export default (str) => { 7 | const line = ' -- '; 8 | 9 | if (!str.includes(line)) 10 | return str; 11 | 12 | const {length} = line; 13 | const index = str.indexOf(line); 14 | 15 | const command = str.slice(0, index); 16 | const params = str.slice(index + length); 17 | 18 | if (!regEnter.test(str)) 19 | return str; 20 | 21 | if (regEnter.test(params)) 22 | return `${command} ${addQuotesOnce(params)}`; 23 | 24 | const args = params 25 | .split(' ') 26 | .map(addQuotes) 27 | .join(' '); 28 | 29 | return `${command} ${args}`; 30 | }; 31 | 32 | function addQuotes(arg) { 33 | return startQuote(arg) + arg + endQuote(arg); 34 | } 35 | 36 | function quote(reg) { 37 | return (arg) => { 38 | if (!reg.test(arg)) 39 | return '"'; 40 | 41 | return ''; 42 | }; 43 | } 44 | 45 | function addQuotesOnce(str) { 46 | const isBegin = /^['"]/.test(str); 47 | const isEnd = /['"]$/.test(str); 48 | 49 | if (isBegin && isEnd) 50 | return str; 51 | 52 | return `"${str}"`; 53 | } 54 | -------------------------------------------------------------------------------- /lib/redrun.js: -------------------------------------------------------------------------------- 1 | import debug from 'debug'; 2 | import _madrun from 'madrun'; 3 | import getBody from './get-body.js'; 4 | import cliParse from './cli-parse.js'; 5 | import replace from './replace.js'; 6 | import * as regexp from './regexp.js'; 7 | 8 | const log = debug('redrun:redrun'); 9 | const isString = (a) => typeof a === 'string'; 10 | const RegExpEnter = regexp.enter; 11 | 12 | export default async (name, options, scripts, {madrun = _madrun} = {}) => { 13 | if (!scripts) { 14 | scripts = options; 15 | options = {}; 16 | } 17 | 18 | check(name, scripts); 19 | 20 | return await parse(name, options, scripts, { 21 | madrun, 22 | }); 23 | }; 24 | 25 | function check(name, scripts) { 26 | if (!isString(name)) 27 | throw Error('name should be string!'); 28 | 29 | if (!name) 30 | throw Error('name should not be empty!'); 31 | 32 | if (typeof scripts !== 'object') 33 | throw Error('json should be object!'); 34 | } 35 | 36 | async function parse(name, options, scripts, {madrun} = {}) { 37 | let body = getBody(name, options, scripts); 38 | 39 | const history = [body]; 40 | 41 | const expandFn = expand(scripts, { 42 | madrun, 43 | }); 44 | 45 | while (RegExpEnter.test(body)) { 46 | body = await replace(body, expandFn); 47 | 48 | const infinite = isInfinite(name, history, body); 49 | 50 | if (infinite) 51 | return infinite; 52 | 53 | if (body.includes('not a redrun option')) 54 | break; 55 | } 56 | 57 | log('parse end'); 58 | 59 | return body; 60 | } 61 | 62 | function isInfinite(name, history, body) { 63 | const i = history.indexOf(body); 64 | 65 | history.push(body); 66 | 67 | if (i === -1) 68 | return false; 69 | 70 | const n = history.length - 1; 71 | 72 | const loop = history 73 | .slice(i, n) 74 | .join(' -> '); 75 | 76 | return `echo "Inifinite loop detected: ${name}: ${loop}"`; 77 | } 78 | 79 | const expand = (scripts, {madrun = _madrun} = {}) => async (type, str) => { 80 | if (type === 'npm') 81 | return getBody(str, scripts) || await redrunParse(str, scripts); 82 | 83 | if (type === 'madrun') { 84 | const [cmd, args = ''] = str.split(' -- '); 85 | return await madrun.run(cmd, args); 86 | } 87 | 88 | return await redrunParse(str, scripts); 89 | }; 90 | 91 | async function redrunParse(str, scripts) { 92 | const args = str.split(' '); 93 | const body = scripts[str]; 94 | 95 | if (body?.includes(str)) 96 | return `echo "Inifinite loop detected: ${str} -> ${str}"`; 97 | 98 | const parsed = await cliParse(args, scripts); 99 | 100 | return parsed.cmd || `echo ${parsed.output}`; 101 | } 102 | -------------------------------------------------------------------------------- /lib/regexp.js: -------------------------------------------------------------------------------- 1 | const run = 'run(-script)?'; 2 | const scriptName = '\\w+|:|\\*|-|\\.'; 3 | const _arg = `${scriptName}| --`; 4 | const _reserved = '(re)?start|stop|test'; 5 | const _reservedTest = '(te?s)?t'; 6 | const redrunArg = '--?\\w+'; 7 | const _redrunEnd = ';|&&?|\\|\\|?|>|<| #|"'; 8 | 9 | export const enter = RegExp(`madrun\\s|redrun\\s|(npm (${run}|${_reserved}|${_reservedTest}))`); 10 | export const arg = RegExp(`npm ${run} (${_arg})+`, 'g'); 11 | export const name = RegExp(`(${scriptName})+`); 12 | export const reserved = RegExp(`npm (${_reserved})`); 13 | export const reservedTest = RegExp(`^npm ${_reservedTest}$`); 14 | export const redrun = RegExp(`(npx\\s)?redrun(\\s|${redrunArg}|${scriptName}|${_redrunEnd})+`); 15 | export const madrun = RegExp(`(npx\\s)?madrun\\s(${redrunArg}|${scriptName}|${_redrunEnd})+`); 16 | export const redrunEnd = RegExp(`\\s+(${_redrunEnd}).*`); 17 | export const cli = buildCLI(); 18 | 19 | function buildCLI() { 20 | const version = 'v(ersion)?'; 21 | const help = 'h(elp)?'; 22 | const quiet = 'q(uiet)?'; 23 | const series = 's(eries)?'; 24 | const parallel = 'p(arallel)?'; 25 | const calm = 'c(alm)?'; 26 | const parallelCalm = 'P|parallel(C|-c)alm'; 27 | const seriesCalm = 'S|series(C|-c)alm'; 28 | 29 | const args = [ 30 | version, 31 | help, 32 | quiet, 33 | calm, 34 | series, 35 | seriesCalm, 36 | parallel, 37 | parallelCalm, 38 | ].join('|'); 39 | 40 | return RegExp(`^(_|\\$0|${args})$`); 41 | } 42 | -------------------------------------------------------------------------------- /lib/replace.js: -------------------------------------------------------------------------------- 1 | import debug from 'debug'; 2 | import replaceAsync from 'string-replace-async'; 3 | import * as regexp from './regexp.js'; 4 | 5 | const log = debug('redrun:replace'); 6 | 7 | export default async (result, fn) => { 8 | result = result.replaceAll('npm run -s', 'npm run'); 9 | 10 | result = await replaceNpmRun(result, fn); 11 | result = await replaceReserved(result, fn); 12 | result = await replaceReservedTest(result, fn); 13 | result = await replaceRedrun(result, fn); 14 | result = await replaceMadrun(result, fn); 15 | 16 | return result; 17 | }; 18 | 19 | async function replaceNpmRun(cmd, fn) { 20 | const str = 'npm run '; 21 | const reg = regexp.arg; 22 | const result = await replaceAsync(cmd, reg, async (cmd) => { 23 | cmd = cmd.replace(str, ''); 24 | return await fn('npm', cmd); 25 | }); 26 | 27 | return result; 28 | } 29 | 30 | async function replaceReserved(cmd, fn) { 31 | const reg = regexp.reserved; 32 | const result = await replaceAsync(cmd, reg, async (cmd) => { 33 | cmd = cmd.replace('npm ', ''); 34 | return await fn('npm', cmd); 35 | }); 36 | 37 | return result; 38 | } 39 | 40 | async function replaceReservedTest(cmd, fn) { 41 | const reg = regexp.reservedTest; 42 | const result = await replaceAsync(cmd, reg, async (cmd) => { 43 | cmd = cmd.replace(reg, 'test'); 44 | return await fn('npm', cmd); 45 | }); 46 | 47 | return result; 48 | } 49 | 50 | async function replaceRedrun(cmd, fn) { 51 | const reg = regexp.redrun; 52 | const regEnd = regexp.redrunEnd; 53 | 54 | log(`replaceRedrun: ${reg.test(cmd)} "${cmd}"`); 55 | const result = await replaceAsync(cmd, reg, async (cmd) => { 56 | log(`1) ${cmd}`); 57 | cmd = cmd.replace(/(npx\s)?redrun\s?/, ''); 58 | log(`2) ${cmd}`); 59 | 60 | const regEndMatch = cmd.match(regEnd); 61 | const ending = !regEndMatch ? '' : regEndMatch[0]; 62 | 63 | cmd = cmd.replace(regEnd, ''); 64 | cmd = await fn('redrun', cmd) + ending; 65 | 66 | return cmd; 67 | }); 68 | 69 | return result; 70 | } 71 | 72 | async function replaceMadrun(cmd, fn) { 73 | const reg = regexp.madrun; 74 | 75 | log(`replaceMadrun: ${reg.test(cmd)} "${cmd}"`); 76 | const result = await replaceAsync(cmd, reg, async (cmd) => { 77 | log(`1) ${cmd}`); 78 | cmd = cmd.replace(/(npx\s)?madrun\s?/, ''); 79 | 80 | log(`2) ${cmd}`); 81 | cmd = await fn('madrun', cmd); 82 | 83 | return cmd; 84 | }); 85 | 86 | return result; 87 | } 88 | -------------------------------------------------------------------------------- /lib/wildcard.js: -------------------------------------------------------------------------------- 1 | export default (str) => { 2 | const wildcard = `^${str 3 | .replace('.', '\\.') 4 | .replace('*', '.*') 5 | .replace('?', '.?')}$`; 6 | 7 | return RegExp(wildcard); 8 | }; 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redrun", 3 | "version": "12.0.0", 4 | "type": "module", 5 | "description": "CLI tool to run multiple npm-scripts fast", 6 | "main": "lib/redrun.js", 7 | "scripts": { 8 | "lint": "madrun lint", 9 | "fix:lint": "madrun fix:lint", 10 | "test": "madrun test", 11 | "watch:test": "madrun watch:test", 12 | "watch:tape": "madrun watch:tape", 13 | "watch:coverage:base": "madrun watch:coverage:base", 14 | "watch:coverage:tape": "madrun watch:coverage:tape", 15 | "watch:coverage": "madrun watch:coverage", 16 | "watcher": "madrun watcher", 17 | "coverage": "madrun coverage", 18 | "report": "madrun report", 19 | "postpublish": "madrun postpublish" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "git+https://github.com/coderaiser/redrun.git" 24 | }, 25 | "bin": { 26 | "redrun": "bin/redrun.js", 27 | "redrun-completion": "bin/redrun-completion.js" 28 | }, 29 | "keywords": [ 30 | "redrun", 31 | "scripts", 32 | "package", 33 | "npm", 34 | "npm run", 35 | "npm-scripts", 36 | "tool", 37 | "cli", 38 | "commond", 39 | "task", 40 | "parallel", 41 | "serial", 42 | "run", 43 | "commandline", 44 | "command" 45 | ], 46 | "author": "coderaiser (http://coderaiser.github.io/)", 47 | "bugs": { 48 | "url": "https://github.com/coderaiser/redrun/issues" 49 | }, 50 | "homepage": "https://github.com/coderaiser/redrun", 51 | "license": "MIT", 52 | "engines": { 53 | "node": ">=20" 54 | }, 55 | "dependencies": { 56 | "currify": "^4.0.0", 57 | "debug": "^4.0.1", 58 | "enquirer": "^2.3.6", 59 | "envir": "^3.0.1", 60 | "for-each-key": "^2.0.0", 61 | "fullstore": "^3.0.0", 62 | "madrun": "^11.0.0", 63 | "mapsome": "^1.0.0", 64 | "parent-directories": "^3.0.0", 65 | "readjson": "^2.0.1", 66 | "squad": "^3.0.0", 67 | "string-replace-async": "^3.0.2", 68 | "try-catch": "^3.0.0", 69 | "try-to-catch": "^3.0.0", 70 | "yargs-parser": "^21.1.1" 71 | }, 72 | "devDependencies": { 73 | "c8": "^10.1.3", 74 | "eslint": "^9.17.0", 75 | "eslint-plugin-n": "^17.15.1", 76 | "eslint-plugin-putout": "^26.0.2", 77 | "nodemon": "^3.0.1", 78 | "npm-run-all": "^4.0.0", 79 | "putout": "^39.0.2", 80 | "supertape": "^10.0.0" 81 | }, 82 | "publishConfig": { 83 | "access": "public" 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /redrun-big.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderaiser/redrun/98c6279d1d6c48b2cd633fa4cce65569276e0495/redrun-big.png -------------------------------------------------------------------------------- /redrun.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderaiser/redrun/98c6279d1d6c48b2cd633fa4cce65569276e0495/redrun.ai -------------------------------------------------------------------------------- /redrun.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderaiser/redrun/98c6279d1d6c48b2cd633fa4cce65569276e0495/redrun.png -------------------------------------------------------------------------------- /shell/redrun-completion.sh: -------------------------------------------------------------------------------- 1 | ###-begin-redrun-completion-### 2 | # 3 | # redrun npm-scripts completion script 4 | # 5 | # Installation: redrun-completion >> ~/.bashrc (or ~/.zshrc) 6 | # Or, maybe: redrun-completion > /usr/local/etc/bash_completion.d/npm 7 | # 8 | 9 | if type complete &>/dev/null; then 10 | _redrun_completion () { 11 | local words cword 12 | if type _get_comp_words_by_ref &>/dev/null; then 13 | _get_comp_words_by_ref -n = -n @ -w words -i cword 14 | else 15 | cword="$COMP_CWORD" 16 | words=("${COMP_WORDS[@]}") 17 | fi 18 | 19 | local si="$IFS" 20 | cword=2 21 | 22 | words[2]=${words[1]} 23 | words[1]='run' 24 | 25 | IFS=$'\n' COMPREPLY=($(COMP_CWORD="$cword" \ 26 | COMP_LINE="$COMP_LINE" \ 27 | COMP_POINT="$COMP_POINT" \ 28 | npm completion -- "${words[@]}" \ 29 | 2>/dev/null)) || return $? 30 | IFS="$si" 31 | } 32 | complete -o default -F _redrun_completion redrun 33 | elif type compdef &>/dev/null; then 34 | _redrun_completion() { 35 | words[3]=${words[2]} 36 | words[2]='run' 37 | 38 | local si=$IFS 39 | compadd -- $(COMP_CWORD=$((CURRENT)) \ 40 | COMP_LINE=$BUFFER \ 41 | COMP_POINT=0 \ 42 | npm completion -- "${words[@]}" \ 43 | 2>/dev/null) 44 | IFS=$si 45 | } 46 | compdef _redrun_completion redrun 47 | elif type compctl &>/dev/null; then 48 | _redrun_completion () { 49 | local cword line point words si 50 | read -Ac words 51 | read -cn cword 52 | read -l line 53 | read -ln point 54 | si="$IFS" 55 | 56 | words[3]=${words[2]} 57 | words[2]='run' 58 | 59 | IFS=$'\n' reply=($(COMP_CWORD="$cword" \ 60 | COMP_LINE="$line" \ 61 | COMP_POINT="$point" \ 62 | npm completion -- "${words[@]}" \ 63 | 2>/dev/null)) || return $? 64 | IFS="$si" 65 | } 66 | compctl -K _redrun_completion redrun 67 | fi 68 | ###-end-redrun-completion-### 69 | -------------------------------------------------------------------------------- /test/cli-parse.js: -------------------------------------------------------------------------------- 1 | import os from 'node:os'; 2 | import test from 'supertape'; 3 | import tryToCatch from 'try-to-catch'; 4 | import cliParse from '../lib/cli-parse.js'; 5 | 6 | test('cli-parse: series', async (t) => { 7 | const result = await cliParse(['--series', 'one', 'two'], { 8 | one: 'ls', 9 | two: 'pwd', 10 | }); 11 | 12 | const expected = { 13 | name: 'run', 14 | cmd: 'ls && pwd', 15 | quiet: false, 16 | calm: false, 17 | }; 18 | 19 | t.deepEqual(result, expected, 'should build cmd object'); 20 | t.end(); 21 | }); 22 | 23 | test('cli-parse: parallel', async (t) => { 24 | const result = await cliParse(['--parallel', 'one', 'two'], { 25 | one: 'ls', 26 | two: 'pwd', 27 | }); 28 | 29 | const expected = { 30 | name: 'run', 31 | cmd: 'ls & pwd', 32 | quiet: false, 33 | calm: false, 34 | }; 35 | 36 | t.deepEqual(result, expected, 'should build cmd object'); 37 | t.end(); 38 | }); 39 | 40 | test('cli-parse: parallel --quiet', async (t) => { 41 | const result = await cliParse([ 42 | '--parallel', 43 | 'one', 44 | 'two', 45 | '--quiet', 46 | ], { 47 | one: 'ls', 48 | two: 'pwd', 49 | }); 50 | 51 | const expected = { 52 | name: 'run', 53 | cmd: 'ls & pwd', 54 | quiet: true, 55 | calm: false, 56 | }; 57 | 58 | t.deepEqual(result, expected, 'should build cmd object'); 59 | t.end(); 60 | }); 61 | 62 | test('cli-parse: parallel: before script', async (t) => { 63 | const result = await cliParse([ 64 | 'main', 65 | '--parallel', 66 | 'one', 67 | 'two', 68 | ], { 69 | one: 'ls', 70 | two: 'pwd', 71 | main: 'echo hi', 72 | }); 73 | 74 | const expected = { 75 | name: 'run', 76 | cmd: 'echo hi && ls & pwd', 77 | quiet: false, 78 | calm: false, 79 | }; 80 | 81 | t.deepEqual(result, expected, 'should build cmd object'); 82 | t.end(); 83 | }); 84 | 85 | test('cli-parse: series and parallel', async (t) => { 86 | const result = await cliParse([ 87 | '--s', 88 | 'one', 89 | 'two', 90 | '-p', 91 | 'three', 92 | 'four', 93 | ], { 94 | one: 'ls', 95 | two: 'pwd', 96 | three: 'whoami', 97 | four: 'ps aux', 98 | }); 99 | 100 | const expected = { 101 | name: 'run', 102 | cmd: 'ls && pwd && whoami & ps aux', 103 | quiet: false, 104 | calm: false, 105 | }; 106 | 107 | t.deepEqual(result, expected, 'should build cmd object'); 108 | t.end(); 109 | }); 110 | 111 | test('cli-parse: series calm: linux', async (t) => { 112 | const {platform} = os; 113 | 114 | os.platform = () => 'linux'; 115 | 116 | const result = await cliParse(['--series-calm', 'one', 'two'], { 117 | one: 'ls', 118 | two: 'pwd', 119 | }); 120 | 121 | const expected = { 122 | name: 'run', 123 | cmd: 'ls || true && pwd || true', 124 | quiet: false, 125 | calm: false, 126 | }; 127 | 128 | t.deepEqual(result, expected, 'should build cmd object with "true"'); 129 | 130 | os.platform = platform; 131 | 132 | t.end(); 133 | }); 134 | 135 | test('cli-parse: parallel calm: windows', async (t) => { 136 | const {platform} = os; 137 | 138 | os.platform = () => 'win32'; 139 | 140 | const result = await cliParse(['--parallel-calm', 'one', 'two'], { 141 | one: 'ls', 142 | two: 'pwd', 143 | }); 144 | 145 | const expected = { 146 | name: 'run', 147 | cmd: 'ls || (exit 0) & pwd || (exit 0)', 148 | quiet: false, 149 | calm: false, 150 | }; 151 | 152 | t.deepEqual(result, expected, 'should build cmd object with "exit 0"'); 153 | 154 | os.platform = platform; 155 | 156 | t.end(); 157 | }); 158 | 159 | test('cli-parse: --calm: linux', async (t) => { 160 | const {platform} = os; 161 | 162 | os.platform = () => 'linux'; 163 | 164 | const result = await cliParse(['--calm', 'one', 'two'], { 165 | one: 'ls', 166 | two: 'pwd', 167 | }); 168 | 169 | const expected = { 170 | name: 'run', 171 | cmd: 'ls || true && pwd || true', 172 | quiet: false, 173 | calm: true, 174 | }; 175 | 176 | t.deepEqual(result, expected, 'should build cmd object with "exit 0"'); 177 | 178 | os.platform = platform; 179 | 180 | t.end(); 181 | }); 182 | 183 | test('cli-parse: scripts arguments: *', async (t) => { 184 | const result = await cliParse([ 185 | 'o*', 186 | '--', 187 | '--parallel', 188 | 'three', 189 | 'four', 190 | ], { 191 | 'one:ls': 'ls', 192 | 'one:ps': 'ps', 193 | }); 194 | 195 | const expected = { 196 | name: 'run', 197 | cmd: 'ls --parallel three four && ps --parallel three four', 198 | quiet: false, 199 | calm: false, 200 | }; 201 | 202 | t.deepEqual(result, expected, 'should build cmd object that contains arguments'); 203 | t.end(); 204 | }); 205 | 206 | test('cli-parse: scripts arguments: simple', async (t) => { 207 | const result = await cliParse(['one', '--', '--fix'], { 208 | 'one': 'redrun one:*', 209 | 'one:ls': 'ls', 210 | 'one:ps': 'ps', 211 | }); 212 | 213 | const expected = { 214 | name: 'run', 215 | cmd: 'ls && ps "--fix"', 216 | quiet: false, 217 | calm: false, 218 | }; 219 | 220 | t.deepEqual(result, expected, 'should build cmd object that contains arguments'); 221 | t.end(); 222 | }); 223 | 224 | test('cli-parse: scripts arguments: parallel', async (t) => { 225 | const result = await cliParse([ 226 | 'o*', 227 | '--', 228 | '--parallel', 229 | 'three', 230 | 'four', 231 | ], { 232 | one: 'ls', 233 | on: 'who', 234 | }); 235 | 236 | const expected = { 237 | name: 'run', 238 | cmd: 'ls --parallel three four && who --parallel three four', 239 | quiet: false, 240 | calm: false, 241 | }; 242 | 243 | t.deepEqual(result, expected, 'should build cmd object that contains arguments'); 244 | t.end(); 245 | }); 246 | 247 | test('cli-parse: --version', async (t) => { 248 | const output = await cliParse.version(); 249 | const result = await cliParse(['--version'], {}); 250 | 251 | const expected = { 252 | name: 'version', 253 | output, 254 | }; 255 | 256 | t.deepEqual(result, expected, 'should return object with name, output and cmd'); 257 | t.end(); 258 | }); 259 | 260 | test('cli-parse: -v', async (t) => { 261 | const output = await cliParse.version(); 262 | const result = await cliParse(['-v'], {}); 263 | 264 | const expected = { 265 | name: 'version', 266 | output, 267 | }; 268 | 269 | t.deepEqual(result, expected, 'should return object with name, output and cmd'); 270 | t.end(); 271 | }); 272 | 273 | test('cli-parse: --help', async (t) => { 274 | const output = await cliParse.help(); 275 | const result = await cliParse(['--help'], {}); 276 | 277 | const expected = { 278 | name: 'help', 279 | output, 280 | }; 281 | 282 | t.deepEqual(result, expected, 'should return object with name, output and cmd'); 283 | t.end(); 284 | }); 285 | 286 | test('cli-parse: -h', async (t) => { 287 | const output = await cliParse.help(); 288 | const result = await cliParse(['-h'], {}); 289 | 290 | const expected = { 291 | name: 'help', 292 | output, 293 | }; 294 | 295 | t.deepEqual(result, expected, 'should return object with name, output and cmd'); 296 | t.end(); 297 | }); 298 | 299 | test('cli-parse: unknown short argument', async (t) => { 300 | const {unknown} = await cliParse; 301 | const result = await cliParse(['-w'], {}); 302 | 303 | const expected = { 304 | name: 'unknown', 305 | output: unknown('w'), 306 | }; 307 | 308 | t.deepEqual(result, expected, 'should return object with name, output and cmd'); 309 | t.end(); 310 | }); 311 | 312 | test('cli-parse: unknown long argument', async (t) => { 313 | const {unknown} = await cliParse; 314 | const result = await cliParse(['--world'], {}); 315 | 316 | const expected = { 317 | name: 'unknown', 318 | output: unknown('world'), 319 | }; 320 | 321 | t.deepEqual(result, expected, 'should return object with name, output and cmd'); 322 | t.end(); 323 | }); 324 | 325 | test('cli-parse: script not found', async (t) => { 326 | const result = await cliParse(['hello'], { 327 | hello: 'npm run world', 328 | }); 329 | 330 | const expected = { 331 | name: 'run', 332 | quiet: false, 333 | calm: false, 334 | cmd: 'echo One of scripts not found: world', 335 | }; 336 | 337 | t.deepEqual(result, expected, 'should return object with name, output and cmd'); 338 | t.end(); 339 | }); 340 | 341 | test('cli-parse: deep script not found', async (t) => { 342 | const result = await cliParse(['docker'], { 343 | 'docker': 'redrun docker:pull:node docker:build docker:push', 344 | 'docker:pull:node': 'echo "docker pull node"', 345 | }); 346 | 347 | const expected = { 348 | calm: false, 349 | cmd: 'echo One of scripts not found: docker:pull:node docker:build docker:push', 350 | name: 'run', 351 | quiet: false, 352 | }; 353 | 354 | t.deepEqual(result, expected, 'should return object with name and output'); 355 | t.end(); 356 | }); 357 | 358 | test('cli-parse: deep scripts are empty', async (t) => { 359 | const result = await cliParse(['docker'], { 360 | 'docker': 'redrun docker:pull:node docker:build docker:push', 361 | 'docker:pull:node': 'echo "docker pull node"', 362 | 'docker:build': '', 363 | 'docker:push': '', 364 | }); 365 | 366 | const expected = { 367 | calm: false, 368 | cmd: 'echo "docker pull node"', 369 | name: 'run', 370 | quiet: false, 371 | }; 372 | 373 | t.deepEqual(result, expected, 'should return object with name and output'); 374 | t.end(); 375 | }); 376 | 377 | test('cli-parse: args: no scripts', async (t) => { 378 | const [e] = await tryToCatch(cliParse, []); 379 | 380 | t.equal(e.message, 'scripts should be object!', 'should throw when no args'); 381 | t.end(); 382 | }); 383 | 384 | test('cli-parse: args: no', async (t) => { 385 | const [e] = await tryToCatch(cliParse); 386 | 387 | t.equal(e.message, 'argv should be an array!', 'should throw when no args'); 388 | t.end(); 389 | }); 390 | -------------------------------------------------------------------------------- /test/get-body.js: -------------------------------------------------------------------------------- 1 | import test from 'supertape'; 2 | import getBody from '../lib/get-body.js'; 3 | 4 | test('get-body: should get script content', (t) => { 5 | const cmd = 'jshint lib/*.js'; 6 | 7 | const body = getBody('lint', { 8 | lint: cmd, 9 | }); 10 | 11 | t.equal(body, cmd, 'should body be equal to script content'); 12 | t.end(); 13 | }); 14 | 15 | test('get-body: should get script content: name with "."', (t) => { 16 | const cmd = 'jshint lib/*.js'; 17 | 18 | const body = getBody('lint.one', { 19 | 'lint.one': cmd, 20 | }); 21 | 22 | t.equal(body, cmd, 'should body be equal to script content'); 23 | t.end(); 24 | }); 25 | 26 | test('get-body: should get script content when name contains args', (t) => { 27 | const cmd = 'jshint --version'; 28 | 29 | const body = getBody('lint --version', { 30 | lint: 'jshint', 31 | }); 32 | 33 | t.equal(body, cmd, 'should body be equal to script content + args'); 34 | t.end(); 35 | }); 36 | 37 | test('get-body: pre + post + args', (t) => { 38 | const body = getBody('lint --version', { 39 | lint: 'jshint', 40 | prelint: 'pre', 41 | postlint: 'post', 42 | }); 43 | 44 | t.equal(body, 'pre && jshint --version && post', 'should body be equal to script content + args'); 45 | t.end(); 46 | }); 47 | 48 | test('get-body: pre + post + args: regexp', (t) => { 49 | const body = getBody('lint:*', { 50 | 'lint:jshint': 'jshint lib/*.js', 51 | 'lint:jscs': 'jscs lib/*.js', 52 | 'lint:eslint': 'eslint lib/*.js', 53 | }); 54 | 55 | t.equal(body, 'jshint lib/*.js && jscs lib/*.js && eslint lib/*.js', 'should body be equal to script content + args'); 56 | t.end(); 57 | }); 58 | 59 | test('get-body: pre + post + symbols: regexp', (t) => { 60 | const cmd = 'pre && jshint --version && post'; 61 | const result = getBody('lint:*', { 62 | 'lint:': 'jshint --version', 63 | 'prelint:': 'pre', 64 | 'postlint:': 'post', 65 | }); 66 | 67 | t.equal(result, cmd, 'should body be equal to script content + args'); 68 | t.end(); 69 | }); 70 | 71 | test('get-body: args: parallel', (t) => { 72 | const body = getBody('lint:*', {parallel: true}, { 73 | 'lint:jshint': 'jshint lib/*.js', 74 | 'lint:jscs': 'jscs lib/*.js', 75 | 'lint:eslint': 'eslint lib/*.js', 76 | }); 77 | 78 | t.equal(body, 'jshint lib/*.js & jscs lib/*.js & eslint lib/*.js', 'should body be equal to script content + args'); 79 | t.end(); 80 | }); 81 | -------------------------------------------------------------------------------- /test/group-parse.js: -------------------------------------------------------------------------------- 1 | import test from 'supertape'; 2 | import groupParse from '../lib/group-parse.js'; 3 | 4 | test('group-parse: serial', async (t) => { 5 | const result = await groupParse(['one', 'two', 'three'], { 6 | one: 'ls', 7 | two: 'pwd', 8 | three: 'whoami', 9 | }); 10 | 11 | t.equal(result, 'ls && pwd && whoami', 'should build cmd line'); 12 | t.end(); 13 | }); 14 | 15 | test('group-parse: parallel', async (t) => { 16 | const result = await groupParse(['one', 'two', 'three'], {parallel: true}, { 17 | one: 'ls', 18 | two: 'pwd', 19 | three: 'whoami', 20 | }); 21 | 22 | t.equal(result, 'ls & pwd & whoami', 'should build cmd line'); 23 | t.end(); 24 | }); 25 | 26 | test('group-parse: parallel: params', async (t) => { 27 | const options = { 28 | params: ' --help', 29 | parallel: true, 30 | }; 31 | 32 | const result = await groupParse(['one', 'two', 'three'], options, { 33 | one: 'ls', 34 | two: 'pwd', 35 | three: 'whoami', 36 | }); 37 | 38 | t.equal(result, 'ls --help & pwd --help & whoami --help', 'should build cmd line with params'); 39 | t.end(); 40 | }); 41 | -------------------------------------------------------------------------------- /test/madrun.js: -------------------------------------------------------------------------------- 1 | import {test, stub} from 'supertape'; 2 | import redrun from '../lib/redrun.js'; 3 | 4 | test('redrun: madrun', async (t) => { 5 | const run = stub().returns('eslint lib'); 6 | const madrun = { 7 | run, 8 | }; 9 | 10 | const options = {}; 11 | 12 | const scripts = { 13 | lint: 'madrun lint', 14 | }; 15 | 16 | await redrun('lint', options, scripts, { 17 | madrun, 18 | }); 19 | 20 | t.calledWith(run, ['lint', ''], 'should call madrun'); 21 | t.end(); 22 | }); 23 | 24 | test('redrun: madrun.js', async (t) => { 25 | const result = await redrun('lint', { 26 | lint: 'bin/madrun.js lint', 27 | }); 28 | 29 | t.equal(result, 'bin/madrun.js lint'); 30 | t.end(); 31 | }); 32 | -------------------------------------------------------------------------------- /test/redrun.js: -------------------------------------------------------------------------------- 1 | import test from 'supertape'; 2 | import tryToCatch from 'try-to-catch'; 3 | import redrun from '../lib/redrun.js'; 4 | 5 | test('simplest parse', async (t) => { 6 | const cmd = 'echo "hello world"'; 7 | const result = await redrun('two', { 8 | two: cmd, 9 | }); 10 | 11 | t.equal(result, cmd, 'should return cmd'); 12 | t.end(); 13 | }); 14 | 15 | test('simplest parse: name with "."', async (t) => { 16 | const cmd = 'bin/redrun.js lint*'; 17 | const result = await redrun('two', { 18 | two: cmd, 19 | }); 20 | 21 | t.equal(result, cmd, 'should return cmd'); 22 | t.end(); 23 | }); 24 | 25 | test('simplest parse: name with "-"', async (t) => { 26 | const cmd = 'babel lib/*.js'; 27 | const result = await redrun('build', { 28 | 'build:js': `echo 'hello'`, 29 | 'build:js-native-full': 'babel lib/*.js', 30 | 'build': 'redrun build:js-native-full', 31 | }); 32 | 33 | t.equal(result, cmd, 'should return cmd'); 34 | t.end(); 35 | }); 36 | 37 | test('simplest parse: "npm install"', async (t) => { 38 | const cmd = 'npm install'; 39 | const result = await redrun('two', { 40 | two: cmd, 41 | }); 42 | 43 | t.equal(result, cmd, 'should return cmd'); 44 | t.end(); 45 | }); 46 | 47 | test('simplest parse: "--"', async (t) => { 48 | const cmd = 'echo -- --hello'; 49 | const result = await redrun('echo--', { 50 | 'echo--': cmd, 51 | }); 52 | 53 | t.equal(result, cmd, 'should return cmd'); 54 | t.end(); 55 | }); 56 | 57 | test('simplest parse: &&', async (t) => { 58 | const cmd = 'nodemon --exec "bin/iocmd.js" && pwd'; 59 | const result = await redrun('run', { 60 | 'run': 'redrun watch:iocmd && pwd', 61 | 'watcher': 'nodemon --exec', 62 | 'watch:iocmd': 'npm run watcher -- bin/iocmd.js', 63 | }); 64 | 65 | t.equal(result, cmd, 'should return cmd'); 66 | t.end(); 67 | }); 68 | 69 | test('infinite loop', async (t) => { 70 | const result = await redrun('one', { 71 | one: 'redrun one', 72 | }); 73 | 74 | const expected = 'echo "Inifinite loop detected: one -> one"'; 75 | 76 | t.equal(result, expected); 77 | t.end(); 78 | }); 79 | 80 | test('infinite loop: one step', async (t) => { 81 | const result = await redrun('one', { 82 | one: 'npm run two', 83 | two: 'npm run one', 84 | }); 85 | 86 | const expected = 'echo "Inifinite loop detected: one: npm run two -> npm run one"'; 87 | 88 | t.equal(result, expected, 'should determine infinite loop'); 89 | t.end(); 90 | }); 91 | 92 | test('infinite loop: more steps', async (t) => { 93 | const result = await redrun('one', { 94 | one: 'npm run two', 95 | two: 'npm run three', 96 | three: 'npm run four', 97 | four: 'npm run one', 98 | }); 99 | 100 | const expected = 'echo "Inifinite loop detected: one: npm run two -> npm run three -> npm run four -> npm run one"'; 101 | 102 | t.equal(result, expected, 'should determine infinite loop'); 103 | t.end(); 104 | }); 105 | 106 | test('similar name', async (t) => { 107 | const cmd = 'redrun.js two'; 108 | const result = await redrun('one', { 109 | one: 'redrun.js two', 110 | }); 111 | 112 | t.equal(result, cmd, 'should not try to parse similar name'); 113 | t.end(); 114 | }); 115 | 116 | test('parse one level deep', async (t) => { 117 | const cmd = 'echo "hello world"'; 118 | const result = await redrun('one', { 119 | one: 'npm run two', 120 | two: cmd, 121 | }); 122 | 123 | t.equal(result, cmd, 'should parse command one leve deep'); 124 | t.end(); 125 | }); 126 | 127 | test('parse arguments', async (t) => { 128 | const cmd = 'git "--version"'; 129 | const result = await redrun('one', { 130 | one: 'npm run two -- --version', 131 | two: 'git', 132 | }); 133 | 134 | t.equal(result, cmd, 'should parse command one leve deep'); 135 | t.end(); 136 | }); 137 | 138 | test('parse reserved names: npm test', async (t) => { 139 | const cmd = 'tape test/*.js'; 140 | const result = await redrun('one', { 141 | one: 'npm run two', 142 | two: 'npm test', 143 | test: cmd, 144 | }); 145 | 146 | t.equal(result, cmd, 'should parse script test'); 147 | t.end(); 148 | }); 149 | 150 | test('parse redrun args', async (t) => { 151 | const result = await redrun('one', { 152 | one: 'npm run two', 153 | two: 'redrun --parallel test lint', 154 | test: 'tape test/*.js', 155 | lint: 'jshint lib test', 156 | }); 157 | 158 | t.equal(result, 'tape test/*.js & jshint lib test', 'should parse script test'); 159 | t.end(); 160 | }); 161 | 162 | test('parse redrun args: "*"', async (t) => { 163 | const result = await redrun('one', { 164 | 'one': 'npm run two', 165 | 'two': 'redrun --parallel lint*', 166 | 'lint:jscs': 'jscs test/*.js', 167 | 'lint:jshint': 'jshint lib test', 168 | }); 169 | 170 | t.equal(result, 'jscs test/*.js & jshint lib test', 'should parse script test'); 171 | t.end(); 172 | }); 173 | 174 | test('parse redrun args: "."', async (t) => { 175 | const result = await redrun('one.start', { 176 | 'one.start': 'npm run two', 177 | 'two': 'redrun --parallel lint*', 178 | 'lint:jscs': 'jscs test/*.js', 179 | 'lint:jshint': 'jshint lib test', 180 | }); 181 | 182 | t.equal(result, 'jscs test/*.js & jshint lib test', 'should parse script test'); 183 | t.end(); 184 | }); 185 | 186 | test('parse redrun args: "--": npm run', async (t) => { 187 | const expect = 'nodemon -w lib --exec "nyc tape test.js"'; 188 | const result = await redrun('watch-coverage', { 189 | 'watcher': 'nodemon -w lib --exec', 190 | 'coverage': 'nyc npm test', 191 | 'watch-coverage': 'npm run watcher -- "npm run coverage"', 192 | 'test': 'tape test.js', 193 | }); 194 | 195 | t.equal(result, expect, 'should add quotes to arguments'); 196 | t.end(); 197 | }); 198 | 199 | test('parse redrun args: "--": npm run: should not add quotes', async (t) => { 200 | const expect = `nodemon -w lib --exec 'nyc tape test.js'`; 201 | const result = await redrun('watch-coverage', { 202 | 'watcher': 'nodemon -w lib --exec', 203 | 'coverage': 'nyc npm test', 204 | 'watch-coverage': `npm run watcher -- 'npm run coverage'`, 205 | 'test': 'tape test.js', 206 | }); 207 | 208 | t.equal(result, expect, 'should not add quotes when there is one'); 209 | t.end(); 210 | }); 211 | 212 | test('parse redrun args: "--": quotes', async (t) => { 213 | const expect = `nodemon -w test -w lib --exec "tape 'lib/**/*.spec.js'"`; 214 | const result = await redrun('watch:test', { 215 | 'test': `tape 'lib/**/*.spec.js'`, 216 | 'watch:test': 'npm run watcher -- npm test', 217 | 'watcher': 'nodemon -w test -w lib --exec', 218 | }); 219 | 220 | t.equal(result, expect, 'should add quotes to arguments'); 221 | t.end(); 222 | }); 223 | 224 | test('parse redrun args: "--": redrun', async (t) => { 225 | const expect = 'nodemon -w lib --exec "bin/iocmd.js"'; 226 | const result = await redrun('watch:iocmd', { 227 | 'watch:iocmd': 'redrun watcher -- bin/iocmd.js', 228 | 'watcher': 'nodemon -w lib --exec', 229 | }); 230 | 231 | t.equal(result, expect, 'should add quotes to arguments'); 232 | t.end(); 233 | }); 234 | 235 | test('parse redrun args: "--": deep npm run', async (t) => { 236 | const expect = 'echo "es5" && echo "es6"'; 237 | const result = await redrun('echo:*', { 238 | 'echo': 'echo', 239 | 'echo:es5': 'npm run echo -- "es5"', 240 | 'echo:es6': 'npm run echo -- "es6"', 241 | }); 242 | 243 | t.equal(result, expect, 'should add quotes to arguments'); 244 | t.end(); 245 | }); 246 | 247 | test('parse redrun args: "--": should not quote "--"', async (t) => { 248 | const expect = 'browserify -s nessy "src/nessy.js" "-o" "dist/nessy.es6.js"'; 249 | const result = await redrun('es6', { 250 | 'bundle': 'browserify -s nessy', 251 | 'es6:base': 'npm run bundle -- src/nessy.js', 252 | 'es6': 'npm run es6:base -- -o dist/nessy.es6.js', 253 | }); 254 | 255 | t.equal(result, expect, 'should add quotes to arguments'); 256 | t.end(); 257 | }); 258 | 259 | test('parse redrun args: unrecognized', async (t) => { 260 | const result = await redrun('one', { 261 | one: 'npm run two', 262 | two: 'redrun hello --fix', 263 | hello: 'echo', 264 | }); 265 | 266 | t.equal(result, `echo --fix is not a redrun option. See 'redrun --help'`, 'should return error'); 267 | t.end(); 268 | }); 269 | 270 | test('parse redrun args with ENV set', async (t) => { 271 | const result = await redrun('good', { 272 | good: 'NODE_ENV=development DEBUG=iocmd* redrun -p t*', 273 | t1: 'tape test/*.js', 274 | t2: 'jshint lib test', 275 | }); 276 | 277 | t.equal(result, 'NODE_ENV=development DEBUG=iocmd* tape test/*.js & jshint lib test', 'should parse script test'); 278 | t.end(); 279 | }); 280 | 281 | test('parse a few redrun scripts', async (t) => { 282 | const result = await redrun('one', { 283 | one: 'redrun -p two three', 284 | two: 'redrun four five', 285 | three: `echo 'hello'`, 286 | four: 'jshint lib', 287 | five: 'jscs test', 288 | }); 289 | 290 | t.equal(result, `jshint lib && jscs test & echo 'hello'`, 'should parse script test'); 291 | t.end(); 292 | }); 293 | 294 | test('parse a few levels deep', async (t) => { 295 | const cmd = 'echo "hello world"'; 296 | const result = await redrun('one', { 297 | one: 'npm run two', 298 | two: 'npm run three', 299 | three: 'npm run four', 300 | four: 'npm run five', 301 | five: 'npm run six', 302 | six: cmd, 303 | }); 304 | 305 | t.equal(result, cmd, 'should parse command a few levels deep'); 306 | t.end(); 307 | }); 308 | 309 | test('npx', async (t) => { 310 | const cmd = 'npx pug -b src src/pages -o dist'; 311 | const body = await redrun('build', { 312 | 'build': 'npx redrun build:html', 313 | 'build:html': cmd, 314 | }); 315 | 316 | t.equal(body, cmd, 'should count npx'); 317 | t.end(); 318 | }); 319 | 320 | test('redrun: npm run -s', async (t) => { 321 | const cmd = '_mocha --reporter progress --timeout 4000 "tests/lib/**/*.js"'; 322 | const result = await redrun('test', { 323 | 'test': 'nyc npm run -s test:_mocha', 324 | 'test:_mocha': cmd, 325 | }); 326 | 327 | const expected = `nyc ${cmd}`; 328 | 329 | t.equal(result, expected); 330 | t.end(); 331 | }); 332 | 333 | test('redrun: npm run -s: couple', async (t) => { 334 | const cmd = '_mocha --reporter progress --timeout 4000 "tests/lib/**/*.js"'; 335 | const result = await redrun('test:*', { 336 | 'test:1': 'nyc npm run -s test_mocha', 337 | 'test:2': 'npm run -s lint', 338 | 'lint': 'putout .', 339 | 'test_mocha': cmd, 340 | }); 341 | 342 | const expected = `nyc ${cmd} && putout .`; 343 | 344 | t.equal(result, expected); 345 | t.end(); 346 | }); 347 | 348 | test('args: no name', async (t) => { 349 | const [e] = await tryToCatch(redrun); 350 | 351 | t.equal(e.message, 'name should be string!', 'should throw when no name'); 352 | t.end(); 353 | }); 354 | 355 | test('args: name is empty', async (t) => { 356 | const [e] = await tryToCatch(redrun, ''); 357 | 358 | t.equal(e.message, 'name should not be empty!', 'should throw when name is empty'); 359 | t.end(); 360 | }); 361 | 362 | test('args: no json', async (t) => { 363 | const [e] = await tryToCatch(redrun, 'on'); 364 | 365 | t.equal(e.message, 'json should be object!', 'should throw when no json'); 366 | t.end(); 367 | }); 368 | -------------------------------------------------------------------------------- /test/replace.js: -------------------------------------------------------------------------------- 1 | import test from 'supertape'; 2 | import replace from '../lib/replace.js'; 3 | 4 | test('replace: one npm run ', async (t) => { 5 | const result = await replace('npm run one', (type, str) => { 6 | t.equal(type, 'npm', 'type should be npm'); 7 | return str; 8 | }); 9 | 10 | t.equal(result, 'one', 'should get script name'); 11 | t.end(); 12 | }); 13 | 14 | test('replace: npm tst', async (t) => { 15 | const result = await replace('npm tst', (type, str) => { 16 | t.equal(type, 'npm', 'type should be npm'); 17 | return str; 18 | }); 19 | 20 | t.equal(result, 'test', 'should determine reserved: tst'); 21 | t.end(); 22 | }); 23 | 24 | test('replace: npm t', async (t) => { 25 | const result = await replace('npm t', (type, str) => { 26 | t.equal(type, 'npm', 'type should be npm'); 27 | return str; 28 | }); 29 | 30 | t.equal(result, 'test', 'should determine reserved: t'); 31 | t.end(); 32 | }); 33 | 34 | test('replace: npm version', async (t) => { 35 | const result = await replace('npm version', (type, str) => { 36 | t.equal(type, 'npm', 'type should be npm'); 37 | return str; 38 | }); 39 | 40 | t.equal(result, 'npm version', 'should leave unchanged "npm version"'); 41 | t.end(); 42 | }); 43 | 44 | test('replace: npm publish', async (t) => { 45 | const result = await replace('npm publish', (type, str) => { 46 | t.equal(type, 'npm', 'type should be npm'); 47 | return str; 48 | }); 49 | 50 | t.equal(result, 'npm publish', 'should leave unchanged "npm publish"'); 51 | t.end(); 52 | }); 53 | 54 | test('replace: a few npm runs', async (t) => { 55 | const cmd = await replace('npm run one && npm run two', (type, str) => { 56 | t.equal(type, 'npm', 'type should be npm'); 57 | return str; 58 | }); 59 | 60 | t.equal(cmd, 'one && two', 'should cut npm run from all expressions'); 61 | t.end(); 62 | }); 63 | --------------------------------------------------------------------------------