General update, removed short-hand argument options, used newer smaller depencies, turned into ecmamodule script and generally made smaller

master
Jonatan Nilsson 2021-10-12 13:43:05 +00:00
parent 628718d9be
commit 0d175c5600
13 changed files with 269 additions and 296 deletions

1
.npmrc Normal file
View File

@ -0,0 +1 @@
package-lock=false

View File

@ -32,7 +32,7 @@ How to use
```bash
npm install [-g] spserver
spserver -f ./myfile.html -s ./public -p 3000
spserver --file ./myfile.html --serve ./public -p 3000
```
Options
@ -42,23 +42,23 @@ Options
By default, spserver will use the settings located in `config.json`. You can also override them or run it directly using only the commands below.
`--config, -c` Location of the config file for the server [default: config.json]
`--config` Location of the config file for the server [default: config.json]
`--port, -p` The port server should bind to [default: 3001 or 80 in production mode]
`--port` The port server should bind to [default: 3001 or 80 in production mode]
`--file, -f` Single static file the server should serve on all unknown requests
`--file` Single static file the server should serve on all unknown requests
`--bunyan, -b` Use bunyan instead of console to log to [default: true in production mode]
`--bunyan` Use bunyan instead of console to log to [default: true in production mode]
`--template, -t` Parse the static file as lodash template with all options/settings being passed to it
`--template` Parse the static file as lodash template with all options/settings being passed to it
`--name, -n` The name for this server for logging [default: spserver]
`--name` The name for this server for logging [default: spserver]
`--serve, -s` Folder path to serve static files from [default: public]
`--serve` Folder path to serve static files from [default: public]
`--prod, -P` Force run the server in production mode
`--prod` Force run the server in production mode
`--debug, -d` Force run the server in development mode
`--debug` Force run the server in development mode
Config
======
@ -91,7 +91,7 @@ Any of the settings in the `config.json` file can be overridden using the CLI op
Template
========
spserver can also help provide any additional info to your single file thanks to lodash.template. If template mode is specified, it will parse the single file first through lodash.template with the whole config file. This can allow you to specify configuration in your config file and expose them in your single file.
spserver can also help provide any additional info to your single file thanks to lodash: `template`. If template mode is specified, it will parse the single file first through `lodash.template` with the whole config file. This can allow you to specify configuration in your config file and expose them in your single file.
Example:
@ -115,4 +115,4 @@ Example:
```
Then you can run it like this:
`spserver -c ./config.json -t -f ./base.html -s ./public`
`spserver --config ./config.json --template --file ./base.html --serve ./public`

39
bin.js
View File

@ -1,39 +0,0 @@
#!/usr/bin/env node
'use strict';
var config = require('./lib/config');
var server = require('./lib/spserver');
var env = config.get('NODE_ENV');
//Check if we any
var displayHelp = config.get('help');
if (!config.get('file') && !config.get(env + ':file') &&
!config.get('serve') && !config.get(env + ':serve')) {
displayHelp = true;
}
if (displayHelp) {
console.log('Run static server for static files, simple servers or pure MVVM projects.');
console.log('Specifying either file or folder serving is required.');
console.log('');
console.log('Usage:');
console.log(' spserver [options]');
console.log('');
console.log(config.stores.argv.help());
console.log('Examples:');
console.log(' spserver -p 2000 -f base.html -s ./dist');
console.log('');
console.log(' Will run the server on port 2000 serving static files from the ./dist folder');
console.log(' with any unknown file being served the contents of base.html.');
console.log('');
console.log(' spserver -f base.html -t --custom test');
console.log('');
console.log(' Will run the server with the base.html as a template as well as');
console.log(' passing the contents of "test" argument into the template.');
process.exit(0);
}
//Run our server \o/
server();

56
bin.mjs Normal file
View File

@ -0,0 +1,56 @@
#!/usr/bin/env node
import config from './lib/config.mjs'
import SPServer from './lib/spserver.mjs'
var env = config.get('NODE_ENV');
//Check if we any
var displayHelp = config.get('help');
if (!config.get('file') && !config.get(env + ':file') &&
!config.get('serve') && !config.get(env + ':serve')) {
displayHelp = true;
}
if (displayHelp) {
console.log(`Run static server for static files, simple servers or pure MVVM projects.
Specifying either file or folder serving is required.
Usage:
spserver [options]
Options:
--config Location of the config file for the server [default:
config.json]
--port The port server should bind to [default: 3001 or 80 in
production mode]
--file Single static file the server should serve on all unknown
requests
--bunyan Use bunyan instead of console to log to [default: true in
production mode]
--template Parse the static file as lodash template with all
options/settings being passed to it
--name The name for this server for logging [default: spserver]
--serve Folder path to serve static files from [default: public]
--prod Force run the server in production mode
--debug Force run the server in development mode
--ip IP server runs on [default: 0.0.0.0]
--rooturlpath Root URL path server is deployed on; will be removed from
URL when resolving to files [default: /]
Examples:
spserver -p 2000 -f base.html -s ./dist
Will run the server on port 2000 serving static files from the ./dist folder
with any unknown file being served the contents of base.html.
spserver -f base.html -t --custom test
Will run the server with the base.html as a template as well as
passing the contents of "test" argument into the template.`);
process.exit(0);
}
//Run our server \o/
let server = new SPServer()

View File

@ -1,48 +0,0 @@
'use strict';
module.exports = {
config: {
alias: 'c',
describe: 'Location of the config file for the server [default: config.json]'
},
port: {
alias: 'p',
describe: 'The port server should bind to [default: 3001 or 80 in production mode]'
},
file: {
alias: 'f',
describe: 'Single static file the server should serve on all unknown requests'
},
bunyan: {
alias: 'b',
describe: 'Use bunyan instead of console to log to [default: true in production mode]'
},
template: {
alias: 't',
describe: 'Parse the static file as lodash template with all options/settings being passed to it'
},
name: {
alias: 'n',
describe: 'The name for this server for logging [default: spserver]'
},
serve: {
alias: 's',
describe: 'Folder path to serve static files from [default: public]'
},
prod: {
alias: 'P',
describe: 'Force run the server in production mode'
},
debug: {
alias: 'd',
describe: 'Force run the server in development mode'
},
ip: {
alias: 'i',
describe: 'IP server runs on [default: 0.0.0.0]'
},
rooturlpath: {
alias: 'r',
describe: 'Root URL path server is deployed on; will be removed from URL when resolving to files [default: /]'
},
};

View File

@ -1,9 +1,10 @@
'use strict';
import fs from 'fs'
import Nconf from 'nconf-lite'
var nconf = require('nconf');
const nconf = new Nconf()
// Load arguments as highest priority
nconf.argv(require('./arguments'));
nconf.argv({ lowerCase: true, separator: '__', parseValues: true, useEqualsign: true });
// Overrides
var overrides = {};
@ -20,8 +21,11 @@ nconf.overrides(overrides);
// Load enviroment variables as third priority
nconf.env();
// Load the config if it exists.
nconf.file(nconf.get('config') || './config.json');
var filename = nconf.get('config') || '../config.json'
if (fs.existsSync(filename)) {
// Load the config if it exists.
nconf.file('main', nconf.get('config') || '../config.json');
}
// Default variables
nconf.defaults({
@ -46,4 +50,4 @@ nconf.defaults({
},
});
module.exports = nconf;
export default nconf

View File

@ -1,10 +1,6 @@
'use strict';
var _ = require('lodash');
var bunyan = require('bunyan-lite');
//Get the config
var config = require('./config');
import _ from 'lodash'
import bunyan from 'bunyan-lite'
import config from './config.mjs'
//Create our variables
var env = config.get('NODE_ENV');
@ -14,7 +10,7 @@ if (config.get('bunyan') || config.get(env + ':use_bunyan')) {
var settings = _.cloneDeep(config.get(env + ':bunyan'));
// Stream can be specified either in settings.streams[ix] or globally in settings.stream, but not
// both. Since the defaults specify stetings.stream, if the user specifies anything of vaulue
// both. Since the defaults specify settings.stream, if the user specifies anything of vaulue
// in settings.streams, we should delete the global defaults, because bunyan gets angry if there
// are multiple keys
if (_.has(settings, 'streams')) {
@ -43,4 +39,4 @@ if (config.get('bunyan') || config.get(env + ':use_bunyan')) {
output.debug = console.log.bind(console);
}
module.exports = output;
export default output;

View File

@ -1,137 +0,0 @@
'use strict';
var fs = require('fs');
var http = require('http');
var path = require('path');
var url = require('url');
var _ = require('lodash');
var nStatic = require('node-static');
var config = require('./config');
var logger = require('./logger');
// The different config sources sometimes manipulate different setting names.
// E.g. command line flags maniuplate root settings, but config files can
// manipulate settings at the prod/debug level. Resolve all of these into a
// final object of settings.
function _resolveFinalSettings(settings) {
var finalSettings = {};
var env = config.get('NODE_ENV');
if (!settings) {
settings = config.get();
}
if (!settings[env]) {
settings[env] = {};
}
// For 'name', 'file', 'serve', 'ip', and 'port', default to the global setting rather than an
// individual environment's setting, because it might have been set via command-line flags
_(['name', 'file', 'serve', 'ip', 'port']).forEach(function (field) {
finalSettings[field] = settings[field] || settings[env][field];
});
finalSettings.rooturlpath = config.get('rooturlpath') || config.get('ROOT_URL_PATH') || '/';
// For 'staticOptions', there are no command-line flags, so individual configuration options
// override global defaults where set
finalSettings.staticOptions = _.defaultsDeep(settings.staticOptions, settings[env].staticOptions);
// Make a template function so we can just pass that in downstream
finalSettings.template = (settings.template || settings[env].template) ?
function (contents) {
// Note: template is run with _original_, non-resolved settings
return _.template(contents)(settings);
} : null;
return finalSettings;
}
function generateBase(file, finalSettings) {
if (!file) {
return null;
}
if (_.endsWith(file, 'js')) {
return require(file);
}
var contents = fs.readFileSync(file);
if (finalSettings.template) {
contents = finalSettings.template(contents);
}
return function(req, res) {
res.writeHead(200, {'Content-Type': 'text/html'});
res.end(contents);
};
}
function _rerouteRootUrl(reqUrl, rootUrl) {
var parsedUrl = url.parse(reqUrl);
parsedUrl.pathname = path.normalize(
parsedUrl.pathname.replace(rootUrl, '/') || '/'
);
return url.format(parsedUrl);
}
var spserver = function (settings) {
var finalSettings = _resolveFinalSettings(settings);
var fileServer = new nStatic.Server(
path.resolve(finalSettings.serve),
finalSettings.staticOptions
);
var base = generateBase(path.resolve(finalSettings.file), finalSettings);
var server = http.createServer(function (req, res) {
logger.debug('[REQ]', req.method + ':', req.url);
var startTime = new Date().getTime();
var done = function () {
var requestTime = new Date().getTime() - startTime;
logger.debug('[RES]', req.method + ':', req.url,
'(' + res.statusCode + ')', 'took', requestTime, 'ms');
};
req.url = _rerouteRootUrl(req.url, finalSettings.rooturlpath);
res.addListener('finish', done);
res.addListener('close', done);
req.addListener('end', function () {
fileServer.serve(req, res, function (err) {
if (err) {
if (err.status === 404 && base) {
return base(req, res);
} else {
logger.error(err);
res.writeHead(err.status, err.headers);
res.end(err.message);
}
}
});
}).resume();
});
server.listen(finalSettings.port, finalSettings.ip);
logger.info(
'Started single-page server: ' + finalSettings.name +
', base file: ' + finalSettings.file +
', static folder: ' + finalSettings.serve +
', port: ' + finalSettings.port
);
return server;
};
spserver.generateBase = generateBase;
module.exports = spserver;

144
lib/spserver.mjs Normal file
View File

@ -0,0 +1,144 @@
import fs from 'fs'
import http from 'http'
import path from 'path'
import url from 'url'
import _ from 'lodash'
import nStatic from 'node-static'
import config from './config.mjs'
import logger from './logger.mjs'
// The different config sources sometimes manipulate different setting names.
// E.g. command line flags maniuplate root settings, but config files can
// manipulate settings at the prod/debug level. Resolve all of these into a
// final object of settings.
function _resolveFinalSettings(settings) {
var finalSettings = {};
var env = config.get('NODE_ENV');
if (!settings) {
settings = config.get();
}
if (!settings[env]) {
settings[env] = {};
}
// For 'name', 'file', 'serve', 'ip', and 'port', default to the global setting rather than an
// individual environment's setting, because it might have been set via command-line flags
_(['name', 'file', 'serve', 'ip', 'port']).forEach(function (field) {
finalSettings[field] = settings[field] || settings[env][field];
});
finalSettings.rooturlpath = config.get('rooturlpath') || config.get('ROOT_URL_PATH') || '/';
// For 'staticOptions', there are no command-line flags, so individual configuration options
// override global defaults where set
finalSettings.staticOptions = _.defaultsDeep(settings.staticOptions, settings[env].staticOptions);
// Make a template function so we can just pass that in downstream
finalSettings.template = (settings.template || settings[env].template) ?
function (contents) {
// Note: template is run with _original_, non-resolved settings
return _.template(contents)(settings);
} : null;
return finalSettings;
}
function _rerouteRootUrl(reqUrl, rootUrl) {
var parsedUrl = url.parse(reqUrl);
parsedUrl.pathname = path.normalize(
parsedUrl.pathname.replace(rootUrl, '/') || '/'
);
return url.format(parsedUrl);
}
class SPServer {
constructor(settings, opts = {}) {
Object.assign(this, {
fs: opts.fs || fs,
})
var finalSettings = _resolveFinalSettings(settings);
var fileServer = new nStatic.Server(
path.resolve(finalSettings.serve),
finalSettings.staticOptions
);
var base = SPServer.generateBase(finalSettings.file ? path.resolve(finalSettings.file) : null, finalSettings);
var server = http.createServer(function (req, res) {
var startTime = new Date().getTime();
var isFinished = false
var done = function () {
if (isFinished) return
isFinished = true
var requestTime = new Date().getTime() - startTime;
logger.debug('[RES]', req.method + ':', '(' + res.statusCode + ')', req.url,
'took', requestTime, 'ms');
};
req.url = _rerouteRootUrl(req.url, finalSettings.rooturlpath);
res.addListener('finish', done);
res.addListener('close', done);
req.addListener('end', function () {
fileServer.serve(req, res, function (err) {
if (err) {
if (err.status === 404 && base) {
return base(req, res);
} else if (err.status === 404) {
res.writeHead(err.status, err.headers);
res.end(err.message);
} else {
logger.debug('[REQ]', req.method + ':', req.url);
logger.error(err);
res.writeHead(err.status, err.headers);
res.end(err.message);
}
}
});
}).resume();
});
server.listen(finalSettings.port, finalSettings.ip);
logger.info(
'Started single-page server: ' + finalSettings.name +
', base file: ' + (finalSettings.file || '<none>') +
', static folder: ' + finalSettings.serve +
', port: ' + finalSettings.port
);
this.server = server
}
static generateBase(file, finalSettings, useFs = fs) {
if (!file) {
return null;
}
if (_.endsWith(file, 'js')) {
throw new Error('javascript file has been deprecated')
}
var contents = useFs.readFileSync(file, { encoding: 'utf-8' });
if (finalSettings.template) {
contents = finalSettings.template(contents)
}
return function(req, res) {
res.writeHead(200, {'Content-Type': 'text/html'});
res.end(contents);
};
}
};
export default SPServer;

View File

@ -2,9 +2,10 @@
"name": "spserver",
"version": "0.3.0",
"description": "Node static page server for running quick MVVM file server",
"main": "lib/spserver.js",
"main": "lib/spserver.mjs",
"scripts": {
"test": "mocha --reporter spec test/base.test.js"
"test": "set NODE_ENV=test&& eltro test/**/*.test.mjs -r dot",
"test:linux": "NODE_ENV=test eltro 'test/**/*.test.mjs' -r dot"
},
"repository": {
"type": "git",
@ -21,14 +22,13 @@
},
"homepage": "https://github.com/TheThing/spserver",
"dependencies": {
"bunyan-lite": "^1.0.0",
"bunyan-lite": "^1.1.1",
"lodash": "^4.17.2",
"nconf": "^0.8.4",
"nconf-lite": "^2.0.0",
"node-static": "^0.7.6"
},
"bin": "./bin.js",
"bin": "./bin.mjs",
"devDependencies": {
"mocha": "^5.2.0",
"sinon": "^1.12.2"
"eltro": "^1.2.3"
}
}

View File

@ -1,35 +0,0 @@
'use strict';
var fs = require('fs');
var assert = require('assert');
var sinon = require('sinon');
describe('spserver', function () {
var spserver = require('../lib/spserver');
describe('#generateBase()', function () {
it('should return null when file is empty', function () {
assert.strictEqual(null, spserver.generateBase());
assert.strictEqual(null, spserver.generateBase(null, {}));
assert.strictEqual(null, spserver.generateBase(''));
assert.strictEqual(null, spserver.generateBase('', {}));
});
it('should read file contents if string', function () {
var stub = sinon.stub(fs, 'readFileSync').returns('bla');
spserver.generateBase('asdf', {});
assert(stub.called);
stub.restore();
});
it('should return function if file is javascript', function () {
var path = require('path');
var nothing = require('./nothing');
var test = spserver.generateBase(path.resolve('test/nothing.js'), {});
assert.strictEqual(nothing.toString(), test.toString());
});
});
});

31
test/base.test.mjs Normal file
View File

@ -0,0 +1,31 @@
import { Eltro as t, assert, stub} from 'eltro'
import path from 'path'
import SPServer from '../lib/spserver.mjs'
t.describe('spserver', function () {
const fakeFs = { readFileSync: stub() }
t.describe('#generateBase()', function () {
t.test('should return null when file is empty', function () {
assert.strictEqual(null, SPServer.generateBase(undefined, undefined, fakeFs));
assert.strictEqual(null, SPServer.generateBase(null, {}, fakeFs));
assert.strictEqual(null, SPServer.generateBase('', undefined, fakeFs));
assert.strictEqual(null, SPServer.generateBase('', {}, fakeFs));
});
t.test('should read file contents if string', function () {
fakeFs.readFileSync.reset()
assert.notOk(fakeFs.readFileSync.called)
SPServer.generateBase('asdf', {}, fakeFs);
assert.ok(fakeFs.readFileSync.called)
});
t.test('should throw if file is javascript', async function () {
assert.throws(function() {
SPServer.generateBase(path.resolve('test/nothing.js'), {});
})
});
});
});

View File

@ -1,3 +1,3 @@
'use strict';
module.exports = function() {};
export default function(req, res) {
res.end('nothing')
}