├── .npmignore ├── test ├── fixtures │ ├── simplest │ │ ├── test.txt │ │ └── index.html │ └── multiple │ │ ├── app │ │ └── index.html │ │ └── dist │ │ └── dist.html ├── test_node20.mjs └── test.js ├── jetbrains.png ├── .gitignore ├── .travis.yml ├── examples ├── multiple │ ├── dist │ │ └── dist.html │ ├── app │ │ ├── stylus │ │ │ └── main.styl │ │ └── index.html │ └── gulpfile.js ├── simplest │ ├── app │ │ └── index.html │ └── gulpfile.js └── livereload │ ├── app │ ├── stylus │ │ └── main.styl │ └── index.html │ ├── path │ └── path.html │ └── gulpfile.js ├── .github └── workflows │ └── github-actions-node.yml ├── bin └── make_certs.sh ├── certs ├── server.crt └── server.key ├── LICENSE ├── package.json ├── README.md └── src └── index.coffee /.npmignore: -------------------------------------------------------------------------------- 1 | examples -------------------------------------------------------------------------------- /test/fixtures/simplest/test.txt: -------------------------------------------------------------------------------- 1 | Hello world -------------------------------------------------------------------------------- /test/fixtures/multiple/app/index.html: -------------------------------------------------------------------------------- 1 | app test -------------------------------------------------------------------------------- /test/fixtures/multiple/dist/dist.html: -------------------------------------------------------------------------------- 1 | dist test -------------------------------------------------------------------------------- /test/fixtures/simplest/index.html: -------------------------------------------------------------------------------- 1 | index page 2 | -------------------------------------------------------------------------------- /jetbrains.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avevlad/gulp-connect/HEAD/jetbrains.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .DS_Store 3 | node_modules 4 | index.js 5 | *.css 6 | *.iml 7 | *.tgz -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - "6" 5 | - "lts/*" 6 | - "stable" 7 | -------------------------------------------------------------------------------- /examples/multiple/dist/dist.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Dist path 6 | 7 | 8 |

Dist path

9 | 10 | -------------------------------------------------------------------------------- /examples/simplest/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Test gulp-connect 5 | 6 | 7 |

Simplest examples

8 | 9 | 10 | -------------------------------------------------------------------------------- /examples/livereload/app/stylus/main.styl: -------------------------------------------------------------------------------- 1 | $bg-color = #373737 2 | $text-color = #ffe42a 3 | body 4 | background: $bg-color 5 | font-family: Arial, 'sans-serif' 6 | color: $text-color 7 | .name 8 | text-align: center -------------------------------------------------------------------------------- /examples/multiple/app/stylus/main.styl: -------------------------------------------------------------------------------- 1 | $bg-color = #373737 2 | $text-color = #ffe42a 3 | body 4 | background: $bg-color 5 | font-family: Arial, 'sans-serif' 6 | color: $text-color 7 | .name 8 | text-align: center -------------------------------------------------------------------------------- /examples/simplest/gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var connect = require('../../index'); 3 | 4 | gulp.task('connect', function() { 5 | connect.server(); 6 | }); 7 | 8 | gulp.task('default', ['connect']); 9 | -------------------------------------------------------------------------------- /examples/livereload/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Test gulp-connect 5 | 6 | 7 | 8 | 9 |

Hello world!

10 | 11 | -------------------------------------------------------------------------------- /examples/multiple/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Test gulp-connect 5 | 6 | 7 | 8 | 9 |

Hello world!

10 | 11 | -------------------------------------------------------------------------------- /examples/livereload/path/path.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Test gulp-connect 5 | 6 | 7 | 8 | 9 |

Hello path.html

10 | 11 | -------------------------------------------------------------------------------- /.github/workflows/github-actions-node.yml: -------------------------------------------------------------------------------- 1 | name: Node.js CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | strategy: 10 | matrix: 11 | node-version: [20.x] 12 | 13 | steps: 14 | - uses: actions/checkout@v3 15 | - name: Use Node.js ${{ matrix.node-version }} 16 | uses: actions/setup-node@v3 17 | with: 18 | node-version: ${{ matrix.node-version }} 19 | cache: "npm" 20 | - run: npm ci 21 | - run: cd test && node --test test_node20.mjs 22 | -------------------------------------------------------------------------------- /examples/livereload/gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var stylus = require('gulp-stylus'); 3 | var connect = require('../../index'); 4 | 5 | gulp.task('connect', function () { 6 | connect.server({ 7 | root: ['app', 'path'], 8 | port: 8080, 9 | livereload: true 10 | }); 11 | }); 12 | 13 | function htmlTask(cb) { 14 | return gulp.src('./app/*.html') 15 | .pipe(connect.reload()) 16 | } 17 | 18 | function stylusTask(cb) { 19 | return gulp.src('./app/stylus/*.styl') 20 | .pipe(stylus()) 21 | .pipe(gulp.dest('./app/css')) 22 | .pipe(connect.reload()); 23 | } 24 | 25 | gulp.task('watch', function () { 26 | gulp.watch(['./app/*.html'], gulp.series(htmlTask)); 27 | gulp.watch(['./app/stylus/*.styl'], gulp.series(stylusTask)); 28 | }); 29 | 30 | gulp.task('default', gulp.parallel(['connect', htmlTask, stylusTask, 'watch'])); 31 | -------------------------------------------------------------------------------- /bin/make_certs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 5 | SSL_DIR="$DIR/../certs" 6 | CERT_NAME="server" 7 | 8 | # Set the wildcarded domain we want to use 9 | DOMAIN="gulp-connect" 10 | 11 | # A blank passphrase 12 | PASSPHRASE="gulp" 13 | 14 | # Set our CSR variables 15 | SUBJ=" 16 | C=US 17 | ST=MN 18 | O= 19 | localityName= 20 | commonName=$DOMAIN 21 | organizationalUnitName= 22 | emailAddress= 23 | " 24 | 25 | # Generate our Private Key, CSR and Certificate 26 | openssl genrsa -out "$SSL_DIR/$CERT_NAME.key" 2048 27 | openssl req -new -subj "$(echo -n "$SUBJ" | tr "\n" "/")" -key "$SSL_DIR/$CERT_NAME.key" -out "$SSL_DIR/$CERT_NAME.csr" -passin pass:$PASSPHRASE 28 | openssl x509 -req -days 1461 -in "$SSL_DIR/$CERT_NAME.csr" -signkey "$SSL_DIR/$CERT_NAME.key" -out "$SSL_DIR/$CERT_NAME.crt" 29 | 30 | # Not needed anymore 31 | rm -rf "$SSL_DIR/$CERT_NAME.csr" -------------------------------------------------------------------------------- /examples/multiple/gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var stylus = require('gulp-stylus'); 3 | var connect = require('../../index'); 4 | 5 | gulp.task('connectDev', function () { 6 | connect.server({ 7 | root: ['app', 'tmp'], 8 | port: 8000, 9 | livereload: true 10 | }); 11 | }); 12 | 13 | gulp.task('connectDist', function () { 14 | connect.server({ 15 | root: 'dist', 16 | port: 8001, 17 | livereload: true 18 | }); 19 | }); 20 | 21 | function htmlTask(cb) { 22 | return gulp.src('./app/*.html') 23 | .pipe(connect.reload()); 24 | } 25 | 26 | function stylusTask(cb) { 27 | return gulp.src('./app/stylus/*.styl') 28 | .pipe(stylus()) 29 | .pipe(gulp.dest('./app/css')) 30 | .pipe(connect.reload()); 31 | } 32 | 33 | gulp.task('watch', function () { 34 | gulp.watch(['./app/*.html'], gulp.series(htmlTask)); 35 | gulp.watch(['./app/stylus/*.styl'], gulp.series(stylusTask)); 36 | }); 37 | 38 | gulp.task('default', ['connectDist', 'connectDev', 'watch']); 39 | -------------------------------------------------------------------------------- /certs/server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIC3jCCAcYCCQCMHA19q2ADHzANBgkqhkiG9w0BAQUFADAxMQswCQYDVQQGEwJV 3 | UzELMAkGA1UECBMCTU4xFTATBgNVBAMTDGd1bHAtY29ubmVjdDAeFw0xNjAzMjgx 4 | NTI2MTZaFw0yMDAzMjgxNTI2MTZaMDExCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJN 5 | TjEVMBMGA1UEAxMMZ3VscC1jb25uZWN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A 6 | MIIBCgKCAQEAwqpQefV78UzVvxw/WwItwdhSatcj1RhxWXgtImOemZs5UzvyvHno 7 | M0R5BdVXfTnGJiTJ2TPdzJPc2RoT43V6PaD7UEjecQLBBF+ZW8XKVABvUIYqy8pO 8 | AN052I8hkCTyARaFiQssPzkXoqDZzAAZPi/dIBaIGnqBOJUJvmem2hJL5MIhcsxZ 9 | /+jrLFl4zh9DWxxo1S+rRu1Mm9QZZRpAKD9F41eUUX1jEP1lSkGHsfT3zpKPaHqX 10 | GKvc2VHk9MiKydb1xeoJNAlda1gYGPIfGWdIgU15FcOwYIpv0r95BgejcLF+u3p8 11 | c1cWMces+Vh0FLXjIHj2TjKt5tsNHgVnXQIDAQABMA0GCSqGSIb3DQEBBQUAA4IB 12 | AQCSV038ppOomwRqNKJuTXVJSzt38ElSsAJMpDdLu4CjsoiZC7DNZPkJ8pZhl7fS 13 | NMUlaKzCpylKQ+0iAgKEsrogdzDSmCtez/vk53zvtvfK0MDmFKaJNQRQwEzXeN/7 14 | RFM/yqyNYoj9deYwyA/BDlx1KWb93n+sAa9Yk7X9PcLZ6PObStBgDkqPFBCrJz2y 15 | 1UoXg3/Ikh2GCS8ihvdqsxcE0Vum7bce/N19HaIRr1uSgmA5NNdDy/wDEvPvZlpU 16 | TNPcPQgmWZq3tfj24EgDwCyfSxce5LffFnald9uPnIpuB3KnuwIEqTMiwJzzXiLy 17 | +vNW6OaLaex4B1l5WaTj0GsL 18 | -----END CERTIFICATE----- 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Vladislav Derjavin 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /test/test_node20.mjs: -------------------------------------------------------------------------------- 1 | import { describe, it, after } from 'node:test'; 2 | import assert from 'node:assert'; 3 | import request from 'supertest'; 4 | import connect from '../index.js'; 5 | 6 | var portCounter = 36000; 7 | console.log("port", portCounter); 8 | 9 | describe('Simple', async () => { 10 | after(() => { 11 | connect.serverClose() 12 | console.log("Close server"); 13 | }); 14 | 15 | it('is a subtest', async () => { 16 | return new Promise((resolve, reject) => { 17 | var port = portCounter++; 18 | console.log("port in subtest", port); 19 | connect.server({ 20 | port: port 21 | }, () => { 22 | console.log("ok"); 23 | request('http://localhost:' + port) 24 | .get('/fixtures/simplest/test.txt') 25 | .expect(/Hello world/) 26 | .expect(200) 27 | .end((err, res) => err ? reject(err) : resolve()); 28 | }); 29 | }); 30 | }); 31 | 32 | it('Implicit /index.html', () => { 33 | return new Promise((resolve, reject) => { 34 | var port = portCounter++; 35 | connect.server({ 36 | port: port 37 | }, function () { 38 | request('http://localhost:' + port) 39 | .get('/fixtures/simplest/') 40 | .expect(/index page/) 41 | .expect(200) 42 | .end((err, res) => err ? reject(err) : resolve()); 43 | }); 44 | }); 45 | }); 46 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gulp-connect", 3 | "version": "5.7.0", 4 | "description": "Gulp plugin to run a webserver (with LiveReload)", 5 | "license": "MIT", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/avevlad/gulp-connect" 9 | }, 10 | "authors": [ 11 | { 12 | "name": "Vladislav Derjavin", 13 | "email": "dev@vld.me", 14 | "url": "http://vld.me/" 15 | }, 16 | { 17 | "name": "Johannes Schickling", 18 | "email": "schickling.j@gmail.com", 19 | "url": "http://schickling.me/" 20 | }, 21 | { 22 | "name": "Justin Chase", 23 | "email": "justin.m.chase@gmail.com", 24 | "url": "http://justinmchase.com" 25 | }, 26 | { 27 | "name": "Contributors", 28 | "homepage": "https://github.com/AveVlad/gulp-connect/graphs/contributors" 29 | } 30 | ], 31 | "scripts": { 32 | "test": "mocha", 33 | "pretest": "coffee -o . -bc src/", 34 | "prepublish": "coffee -o . -bc src/" 35 | }, 36 | "engines": { 37 | "node": ">=0.10.0" 38 | }, 39 | "keywords": [ 40 | "gulpfriendly", 41 | "connect", 42 | "livereload", 43 | "webserver", 44 | "server" 45 | ], 46 | "dependencies": { 47 | "ansi-colors": "^2.0.5", 48 | "connect": "^3.6.6", 49 | "connect-livereload": "^0.6.0", 50 | "map-stream": "^0.0.7", 51 | "fancy-log": "^1.3.2", 52 | "send": "^0.16.2", 53 | "serve-index": "^1.9.1", 54 | "serve-static": "^1.13.2", 55 | "tiny-lr": "^1.1.1" 56 | }, 57 | "devDependencies": { 58 | "coffee-script": "^1.12.7", 59 | "gulp": "^4.0.0", 60 | "gulp-stylus": "^2.7.0", 61 | "mocha": "^5.2.0", 62 | "supertest": "^3.1.0" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /certs/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpQIBAAKCAQEAwqpQefV78UzVvxw/WwItwdhSatcj1RhxWXgtImOemZs5Uzvy 3 | vHnoM0R5BdVXfTnGJiTJ2TPdzJPc2RoT43V6PaD7UEjecQLBBF+ZW8XKVABvUIYq 4 | y8pOAN052I8hkCTyARaFiQssPzkXoqDZzAAZPi/dIBaIGnqBOJUJvmem2hJL5MIh 5 | csxZ/+jrLFl4zh9DWxxo1S+rRu1Mm9QZZRpAKD9F41eUUX1jEP1lSkGHsfT3zpKP 6 | aHqXGKvc2VHk9MiKydb1xeoJNAlda1gYGPIfGWdIgU15FcOwYIpv0r95BgejcLF+ 7 | u3p8c1cWMces+Vh0FLXjIHj2TjKt5tsNHgVnXQIDAQABAoIBAHZxsq3CVDuqCJRT 8 | 16jEVpsPyHYBmWfbi3xoxu2Zt7K249ZlL3jGpCYtj4WhHa6wLISg9cAW8um7PjFK 9 | 0Lchj9NX1BIxhEc26cKDeIiCfG1IJnLUb823tmaINN4zmLRgBm/rCC0ugymkz2DA 10 | hkS9+p3/+YfAreeOinmnFVqpLriYQQ7gaq0aX93nheJaW7FSlPfwjHD7RLpyYmfN 11 | TOczgb5n0rlqLuENpoyfX6XC5DXT5qfKkeEIm+wrqK9KXW5jlBWFxsOY3aEFJpae 12 | THjAxtoyxHb+TXPi4oQd7JmFPz+oaP+IqQ3ajqsLRWqk9jvTbczNfI0L6AUoCNwF 13 | cGjC4oECgYEA7y0/lQqVdXs4XQeN2h4ugx0v9oEoYcMIDGkNrT8sNIvg3rjYUsNq 14 | 0tjY1bIr6366i1bBU1x2Q1r94r36i0NBHhDONEefHfOA7+U6PkNqwekKO22th21J 15 | YiMq8I1hCX7/dueRB0x1e5PsH9Cd0ViqonApzi4ViMXgH/4thh4vYckCgYEA0FuR 16 | R1G2XChmrScUTkLDBssTHh1ve+nMQlDSdi+ICjY7HKlZOQIxOjQmbwz8w+v+Bayv 17 | C1XUbbKxAG5ESp1dMQlL9SBOw1V4OfyFAwEtE7nkn0+LEEejRlzNUwC9ukz496ll 18 | O5WECX2kose2bCW9coYlqbFJo4EUmihfyts/QvUCgYEAjG3Fyi1nqY3KfxyjVfCo 19 | oITvl00oN5Y6UIcR56mSu9txEXP4V0eznc6CKZ4uGVvUDWHHqrk8Grn/HTUsOL4p 20 | Koz1dU6kBwLkrnWpZ8tPkEDSqCfyXfR1xLAfUxO/aMLvHZJC3cUvqXolhlyHfNYR 21 | gcRXV15JeH+t4zrq4bcxXLkCgYEAn64tbs0Oxx4nYnhatYew05n6ss+4SuzpH8G1 22 | KeJPjhP1xYd725b0hzjEY8M+TCCmc07eb/99Z/zUbhITwGNNsnD/h4MVGdp842PF 23 | xIG4PC/xKK7bUsS0Ow6QRRaCUS8Adz2ZWcrCgEuMSzxncAaUZBqu7pfyaZ5ZXbOn 24 | hl3046kCgYEAzOan7eIRg6UlajCC6WO9Io8ESNTk4MP29MVDAK1P9qK9hq1V1KKR 25 | 7azygnynSjAHVct/07cHaKT/qJhICWs3pPEUR7kRKTQcO0mr69LS2vGhYycV6WHz 26 | b/q5EbNFLSrck+CghzBJJpYxe+6sE6/xzBr3RhgVedcqK9F32pejmPE= 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | gulp-connect [![](http://img.shields.io/npm/dm/gulp-connect.svg?style=flat-square)](https://www.npmjs.org/package/gulp-connect) [![](http://img.shields.io/npm/v/gulp-connect.svg?style=flat-square)](https://www.npmjs.org/package/gulp-connect) [![Join the chat at https://gitter.im/AveVlad/gulp-connect](https://badges.gitter.im/AveVlad/gulp-connect.svg)](https://gitter.im/AveVlad/gulp-connect?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 2 | ============== 3 | 4 | > Gulp plugin to run a webserver (with LiveReload) 5 | 6 | ## Sponsors 7 | 8 | gulp-connect is sponsored by [JetBrains](https://www.jetbrains.com/)! 9 | 10 | 11 | 12 | 13 | ## Install 14 | 15 | ``` 16 | npm install --save-dev gulp-connect 17 | ``` 18 | 19 | ## Usage 20 | 21 | ```js 22 | var gulp = require('gulp'); 23 | var connect = require('gulp-connect'); 24 | 25 | gulp.task('connect', function() { 26 | connect.server(); 27 | }); 28 | 29 | gulp.task('default', ['connect']); 30 | ``` 31 | 32 | #### LiveReload 33 | ```js 34 | var gulp = require('gulp'); 35 | var connect = require('gulp-connect'); 36 | 37 | gulp.task('connect', function() { 38 | connect.server({ 39 | root: 'app', 40 | livereload: true 41 | }); 42 | }); 43 | 44 | gulp.task('html', function () { 45 | gulp.src('./app/*.html') 46 | .pipe(gulp.dest('./app')) 47 | .pipe(connect.reload()); 48 | }); 49 | 50 | gulp.task('watch', function () { 51 | gulp.watch(['./app/*.html'], ['html']); 52 | }); 53 | 54 | gulp.task('default', ['connect', 'watch']); 55 | ``` 56 | 57 | 58 | #### Start and stop server 59 | 60 | ```js 61 | gulp.task('jenkins-tests', function() { 62 | connect.server({ 63 | port: 8888 64 | }); 65 | // run some headless tests with phantomjs 66 | // when process exits: 67 | connect.serverClose(); 68 | }); 69 | ``` 70 | 71 | 72 | #### Multiple server 73 | 74 | ```js 75 | var gulp = require('gulp'); 76 | var connect = require('gulp-connect'); 77 | var stylus = require('gulp-stylus'); 78 | 79 | gulp.task('connectDev', function () { 80 | connect.server({ 81 | name: 'Dev App', 82 | root: ['app', 'tmp'], 83 | port: 8000, 84 | livereload: true 85 | }); 86 | }); 87 | 88 | gulp.task('connectDist', function () { 89 | connect.server({ 90 | name: 'Dist App', 91 | root: 'dist', 92 | port: 8001, 93 | livereload: true 94 | }); 95 | }); 96 | 97 | gulp.task('html', function () { 98 | gulp.src('./app/*.html') 99 | .pipe(gulp.dest('./app')) 100 | .pipe(connect.reload()); 101 | }); 102 | 103 | gulp.task('stylus', function () { 104 | gulp.src('./app/stylus/*.styl') 105 | .pipe(stylus()) 106 | .pipe(gulp.dest('./app/css')) 107 | .pipe(connect.reload()); 108 | }); 109 | 110 | gulp.task('watch', function () { 111 | gulp.watch(['./app/*.html'], ['html']); 112 | gulp.watch(['./app/stylus/*.styl'], ['stylus']); 113 | }); 114 | 115 | gulp.task('default', ['connectDist', 'connectDev', 'watch']); 116 | ``` 117 | 118 | #### http2 support 119 | 120 | If the [http2](https://www.npmjs.com/package/http2) package is installed and you use an https connection to gulp connect then http 2 will be used in preference to http 1. 121 | 122 | ## API 123 | 124 | #### options.root 125 | 126 | Type: `Array or String` 127 | Default: `Directory with gulpfile` 128 | 129 | The root path 130 | 131 | #### options.port 132 | 133 | Type: `Number` 134 | Default: `8080` 135 | 136 | The connect webserver port 137 | 138 | #### options.host 139 | 140 | Type: `String` 141 | Default: `localhost` 142 | 143 | #### options.name 144 | 145 | Type: `String` 146 | Default: `Server` 147 | 148 | The name that will be output when the server starts/stops. 149 | 150 | #### options.https 151 | 152 | Type: `Object` 153 | Default: `false` 154 | 155 | Can be any options documented at https://nodejs.org/api/https.html#https_https_createserver_options_requestlistener 156 | 157 | When https is just set to `true` (boolean), then internally some defaults will be used. 158 | 159 | #### options.livereload 160 | 161 | Type: `Object or Boolean` 162 | Default: `false` 163 | 164 | #### options.livereload.port 165 | 166 | Type: `Number` 167 | Default: `35729` 168 | 169 | Overrides the hostname of the script livereload injects in index.html 170 | 171 | #### options.livereload.hostname 172 | 173 | Type: `String` 174 | Default: 'undefined' 175 | 176 | #### options.fallback 177 | 178 | Type: `String` 179 | Default: `undefined` 180 | 181 | Fallback file (e.g. `index.html`) 182 | 183 | #### options.middleware 184 | 185 | Type: `Function` 186 | Default: `[]` 187 | 188 | #### options.debug 189 | 190 | Type: `Boolean` 191 | Default: `false` 192 | 193 | #### options.index 194 | 195 | Type: `Boolean or String of a new index pass or Array of new indexes in preferred order` 196 | Default: `true` 197 | 198 | ```js 199 | gulp.task('connect', function() { 200 | connect.server({ 201 | root: "app", 202 | middleware: function(connect, opt) { 203 | return [ 204 | // ... 205 | ] 206 | } 207 | }); 208 | }); 209 | ``` 210 | 211 | ## Contributing 212 | 213 | To contribute to this project, you must have CoffeeScript installed: `npm install -g coffee-script`. 214 | 215 | Then, to build the `index.js` file, run `coffee -o . -bc src/`. Run `npm test` to run the tests. 216 | 217 | ## Contributors 218 | 219 | * [AveVlad](https://github.com/AveVlad) 220 | * [schickling](https://github.com/schickling) 221 | * [justinmchase](https://github.com/justinmchase) 222 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | var request = require('supertest'); 2 | var connect = require('../index'); 3 | require('mocha'); 4 | 5 | var portCounter = 35000; 6 | describe('gulp-connect', function () { 7 | describe('Simple', function() { 8 | after(function() { 9 | connect.serverClose(); 10 | }); 11 | it('Explicit /test.txt', function (done) { 12 | var port = portCounter++; 13 | connect.server({ 14 | port: port 15 | }, function() { 16 | request('http://localhost:' + port) 17 | .get('/fixtures/simplest/test.txt') 18 | .expect(/Hello world/) 19 | .expect(200) 20 | .end(function (err, res) { 21 | done(err); 22 | }); 23 | }); 24 | }); 25 | it('Implicit /index.html', function (done) { 26 | var port = portCounter++; 27 | connect.server({ 28 | port: port 29 | }, function() { 30 | request('http://localhost:' + port) 31 | .get('/fixtures/simplest/') 32 | .expect(/index page/) 33 | .expect(200) 34 | .end(function (err, res) { 35 | done(err); 36 | }); 37 | }); 38 | }) 39 | }) 40 | }); 41 | describe('Self Start / Stop', function() { 42 | after(function() { 43 | connect.serverClose(); 44 | }); 45 | it('Root string', function (done) { 46 | var port = portCounter++; 47 | connect.server({ 48 | port: port, 49 | root: __dirname + "/fixtures" 50 | }); 51 | request('http://localhost:' + port) 52 | .get('/multiple/app/index.html') 53 | .expect(/app test/) 54 | .end(function (err, res) { 55 | connect.serverClose(); 56 | if (err) return done(err); 57 | done() 58 | }); 59 | }); 60 | it('Root array', function (done) { 61 | var port = portCounter++; 62 | connect.server({ 63 | port: port, 64 | root: [__dirname + "/fixtures/multiple/app", __dirname + "/fixtures/multiple/dist"] 65 | }); 66 | request('http://localhost:' + port) 67 | .get('/index.html') 68 | .expect(/app test/) 69 | .expect(200) 70 | .end(function (err) { 71 | if (err) return done(err); 72 | }); 73 | request('http://localhost:' + port) 74 | .get('/dist.html') 75 | .expect(/dist test/) 76 | .expect(200) 77 | .end(function (err) { 78 | connect.serverClose(); 79 | if (err) return done(err); 80 | done() 81 | }); 82 | }); 83 | it('Port test', function (done) { 84 | connect.server({ 85 | root: __dirname + "/fixtures/multiple/app", 86 | port: 3333 87 | }); 88 | request('http://localhost:3333') 89 | .get('/index.html') 90 | .expect(/app test/) 91 | .end(function (err) { 92 | connect.serverClose(); 93 | if (err) return done(err); 94 | done() 95 | }); 96 | }); 97 | it('Https test', function (done) { 98 | //suppress invalid self-signed ssl certificate error 99 | process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0" 100 | var port = portCounter++; 101 | connect.server({ 102 | port: port, 103 | root: __dirname + "/fixtures/multiple/app", 104 | https: true 105 | }); 106 | request('https://localhost:' + port) 107 | .get('/index.html') 108 | .expect(/app test/) 109 | .end(function (err) { 110 | connect.serverClose(); 111 | if (err) return done(err); 112 | done() 113 | }); 114 | }); 115 | it('Livereload test', function (done) { 116 | var port = portCounter++; 117 | connect.server({ 118 | port: port, 119 | livereload: true 120 | }, function() { 121 | request('http://localhost:35729') 122 | .get('/') 123 | .expect('Content-Type', /json/) 124 | .end(function (err) { 125 | if (err) return done(err); 126 | request('http://localhost:35729') 127 | .get('/livereload.js') 128 | .expect(200) 129 | .end(function (err) { 130 | connect.serverClose(); 131 | if (err) return done(err); 132 | done(); 133 | }); 134 | }); 135 | }); 136 | }); 137 | it('Livereload https test', function (done) { 138 | var port = portCounter++; 139 | connect.server({ 140 | port: port, 141 | livereload: true, 142 | https: true 143 | }, function() { 144 | request('https://localhost:35729') 145 | .get('/') 146 | .expect('Content-Type', /json/) 147 | .end(function (err) { 148 | if (err) return done(err); 149 | request('https://localhost:35729') 150 | .get('/livereload.js') 151 | .expect(200) 152 | .end(function (err) { 153 | connect.serverClose(); 154 | if (err) return done(err); 155 | done(); 156 | }); 157 | }); 158 | }); 159 | }); 160 | it('Livereload port', function (done) { 161 | var port = portCounter++; 162 | var liveReloadPort = portCounter++; 163 | connect.server({ 164 | port: port, 165 | livereload: { 166 | port: liveReloadPort 167 | } 168 | }, 169 | function() { 170 | request('http://localhost:' + liveReloadPort) 171 | .get('/') 172 | .expect('Content-Type', /json/) 173 | .end(function (err) { 174 | connect.serverClose(); 175 | if (err) return done(err); 176 | done(); 177 | }); 178 | }); 179 | }); 180 | it('livereload closes', function (done) { 181 | this.timeout(10000); 182 | var port = portCounter++; 183 | var liveReloadPort = portCounter++; 184 | connect.server({ 185 | port: port, 186 | livereload: { 187 | port: liveReloadPort 188 | } 189 | }, 190 | function() { 191 | request('http://localhost:' + liveReloadPort) 192 | .get('/') 193 | .expect('Content-Type', /json/) 194 | .expect(200) 195 | .end(function (err) { 196 | if (err) return done(err); 197 | connect.serverClose(); 198 | setTimeout(function() { 199 | request('http://localhost:' + liveReloadPort) 200 | .get('/') 201 | .end(function (err) { 202 | if (err) return done(); 203 | done(new Error("Live reload is still running.")); 204 | })}, 100); 205 | }) 206 | }); 207 | }); 208 | it('Fallback test', function (done) { 209 | var port = portCounter++; 210 | connect.server({ 211 | port: port, 212 | fallback: __dirname + '/fixtures/simplest/index.html' 213 | }, function() { 214 | request('http://localhost:' + port) 215 | .get('/not/existing/path') 216 | .expect(/index page/) 217 | .expect('Content-Type', new RegExp('text/html; charset=UTF-8')) 218 | .expect(200) 219 | .end(function (err, res) { 220 | connect.serverClose(); 221 | if (err) return done(err); 222 | done() 223 | }); 224 | }); 225 | }) 226 | }); 227 | -------------------------------------------------------------------------------- /src/index.coffee: -------------------------------------------------------------------------------- 1 | path = require("path") 2 | fancyLog = require("fancy-log") 3 | colors = require("ansi-colors") 4 | mapStream = require("map-stream") 5 | http = require("http") 6 | https = require("https") 7 | fs = require("fs") 8 | connect = require("connect") 9 | serveStatic = require('serve-static') 10 | serveIndex = require('serve-index') 11 | liveReload = require("connect-livereload") 12 | send = require("send") 13 | tiny_lr = require("tiny-lr") 14 | apps = [] 15 | 16 | class ConnectApp 17 | constructor: (options, startedCallback) -> 18 | @name = options.name || "Server" 19 | @port = options.port || "8080" 20 | @root = options.root || path.dirname(module?.parent?.id || ".") 21 | @host = options.host || "localhost" 22 | @debug = options.debug || false 23 | @silent = options.silent || false 24 | @https = options.https || false 25 | @preferHttp1 = options.preferHttp1 || false; 26 | @livereload = options.livereload || false 27 | @middleware = options.middleware || undefined 28 | @startedCallback = startedCallback || () -> {}; 29 | @serverInit = options.serverInit || undefined 30 | @fallback = options.fallback || undefined 31 | @index = options.index 32 | @oldMethod("open") if options.open 33 | @sockets = [] 34 | @app = undefined 35 | @lr = undefined 36 | @state = "initializing" 37 | @run() 38 | 39 | run: -> 40 | if @state == "stopped" 41 | return 42 | 43 | @state = "starting" 44 | @log "Starting server..." 45 | @app = connect() 46 | 47 | @handlers().forEach (middleware) => 48 | if typeof (middleware) is "object" 49 | @app.use middleware[0], middleware[1] 50 | else 51 | @app.use middleware 52 | 53 | @app.use serveIndex(if typeof @root == "object" then @root[0] else @root) 54 | 55 | if @https 56 | 57 | # use some defaults when not set. do not touch when a key is already specified 58 | # see https://github.com/AveVlad/gulp-connect/issues/172 59 | if typeof (@https) is 'boolean' || (!@https.key && !@https.pfx) 60 | 61 | # change it into an object if it is not already one 62 | if !(typeof (@https) is "object") 63 | @https = {} 64 | 65 | @https.key = fs.readFileSync __dirname + '/certs/server.key' 66 | @https.cert = fs.readFileSync __dirname + '/certs/server.crt' 67 | @https.ca = fs.readFileSync __dirname + '/certs/server.crt' 68 | @https.passphrase = 'gulp' 69 | 70 | http2 = undefined 71 | if !@preferHttp1 72 | try 73 | http2 = require('http2') 74 | 75 | if http2 76 | @https.allowHTTP1 = true 77 | @server = http2.createSecureServer(@https, @app) 78 | else 79 | @server = https.createServer(@https, @app) 80 | 81 | else 82 | @server = http.createServer @app 83 | if @serverInit 84 | @serverInit @server 85 | @server.listen @port, @host, (err) => 86 | if err 87 | @log "Error on starting server: #{err}" 88 | else 89 | @log "#{@name} started http#{if @https then 's' else ''}://#{if @host == '0.0.0.0' then 'localhost' else @host}:#{@port}" 90 | 91 | stoped = false 92 | sockets = [] 93 | 94 | @server.on "close", => 95 | if (!stoped) 96 | stoped = true 97 | @log "#{@name} stopped" 98 | 99 | # Log connections and request in debug 100 | @server.on "connection", (socket) => 101 | @logDebug "Received incoming connection from #{socket.address().address}" 102 | @sockets.push socket 103 | socket.on "close", => 104 | @sockets.splice @sockets.indexOf(socket), 1 105 | 106 | @server.on "request", (request, response) => 107 | @logDebug "Received request #{request.method} #{request.url}" 108 | 109 | @server.on "error", (err) => 110 | @log err.toString() 111 | 112 | stopServer = => 113 | if (!stoped) 114 | @sockets.forEach (socket) => 115 | socket.destroy() 116 | 117 | @server.close() 118 | if @livereload 119 | @lr.close() 120 | process.nextTick( -> 121 | process.exit(0) 122 | ) 123 | 124 | process.on("SIGINT", stopServer) 125 | process.on("exit", stopServer) 126 | 127 | if @livereload 128 | tiny_lr.Server::error = -> 129 | if @https 130 | @lr = tiny_lr 131 | key: @https.key || fs.readFileSync __dirname + '/certs/server.key' 132 | cert: @https.cert || fs.readFileSync __dirname + '/certs/server.crt' 133 | else 134 | @lr = tiny_lr() 135 | 136 | @lr.listen @livereload.port 137 | @log "LiveReload started on port #{@livereload.port}" 138 | @state = "running"; 139 | @log "Running server" 140 | @startedCallback() 141 | 142 | close: -> 143 | if @state == "running" 144 | @log "Stopping server" 145 | if @livereload 146 | @lr.close() 147 | @server.close() 148 | @state = "stopped" 149 | @log "Stopped server" 150 | else if @state == "stopped" 151 | @log "Server has already been stopped." 152 | else 153 | @log "Ignoring stop as server is in " + @state + " state." 154 | 155 | handlers: -> 156 | steps = if @middleware then @middleware.call(this, connect, @) else [] 157 | if @livereload 158 | @livereload = {} if typeof @livereload is "boolean" 159 | @livereload.port = 35729 unless @livereload.port 160 | steps.unshift liveReload(@livereload) 161 | if @index is true then @index = "index.html" 162 | if typeof @root == "object" 163 | @root.forEach (path) -> 164 | steps.push serveStatic(path, {index: @index}) 165 | else 166 | steps.push serveStatic(@root, {index: @index}) 167 | if @fallback 168 | steps.push (req, res) => 169 | fallbackPath = @fallback 170 | 171 | if typeof @fallback is "function" 172 | fallbackPath = @fallback(req, res) 173 | 174 | send(req, fallbackPath).pipe(res); 175 | 176 | return steps 177 | 178 | log: (text) -> 179 | if !@silent 180 | fancyLog colors.green(text) 181 | 182 | logWarning: (text) -> 183 | if !@silent 184 | fancyLog colors.yellow(text) 185 | 186 | logDebug: (text) -> 187 | if @debug 188 | fancyLog colors.blue(text) 189 | 190 | oldMethod: (type) -> 191 | text = 'does not work in gulp-connect v 2.*. Please read "readme" https://github.com/AveVlad/gulp-connect' 192 | switch type 193 | when "open" then @logWarning("Option open #{text}") 194 | 195 | module.exports = 196 | server: (options = {}, startedCallback = null) -> 197 | app = new ConnectApp(options, startedCallback) 198 | apps.push(app) 199 | app 200 | reload: -> 201 | mapStream (file, callback) -> 202 | apps.forEach (app) => 203 | if app.livereload and typeof app.lr == "object" 204 | app.lr.changed body: 205 | files: file.path 206 | callback null, file 207 | serverClose: -> 208 | apps.forEach((app) -> do app.close) 209 | apps = [] 210 | --------------------------------------------------------------------------------