Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@stuntman/server

Package Overview
Dependencies
Maintainers
1
Versions
24
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@stuntman/server - npm Package Compare versions

Comparing version 0.1.2 to 0.1.3

dist/api/validators.d.ts

5

dist/api/api.d.ts
/// <reference types="node" />
import http from 'http';
import { Express as ExpressServer } from 'express';
import { NextFunction, Request, Response, Express as ExpressServer } from 'express';
import type * as Stuntman from '@stuntman/shared';

@@ -14,2 +14,5 @@ import LRUCache from 'lru-cache';

server: http.Server | null;
auth: (req: Request, type: 'read' | 'write') => void;
authReadOnly: (req: Request, res: Response, next: NextFunction) => void;
authReadWrite: (req: Request, res: Response, next: NextFunction) => void;
constructor(options: ApiOptions, webGuiOptions?: Stuntman.WebGuiConfig);

@@ -16,0 +19,0 @@ private initWebGui;

60

dist/api/api.js

@@ -14,7 +14,11 @@ "use strict";

const serialize_javascript_1 = __importDefault(require("serialize-javascript"));
const validatiors_1 = require("./validatiors");
const validators_1 = require("./validators");
const utils_1 = require("./utils");
const API_KEY_HEADER = 'x-api-key';
class API {
constructor(options, webGuiOptions) {
this.server = null;
if (!options.apiKeyReadOnly !== !options.apiKeyReadWrite) {
throw new Error('apiKeyReadOnly and apiKeyReadWrite options need to be set either both or none');
}
this.options = options;

@@ -25,2 +29,19 @@ this.trafficStore = (0, storage_1.getTrafficStore)(this.options.mockUuid);

this.apiApp.use(express_1.default.text());
this.auth = (req, type) => {
const hasValidReadKey = req.header(API_KEY_HEADER) === this.options.apiKeyReadOnly;
const hasValidWriteKey = req.header(API_KEY_HEADER) === this.options.apiKeyReadWrite;
const hasValidKey = type === 'read' ? hasValidReadKey || hasValidWriteKey : hasValidWriteKey;
if (!hasValidKey) {
throw new shared_1.AppError({ httpCode: shared_1.HttpCode.UNAUTHORIZED, message: 'unauthorized' });
}
return;
};
this.authReadOnly = (req, res, next) => {
this.auth(req, 'read');
next();
};
this.authReadWrite = (req, res, next) => {
this.auth(req, 'write');
next();
};
this.apiApp.use((req, res, next) => {

@@ -30,19 +51,19 @@ requestContext_1.default.bind(req, this.options.mockUuid);

});
this.apiApp.get('/rule', async (req, res) => {
this.apiApp.get('/rule', this.authReadOnly, async (req, res) => {
res.send((0, shared_1.stringify)(await (0, ruleExecutor_1.getRuleExecutor)(this.options.mockUuid).getRules()));
});
this.apiApp.get('/rule/:ruleId', async (req, res) => {
this.apiApp.get('/rule/:ruleId', this.authReadOnly, async (req, res) => {
res.send((0, shared_1.stringify)(await (0, ruleExecutor_1.getRuleExecutor)(this.options.mockUuid).getRule(req.params.ruleId)));
});
this.apiApp.get('/rule/:ruleId/disable', (req, res) => {
this.apiApp.get('/rule/:ruleId/disable', this.authReadWrite, (req, res) => {
(0, ruleExecutor_1.getRuleExecutor)(this.options.mockUuid).disableRule(req.params.ruleId);
res.send();
});
this.apiApp.get('/rule/:ruleId/enable', (req, res) => {
this.apiApp.get('/rule/:ruleId/enable', this.authReadWrite, (req, res) => {
(0, ruleExecutor_1.getRuleExecutor)(this.options.mockUuid).enableRule(req.params.ruleId);
res.send();
});
this.apiApp.post('/rule', async (req, res) => {
this.apiApp.post('/rule', this.authReadWrite, async (req, res) => {
const deserializedRule = (0, utils_1.deserializeRule)(req.body);
(0, validatiors_1.validateDeserializedRule)(deserializedRule);
(0, validators_1.validateDeserializedRule)(deserializedRule);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment

@@ -53,7 +74,7 @@ // @ts-ignore

});
this.apiApp.get('/rule/:ruleId/remove', async (req, res) => {
this.apiApp.get('/rule/:ruleId/remove', this.authReadWrite, async (req, res) => {
await (0, ruleExecutor_1.getRuleExecutor)(this.options.mockUuid).removeRule(req.params.ruleId);
res.send();
});
this.apiApp.get('/traffic', (req, res) => {
this.apiApp.get('/traffic', this.authReadOnly, (req, res) => {
const serializedTraffic = {};

@@ -65,3 +86,3 @@ for (const [key, value] of this.trafficStore.entries()) {

});
this.apiApp.get('/traffic/:ruleIdOrLabel', (req, res) => {
this.apiApp.get('/traffic/:ruleIdOrLabel', this.authReadOnly, (req, res) => {
const serializedTraffic = {};

@@ -75,3 +96,9 @@ for (const [key, value] of this.trafficStore.entries()) {

});
this.apiApp.use((error, req, res, _next) => {
if (!(webGuiOptions === null || webGuiOptions === void 0 ? void 0 : webGuiOptions.disabled)) {
this.apiApp.set('views', __dirname + '/webgui');
this.apiApp.set('view engine', 'pug');
this.initWebGui();
}
this.apiApp.all(/.*/, (req, res) => res.status(404).send());
this.apiApp.use((error, req, res) => {
const ctx = requestContext_1.default.get(req);

@@ -97,10 +124,5 @@ const uuid = (ctx === null || ctx === void 0 ? void 0 : ctx.uuid) || (0, uuid_1.v4)();

});
if (!(webGuiOptions === null || webGuiOptions === void 0 ? void 0 : webGuiOptions.disabled)) {
this.apiApp.set('views', __dirname + '/webgui');
this.apiApp.set('view engine', 'pug');
this.initWebGui();
}
}
initWebGui() {
this.apiApp.get('/webgui/rules', async (req, res) => {
this.apiApp.get('/webgui/rules', this.authReadOnly, async (req, res) => {
const rules = {};

@@ -112,3 +134,3 @@ for (const rule of await (0, ruleExecutor_1.getRuleExecutor)(this.options.mockUuid).getRules()) {

});
this.apiApp.get('/webgui/traffic', async (req, res) => {
this.apiApp.get('/webgui/traffic', this.authReadOnly, async (req, res) => {
const serializedTraffic = [];

@@ -123,3 +145,3 @@ for (const value of this.trafficStore.values()) {

// TODO make webui way better and safer, nicer formatting, eslint/prettier, blackjack and hookers
this.apiApp.post('/webgui/rules/unsafesave', async (req, res) => {
this.apiApp.post('/webgui/rules/unsafesave', this.authReadWrite, async (req, res) => {
const rule = new Function(req.body)();

@@ -126,0 +148,0 @@ if (!rule ||

@@ -9,7 +9,7 @@ "use strict";

const shared_1 = require("@stuntman/shared");
const validatiors_1 = require("./validatiors");
const validators_1 = require("./validators");
// TODO
const deserializeRule = (serializedRule) => {
shared_1.logger.debug(serializedRule, 'attempt to deserialize rule');
(0, validatiors_1.validateSerializedRuleProperties)(serializedRule);
(0, validators_1.validateSerializedRuleProperties)(serializedRule);
const rule = {

@@ -16,0 +16,0 @@ id: serializedRule.id,

@@ -24,2 +24,4 @@ /// <reference types="node" />

constructor(options: Stuntman.ServerConfig);
private proxyRequest;
private requestHandler;
start(): void;

@@ -26,0 +28,0 @@ stop(): void;

@@ -88,157 +88,3 @@ "use strict";

});
this.mockApp.all(/.*/, async (req, res) => {
var _a, _b, _c, _d, _e;
const ctx = requestContext_1.default.get(req);
const requestUuid = (ctx === null || ctx === void 0 ? void 0 : ctx.uuid) || (0, uuid_1.v4)();
const timestamp = Date.now();
const originalHostname = req.headers.host || req.hostname;
const unproxiedHostname = req.hostname.replace(this.MOCK_DOMAIN_REGEX, '');
const isProxiedHostname = originalHostname !== unproxiedHostname;
const originalRequest = {
id: requestUuid,
timestamp,
url: `${req.protocol}://${req.hostname}${req.originalUrl}`,
method: req.method,
rawHeaders: new shared_1.RawHeaders(...req.rawHeaders),
...((Buffer.isBuffer(req.body) && { body: req.body.toString('utf-8') }) ||
(typeof req.body === 'string' && { body: req.body })),
};
shared_1.logger.debug(originalRequest, 'processing request');
const logContext = {
requestId: originalRequest.id,
};
const mockEntry = {
originalRequest,
modifiedRequest: {
...this.unproxyRequest(req),
id: requestUuid,
timestamp,
...(originalRequest.body && { gqlBody: naiveGQLParser(originalRequest.body) }),
},
};
if (!isProxiedHostname) {
this.removeProxyPort(mockEntry.modifiedRequest);
}
const matchingRule = await (0, ruleExecutor_1.getRuleExecutor)(this.mockUuid).findMatchingRule(mockEntry.modifiedRequest);
if (matchingRule) {
mockEntry.mockRuleId = matchingRule.id;
mockEntry.labels = matchingRule.labels;
if ((_a = matchingRule.actions) === null || _a === void 0 ? void 0 : _a.mockResponse) {
const staticResponse = typeof matchingRule.actions.mockResponse === 'function'
? matchingRule.actions.mockResponse(mockEntry.modifiedRequest)
: matchingRule.actions.mockResponse;
mockEntry.modifiedResponse = staticResponse;
shared_1.logger.debug({ ...logContext, staticResponse }, 'replying with mocked response');
if (matchingRule.storeTraffic) {
this.trafficStore.set(requestUuid, mockEntry);
}
if (staticResponse.rawHeaders) {
for (const header of staticResponse.rawHeaders.toHeaderPairs()) {
res.setHeader(header[0], header[1]);
}
}
res.status(staticResponse.status || 200);
res.send(staticResponse.body);
// static response blocks any further processing
return;
}
if ((_b = matchingRule.actions) === null || _b === void 0 ? void 0 : _b.modifyRequest) {
mockEntry.modifiedRequest = (_c = matchingRule.actions) === null || _c === void 0 ? void 0 : _c.modifyRequest(mockEntry.modifiedRequest);
shared_1.logger.debug({ ...logContext, modifiedRequest: mockEntry.modifiedRequest }, 'modified original request');
}
}
if (this.ipUtils && !isProxiedHostname && !this.ipUtils.isIP(originalHostname)) {
const hostname = originalHostname.split(':')[0];
try {
const internalIPs = await this.ipUtils.resolveIP(hostname);
if (this.ipUtils.isLocalhostIP(internalIPs) && this.options.mock.externalDns.length) {
const externalIPs = await this.ipUtils.resolveIP(hostname, { useExternalDns: true });
shared_1.logger.debug({ ...logContext, hostname, externalIPs, internalIPs }, 'switched to external IP');
mockEntry.modifiedRequest.url = mockEntry.modifiedRequest.url.replace(/^(https?:\/\/)[^:/]+/i, `$1${externalIPs}`);
}
}
catch (error) {
// swallow the exeception, don't think much can be done at this point
shared_1.logger.warn({ ...logContext, error }, `error trying to resolve IP for "${hostname}"`);
}
}
let controller = new AbortController();
const fetchTimeout = setTimeout(() => {
if (controller) {
controller.abort(`timeout after ${this.options.mock.timeout}`);
}
}, this.options.mock.timeout);
req.on('close', () => {
shared_1.logger.debug(logContext, 'remote client canceled the request');
clearTimeout(fetchTimeout);
if (controller) {
controller.abort('remote client canceled the request');
}
});
let targetResponse;
try {
const requestOptions = {
headers: mockEntry.modifiedRequest.rawHeaders,
body: mockEntry.modifiedRequest.body,
method: mockEntry.modifiedRequest.method.toUpperCase(),
};
shared_1.logger.debug({
...logContext,
url: mockEntry.modifiedRequest.url,
...requestOptions,
}, 'outgoing request attempt');
targetResponse = await (0, undici_1.request)(mockEntry.modifiedRequest.url, requestOptions);
}
catch (error) {
shared_1.logger.error({ ...logContext, error, request: mockEntry.modifiedRequest }, 'error fetching');
throw error;
}
finally {
controller = null;
clearTimeout(fetchTimeout);
}
const targetResponseBuffer = Buffer.from(await targetResponse.body.arrayBuffer());
const originalResponse = {
timestamp: Date.now(),
body: targetResponseBuffer.toString('binary'),
status: targetResponse.statusCode,
rawHeaders: shared_1.RawHeaders.fromHeadersRecord(targetResponse.headers),
};
shared_1.logger.debug({ ...logContext, originalResponse }, 'received response');
mockEntry.originalResponse = originalResponse;
let modifedResponse = {
...originalResponse,
rawHeaders: new shared_1.RawHeaders(...Array.from(originalResponse.rawHeaders.toHeaderPairs()).flatMap(([key, value]) => {
// TODO this replace may be too aggressive and doesn't handle protocol (won't be necessary with a trusted cert and mock serving http+https)
return [
key,
isProxiedHostname
? value
: value.replace(new RegExp(`(?:^|\\b)(${unproxiedHostname.replace('.', '\\.')})(?:\\b|$)`, 'igm'), originalHostname),
];
})),
};
if ((_d = matchingRule === null || matchingRule === void 0 ? void 0 : matchingRule.actions) === null || _d === void 0 ? void 0 : _d.modifyResponse) {
modifedResponse = (_e = matchingRule === null || matchingRule === void 0 ? void 0 : matchingRule.actions) === null || _e === void 0 ? void 0 : _e.modifyResponse(mockEntry.modifiedRequest, originalResponse);
shared_1.logger.debug({ ...logContext, modifedResponse }, 'modified response');
}
mockEntry.modifiedResponse = modifedResponse;
if (matchingRule === null || matchingRule === void 0 ? void 0 : matchingRule.storeTraffic) {
this.trafficStore.set(requestUuid, mockEntry);
}
if (modifedResponse.status) {
res.status(modifedResponse.status);
}
if (modifedResponse.rawHeaders) {
for (const header of modifedResponse.rawHeaders.toHeaderPairs()) {
// since fetch decompresses responses we need to get rid of some headers
// TODO maybe could be handled better than just skipping, although express should add these back for new body
if (/^content-(?:length|encoding)$/i.test(header[0])) {
continue;
}
res.setHeader(header[0], isProxiedHostname ? header[1].replace(unproxiedHostname, originalHostname) : header[1]);
}
}
res.end(Buffer.from(modifedResponse.body, 'binary'));
});
this.mockApp.all(/.*/, this.requestHandler);
this.mockApp.use((error, req, res, _next) => {

@@ -259,2 +105,160 @@ const ctx = requestContext_1.default.get(req);

}
async proxyRequest(req, mockEntry, logContext) {
let controller = new AbortController();
const fetchTimeout = setTimeout(() => {
if (controller) {
controller.abort(`timeout after ${this.options.mock.timeout}`);
}
}, this.options.mock.timeout);
req.on('close', () => {
shared_1.logger.debug(logContext, 'remote client canceled the request');
clearTimeout(fetchTimeout);
if (controller) {
controller.abort('remote client canceled the request');
}
});
let targetResponse;
try {
const requestOptions = {
headers: mockEntry.modifiedRequest.rawHeaders,
body: mockEntry.modifiedRequest.body,
method: mockEntry.modifiedRequest.method.toUpperCase(),
};
shared_1.logger.debug({
...logContext,
url: mockEntry.modifiedRequest.url,
...requestOptions,
}, 'outgoing request attempt');
targetResponse = await (0, undici_1.request)(mockEntry.modifiedRequest.url, requestOptions);
}
catch (error) {
shared_1.logger.error({ ...logContext, error, request: mockEntry.modifiedRequest }, 'error fetching');
throw error;
}
finally {
controller = null;
clearTimeout(fetchTimeout);
}
const targetResponseBuffer = Buffer.from(await targetResponse.body.arrayBuffer());
return {
timestamp: Date.now(),
body: targetResponseBuffer.toString('binary'),
status: targetResponse.statusCode,
rawHeaders: shared_1.RawHeaders.fromHeadersRecord(targetResponse.headers),
};
}
async requestHandler(req, res) {
var _a, _b, _c, _d, _e;
const ctx = requestContext_1.default.get(req);
const requestUuid = (ctx === null || ctx === void 0 ? void 0 : ctx.uuid) || (0, uuid_1.v4)();
const timestamp = Date.now();
const originalHostname = req.headers.host || req.hostname;
const unproxiedHostname = req.hostname.replace(this.MOCK_DOMAIN_REGEX, '');
const isProxiedHostname = originalHostname !== unproxiedHostname;
const originalRequest = {
id: requestUuid,
timestamp,
url: `${req.protocol}://${req.hostname}${req.originalUrl}`,
method: req.method,
rawHeaders: new shared_1.RawHeaders(...req.rawHeaders),
...((Buffer.isBuffer(req.body) && { body: req.body.toString('utf-8') }) ||
(typeof req.body === 'string' && { body: req.body })),
};
shared_1.logger.debug(originalRequest, 'processing request');
const logContext = {
requestId: originalRequest.id,
};
const mockEntry = {
originalRequest,
modifiedRequest: {
...this.unproxyRequest(req),
id: requestUuid,
timestamp,
...(originalRequest.body && { gqlBody: naiveGQLParser(originalRequest.body) }),
},
};
if (!isProxiedHostname) {
this.removeProxyPort(mockEntry.modifiedRequest);
}
const matchingRule = await (0, ruleExecutor_1.getRuleExecutor)(this.mockUuid).findMatchingRule(mockEntry.modifiedRequest);
if (matchingRule) {
mockEntry.mockRuleId = matchingRule.id;
mockEntry.labels = matchingRule.labels;
if ((_a = matchingRule.actions) === null || _a === void 0 ? void 0 : _a.mockResponse) {
const staticResponse = typeof matchingRule.actions.mockResponse === 'function'
? matchingRule.actions.mockResponse(mockEntry.modifiedRequest)
: matchingRule.actions.mockResponse;
mockEntry.modifiedResponse = staticResponse;
shared_1.logger.debug({ ...logContext, staticResponse }, 'replying with mocked response');
if (matchingRule.storeTraffic) {
this.trafficStore.set(requestUuid, mockEntry);
}
if (staticResponse.rawHeaders) {
for (const header of staticResponse.rawHeaders.toHeaderPairs()) {
res.setHeader(header[0], header[1]);
}
}
res.status(staticResponse.status || 200);
res.send(staticResponse.body);
// static response blocks any further processing
return;
}
if ((_b = matchingRule.actions) === null || _b === void 0 ? void 0 : _b.modifyRequest) {
mockEntry.modifiedRequest = (_c = matchingRule.actions) === null || _c === void 0 ? void 0 : _c.modifyRequest(mockEntry.modifiedRequest);
shared_1.logger.debug({ ...logContext, modifiedRequest: mockEntry.modifiedRequest }, 'modified original request');
}
}
if (this.ipUtils && !isProxiedHostname && !this.ipUtils.isIP(originalHostname)) {
const hostname = originalHostname.split(':')[0];
try {
const internalIPs = await this.ipUtils.resolveIP(hostname);
if (this.ipUtils.isLocalhostIP(internalIPs) && this.options.mock.externalDns.length) {
const externalIPs = await this.ipUtils.resolveIP(hostname, { useExternalDns: true });
shared_1.logger.debug({ ...logContext, hostname, externalIPs, internalIPs }, 'switched to external IP');
mockEntry.modifiedRequest.url = mockEntry.modifiedRequest.url.replace(/^(https?:\/\/)[^:/]+/i, `$1${externalIPs}`);
}
}
catch (error) {
// swallow the exeception, don't think much can be done at this point
shared_1.logger.warn({ ...logContext, error }, `error trying to resolve IP for "${hostname}"`);
}
}
const originalResponse = await this.proxyRequest(req, mockEntry, logContext);
shared_1.logger.debug({ ...logContext, originalResponse }, 'received response');
mockEntry.originalResponse = originalResponse;
let modifedResponse = {
...originalResponse,
rawHeaders: new shared_1.RawHeaders(...Array.from(originalResponse.rawHeaders.toHeaderPairs()).flatMap(([key, value]) => {
// TODO this replace may be too aggressive and doesn't handle protocol (won't be necessary with a trusted cert and mock serving http+https)
return [
key,
isProxiedHostname
? value
: value.replace(new RegExp(`(?:^|\\b)(${unproxiedHostname.replace('.', '\\.')})(?:\\b|$)`, 'igm'), originalHostname),
];
})),
};
if ((_d = matchingRule === null || matchingRule === void 0 ? void 0 : matchingRule.actions) === null || _d === void 0 ? void 0 : _d.modifyResponse) {
modifedResponse = (_e = matchingRule === null || matchingRule === void 0 ? void 0 : matchingRule.actions) === null || _e === void 0 ? void 0 : _e.modifyResponse(mockEntry.modifiedRequest, originalResponse);
shared_1.logger.debug({ ...logContext, modifedResponse }, 'modified response');
}
mockEntry.modifiedResponse = modifedResponse;
if (matchingRule === null || matchingRule === void 0 ? void 0 : matchingRule.storeTraffic) {
this.trafficStore.set(requestUuid, mockEntry);
}
if (modifedResponse.status) {
res.status(modifedResponse.status);
}
if (modifedResponse.rawHeaders) {
for (const header of modifedResponse.rawHeaders.toHeaderPairs()) {
// since fetch decompresses responses we need to get rid of some headers
// TODO maybe could be handled better than just skipping, although express should add these back for new body
// if (/^content-(?:length|encoding)$/i.test(header[0])) {
// continue;
// }
res.setHeader(header[0], isProxiedHostname ? header[1].replace(unproxiedHostname, originalHostname) : header[1]);
}
}
res.end(Buffer.from(modifedResponse.body, 'binary'));
}
start() {

@@ -261,0 +265,0 @@ if (this.server) {

@@ -98,2 +98,3 @@ "use strict";

if (r.id === (typeof ruleOrId === 'string' ? ruleOrId : ruleOrId.id)) {
r.counter = 0;
r.isEnabled = true;

@@ -143,2 +144,3 @@ shared_1.logger.debug({ ruleId: r.id }, 'rule enabled');

}
// TODO check if that works
if (matchingRule.disableAfterUse) {

@@ -153,3 +155,3 @@ if (typeof matchingRule.disableAfterUse === 'boolean' || matchingRule.disableAfterUse <= matchingRule.counter) {

shared_1.logger.debug(logContext, 'removing rule for future requests');
this.removeRule(matchingRule);
await this.removeRule(matchingRule);
}

@@ -184,3 +186,3 @@ }

if (!ruleExecutors[mockUuid]) {
ruleExecutors[mockUuid] = new RuleExecutor(rules_1.DEFAULT_RULES.map((r) => ({ ...r, ttlSeconds: Infinity })));
ruleExecutors[mockUuid] = new RuleExecutor([...rules_1.DEFAULT_RULES, ...rules_1.CUSTOM_RULES].map((r) => ({ ...r, ttlSeconds: Infinity })));
}

@@ -187,0 +189,0 @@ return ruleExecutors[mockUuid];

import type * as Stuntman from '@stuntman/shared';
export declare const DEFAULT_RULES: Stuntman.DeployedRule[];
export declare const CUSTOM_RULES: Stuntman.DeployedRule[];
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DEFAULT_RULES = void 0;
exports.CUSTOM_RULES = exports.DEFAULT_RULES = void 0;
const fs_1 = __importDefault(require("fs"));
const glob_1 = __importDefault(require("glob"));
const tsImport = __importStar(require("ts-import"));
const catchAll_1 = require("./catchAll");
const echo_1 = require("./echo");
// TODO add option to load rules additional default rules from some nice configurable folder
const shared_1 = require("@stuntman/shared");
exports.DEFAULT_RULES = [catchAll_1.catchAllRule, echo_1.echoRule];
exports.CUSTOM_RULES = [];
const loadAdditionalRules = () => {
if (!shared_1.serverConfig.mock.rulesPath || !fs_1.default.existsSync(shared_1.serverConfig.mock.rulesPath)) {
shared_1.logger.debug({ rulesPath: shared_1.serverConfig.mock.rulesPath }, `additional rules directory not found`);
return;
}
shared_1.logger.debug({ rulesPath: shared_1.serverConfig.mock.rulesPath }, `loading additional rules`);
const filePaths = glob_1.default.sync('*.[tj]s', { absolute: true, cwd: shared_1.serverConfig.mock.rulesPath });
for (const filePath of filePaths) {
// TODO add .ts rule support
try {
const loadedFile = /\.js$/.test(filePath) ? require(filePath) : tsImport.loadSync(filePath);
// eslint-disable-next-line @typescript-eslint/no-var-requires
const exportedRules = Object.values(loadedFile).filter((rule) => {
if (!rule || !rule.id || typeof rule.matches !== 'function') {
shared_1.logger.error({ filePath, rule }, 'invalid exported rule');
return false;
}
return true;
});
exports.CUSTOM_RULES.push(...exportedRules);
}
catch (error) {
shared_1.logger.error({ filePath, error }, 'error importing rule');
}
}
const ruleIds = [...exports.DEFAULT_RULES, ...exports.CUSTOM_RULES].map((rule) => rule.id);
const duplicatedRuleIds = ruleIds.filter((currentValue, currentIndex) => ruleIds.indexOf(currentValue) !== currentIndex);
if (duplicatedRuleIds.length > 0) {
shared_1.logger.error({ duplicatedRuleIds }, 'duplicated rule ids');
throw new Error('duplicated rule ids');
}
};
loadAdditionalRules();
{
"name": "@stuntman/server",
"version": "0.1.2",
"version": "0.1.3",
"description": "Stuntman - HTTP proxy / mock server with API",

@@ -38,2 +38,3 @@ "main": "dist/index.js",

"express": "5.0.0-beta.1",
"glob": "8.1.0",
"lru-cache": "7.16.0",

@@ -43,2 +44,4 @@ "object-sizeof": "2.6.1",

"serialize-javascript": "6.0.1",
"ts-import": "4.0.0-beta.10",
"typescript": "4.9.5",
"undici": "5.20.0",

@@ -50,2 +53,3 @@ "uuid": "9.0.0"

"@types/express": "4.17.17",
"@types/glob": "8.1.0",
"@types/serialize-javascript": "5.0.2",

@@ -69,3 +73,3 @@ "@types/uuid": "9.0.0",

"lint": "prettier --check . && eslint . --ext ts",
"lint:fix": "prettier --write ./src && eslint ./src --ext ts --fix",
"lint:fix": "prettier --write ./{src,test} && eslint ./{src,test} --ext ts --fix",
"start": "node ./dist/bin/stuntman.js",

@@ -72,0 +76,0 @@ "start:dev": "nodemon --watch 'src/**/*.ts' --exec 'ts-node' ./src/bin/stuntman.ts",

@@ -7,3 +7,3 @@ # Stuntman

In order to get more familiar with the concept and how to use it please refer to [example app](https://github.com/andrzej-woof/stuntman/tree/master/packages/example#readme)
In order to get more familiar with the concept and how to use it please refer to [example app](https://github.com/andrzej-woof/stuntman/tree/master/apps/example#readme)

@@ -10,0 +10,0 @@ > **_NOTE:_** This project is at a very early stage of developement and as such may often contain breaking changes in upcoming releases before reaching stable version 1.0.0

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