diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..9bcdb46 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,6 @@ +{ + "extends": [ + "eslint-config-egg/typescript", + "eslint-config-egg/lib/rules/enforce-node-prefix" + ] +} diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index ed9eafd..0000000 --- a/.eslintrc.js +++ /dev/null @@ -1,8 +0,0 @@ -'use strict'; - -module.exports = { - extends: 'eslint-config-egg', - parserOptions: { - ecmaVersion: 2020, - }, -}; diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml deleted file mode 100644 index 0c8cb40..0000000 --- a/.github/workflows/codeql-analysis.yml +++ /dev/null @@ -1,74 +0,0 @@ -# For most projects, this workflow file will not need changing; you simply need -# to commit it to your repository. -# -# You may wish to alter this file to override the set of languages analyzed, -# or to provide custom queries or build logic. -# -# ******** NOTE ******** -# We have attempted to detect the languages in your repository. Please check -# the `language` matrix defined below to confirm you have the correct set of -# supported CodeQL languages. -# -name: "CodeQL" - -on: - push: - branches: [ "master" ] - pull_request: - # The branches below must be a subset of the branches above - branches: [ "master" ] - schedule: - - cron: '38 21 * * 1' - -jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write - - strategy: - fail-fast: false - matrix: - language: [ 'javascript' ] - # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] - # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support - - steps: - - name: Checkout repository - uses: actions/checkout@v3 - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v2 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - - # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs - # queries: security-extended,security-and-quality - - - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v2 - - # ℹ️ Command-line programs to run using the OS shell. - # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun - - # If the Autobuild fails above, remove it and uncomment the following three lines. - # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. - - # - run: | - # echo "Run, Build Application using script" - # ./location_of_script_within_repo/buildscript.sh - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 - with: - category: "/language:${{matrix.language}}" diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 22a8662..075facf 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -3,16 +3,13 @@ name: CI on: push: branches: [ master ] - pull_request: branches: [ master ] - workflow_dispatch: {} - jobs: Job: name: Node.js - uses: artusjs/github-actions/.github/workflows/node-test.yml@v1 + uses: node-modules/github-actions/.github/workflows/node-test.yml@master with: os: 'ubuntu-latest' - version: '14, 16, 18' + version: '16, 18, 20, 22' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1612587..1c6cbb1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,14 +4,10 @@ on: push: branches: [ master ] - workflow_dispatch: {} - jobs: release: name: Node.js - uses: artusjs/github-actions/.github/workflows/node-release.yml@v1 + uses: node-modules/github-actions/.github/workflows/node-release.yml@master secrets: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} GIT_TOKEN: ${{ secrets.GIT_TOKEN }} - with: - checkTest: false diff --git a/.gitignore b/.gitignore index 8dfbbd1..ad548be 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,6 @@ node_modules coverage *.un~ *.sw* +.tshy* +dist/ +package-lock.json diff --git a/CHANGELOG.md b/CHANGELOG.md index f5984fa..4b3f9a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,3 +13,97 @@ ### Features * use address@2 ([#53](https://github.com/node-modules/detect-port/issues/53)) ([55f48d7](https://github.com/node-modules/detect-port/commit/55f48d755f3c8b480d4e4ce1065abc1c8e3c5a19)) + +--- + + +1.5.1 / 2022-09-23 +================== + +**fixes** + * [[`9dd9ce3`](http://github.com/node-modules/detect-port/commit/9dd9ce34b560a434ee3a393f6ddea884691f632f)] - fix: add #!/usr/bin/env node header (#49) (达峰的夏天 <>) + +1.5.0 / 2022-09-21 +================== + +**features** + * [[`618dec5`](http://github.com/node-modules/detect-port/commit/618dec5661d94535800089f9d941f4896825cb69)] - feat: support wait port (#46) (达峰的夏天 <>) + +**fixes** + * [[`a54e2ef`](http://github.com/node-modules/detect-port/commit/a54e2ef70e388ed4b0c7a4b79ad88bc91e0f8ae3)] - fix: typo on line 54 (#45) (Yavuz Akyuz <<56271907+yavuzakyuz@users.noreply.github.com>>) + +**others** + * [[`28f07b3`](http://github.com/node-modules/detect-port/commit/28f07b31a7c591cb28b13281246c7f0c64c3dded)] - 🤖 TEST: Run CI on Github Action (#47) (fengmk2 <>) + * [[`ae55c95`](http://github.com/node-modules/detect-port/commit/ae55c956ca36749e22c48b8d1a7d98afec2e6a4d)] - Create codeql-analysis.yml (fengmk2 <>) + * [[`f35409d`](http://github.com/node-modules/detect-port/commit/f35409d53f9298a60e2c6c1560f42ea182025dd4)] - chore: update project config (xudafeng <>) + * [[`cd21d30`](http://github.com/node-modules/detect-port/commit/cd21d3044db73d1556bf264209c8fd0ee08fa9c4)] - chore: update readme (#43) (XiaoRui <>) + * [[`da01e68`](http://github.com/node-modules/detect-port/commit/da01e68b43952e06430cc42f873e4253d8cba09e)] - chore: add .editorconfig (#42) (达峰的夏天 <>) + * [[`a2c6b04`](http://github.com/node-modules/detect-port/commit/a2c6b043954895cba9cbae369e0d79a337c9d73a)] - chore: update repo config (#41) (达峰的夏天 <>) + * [[`8da6f33`](http://github.com/node-modules/detect-port/commit/8da6f33e10b44cdbcfb9eb5727b0f2117e6929e9)] - chore: update readme (#38) (达峰的夏天 <>) + * [[`ee88ccb`](http://github.com/node-modules/detect-port/commit/ee88ccb9e2a747dc84a30bcfc1cd4c73b64e3ea5)] - chore: remove unuse file (fengmk2 <>) + +1.3.0 / 2018-11-20 +================== + +**features** + * [[`a00357a`](http://github.com/node-modules/detect-port/commit/a00357aea32c4f011b7240641cb8da2dfc97b491)] - feat: support detect port with custom hostname (#35) (Ender Lee <<34906299+chnliquan@users.noreply.github.com>>) + +**others** + * [[`671094f`](http://github.com/node-modules/detect-port/commit/671094f3a3660a29a0920d78e39d17f8dead0b7a)] - update readme (xudafeng <>) + * [[`285e59b`](http://github.com/node-modules/detect-port/commit/285e59b0464d670c886007ff5052892393d57314)] - chore: add files to package.json (fengmk2 <>) + +1.2.3 / 2018-05-16 +================== + +**fixes** + * [[`64777f8`](http://github.com/node-modules/detect-port/commit/64777f85cc519c9c4c2c84c23d2afed6a916f3c4)] - fix: ignore EADDRNOTAVAIL error when listen localhost (#33) (Haoliang Gao <>) + * [[`398bc4f`](http://github.com/node-modules/detect-port/commit/398bc4f65f4d61ddfdc9bf7721118ea1a3bb6289)] - fix: handle 0.0.0.0:port binding (#26) (fengmk2 <>) + +**others** + * [[`aedf44f`](http://github.com/node-modules/detect-port/commit/aedf44fc3f949de9ec187bdc8ee4d8daf84d6c2b)] - doc: tweak description (xudafeng <>) + * [[`b7ff76f`](http://github.com/node-modules/detect-port/commit/b7ff76f24db3d8d9123cbf396b9032b05a6b7146)] - update FAQ & contributor (xudafeng <>) + * [[`4a9e127`](http://github.com/node-modules/detect-port/commit/4a9e127b6d01bd45d9b689bd931d878aa9b5d397)] - cli tweak to verbose (#25) (xdf <>), + +1.1.3 / 2017-05-24 +================== + + * fix: should ignore getaddrinfo ENOTFOUND error (#22) + +1.1.2 / 2017-05-11 +================== + + * fix: should double check 0.0.0.0 and localhost (#20) + * docs: ignore type of port when checking if it's occupied (#18) + +# 1.1.1 / 2017-03-17 + + * fix: try to use next available port (#16) + +# 1.1.0 / 2016-01-17 + + * Use server listen to detect port + +# 1.0.7 / 2016-12-11 + + * Early return for rejected promise + * Prevent promsie swallow in callback + +# 1.0.6 / 2016-11-29 + + * Bump version for new Repo + +# 0.1.4 / 2015-08-24 + + * Support promise + +# 0.1.2 / 2014-05-31 + + * Fix commander + +# 0.1.1 / 2014-05-30 + + * Add command line support + +# 0.1.0 / 2014-05-29 + + * Initial release diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 31b836a..e5678a5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,4 +2,4 @@ - Fork the project, make a change, and send a pull request; - Have a look at code style now before starting; -- Make sure the tests case (`$ make test`) pass before sending a pull request; +- Make sure the tests case (`$ npm test`) pass before sending a pull request; diff --git a/HISTORY.md b/HISTORY.md deleted file mode 100644 index 42d0532..0000000 --- a/HISTORY.md +++ /dev/null @@ -1,91 +0,0 @@ - -1.5.1 / 2022-09-23 -================== - -**fixes** - * [[`9dd9ce3`](http://github.com/node-modules/detect-port/commit/9dd9ce34b560a434ee3a393f6ddea884691f632f)] - fix: add #!/usr/bin/env node header (#49) (达峰的夏天 <>) - -1.5.0 / 2022-09-21 -================== - -**features** - * [[`618dec5`](http://github.com/node-modules/detect-port/commit/618dec5661d94535800089f9d941f4896825cb69)] - feat: support wait port (#46) (达峰的夏天 <>) - -**fixes** - * [[`a54e2ef`](http://github.com/node-modules/detect-port/commit/a54e2ef70e388ed4b0c7a4b79ad88bc91e0f8ae3)] - fix: typo on line 54 (#45) (Yavuz Akyuz <<56271907+yavuzakyuz@users.noreply.github.com>>) - -**others** - * [[`28f07b3`](http://github.com/node-modules/detect-port/commit/28f07b31a7c591cb28b13281246c7f0c64c3dded)] - 🤖 TEST: Run CI on Github Action (#47) (fengmk2 <>) - * [[`ae55c95`](http://github.com/node-modules/detect-port/commit/ae55c956ca36749e22c48b8d1a7d98afec2e6a4d)] - Create codeql-analysis.yml (fengmk2 <>) - * [[`f35409d`](http://github.com/node-modules/detect-port/commit/f35409d53f9298a60e2c6c1560f42ea182025dd4)] - chore: update project config (xudafeng <>) - * [[`cd21d30`](http://github.com/node-modules/detect-port/commit/cd21d3044db73d1556bf264209c8fd0ee08fa9c4)] - chore: update readme (#43) (XiaoRui <>) - * [[`da01e68`](http://github.com/node-modules/detect-port/commit/da01e68b43952e06430cc42f873e4253d8cba09e)] - chore: add .editorconfig (#42) (达峰的夏天 <>) - * [[`a2c6b04`](http://github.com/node-modules/detect-port/commit/a2c6b043954895cba9cbae369e0d79a337c9d73a)] - chore: update repo config (#41) (达峰的夏天 <>) - * [[`8da6f33`](http://github.com/node-modules/detect-port/commit/8da6f33e10b44cdbcfb9eb5727b0f2117e6929e9)] - chore: update readme (#38) (达峰的夏天 <>) - * [[`ee88ccb`](http://github.com/node-modules/detect-port/commit/ee88ccb9e2a747dc84a30bcfc1cd4c73b64e3ea5)] - chore: remove unuse file (fengmk2 <>) - -1.3.0 / 2018-11-20 -================== - -**features** - * [[`a00357a`](http://github.com/node-modules/detect-port/commit/a00357aea32c4f011b7240641cb8da2dfc97b491)] - feat: support detect port with custom hostname (#35) (Ender Lee <<34906299+chnliquan@users.noreply.github.com>>) - -**others** - * [[`671094f`](http://github.com/node-modules/detect-port/commit/671094f3a3660a29a0920d78e39d17f8dead0b7a)] - update readme (xudafeng <>) - * [[`285e59b`](http://github.com/node-modules/detect-port/commit/285e59b0464d670c886007ff5052892393d57314)] - chore: add files to package.json (fengmk2 <>) - -1.2.3 / 2018-05-16 -================== - -**fixes** - * [[`64777f8`](http://github.com/node-modules/detect-port/commit/64777f85cc519c9c4c2c84c23d2afed6a916f3c4)] - fix: ignore EADDRNOTAVAIL error when listen localhost (#33) (Haoliang Gao <>) - * [[`398bc4f`](http://github.com/node-modules/detect-port/commit/398bc4f65f4d61ddfdc9bf7721118ea1a3bb6289)] - fix: handle 0.0.0.0:port binding (#26) (fengmk2 <>) - -**others** - * [[`aedf44f`](http://github.com/node-modules/detect-port/commit/aedf44fc3f949de9ec187bdc8ee4d8daf84d6c2b)] - doc: tweak description (xudafeng <>) - * [[`b7ff76f`](http://github.com/node-modules/detect-port/commit/b7ff76f24db3d8d9123cbf396b9032b05a6b7146)] - update FAQ & contributor (xudafeng <>) - * [[`4a9e127`](http://github.com/node-modules/detect-port/commit/4a9e127b6d01bd45d9b689bd931d878aa9b5d397)] - cli tweak to verbose (#25) (xdf <>), - -1.1.3 / 2017-05-24 -================== - - * fix: should ignore getaddrinfo ENOTFOUND error (#22) - -1.1.2 / 2017-05-11 -================== - - * fix: should double check 0.0.0.0 and localhost (#20) - * docs: ignore type of port when checking if it's occupied (#18) - -# 1.1.1 / 2017-03-17 - - * fix: try to use next available port (#16) - -# 1.1.0 / 2016-01-17 - - * Use server listen to detect port - -# 1.0.7 / 2016-12-11 - - * Early return for rejected promise - * Prevent promsie swallow in callback - -# 1.0.6 / 2016-11-29 - - * Bump version for new Repo - -# 0.1.4 / 2015-08-24 - - * Support promise - -# 0.1.2 / 2014-05-31 - - * Fix commander - -# 0.1.1 / 2014-05-30 - - * Add command line support - -# 0.1.0 / 2014-05-29 - - * Initial release diff --git a/README.md b/README.md index 455d3ae..65620b4 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,22 @@ -[![logo][logo-image]][logo-url] - ---- +# detect-port [![NPM version][npm-image]][npm-url] -[![build status][travis-image]][travis-url] +[![CI](https://github.com/node-modules/detect-port/actions/workflows/nodejs.yml/badge.svg)](https://github.com/node-modules/detect-port/actions/workflows/nodejs.yml) [![Test coverage][codecov-image]][codecov-url] +[![Known Vulnerabilities][snyk-image]][snyk-url] [![npm download][download-image]][download-url] +[![Node.js Version][node-version-image]][node-version-url] -[logo-image]: ./logo.png -[logo-url]: https://npmjs.org/package/detect-port -[npm-image]: https://img.shields.io/npm/v/detect-port.svg +[npm-image]: https://img.shields.io/npm/v/detect-port.svg?style=flat-square [npm-url]: https://npmjs.org/package/detect-port -[travis-image]: https://img.shields.io/travis/node-modules/detect-port.svg -[travis-url]: https://travis-ci.org/node-modules/detect-port -[codecov-image]: https://img.shields.io/coveralls/node-modules/detect-port.svg +[codecov-image]: https://codecov.io/gh/node-modules/detect-port/branch/master/graph/badge.svg [codecov-url]: https://codecov.io/gh/node-modules/detect-port -[download-image]: https://img.shields.io/npm/dm/detect-port.svg +[snyk-image]: https://snyk.io/test/npm/detect-port/badge.svg?style=flat-square +[snyk-url]: https://snyk.io/test/npm/detect-port +[download-image]: https://img.shields.io/npm/dm/detect-port.svg?style=flat-square [download-url]: https://npmjs.org/package/detect-port +[node-version-image]: https://img.shields.io/node/v/detect-port.svg?style=flat-square +[node-version-url]: https://nodejs.org/en/download/ > Node.js implementation of port detector @@ -39,7 +39,7 @@ ## Usage ```bash -$ npm i detect-port --save +npm i detect-port ``` ```javascript @@ -65,7 +65,7 @@ detect(port) ## Command Line Tool ```bash -$ npm i detect-port -g +npm i detect-port -g ``` ### Quick Start @@ -88,24 +88,18 @@ $ detect --help Most likely network error, check that your `/etc/hosts` and make sure the content below: -``` +```bash 127.0.0.1 localhost 255.255.255.255 broadcasthost ::1 localhost ``` - - -## Contributors - -|[
xudafeng](https://github.com/xudafeng)
|[
fengmk2](https://github.com/fengmk2)
|[
ziczhu](https://github.com/ziczhu)
|[
gaearon](https://github.com/gaearon)
|[
chnliquan](https://github.com/chnliquan)
|[
popomore](https://github.com/popomore)
| -| :---: | :---: | :---: | :---: | :---: | :---: | -[
snapre](https://github.com/snapre)
|[
yavuzakyuz](https://github.com/yavuzakyuz)
|[
antife-yinyue](https://github.com/antife-yinyue)
+## License -This project follows the git-contributor [spec](https://github.com/xudafeng/git-contributor), auto updated at `Wed Sep 21 2022 23:10:27 GMT+0800`. +[MIT](LICENSE) - +## Contributors -## License +[![Contributors](https://contrib.rocks/image?repo=node-modules/detect-port)](https://github.com/node-modules/detect-port/graphs/contributors) -[MIT](LICENSE) +Made with [contributors-img](https://contrib.rocks). diff --git a/index.js b/index.js deleted file mode 100644 index 2dc88ab..0000000 --- a/index.js +++ /dev/null @@ -1,4 +0,0 @@ -'use strict'; - -module.exports = require('./lib/detect-port'); -module.exports.waitPort = require('./lib/wait-port'); diff --git a/lib/wait-port.js b/lib/wait-port.js deleted file mode 100644 index 2322aae..0000000 --- a/lib/wait-port.js +++ /dev/null @@ -1,32 +0,0 @@ -'use strict'; - -const debug = require('debug')('wait-port'); -const detect = require('./detect-port'); - -const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)); - -async function waitPort(port, options = {}) { - const { retryInterval = 1000, retries = Infinity } = options; - let count = 1; - - async function loop() { - debug('retries', retries, 'count', count); - if (count > retries) { - const err = new Error('retries exceeded'); - err.retries = retries; - err.count = count; - throw err; - } - count++; - const freePort = await detect(port); - if (freePort === port) { - await sleep(retryInterval); - return loop(); - } - return true; - } - - return await loop(); -} - -module.exports = waitPort; diff --git a/logo.png b/logo.png deleted file mode 100644 index 2dc13db..0000000 Binary files a/logo.png and /dev/null differ diff --git a/package.json b/package.json index 2104d7e..63ab590 100644 --- a/package.json +++ b/package.json @@ -7,42 +7,67 @@ "port" ], "bin": { - "detect": "./bin/detect-port.js", - "detect-port": "./bin/detect-port.js" + "detect": "dist/commonjs/bin/detect-port.js", + "detect-port": "dist/commonjs/bin/detect-port.js" }, - "main": "index.js", + "main": "./dist/commonjs/index.js", "files": [ - "bin", - "lib", - "index.js" + "dist", + "src" ], "repository": { "type": "git", "url": "git://github.com/node-modules/detect-port.git" }, "dependencies": { - "address": "^1.0.1", - "debug": "4" + "address": "^2.0.1" }, "devDependencies": { - "command-line-test": "1", - "egg-bin": "^5.2.0", - "eslint": "^8.23.1", - "eslint-config-egg": "^12.0.0", - "git-contributor": "1", - "mm": "^2.1.0", - "pedding": "^1.1.0", - "power-assert": "^1.6.1" + "@eggjs/tsconfig": "^1.3.3", + "@types/mocha": "^10.0.6", + "@types/node": "^22.10.1", + "egg-bin": "^6.9.0", + "eslint": "^8.52.0", + "eslint-config-egg": "^13.0.0", + "execa": "^8.0.1", + "mm": "^3.4.0", + "strip-ansi": "^7.1.0", + "tshy": "^3.0.2", + "tshy-after": "^1.0.0", + "typescript": "^5.2.2" }, "scripts": { + "pretest": "npm run lint -- --fix && npm run prepublishOnly", "test": "egg-bin test", - "ci": "npm run lint && egg-bin cov", - "lint": "eslint .", - "contributor": "git-contributor" + "lint": "eslint src test --ext ts", + "ci": "npm run lint && npm run cov && npm run prepublishOnly", + "prepublishOnly": "tshy && tshy-after", + "precov": "npm run prepublishOnly", + "cov": "egg-bin cov" }, "engines": { - "node": ">= 4.0.0" + "node": ">= 16.0.0" }, "homepage": "https://github.com/node-modules/detect-port", - "license": "MIT" + "license": "MIT", + "tshy": { + "exports": { + ".": "./src/index.ts" + } + }, + "exports": { + ".": { + "import": { + "types": "./dist/esm/index.d.ts", + "default": "./dist/esm/index.js" + }, + "require": { + "types": "./dist/commonjs/index.d.ts", + "default": "./dist/commonjs/index.js" + } + } + }, + "types": "./dist/commonjs/index.d.ts", + "type": "module", + "module": "./dist/esm/index.js" } diff --git a/bin/detect-port.js b/src/bin/detect-port.ts old mode 100644 new mode 100755 similarity index 81% rename from bin/detect-port.js rename to src/bin/detect-port.ts index 84da022..96c9df8 --- a/bin/detect-port.js +++ b/src/bin/detect-port.ts @@ -1,8 +1,11 @@ #!/usr/bin/env node -'use strict'; +import path from 'node:path'; +import { readFileSync } from 'node:fs'; +import detectPort from '../detect-port.js'; -const pkg = require('../package'); +const pkgFile = path.join(__dirname, '../../../package.json'); +const pkg = JSON.parse(readFileSync(pkgFile, 'utf-8')); const args = process.argv.slice(2); let arg_0 = args[0]; @@ -12,7 +15,7 @@ if (arg_0 && [ '-v', '--version' ].includes(arg_0.toLowerCase())) { process.exit(0); } -const removeByValue = (arr, val) => { +const removeByValue = (arr: string[], val: string) => { for (let i = 0; i < arr.length; i++) { if (arr[i] === val) { arr.splice(i, 1); @@ -21,18 +24,15 @@ const removeByValue = (arr, val) => { } }; -const main = require('..'); - const port = parseInt(arg_0, 10); const isVerbose = args.includes('--verbose'); removeByValue(args, '--verbose'); arg_0 = args[0]; - if (!arg_0) { const random = Math.floor(9000 + Math.random() * (65535 - 9000)); - main(random, (err, port) => { + detectPort(random, (err, port) => { if (isVerbose) { if (err) { console.log(`get available port failed with ${err}`); @@ -61,7 +61,7 @@ if (!arg_0) { console.log(` ${pkg.homepage}`); console.log(); } else { - main(port, (err, _port) => { + detectPort(port, (err, _port) => { if (isVerbose) { if (err) { console.log(`get available port failed with ${err}`); diff --git a/lib/detect-port.js b/src/detect-port.ts similarity index 52% rename from lib/detect-port.js rename to src/detect-port.ts index c94ceb0..1a30168 100644 --- a/lib/detect-port.js +++ b/src/detect-port.ts @@ -1,24 +1,35 @@ -'use strict'; +import { createServer, AddressInfo } from 'node:net'; +import { debuglog } from 'node:util'; +import { ip } from 'address'; -const net = require('net'); -const address = require('address'); -const debug = require('debug')('detect-port'); +const debug = debuglog('detect-port'); -module.exports = (port, callback) => { - let hostname = ''; +type DetectPortCallback = (err: Error | null, port?: number) => void; - if (typeof port === 'object' && port) { +interface PortConfig { + port?: number | string; + hostname?: string | undefined; + callback?: DetectPortCallback; +} + +export default function detectPort(port?: number | PortConfig | string): Promise; +export default function detectPort(callback: DetectPortCallback): void; +export default function detectPort(port: number | PortConfig | string | undefined, callback: DetectPortCallback): void; +export default function detectPort(port?: number | string | PortConfig | DetectPortCallback, callback?: DetectPortCallback) { + let hostname: string | undefined = ''; + + if (port && typeof port === 'object') { hostname = port.hostname; callback = port.callback; port = port.port; } else { if (typeof port === 'function') { callback = port; - port = null; + port = void 0; } } - port = parseInt(port) || 0; + port = parseInt(port as unknown as string) || 0; let maxPort = port + 10; if (maxPort > 65535) { maxPort = 65535; @@ -29,13 +40,13 @@ module.exports = (port, callback) => { } // promise return new Promise(resolve => { - tryListen(port, maxPort, hostname, (_, realPort) => { + tryListen(port as number, maxPort, hostname, (_, realPort) => { resolve(realPort); }); }); -}; +} -function tryListen(port, maxPort, hostname, callback) { +function tryListen(port: number, maxPort: number, hostname: string | undefined, callback: DetectPortCallback) { function handleError() { port++; if (port >= maxPort) { @@ -50,8 +61,8 @@ function tryListen(port, maxPort, hostname, callback) { if (hostname) { listen(port, hostname, (err, realPort) => { if (err) { - if (err.code === 'EADDRNOTAVAIL') { - return callback(new Error('the ip that is not unknown on the machine')); + if ((err as any).code === 'EADDRNOTAVAIL') { + return callback(new Error('The IP address is not available on this machine')); } return handleError(); } @@ -60,34 +71,34 @@ function tryListen(port, maxPort, hostname, callback) { }); } else { // 1. check null - listen(port, null, (err, realPort) => { + listen(port, void 0, (err, realPort) => { // ignore random listening if (port === 0) { return callback(err, realPort); } if (err) { - return handleError(err); + return handleError(); } // 2. check 0.0.0.0 listen(port, '0.0.0.0', err => { if (err) { - return handleError(err); + return handleError(); } // 3. check localhost listen(port, 'localhost', err => { // if localhost refer to the ip that is not unkonwn on the machine, you will see the error EADDRNOTAVAIL // https://stackoverflow.com/questions/10809740/listen-eaddrnotavail-error-in-node-js - if (err && err.code !== 'EADDRNOTAVAIL') { - return handleError(err); + if (err && (err as any).code !== 'EADDRNOTAVAIL') { + return handleError(); } // 4. check current ip - listen(port, address.ip(), (err, realPort) => { + listen(port, ip(), (err, realPort) => { if (err) { - return handleError(err); + return handleError(); } callback(null, realPort); @@ -98,23 +109,26 @@ function tryListen(port, maxPort, hostname, callback) { } } -function listen(port, hostname, callback) { - const server = new net.Server(); +function listen(port: number, hostname: string | undefined, callback: DetectPortCallback) { + const server = createServer(); - server.on('error', err => { + server.once('error', err => { debug('listen %s:%s error: %s', hostname, port, err); server.close(); - if (err.code === 'ENOTFOUND') { + + if ((err as any).code === 'ENOTFOUND') { debug('ignore dns ENOTFOUND error, get free %s:%s', hostname, port); return callback(null, port); } + return callback(err); }); + debug('try listen %d on %s', port, hostname); server.listen(port, hostname, () => { - port = server.address().port; - server.close(); + port = (server.address() as AddressInfo).port; debug('get free %s:%s', hostname, port); + server.close(); return callback(null, port); }); } diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..ebad509 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,6 @@ +import detectPort from './detect-port.js'; + +export default detectPort; + +export { detectPort }; +export * from './wait-port.js'; diff --git a/src/wait-port.ts b/src/wait-port.ts new file mode 100644 index 0000000..82809a9 --- /dev/null +++ b/src/wait-port.ts @@ -0,0 +1,45 @@ +import { debuglog } from 'node:util'; +import { setTimeout as sleep } from 'node:timers/promises'; +import detectPort from './detect-port.js'; + +const debug = debuglog('detect-port:wait-port'); + +export class WaitPortRetryError extends Error { + retries: number; + count: number; + + constructor(message: string, retries: number, count: number, options?: ErrorOptions) { + super(message, options); + this.name = this.constructor.name; + this.retries = retries; + this.count = count; + Error.captureStackTrace(this, this.constructor); + } +} + +export interface WaitPortOptions { + retryInterval?: number; + retries?: number; +} + +export async function waitPort(port: number, options: WaitPortOptions = {}) { + const { retryInterval = 1000, retries = Infinity } = options; + let count = 1; + + async function loop() { + debug('wait port %d, retries %d, count %d', port, retries, count); + if (count > retries) { + const err = new WaitPortRetryError('retries exceeded', retries, count); + throw err; + } + count++; + const freePort = await detectPort(port); + if (freePort === port) { + await sleep(retryInterval); + return loop(); + } + return true; + } + + return await loop(); +} diff --git a/test/cli.test.js b/test/cli.test.js deleted file mode 100644 index a5067ab..0000000 --- a/test/cli.test.js +++ /dev/null @@ -1,50 +0,0 @@ -'use strict'; - -const path = require('path'); -const assert = require('assert'); -const CliTest = require('command-line-test'); - -const pkg = require('../package'); - -const cliTest = new CliTest(); -const binFile = path.resolve(pkg.bin[pkg.name]); - -describe('command-line tool test', () => { - - it('should show version and exit', async () => { - let res = await cliTest.exec(`node ${binFile} -v`, {}); - assert.equal(res.stdout, pkg.version); - res = await cliTest.exec(`node ${binFile} --version`, {}); - assert(res.stdout.includes(pkg.version)); - }); - - it('should output usage information', async () => { - let res = await cliTest.exec(`node ${binFile} -h`, {}); - assert(res.stdout.includes(pkg.description)); - res = await cliTest.exec(`node ${binFile} --help`, {}); - assert(res.stdout.includes(pkg.description)); - res = await cliTest.exec(`node ${binFile} help`, {}); - assert(res.stdout.includes(pkg.description)); - res = await cliTest.exec(`node ${binFile} xxx`, {}); - assert(res.stdout.includes(pkg.description)); - }); - - it('should output available port randomly', async () => { - const res = await cliTest.exec(`node ${binFile}`, {}); - const port = parseInt(res.stdout.trim(), 10); - assert(port >= 9000 && port < 65535); - }); - - it('should output available port from the given port', async () => { - const givenPort = 9000; - const res = await cliTest.exec(`node ${binFile} ${givenPort}`, {}); - const port = parseInt(res.stdout.trim(), 10); - assert(port >= givenPort && port < 65535); - }); - - it('should output verbose logs', async () => { - const res = await cliTest.exec(`node ${binFile} --verbose`, {}); - assert(res.stdout.includes('random')); - }); - -}); diff --git a/test/cli.test.ts b/test/cli.test.ts new file mode 100644 index 0000000..0489400 --- /dev/null +++ b/test/cli.test.ts @@ -0,0 +1,51 @@ +import stripAnsi from 'strip-ansi'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { execaNode } from 'execa'; +import { strict as assert } from 'node:assert'; +import { readFileSync } from 'node:fs'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const pkgFile = path.join(__dirname, '../package.json'); +const pkg = JSON.parse(readFileSync(pkgFile, 'utf-8')); + +describe('test/cli.test.ts', async () => { + const binFile = path.join(__dirname, '../dist/commonjs/bin/detect-port.js'); + + it('should show version', async () => { + let res = await execaNode(binFile, [ '-v' ]); + assert(res.stdout, pkg.version); + res = await execaNode(binFile, [ '--version' ]); + assert(res.stdout, pkg.version); + }); + + it('should output usage information', async () => { + let res = await execaNode(binFile, [ '-h' ]); + assert(res.stdout.includes(pkg.description)); + res = await execaNode(binFile, [ '--help' ]); + assert(res.stdout.includes(pkg.description)); + res = await execaNode(binFile, [ 'help' ]); + assert(res.stdout.includes(pkg.description)); + res = await execaNode(binFile, [ 'xxx' ]); + assert(res.stdout.includes(pkg.description)); + }); + + // it('should output available port randomly', { only: true }, async () => { + // const res = await execaNode(binFile); + // const port = parseInt(stripAnsi(res.stdout).trim(), 10); + // assert(port >= 9000 && port < 65535); + // }); + + it('should output available port from the given port', async () => { + const givenPort = 9000; + const res = await execaNode(binFile, [ givenPort + '' ]); + const port = parseInt(stripAnsi(res.stdout).trim(), 10); + assert(port >= givenPort && port < 65535); + }); + + it('should output verbose logs', async () => { + const res = await execaNode(binFile, [ '--verbose' ]); + assert(res.stdout.includes('random')); + }); +}); diff --git a/test/detect-port.test.js b/test/detect-port.test.js deleted file mode 100644 index af1b3a5..0000000 --- a/test/detect-port.test.js +++ /dev/null @@ -1,189 +0,0 @@ -'use strict'; - -const mm = require('mm'); -const dns = require('dns'); -const net = require('net'); -const pedding = require('pedding'); -const address = require('address'); -const assert = require('power-assert'); - -const detectPort = require('..'); - -describe('test/detect-port.test.js', () => { - describe('detect port test', () => { - const servers = []; - before(done => { - done = pedding(13, done); - const server = new net.Server(); - server.listen(3000, 'localhost', done); - server.on('error', err => { - console.error('listen localhost error:', err); - }); - servers.push(server); - - const server2 = new net.Server(); - server2.listen(4000, address.ip(), done); - servers.push(server2); - - const server3 = new net.Server(); - server3.listen(8080, '0.0.0.0', done); - servers.push(server3); - - for (let port = 7000; port < 7010; port++) { - const server = new net.Server(); - if (port % 3 === 0) { - server.listen(port, done); - } else if (port % 3 === 1) { - server.listen(port, 'localhost', done); - } else { - server.listen(port, address.ip(), done); - } - servers.push(server); - } - }); - after(() => { - servers.forEach(server => server.close()); - }); - - afterEach(mm.restore); - - it('get random port', done => { - detectPort((_, port) => { - assert(port >= 1024 && port < 65535); - done(); - }); - }); - - it('callback with occupied port', done => { - const _port = 80; - detectPort(_port, (_, port) => { - assert(port >= _port && port < 65535); - done(); - }); - }); - - it('work with listening next port 3001 because 3000 was listened to localhost', done => { - const port = 3000; - detectPort(port, (_, realPort) => { - assert(realPort === 3001); - done(); - }); - }); - - it('should listen next port 4001 when localhost is not binding', done => { - // https://github.com/nodejs/node/blob/6af72d4b037eba38d94395f57a03a498a2efef09/lib/net.js#L1463 - // mock dns.lookup - mm(dns, '__rawLookup', dns.lookup); - mm(dns, 'lookup', (address, callback) => { - if (address !== 'localhost') { - return dns.__rawLookup(address, callback); - } - process.nextTick(() => { - const err = new Error(`getaddrinfo ENOTFOUND ${address}`); - err.code = 'ENOTFOUND'; - callback(err); - }); - }); - const port = 4000; - detectPort(port, (_, realPort) => { - assert(realPort === 4001); - done(); - }); - }); - - it('work with listening next port 4001 because 4000 was listened to ' + address.ip(), done => { - const port = 4000; - detectPort(port, (_, realPort) => { - assert(realPort === 4001); - done(); - }); - }); - - it('work with listening next port 8081 because 8080 was listened to 0.0.0.0:8080', done => { - const port = 8080; - detectPort(port, (_, realPort) => { - assert(realPort === 8081); - done(); - }); - }); - - it('work with listening random port when try port hit maxPort', done => { - const port = 7000; - detectPort(port, (_, realPort) => { - assert(realPort < 7000 || realPort > 7009); - done(); - }); - }); - - it('work with sending object with hostname', done => { - const port = 7000; - const hostname = '127.0.0.1'; - detectPort({ - port, - hostname, - callback: (_, realPort) => { - assert(realPort >= 7000 && realPort < 65535); - done(); - }, - }); - }); - - it('promise with sending object with hostname', done => { - const port = 7000; - const hostname = '127.0.0.1'; - detectPort({ - port, - hostname, - }).then(realPort => { - assert(realPort >= 7000 && realPort < 65535); - done(); - }); - }); - - it('callback with string arg', done => { - const _port = '8080'; - detectPort(_port, (_, port) => { - assert(port >= 8080 && port < 65535); - done(); - }); - }); - - it('callback with wrong arguments', done => { - detectPort('oooo', (_, port) => { - assert(port > 0); - done(); - }); - }); - - it('generator usage', async () => { - const _port = 8080; - const port = await detectPort(_port); - assert(port >= _port && port < 65535); - }); - - it('promise usage', done => { - const _port = 8080; - detectPort(_port) - .then(port => { - assert(port >= _port && port < 65535); - done(); - }) - .catch(done); - }); - - it('promise with wrong arguments', done => { - detectPort() - .then(port => { - assert(port > 0); - done(); - }) - .catch(done); - }); - - it('generator with wrong arguments and return random port', async () => { - const port = await detectPort('oooo'); - assert(port > 0); - assert(typeof port === 'number'); - }); - }); -}); diff --git a/test/detect-port.test.ts b/test/detect-port.test.ts new file mode 100644 index 0000000..1cd2a5a --- /dev/null +++ b/test/detect-port.test.ts @@ -0,0 +1,189 @@ +import dns from 'node:dns'; +import net from 'node:net'; +import { strict as assert } from 'node:assert'; +import { ip } from 'address'; +import mm from 'mm'; + +import detectPort from '../src/detect-port.js'; + +describe('test/detect-port.test.ts', () => { + afterEach(mm.restore); + + describe('detect port test', () => { + const servers: net.Server[] = []; + before(done => { + let count = 0; + const cb = (err?: Error) => { + if (err) { + done(err); + } + count += 1; + if (count === 13) { + done(); + } + }; + const server = new net.Server(); + server.listen(23000, 'localhost', cb); + server.on('error', err => { + console.error('listen localhost error:', err); + }); + servers.push(server); + + const server2 = new net.Server(); + server2.listen(24000, ip(), cb); + servers.push(server2); + + const server3 = new net.Server(); + server3.listen(28080, '0.0.0.0', cb); + servers.push(server3); + + for (let port = 27000; port < 27010; port++) { + const server = new net.Server(); + if (port % 3 === 0) { + server.listen(port, cb); + } else if (port % 3 === 1) { + server.listen(port, 'localhost', cb); + } else { + server.listen(port, ip(), cb); + } + servers.push(server); + } + }); + + after(() => { + servers.forEach(server => server.close()); + }); + + it('get random port with callback', done => { + detectPort((_, port) => { + assert(port); + assert(port >= 1024 && port < 65535); + done(); + }); + }); + + it('get random port with promise', async () => { + const port = await detectPort(); + + assert(port >= 1024 && port < 65535); + }); + + it('with occupied port, like "listen EACCES: permission denied"', async () => { + const port = 80; + const realPort = await detectPort(port); + assert(realPort >= port && realPort < 65535); + }); + + it('work with listening next port 23001 because 23000 was listened to localhost', async () => { + const port = 23000; + const realPort = await detectPort(port); + assert(realPort); + assert.equal(realPort, 23001); + }); + + it('should listen next port 24001 when localhost is not binding', async () => { + mm(dns, 'lookup', (...args: any[]) => { + mm.restore(); + const address = args[0] as string; + if (address !== 'localhost') { + return dns.lookup(args[0], args[1], args[2]); + } + process.nextTick(() => { + const err = new Error(`getaddrinfo ENOTFOUND ${address}`); + (err as any).code = 'ENOTFOUND'; + const callback = args[-1]; + callback(err); + }); + }); + + const port = 24000; + const realPort = await detectPort(port); + assert.equal(realPort, 24001); + }); + + it('work with listening next port 24001 because 24000 was listened', async () => { + const port = 24000; + const realPort = await detectPort(port); + assert.equal(realPort, 24001); + }); + + it('work with listening next port 28081 because 28080 was listened to 0.0.0.0:28080', async () => { + const port = 28080; + const realPort = await detectPort(port); + + assert.equal(realPort, 28081); + }); + + it('work with listening random port when try port hit maxPort', async () => { + const port = 27000; + const realPort = await detectPort(port); + assert(realPort < 27000 || realPort > 27009); + }); + + it('work with sending object with hostname', done => { + const port = 27000; + const hostname = '127.0.0.1'; + detectPort({ + port, + hostname, + callback: (_, realPort) => { + assert(realPort); + assert(realPort >= 27000 && realPort < 65535); + done(); + }, + }); + }); + + it('promise with sending object with hostname', async () => { + const port = 27000; + const hostname = '127.0.0.1'; + const realPort = await detectPort({ + port, + hostname, + }); + assert(realPort >= 27000 && realPort < 65535); + }); + + it('with string arg', async () => { + const port = '28080'; + const realPort = await detectPort(port); + assert(realPort >= 28080 && realPort < 65535); + }); + + it('with wrong arguments', async () => { + const port = await detectPort('oooo'); + assert(port && port > 0); + }); + + it('async/await usage', async () => { + const port = 28080; + const realPort = await detectPort(port); + assert(realPort >= port && realPort < 65535); + }); + + it('promise usage', done => { + const _port = 28080; + detectPort(_port) + .then(port => { + assert(port >= _port && port < 65535); + done(); + }) + .catch(done); + }); + + it('promise with wrong arguments', done => { + detectPort() + .then(port => { + assert(port > 0); + done(); + }) + .catch(done); + }); + + it('generator with wrong arguments and return random port', async () => { + const port = await detectPort('oooo'); + assert(port > 0); + assert(typeof port === 'number'); + }); + }); +}); diff --git a/test/mocha.opts b/test/mocha.opts deleted file mode 100644 index de65ecc..0000000 --- a/test/mocha.opts +++ /dev/null @@ -1 +0,0 @@ ---timeout 30000 \ No newline at end of file diff --git a/test/wait-port.test.js b/test/wait-port.test.js deleted file mode 100644 index 84f407a..0000000 --- a/test/wait-port.test.js +++ /dev/null @@ -1,35 +0,0 @@ -'use strict'; - -const mm = require('mm'); -const net = require('net'); - -const { waitPort } = require('..'); - -describe('test/wait-port.test.js', () => { - describe('wait for port', () => { - const servers = []; - after(() => { - servers.forEach(server => server.close()); - }); - - afterEach(mm.restore); - - it('should be work', done => { - const port = 9090; - const server = new net.Server(); - server.listen(port, '0.0.0.0'); - servers.push(server); - setTimeout(() => { - waitPort(port).then().finally(done); - }); - }); - - it('should be work when retries exceeded', done => { - const port = 9093; - waitPort(port, { - retries: 3, - retryInterval: 100, - }).then().finally(done); - }); - }); -}); diff --git a/test/wait-port.test.ts b/test/wait-port.test.ts new file mode 100644 index 0000000..e5d6218 --- /dev/null +++ b/test/wait-port.test.ts @@ -0,0 +1,37 @@ +import { once } from 'node:events'; +import { createServer, Server } from 'node:net'; +import { strict as assert } from 'node:assert'; +import { waitPort, detectPort, WaitPortRetryError } from '../src/index.js'; + +describe('test/wait-port.test.ts', () => { + describe('wait for port', () => { + const servers: Server[] = []; + after(() => { + servers.forEach(server => server.close()); + }); + + it('should be work', async () => { + const port = await detectPort(); + const server = createServer(); + servers.push(server); + server.listen(port, '0.0.0.0'); + await once(server, 'listening'); + setTimeout(() => { + server.close(); + }, 2000); + await waitPort(port); + }); + + it('should be work when retries exceeded', async () => { + try { + const port = 9093; + await waitPort(port, { retries: 3, retryInterval: 100 }); + } catch (err: unknown) { + assert(err instanceof WaitPortRetryError); + assert.equal(err.message, 'retries exceeded'); + assert.equal(err.retries, 3); + assert.equal(err.count, 4); + } + }); + }); +}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..6c0f511 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "@eggjs/tsconfig", + "compilerOptions": { + "strict": true, + "noImplicitAny": true, + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext" + } + }