diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..c66ad55
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,154 @@
+#/
+# @license Apache-2.0
+#
+# Copyright (c) 2017 The Stdlib Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#/
+
+# EditorConfig configuration file (see ).
+
+# Indicate that this file is a root-level configuration file:
+root = true
+
+# Set properties for all files:
+[*]
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+# Set properties for JavaScript files:
+[*.js]
+indent_style = tab
+
+# Set properties for TypeScript files:
+[*.ts]
+indent_style = tab
+
+# Set properties for Python files:
+[*.py]
+indent_style = space
+indent_size = 4
+
+# Set properties for Julia files:
+[*.jl]
+indent_style = tab
+
+# Set properties for R files:
+[*.R]
+indent_style = tab
+
+# Set properties for C files:
+[*.c]
+indent_style = tab
+
+# Set properties for C header files:
+[*.h]
+indent_style = tab
+
+# Set properties for C++ files:
+[*.cpp]
+indent_style = tab
+
+# Set properties for C++ header files:
+[*.hpp]
+indent_style = tab
+
+# Set properties for Fortran files:
+[*.f]
+indent_style = space
+indent_size = 2
+insert_final_newline = false
+
+# Set properties for shell files:
+[*.sh]
+indent_style = tab
+
+# Set properties for AWK files:
+[*.awk]
+indent_style = tab
+
+# Set properties for HTML files:
+[*.html]
+indent_style = tab
+tab_width = 2
+
+# Set properties for CSS files:
+[*.css]
+indent_style = tab
+
+# Set properties for Makefiles:
+[Makefile]
+indent_style = tab
+
+[*.mk]
+indent_style = tab
+
+# Set properties for Markdown files:
+[*.md]
+indent_style = space
+indent_size = 4
+trim_trailing_whitespace = false
+
+# Set properties for `usage.txt` files:
+[usage.txt]
+indent_style = space
+indent_size = 2
+
+# Set properties for `repl.txt` files:
+[repl.txt]
+indent_style = space
+indent_size = 4
+
+# Set properties for `package.json` files:
+[package.json]
+indent_style = space
+indent_size = 2
+
+# Set properties for `datapackage.json` files:
+[datapackage.json]
+indent_style = space
+indent_size = 2
+
+# Set properties for `tslint.json` files:
+[tslint.json]
+indent_style = space
+indent_size = 2
+
+# Set properties for `tsconfig.json` files:
+[tsconfig.json]
+indent_style = space
+indent_size = 2
+
+# Set properties for LaTeX files:
+[*.tex]
+indent_style = tab
+
+# Set properties for LaTeX Bibliography files:
+[*.bib]
+indent_style = tab
+
+# Set properties for YAML files:
+[*.yml]
+indent_style = space
+indent_size = 2
+
+# Set properties for GYP files:
+[binding.gyp]
+indent_style = space
+indent_size = 2
+
+[*.gypi]
+indent_style = space
+indent_size = 2
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..7212d81
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,33 @@
+#/
+# @license Apache-2.0
+#
+# Copyright (c) 2017 The Stdlib Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#/
+
+# Configuration file which assigns attributes to pathnames.
+#
+# [1]: https://git-scm.com/docs/gitattributes
+
+# Automatically normalize the line endings of any committed text files:
+* text=auto
+
+# Override what is considered "vendored" by GitHub's linguist:
+/deps/** linguist-vendored=false
+/lib/node_modules/** linguist-vendored=false linguist-generated=false
+test/fixtures/** linguist-vendored=false
+tools/** linguist-vendored=false
+
+# Override what is considered "documentation" by GitHub's linguist:
+examples/** linguist-documentation=false
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000..e6a7232
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,7 @@
+
+
+We are excited about your pull request, but unfortunately we are not accepting pull requests against this repository, as all development happens on the [main project repository](https://github.com/stdlib-js/stdlib). We kindly request that you submit this pull request against the [respective directory](https://github.com/stdlib-js/stdlib/tree/develop/lib/node_modules/%40stdlib/net/disposable-http-server) of the main repository where we’ll review and provide feedback.
+
+If this is your first stdlib contribution, be sure to read the [contributing guide](https://github.com/stdlib-js/stdlib/blob/develop/CONTRIBUTING.md) which provides guidelines and instructions for submitting contributions. You may also consult the [development guide](https://github.com/stdlib-js/stdlib/blob/develop/docs/development.md) for help on developing stdlib.
+
+We look forward to receiving your contribution! :smiley:
\ No newline at end of file
diff --git a/.github/workflows/close_pull_requests.yml b/.github/workflows/close_pull_requests.yml
new file mode 100644
index 0000000..f17fb67
--- /dev/null
+++ b/.github/workflows/close_pull_requests.yml
@@ -0,0 +1,23 @@
+name: Close Pull Requests
+
+on:
+ pull_request_target:
+ types: [opened]
+
+jobs:
+ run:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: superbrothers/close-pull-request@v3
+ with:
+ comment: |
+ Thank you for submitting a pull request. :raised_hands:
+
+ We greatly appreciate your willingness to submit a contribution. However, we are not accepting pull requests against this repository, as all development happens on the [main project repository](https://github.com/stdlib-js/stdlib).
+
+ We kindly request that you submit this pull request against the [respective directory](https://github.com/stdlib-js/stdlib/tree/develop/lib/node_modules/%40stdlib/net/disposable-http-server) of the main repository where we’ll review and provide feedback. If this is your first stdlib contribution, be sure to read the [contributing guide](https://github.com/stdlib-js/stdlib/blob/develop/CONTRIBUTING.md) which provides guidelines and instructions for submitting contributions.
+
+ Thank you again, and we look forward to receiving your contribution! :smiley:
+
+ Best,
+ The stdlib team
\ No newline at end of file
diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml
new file mode 100644
index 0000000..7668c70
--- /dev/null
+++ b/.github/workflows/examples.yml
@@ -0,0 +1,19 @@
+name: examples
+
+on:
+ workflow_dispatch:
+
+jobs:
+ examples:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - uses: actions/setup-node@v1
+ with:
+ node-version: 14
+ - name: Install production and development dependencies
+ run: |
+ npm install
+ - name: Run examples
+ run: |
+ npm run examples
\ No newline at end of file
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
new file mode 100644
index 0000000..164619b
--- /dev/null
+++ b/.github/workflows/publish.yml
@@ -0,0 +1,34 @@
+name: Publish Package
+
+on: push
+
+jobs:
+ publish:
+ runs-on: ubuntu-latest
+ env:
+ SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
+ steps:
+ - uses: actions/checkout@v2
+ - uses: actions/setup-node@v1
+ with:
+ node-version: 14
+ - name: Increment version
+ run: |
+ git config --local user.email "noreply@stdlib.io"
+ git config --local user.name "stdlib-bot"
+ npm version patch
+ - name: Publish package to npm
+ uses: JS-DevTools/npm-publish@v1
+ with:
+ token: ${{ secrets.NPM_TOKEN }}
+ access: public
+ - name: Push changes
+ run: |
+ git push origin main
+ git push --tags
+ - uses: act10ns/slack@v1
+ with:
+ status: ${{ job.status }}
+ steps: ${{ toJson(steps) }}
+ channel: '#npm-ci'
+ if: failure()
\ No newline at end of file
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000..72b66f4
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,29 @@
+name: build
+
+on:
+ workflow_dispatch:
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ env:
+ SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
+ steps:
+ - uses: actions/checkout@v2
+ - uses: actions/setup-node@v1
+ with:
+ node-version: 14
+ - name: Install production and development dependencies
+ id: install
+ run: |
+ npm install
+ - name: Run tests
+ id: tests
+ run: |
+ npm test
+ - uses: act10ns/slack@v1
+ with:
+ status: ${{ job.status }}
+ steps: ${{ toJson(steps) }}
+ channel: '#npm-ci'
+ if: failure()
\ No newline at end of file
diff --git a/.github/workflows/test_coverage.yml b/.github/workflows/test_coverage.yml
new file mode 100644
index 0000000..debbfd5
--- /dev/null
+++ b/.github/workflows/test_coverage.yml
@@ -0,0 +1,24 @@
+name: coverage
+
+on:
+ workflow_dispatch:
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - uses: actions/setup-node@v1
+ with:
+ node-version: 14
+ - name: Install production and development dependencies
+ run: |
+ npm install
+ - name: Calculate test coverage
+ run: |
+ npm run test-cov
+ - name: Upload coverage to Codecov
+ uses: codecov/codecov-action@v1
+ with:
+ directory: reports/coverage
+ flags: unittests
\ No newline at end of file
diff --git a/.github/workflows/test_install.yml b/.github/workflows/test_install.yml
new file mode 100644
index 0000000..039afb6
--- /dev/null
+++ b/.github/workflows/test_install.yml
@@ -0,0 +1,27 @@
+name: Test Installing Dependencies
+
+on:
+ workflow_run:
+ workflows: ["Publish Package"]
+ types: [completed]
+
+jobs:
+ on-success:
+ runs-on: ubuntu-latest
+ env:
+ SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
+ if: ${{ github.event.workflow_run.conclusion == 'success' }}
+ steps:
+ - uses: actions/checkout@v1
+ - uses: actions/setup-node@v1
+ with:
+ node-version: 14
+ - name: Install production dependencies via npm
+ run: |
+ npm install --only=prod
+ - uses: act10ns/slack@v1
+ with:
+ status: ${{ job.status }}
+ steps: ${{ toJson(steps) }}
+ channel: '#npm-ci'
+ if: failure()
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1475963
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,181 @@
+#/
+# @license Apache-2.0
+#
+# Copyright (c) 2017 The Stdlib Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#/
+
+# Files #
+#########
+.postinstall.json
+
+# Directories #
+###############
+build/
+downloads/
+reports/
+tmp/
+
+# Compiled source #
+###################
+*.com
+*.class
+*.dll
+*.o
+*.so
+*.slo
+*.lo
+*.obj
+*.dylib
+*.lai
+*.la
+*.a
+*.lib
+*.ko
+*.elf
+*.node
+
+# Precompiled headers #
+#######################
+*.gch
+*.pch
+
+# Executables #
+###############
+*.exe
+*.out
+*.app
+
+# Packages #
+############
+# It is better to unpack these files and commit the raw source
+# git has its own built in compression methods
+*.7z
+*.dmg
+*.gz
+*.iso
+*.jar
+*.rar
+*.tar
+*.zip
+
+# Make an exception for compressed distributable files:
+!dist/*.gz
+
+# Logs and databases #
+######################
+*.log
+*.sql
+*.sqlite
+
+# OS generated files #
+######################
+.DS_Store
+.DS_Store?
+._*
+.Spotlight-V100
+.Trashes
+Icon?
+ehthumbs.db
+Thumbs.db
+Desktop.ini
+
+# Temporary files #
+###################
+*~
+
+# Node.js #
+###########
+/node_modules/
+lib/node_modules/**/node_modules/
+docs/**/node_modules/
+pids
+*.pid
+*.seed
+
+# Typescript #
+##############
+*.tsbuildinfo
+lib/node_modules/**/tsconfig.json
+lib/node_modules/**/tslint.json
+
+# Matlab #
+##########
+*.asv
+*.mex*
+
+# Fortran #
+###########
+*.mod
+
+# R #
+#####
+.Rhistory
+.Rapp.history
+.Rproj.user/
+
+# Python #
+##########
+__pycache__/
+*.py[cod]
+*$py.class
+*.egg-info/
+
+# TeX #
+#######
+*.aux
+*.lof
+*.log
+*.lot
+*.fls
+*.out
+*.toc
+*.dvi
+*-converted-to.*
+*.bbl
+*.bcf
+*.blg
+*-blx.aux
+*-blx.bib
+*.brf
+*.run.xml
+*.fdb_latexmk
+*.synctex
+*.synctex.gz
+*.synctex.gz(busy)
+*.pdfsync
+*.alg
+*.loa
+acs-*.bib
+*.thm
+*.nav
+*.snm
+*.vrb
+*.acn
+*.acr
+*.glg
+*.glo
+*.gls
+*-concordance.tex
+*.tikz
+*-tikzDictionary
+*.idx
+*.ilg
+*.ind
+*.ist
+
+# Visual Studio #
+#################
+.vscode/
+jsconfig.json
diff --git a/.npmignore b/.npmignore
new file mode 100644
index 0000000..401aa76
--- /dev/null
+++ b/.npmignore
@@ -0,0 +1,225 @@
+#/
+# @license Apache-2.0
+#
+# Copyright (c) 2017 The Stdlib Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#/
+
+# Files #
+#########
+CODE_OF_CONDUCT.md
+CONTRIBUTING.md
+CONTRIBUTORS
+TODO.md
+ROADMAP.md
+.postinstall.json
+
+# Directories #
+###############
+.circleci/
+.github/
+**/benchmark/
+**/build/
+**/examples/
+reports/
+support/
+**/tmp/
+workshops/
+
+# Ignore test directories, except for testing dependency installation:
+**/test/
+!/deps/test/
+
+# Only top-level directories:
+/etc/
+/docs/
+
+# Compiled source #
+###################
+*.com
+*.class
+*.dll
+*.o
+*.so
+*.slo
+*.lo
+*.obj
+*.dylib
+*.lai
+*.la
+*.a
+*.lib
+*.ko
+*.elf
+*.node
+
+# Precompiled headers #
+#######################
+*.gch
+*.pch
+
+# Executables #
+###############
+*.exe
+*.out
+*.app
+
+# Packages #
+############
+*.7z
+*.dmg
+*.gz
+*.iso
+*.jar
+*.rar
+*.tar
+*.zip
+
+# Make an exception for compressed distributable files:
+!dist/*.gz
+
+# Logs and databases #
+######################
+*.log
+*.sql
+*.sqlite
+
+# OS generated files #
+######################
+.DS_Store
+.DS_Store?
+._*
+.Spotlight-V100
+.Trashes
+Icon?
+ehthumbs.db
+Thumbs.db
+Desktop.ini
+
+# Temporary files #
+###################
+*~
+
+# Node.js #
+###########
+.npmignore
+
+# Only top-level node_modules:
+/node_modules/
+
+# TypeScript #
+##############
+tsconfig.json
+tslint.json
+*.tsbuildinfo
+
+# Matlab #
+##########
+*.asv
+*.mex*
+
+# Fortran #
+###########
+*.mod
+
+# R #
+#####
+.Rhistory
+.Rapp.history
+.Rproj.user/
+
+# Python #
+##########
+__pycache__/
+*.py[cod]
+*$py.class
+*.egg-info/
+.ipynb_checkpoints
+setup.cfg
+setup.py
+
+# TeX #
+#######
+*.aux
+*.lof
+*.log
+*.lot
+*.fls
+*.out
+*.toc
+*.dvi
+*-converted-to.*
+*.bbl
+*.bcf
+*.blg
+*-blx.aux
+*-blx.bib
+*.brf
+*.run.xml
+*.fdb_latexmk
+*.synctex
+*.synctex.gz
+*.synctex.gz(busy)
+*.pdfsync
+*.alg
+*.loa
+acs-*.bib
+*.thm
+*.nav
+*.snm
+*.vrb
+*.acn
+*.acr
+*.glg
+*.glo
+*.gls
+*-concordance.tex
+*.tikz
+*-tikzDictionary
+*.idx
+*.ilg
+*.ind
+*.ist
+
+# Git #
+#######
+.git*
+.mailmap
+
+# Visual Studio #
+#################
+.vscode/
+jsconfig.json
+
+# Utilities #
+#############
+.jshintrc
+.jshintignore
+.eslintrc*
+.eslintignore
+
+.pylintrc
+.pycodestyle
+.pydocstyle
+
+.travis.yml
+circle.yml
+appveyor.yml
+azure-pipelines.yml
+
+.editorconfig
+.codeclimate.yml
+.codecov.yml
+
+.rtlintrc
diff --git a/.npmrc b/.npmrc
new file mode 100644
index 0000000..36f5bef
--- /dev/null
+++ b/.npmrc
@@ -0,0 +1,28 @@
+#/
+# @license Apache-2.0
+#
+# Copyright (c) 2017 The Stdlib Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#/
+
+# Configuration for [npm][1].
+#
+# [1]: https://docs.npmjs.com/files/npmrc
+
+# Disable the creation of a lock file:
+package-lock = false
+shrinkwrap = false
+
+# Disable automatically "saving" dependencies on install:
+save = false
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..35b70c9
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,3 @@
+# Code of Conduct
+
+stdlib expects community participants to adhere to the project Code of Conduct. The [full text](https://github.com/stdlib-js/stdlib/blob/develop/CODE_OF_CONDUCT.md) is available in the main project repository.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..5f59443
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,3 @@
+# Contribution Guidelines
+
+Woot woot! If you are new to stdlib, welcome! And thanks for your interest! Guidelines for how to contribute to the project are [available](https://github.com/stdlib-js/stdlib/blob/develop/CONTRIBUTING.md) in the main project repository.
diff --git a/CONTRIBUTORS b/CONTRIBUTORS
new file mode 100644
index 0000000..da469e5
--- /dev/null
+++ b/CONTRIBUTORS
@@ -0,0 +1,24 @@
+# This file is generated by tools/scripts/update_contributors.
+#
+# Contributors listed in alphabetical order.
+
+Athan Reines
+Brendan Graetz
+Bruno Fenzl
+Christopher Dambamuromo
+Dominik Moritz
+Frank Kovacs
+James
+Jithin KS
+Joey Reed
+Joris Labie
+Justin Dennison
+Marcus
+Matt Cochrane
+Milan Raj
+Ognjen Jevremović
+Philipp Burckhardt
+Ricky Reusser
+Ryan Seal
+Shraddheya Shendre
+rei2hu
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..fcc9934
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,481 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+
+
+Boost Software License - Version 1.0 - August 17th, 2003
+
+Permission is hereby granted, free of charge, to any person or organization
+obtaining a copy of the software and accompanying documentation covered by this
+license (the "Software") to use, reproduce, display, distribute, execute, and
+transmit the Software, and to prepare derivative works of the Software, and to
+permit third-parties to whom the Software is furnished to do so, all subject to
+the following:
+
+The copyright notices in the Software and this entire statement, including the
+above license grant, this restriction and the following disclaimer, must be
+included in all copies of the Software, in whole or in part, and all derivative
+works of the Software, unless such copies or derivative works are solely in the
+form of machine-executable object code generated by a source language processor.
+
+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, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES
+OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
+OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
+
+
+DEPENDENCIES
+
+The library links against the following external libraries, which have their own
+licenses:
+
+* OpenBLAS
+
+Copyright (c) 2011-2014, The OpenBLAS Project
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+
+ 3. Neither the name of the OpenBLAS project nor the names of
+ its contributors may be used to endorse or promote products
+ derived from this software without specific prior written
+ permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+* Electron
+
+Copyright (c) 2013-2017 GitHub Inc.
+
+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.
+
+
+* Boost
+
+Boost Software License - Version 1.0 - August 17th, 2003
+
+Permission is hereby granted, free of charge, to any person or organization
+obtaining a copy of the software and accompanying documentation covered by
+this license (the "Software") to use, reproduce, display, distribute,
+execute, and transmit the Software, and to prepare derivative works of the
+Software, and to permit third-parties to whom the Software is furnished to
+do so, all subject to the following:
+
+The copyright notices in the Software and this entire statement, including
+the above license grant, this restriction and the following disclaimer,
+must be included in all copies of the Software, in whole or in part, and
+all derivative works of the Software, unless such copies or derivative
+works are solely in the form of machine-executable object code generated by
+a source language processor.
+
+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, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+
+
+* Cephes
+
+Copyright (c) 1984-2000 Stephen L. Moshier
+
+Some software in this archive may be from the book _Methods and Programs for
+Mathematical Functions_ (Prentice-Hall or Simon & Schuster International, 1989)
+or from the Cephes Mathematical Library, a commercial product. In either event,
+it is copyrighted by the author. What you see here may be used freely but it
+comes with no support or guarantee.
+
+Stephen L. Moshier
+moshier@na-net.ornl.gov
+
+
+
+ATTRIBUTION
+
+The library contains implementations from the following external libraries,
+which have their own licenses:
+
+* FreeBSD
+
+Copyright (C) 1993-2004 by Sun Microsystems, Inc. All rights reserved.
+
+Developed at SunPro, a Sun Microsystems, Inc. business.
+Permission to use, copy, modify, and distribute this
+software is freely granted, provided that this notice
+is preserved.
+
+
+* FDLIBM
+
+Copyright (C) 2004 by Sun Microsystems, Inc. All rights reserved.
+
+Developed at SunPro, a Sun Microsystems, Inc. business.
+Permission to use, copy, modify, and distribute this
+software is freely granted, provided that this notice
+is preserved.
+
+
+* Go
+
+Copyright (c) 2009 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+* SLATEC Common Mathematical Library
+
+Public domain.
+
+
+* ESLint
+
+Copyright JS Foundation and other contributors, https://js.foundation
+
+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.
+
+
+* StatsFuns.jl
+
+Copyright (c) 2015: Dahua Lin.
+
+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.
+
+
+* SpecialFunctions.jl
+
+The MIT License (MIT)
+
+Copyright (c) 2017 Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and others:
+
+https://github.com/JuliaMath/SpecialFunctions.jl/graphs/contributors
+
+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.
+
+
+* MT19937
+
+Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura,
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ 3. The names of its contributors may not be used to endorse or promote
+ products derived from this software without specific prior written
+ permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..1c7d53e
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,534 @@
+#/
+# @license Apache-2.0
+#
+# Copyright (c) 2021 The Stdlib Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#/
+
+# USER VARIABLES #
+
+ifndef VERBOSE
+ QUIET := @
+else
+ QUIET :=
+endif
+
+# Indicate whether to "fast" fail when linting, running tests, etc:
+ifndef FAST_FAIL
+ FAIL_FAST := true
+else
+ifeq ($(FAST_FAIL), 0)
+ FAIL_FAST := false
+else
+ FAIL_FAST := true
+endif
+endif
+
+# Define the `NODE_PATH` environment variable:
+NODE_PATH ?=
+
+# Define the `NODE_ENV` environment variable:
+NODE_ENV ?=
+
+
+# INTERNAL VARIABLES #
+
+# Instruct make to warn us when we use an undefined variable (e.g., misspellings).
+MAKEFLAGS += --warn-undefined-variables
+
+# Define the default target:
+.DEFAULT_GOAL := all
+
+# Define the `SHELL` variable to avoid issues on systems where the variable may be inherited from the environment.
+#
+# ## Notes
+#
+# - We use `bash` so that we can use `pipefail`.
+#
+#
+# [1]: https://www.gnu.org/prep/standards/html_node/Makefile-Basics.html#Makefile-Basics
+# [2]: http://clarkgrubb.com/makefile-style-guide
+SHELL := bash
+
+# Define shell flags.
+#
+# ## Notes
+#
+# - `.SHELLFLAGS` was introduced in GNU Make 3.82 and has no effect on the version of GNU Make installed on Mac OS X, which is 3.81.
+# - The `-e` flag causes `bash` to exit immediately if a `bash` executed command fails.
+# - The `-u` flag causes `bash` to exit with an error message if a variable is accessed without being defined.
+# - The `pipefail` option specifies that, if any of the commands in a pipeline fail, the entire pipeline fails. Otherwise the return value of a pipeline is the return value of the last command.
+# - The `-c` flag is in the default value of `.SHELLFLAGS`, which must be preserved, as this is how `make` passes the script to be executed to `bash`.
+#
+.SHELLFLAGS := -eu -o pipefail -c
+
+# Remove targets if its recipe fails.
+#
+# ## Notes
+#
+# - Mentioning this target anywhere in a Makefile prevents a user from re-running make and using an incomplete or invalid target.
+# - When debugging, it may be necessary to comment this line out so the incomplete or invalid target can be inspected.
+#
+# [1]: https://www.gnu.org/software/make/manual/html_node/Special-Targets.html
+.DELETE_ON_ERROR:
+
+# Remove all the default suffixes, preferring to define all rules explicitly.
+#
+# [1]: https://www.gnu.org/software/make/manual/html_node/Suffix-Rules.html#Suffix-Rules
+# [2]: https://www.gnu.org/software/make/manual/html_node/Suffix-Rules.html#Suffix-Rules
+.SUFFIXES:
+
+# Determine the OS ([1][1], [2][2]).
+#
+# [1]: https://en.wikipedia.org/wiki/Uname#Examples
+# [2]: http://stackoverflow.com/a/27776822/2225624
+OS ?= $(shell uname)
+ifneq (, $(findstring MINGW,$(OS)))
+ OS := WINNT
+else
+ifneq (, $(findstring MSYS,$(OS)))
+ OS := WINNT
+else
+ifneq (, $(findstring CYGWIN,$(OS)))
+ OS := WINNT
+else
+ifneq (, $(findstring Windows_NT,$(OS)))
+ OS := WINNT
+endif
+endif
+endif
+endif
+
+# Determine the filename:
+this_file := $(lastword $(MAKEFILE_LIST))
+
+# Determine the absolute path of the Makefile (see http://blog.jgc.org/2007/01/what-makefile-am-i-in.html):
+this_dir := $(dir $(CURDIR)/$(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST)))
+
+# Remove the trailing slash:
+this_dir := $(patsubst %/,%,$(this_dir))
+
+# Determine root directory:
+ROOT_DIR = $(this_dir)
+
+# Define the root build directory:
+BUILD_DIR ?= $(ROOT_DIR)/build
+
+# Define the root directory for storing distributable files:
+DIST_DIR ?= $(ROOT_DIR)/dist
+
+# Define the root directory for storing temporary files:
+TMP_DIR ?= $(ROOT_DIR)/tmp
+
+# Define the directories for writing reports, including code coverage:
+REPORTS_DIR ?= $(ROOT_DIR)/reports
+COVERAGE_DIR ?= $(REPORTS_DIR)/coverage
+
+# Define the top-level directory containing node module dependencies:
+NODE_MODULES ?= $(ROOT_DIR)/node_modules
+
+# Define the top-level directory containing node module executables:
+BIN_DIR ?= $(NODE_MODULES)/.bin
+
+# Define the path to the root `package.json`:
+ROOT_PACKAGE_JSON ?= $(ROOT_DIR)/package.json
+
+# Define the folder name convention for source files requiring compilation:
+SRC_FOLDER ?= src
+
+# Define the folder name convention for documentation files:
+DOCUMENTATION_FOLDER ?= docs
+
+# Define the folder name convention for configuration files:
+CONFIG_FOLDER ?= etc
+
+# Define the folder name convention for benchmark files:
+BENCHMARKS_FOLDER ?= benchmark
+
+# Define the folder name convention for benchmark fixtures:
+BENCHMARKS_FIXTURES_FOLDER ?= $(BENCHMARKS_FOLDER)/fixtures
+
+# Define the folder name convention for examples files:
+EXAMPLES_FOLDER ?= examples
+
+# Define the folder name convention for examples fixtures:
+EXAMPLES_FIXTURES_FOLDER ?= $(EXAMPLES_FOLDER)/fixtures
+
+# Define the folder name convention for test files:
+TESTS_FOLDER ?= test
+
+# Define the folder name convention for test fixtures:
+TESTS_FIXTURES_FOLDER ?= $(TESTS_FOLDER)/fixtures
+
+# Define a filepath pattern for benchmark files:
+BENCHMARKS_FILTER ?= .*/.*
+
+# Define a filepath pattern for example files:
+EXAMPLES_FILTER ?= .*/.*
+
+# Define a filepath pattern for test files:
+TESTS_FILTER ?= .*/.*
+
+# Define a filename pattern for benchmark files:
+BENCHMARKS_PATTERN ?= benchmark*.js
+
+# Define a filename pattern for example files:
+EXAMPLES_PATTERN ?= *.js
+
+# Define a filename pattern for test files:
+TESTS_PATTERN ?= test*.js
+
+# Define Node environments:
+ifdef NODE_ENV
+ NODE_ENV_BENCHMARK := $(NODE_ENV)
+ NODE_ENV_EXAMPLES := $(NODE_ENV)
+ NODE_ENV_TEST := $(NODE_ENV)
+else
+ NODE_ENV ?=
+ NODE_ENV_BENCHMARK ?= benchmark
+ NODE_ENV_EXAMPLES ?= examples
+ NODE_ENV_TEST ?= test
+endif
+
+# Define whether delete operations should be safe (i.e., deleted items are sent to trash, rather than permanently deleted):
+SAFE_DELETE ?= false
+
+# Define the delete command:
+ifeq ($(SAFE_DELETE), true)
+ # FIXME: -rm -rf
+ DELETE := -rm
+ DELETE_FLAGS := -rf
+else
+ DELETE ?= -rm
+ DELETE_FLAGS ?= -rf
+endif
+
+# Determine the `open` command:
+ifeq ($(OS), Darwin)
+ OPEN ?= open
+else
+ OPEN ?= xdg-open
+endif
+# TODO: add Windows command
+
+# Define the command for `node`:
+NODE ?= node
+
+# Define the command for `npm`:
+NPM ?= npm
+
+# Define the path to a JavaScript test runner.
+#
+# ## Notes
+#
+# - We reference the `bin` file directly in order to support using `istanbul` for code coverage on Windows (https://github.com/gotwarlost/istanbul#usage-on-windows)
+JAVASCRIPT_TEST ?= $(NODE_MODULES)/tape/bin/tape
+
+# Define any command-line options to use when invoking the test runner:
+JAVASCRIPT_TEST_FLAGS ?=
+
+# Define the path to the executable for parsing TAP output:
+TAP_REPORTER ?= $(BIN_DIR)/tap-spec
+
+# Define the path to the Istanbul executable:
+ISTANBUL ?= $(BIN_DIR)/istanbul
+
+# Define which files and directories to exclude from coverage instrumentation:
+ISTANBUL_EXCLUDES_FLAGS ?= \
+ --no-default-excludes \
+ -x 'node_modules/**' \
+ -x 'reports/**' \
+ -x 'tmp/**' \
+ -x 'deps/**' \
+ -x 'dist/**' \
+ -x "**/$(SRC_FOLDER)/**" \
+ -x "**/$(TESTS_FOLDER)/**" \
+ -x "**/$(EXAMPLES_FOLDER)/**" \
+ -x "**/$(BENCHMARKS_FOLDER)/**" \
+ -x "**/$(CONFIG_FOLDER)/**" \
+ -x "**/$(DOCUMENTATION_FOLDER)/**"
+
+# Define the command to generate test coverage:
+ISTANBUL_COVER ?= $(ISTANBUL) cover
+
+# Define the type of report Istanbul should produce:
+ISTANBUL_COVER_REPORT_FORMAT ?= lcov
+
+# Define the command-line options to be used when generating code coverage:
+ISTANBUL_COVER_FLAGS ?= \
+ $(ISTANBUL_EXCLUDES_FLAGS) \
+ --dir $(COVERAGE_DIR) \
+ --report $(ISTANBUL_COVER_REPORT_FORMAT)
+
+# On Mac OSX, in order to use `|` and other regular expression operators, we need to use enhanced regular expression syntax (-E); see https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man7/re_format.7.html#//apple_ref/doc/man/7/re_format.
+ifeq ($(OS), Darwin)
+ find_kernel_prefix := -E
+else
+ find_kernel_prefix :=
+endif
+
+# Common exclude flags that most recipes for finding package files should use (Note: order does matter to some degree):
+FIND_COMMON_EXCLUDE_FLAGS ?= \
+ -not -path "$(ROOT_DIR)/.*" \
+ -not -path "$(NODE_MODULES)/*" \
+ -not -path "$(BUILD_DIR)/*" \
+ -not -path "$(REPORTS_DIR)/*" \
+
+# Define exclusion flags to use when searching for benchmark files:
+FIND_BENCHMARKS_EXCLUDE_FLAGS ?= \
+ $(FIND_COMMON_EXCLUDE_FLAGS) \
+ -not -path "$(ROOT_DIR)/**/$(BENCHMARKS_FIXTURES_FOLDER)/*"
+
+# Define flags for finding benchmark files:
+FIND_BENCHMARKS_FLAGS ?= \
+ -type f \
+ -name "$(BENCHMARKS_PATTERN)" \
+ -path "$(ROOT_DIR)/**/$(BENCHMARKS_FOLDER)/**" \
+ -regex "$(BENCHMARKS_FILTER)" \
+ $(FIND_BENCHMARKS_EXCLUDE_FLAGS)
+
+ifneq ($(OS), Darwin)
+ FIND_BENCHMARKS_FLAGS := -regextype posix-extended $(FIND_BENCHMARKS_FLAGS)
+endif
+
+# Define a command to list benchmark files:
+FIND_BENCHMARKS_CMD ?= find $(find_kernel_prefix) $(ROOT_DIR) $(FIND_BENCHMARKS_FLAGS)
+
+# Define exclusion flags to use when searching for examples files:
+FIND_EXAMPLES_EXCLUDE_FLAGS ?= \
+ $(FIND_COMMON_EXCLUDE_FLAGS) \
+ -not -path "$(ROOT_DIR)/**/$(EXAMPLES_FIXTURES_FOLDER)/*"
+
+# Define flags for finding examples files:
+FIND_EXAMPLES_FLAGS ?= \
+ -type f \
+ -name "$(EXAMPLES_PATTERN)" \
+ -path "$(ROOT_DIR)/**/$(EXAMPLES_FOLDER)/**" \
+ -regex "$(EXAMPLES_FILTER)" \
+ $(FIND_EXAMPLES_EXCLUDE_FLAGS)
+
+ifneq ($(OS), Darwin)
+ FIND_EXAMPLES_FLAGS := -regextype posix-extended $(FIND_EXAMPLES_FLAGS)
+endif
+
+# Define a command to list example files:
+FIND_EXAMPLES_CMD ?= find $(find_kernel_prefix) $(ROOT_DIR) $(FIND_EXAMPLES_FLAGS)
+
+# Define exclusion flags to use when searching for test files:
+FIND_TESTS_EXCLUDE_FLAGS ?= \
+ $(FIND_COMMON_EXCLUDE_FLAGS) \
+ -not -path "$(ROOT_DIR)/**/$(TESTS_FIXTURES_FOLDER)/*"
+
+# Define flags for finding test files:
+FIND_TESTS_FLAGS ?= \
+ -type f \
+ -name "$(TESTS_PATTERN)" \
+ -regex "$(TESTS_FILTER)" \
+ $(FIND_TESTS_EXCLUDE_FLAGS)
+
+ifneq ($(OS), Darwin)
+ FIND_TESTS_FLAGS := -regextype posix-extended $(FIND_TESTS_FLAGS)
+endif
+
+# Define a command to list test files:
+FIND_TESTS_CMD ?= find $(find_kernel_prefix) $(ROOT_DIR) $(FIND_TESTS_FLAGS)
+
+
+# RULES #
+
+#/
+# Default target.
+#
+# @example
+# make
+#
+# @example
+# make all
+#/
+all: help
+
+.PHONY: all
+
+#/
+# Prints a `Makefile` help message.
+#
+# @example
+# make help
+#/
+help:
+ $(QUIET) echo 'Read the Makefile to see the list of available commands.'
+ $(QUIET) echo ''
+
+.PHONY: help
+
+#/
+# Prints the runtime value of a `Makefile` variable.
+#
+# ## Notes
+#
+# - The rule uses the following format:
+#
+# ```bash
+# $ make inspect.
+# ```
+#
+# @example
+# make inspect.ROOT_DIR
+#
+# @example
+# make inspect.CC
+#/
+inspect.%:
+ $(QUIET) echo '$*=$($*)'
+
+#/
+# Runs the project's install sequence.
+#
+# @example
+# make install
+#/
+install:
+ $(NPM) install
+
+.PHONY: install
+
+#/
+# Removes node module dependencies.
+#
+# @example
+# make clean-node
+#/
+clean-node:
+ $(QUIET) $(DELETE) $(DELETE_FLAGS) $(NODE_MODULES)
+
+#/
+# Runs the project's cleanup sequence.
+#
+# @example
+# make clean
+#/
+clean: clean-node clean-cov
+ $(QUIET) $(DELETE) $(DELETE_FLAGS) $(BUILD_DIR)
+ $(QUIET) $(DELETE) $(DELETE_FLAGS) $(REPORTS_DIR)
+
+.PHONY: clean
+
+#/
+# Runs JavaScript benchmarks consecutively.
+#
+# ## Notes
+#
+# - The recipe assumes that benchmark files can be run via Node.js.
+# - This rule is useful when wanting to glob for JavaScript benchmark files (e.g., run all JavaScript benchmarks for a particular package).
+#
+#
+# @param {string} [BENCHMARKS_FILTER] - file path pattern (e.g., `.*/utils/group-by/.*`)
+#
+# @example
+# make benchmark
+#
+# @example
+# make benchmark BENCHMARKS_FILTER=".*/utils/group-by/.*"
+#/
+benchmark: $(NODE_MODULES)
+ $(QUIET) $(FIND_BENCHMARKS_CMD) | grep '^[\/]\|^[a-zA-Z]:[/\]' | while read -r file; do \
+ echo ""; \
+ echo "Running benchmark: $$file"; \
+ NODE_ENV="$(NODE_ENV_BENCHMARK)" \
+ NODE_PATH="$(NODE_PATH)" \
+ $(NODE) $$file || exit 1; \
+ done
+
+.PHONY: benchmark
+
+#/
+# Runs JavaScript examples consecutively.
+#
+# ## Notes
+#
+# - This rule is useful when wanting to glob for JavaScript examples files (e.g., run all JavaScript examples for a particular package).
+# - This rule **assumes** that examples files can be run using Node.js.
+#
+#
+# @param {string} [EXAMPLES_FILTER] - file path pattern (e.g., `.*/math/base/special/abs/.*`)
+#
+# @example
+# make examples
+#
+# @example
+# make examples EXAMPLES_FILTER=".*/strided/common/.*"
+#/
+examples: $(NODE_MODULES)
+ $(QUIET) $(FIND_EXAMPLES_CMD) | grep '^[\/]\|^[a-zA-Z]:[/\]' | while read -r file; do \
+ echo ""; \
+ echo "Running example: $$file"; \
+ NODE_ENV="$(NODE_ENV_EXAMPLES)" \
+ NODE_PATH="$(NODE_PATH)" \
+ $(NODE) $$file || exit 1; \
+ done
+
+.PHONY: examples
+
+#/
+# Runs JavaScript tests consecutively.
+#
+# ## Notes
+#
+# - This rule is useful when wanting to glob for JavaScript test files (e.g., run all JavaScript tests for a particular package).
+# - This rule **assumes** that test files can be run using Node.js.
+#
+#
+# @param {string} [TEST_FILTER] - file path pattern (e.g., `.*/math/base/special/abs/.*`)
+#
+# @example
+# make test
+#
+# @example
+# make test TESTS_FILTER=".*/strided/common/.*"
+#/
+test: $(NODE_MODULES)
+ $(QUIET) $(FIND_TESTS_CMD) | grep '^[\/]\|^[a-zA-Z]:[/\]' | while read -r test; do \
+ echo ''; \
+ echo "Running test: $$test"; \
+ NODE_ENV="$(NODE_ENV_TEST)" \
+ NODE_PATH="$(NODE_PATH)" \
+ $(JAVASCRIPT_TEST) \
+ $(JAVASCRIPT_TEST_FLAGS) \
+ $$test \
+ | $(TAP_REPORTER) || exit 1; \
+ done
+
+.PHONY: test
+
+#/
+# Runs unit tests and generate a test coverage report.
+#
+# @example
+# make test-cov
+#/
+test-cov: clean-cov
+ $(QUIET) NODE_ENV="$(NODE_ENV_TEST)" \
+ NODE_PATH="$(NODE_PATH)" \
+ $(ISTANBUL_COVER) $(ISTANBUL_COVER_FLAGS) $(JAVASCRIPT_TEST) -- $$( $(FIND_TESTS_CMD) )
+
+.PHONY: test-cov
+
+#/
+# Removes a test coverage directory.
+#
+# @example
+# make clean-cov
+#/
+clean-cov:
+ $(QUIET) $(DELETE) $(DELETE_FLAGS) $(COVERAGE_DIR)
diff --git a/NOTICE b/NOTICE
new file mode 100644
index 0000000..f5374f6
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1 @@
+Copyright (c) 2016-2021 The Stdlib Authors.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..a8875af
--- /dev/null
+++ b/README.md
@@ -0,0 +1,361 @@
+
+
+# Disposable HTTP Server
+
+[![NPM version][npm-image]][npm-url] [![Build Status][test-image]][test-url] [![Coverage Status][coverage-image]][coverage-url] [![dependencies][dependencies-image]][dependencies-url]
+
+> Create a disposable HTTP server.
+
+
+
+## Installation
+
+```bash
+npm install @stdlib/net-disposable-http-server
+```
+
+
+
+
+
+## Usage
+
+
+
+```javascript
+var httpServer = require( '@stdlib/net-disposable-http-server' );
+```
+
+#### httpServer( options\[, clbk] )
+
+Creates a disposable HTTP server; i.e., the server closes immediately after serving provided content.
+
+
+
+```javascript
+var opts = {
+ 'html': '',
+ 'javascript': 'console.log( "Boop" );'
+};
+
+httpServer( opts );
+```
+
+The function accepts the following options:
+
+- **html**: `buffer` or `string` to serve as HTML content.
+- **javascript**: `buffer` or `string` to serve as JavaScript.
+- **port**: server port. Default: `0` (i.e., randomly assigned).
+- **maxport**: max server port (used when port hunting). Default: `=port`.
+- **hostname**: server hostname.
+- **address**: server address. Default: `"0.0.0.0"`.
+- **open**: `boolean` indicating whether to launch a web browser. Default: `false`.
+
+To serve HTML content, set the `html` option. Once the content is requested, the server will close.
+
+
+
+```javascript
+var opts = {
+ 'html': 'Beep
'
+};
+
+httpServer( opts );
+```
+
+To serve JavaScript, set the `javascript` option. If no HTML is provided, an HTML boilerplate is served and the JavaScript is served as `/bundle.js`. Once the content is requested, the server will close.
+
+
+
+```javascript
+var opts = {
+ 'javascript': 'console.log( "Boop" );'
+};
+
+httpServer( opts );
+```
+
+If HTML and JavaScript are provided, in order for the JavaScript to be served, the HTML content should request the file `/bundle.js`.
+
+
+
+```javascript
+var opts = {
+ 'html': '',
+ 'javascript': 'console.log( "Boop" );'
+};
+
+httpServer( opts );
+```
+
+To obtain the `server` handle, provide a callback.
+
+
+
+```javascript
+var nextTick = require( '@stdlib/utils-next-tick' );
+
+function onReady( error, server ) {
+ if ( error ) {
+ throw error;
+ }
+ nextTick( close );
+ function close() {
+ server.close();
+ }
+}
+
+var opts = {
+ 'html': html,
+ 'javascript': 'console.log( "Boop" );'
+};
+
+httpServer( opts, onReady );
+```
+
+
+
+
+
+
+
+## Notes
+
+- If neither the `html` or `javascript` option is set, the server serves an HTML boilerplate and then closes.
+
+
+
+
+
+
+
+## Examples
+
+
+
+
+
+```javascript
+var join = require( 'path' ).join;
+var readFileSync = require( '@stdlib/fs-read-file' ).sync;
+var httpServer = require( '@stdlib/net-disposable-http-server' );
+
+var html = join( __dirname, 'examples', 'fixtures', 'index.html' );
+var js = join( __dirname, 'examples', 'fixtures', 'script.js' );
+
+var opts = {
+ 'html': readFileSync( html ),
+ 'javascript': readFileSync( js ),
+ 'port': 7331,
+ 'hostname': 'localhost',
+ 'open': false
+};
+
+httpServer( opts, clbk );
+
+function clbk( error, server ) {
+ if ( error ) {
+ throw error;
+ }
+ // Give the user a few seconds to open her web browser before closing the server...
+ setTimeout( onTimeout, 5000 );
+
+ function onTimeout() {
+ server.close();
+ }
+}
+```
+
+
+
+
+
+* * *
+
+
+
+## CLI
+
+
+
+## Installation
+
+To use the module as a general utility, install the module globally
+
+```bash
+npm install -g @stdlib/net-disposable-http-server
+```
+
+
+
+
+
+### Usage
+
+```text
+Usage: temp-http-server [options] (--html path | --js path | --stdin type)
+
+Options:
+
+ -h, --help Print this message.
+ -V, --version Print the package version.
+ --html path Serve HTML.
+ --js, --javascript path Serve JavaScript.
+ --stdin type Type of content: html or javascript.
+ -p, --port port Server port. Default: 0.
+ --maxport maxport Max server port. Default: `port`.
+ --hostname hostname Server hostname.
+ --address address Server address. Default: 0.0.0.0.
+ --open Launch a browser once server is ready.
+```
+
+The application recognizes the following [environment variables][environment-variable]:
+
+- `DEBUG`: enable verbose logging.
+- `PORT`: server port.
+- `MAXPORT`: max server port.
+- `HOSTNAME`: server hostname.
+- `ADDRESS`: server address.
+
+
+
+
+
+
+
+### Notes
+
+- Command-line arguments **always** take precedence over [environment variables][environment-variable].
+- If either the `--html` or `--javascript` command-line flag is set, `stdin` is assumed to be of the other type. Accordingly, the `--stdin` flag may be omitted.
+
+
+
+
+
+
+
+### Examples
+
+To serve an HTML file,
+
+```bash
+$ DEBUG=* temp-http-server --html ./examples/fixtures/index.html
+...
+```
+
+To serve a JavaScript file (and a default HTML boilerplate),
+
+```bash
+$ DEBUG=* temp-http-server --javascript ./examples/fixtures/script.js
+...
+```
+
+In addition to file input, the application accepts [standard input][standard-streams]. To pipe HTML,
+
+```bash
+$ cat ./examples/fixtures/index.html | DEBUG=* temp-http-server --port 7331 --stdin html
+...
+```
+
+To pipe HTML and load a JavaScript file,
+
+```bash
+$ cat ./examples/fixtures/index.html | DEBUG=* temp-http-server --port 7331 --javascript ./examples/fixtures/script.js
+...
+```
+
+To pipe JavaScript (and serve a default HTML boilerplate),
+
+```bash
+$ cat ./examples/fixtures/script.js | DEBUG=* temp-http-server --address '127.0.0.1' --stdin javascript
+...
+```
+
+To pipe JavaScript and serve custom HTML content which requests a `/bundle.js` file,
+
+```bash
+$ cat ./examples/fixtures/script.js | DEBUG=* temp-http-server --html ./examples/fixtures/index.html
+...
+```
+
+
+
+
+
+
+
+
+
+
+
+
+* * *
+
+## Notice
+
+This package is part of [stdlib][stdlib], a standard library for JavaScript and Node.js, with an emphasis on numerical and scientific computing. The library provides a collection of robust, high performance libraries for mathematics, statistics, streams, utilities, and more.
+
+For more information on the project, filing bug reports and feature requests, and guidance on how to develop [stdlib][stdlib], see the main project [repository][stdlib].
+
+---
+
+## License
+
+See [LICENSE][stdlib-license].
+
+
+## Copyright
+
+Copyright © 2016-2021. The Stdlib [Authors][stdlib-authors].
+
+
+
+
+
+
+
+
+
+[npm-image]: http://img.shields.io/npm/v/@stdlib/net-disposable-http-server.svg
+[npm-url]: https://npmjs.org/package/@stdlib/net-disposable-http-server
+
+[test-image]: https://github.com/stdlib-js/net-disposable-http-server/actions/workflows/test.yml/badge.svg
+[test-url]: https://github.com/stdlib-js/net-disposable-http-server/actions/workflows/test.yml
+
+[coverage-image]: https://img.shields.io/codecov/c/github/stdlib-js/net-disposable-http-server/main.svg
+[coverage-url]: https://codecov.io/github/stdlib-js/net-disposable-http-server?branch=main
+
+[dependencies-image]: https://img.shields.io/david/stdlib-js/net-disposable-http-server
+[dependencies-url]: https://david-dm.org/stdlib-js/net-disposable-http-server/main
+
+[stdlib]: https://github.com/stdlib-js/stdlib
+
+[stdlib-authors]: https://github.com/stdlib-js/stdlib/graphs/contributors
+
+[stdlib-license]: https://raw.githubusercontent.com/stdlib-js/net-disposable-http-server/main/LICENSE
+
+[environment-variable]: https://en.wikipedia.org/wiki/Environment_variable
+
+[standard-streams]: https://en.wikipedia.org/wiki/Standard_streams
+
+
+
+
diff --git a/bin/cli b/bin/cli
new file mode 100644
index 0000000..683237a
--- /dev/null
+++ b/bin/cli
@@ -0,0 +1,241 @@
+#!/usr/bin/env node
+
+/**
+* @license Apache-2.0
+*
+* Copyright (c) 2018 The Stdlib Authors.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+'use strict';
+
+// MODULES //
+
+var fs = require( 'fs' );
+var path = require( 'path' );
+var parseArgs = require( 'minimist' );
+var notifier = require( 'update-notifier' );
+var ENV = require( '@stdlib/process/env' );
+var cwd = require( '@stdlib/process/cwd' );
+var stdin = require( '@stdlib/process/read-stdin' );
+var readFileSync = require( '@stdlib/fs/read-file' ).sync;
+var pkg = require( './../package.json' );
+var opts = require( './opts.json' );
+var httpServer = require( './../lib' );
+
+
+// FUNCTIONS //
+
+/**
+* Performs initialization tasks.
+*
+* @private
+* @example
+* init();
+*/
+function init() {
+ var opts;
+
+ // Check if newer versions exist for this package:
+ opts = {
+ 'pkg': pkg
+ };
+ notifier( opts ).notify();
+
+ // Set the process title to allow the process to be more easily identified:
+ process.title = pkg.name;
+ process.stdout.on( 'error', process.exit );
+}
+
+/**
+* Prints usage information.
+*
+* @private
+* @example
+* help();
+* // => '...'
+*/
+function help() {
+ var fpath = path.join( __dirname, 'usage.txt' );
+ fs.createReadStream( fpath )
+ .pipe( process.stderr )
+ .on( 'close', onClose );
+
+ function onClose() {
+ process.exit( 0 );
+ }
+}
+
+/**
+* Prints the package version.
+*
+* @private
+* @example
+* version();
+* // => '#.#.#'
+*/
+function version() {
+ var msg = pkg.version.toString()+'\n';
+ process.stdout.write( msg, 'utf8' );
+ process.exit( 0 );
+}
+
+/**
+* Writes an error message to `stderr` and then exits.
+*
+* @private
+* @param {string} msg - error message
+*/
+function exit( msg ) {
+ process.stderr.write( msg+'\n', 'utf8' );
+ process.exit( 1 );
+}
+
+
+// VARIABLES //
+
+var fpath;
+var file;
+var args;
+var err;
+var v;
+
+
+// MAIN //
+
+init();
+
+// Parse command-line arguments:
+args = parseArgs( process.argv.slice( 2 ), opts );
+
+if ( args.help ) {
+ return help();
+}
+if ( args.version ) {
+ return version();
+}
+
+opts = {};
+
+v = args.port || ENV.PORT;
+if ( v ) {
+ opts.port = parseInt( v, 10 );
+}
+v = args.maxport || ENV.MAXPORT;
+if ( v ) {
+ opts.maxport = parseInt( v, 10 );
+}
+v = args.hostname || ENV.HOSTNAME;
+if ( v ) {
+ opts.hostname = v;
+}
+v = args.address || ENV.ADDRESS;
+if ( v ) {
+ opts.address = v;
+}
+if ( args.open ) {
+ opts.open = true;
+}
+
+// Load an HTML file...
+if ( args.html ) {
+ fpath = path.resolve( cwd(), args.html );
+ file = readFileSync( fpath );
+ if ( file instanceof Error ) {
+ return exit( file.message );
+ }
+ opts.html = file;
+}
+// Load a JavaScript file...
+if ( args.javascript ) {
+ fpath = path.resolve( cwd(), args.javascript );
+ file = readFileSync( fpath );
+ if ( file instanceof Error ) {
+ return exit( file.message );
+ }
+ opts.javascript = file;
+}
+// Determine if we need to read data from `stdin`...
+if ( args.stdin ) {
+ if ( args.stdin === 'html' ) {
+ return stdin( html );
+ } else if (
+ args.stdin === 'javascript' ||
+ args.stdin === 'js'
+ ) {
+ return stdin( javascript );
+ }
+ err = new Error( 'invalid flag. Unrecognized/unsupported `stdin` type: '+args.stdin+'.' );
+ return exit( err.message );
+}
+// If the `stdin` flag has not been set and we're receiving data (i.e., not running in a terminal context), assume a content type based on either an `--html` or `--javascript` flag being set...
+else if ( !process.stdin.isTTY ) {
+ if ( args.html ) {
+ return stdin( javascript );
+ } else if ( args.javascript ) {
+ return stdin( html );
+ }
+ // Neither `--html` or `--javascript` was set...
+ err = new Error( 'invalid invocation. Must specify a `stdin` content type.' );
+ return exit( err.message );
+}
+
+return process.nextTick( next );
+
+/**
+* Callback invoked after attempting to read HTML from `stdin`.
+*
+* @private
+* @param {(Error|null)} error - error object
+* @param {Buffer} data - `stdin` data
+*/
+function html( error, data ) {
+ if ( error ) {
+ return exit( error.message );
+ }
+ if ( data.toString() === '' ) {
+ error = new Error( 'no input data. Provide either a path to an HTML file or provide HTML via `stdin`.' );
+ return exit( error.message );
+ }
+ opts.html = data;
+ next();
+}
+
+/**
+* Callback invoked after attempting to read JavaScript from `stdin`.
+*
+* @private
+* @param {(Error|null)} error - error object
+* @param {Buffer} data - `stdin` data
+*/
+function javascript( error, data ) {
+ if ( error ) {
+ return exit( error.message );
+ }
+ if ( data.toString() === '' ) {
+ error = new Error( 'no input data. Provide either a path to a JavaScript file or provide JavaScript via `stdin`.' );
+ return exit( error.message );
+ }
+ opts.javascript = data;
+ next();
+}
+
+/**
+* Callback invoked once ready to run an HTTP server.
+*
+* @private
+*/
+function next() {
+ httpServer( opts );
+}
diff --git a/bin/opts.json b/bin/opts.json
new file mode 100644
index 0000000..936443c
--- /dev/null
+++ b/bin/opts.json
@@ -0,0 +1,30 @@
+{
+ "string": [
+ "port",
+ "maxport",
+ "hostname",
+ "address",
+ "html",
+ "javascript",
+ "stdin"
+ ],
+ "boolean": [
+ "help",
+ "version",
+ "open"
+ ],
+ "alias": {
+ "help": [
+ "h"
+ ],
+ "version": [
+ "V"
+ ],
+ "port": [
+ "p"
+ ],
+ "javascript": [
+ "js"
+ ]
+ }
+}
diff --git a/bin/usage.txt b/bin/usage.txt
new file mode 100644
index 0000000..5fb3329
--- /dev/null
+++ b/bin/usage.txt
@@ -0,0 +1,16 @@
+
+Usage: temp-http-server [options] (--html path | --js path | --stdin type)
+
+Options:
+
+ -h, --help Print this message.
+ -V, --version Print the package version.
+ --html path Serve HTML.
+ --js, --javascript path Serve JavaScript.
+ --stdin type Type of content. Either: html or javascript.
+ -p, --port port Server port. Default: 0.
+ --maxport maxport Max server port. Default: `port`.
+ --hostname hostname Server hostname.
+ --address address Server address. Default: 0.0.0.0.
+ --open Launch a browser once server is ready.
+
diff --git a/docs/types/index.d.ts b/docs/types/index.d.ts
new file mode 100644
index 0000000..0117f05
--- /dev/null
+++ b/docs/types/index.d.ts
@@ -0,0 +1,127 @@
+/*
+* @license Apache-2.0
+*
+* Copyright (c) 2021 The Stdlib Authors.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+// TypeScript Version: 2.0
+
+///
+
+import { Buffer } from 'buffer';
+
+/**
+* Interface defining function options.
+*/
+interface Options {
+ /**
+ * HTML content to serve.
+ */
+ html?: Buffer | string;
+
+ /**
+ * JavaScript to serve.
+ */
+ javascript?: Buffer | string;
+
+ /**
+ * Server port (default: 0).
+ */
+ port?: number;
+
+ /**
+ * Max server port.
+ */
+ maxport?: number;
+
+ /**
+ * Server hostname.
+ */
+ hostname?: string;
+
+ /**
+ * Server address (default: '0.0.0.0').
+ */
+ address?: string;
+
+ /**
+ * Indicates whether to launch a web browser (default: false).
+ */
+ open?: boolean;
+}
+
+/**
+* Callback invoked after creating a server.
+*/
+type Nullary = () => void;
+
+/**
+* Callback invoked after creating a server.
+*
+* @param error - error object or null
+*/
+type Unary = ( error: Error | null ) => void;
+
+/**
+* Callback invoked after creating a server.
+*
+* @param error - error object or null
+* @param server - server object
+*/
+type Binary = ( error: Error | null, server: any ) => void;
+
+/**
+* Callback invoked after creating a server.
+*
+* @param error - error object or null
+* @param server - server object
+*/
+type Callback = Nullary | Unary | Binary;
+
+/**
+* Creates a disposable HTTP server.
+*
+* @param options - server options
+* @param options.html - HTML content to serve
+* @param options.javascript - JavaScript to serve
+* @param options.port - server port (default: 0)
+* @param options.maxport - max server port
+* @param options.hostname - server hostname
+* @param options.address - server address (default: '0.0.0.0')
+* @param options.open - boolean indicating whether to launch a web browser (default: false)
+* @param clbk - callback to invoke upon creating a server
+* @throws must provide valid options
+* @throws error encountered when starting server
+*
+* @example
+* var opts = {
+* 'html': 'Beep
'
+* };
+*
+* httpServer( opts, onReady );
+*
+* function onReady( error, server ) {
+* if ( error ) {
+* throw error;
+* }
+* server.close();
+* }
+*/
+declare function httpServer( options: Options, clbk?: Callback ): void;
+
+
+// EXPORTS //
+
+export = httpServer;
diff --git a/docs/types/test.ts b/docs/types/test.ts
new file mode 100644
index 0000000..03645ca
--- /dev/null
+++ b/docs/types/test.ts
@@ -0,0 +1,143 @@
+/*
+* @license Apache-2.0
+*
+* Copyright (c) 2021 The Stdlib Authors.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+/* tslint:disable:no-unsafe-any */
+
+import httpServer = require( './index' );
+
+/**
+* Callback invoked after creating a server.
+*
+* @param error - error object or null
+* @param server - server object
+*/
+function done( error: Error | null, server: any ) {
+ if ( error ) {
+ throw error;
+ }
+ server.close();
+}
+
+
+// TESTS //
+
+// The function returns a function to create a server...
+{
+ httpServer( { 'html': 'Beep
' }, done ); // $ExpectType void
+}
+
+// The compiler throws an error if the function is provided a first argument which is not an options object...
+{
+ httpServer( 'abc' ); // $ExpectError
+ httpServer( true ); // $ExpectError
+ httpServer( false ); // $ExpectError
+ httpServer( 5 ); // $ExpectError
+ httpServer( [] ); // $ExpectError
+}
+
+// The compiler throws an error if the function is provided a second argument which is not a callback function...
+{
+ httpServer( {}, false ); // $ExpectError
+ httpServer( {}, true ); // $ExpectError
+ httpServer( {}, 123 ); // $ExpectError
+ httpServer( {}, null ); // $ExpectError
+ httpServer( {}, 'abc' ); // $ExpectError
+ httpServer( {}, [] ); // $ExpectError
+ httpServer( {}, {} ); // $ExpectError
+}
+
+// The compiler throws an error if the function is provided a `html` option which is not a string or buffer...
+{
+ httpServer( { 'html': 123 }, done ); // $ExpectError
+ httpServer( { 'html': true }, done ); // $ExpectError
+ httpServer( { 'html': false }, done ); // $ExpectError
+ httpServer( { 'html': null }, done ); // $ExpectError
+ httpServer( { 'html': [] }, done ); // $ExpectError
+ httpServer( { 'html': {} }, done ); // $ExpectError
+ httpServer( { 'html': ( x: number ): number => x }, done ); // $ExpectError
+}
+
+// The compiler throws an error if the function is provided a `javascript` option which is not a string or buffer...
+{
+ httpServer( { 'javascript': 123 }, done ); // $ExpectError
+ httpServer( { 'javascript': true }, done ); // $ExpectError
+ httpServer( { 'javascript': false }, done ); // $ExpectError
+ httpServer( { 'javascript': null }, done ); // $ExpectError
+ httpServer( { 'javascript': [] }, done ); // $ExpectError
+ httpServer( { 'javascript': {} }, done ); // $ExpectError
+ httpServer( { 'javascript': ( x: number ): number => x }, done ); // $ExpectError
+}
+
+// The compiler throws an error if the function is provided a `port` option which is not a number...
+{
+ httpServer( { 'port': 'abc' }, done ); // $ExpectError
+ httpServer( { 'port': true }, done ); // $ExpectError
+ httpServer( { 'port': false }, done ); // $ExpectError
+ httpServer( { 'port': null }, done ); // $ExpectError
+ httpServer( { 'port': [] }, done ); // $ExpectError
+ httpServer( { 'port': {} }, done ); // $ExpectError
+ httpServer( { 'port': ( x: number ): number => x }, done ); // $ExpectError
+}
+
+// The compiler throws an error if the function is provided a `maxport` option which is not a number...
+{
+ httpServer( { 'maxport': 'abc' }, done ); // $ExpectError
+ httpServer( { 'maxport': true }, done ); // $ExpectError
+ httpServer( { 'maxport': false }, done ); // $ExpectError
+ httpServer( { 'maxport': null }, done ); // $ExpectError
+ httpServer( { 'maxport': [] }, done ); // $ExpectError
+ httpServer( { 'maxport': {} }, done ); // $ExpectError
+ httpServer( { 'maxport': ( x: number ): number => x }, done ); // $ExpectError
+}
+
+// The compiler throws an error if the function is provided a `hostname` option which is not a string...
+{
+ httpServer( { 'hostname': 123 }, done ); // $ExpectError
+ httpServer( { 'hostname': true }, done ); // $ExpectError
+ httpServer( { 'hostname': false }, done ); // $ExpectError
+ httpServer( { 'hostname': null }, done ); // $ExpectError
+ httpServer( { 'hostname': [] }, done ); // $ExpectError
+ httpServer( { 'hostname': {} }, done ); // $ExpectError
+ httpServer( { 'hostname': ( x: number ): number => x }, done ); // $ExpectError
+}
+
+// The compiler throws an error if the function is provided an `address` option which is not a string...
+{
+ httpServer( { 'address': 123 }, done ); // $ExpectError
+ httpServer( { 'address': true }, done ); // $ExpectError
+ httpServer( { 'address': false }, done ); // $ExpectError
+ httpServer( { 'address': null }, done ); // $ExpectError
+ httpServer( { 'address': [] }, done ); // $ExpectError
+ httpServer( { 'address': {} }, done ); // $ExpectError
+ httpServer( { 'address': ( x: number ): number => x }, done ); // $ExpectError
+}
+
+// The compiler throws an error if the function is provided an `open` option which is not a boolean...
+{
+ httpServer( { 'open': 123 }, done ); // $ExpectError
+ httpServer( { 'open': 'abc' }, done ); // $ExpectError
+ httpServer( { 'open': null }, done ); // $ExpectError
+ httpServer( { 'open': [] }, done ); // $ExpectError
+ httpServer( { 'open': {} }, done ); // $ExpectError
+ httpServer( { 'open': ( x: number ): number => x }, done ); // $ExpectError
+}
+
+// The compiler throws an error if the function is provided an invalid number of arguments...
+{
+ httpServer( {}, done, {} ); // $ExpectError
+}
diff --git a/examples/fixtures/index.html b/examples/fixtures/index.html
new file mode 100644
index 0000000..18b87af
--- /dev/null
+++ b/examples/fixtures/index.html
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+ Disposable HTTP Server Example
+
+
+ Beep
+
+
+
+
+
diff --git a/examples/fixtures/script.js b/examples/fixtures/script.js
new file mode 100644
index 0000000..d3f364e
--- /dev/null
+++ b/examples/fixtures/script.js
@@ -0,0 +1,39 @@
+/* eslint-disable */
+
+/**
+* @license Apache-2.0
+*
+* Copyright (c) 2018 The Stdlib Authors.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+(function() {
+ 'use strict';
+
+ var idx;
+ var str;
+ var el;
+
+ el = document.querySelector( 'h1' );
+
+ str = [ 'Beep', 'Boop' ];
+ idx = 0;
+
+ setInterval( update, 1000 );
+
+ function update() {
+ idx = (idx+1) % 2;
+ el.innerHTML = str[ idx ];
+ }
+})();
diff --git a/examples/index.js b/examples/index.js
new file mode 100644
index 0000000..d0fac7e
--- /dev/null
+++ b/examples/index.js
@@ -0,0 +1,48 @@
+/**
+* @license Apache-2.0
+*
+* Copyright (c) 2018 The Stdlib Authors.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+'use strict';
+
+var join = require( 'path' ).join;
+var readFileSync = require( '@stdlib/fs-read-file' ).sync;
+var httpServer = require( './../lib' );
+
+var html = join( __dirname, 'fixtures', 'index.html' );
+var js = join( __dirname, 'fixtures', 'script.js' );
+
+var opts = {
+ 'html': readFileSync( html ),
+ 'javascript': readFileSync( js ),
+ 'port': 7331,
+ 'hostname': 'localhost',
+ 'open': false
+};
+
+httpServer( opts, clbk );
+
+function clbk( error, server ) {
+ if ( error ) {
+ throw error;
+ }
+ // Give the user a few seconds to open her web browser before closing the server...
+ setTimeout( onTimeout, 5000 );
+
+ function onTimeout() {
+ server.close();
+ }
+}
diff --git a/lib/connections_store.js b/lib/connections_store.js
new file mode 100644
index 0000000..93c0bcf
--- /dev/null
+++ b/lib/connections_store.js
@@ -0,0 +1,34 @@
+/**
+* @license Apache-2.0
+*
+* Copyright (c) 2018 The Stdlib Authors.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+'use strict';
+
+/**
+* Returns a new connections store.
+*
+* @private
+* @returns {Object} store
+*/
+function create() {
+ return {};
+}
+
+
+// EXPORTS //
+
+module.exports = create;
diff --git a/lib/index.js b/lib/index.js
new file mode 100644
index 0000000..657f924
--- /dev/null
+++ b/lib/index.js
@@ -0,0 +1,50 @@
+/**
+* @license Apache-2.0
+*
+* Copyright (c) 2018 The Stdlib Authors.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+'use strict';
+
+/**
+* Create a disposable HTTP server.
+*
+* @module @stdlib/net/disposable-http-server
+*
+* @example
+* var httpServer = require( '@stdlib/net-disposable-http-server' );
+*
+* var opts = {
+* 'html': 'Beep
'
+* };
+*
+* httpServer( opts, onReady );
+*
+* function onReady( error, server ) {
+* if ( error ) {
+* throw error;
+* }
+* server.close();
+* }
+*/
+
+// MODULES //
+
+var httpServer = require( './server.js' );
+
+
+// EXPORTS //
+
+module.exports = httpServer;
diff --git a/lib/opts.js b/lib/opts.js
new file mode 100644
index 0000000..3cf374b
--- /dev/null
+++ b/lib/opts.js
@@ -0,0 +1,69 @@
+/**
+* @license Apache-2.0
+*
+* Copyright (c) 2018 The Stdlib Authors.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+'use strict';
+
+// MODULES //
+
+var hasOwnProp = require( '@stdlib/assert-has-own-property' );
+
+
+// MAIN //
+
+/**
+* Extracts HTTP server options from input function options.
+*
+* @private
+* @param {Options} options - function options
+* @param {NonNegativeInteger} [options.port] - server port
+* @param {NonNegativeInteger} [options.maxport] - max server port
+* @param {string} [options.hostname] - server hostname
+* @param {string} [options.address] - server address
+* @returns {Options} server options
+*
+* @example
+* var options = {
+* 'html': 'beep
',
+* 'open': true,
+* 'port': 7331,
+* 'address': '127.0.0.1'
+* };
+* var out = opts( options );
+* // returns {'port': 7331, 'address': '127.0.0.1'}
+*/
+function opts( options ) {
+ var out = {};
+ if ( hasOwnProp( options, 'port' ) ) {
+ out.port = options.port;
+ }
+ if ( hasOwnProp( options, 'maxport' ) ) {
+ out.maxport = options.maxport;
+ }
+ if ( hasOwnProp( options, 'hostname' ) ) {
+ out.hostname = options.hostname;
+ }
+ if ( hasOwnProp( options, 'address' ) ) {
+ out.address = options.address;
+ }
+ return out;
+}
+
+
+// EXPORTS //
+
+module.exports = opts;
diff --git a/lib/server.js b/lib/server.js
new file mode 100644
index 0000000..286a809
--- /dev/null
+++ b/lib/server.js
@@ -0,0 +1,328 @@
+/**
+* @license Apache-2.0
+*
+* Copyright (c) 2018 The Stdlib Authors.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+'use strict';
+
+// MODULES //
+
+var path = require( 'path' );
+var logger = require( 'debug' );
+var objectKeys = require( '@stdlib/utils-keys' );
+var createServer = require( '@stdlib/net-http-server' );
+var readFileSync = require( '@stdlib/fs-read-file' ).sync;
+var isString = require( '@stdlib/assert-is-string' ).isPrimitive;
+var isFunction = require( '@stdlib/assert-is-function' );
+var openURL = require( '@stdlib/utils-open-url' );
+var noop = require( '@stdlib/utils-noop' );
+var Buffer = require( '@stdlib/buffer-ctor' );
+var string2buffer = require( '@stdlib/buffer-from-string' );
+var nextTick = require( '@stdlib/utils-next-tick' );
+var validate = require( './validate.js' );
+var serverOpts = require( './opts.js' );
+var createStore = require( './connections_store.js' );
+
+
+// VARIABLES //
+
+var debug = logger( 'disposable-http-server' );
+
+
+// MAIN //
+
+/**
+* Creates a disposable HTTP server.
+*
+* @param {Options} options - server options
+* @param {(Buffer|string)} [options.html] - HTML content to serve
+* @param {(Buffer|string)} [options.javascript] - JavaScript to serve
+* @param {NonNegativeInteger} [options.port=0] - server port
+* @param {NonNegativeInteger} [options.maxport] - max server port
+* @param {string} [options.hostname] - server hostname
+* @param {string} [options.address="0.0.0.0"] - server address
+* @param {boolean} [options.open=false] - boolean indicating whether to launch a web browser
+* @param {Callback} [clbk] - callback to invoke upon creating a server
+* @throws {TypeError} must provide valid options
+* @throws {Error} error encountered when starting server
+*
+* @example
+* var opts = {
+* 'html': 'Beep
'
+* };
+*
+* httpServer( opts, onReady );
+*
+* function onReady( error, server ) {
+* if ( error ) {
+* throw error;
+* }
+* server.close();
+* }
+*/
+function httpServer( options ) {
+ var connections;
+ var isClosing;
+ var server;
+ var sopts;
+ var fpath;
+ var clbk;
+ var boot;
+ var opts;
+ var err;
+
+ opts = {};
+ if ( arguments.length > 1 ) {
+ clbk = arguments[ 1 ];
+ if ( !isFunction( clbk ) ) {
+ throw new TypeError( 'invalid argument. Callback argument must be a function. Value: `' + clbk + '`.' );
+ }
+ } else {
+ clbk = noop;
+ }
+ err = validate( opts, options );
+ if ( err ) {
+ throw err;
+ }
+ // If provided HTML and/or JavaScript as `strings`, convert to `buffers`...
+ if ( opts.html && isString( opts.html ) ) {
+ opts.html = string2buffer( opts.html );
+ }
+ if ( opts.javascript && isString( opts.javascript ) ) {
+ opts.javascript = string2buffer( opts.javascript );
+ }
+ // Extract server options:
+ sopts = serverOpts( options );
+
+ debug( 'Serving provided content.' );
+ if ( !opts.html ) {
+ debug( 'No HTML content provided.' );
+ debug( 'Loading a boilerplate HTML page...' );
+ fpath = path.resolve( __dirname, '../static/index.html' );
+ opts.html = readFileSync( fpath );
+ }
+ // Create a function to boot a server...
+ boot = createServer( sopts, requestListener );
+
+ debug( 'Starting server...' );
+ boot( onServer );
+
+ // Initialize a connections store:
+ connections = createStore();
+
+ /**
+ * Callback invoked upon creating a server.
+ *
+ * @private
+ * @param {(Error|null)} error - error object
+ * @param {Server} _server - server instance
+ * @throws {Error} error encountered when starting server
+ */
+ function onServer( error, _server ) {
+ var addr;
+ if ( error ) {
+ throw error;
+ }
+ debug( 'Server started.' );
+ server = _server;
+ server.on( 'connection', onConnection );
+ server.once( 'close', onClose );
+ if ( opts.open ) {
+ addr = server.address();
+ openURL( 'http://'+addr.address+':'+addr.port );
+ }
+ clbk( null, server );
+ }
+
+ /**
+ * Callback invoked upon receiving a socket connection.
+ *
+ * @private
+ * @param {Socket} socket - socket connection
+ */
+ function onConnection( socket ) {
+ var key = socket.remoteAddress + ':' + socket.remotePort;
+
+ debug( 'Received a socket connection: %s.', key );
+ connections[ key ] = socket;
+ socket.on( 'close', onClose );
+
+ /**
+ * Callback invoked once a socket connection closes.
+ *
+ * @private
+ */
+ function onClose() {
+ debug( 'Socket connection closed: %s.', key );
+ delete connections[ key ];
+ }
+ }
+
+ /**
+ * Callback invoked upon receiving an HTTP request for provided content.
+ *
+ * @private
+ * @param {IncomingMessage} request - HTTP request object
+ * @param {ServerResponse} response - HTTP response object
+ * @returns {void}
+ */
+ function requestListener( request, response ) {
+ debug( 'Received a request for %s', request.url );
+
+ if ( isClosing ) {
+ return unavailable( request, response );
+ }
+ if ( request.url === '/bundle.js' ) {
+ nextTick( onTick( sendJavaScript ) );
+ return response.once( 'finish', onFinish );
+ }
+ if ( request.url !== '/' && request.url !== '/index.html' ) {
+ return notFound( request, response );
+ }
+ nextTick( onTick( sendHTML ) );
+ if ( !opts.javascript ) {
+ response.once( 'finish', onFinish );
+ }
+
+ /**
+ * Returns a callback to return a response on the next tick. Note that this is a workaround for a race condition bug in Node v0.10 (see [nodejs/node#1309][1]).
+ *
+ * [1]: https://github.com/nodejs/node/issues/1309
+ *
+ * @private
+ * @param {Function} fcn - response function
+ * @returns {Callback} callback to invoke on next tick
+ */
+ function onTick( fcn ) {
+ return next;
+
+ /**
+ * Callback to return on a response.
+ *
+ * @private
+ */
+ function next() {
+ fcn( request, response );
+ }
+ }
+ }
+
+ /**
+ * Sends a 404 response.
+ *
+ * @private
+ * @param {IncomingMessage} request - HTTP request object
+ * @param {ServerResponse} response - HTTP response object
+ */
+ function notFound( request, response ) {
+ debug( 'Sending 404 response...' );
+ response.statusCode = 404;
+ response.end();
+ }
+
+ /**
+ * Sends a 503 response.
+ *
+ * @private
+ * @param {IncomingMessage} request - HTTP request object
+ * @param {ServerResponse} response - HTTP response object
+ */
+ function unavailable( request, response ) {
+ debug( 'Sending 503 response...' );
+ response.statusCode = 503;
+ response.end();
+ }
+
+ /**
+ * Sends HTML content in response to a client request.
+ *
+ * @private
+ * @param {IncomingMessage} request - HTTP request object
+ * @param {ServerResponse} response - HTTP response object
+ */
+ function sendHTML( request, response ) {
+ debug( 'Sending HTML...' );
+ response.statusCode = 200;
+ response.setHeader( 'Content-Type', 'text/html' );
+
+ // TODO: we have to convert to a `string` because Node v0.10 requires a `string`. Subsequent versions support providing a `Buffer` object. Ideally, we would sniff `Buffer` support and only convert to a `string` if necessary.
+ response.setHeader( 'Content-Length', Buffer.byteLength( opts.html.toString() ) );
+ response.end( opts.html );
+ }
+
+ /**
+ * Sends JavaScript content in response to a client request.
+ *
+ * @private
+ * @param {IncomingMessage} request - HTTP request object
+ * @param {ServerResponse} response - HTTP response object
+ */
+ function sendJavaScript( request, response ) {
+ debug( 'Sending JavaScript...' );
+ response.statusCode = 200;
+ response.setHeader( 'Content-Type', 'text/javascript' );
+
+ // TODO: we have to convert to a `string` because Node v0.10 requires a `string`. Subsequent versions support providing a `Buffer` object. Ideally, we would sniff `Buffer` support and only convert to a `string` if necessary.
+ response.setHeader( 'Content-Length', Buffer.byteLength( opts.javascript.toString() ) );
+ response.end( opts.javascript );
+ }
+
+ /**
+ * Callback invoked once the server should close.
+ *
+ * @private
+ */
+ function onFinish() {
+ debug( 'Finished serving content.' );
+ isClosing = true;
+
+ debug( 'Closing the server...' );
+ server.close();
+
+ setTimeout( destroyConnections, 5000 );
+ }
+
+ /**
+ * Destroys all connections.
+ *
+ * @private
+ */
+ function destroyConnections() {
+ var keys;
+ var i;
+
+ debug( 'Destroying all connections...' );
+ keys = objectKeys( connections );
+ for ( i = 0; i < keys.length; i++ ) {
+ debug( 'Destroying connection %s...', keys[i] );
+ connections[ keys[i] ].destroy();
+ }
+ }
+
+ /**
+ * Callback invoked once a server closes.
+ *
+ * @private
+ */
+ function onClose() {
+ debug( 'Server closed.' );
+ }
+}
+
+
+// EXPORTS //
+
+module.exports = httpServer;
diff --git a/lib/validate.js b/lib/validate.js
new file mode 100644
index 0000000..19e606d
--- /dev/null
+++ b/lib/validate.js
@@ -0,0 +1,83 @@
+/**
+* @license Apache-2.0
+*
+* Copyright (c) 2018 The Stdlib Authors.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+'use strict';
+
+// MODULES //
+
+var isBuffer = require( '@stdlib/assert-is-buffer' );
+var isBoolean = require( '@stdlib/assert-is-boolean' ).isPrimitive;
+var isString = require( '@stdlib/assert-is-string' ).isPrimitive;
+var isObject = require( '@stdlib/assert-is-plain-object' );
+var hasOwnProp = require( '@stdlib/assert-has-own-property' );
+
+
+// MAIN //
+
+/**
+* Validates function options.
+*
+* @private
+* @param {Object} opts - destination object
+* @param {Options} options - function options
+* @param {(Buffer|string)} [options.html] - HTML content to serve
+* @param {(Buffer|string)} [options.javascript] - JavaScript script to serve
+* @param {boolean} [options.open] - indicates whether to launch a web browser
+* @returns {Error|null} error or null
+*
+* @example
+* var options = {
+* 'port': 7331,
+* 'address': '127.0.0.1',
+* 'open': false
+* };
+* var opts = {};
+* var err = validate( opts, options );
+* if ( err ) {
+* throw err;
+* }
+*/
+function validate( opts, options ) {
+ if ( !isObject( options ) ) {
+ return new TypeError( 'invalid argument. Options argument must be an object. Value: `' + options + '`.' );
+ }
+ if ( hasOwnProp( options, 'html' ) ) {
+ opts.html = options.html;
+ if ( !isBuffer( opts.html ) && !isString( opts.html ) ) {
+ return new TypeError( 'invalid option. `html` option must be either a `buffer` or a primitive string. Option: `' + opts.html + '`.' );
+ }
+ }
+ if ( hasOwnProp( options, 'javascript' ) ) {
+ opts.javascript = options.javascript;
+ if ( !isBuffer( opts.javascript ) && !isString( opts.javascript ) ) {
+ return new TypeError( 'invalid option. `javascript` option must be either a `buffer` or a primitive string. Option: `' + opts.javascript + '`.' );
+ }
+ }
+ if ( hasOwnProp( options, 'open' ) ) {
+ opts.open = options.open;
+ if ( !isBoolean( opts.open ) ) {
+ return new TypeError( 'invalid option. `open` option must be a primitive boolean. Option: `' + opts.open + '`.' );
+ }
+ }
+ return null;
+}
+
+
+// EXPORTS //
+
+module.exports = validate;
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..12ea759
--- /dev/null
+++ b/package.json
@@ -0,0 +1,109 @@
+{
+ "name": "@stdlib/net-disposable-http-server",
+ "version": "0.0.0",
+ "description": "Disposable HTTP server.",
+ "license": "Apache-2.0",
+ "author": {
+ "name": "The Stdlib Authors",
+ "url": "https://github.com/stdlib-js/stdlib/graphs/contributors"
+ },
+ "contributors": [
+ {
+ "name": "The Stdlib Authors",
+ "url": "https://github.com/stdlib-js/stdlib/graphs/contributors"
+ }
+ ],
+ "bin": {
+ "temp-http-server": "./bin/cli"
+ },
+ "main": "./lib",
+ "directories": {
+ "bin": "./bin",
+ "example": "./examples",
+ "lib": "./lib",
+ "test": "./test"
+ },
+ "types": "./docs/types",
+ "scripts": {
+ "test": "make test",
+ "test-cov": "make test-cov",
+ "examples": "make examples"
+ },
+ "homepage": "https://github.com/stdlib-js/stdlib",
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/stdlib-js/net-disposable-http-server.git"
+ },
+ "bugs": {
+ "url": "https://github.com/stdlib-js/stdlib/issues"
+ },
+ "dependencies": {
+ "@stdlib/assert-has-own-property": "^0.0.x",
+ "@stdlib/assert-is-boolean": "^0.0.x",
+ "@stdlib/assert-is-buffer": "^0.0.x",
+ "@stdlib/assert-is-function": "^0.0.x",
+ "@stdlib/assert-is-plain-object": "^0.0.x",
+ "@stdlib/assert-is-string": "^0.0.x",
+ "@stdlib/buffer-ctor": "^0.0.x",
+ "@stdlib/buffer-from-string": "^0.0.x",
+ "@stdlib/fs-read-file": "^0.0.x",
+ "@stdlib/net-http-server": "^0.0.x",
+ "@stdlib/process-cwd": "^0.0.x",
+ "@stdlib/process-env": "^0.0.x",
+ "@stdlib/process-read-stdin": "^0.0.x",
+ "@stdlib/utils-keys": "^0.0.x",
+ "@stdlib/utils-next-tick": "^0.0.x",
+ "@stdlib/utils-noop": "^0.0.x",
+ "@stdlib/utils-open-url": "^0.0.x",
+ "debug": "^2.6.9",
+ "minimist": "^1.2.0",
+ "update-notifier": "^1.0.0"
+ },
+ "devDependencies": {
+ "proxyquire": "^2.0.0",
+ "tape": "git+https://github.com/kgryte/tape.git#fix/globby",
+ "istanbul": "^0.4.1",
+ "tap-spec": "5.x.x"
+ },
+ "engines": {
+ "node": ">=0.10.0",
+ "npm": ">2.7.0"
+ },
+ "os": [
+ "aix",
+ "darwin",
+ "freebsd",
+ "linux",
+ "macos",
+ "openbsd",
+ "sunos",
+ "win32",
+ "windows"
+ ],
+ "keywords": [
+ "stdlib",
+ "net",
+ "network",
+ "networking",
+ "http",
+ "server",
+ "create",
+ "serve",
+ "static",
+ "disposable",
+ "once",
+ "one-time",
+ "throwaway",
+ "temporary",
+ "temp"
+ ],
+ "__stdlib__": {
+ "envs": {
+ "browser": false
+ }
+ },
+ "funding": {
+ "type": "patreon",
+ "url": "https://www.patreon.com/athan"
+ }
+}
diff --git a/test/fixtures/request.js b/test/fixtures/request.js
new file mode 100644
index 0000000..7b2edbe
--- /dev/null
+++ b/test/fixtures/request.js
@@ -0,0 +1,68 @@
+/**
+* @license Apache-2.0
+*
+* Copyright (c) 2018 The Stdlib Authors.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+'use strict';
+
+// MODULES //
+
+var http = require( 'http' );
+
+
+// MAIN //
+
+/**
+* Makes an HTTP `GET` request.
+*
+* @private
+* @param {Options} opts - request options
+* @param {Callback} clbk - callback to invoke after receiving an HTTP response
+*/
+function request( opts, clbk ) {
+ var body;
+ var req;
+ var res;
+
+ req = http.request( opts, onResponse );
+ req.on( 'error', onError );
+ req.end();
+
+ function onError( error ) {
+ clbk( error );
+ }
+
+ function onResponse( response ) {
+ body = '';
+ response.setEncoding( 'utf8' );
+ response.on( 'data', onData );
+ response.on( 'end', onEnd );
+ res = response;
+ }
+
+ function onData( chunk ) {
+ body += chunk;
+ }
+
+ function onEnd() {
+ clbk( null, res, body );
+ }
+}
+
+
+// EXPORTS //
+
+module.exports = request;
diff --git a/test/test.js b/test/test.js
new file mode 100644
index 0000000..cbf8925
--- /dev/null
+++ b/test/test.js
@@ -0,0 +1,33 @@
+/**
+* @license Apache-2.0
+*
+* Copyright (c) 2018 The Stdlib Authors.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+'use strict';
+
+// MODULES //
+
+var tape = require( 'tape' );
+var httpServer = require( './../lib' );
+
+
+// TESTS //
+
+tape( 'main export is a function', function test( t ) {
+ t.ok( true, __filename );
+ t.equal( typeof httpServer, 'function', 'main export is a function' );
+ t.end();
+});
diff --git a/test/test.opts.js b/test/test.opts.js
new file mode 100644
index 0000000..57ec2c8
--- /dev/null
+++ b/test/test.opts.js
@@ -0,0 +1,144 @@
+/**
+* @license Apache-2.0
+*
+* Copyright (c) 2018 The Stdlib Authors.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+'use strict';
+
+// MODULES //
+
+var tape = require( 'tape' );
+var getOpts = require( './../lib/opts.js' );
+
+
+// TESTS //
+
+tape( 'main export is a function', function test( t ) {
+ t.ok( true, __filename );
+ t.equal( typeof getOpts, 'function', 'main export is a function' );
+ t.end();
+});
+
+tape( 'the function extracts a `port` option', function test( t ) {
+ var expected;
+ var actual;
+ var opts;
+
+ opts = {
+ 'html': 'beep
',
+ 'open': true,
+ 'port': 7331
+ };
+
+ expected = {
+ 'port': opts.port
+ };
+
+ actual = getOpts( opts );
+
+ t.deepEqual( actual, expected, 'extracts server options' );
+ t.end();
+});
+
+tape( 'the function extracts a `maxport` option', function test( t ) {
+ var expected;
+ var actual;
+ var opts;
+
+ opts = {
+ 'html': 'beep
',
+ 'open': true,
+ 'maxport': 8000
+ };
+
+ expected = {
+ 'maxport': opts.maxport
+ };
+
+ actual = getOpts( opts );
+
+ t.deepEqual( actual, expected, 'extracts server options' );
+ t.end();
+});
+
+tape( 'the function extracts a `hostname` option', function test( t ) {
+ var expected;
+ var actual;
+ var opts;
+
+ opts = {
+ 'html': 'beep
',
+ 'open': true,
+ 'hostname': 'localhost'
+ };
+
+ expected = {
+ 'hostname': opts.hostname
+ };
+
+ actual = getOpts( opts );
+
+ t.deepEqual( actual, expected, 'extracts server options' );
+ t.end();
+});
+
+tape( 'the function extracts an `address` option', function test( t ) {
+ var expected;
+ var actual;
+ var opts;
+
+ opts = {
+ 'html': 'beep
',
+ 'open': true,
+ 'address': '127.0.0.1'
+ };
+
+ expected = {
+ 'address': opts.address
+ };
+
+ actual = getOpts( opts );
+
+ t.deepEqual( actual, expected, 'extracts server options' );
+ t.end();
+});
+
+tape( 'the function ignores non HTTP server options', function test( t ) {
+ var expected;
+ var actual;
+ var opts;
+
+ opts = {
+ 'html': 'beep
',
+ 'open': true,
+ 'port': 7331,
+ 'maxport': 8000,
+ 'address': '127.0.0.1',
+ 'hostname': 'localhost'
+ };
+
+ expected = {
+ 'port': opts.port,
+ 'maxport': opts.maxport,
+ 'address': opts.address,
+ 'hostname': opts.hostname
+ };
+
+ actual = getOpts( opts );
+
+ t.deepEqual( actual, expected, 'extracts server options' );
+ t.end();
+});
diff --git a/test/test.server.js b/test/test.server.js
new file mode 100644
index 0000000..cdc2129
--- /dev/null
+++ b/test/test.server.js
@@ -0,0 +1,711 @@
+/**
+* @license Apache-2.0
+*
+* Copyright (c) 2018 The Stdlib Authors.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+'use strict';
+
+// MODULES //
+
+var path = require( 'path' );
+var tape = require( 'tape' );
+var proxyquire = require( 'proxyquire' );
+var readFileSync = require( '@stdlib/fs-read-file' ).sync;
+var noop = require( '@stdlib/utils-noop' );
+var string2buffer = require( '@stdlib/buffer-from-string' );
+var httpServer = require( './../lib/server.js' );
+
+
+// FIXTURES //
+
+var request = require( './fixtures/request.js' );
+
+
+// TESTS //
+
+tape( 'main export is a function', function test( t ) {
+ t.ok( true, __filename );
+ t.equal( typeof httpServer, 'function', 'main export is a function' );
+ t.end();
+});
+
+tape( 'the function requires an options object', function test( t ) {
+ t.throws( foo, Error, 'throws an error' );
+ t.end();
+
+ function foo() {
+ httpServer();
+ }
+});
+
+tape( 'the function throws a type error if provided an invalid option', function test( t ) {
+ t.throws( foo, TypeError, 'throws a type error' );
+ t.end();
+
+ function foo() {
+ httpServer({
+ 'open': null
+ });
+ }
+});
+
+tape( 'the function throws a type error if provided a callback argument which is not a function', function test( t ) {
+ var values;
+ var i;
+
+ values = [
+ '5',
+ 5,
+ NaN,
+ null,
+ undefined,
+ true,
+ [],
+ {}
+ ];
+
+ for ( i = 0; i < values.length; i++ ) {
+ t.throws( badValue( values[i] ), TypeError, 'throws a type error when provided '+values[i] );
+ }
+ t.end();
+
+ function badValue( value ) {
+ return function badValue() {
+ httpServer( {}, value );
+ };
+ }
+});
+
+tape( 'the function throws an error if an error is encountered while attempting to start a server', function test( t ) {
+ var httpServer = proxyquire( './../lib/server.js', {
+ '@stdlib/net/http-server': createServer,
+ '@stdlib/utils/open-url': noop
+ });
+
+ t.throws( foo, Error, 'throws an error' );
+ t.end();
+
+ function createServer() {
+ return function boot( clbk ) {
+ clbk( new Error( 'beep' ) );
+ };
+ }
+
+ function foo() {
+ httpServer( {} );
+ }
+});
+
+tape( 'if only provided HTML, the server serves the HTML content once and then closes', function test( t ) {
+ var server;
+ var opts;
+ var flg;
+
+ opts = {
+ 'port': 7331,
+ 'address': '127.0.0.1',
+ 'html': 'Beep
'
+ };
+
+ httpServer( opts, onReady );
+
+ function onReady( error, _server ) {
+ var options;
+ if ( error ) {
+ t.ok( false, error.message );
+ return t.end();
+ }
+ server = _server;
+ server.on( 'close', onClose );
+
+ options = {
+ 'protocol': 'http:',
+ 'hostname': opts.address,
+ 'port': opts.port,
+ 'path': '/',
+ 'method': 'GET'
+ };
+ request( options, onResponse );
+ }
+
+ function onResponse( error, res, body ) {
+ if ( error ) {
+ t.ok( false, error.message );
+ }
+ t.equal( body, opts.html, 'returns content' );
+ if ( flg ) {
+ t.end();
+ } else {
+ flg = true;
+ }
+ }
+
+ function onClose() {
+ t.ok( true, 'server closed' );
+ if ( flg ) {
+ t.end();
+ } else {
+ flg = true;
+ }
+ }
+});
+
+tape( 'if only provided JavaScript, the server serves an HTML boilerplate and then the JavaScript and then closes', function test( t ) {
+ var server;
+ var fpath;
+ var html;
+ var opts;
+ var flg;
+
+ opts = {
+ 'port': 7331,
+ 'address': '127.0.0.1',
+ 'javascript': 'console.log("Beep");'
+ };
+
+ fpath = path.resolve( __dirname, '../static/index.html' );
+ html = readFileSync( fpath, 'utf8' );
+
+ httpServer( opts, onReady );
+
+ function onReady( error, _server ) {
+ if ( error ) {
+ t.ok( false, error.message );
+ return t.end();
+ }
+ server = _server;
+ server.on( 'close', onClose );
+ getHTML();
+ }
+
+ function getHTML() {
+ var options = {
+ 'protocol': 'http:',
+ 'hostname': opts.address,
+ 'port': opts.port,
+ 'path': '/',
+ 'method': 'GET'
+ };
+ request( options, onHTML );
+ }
+
+ function onHTML( error, res, data ) {
+ if ( error ) {
+ t.ok( false, error.message );
+ return t.end();
+ }
+ t.equal( data, html, 'returns HTML boilerplate' );
+ getJavaScript();
+ }
+
+ function getJavaScript() {
+ var options = {
+ 'protocol': 'http:',
+ 'hostname': opts.address,
+ 'port': opts.port,
+ 'path': '/bundle.js',
+ 'method': 'GET'
+ };
+ request( options, onJavaScript );
+ }
+
+ function onJavaScript( error, res, data ) {
+ if ( error ) {
+ t.ok( false, error.message );
+ return t.end();
+ }
+ t.equal( data, opts.javascript, 'returns JavaScript' );
+ if ( flg ) {
+ t.end();
+ } else {
+ flg = true;
+ }
+ }
+
+ function onClose() {
+ t.ok( true, 'server closed' );
+ if ( flg ) {
+ t.end();
+ } else {
+ flg = true;
+ }
+ }
+});
+
+tape( 'if provided HTML and JavaScript, the server serves the HTML and then the JavaScript and then closes', function test( t ) {
+ var server;
+ var opts;
+ var flg;
+
+ opts = {
+ 'port': 7331,
+ 'address': '127.0.0.1',
+ 'html': '',
+ 'javascript': 'console.log("Beep");'
+ };
+
+ httpServer( opts, onReady );
+
+ function onReady( error, _server ) {
+ if ( error ) {
+ t.ok( false, error.message );
+ return t.end();
+ }
+ server = _server;
+ server.on( 'close', onClose );
+ getHTML();
+ }
+
+ function getHTML() {
+ var options = {
+ 'protocol': 'http:',
+ 'hostname': opts.address,
+ 'port': opts.port,
+ 'path': '/',
+ 'method': 'GET'
+ };
+ request( options, onHTML );
+ }
+
+ function onHTML( error, res, data ) {
+ if ( error ) {
+ t.ok( false, error.message );
+ return t.end();
+ }
+ t.equal( data, opts.html, 'returns HTML' );
+ getJavaScript();
+ }
+
+ function getJavaScript() {
+ var options = {
+ 'protocol': 'http:',
+ 'hostname': opts.address,
+ 'port': opts.port,
+ 'path': '/bundle.js',
+ 'method': 'GET'
+ };
+ request( options, onJavaScript );
+ }
+
+ function onJavaScript( error, res, data ) {
+ if ( error ) {
+ t.ok( false, error.message );
+ return t.end();
+ }
+ t.equal( data, opts.javascript, 'returns JavaScript' );
+ if ( flg ) {
+ t.end();
+ } else {
+ flg = true;
+ }
+ }
+
+ function onClose() {
+ t.ok( true, 'server closed' );
+ if ( flg ) {
+ t.end();
+ } else {
+ flg = true;
+ }
+ }
+});
+
+tape( 'if a client only requests JavaScript, the server serves the JavaScript and then closes', function test( t ) {
+ var server;
+ var opts;
+ var flg;
+
+ opts = {
+ 'port': 7331,
+ 'address': '127.0.0.1',
+ 'javascript': 'console.log("Beep");'
+ };
+
+ httpServer( opts, onReady );
+
+ function onReady( error, _server ) {
+ if ( error ) {
+ t.ok( false, error.message );
+ return t.end();
+ }
+ server = _server;
+ server.on( 'close', onClose );
+ getJavaScript();
+ }
+
+ function getJavaScript() {
+ var options = {
+ 'protocol': 'http:',
+ 'hostname': opts.address,
+ 'port': opts.port,
+ 'path': '/bundle.js',
+ 'method': 'GET'
+ };
+ request( options, onJavaScript );
+ }
+
+ function onJavaScript( error, res, data ) {
+ if ( error ) {
+ t.ok( false, error.message );
+ return t.end();
+ }
+ t.equal( data, opts.javascript, 'returns JavaScript' );
+ if ( flg ) {
+ t.end();
+ } else {
+ flg = true;
+ }
+ }
+
+ function onClose() {
+ t.ok( true, 'server closed' );
+ if ( flg ) {
+ t.end();
+ } else {
+ flg = true;
+ }
+ }
+});
+
+tape( 'if the server receives a request for something other than the JavaScript bundle or base HTML page, the server returns a `404` response', function test( t ) {
+ var server;
+ var opts;
+
+ opts = {
+ 'port': 7331,
+ 'address': '127.0.0.1',
+ 'html': 'Beep
'
+ };
+
+ httpServer( opts, onReady );
+
+ function onReady( error, _server ) {
+ var options;
+ if ( error ) {
+ t.ok( false, error.message );
+ return t.end();
+ }
+ server = _server;
+ server.on( 'close', onClose );
+
+ options = {
+ 'protocol': 'http:',
+ 'hostname': opts.address,
+ 'port': opts.port,
+ 'path': '/beep/boop.js',
+ 'method': 'GET'
+ };
+ request( options, onResponse );
+ }
+
+ function onResponse( error, res ) {
+ if ( error ) {
+ t.ok( false, error.message );
+ }
+ t.equal( res.statusCode, 404, 'returns 404' );
+ server.close();
+ }
+
+ function onClose() {
+ t.ok( true, 'server closed' );
+ t.end();
+ }
+});
+
+tape( 'if the server receives a request while closing, the server returns a `503` response', function test( t ) {
+ var server;
+ var close;
+ var opts;
+
+ opts = {
+ 'port': 7331,
+ 'address': '127.0.0.1',
+ 'html': 'Beep!'
+ };
+
+ httpServer( opts, onReady );
+
+ function onReady( error, _server ) {
+ if ( error ) {
+ t.ok( false, error.message );
+ return t.end();
+ }
+ server = _server;
+
+ // Intercept the close method:
+ close = server.close;
+ close = close.bind( server );
+ server.close = noop;
+
+ getHTML();
+ }
+
+ function getHTML() {
+ var options = {
+ 'protocol': 'http:',
+ 'hostname': opts.address,
+ 'port': opts.port,
+ 'path': '/',
+ 'method': 'GET'
+ };
+
+ request( options, onHTML );
+ }
+
+ function onHTML( error, res, data ) {
+ if ( error ) {
+ t.ok( false, error.message );
+ return t.end();
+ }
+ t.equal( data, opts.html, 'returns HTML' );
+ next();
+ }
+
+ function next() {
+ var options = {
+ 'protocol': 'http:',
+ 'hostname': opts.address,
+ 'port': opts.port,
+ 'path': '/bundle.js',
+ 'method': 'GET'
+ };
+ request( options, onResponse );
+ }
+
+ function onResponse( error, res ) {
+ if ( error ) {
+ t.ok( false, error.message );
+ return t.end();
+ }
+ t.equal( res.statusCode, 503, 'returns 503 response' );
+
+ // Restore the close method:
+ server.close = close;
+ server.close();
+
+ t.end();
+ }
+});
+
+tape( 'the function accepts `Buffer` objects for both HTML and JavaScript', function test( t ) {
+ var server;
+ var opts;
+ var flg;
+
+ opts = {
+ 'port': 7331,
+ 'address': '127.0.0.1',
+ 'html': string2buffer( '' ),
+ 'javascript': string2buffer( 'console.log("Beep");' )
+ };
+
+ httpServer( opts, onReady );
+
+ function onReady( error, _server ) {
+ if ( error ) {
+ t.ok( false, error.message );
+ return t.end();
+ }
+ server = _server;
+ server.on( 'close', onClose );
+ getHTML();
+ }
+
+ function getHTML() {
+ var options = {
+ 'protocol': 'http:',
+ 'hostname': opts.address,
+ 'port': opts.port,
+ 'path': '/',
+ 'method': 'GET'
+ };
+ request( options, onHTML );
+ }
+
+ function onHTML( error, res, data ) {
+ if ( error ) {
+ t.ok( false, error.message );
+ return t.end();
+ }
+ t.equal( data, opts.html.toString(), 'returns HTML' );
+ getJavaScript();
+ }
+
+ function getJavaScript() {
+ var options = {
+ 'protocol': 'http:',
+ 'hostname': opts.address,
+ 'port': opts.port,
+ 'path': '/bundle.js',
+ 'method': 'GET'
+ };
+ request( options, onJavaScript );
+ }
+
+ function onJavaScript( error, res, data ) {
+ if ( error ) {
+ t.ok( false, error.message );
+ return t.end();
+ }
+ t.equal( data, opts.javascript.toString(), 'returns JavaScript' );
+ if ( flg ) {
+ t.end();
+ } else {
+ flg = true;
+ }
+ }
+
+ function onClose() {
+ t.ok( true, 'server closed' );
+ if ( flg ) {
+ t.end();
+ } else {
+ flg = true;
+ }
+ }
+});
+
+tape( 'the server will close any persistent connections on close', function test( t ) {
+ var httpServer;
+ var server;
+ var opts;
+ var flg;
+
+ httpServer = proxyquire( './../lib/server.js', {
+ './connections_store.js': store
+ });
+
+ opts = {
+ 'port': 7331,
+ 'address': '127.0.0.1',
+ 'html': 'Beep
'
+ };
+
+ httpServer( opts, onReady );
+
+ function onReady( error, _server ) {
+ var options;
+ if ( error ) {
+ t.ok( false, error.message );
+ return t.end();
+ }
+ server = _server;
+ server.on( 'close', onClose );
+
+ options = {
+ 'protocol': 'http:',
+ 'hostname': opts.address,
+ 'port': opts.port,
+ 'path': '/',
+ 'method': 'GET'
+ };
+ request( options, noop );
+ }
+
+ function onClose() {
+ t.ok( true, 'server closed' );
+ if ( flg ) {
+ t.end();
+ } else {
+ flg = true;
+ }
+ }
+
+ function store() {
+ return {
+ '1234': {
+ 'destroy': destroy
+ }
+ };
+ }
+
+ function destroy() {
+ if ( flg ) {
+ t.end();
+ } else {
+ flg = true;
+ }
+ }
+});
+
+tape( 'if the `open` option is `true`, the function will attempt to open the content in a user\'s default web browser', function test( t ) {
+ var httpServer;
+ var server;
+ var opts;
+ var flg;
+
+ opts = {
+ 'port': 7331,
+ 'address': '127.0.0.1',
+ 'html': 'Beep
',
+ 'open': true
+ };
+
+ httpServer = proxyquire( './../lib/server.js', {
+ '@stdlib/utils/open-url': openURL
+ });
+
+ httpServer( opts, onReady );
+
+ function openURL( url ) {
+ var options;
+
+ t.equal( url, 'http://'+opts.address+':'+opts.port, 'attempts to open URL' );
+
+ options = {
+ 'protocol': 'http:',
+ 'hostname': opts.address,
+ 'port': opts.port,
+ 'path': '/',
+ 'method': 'GET'
+ };
+ setTimeout( onTimeout, 0 );
+
+ function onTimeout() {
+ request( options, onResponse );
+ }
+ }
+
+ function onReady( error, _server ) {
+ if ( error ) {
+ t.ok( false, error.message );
+ return t.end();
+ }
+ server = _server;
+ server.on( 'close', onClose );
+ }
+
+ function onResponse( error, res, body ) {
+ if ( error ) {
+ t.ok( false, error.message );
+ }
+ t.equal( body, opts.html, 'returns content' );
+ if ( flg ) {
+ t.end();
+ } else {
+ flg = true;
+ }
+ }
+
+ function onClose() {
+ t.ok( true, 'server closed' );
+ if ( flg ) {
+ t.end();
+ } else {
+ flg = true;
+ }
+ }
+});
diff --git a/test/test.validate.js b/test/test.validate.js
new file mode 100644
index 0000000..081bdfb
--- /dev/null
+++ b/test/test.validate.js
@@ -0,0 +1,179 @@
+/**
+* @license Apache-2.0
+*
+* Copyright (c) 2018 The Stdlib Authors.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+'use strict';
+
+// MODULES //
+
+var tape = require( 'tape' );
+var validate = require( './../lib/validate.js' );
+
+
+// TESTS //
+
+tape( 'main export is a function', function test( t ) {
+ t.ok( true, __filename );
+ t.equal( typeof validate, 'function', 'main export is a function' );
+ t.end();
+});
+
+tape( 'the function returns a type error if not provided an options object', function test( t ) {
+ var values;
+ var err;
+ var i;
+
+ values = [
+ '5',
+ 5,
+ NaN,
+ true,
+ null,
+ undefined,
+ [],
+ function noop() {}
+ ];
+
+ for ( i = 0; i < values.length; i++ ) {
+ err = validate( {}, values[i] );
+ t.equal( err instanceof TypeError, true, 'returns a TypeError when provided '+values[i] );
+ }
+ t.end();
+});
+
+tape( 'the function returns a type error if provided an `html` option which is neither a `Buffer` or a `string`', function test( t ) {
+ var values;
+ var err;
+ var i;
+
+ values = [
+ 5,
+ NaN,
+ true,
+ null,
+ undefined,
+ [],
+ {},
+ function noop() {}
+ ];
+
+ for ( i = 0; i < values.length; i++ ) {
+ err = validate( {}, {
+ 'html': values[i]
+ });
+ t.equal( err instanceof TypeError, true, 'returns a TypeError when provided '+values[i] );
+ }
+ t.end();
+});
+
+tape( 'the function returns a type error if provided a `javascript` option which is neither a `Buffer` or a `string`', function test( t ) {
+ var values;
+ var err;
+ var i;
+
+ values = [
+ 5,
+ NaN,
+ true,
+ null,
+ undefined,
+ [],
+ {},
+ function noop() {}
+ ];
+
+ for ( i = 0; i < values.length; i++ ) {
+ err = validate( {}, {
+ 'javascript': values[i]
+ });
+ t.equal( err instanceof TypeError, true, 'returns a TypeError when provided '+values[i] );
+ }
+ t.end();
+});
+
+tape( 'the function returns a type error if provided an `open` option which is not a `boolean` primitive', function test( t ) {
+ var values;
+ var err;
+ var i;
+
+ values = [
+ '5',
+ 5,
+ NaN,
+ null,
+ undefined,
+ [],
+ {},
+ function noop() {}
+ ];
+
+ for ( i = 0; i < values.length; i++ ) {
+ err = validate( {}, {
+ 'open': values[i]
+ });
+ t.equal( err instanceof TypeError, true, 'returns a TypeError when provided '+values[i] );
+ }
+ t.end();
+});
+
+tape( 'the function returns `null` if all options are valid', function test( t ) {
+ var expected;
+ var options;
+ var opts;
+ var err;
+
+ options = {
+ 'html': 'beep',
+ 'javascript': 'console.log("beep");',
+ 'open': true
+ };
+ opts = {};
+
+ expected = {
+ 'html': options.html,
+ 'javascript': options.javascript,
+ 'open': options.open
+ };
+
+ err = validate( opts, options );
+
+ t.equal( err, null, 'returns null' );
+ t.deepEqual( opts, expected, 'extracts options' );
+
+ t.end();
+});
+
+tape( 'the function ignores unrecognized options', function test( t ) {
+ var options;
+ var opts;
+ var err;
+
+ options = {
+ 'beep': 'boop',
+ 'foo': 5,
+ 'bar': {}
+ };
+
+ opts = {};
+
+ err = validate( opts, options );
+
+ t.equal( err, null, 'returns null' );
+ t.deepEqual( opts, {}, 'ignores unrecognized options' );
+
+ t.end();
+});