New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

servitsy

Package Overview
Dependencies
Maintainers
0
Versions
17
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

servitsy - npm Package Compare versions

Comparing version 0.4.4 to 0.4.5

66

lib/cli.js

@@ -10,3 +10,3 @@ import { createServer } from 'node:http';

import { RequestHandler } from './handler.js';
import { color, logger, requestLogLine } from './logger.js';
import { color, Logger, requestLogLine } from './logger.js';
import { serverOptions } from './options.js';

@@ -16,2 +16,3 @@ import { FileResolver } from './resolver.js';

export async function run() {
const logger = new Logger(process.stdout, process.stderr);
const args = new CLIArgs(argv.slice(2));

@@ -38,3 +39,3 @@ if (args.has('--version')) {

}
const cliServer = new CLIServer(options);
const cliServer = new CLIServer(options, logger);
cliServer.start();

@@ -48,5 +49,5 @@ }

#server;
#resolver;
#shuttingDown = false;
constructor(options) {
#logger;
constructor(options, logger) {
this.#logger = logger;
this.#options = options;

@@ -61,25 +62,26 @@ this.#portIterator = new Set(options.ports).values();

res.on('close', () => {
logger.write('request', requestLogLine(handler.data()));
this.#logger.write('request', requestLogLine(handler.data()));
});
await handler.process();
});
server.on('error', (error) => this.#onServerError(error));
server.on('error', (error) => this.onError(error));
server.on('listening', () => {
const info = this.headerInfo();
if (info) logger.write('header', info, { top: 1, bottom: 1 });
if (info) {
this.#logger.write('header', info, { top: 1, bottom: 1 });
}
});
this.#resolver = resolver;
this.#server = server;
}
nextPort() {
this.#port = this.#portIterator.next().value;
return this.#port;
}
start() {
const port = this.nextPort();
if (!port) throw new Error('Port not specified');
this.handleSignals();
this.#server.listen(
{
host: this.#options.host,
port: this.#portIterator.next().value,
},
() => {
this.handleKeyboardInput();
},
);
this.#server.listen({ host: this.#options.host, port }, () => {
this.handleKeyboardInput();
});
}

@@ -120,3 +122,3 @@ headerInfo() {

helpShown = true;
logger.write('info', 'Hit Control+C or Escape to stop the server.');
this.#logger.write('info', 'Hit Control+C or Escape to stop the server.');
}

@@ -126,7 +128,11 @@ });

}
#attached = false;
handleSignals() {
if (this.#attached) return;
process.on('SIGBREAK', this.shutdown);
process.on('SIGINT', this.shutdown);
process.on('SIGTERM', this.shutdown);
this.#attached = true;
}
#shuttingDown = false;
shutdown = async () => {

@@ -136,3 +142,3 @@ if (this.#shuttingDown) return;

process.exitCode = 0;
const promise = logger.write('info', 'Gracefully shutting down...');
const promise = this.#logger.write('info', 'Gracefully shutting down...');
this.#server.closeAllConnections();

@@ -143,17 +149,13 @@ this.#server.close();

};
#onServerError(error) {
onError(error) {
if (error.code === 'EADDRINUSE') {
const { value: nextPort } = this.#portIterator.next();
const { ports } = this.#options;
this.#server.closeAllConnections();
this.#server.close(() => {
if (nextPort) {
this.#port = nextPort;
this.#server.listen({
host: this.#options.host,
port: this.#port,
});
const port = this.nextPort();
if (port) {
this.#server.listen({ host: this.#options.host, port });
} else {
const { ports } = this.#options;
const msg = `${ports.length > 1 ? 'ports' : 'port'} already in use: ${ports.join(', ')}`;
logger.error(msg);
this.#logger.error(msg);
exit(1);

@@ -165,5 +167,5 @@ }

if (error.code === 'ENOTFOUND') {
logger.error(`host not found: '${error.hostname}'`);
this.#logger.error(`host not found: '${error.hostname}'`);
} else {
logger.error(error);
this.#logger.error(error);
}

@@ -170,0 +172,0 @@ exit(1);

import { release } from 'node:os';
import { platform } from 'node:process';
import { stderr, stdout } from 'node:process';
import { inspect } from 'node:util';

@@ -28,5 +27,9 @@ import { clamp, fwdSlash, getEnv, getRuntime, trimSlash, withResolvers } from './utils.js';

}
class Logger {
#lastout;
#lasterr;
export class Logger {
#out;
#err;
constructor(out, err) {
this.#out = { stream: out };
this.#err = { stream: err ?? out };
}
async write(group, data = '', padding = { top: 0, bottom: 0 }) {

@@ -42,13 +45,9 @@ const item = {

const { promise, resolve, reject } = withResolvers();
const writeCallback = (err) => {
const dest = group === 'error' ? this.#err : this.#out;
const text = this.#withPadding(dest.last, item);
dest.last = item;
dest.stream.write(text, (err) => {
if (err) reject(err);
else resolve();
};
if (group === 'error') {
stderr.write(this.#withPadding(this.#lasterr, item), writeCallback);
this.#lasterr = item;
} else {
stdout.write(this.#withPadding(this.#lastout, item), writeCallback);
this.#lastout = item;
}
});
return promise;

@@ -151,2 +150,1 @@ }

export const color = new ColorUtils(supportsColor());
export const logger = new Logger();
import { isAbsolute, resolve } from 'node:path';
import { DEFAULT_OPTIONS, PORTS_CONFIG } from './constants.js';
export function serverOptions(options, context) {
const validator = new OptionsValidator(context.onError);
const checked = {
ports: validator.ports(options.ports),
gzip: validator.gzip(options.gzip),
host: validator.host(options.host),
cors: validator.cors(options.cors),
headers: validator.headers(options.headers),
dirFile: validator.dirFile(options.dirFile),
dirList: validator.dirList(options.dirList),
ext: validator.ext(options.ext),
exclude: validator.exclude(options.exclude),
};
const final = structuredClone({
root: validator.root(options.root),
...DEFAULT_OPTIONS,
});
for (const [key, value] of Object.entries(checked)) {
const valid = typeof value !== 'undefined';
if (typeof value !== 'undefined') {
final[key] = value;
}
}
return final;
}
export class OptionsValidator {
onError;
#errorCb;
constructor(onError) {
this.onError = onError;
this.#errorCb = onError;
}
#array(input, filterFn) {
if (!Array.isArray(input)) return;
if (input.length === 0) return input;
const value = input.filter(filterFn);
if (value.length) return value;
#error(msg, input) {
let dbg = input;
if (typeof input === 'object') {
dbg = JSON.stringify(input);
} else if (typeof input === 'string') {
dbg = `'${dbg.replaceAll("'", "\\'")}'`;
}
this.#errorCb(`${msg}: ${dbg}`);
}
#bool(optName, input) {
#arr(input, msg, validFn) {
if (typeof input === 'undefined') return;
if (Array.isArray(input)) {
if (input.length === 0) return input;
const valid = input.filter((item) => {
if (validFn(item)) return true;
this.#error(msg, item);
});
if (valid.length) {
return valid;
}
} else {
this.#error(msg, input);
}
}
#bool(input, msg) {
if (typeof input === 'undefined') return;
if (typeof input === 'boolean') return input;
else this.#error(`invalid ${optName} value: '${input}'`);
else this.#error(msg, input);
}
#error(msg) {
this.onError?.(msg);
#str(input, msg, isValid) {
if (typeof input === 'undefined') return;
if (typeof input === 'string' && isValid(input)) return input;
else this.#error(msg, input);
}
cors(input) {
return this.#bool('cors', input);
return this.#bool(input, 'invalid cors value');
}
dirFile(input) {
return this.#array(input, (item) => {
const ok = isValidPattern(item);
if (!ok) this.#error(`invalid dirFile value: '${item}'`);
return ok;
});
return this.#arr(input, 'invalid dirFile value', isValidPattern);
}
dirList(input) {
return this.#bool('dirList', input);
return this.#bool(input, 'invalid dirList value');
}
exclude(input) {
return this.#array(input, (item) => {
const ok = isValidPattern(item);
if (!ok) this.#error(`invalid exclude pattern: '${item}'`);
return ok;
});
return this.#arr(input, 'invalid exclude pattern', isValidPattern);
}
ext(input) {
return this.#array(input, (item) => {
const ok = isValidExt(item);
if (!ok) this.#error(`invalid ext value: '${item}'`);
return ok;
});
return this.#arr(input, 'invalid ext value', isValidExt);
}
gzip(input) {
return this.#bool('gzip', input);
return this.#bool(input, 'invalid gzip value');
}
headers(input) {
return this.#array(input, (rule) => {
const ok = isValidHeaderRule(rule);
if (!ok) this.#error(`invalid header value: ${JSON.stringify(rule)}`);
return ok;
});
return this.#arr(input, 'invalid header value', isValidHeaderRule);
}
host(input) {
if (typeof input !== 'string') return;
if (isValidHost(input)) return input;
else this.#error(`invalid host value: '${input}'`);
return this.#str(input, 'invalid host value', isValidHost);
}
ports(input) {
if (!Array.isArray(input) || input.length === 0) return;
if (typeof input === 'undefined') return;
if (!Array.isArray(input)) {
this.#error('invalid port value', input);
return;
}
if (input.length === 0) return;
const value = input.slice(0, PORTS_CONFIG.maxCount);
const invalid = value.find((num) => !isValidPort(num));
if (typeof invalid === 'undefined') return value;
else this.#error(`invalid port number: '${invalid}'`);
if (typeof invalid === 'undefined') {
return value;
} else {
this.#error('invalid port number', invalid);
}
}

@@ -111,3 +146,5 @@ root(input) {

export function isValidPattern(value) {
return typeof value === 'string' && value.length > 0 && !/[\\\/\:]/.test(value);
if (typeof value !== 'string') return false;
if (value.length < (value.startsWith('!') ? 2 : 1)) return false;
return !/[\\\/\:]/.test(value);
}

@@ -117,25 +154,1 @@ export function isValidPort(num) {

}
export function serverOptions(options, context) {
const validator = new OptionsValidator(context?.onError);
const checked = {
ports: validator.ports(options.ports),
gzip: validator.gzip(options.gzip),
host: validator.host(options.host),
cors: validator.cors(options.cors),
headers: validator.headers(options.headers),
dirFile: validator.dirFile(options.dirFile),
dirList: validator.dirList(options.dirList),
ext: validator.ext(options.ext),
exclude: validator.exclude(options.exclude),
};
const final = structuredClone({
root: validator.root(options.root),
...DEFAULT_OPTIONS,
});
for (const [key, value] of Object.entries(checked)) {
if (typeof value !== 'undefined') {
final[key] = value;
}
}
return final;
}
{
"name": "servitsy",
"version": "0.4.4",
"version": "0.4.5",
"license": "MIT",

@@ -5,0 +5,0 @@ "description": "Small, local HTTP server for static files",

@@ -67,3 +67,3 @@ # servitsy

| ------------- | ------- | ------------ | --------------- |
| [servitsy] | 0.4.4 | 0 | 108 kB |
| [servitsy] | 0.4.5 | 0 | 108 kB |
| [servor] | 4.0.2 | 0 | 144 kB |

@@ -70,0 +70,0 @@ | [sirv-cli] | 3.0.0 | 12 | 396 kB |

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc