diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..8ccac4c --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,28 @@ +name: Test + +on: + pull_request: + branches: [main] + +jobs: + test: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [20.x, 22.x, 24.x] + + steps: + - uses: actions/checkout@v4 + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Run tests + run: npm test \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 3de8428..0000000 --- a/.travis.yml +++ /dev/null @@ -1,6 +0,0 @@ -language: node_js -node_js: - - "8" - - "11" - - diff --git a/README.md b/README.md index 51a23b2..b670eda 100644 --- a/README.md +++ b/README.md @@ -224,6 +224,14 @@ When submitting code, please keep commits small, and do not modify the README fi # Changelog +### 0.10.2 +* Dependencies: Update Chokidar, opts, and WS to latest versions. +* CLI: Fix `--corp` flag not working as intended +* CLI: Fix `--cors` flag no longer sets CORP header. +* CLI: Fix `--originalpath` flag sets proper value. +* CLI: Fix some typos in CLI help +* Add tests for CLI arguments and reworked CLI code to allow for testing. + ### 0.10.1 * Fix errant debug message in CLI diff --git a/lib/command.coffee b/lib/command.coffee index 695274f..10c8d9b 100644 --- a/lib/command.coffee +++ b/lib/command.coffee @@ -1,9 +1,30 @@ +pjson = require('../package.json') +livereload = require './livereload' +resolve = require('path').resolve + runner = -> - pjson = require('../package.json') - version = pjson.version - livereload = require './livereload' - resolve = require('path').resolve - opts = require 'opts' + res = parseArgsAndCreateServer(true) + server = res.server + path = res.path + + console.log "Starting LiveReload v#{pjson.version} for #{path} on #{server.host}:#{server.port}." + + server.on 'error', (err) -> + + if err.code == "EADDRINUSE" + console.log("The port LiveReload wants to use is used by something else.") + else + throw err + + process.exit(1) + + server.watch(path) + +# Parse the arguments and create the server. +# shouldListen option exists so we can start the server under CLI, but not when +# we use this from the entrypoint for tests +parseArgsAndCreateServer = (shouldListen = true) -> + opts = require 'opts' args = [ { @@ -19,7 +40,7 @@ runner = -> description: "Show the version" required: false callback: -> - console.log version + console.log pjson.version process.exit(1) } { @@ -52,7 +73,7 @@ runner = -> { short: "e" long: "exts", - description: "A comma-separated list of extensions that should trigger a reload when changed. Replaces default extentions", + description: "A comma-separated list of extensions that should trigger a reload when changed. Replaces default extensions", required: false, value: true } @@ -86,7 +107,7 @@ runner = -> { short: "op" long: "originalpath", - description: "Set a URL you use for development, e.g 'http:/domain.com', then LiveReload will proxy this url to local path." + description: "Set a URL you use for development, e.g 'http://domain.com', then LiveReload will proxy this url to local path." required: false, value: true } @@ -94,35 +115,35 @@ runner = -> short: "cp" long: "corp" description: "Enable CORP Header with cross-origin", - require: false + required: false } { short: "cs" long: "cors" description: "Enable CORS Header for all or specific origins", - require: false, + required: false, value: true } ] - opts.parse(options.reverse(), args, true) + opts.parse(options.reverse(), args, true) path = (opts.arg('path') || '.') .split(/\s*,\s*/) .map((x)->resolve(x)) debug = opts.get('debug') || false - port = opts.get('port') || 35729 + port = parseInt(opts.get('port')) || 35729 host = opts.get('bind') || 'localhost' - exclusions = if opts.get('exclusions') then opts.get('exclusions' ).split(',' ).map((s) -> new RegExp(s)) else [] - exts = if opts.get('exts') then opts.get('exts').split(',').map((ext) -> ext.trim()) else [] - extraExts = if opts.get('extraExts') then opts.get('extraExts').split(',').map((ext) -> ext.trim()) else [] - filesToReload = if opts.get('filesToReload') then opts.get('filesToReload').split(',').map((file) -> file.trim()) else [] + exclusions = if opts.get('exclusions') then opts.get('exclusions').split(',').map((s) -> new RegExp(s)) else [] + exts = if opts.get('exts') then opts.get('exts').split(',').map((ext) -> ext.trim()) else [] + extraExts = if opts.get('extraExts') then opts.get('extraExts').split(',').map((ext) -> ext.trim()) else [] + filesToReload = if opts.get('filesToReload') then opts.get('filesToReload').split(',').map((file) -> file.trim()) else [] usePolling = opts.get('usepolling') || false - wait = opts.get('wait') || 0 - originalPath = opts.get('originalPath') || '' + wait = parseInt(opts.get('wait')) || 0 + originalPath = opts.get('originalpath') || '' cors = opts.get('cors') || false - corp = opts.get('cors') || false + corp = opts.get('corp') || false server = livereload.createServer({ port: port @@ -137,18 +158,40 @@ runner = -> originalPath: originalPath cors: cors corp: corp + noListen: not shouldListen }) - console.log "Starting LiveReload v#{version} for #{path} on #{host}:#{port}." - server.on 'error', (err) -> - if err.code == "EADDRINUSE" - console.log("The port LiveReload wants to use is used by something else.") - else - throw err - process.exit(1) + { + server: server + path: path + } - server.watch(path) + +# This is the entrypoint that tests use to verify our option parsing. +# It doesn't start the server. +# The opts library directly parses process.argv. That means +# when we run tests with Mocha it gets the Mocha args, not our args. +# So this is hacky indirection that lets us send our own args +# from the tests and still run them through the same parsing function +# the CLI uses. +createServerFromArgs = (testArgv) -> + # Save original process.argv and parse with test arguments + originalArgv = process.argv + + # replace the args with our test args + process.argv = ['node', 'test'].concat(testArgv) + + # Reset opts internal state by requiring a fresh instance + delete require.cache[require.resolve('opts')] + + try + result = parseArgsAndCreateServer(false) # false = don't listen, for testing + return result + finally + # Always restore original process.argv + process.argv = originalArgv module.exports = run: runner + createServerFromArgs: createServerFromArgs diff --git a/lib/command.js b/lib/command.js index 91c8e25..550741e 100644 --- a/lib/command.js +++ b/lib/command.js @@ -1,13 +1,35 @@ // Generated by CoffeeScript 2.7.0 (function() { - var runner; + var createServerFromArgs, livereload, parseArgsAndCreateServer, pjson, resolve, runner; + + pjson = require('../package.json'); + + livereload = require('./livereload'); + + resolve = require('path').resolve; runner = function() { - var args, corp, cors, debug, exclusions, extraExts, exts, filesToReload, host, livereload, options, opts, originalPath, path, pjson, port, resolve, server, usePolling, version, wait; - pjson = require('../package.json'); - version = pjson.version; - livereload = require('./livereload'); - resolve = require('path').resolve; + var path, res, server; + res = parseArgsAndCreateServer(true); + server = res.server; + path = res.path; + console.log(`Starting LiveReload v${pjson.version} for ${path} on ${server.host}:${server.port}.`); + server.on('error', function(err) { + if (err.code === "EADDRINUSE") { + console.log("The port LiveReload wants to use is used by something else."); + } else { + throw err; + } + return process.exit(1); + }); + return server.watch(path); + }; + + // Parse the arguments and create the server. + // shouldListen option exists so we can start the server under CLI, but not when + // we use this from the entrypoint for tests + parseArgsAndCreateServer = function(shouldListen = true) { + var args, corp, cors, debug, exclusions, extraExts, exts, filesToReload, host, options, opts, originalPath, path, port, server, usePolling, wait; opts = require('opts'); args = [ { @@ -22,7 +44,7 @@ description: "Show the version", required: false, callback: function() { - console.log(version); + console.log(pjson.version); return process.exit(1); } }, @@ -56,7 +78,7 @@ { short: "e", long: "exts", - description: "A comma-separated list of extensions that should trigger a reload when changed. Replaces default extentions", + description: "A comma-separated list of extensions that should trigger a reload when changed. Replaces default extensions", required: false, value: true }, @@ -90,7 +112,7 @@ { short: "op", long: "originalpath", - description: "Set a URL you use for development, e.g 'http:/domain.com', then LiveReload will proxy this url to local path.", + description: "Set a URL you use for development, e.g 'http://domain.com', then LiveReload will proxy this url to local path.", required: false, value: true }, @@ -98,13 +120,13 @@ short: "cp", long: "corp", description: "Enable CORP Header with cross-origin", - require: false + required: false }, { short: "cs", long: "cors", description: "Enable CORS Header for all or specific origins", - require: false, + required: false, value: true } ]; @@ -113,7 +135,7 @@ return resolve(x); }); debug = opts.get('debug') || false; - port = opts.get('port') || 35729; + port = parseInt(opts.get('port')) || 35729; host = opts.get('bind') || 'localhost'; exclusions = opts.get('exclusions') ? opts.get('exclusions').split(',').map(function(s) { return new RegExp(s); @@ -128,10 +150,10 @@ return file.trim(); }) : []; usePolling = opts.get('usepolling') || false; - wait = opts.get('wait') || 0; - originalPath = opts.get('originalPath') || ''; + wait = parseInt(opts.get('wait')) || 0; + originalPath = opts.get('originalpath') || ''; cors = opts.get('cors') || false; - corp = opts.get('cors') || false; + corp = opts.get('corp') || false; server = livereload.createServer({ port: port, host: host, @@ -144,22 +166,42 @@ delay: wait, originalPath: originalPath, cors: cors, - corp: corp + corp: corp, + noListen: !shouldListen }); - console.log(`Starting LiveReload v${version} for ${path} on ${host}:${port}.`); - server.on('error', function(err) { - if (err.code === "EADDRINUSE") { - console.log("The port LiveReload wants to use is used by something else."); - } else { - throw err; - } - return process.exit(1); - }); - return server.watch(path); + return { + server: server, + path: path + }; + }; + + // This is the entrypoint that tests use to verify our option parsing. + // It doesn't start the server. + // The opts library directly parses process.argv. That means + // when we run tests with Mocha it gets the Mocha args, not our args. + // So this is hacky indirection that lets us send our own args + // from the tests and still run them through the same parsing function + // the CLI uses. + createServerFromArgs = function(testArgv) { + var originalArgv, result; + // Save original process.argv and parse with test arguments + originalArgv = process.argv; + // replace the args with our test args + process.argv = ['node', 'test'].concat(testArgv); + // Reset opts internal state by requiring a fresh instance + delete require.cache[require.resolve('opts')]; + try { + result = parseArgsAndCreateServer(false); // false = don't listen, for testing + return result; + } finally { + // Always restore original process.argv + process.argv = originalArgv; + } }; module.exports = { - run: runner + run: runner, + createServerFromArgs: createServerFromArgs }; }).call(this); diff --git a/package-lock.json b/package-lock.json index 4d0d50e..fe29edd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,18 @@ { "name": "livereload", - "version": "0.10.0", + "version": "0.10.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "livereload", - "version": "0.10.0", + "version": "0.10.2", "license": "MIT", "dependencies": { - "chokidar": "^3.5.0", + "chokidar": "^4.0.3", "livereload-js": "^4.0.2", - "opts": ">= 1.2.0", - "ws": "^7.4.3" + "opts": "^2.0.2", + "ws": "^8.4.3" }, "bin": { "livereload": "bin/livereload.js" @@ -85,18 +85,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -111,14 +99,6 @@ "dev": true, "license": "MIT" }, - "node_modules/binary-extensions": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", - "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", - "engines": { - "node": ">=8" - } - }, "node_modules/brace-expansion": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", @@ -129,18 +109,6 @@ "balanced-match": "^1.0.0" } }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/browser-stdout": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", @@ -191,101 +159,18 @@ } }, "node_modules/chokidar": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", - "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", - "dependencies": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.5.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.1" - } - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/cliui/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/cliui/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "readdirp": "^4.0.1" }, "engines": { - "node": ">=10" + "node": ">= 14.16.0" }, "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "url": "https://paulmillr.com/funding/" } }, "node_modules/coffeescript": { @@ -415,18 +300,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -481,19 +354,6 @@ "samsam": "~1.1" } }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -525,18 +385,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -557,25 +405,6 @@ "he": "bin/he" } }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -586,26 +415,6 @@ "node": ">=8" } }, - "node_modules/is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, "node_modules/is-plain-obj": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", @@ -779,34 +588,101 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/mocha/node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "node_modules/mocha/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/mocha/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", "dependencies": { - "readdirp": "^4.0.1" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" }, "engines": { - "node": ">= 14.16.0" + "node": ">=12" + } + }, + "node_modules/mocha/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/mocha/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, - "funding": { - "url": "https://paulmillr.com/funding/" + "engines": { + "node": ">=8" } }, - "node_modules/mocha/node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "node_modules/mocha/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, "engines": { - "node": ">= 14.18.0" + "node": ">=8" + } + }, + "node_modules/mocha/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" }, "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/mocha/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" } }, "node_modules/ms": { @@ -816,18 +692,11 @@ "dev": true, "license": "MIT" }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/opts": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/opts/-/opts-1.2.6.tgz", - "integrity": "sha1-0YXAQlz9652h0YKQi2W1wCOP67M=" + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/opts/-/opts-2.0.2.tgz", + "integrity": "sha512-k41FwbcLnlgnFh69f4qdUfvDQ+5vaSDnVPFI/y5XuhKRq97EnVVneO9F1ESVCdiVu4fCS2L8usX3mU331hB7pg==", + "license": "BSD-2-Clause" }, "node_modules/p-limit": { "version": "3.1.0", @@ -912,17 +781,6 @@ "dev": true, "license": "ISC" }, - "node_modules/picomatch": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.1.tgz", - "integrity": "sha512-ISBaA8xQNmwELC7eOjqFKMESB2VIqt4PPDD0nsS95b/9dZXvVKOlz9keMSnoGGKcOHXfTvDD6WMaRoSc9UuhRA==", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -934,14 +792,16 @@ } }, "node_modules/readdirp": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", - "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", - "dependencies": { - "picomatch": "^2.2.1" - }, + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "license": "MIT", "engines": { - "node": ">=8.10.0" + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" } }, "node_modules/require-directory": { @@ -1216,18 +1076,6 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, "node_modules/util": { "version": "0.10.3", "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", @@ -1362,16 +1210,16 @@ } }, "node_modules/ws": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", "license": "MIT", "engines": { - "node": ">=8.3.0" + "node": ">=10.0.0" }, "peerDependencies": { "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "utf-8-validate": ">=5.0.2" }, "peerDependenciesMeta": { "bufferutil": { @@ -1392,25 +1240,6 @@ "node": ">=10" } }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/yargs-parser": { "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", @@ -1437,51 +1266,6 @@ "node": ">=10" } }, - "node_modules/yargs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/yargs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 2b6f13b..2a1066b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "livereload", "description": "LiveReload server", - "version": "0.10.1", + "version": "0.10.2", "contributors": [ { "name": "Brian P. Hogan", @@ -18,10 +18,10 @@ }, "main": "./lib/livereload.js", "dependencies": { - "chokidar": "^3.5.0", + "chokidar": "^4.0.3", "livereload-js": "^4.0.2", - "opts": ">= 1.2.0", - "ws": "^7.4.3" + "opts": "^2.0.2", + "ws": "^8.4.3" }, "devDependencies": { "coffeescript": "^2.7.0", diff --git a/test/command.test.coffee b/test/command.test.coffee new file mode 100644 index 0000000..a35ee51 --- /dev/null +++ b/test/command.test.coffee @@ -0,0 +1,140 @@ +command = require '../lib/command' +should = require 'should' + +describe 'CLI argument parsing', -> + + it 'should create server with configuration options', -> + result = command.createServerFromArgs(['--port', '45001']) + result.server.config.port.should.equal(45001) + result.server.config.host.should.equal('localhost') + result.server.config.debug.should.equal(false) + result.server.config.usePolling.should.equal(false) + result.server.config.delay.should.equal(0) + result.server.config.originalPath.should.equal('') + result.server.config.cors.should.equal(false) + result.server.config.corp.should.equal(false) + result.path.should.be.an.Array() + + it 'should parse port option correctly', -> + result = command.createServerFromArgs(['--port', '8080']) + result.server.config.port.should.equal(8080) + + it 'should parse short port option correctly', -> + result = command.createServerFromArgs(['-p', '45003']) + result.server.config.port.should.equal(45003) + + it 'should parse host option correctly', -> + result = command.createServerFromArgs(['--bind', '0.0.0.0']) + result.server.config.host.should.equal('0.0.0.0') + + it 'should parse short host option correctly', -> + result = command.createServerFromArgs(['-b', '127.0.0.1']) + result.server.config.host.should.equal('127.0.0.1') + + it 'should parse debug flag correctly', -> + result = command.createServerFromArgs(['--debug']) + result.server.config.debug.should.equal(true) + + it 'should parse short debug flag correctly', -> + result = command.createServerFromArgs([ '-d']) + result.server.config.debug.should.equal(true) + + it 'should parse exts option correctly', -> + result = command.createServerFromArgs(['--exts', 'html,css,js']) + result.server.config.exts.should.eql(['html', 'css', 'js']) + + it 'should parse short exts option correctly', -> + result = command.createServerFromArgs(['-e', 'md,txt']) + result.server.config.exts.should.eql(['md', 'txt']) + + it 'should parse extraExts option correctly', -> + result = command.createServerFromArgs(['--extraExts', 'scss,less']) + expectedExts = ['scss', 'less', 'html', 'css', 'js', 'png', 'gif', 'jpg', 'php', 'php5', 'py', 'rb', 'erb', 'coffee'] + result.server.config.exts.should.eql(expectedExts) + + it 'should parse short extraExts option correctly', -> + result = command.createServerFromArgs(['-ee', 'vue']) + expectedExts = ['vue', 'html', 'css', 'js', 'png', 'gif', 'jpg', 'php', 'php5', 'py', 'rb', 'erb', 'coffee'] + result.server.config.exts.should.eql(expectedExts) + + it 'should parse filesToReload option correctly', -> + result = command.createServerFromArgs(['--filesToReload', 'index.html,app.js']) + result.server.config.filesToReload.should.eql(['index.html', 'app.js']) + + it 'should parse short filesToReload option correctly', -> + result = command.createServerFromArgs(['-f', 'config.json']) + result.server.config.filesToReload.should.eql(['config.json']) + + it 'should parse usepolling flag correctly', -> + result = command.createServerFromArgs(['--usepolling']) + result.server.config.usePolling.should.equal(true) + + it 'should parse short usepolling flag correctly', -> + result = command.createServerFromArgs(['-u']) + result.server.config.usePolling.should.equal(true) + + it 'should parse wait option correctly', -> + result = command.createServerFromArgs(['--wait', '1000']) + result.server.config.delay.should.equal(1000) + + it 'should parse short wait option correctly', -> + result = command.createServerFromArgs(['-w', '500']) + result.server.config.delay.should.equal(500) + + it 'should parse originalpath option correctly', -> + result = command.createServerFromArgs(['--originalpath', 'http://example.com']) + result.server.config.originalPath.should.equal('http://example.com') + + it 'should parse short originalpath option correctly', -> + result = command.createServerFromArgs(['-op', 'http://localhost:3000']) + result.server.config.originalPath.should.equal('http://localhost:3000') + + it 'should parse corp flag correctly', -> + result = command.createServerFromArgs(['--corp']) + result.server.config.corp.should.equal(true) + + it 'should parse short corp flag correctly', -> + result = command.createServerFromArgs(['-cp']) + result.server.config.corp.should.equal(true) + + it 'should parse cors option correctly', -> + result = command.createServerFromArgs(['--cors', 'http://localhost:8080']) + result.server.config.cors.should.equal('http://localhost:8080') + + it 'should parse short cors option correctly', -> + result = command.createServerFromArgs(['-cs', '*']) + result.server.config.cors.should.equal('*') + + + it 'should parse exclusions option correctly', -> + result = command.createServerFromArgs(['--port', '45025', '--exclusions', '\\.tmp,\\.log']) + result.server.config.exclusions.should.have.length(5) # 2 user + 3 defaults + result.server.config.exclusions[0].should.be.an.instanceOf(RegExp) + result.server.config.exclusions[1].should.be.an.instanceOf(RegExp) + + it 'should parse short exclusions option correctly', -> + result = command.createServerFromArgs(['--port', '45026', '-x', 'node_modules']) + result.server.config.exclusions.should.have.length(4) # 1 user + 3 defaults + result.server.config.exclusions[0].should.be.an.instanceOf(RegExp) + + it 'should handle multiple options correctly', -> + result = command.createServerFromArgs(['-p', '8080', '-d', '--cors', '*', '--exts', 'html,js']) + result.server.config.port.should.equal(8080) + result.server.config.debug.should.equal(true) + result.server.config.cors.should.equal('*') + result.server.config.exts.should.eql(['html', 'js']) + + it 'should handle path argument correctly', -> + result = command.createServerFromArgs(['./src']) + result.path.should.be.an.Array() + result.path[0].should.match(/\/src$/) + + it 'should handle multiple comma-separated paths correctly', -> + result = command.createServerFromArgs(['./src,./public']) + result.path.should.have.length(2) + result.path[0].should.match(/\/src$/) + result.path[1].should.match(/\/public$/) + + it 'should use default port when no port specified', -> + result = command.createServerFromArgs([]) + result.server.config.port.should.equal(35729) diff --git a/test/index.test.coffee b/test/index.test.coffee index bf0dea2..42ec498 100644 --- a/test/index.test.coffee +++ b/test/index.test.coffee @@ -9,18 +9,12 @@ sinon = require 'sinon' describe 'livereload config', -> - it 'should remove default exts when provided new exts', (done) -> - server = livereload.createServer({ port: 35729, exts: ["html"]}, -> - server.close() - done() - ) + it 'should remove default exts when provided new exts', -> + server = livereload.createServer({ port: 35729, exts: ["html"], noListen: true}) server.config.exts.should.eql(["html"]) - it 'should incldue default exts when provided extraExts', (done) -> - server = livereload.createServer({ port: 35729, extraExts: ["foobar"]}, -> - server.close() - done() - ) + it 'should incldue default exts when provided extraExts', -> + server = livereload.createServer({ port: 35729, extraExts: ["foobar"], noListen: true}) extensionsList = [ 'foobar', @@ -29,11 +23,8 @@ describe 'livereload config', -> ] server.config.exts.should.eql(extensionsList) - it 'extraExts must override exts if both are given', (done) -> - server = livereload.createServer({ port: 35729, exts: ["md"], extraExts: ["foobar"]}, -> - server.close() - done() - ) + it 'extraExts must override exts if both are given', -> + server = livereload.createServer({ port: 35729, exts: ["md"], extraExts: ["foobar"], noListen: true}) extensionsList = [ 'foobar', @@ -42,35 +33,22 @@ describe 'livereload config', -> ] server.config.exts.should.eql(extensionsList) - it 'should support filesToReload', (done) -> - server = livereload.createServer({ port: 35729, filesToReload: ["index.html"]}, -> - server.close() - done() - ) + it 'should support filesToReload', -> + server = livereload.createServer({ port: 35729, filesToReload: ["index.html"], noListen: true}) server.config.filesToReload.should.eql(["index.html"]) - it 'should support CORP headers', (done) -> - server = livereload.createServer({ corp: true }, -> - server.close() - done() - ) + it 'should support CORP headers', -> + server = livereload.createServer({ corp: true, noListen: true }) server.config.corp.should.eql true - it 'should support default CORS headers', (done) -> - server = livereload.createServer({ cors: true }, -> - server.close() - done() - ) + it 'should support default CORS headers', -> + server = livereload.createServer({ cors: true, noListen: true }) server.config.cors.should.eql true - it 'should support a specific CORS headers', (done) -> - server = livereload.createServer({ cors: 'localhost' }, -> - server.close() - done() - ) + it 'should support a specific CORS headers', -> + server = livereload.createServer({ cors: 'localhost', noListen: true }) server.config.cors.should.eql 'localhost' - describe 'livereload headers', -> it 'should receive the correct CORP headers', (done) -> server = livereload.createServer({ corp: true }, -> @@ -143,7 +121,8 @@ describe 'livereload http file serving', -> ws.on 'message', (data, flags) -> console.log "hello" - data.should.equal JSON.stringify { + message = data.toString() + message.should.equal JSON.stringify { command: 'hello', protocols: [ 'http://livereload.com/protocols/official-7', @@ -187,9 +166,6 @@ describe 'livereload http file serving', -> fileContents = fs.readFileSync('./node_modules/livereload-js/dist/livereload.js').toString() - # allow us to use our self-signed cert for testing - # process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0' - fetch 'https://localhost:35729/livereload.js?snipver=1' .then (response) -> response.status.should.equal 200 @@ -197,7 +173,6 @@ describe 'livereload http file serving', -> .then (body) -> fileContents.should.equal body .finally -> - # delete process.env.NODE_TLS_REJECT_UNAUTHORIZED server.config.server.close() it 'should support passing a callback to the websocket server', (done) ->