@stuntman/server 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,128 @@
1
+ # Stuntman
2
+
3
+ Stuntman is a proxy/mock server that can be deployed remotely together with your application under test, working as either pass-through proxy allowing you to inspect traffic or proxy/mock which can intercept requests/responses and modify them or stub with predefined ones.
4
+
5
+ It offers API and client library that can be used for example within E2E functional test scripts to dynamically alter it's behaviour for specific traffic matching set of rules of your definition.
6
+
7
+ 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)
8
+
9
+ > **_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
10
+
11
+ ## Building from source
12
+
13
+ ### Prerequisites
14
+
15
+ * [pnpm](https://github.com/pnpm/pnpm) package manager
16
+ * [nvm](https://github.com/nvm-sh/nvm) node version manager (optional)
17
+
18
+ ```bash
19
+ nvm use
20
+ pnpm install --frozen-lockfile
21
+ pnpm build
22
+ ```
23
+
24
+ ### Start server
25
+
26
+ ```bash
27
+ pnpm stuntman
28
+ ```
29
+
30
+ ## Configuration
31
+
32
+ Stuntman uses [config](https://github.com/node-config/node-config)
33
+
34
+ You can create `config/default.json` with settings of your liking matching `ServerConfig` type
35
+
36
+ ## Running as a package
37
+
38
+ ### Install with package manager of your choice
39
+
40
+ ```bash
41
+ npm install @stuntman/server
42
+ yarn add @stuntman/server
43
+ pnpm add @stuntman/server
44
+ ```
45
+
46
+ ### Run from bin
47
+
48
+ ```bash
49
+ stuntman
50
+ yarn stuntman
51
+ node ./node_modules/.bin/stuntman
52
+ ```
53
+
54
+ ### Run programatically
55
+
56
+ ```ts
57
+ import { Mock } from '../mock';
58
+ import { serverConfig } from '@stuntman/shared';
59
+
60
+ const mock = new Mock(serverConfig);
61
+
62
+ mock.start();
63
+ ```
64
+
65
+ ### Point domain to localhost
66
+
67
+ Add some domains with `.stuntman` suffix (or `.stuntmanhttp` / `.stuntmanhttps` depending where you want to direct the traffic in proxy mode) to your `/etc/hosts` for example
68
+
69
+ ```text
70
+ 127.0.0.1 www.example.com.stuntman
71
+ ```
72
+
73
+ ### Try in browser
74
+
75
+ go to your browser and visit `http://www.example.com.stuntman:2015/` to see the proxied page
76
+ for local playground you can also use `http://www.example.com.localhost:2015`
77
+
78
+ ### Take a look at client
79
+
80
+ Mind the scope of `Stuntman.RemotableFunction` like `matches`, `modifyRequest`, `modifyResponse`.
81
+ `Stuntman.RemotableFunction.localFn` contains the function, but since it'll be executed on a remote mock server it cannot access any variables outside it's body. In order to pass variable values into the function use `Stuntman.RemotableFunction.variables` for example:
82
+
83
+ ```ts
84
+ matches: {
85
+ localFn: (req) => {
86
+ // you might need to ignore typescript errors about undefined variables in this scope
87
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
88
+ // @ts-ignore
89
+ return /http:\/\/[^/]+\/somepath$/.test(req.url) && req.url.includes(`?someparam=${myVar}`);
90
+ },
91
+ localVariables: { myVar: 'myValue' },
92
+ }
93
+ ```
94
+
95
+ You can build the rules using fluentish `ruleBuilder`
96
+
97
+ ```ts
98
+ import { Client } from './apiClient';
99
+ import { ruleBuilder } from './ruleBuilder';
100
+
101
+ const client = new Client();
102
+
103
+ const uniqueQaUserEmail = 'unique_qa_email@example.com';
104
+ const rule = ruleBuilder()
105
+ .limitedUse(2)
106
+ .onRequestToHostname('example.com')
107
+ .withSearchParam('user', uniqueQaUserEmail)
108
+ .mockResponse({
109
+ localFn: (req) => {
110
+ if (JSON.parse(req.body).email !== uniqueQaUserEmail) {
111
+ return {
112
+ status: 500,
113
+ };
114
+ }
115
+ return { status: 201 };
116
+ },
117
+ localVariables: { uniqueQaUserEmail },
118
+ });
119
+
120
+ client.addRule(rule).then((x) => console.log(x));
121
+ ```
122
+
123
+ ### Take a look at PoC of WebGUI
124
+
125
+ ....just don't look to closely, it's very much incomplete and hacky
126
+
127
+ * http://stuntman:1985/webgui/rules - rule viewer/editor
128
+ * http://stuntman:1985/webgui/traffic - traffic viewer for the rules that store traffic
package/dist/api/api.js CHANGED
@@ -26,17 +26,17 @@ class API {
26
26
  next();
27
27
  });
28
28
  this.apiApp.get('/rule', async (req, res) => {
29
- res.send((0, shared_1.stringify)(await ruleExecutor_1.ruleExecutor.getRules()));
29
+ res.send((0, shared_1.stringify)(await (0, ruleExecutor_1.getRuleExecutor)(this.options.mockUuid).getRules()));
30
30
  });
31
31
  this.apiApp.get('/rule/:ruleId', async (req, res) => {
32
- res.send((0, shared_1.stringify)(await ruleExecutor_1.ruleExecutor.getRule(req.params.ruleId)));
32
+ res.send((0, shared_1.stringify)(await (0, ruleExecutor_1.getRuleExecutor)(this.options.mockUuid).getRule(req.params.ruleId)));
33
33
  });
34
34
  this.apiApp.get('/rule/:ruleId/disable', (req, res) => {
35
- ruleExecutor_1.ruleExecutor.disableRule(req.params.ruleId);
35
+ (0, ruleExecutor_1.getRuleExecutor)(this.options.mockUuid).disableRule(req.params.ruleId);
36
36
  res.send();
37
37
  });
38
38
  this.apiApp.get('/rule/:ruleId/enable', (req, res) => {
39
- ruleExecutor_1.ruleExecutor.enableRule(req.params.ruleId);
39
+ (0, ruleExecutor_1.getRuleExecutor)(this.options.mockUuid).enableRule(req.params.ruleId);
40
40
  res.send();
41
41
  });
42
42
  this.apiApp.post('/rule', async (req, res) => {
@@ -44,11 +44,11 @@ class API {
44
44
  (0, validatiors_1.validateDeserializedRule)(deserializedRule);
45
45
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
46
46
  // @ts-ignore
47
- const rule = await ruleExecutor_1.ruleExecutor.addRule(deserializedRule);
47
+ const rule = await (0, ruleExecutor_1.getRuleExecutor)(this.options.mockUuid).addRule(deserializedRule);
48
48
  res.send((0, shared_1.stringify)(rule));
49
49
  });
50
50
  this.apiApp.get('/rule/:ruleId/remove', async (req, res) => {
51
- await ruleExecutor_1.ruleExecutor.removeRule(req.params.ruleId);
51
+ await (0, ruleExecutor_1.getRuleExecutor)(this.options.mockUuid).removeRule(req.params.ruleId);
52
52
  res.send();
53
53
  });
54
54
  this.apiApp.get('/traffic', (req, res) => {
@@ -84,19 +84,20 @@ class API {
84
84
  });
85
85
  return;
86
86
  }
87
- console.log('Application encountered a critical error. Exiting');
87
+ // eslint-disable-next-line no-console
88
+ console.log('API server encountered a critical error. Exiting');
88
89
  process.exit(1);
89
90
  });
90
- this.apiApp.set('views', __dirname + '/webgui');
91
- this.apiApp.set('view engine', 'pug');
92
91
  if (!(webGuiOptions === null || webGuiOptions === void 0 ? void 0 : webGuiOptions.disabled)) {
92
+ this.apiApp.set('views', __dirname + '/webgui');
93
+ this.apiApp.set('view engine', 'pug');
93
94
  this.initWebGui();
94
95
  }
95
96
  }
96
97
  initWebGui() {
97
98
  this.apiApp.get('/webgui/rules', async (req, res) => {
98
99
  const rules = {};
99
- for (const rule of await ruleExecutor_1.ruleExecutor.getRules()) {
100
+ for (const rule of await (0, ruleExecutor_1.getRuleExecutor)(this.options.mockUuid).getRules()) {
100
101
  rules[rule.id] = (0, serialize_javascript_1.default)((0, utils_1.liveRuleToRule)(rule), { unsafe: true });
101
102
  }
102
103
  res.render('rules', { rules: (0, utils_1.escapedSerialize)(rules), INDEX_DTS: shared_1.INDEX_DTS, ruleKeys: Object.keys(rules) });
@@ -120,7 +121,7 @@ class API {
120
121
  rule.ttlSeconds > shared_1.MAX_RULE_TTL_SECONDS) {
121
122
  throw new shared_1.AppError({ httpCode: shared_1.HttpCode.BAD_REQUEST, message: 'Invalid rule' });
122
123
  }
123
- await ruleExecutor_1.ruleExecutor.addRule({
124
+ await (0, ruleExecutor_1.getRuleExecutor)(this.options.mockUuid).addRule({
124
125
  id: rule.id,
125
126
  matches: rule.matches,
126
127
  ttlSeconds: rule.ttlSeconds,
@@ -104,7 +104,7 @@ const validateDeserializedRule = (deserializedRule) => {
104
104
  shared_1.logger.error({ ruleId: deserializedRule.id }, error);
105
105
  throw new shared_1.AppError({
106
106
  httpCode: shared_1.HttpCode.UNPROCESSABLE_ENTITY,
107
- message: 'match function returned invalid value',
107
+ message: 'match function threw an error',
108
108
  });
109
109
  }
110
110
  if (matchValidationResult !== true &&
package/dist/mock.d.ts CHANGED
@@ -8,7 +8,7 @@ import { IPUtils } from './ipUtils';
8
8
  import LRUCache from 'lru-cache';
9
9
  import { API } from './api/api';
10
10
  export declare class Mock {
11
- protected mockUuid: string;
11
+ readonly mockUuid: string;
12
12
  protected options: Stuntman.ServerConfig;
13
13
  protected mockApp: express.Express;
14
14
  protected MOCK_DOMAIN_REGEX: RegExp;
@@ -19,6 +19,7 @@ export declare class Mock {
19
19
  protected ipUtils: IPUtils | null;
20
20
  private _api;
21
21
  get apiServer(): API | null;
22
+ get ruleExecutor(): Stuntman.RuleExecutorInterface;
22
23
  constructor(options: Stuntman.ServerConfig);
23
24
  start(): void;
24
25
  stop(): void;
package/dist/mock.js CHANGED
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.Mock = void 0;
7
+ const undici_1 = require("undici");
7
8
  const https_1 = __importDefault(require("https"));
8
9
  const express_1 = __importDefault(require("express"));
9
10
  const uuid_1 = require("uuid");
@@ -57,6 +58,9 @@ class Mock {
57
58
  }
58
59
  return this._api;
59
60
  }
61
+ get ruleExecutor() {
62
+ return (0, ruleExecutor_1.getRuleExecutor)(this.mockUuid);
63
+ }
60
64
  constructor(options) {
61
65
  this.server = null;
62
66
  this.serverHttps = null;
@@ -67,7 +71,7 @@ class Mock {
67
71
  if (this.options.mock.httpsPort && (!this.options.mock.httpsKey || !this.options.mock.httpsCert)) {
68
72
  throw new Error('missing https key/cert');
69
73
  }
70
- this.MOCK_DOMAIN_REGEX = new RegExp(`(?:\\.([0-9]+))?\\.(?:(?:${this.options.mock.domain})|(?:localhost))(https?)?(:${this.options.mock.port}${this.options.mock.httpsPort ? `|${this.options.mock.httpsPort}` : ''})?(?:\\b|$)`, 'i');
74
+ this.MOCK_DOMAIN_REGEX = new RegExp(`(?:\\.([0-9]+))?\\.(?:(?:${this.options.mock.domain}(https?)?)|(?:localhost))(:${this.options.mock.port}${this.options.mock.httpsPort ? `|${this.options.mock.httpsPort}` : ''})?(?:\\b|$)`, 'i');
71
75
  this.URL_PORT_REGEX = new RegExp(`^(https?:\\/\\/[^:/]+):(?:${this.options.mock.port}${this.options.mock.httpsPort ? `|${this.options.mock.httpsPort}` : ''})(\\/.*)`, 'i');
72
76
  this.trafficStore = (0, storage_1.getTrafficStore)(this.mockUuid, this.options.storage.traffic);
73
77
  this.ipUtils =
@@ -86,7 +90,7 @@ class Mock {
86
90
  const ctx = requestContext_1.default.get(req);
87
91
  const requestUuid = (ctx === null || ctx === void 0 ? void 0 : ctx.uuid) || (0, uuid_1.v4)();
88
92
  const timestamp = Date.now();
89
- const originalHostname = req.hostname;
93
+ const originalHostname = req.headers.host || req.hostname;
90
94
  const unproxiedHostname = req.hostname.replace(this.MOCK_DOMAIN_REGEX, '');
91
95
  const isProxiedHostname = originalHostname !== unproxiedHostname;
92
96
  const originalRequest = {
@@ -95,7 +99,8 @@ class Mock {
95
99
  url: `${req.protocol}://${req.hostname}${req.originalUrl}`,
96
100
  method: req.method,
97
101
  rawHeaders: new shared_1.RawHeaders(...req.rawHeaders),
98
- ...(Buffer.isBuffer(req.body) && { body: req.body.toString('utf-8') }),
102
+ ...((Buffer.isBuffer(req.body) && { body: req.body.toString('utf-8') }) ||
103
+ (typeof req.body === 'string' && { body: req.body })),
99
104
  };
100
105
  shared_1.logger.debug(originalRequest, 'processing request');
101
106
  const logContext = {
@@ -113,7 +118,7 @@ class Mock {
113
118
  if (!isProxiedHostname) {
114
119
  this.removeProxyPort(mockEntry.modifiedRequest);
115
120
  }
116
- const matchingRule = await ruleExecutor_1.ruleExecutor.findMatchingRule(mockEntry.modifiedRequest);
121
+ const matchingRule = await (0, ruleExecutor_1.getRuleExecutor)(this.mockUuid).findMatchingRule(mockEntry.modifiedRequest);
117
122
  if (matchingRule) {
118
123
  mockEntry.mockRuleId = matchingRule.id;
119
124
  mockEntry.labels = matchingRule.labels;
@@ -170,36 +175,39 @@ class Mock {
170
175
  }
171
176
  });
172
177
  let targetResponse;
173
- const hasKeepAlive = !!mockEntry.modifiedRequest.rawHeaders
174
- .toHeaderPairs()
175
- .find((h) => /^connection$/.test(h[0]) && /^keep-alive$/.test(h[1]));
176
178
  try {
177
- targetResponse = await fetch(mockEntry.modifiedRequest.url, {
178
- redirect: 'manual',
179
- headers: mockEntry.modifiedRequest.rawHeaders
180
- .toHeaderPairs()
181
- .filter((h) => !/^connection$/.test(h[0]) && !/^keep-alive$/.test(h[1])),
179
+ const requestOptions = {
180
+ headers: mockEntry.modifiedRequest.rawHeaders,
182
181
  body: mockEntry.modifiedRequest.body,
183
- method: mockEntry.modifiedRequest.method,
184
- keepalive: !!hasKeepAlive,
185
- });
182
+ method: mockEntry.modifiedRequest.method.toUpperCase(),
183
+ };
184
+ shared_1.logger.debug({
185
+ ...logContext,
186
+ url: mockEntry.modifiedRequest.url,
187
+ ...requestOptions,
188
+ }, 'outgoing request attempt');
189
+ targetResponse = await (0, undici_1.request)(mockEntry.modifiedRequest.url, requestOptions);
190
+ }
191
+ catch (error) {
192
+ shared_1.logger.error({ ...logContext, error, request: mockEntry.modifiedRequest }, 'error fetching');
193
+ throw error;
186
194
  }
187
195
  finally {
188
196
  controller = null;
189
197
  clearTimeout(fetchTimeout);
190
198
  }
191
- const targetResponseBuffer = Buffer.from(await targetResponse.arrayBuffer());
199
+ const targetResponseBuffer = Buffer.from(await targetResponse.body.arrayBuffer());
192
200
  const originalResponse = {
193
201
  timestamp: Date.now(),
194
202
  body: targetResponseBuffer.toString('binary'),
195
- status: targetResponse.status,
196
- rawHeaders: new shared_1.RawHeaders(...Array.from(targetResponse.headers.entries()).flatMap(([key, value]) => [key, value])),
203
+ status: targetResponse.statusCode,
204
+ rawHeaders: shared_1.RawHeaders.fromHeadersRecord(targetResponse.headers),
197
205
  };
198
206
  shared_1.logger.debug({ ...logContext, originalResponse }, 'received response');
199
207
  mockEntry.originalResponse = originalResponse;
200
208
  let modifedResponse = {
201
209
  ...originalResponse,
202
- rawHeaders: new shared_1.RawHeaders(...Array.from(targetResponse.headers.entries()).flatMap(([key, value]) => {
210
+ rawHeaders: new shared_1.RawHeaders(...Array.from(originalResponse.rawHeaders.toHeaderPairs()).flatMap(([key, value]) => {
203
211
  // 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)
204
212
  return [
205
213
  key,
@@ -227,7 +235,7 @@ class Mock {
227
235
  if (/^content-(?:length|encoding)$/i.test(header[0])) {
228
236
  continue;
229
237
  }
230
- res.setHeader(header[0], header[1]);
238
+ res.setHeader(header[0], isProxiedHostname ? header[1].replace(unproxiedHostname, originalHostname) : header[1]);
231
239
  }
232
240
  }
233
241
  res.end(Buffer.from(modifedResponse.body, 'binary'));
@@ -242,7 +250,8 @@ class Mock {
242
250
  });
243
251
  return;
244
252
  }
245
- console.log('mock encountered a critical error. exiting');
253
+ // eslint-disable-next-line no-console
254
+ console.error('mock server encountered a critical error. exiting');
246
255
  process.exit(1);
247
256
  });
248
257
  }
@@ -1,6 +1,7 @@
1
1
  import type * as Stuntman from '@stuntman/shared';
2
- declare class RuleExecutor {
2
+ declare class RuleExecutor implements Stuntman.RuleExecutorInterface {
3
3
  private _rules;
4
+ private rulesLock;
4
5
  private get enabledRules();
5
6
  constructor(rules?: Stuntman.Rule[]);
6
7
  private hasExpired;
@@ -17,5 +18,5 @@ declare class RuleExecutor {
17
18
  getRules(): Promise<readonly Stuntman.LiveRule[]>;
18
19
  getRule(id: string): Promise<Stuntman.LiveRule | undefined>;
19
20
  }
20
- export declare const ruleExecutor: RuleExecutor;
21
+ export declare const getRuleExecutor: (mockUuid: string) => RuleExecutor;
21
22
  export {};
@@ -3,11 +3,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.ruleExecutor = void 0;
6
+ exports.getRuleExecutor = void 0;
7
7
  const await_lock_1 = __importDefault(require("await-lock"));
8
8
  const shared_1 = require("@stuntman/shared");
9
9
  const rules_1 = require("./rules");
10
- const rulesLock = new await_lock_1.default();
10
+ const ruleExecutors = {};
11
11
  const transformMockRuleToLive = (rule) => {
12
12
  var _a;
13
13
  return {
@@ -28,6 +28,7 @@ class RuleExecutor {
28
28
  .sort((a, b) => { var _a, _b; return ((_a = a.priority) !== null && _a !== void 0 ? _a : shared_1.DEFAULT_RULE_PRIORITY) - ((_b = b.priority) !== null && _b !== void 0 ? _b : shared_1.DEFAULT_RULE_PRIORITY); });
29
29
  }
30
30
  constructor(rules) {
31
+ this.rulesLock = new await_lock_1.default();
31
32
  this._rules = (rules || []).map(transformMockRuleToLive);
32
33
  }
33
34
  hasExpired() {
@@ -38,7 +39,7 @@ class RuleExecutor {
38
39
  if (!this.hasExpired()) {
39
40
  return;
40
41
  }
41
- await rulesLock.acquireAsync();
42
+ await this.rulesLock.acquireAsync();
42
43
  const now = Date.now();
43
44
  try {
44
45
  this._rules = this._rules.filter((r) => {
@@ -50,12 +51,12 @@ class RuleExecutor {
50
51
  });
51
52
  }
52
53
  finally {
53
- await rulesLock.release();
54
+ await this.rulesLock.release();
54
55
  }
55
56
  }
56
57
  async addRule(rule, overwrite) {
57
58
  await this.cleanUpExpired();
58
- await rulesLock.acquireAsync();
59
+ await this.rulesLock.acquireAsync();
59
60
  try {
60
61
  if (this._rules.some((r) => r.id === rule.id)) {
61
62
  if (!overwrite) {
@@ -69,7 +70,7 @@ class RuleExecutor {
69
70
  return liveRule;
70
71
  }
71
72
  finally {
72
- await rulesLock.release();
73
+ await this.rulesLock.release();
73
74
  }
74
75
  }
75
76
  _removeRule(ruleOrId) {
@@ -83,12 +84,12 @@ class RuleExecutor {
83
84
  }
84
85
  async removeRule(ruleOrId) {
85
86
  await this.cleanUpExpired();
86
- await rulesLock.acquireAsync();
87
+ await this.rulesLock.acquireAsync();
87
88
  try {
88
89
  this._removeRule(ruleOrId);
89
90
  }
90
91
  finally {
91
- await rulesLock.release();
92
+ await this.rulesLock.release();
92
93
  }
93
94
  }
94
95
  enableRule(ruleOrId) {
@@ -112,11 +113,17 @@ class RuleExecutor {
112
113
  requestId: request.id,
113
114
  };
114
115
  const matchingRule = this.enabledRules.find((rule) => {
115
- const matchResult = rule.matches(request);
116
- if (typeof matchResult === 'boolean') {
117
- return matchResult;
116
+ try {
117
+ const matchResult = rule.matches(request);
118
+ shared_1.logger.trace({ ...logContext, matchResult }, `rule match attempt for ${rule.id}`);
119
+ if (typeof matchResult === 'boolean') {
120
+ return matchResult;
121
+ }
122
+ return matchResult.result;
123
+ }
124
+ catch (error) {
125
+ shared_1.logger.error({ ...logContext, ruleId: rule === null || rule === void 0 ? void 0 : rule.id, error }, 'error in rule match function');
118
126
  }
119
- return matchResult.result;
120
127
  });
121
128
  if (!matchingRule) {
122
129
  shared_1.logger.debug(logContext, 'no matching rule found');
@@ -124,7 +131,7 @@ class RuleExecutor {
124
131
  }
125
132
  const matchResult = matchingRule.matches(request);
126
133
  logContext.ruleId = matchingRule.id;
127
- shared_1.logger.debug(logContext, 'matching rule found');
134
+ shared_1.logger.debug({ ...logContext, matchResultMessage: typeof matchResult !== 'boolean' ? matchResult.description : null }, 'found matching rule');
128
135
  const matchingRuleClone = Object.freeze(Object.assign({}, matchingRule));
129
136
  ++matchingRule.counter;
130
137
  logContext.ruleCounter = matchingRule.counter;
@@ -169,4 +176,10 @@ class RuleExecutor {
169
176
  return this._rules.find((r) => r.id === id);
170
177
  }
171
178
  }
172
- exports.ruleExecutor = new RuleExecutor(rules_1.DEFAULT_RULES.map((r) => ({ ...r, ttlSeconds: Infinity })));
179
+ const getRuleExecutor = (mockUuid) => {
180
+ if (!ruleExecutors[mockUuid]) {
181
+ ruleExecutors[mockUuid] = new RuleExecutor(rules_1.DEFAULT_RULES.map((r) => ({ ...r, ttlSeconds: Infinity })));
182
+ }
183
+ return ruleExecutors[mockUuid];
184
+ };
185
+ exports.getRuleExecutor = getRuleExecutor;
package/package.json CHANGED
@@ -1,12 +1,16 @@
1
1
  {
2
2
  "name": "@stuntman/server",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Stuntman - HTTP proxy / mock server with API",
5
5
  "main": "dist/index.js",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "https://github.com/andrzej-woof/stuntman.git"
9
9
  },
10
+ "homepage": "https://github.com/andrzej-woof/stuntman#readme",
11
+ "bugs": {
12
+ "url": "https://github.com/andrzej-woof/stuntman/issues"
13
+ },
10
14
  "keywords": [
11
15
  "proxy",
12
16
  "mock",
@@ -28,23 +32,32 @@
28
32
  "author": "Andrzej Pasterczyk",
29
33
  "license": "MIT",
30
34
  "dependencies": {
31
- "@stuntman/shared": "^0.1.0",
35
+ "@stuntman/shared": "^0.1.2",
32
36
  "await-lock": "2.2.2",
33
37
  "express": "5.0.0-beta.1",
34
38
  "lru-cache": "7.16.0",
35
39
  "object-sizeof": "2.6.1",
36
- "pug": "^3.0.2",
40
+ "pug": "3.0.2",
37
41
  "serialize-javascript": "6.0.1",
42
+ "undici": "5.20.0",
38
43
  "uuid": "9.0.0"
39
44
  },
40
45
  "devDependencies": {
46
+ "@prettier/plugin-pug": "2.4.1",
41
47
  "@types/express": "4.17.17",
42
48
  "@types/serialize-javascript": "5.0.2",
43
- "@types/uuid": "9.0.0"
49
+ "@types/uuid": "9.0.0",
50
+ "prettier": "2.8.4"
44
51
  },
45
52
  "bin": {
46
53
  "stuntman": "./dist/bin/stuntman.js"
47
54
  },
55
+ "files": [
56
+ "dist/",
57
+ "README.md",
58
+ "LICENSE",
59
+ "CHANGELOG.md"
60
+ ],
48
61
  "scripts": {
49
62
  "test": "echo \"Error: no test specified\" && exit 1",
50
63
  "clean": "rm -fr dist",
@@ -1,23 +0,0 @@
1
- {
2
- "stuntman": {
3
- "mock": {
4
- "domain": "stuntman",
5
- "port": 2015,
6
- "timeout": 60000,
7
- "externalDns": ["8.8.8.8", "1.1.1.1"]
8
- },
9
- "api": {
10
- "port": 1985,
11
- "disabled": false
12
- },
13
- "webgui": {
14
- "disabled": false
15
- },
16
- "storage": {
17
- "traffic": {
18
- "limitCount": 500,
19
- "limitSize": 524288000
20
- }
21
- }
22
- }
23
- }
package/config/test.json DELETED
@@ -1,26 +0,0 @@
1
- {
2
- "stuntman": {
3
- "mock": {
4
- "domain": "stuntman",
5
- "port": 80,
6
- "httpsPort": 443,
7
- "httpsCert": "-----BEGIN CERTIFICATE-----\nMIIC7jCCAdYCCQDHj59tQDx5iTANBgkqhkiG9w0BAQsFADA5MREwDwYDVQQKDAhz\ndHVudG1hbjERMA8GA1UECwwIc3R1bnRtYW4xETAPBgNVBAMMCHN0dW50bWFuMB4X\nDTIzMDIxNjE1MzQzNFoXDTI0MDIxNjE1MzQzNFowOTERMA8GA1UECgwIc3R1bnRt\nYW4xETAPBgNVBAsMCHN0dW50bWFuMREwDwYDVQQDDAhzdHVudG1hbjCCASIwDQYJ\nKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK23MCp+grcLiyFzxvSU9iOFReIKZRVN\n0+DTlxl/sN4pvYlt7Hji7n/Yh55m7ACF1j8LRjaU6MOYIGofF4lbgA3nEbZVNJoH\nAtlSjk/JZc4LnDFinAWWxna2FpwrpfEnknIJ2B7fjtk5dM/WzSMn1MdPiC9V9Ee0\nPFe0PlpFl/hQSd4/VXfLxNy3bzW5AXa5CuTVRaEmts21TbL4VYe6KNPMkbTe+NJh\nwBrwVqS5lB3Z5racxOn5Dw5g5NuHgSA6LvUxdKhdkPs7y7e87XADadakibd9u02j\nimJwQih31O4rPJINLDYhVj5muyPGw9lpxEQ7UthxRxuzodm4F+5ZM1ECAwEAATAN\nBgkqhkiG9w0BAQsFAAOCAQEAhqISsPYrM+G37vw8I6YCWNSW0dJrpvfNpiz6oXal\nicIxOJz06qg0HsEXoWhdneo9PSA66KAmdcTplwPJtZ486izwD3F46+TZLkesOuCS\nDW9ihEPY5XPyjZDSz2J4EwBD4pH0AFeXSVFDIyXCSoWypSKjSq5lm7hOQuCOLkkm\ntlsptc4R3MGuvNYKSDBvxCjTy76jlXpMWINdVV18M4bVmRnVj+vYlQbYP5tCYGUm\nnzlFVi0dCLdvS2LGiKhARLQILP9YzC86a9UDPyWs703Zvqm5cnknCLEpjaR8dhd8\njAcDPHUe1RkR8wGrGwkkrIQfe8r8ovEylJgLT8HtNLqEXg==\n-----END CERTIFICATE-----\n",
8
- "httpsKey": "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEArbcwKn6CtwuLIXPG9JT2I4VF4gplFU3T4NOXGX+w3im9iW3s\neOLuf9iHnmbsAIXWPwtGNpTow5ggah8XiVuADecRtlU0mgcC2VKOT8llzgucMWKc\nBZbGdrYWnCul8SeScgnYHt+O2Tl0z9bNIyfUx0+IL1X0R7Q8V7Q+WkWX+FBJ3j9V\nd8vE3LdvNbkBdrkK5NVFoSa2zbVNsvhVh7oo08yRtN740mHAGvBWpLmUHdnmtpzE\n6fkPDmDk24eBIDou9TF0qF2Q+zvLt7ztcANp1qSJt327TaOKYnBCKHfU7is8kg0s\nNiFWPma7I8bD2WnERDtS2HFHG7Oh2bgX7lkzUQIDAQABAoIBAH+L7GKXBvTNFfeW\n4XK9WMgV14yzIyr0POhrkxrWxY8pSI/6VNEhlgnqexET8p4jpn4dkg0LYqgSL2Kb\nt5VTyH7stPWSNBAPq8jTM8hjUEtr/N/JzlLQNKH+6jT6W1noOz9d+QAaFvFpnVnp\nFi+E1FcPDyfqTXTEYjXnEo0HYiCf5RAIw64VYRR3OfmCWFHjwz3sDbhvTW1bYfZA\nddwViTIoELfebF3cCLg4zWVkyCeZRpmbRJaeyttrqgOLbjD6tn7SFkZVJ8v4BBoK\nZSdRaFrzPrRxBYcLRbXlIaNp0QeM/NkSBZIg63vhwZydR+Y3wDE4mCzZ8UqiOyLC\nGIdHky0CgYEA1yY1WJ1ubOB67w4dckWMw9SOw00AP/i5o0j4kFz8nkcWhx8W6rJW\nrZrPq6yAZ6ffzR4aIwrq0W22nHB20sOvamO7UH7TzG3BuM787ChMqB0bEy98hKHZ\nHTAOKyqG9A50N+QNicUS9gXDWk67/i3j19bw8rLWMrNsQDM9SZHYvaMCgYEAzrMB\nz+ofYQ04z7gIKmlOK+lG9pT3JyNVdnLnHFhil4Q4AKMDDIsoKkDqjE2jJeTNzE+G\nIGlZyiBK6sArTJdNthrvrJLmvJJfVEGWpSnShNxDf+gzIJeUoA/TCJvUac1CXd8g\nHwnhR3Dp1I3SZwm32Hig/vzxe8Dd+YONPoNm8nsCgYBJ1pcgXodzXmdSe+mnOi9h\nViXY6ShYzCgJ3hVQllksiQE2Rnk6+xG8axEyvfUjnf21C8u0kx6b2ad+cSqWkwo0\n3R2ANsbBtjlyD7fF5N7KI5MTNozpiBJXbhKuxd2jDQLd26q5yaUEQl4VNEhYp682\neFIhOTdCF0njjrJN+XwFOQKBgF6j2aWQBhQS0LtTAPIiSzeR1PscE9notL3KOIVi\n9ql3UYkBGmlI4fgOxxW8ioHUNGJi2v/GHOWOSZ8Yo/qqoFtMFAdJL7qRrnJOoaI3\n9vr8Oy+6aoZ2wQdUl4SujOBwqf1/Jx7vECX8ziOTWA3zhijoepalzA+krD4NfMNt\nuNo3AoGBAISjSwEUrpR3II4uj8UuPZaVFNvACujaJLWnKcGwFvQsn/2GTcfzdzm/\nTxwHwpRZLJhFFpboFGVW5pX7g9leqdZERGlqPpTkCSAiQ0eFRbHsIhX7TS9VyYMz\n7iLq9dccEn5DDnUVXWkZxz2h0yG8/nTlNGli0BL2O+DEWZ9b32xE\n-----END RSA PRIVATE KEY-----\n",
9
- "timeout": 60000,
10
- "externalDns": ["8.8.8.8", "1.1.1.1"]
11
- },
12
- "api": {
13
- "port": 1985,
14
- "disabled": false
15
- },
16
- "webgui": {
17
- "disabled": false
18
- },
19
- "storage": {
20
- "traffic": {
21
- "limitCount": 500,
22
- "limitSize": 524288000
23
- }
24
- }
25
- }
26
- }