diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..f7e5077 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,18 @@ +name: CI +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Use Node.js + uses: actions/setup-node@v1 + with: + node-version: 12.x + - run: npm ci + - run: npm run build --if-present + - run: npm run test --if-present diff --git a/.gitignore b/.gitignore index 6704566..b17578f 100644 --- a/.gitignore +++ b/.gitignore @@ -102,3 +102,5 @@ dist # TernJS port file .tern-port + +lib/ \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..486c995 --- /dev/null +++ b/LICENSE @@ -0,0 +1,8 @@ +Copyright 2020 playwright-community +Copyright 2018 Smooth Code (forked) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..b6b861f --- /dev/null +++ b/package-lock.json @@ -0,0 +1,498 @@ +{ + "name": "jest-process-manager", + "version": "0.1.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@hapi/address": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@hapi/address/-/address-4.0.1.tgz", + "integrity": "sha512-0oEP5UiyV4f3d6cBL8F3Z5S7iWSX39Knnl0lY8i+6gfmmIBj44JCBNtcMgwyS+5v7j3VYavNay0NFHDS+UGQcw==", + "requires": { + "@hapi/hoek": "^9.0.0" + } + }, + "@hapi/formula": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@hapi/formula/-/formula-2.0.0.tgz", + "integrity": "sha512-V87P8fv7PI0LH7LiVi8Lkf3x+KCO7pQozXRssAHNXXL9L1K+uyu4XypLXwxqVDKgyQai6qj3/KteNlrqDx4W5A==" + }, + "@hapi/hoek": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.0.4.tgz", + "integrity": "sha512-EwaJS7RjoXUZ2cXXKZZxZqieGtc7RbvQhUy8FwDoMQtxWVi14tFjeFCYPZAM1mBCpOpiBpyaZbb9NeHc7eGKgw==" + }, + "@hapi/joi": { + "version": "17.1.1", + "resolved": "https://registry.npmjs.org/@hapi/joi/-/joi-17.1.1.tgz", + "integrity": "sha512-p4DKeZAoeZW4g3u7ZeRo+vCDuSDgSvtsB/NpfjXEHTUjSeINAi/RrVOWiVQ1isaoLzMvFEhe8n5065mQq1AdQg==", + "requires": { + "@hapi/address": "^4.0.1", + "@hapi/formula": "^2.0.0", + "@hapi/hoek": "^9.0.0", + "@hapi/pinpoint": "^2.0.0", + "@hapi/topo": "^5.0.0" + } + }, + "@hapi/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@hapi/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-vzXR5MY7n4XeIvLpfl3HtE3coZYO4raKXW766R6DZw/6aLqR26iuZ109K7a0NtF2Db0jxqh7xz2AxkUwpUFybw==" + }, + "@hapi/topo": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.0.0.tgz", + "integrity": "sha512-tFJlT47db0kMqVm3H4nQYgn6Pwg10GTZHb1pwmSiv1K4ks6drQOtfEF5ZnPjkvC+y4/bUPHK+bc87QvLcL+WMw==", + "requires": { + "@hapi/hoek": "^9.0.0" + } + }, + "@types/color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" + }, + "@types/cwd": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@types/cwd/-/cwd-0.10.0.tgz", + "integrity": "sha512-DpCQLallLsMpPxUp+OIkN5zHZV64w8ESDPCbjjbzdX5S6ycVSYiTVURx+B19zQkNQxFD5TB/9TRH8564iCWemQ==", + "dev": true + }, + "@types/exit": { + "version": "0.1.31", + "resolved": "https://registry.npmjs.org/@types/exit/-/exit-0.1.31.tgz", + "integrity": "sha512-ghT0E6Bw64RXc6lM30ExMNlYbUCsyR8oucl/q+ESGGhNOXOmBPpZHYGXCH2T/yTFq9YtudNCnZEQ4puX6aq87A==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/node": { + "version": "13.13.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.5.tgz", + "integrity": "sha512-3ySmiBYJPqgjiHA7oEaIo2Rzz0HrOZ7yrNO5HWyaE5q0lQ3BppDZ3N53Miz8bw2I7gh1/zir2MGVZBvpb1zq9g==", + "dev": true + }, + "@types/prompts": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@types/prompts/-/prompts-2.0.8.tgz", + "integrity": "sha512-lXXAa8c8ASoA61Tj9H5E5V2mRUTvNZ/dpJ5KxghI+O5geWMHt73NLZ9kEBBDK7zUUfMsMY3ZH4Sqeqh3SjSyfg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/signal-exit": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/signal-exit/-/signal-exit-3.0.0.tgz", + "integrity": "sha512-MaJ+16SOXz0Z27EMf3d88+B6UDglq1sn140a+5X/ROLkIcEfRq0CPg+1B2efF1GXQn4n+aKH4ti2hHG4Ya+Dzg==", + "dev": true + }, + "@types/wait-on": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/wait-on/-/wait-on-4.0.0.tgz", + "integrity": "sha512-Cj2jcMOzrdvWMP+Vl+qlz942eQfJk96S9kRnB1ejVMl+w9/9mUn0+pF4J+v0Iv+6zCrmBBODZAXKsRo6Y91Cfw==", + "dev": true + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + }, + "dependencies": { + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + } + } + }, + "axios": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz", + "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==", + "requires": { + "follow-redirects": "1.5.10" + } + }, + "chalk": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz", + "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + }, + "dependencies": { + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + } + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "cwd": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/cwd/-/cwd-0.10.0.tgz", + "integrity": "sha1-FyQAaUBXwioTsM8WFix+S3p/5Wc=", + "requires": { + "find-pkg": "^0.1.2", + "fs-exists-sync": "^0.1.0" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=" + }, + "expand-tilde": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-1.2.2.tgz", + "integrity": "sha1-C4HrqJflo9MdHD0QL48BRB5VlEk=", + "requires": { + "os-homedir": "^1.0.1" + } + }, + "find-file-up": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/find-file-up/-/find-file-up-0.1.3.tgz", + "integrity": "sha1-z2gJG8+fMApA2kEbN9pczlovvqA=", + "requires": { + "fs-exists-sync": "^0.1.0", + "resolve-dir": "^0.1.0" + } + }, + "find-pkg": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/find-pkg/-/find-pkg-0.1.2.tgz", + "integrity": "sha1-G9wiwG42NlUy4qJIBGhUuXiNpVc=", + "requires": { + "find-file-up": "^0.1.2" + } + }, + "find-process": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/find-process/-/find-process-1.4.3.tgz", + "integrity": "sha512-+IA+AUsQCf3uucawyTwMWcY+2M3FXq3BRvw3S+j5Jvydjk31f/+NPWpYZOJs+JUs2GvxH4Yfr6Wham0ZtRLlPA==", + "requires": { + "chalk": "^2.0.1", + "commander": "^2.11.0", + "debug": "^2.6.8" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + } + } + }, + "follow-redirects": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", + "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", + "requires": { + "debug": "=3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + } + } + }, + "fs-exists-sync": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz", + "integrity": "sha1-mC1ok6+RjnLQjeyehnP/K1qNat0=" + }, + "global-modules": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-0.2.3.tgz", + "integrity": "sha1-6lo77ULG1s6ZWk+KEmm12uIjgo0=", + "requires": { + "global-prefix": "^0.1.4", + "is-windows": "^0.2.0" + } + }, + "global-prefix": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-0.1.5.tgz", + "integrity": "sha1-jTvGuNo8qBEqFg2NSW/wRiv+948=", + "requires": { + "homedir-polyfill": "^1.0.0", + "ini": "^1.3.4", + "is-windows": "^0.2.0", + "which": "^1.2.12" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "requires": { + "parse-passwd": "^1.0.0" + } + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" + }, + "is-windows": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-0.2.0.tgz", + "integrity": "sha1-3hqm1j6indJIc3tp8f+LgALSEIw=" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + }, + "kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==" + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" + }, + "parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=" + }, + "prompts": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.3.2.tgz", + "integrity": "sha512-Q06uKs2CkNYVID0VqwfAl9mipo99zkBv/n2JtWY89Yxa3ZabWSrs0e2KTudKVa3peLUvYXMefDqIleLPVUBZMA==", + "requires": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.4" + } + }, + "resolve-dir": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-0.1.1.tgz", + "integrity": "sha1-shklmlYC+sXFxJatiUpujMQwJh4=", + "requires": { + "expand-tilde": "^1.2.2", + "global-modules": "^0.2.3" + } + }, + "rxjs": { + "version": "6.5.5", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.5.tgz", + "integrity": "sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ==", + "requires": { + "tslib": "^1.9.0" + } + }, + "signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" + }, + "sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==" + }, + "spawnd": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/spawnd/-/spawnd-4.4.0.tgz", + "integrity": "sha512-jLPOfB6QOEgMOQY15Z6+lwZEhH3F5ncXxIaZ7WHPIapwNNLyjrs61okj3VJ3K6tmP5TZ6cO0VAu9rEY4MD4YQg==", + "requires": { + "exit": "^0.1.2", + "signal-exit": "^3.0.2", + "tree-kill": "^1.2.2", + "wait-port": "^0.2.7" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + }, + "tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==" + }, + "tslib": { + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.2.tgz", + "integrity": "sha512-tTSkux6IGPnUGUd1XAZHcpu85MOkIl5zX49pO+jfsie3eP0B6pyhOlLXm3cAC6T7s+euSDDUUV+Acop5WmtkVg==" + }, + "typescript": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz", + "integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==", + "dev": true + }, + "wait-on": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-5.0.0.tgz", + "integrity": "sha512-6v9lttmGGRT7Lr16E/0rISTBIV1DN72n9+77Bpt1iBfzmhBI+75RDlacFe0Q+JizkmwWXmgHUcFG5cgx3Bwqzw==", + "requires": { + "@hapi/joi": "^17.1.1", + "axios": "^0.19.2", + "lodash": "^4.17.15", + "minimist": "^1.2.5", + "rxjs": "^6.5.5" + } + }, + "wait-port": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/wait-port/-/wait-port-0.2.7.tgz", + "integrity": "sha512-pJ6cSBIa0w1sDg4y/wXN4bmvhM9OneOvwdFHo647L2NShBi/oXG4lRaLic5cO1HaYGbUhEvratPfl/WMlIC+tg==", + "requires": { + "chalk": "^2.4.2", + "commander": "^3.0.2", + "debug": "^4.1.1" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "commander": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-3.0.2.tgz", + "integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==" + } + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "requires": { + "isexe": "^2.0.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..b9b6b41 --- /dev/null +++ b/package.json @@ -0,0 +1,39 @@ +{ + "name": "jest-process-manager", + "description": "Starts a server before your Jest tests and tears it down after.", + "version": "0.1.0", + "main": "lib/index.js", + "repository": "https://github.com/playwright-community/jest-process-manager", + "author": "Playwright Community", + "license": "MIT", + "keywords": [ + "jest", + "jest-environment", + "server" + ], + "scripts": { + "prebuild": "rm -rf lib/", + "prepublishOnly": "yarn build", + "build": "tsc", + "dev": "tsc --watch" + }, + "dependencies": { + "chalk": "^4.0.0", + "cwd": "^0.10.0", + "exit": "^0.1.2", + "find-process": "^1.4.3", + "prompts": "^2.3.2", + "signal-exit": "^3.0.3", + "spawnd": "^4.4.0", + "tree-kill": "^1.2.2", + "wait-on": "^5.0.0" + }, + "devDependencies": { + "@types/cwd": "^0.10.0", + "@types/exit": "^0.1.31", + "@types/prompts": "^2.0.8", + "@types/signal-exit": "^3.0.0", + "@types/wait-on": "^4.0.0", + "typescript": "^3.8.3" + } +} diff --git a/src/constants.ts b/src/constants.ts new file mode 100644 index 0000000..f91f640 --- /dev/null +++ b/src/constants.ts @@ -0,0 +1,17 @@ +import { JestProcessManagerOptions } from './types'; + +export const ERROR_TIMEOUT = 'ERROR_TIMEOUT' +export const ERROR_PORT_USED = 'ERROR_PORT_USED' +export const ERROR_NO_COMMAND = 'ERROR_NO_COMMAND' + +export const DEFAULT_CONFIG: JestProcessManagerOptions = { + command: 'npm run start', + debug: false, + options: {}, + launchTimeout: 5000, + host: 'localhost', + port: 3000, + protocol: 'tcp', + usedPortAction: 'ask', + waitOnScheme: {}, +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..fbf1b54 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,245 @@ +/* eslint-disable no-console */ +import stream from 'stream' +import net from 'net' +import chalk from 'chalk' +import cwd from 'cwd' +import waitOn from 'wait-on' +import findProcess from 'find-process' +import { promisify } from 'util' +import treeKill from 'tree-kill' +import prompts from 'prompts' + +import { spawn } from 'child_process' +import exit from 'exit' +import onExit from 'signal-exit' + +import type { ChildProcess, SpawnOptions } from 'child_process' +import type { CustomSpawnD, JestProcessManagerOptions } from './types'; + +import { DEFAULT_CONFIG, ERROR_NO_COMMAND, ERROR_PORT_USED, ERROR_TIMEOUT } from './constants'; + +const pTreeKill = promisify(treeKill) + +function spawnd(command: string, options: SpawnOptions): CustomSpawnD { + const proc = spawn(command, options) + const cleanExit = (code = 1) => { + if (proc && proc.pid) { + treeKill(proc.pid, () => exit(code)) + } else { + exit(code) + } + } + if (proc.stderr !== null) { + proc.stderr.pipe(process.stderr) + } + proc.on('exit', cleanExit) + proc.on('error', () => cleanExit(1)) + + const removeExitHandler = onExit(code => { + cleanExit(typeof code === 'number' ? code : 1) + }); + + proc.destroy = async (): Promise => { + removeExitHandler() + proc.removeAllListeners('exit') + proc.removeAllListeners('error') + return pTreeKill(proc.pid).catch(() => { + /* ignore error */ + }) + } + return proc as CustomSpawnD +} + +const serverLogPrefixer = new stream.Transform({ + transform(chunk, encoding, callback) { + this.push(chalk.magentaBright(`[Jest Dev server] ${chunk.toString()}`)) + callback() + }, +}) + +export class JestDevServerError extends Error { + code?: string + constructor(message: string, code?: string) { + super(message) + this.code = code + } +} + +const servers: CustomSpawnD[] = [] + +const logProcDetection = (procName: string, port: number) => { + console.log( + chalk.blue( + `🕵️ Detecting a process "${procName}" running on port "${port}"`, + ), + ) +} + +type Unwrap = T extends (...args: any) => Promise ? U : T + +async function killProc(proc: Unwrap[0]): Promise { + console.log(chalk.yellow(`Killing process ${proc.name}...`)) + await pTreeKill(proc.pid) + console.log(chalk.green(`Successfully killed process ${proc.name}`)) +} + +function runServer(config: JestProcessManagerOptions, index: number) { + if (!config.command) { + throw new JestDevServerError( + 'You must define a `command`', + ERROR_NO_COMMAND, + ) + } + + servers[index] = spawnd(config.command, { + shell: true, + env: process.env, + cwd: cwd(), + ...config.options, + }) + + if (config.debug) { + console.log(chalk.magentaBright('\nJest dev-server output:')) + servers[index].stdout!.pipe(serverLogPrefixer).pipe(process.stdout) + } +} + +async function outOfStin(block: () => Promise) { + const { stdin } = process + const listeners = stdin.listeners('data') + const result = await block() + listeners.forEach(listener => stdin.on('data', listener as (...args: any[]) => void)) + stdin.setRawMode(true) + stdin.setEncoding('utf8') + stdin.resume() + return result +} + +function getIsPortTaken(config: JestProcessManagerOptions) { + let server: net.Server + const cleanupAndReturn: (val: boolean) => void = result => + new Promise(resolve => server.once('close', () => resolve(result)).close()) + return new Promise((resolve, reject) => { + server = net + .createServer() + .once('error', (err: JestDevServerError) => + err.code === 'EADDRINUSE' ? resolve(cleanupAndReturn(true)) : reject(), + ) + .once('listening', () => resolve(cleanupAndReturn(false))) + .listen(config.port, config.host) + }) +} + +export async function setup(providedConfigs: JestProcessManagerOptions | JestProcessManagerOptions[]): Promise { + // Compatible with older versions + const configs = Array.isArray(providedConfigs) + ? providedConfigs + : [providedConfigs] + await Promise.all( + configs.map((providedConfig, index) => + setupJestServer(providedConfig, index), + ), + ) +} + +async function setupJestServer(providedConfig: JestProcessManagerOptions, index: number) { + const config = { ...DEFAULT_CONFIG, ...providedConfig } + + const usedPortHandlers = { + error() { + throw new JestDevServerError( + `Port ${config.port} is in use`, + ERROR_PORT_USED, + ) + }, + async kill() { + console.log('') + console.log( + `Killing process listening to ${config.port}. On linux, this may require you to enter your password.`, + ) + const [portProcess] = await findProcess('port', config.port) + logProcDetection(portProcess.name, config.port) + await killProc(portProcess) + }, + async ask() { + console.log('') + const answers = await outOfStin<{ kill: boolean }>(() => + prompts({ + type: 'confirm', + name: 'kill', + message: `Another process is listening on ${config.port}. Should I kill it for you? On linux, this may require you to enter your password.`, + initial: true, + }), + ) + if (answers.kill) { + const [portProcess] = await findProcess('port', config.port) + logProcDetection(portProcess.name, config.port) + await killProc(portProcess) + } else { + process.exit(1) + } + }, + ignore() { }, + } + + const usedPortHandler = usedPortHandlers[config.usedPortAction] + if (!usedPortHandler) { + const availableActions = Object.keys(usedPortHandlers) + .map(action => `\`${action}\``) + .join(', ') + throw new JestDevServerError( + `Invalid \`usedPortAction\`, only ${availableActions} are possible`, + ) + } + + if (config.port) { + const isPortTaken = await getIsPortTaken(config) + if (isPortTaken) { + await usedPortHandler() + } + + if (config.usedPortAction === 'ignore' && isPortTaken) { + console.log('') + console.log('Port is already taken. Assuming server is already running.') + } else { + runServer(config, index) + } + } else { + runServer(config, index) + } + + if (config.port) { + const { launchTimeout, protocol, host, port, waitOnScheme } = config + + let url = '' + if (protocol === 'tcp' || protocol === 'socket') { + url = `${protocol}:${host}:${port}` + } else { + url = `${protocol}://${host}:${port}` + } + const opts = { + resources: [url], + timeout: launchTimeout, + ...waitOnScheme, + } + + try { + await waitOn(opts) + } catch (err) { + throw new JestDevServerError( + `Server has taken more than ${launchTimeout}ms to start.`, + ERROR_TIMEOUT, + ) + } + } +} + +export function getServers(): ChildProcess[] { + return servers +} + +export async function teardown(): Promise { + if (servers.length) { + await Promise.all(servers.map(server => server.destroy())) + } +} diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..1827fbf --- /dev/null +++ b/src/types.ts @@ -0,0 +1,122 @@ +import type { ChildProcess, SpawnOptions } from "child_process"; +import type { WaitOnOptions } from "wait-on"; + +export interface CustomSpawnD extends ChildProcess { + destroy: () => Promise +} + +export interface JestProcessManagerOptions { + /** + * Command to execute to start the port. Directly passed to spawnd. + * + * ```js + * module.exports = { + * command: 'npm run start', + * } + * ``` + */ + command: string; + + /** + * Log server output, useful if server is crashing at start. + * @default false + * ```js + * module.exports = { + * command: 'npm run start', + * debug: true, + * } + * ``` + */ + debug?: boolean; + + /** + * How many milliseconds to wait for the spawned server to be available before giving up. Defaults to wait-port's default. + * @default 5000 + * ```js + * module.exports = { + * command: 'npm run start', + * launchTimeout: 30000, + * } + * ``` + */ + launchTimeout?: number; + + /** + * Host to wait for activity on before considering the server running. Must be used in conjunction with port. + * @default 'localhost' + * + * ```js + * module.exports = { + * command: 'npm run start --port 3000', + * host: 'customhost.com', + * port: 3000 + * } + * ``` + */ + host?: string; + + /** + * To wait for an HTTP or TCP endpoint before considering the server running, include http or tcp as a protocol. Must be used in conjunction with port. + * @default 'tcp' + * ```js + * module.exports = { + * command: 'npm run start --port 3000', + * protocol: 'http', + * port: 3000, + * } + * ``` + */ + protocol?: 'https' | 'http' | 'tcp' | 'socket'; + + /** + * Port to wait for activity on before considering the server running. If not provided, the server is assumed to immediately be running. + * @default null + * + * ```js + * module.exports = { + * command: 'npm run start --port 3000', + * port: 3000, + * } + * ``` + */ + port: number; + + /** + * It defines the action to take if port is already used: + * @default 'ask' + * + * - ask: a prompt is shown to decide if you want to kill the process or not + * - error: an errow is thrown + * - ignore: your test are executed, we assume that the server is already started + * - kill: the process is automatically killed without a prompt + * + * ```js + * module.exports = { + * command: 'npm run start --port 3000', + * port: 3000, + * usedPortAction: 'kill', + * } + */ + usedPortAction: 'ask' | 'error' | 'ignore' | 'kill'; + + /** + * jest-dev-server uses the wait-on npm package to wait for resources to become available before calling callback. + * @default {} + * + * ```js + * module.exports = { + * command: 'npm run start --port 3000', + * port: 3000, + * usedPortAction: 'kill', + * waitOnScheme: { + * delay: 1000, + * }, + * } + */ + waitOnScheme?: Partial; + + /** + * Options which will be passed down to the spawn of the process + */ + options?: SpawnOptions +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..d368aeb --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "target": "ES2018", + "module": "commonjs", + "strict": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "outDir": "lib", + "rootDir": "src" + } +}