@stuntman/server 0.1.5 → 0.1.7

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.
Files changed (43) hide show
  1. package/README.md +3 -3
  2. package/package.json +8 -6
  3. package/src/api/api.ts +69 -48
  4. package/src/api/utils.ts +26 -26
  5. package/src/api/validators.ts +44 -35
  6. package/src/api/webgui/rules.pug +4 -2
  7. package/src/api/webgui/style.css +3 -3
  8. package/src/api/webgui/traffic.pug +1 -0
  9. package/src/bin/stuntman.ts +2 -2
  10. package/src/ipUtils.ts +1 -1
  11. package/src/mock.ts +137 -153
  12. package/src/ruleExecutor.ts +2 -1
  13. package/src/rules/index.ts +5 -5
  14. package/src/storage.ts +2 -2
  15. package/dist/api/api.d.ts +0 -22
  16. package/dist/api/api.js +0 -188
  17. package/dist/api/utils.d.ts +0 -4
  18. package/dist/api/utils.js +0 -69
  19. package/dist/api/validators.d.ts +0 -3
  20. package/dist/api/validators.js +0 -118
  21. package/dist/api/webgui/rules.pug +0 -145
  22. package/dist/api/webgui/style.css +0 -28
  23. package/dist/api/webgui/traffic.pug +0 -37
  24. package/dist/bin/stuntman.d.ts +0 -2
  25. package/dist/bin/stuntman.js +0 -7
  26. package/dist/index.d.ts +0 -1
  27. package/dist/index.js +0 -5
  28. package/dist/ipUtils.d.ts +0 -17
  29. package/dist/ipUtils.js +0 -101
  30. package/dist/mock.d.ts +0 -30
  31. package/dist/mock.js +0 -321
  32. package/dist/requestContext.d.ts +0 -9
  33. package/dist/requestContext.js +0 -18
  34. package/dist/ruleExecutor.d.ts +0 -22
  35. package/dist/ruleExecutor.js +0 -187
  36. package/dist/rules/catchAll.d.ts +0 -2
  37. package/dist/rules/catchAll.js +0 -15
  38. package/dist/rules/echo.d.ts +0 -2
  39. package/dist/rules/echo.js +0 -15
  40. package/dist/rules/index.d.ts +0 -3
  41. package/dist/rules/index.js +0 -70
  42. package/dist/storage.d.ts +0 -4
  43. package/dist/storage.js +0 -42
package/README.md CHANGED
@@ -31,7 +31,7 @@ pnpm stuntman
31
31
 
32
32
  Stuntman uses [config](https://github.com/node-config/node-config)
33
33
 
34
- You can create `config/default.json` with settings of your liking matching `ServerConfig` type
34
+ You can create `config/default.json` with settings of your liking matching `Stuntman.Config` type
35
35
 
36
36
  ## Running as a package
37
37
 
@@ -55,9 +55,9 @@ node ./node_modules/.bin/stuntman
55
55
 
56
56
  ```ts
57
57
  import { Mock } from '../mock';
58
- import { serverConfig } from '@stuntman/shared';
58
+ import { stuntmanConfig } from '@stuntman/shared';
59
59
 
60
- const mock = new Mock(serverConfig);
60
+ const mock = new Mock(stuntmanConfig);
61
61
 
62
62
  mock.start();
63
63
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stuntman/server",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "Stuntman - HTTP proxy / mock server with API",
5
5
  "main": "dist/index.js",
6
6
  "repository": {
@@ -41,17 +41,19 @@
41
41
  "pug": "3.0.2",
42
42
  "serialize-javascript": "6.0.1",
43
43
  "ts-import": "4.0.0-beta.10",
44
- "typescript": "4.9.5",
45
44
  "undici": "5.20.0",
46
45
  "uuid": "9.0.0"
47
46
  },
48
47
  "devDependencies": {
48
+ "@jest/globals": "29.4.3",
49
49
  "@prettier/plugin-pug": "2.4.1",
50
50
  "@types/express": "4.17.17",
51
51
  "@types/glob": "8.1.0",
52
52
  "@types/serialize-javascript": "5.0.2",
53
53
  "@types/uuid": "9.0.0",
54
- "prettier": "2.8.4"
54
+ "jest": "29.4.3",
55
+ "prettier": "2.8.4",
56
+ "typescript": "4.9.5"
55
57
  },
56
58
  "bin": {
57
59
  "stuntman": "./dist/bin/stuntman.js"
@@ -64,11 +66,11 @@
64
66
  "CHANGELOG.md"
65
67
  ],
66
68
  "scripts": {
67
- "test": "echo \"Error: no test specified\" && exit 1",
69
+ "test": "SUPPRESS_NO_CONFIG_WARNING=1 jest",
68
70
  "clean": "rm -fr dist",
69
71
  "build": "tsc && cp -rv src/api/webgui dist/api",
70
- "lint": "prettier --check . && eslint . --ext ts",
71
- "lint:fix": "prettier --write ./{src,test} && eslint ./{src,test} --ext ts --fix",
72
+ "lint": "prettier --check \"./{src,test}/**/*\" && eslint \"./{src,test}/**/*\"",
73
+ "lint:fix": "prettier --write \"./{src,test}/**/*\" && eslint \"./{src,test}/**/*\" --fix",
72
74
  "start": "node ./dist/bin/stuntman.js",
73
75
  "start:dev": "nodemon --watch 'src/**/*.ts' --exec 'ts-node' ./src/bin/stuntman.ts",
74
76
  "start:debug": "node --inspect-brk=0.0.0.0 ./node_modules/.bin/ts-node --transpile-only ./src/bin/stuntman.ts"
package/src/api/api.ts CHANGED
@@ -19,67 +19,81 @@ const API_KEY_HEADER = 'x-api-key';
19
19
 
20
20
  export class API {
21
21
  protected options: Required<ApiOptions>;
22
- protected apiApp: ExpressServer;
22
+ protected webGuiOptions: Stuntman.WebGuiConfig;
23
+ protected apiApp: ExpressServer | null = null;
23
24
  trafficStore: LRUCache<string, Stuntman.LogEntry>;
24
25
  server: http.Server | null = null;
25
- auth: (req: Request, type: 'read' | 'write') => void;
26
- authReadOnly: (req: Request, res: Response, next: NextFunction) => void;
27
- authReadWrite: (req: Request, res: Response, next: NextFunction) => void;
28
26
 
29
- constructor(options: ApiOptions, webGuiOptions?: Stuntman.WebGuiConfig) {
27
+ constructor(options: ApiOptions, webGuiOptions: Stuntman.WebGuiConfig = { disabled: false }) {
30
28
  if (!options.apiKeyReadOnly !== !options.apiKeyReadWrite) {
31
29
  throw new Error('apiKeyReadOnly and apiKeyReadWrite options need to be set either both or none');
32
30
  }
33
31
  this.options = options;
32
+ this.webGuiOptions = webGuiOptions;
34
33
 
35
34
  this.trafficStore = getTrafficStore(this.options.mockUuid);
36
- this.apiApp = express();
37
-
38
- this.apiApp.use(express.json());
39
- this.apiApp.use(express.text());
35
+ this.auth = this.auth.bind(this);
36
+ this.authReadOnly = this.authReadOnly.bind(this);
37
+ this.authReadWrite = this.authReadWrite.bind(this);
38
+ }
40
39
 
41
- this.auth = (req: Request, type: 'read' | 'write'): void => {
42
- if (!this.options.apiKeyReadOnly && !this.options.apiKeyReadWrite) {
43
- return;
44
- }
45
- const hasValidReadKey = req.header(API_KEY_HEADER) === this.options.apiKeyReadOnly;
46
- const hasValidWriteKey = req.header(API_KEY_HEADER) === this.options.apiKeyReadWrite;
47
- const hasValidKey = type === 'read' ? hasValidReadKey || hasValidWriteKey : hasValidWriteKey;
48
- if (!hasValidKey) {
49
- throw new AppError({ httpCode: HttpCode.UNAUTHORIZED, message: 'unauthorized' });
50
- }
40
+ private auth(req: Request, type: 'read' | 'write'): void {
41
+ if (!this.options.apiKeyReadOnly && !this.options.apiKeyReadWrite) {
51
42
  return;
52
- };
43
+ }
44
+ const hasValidReadKey = req.header(API_KEY_HEADER) === this.options.apiKeyReadOnly;
45
+ const hasValidWriteKey = req.header(API_KEY_HEADER) === this.options.apiKeyReadWrite;
46
+ const hasValidKey = type === 'read' ? hasValidReadKey || hasValidWriteKey : hasValidWriteKey;
47
+ if (!hasValidKey) {
48
+ throw new AppError({ httpCode: HttpCode.UNAUTHORIZED, message: 'unauthorized' });
49
+ }
50
+ return;
51
+ }
53
52
 
54
- this.authReadOnly = (req: Request, res: Response, next: NextFunction): void => {
55
- this.auth(req, 'read');
56
- next();
57
- };
53
+ protected authReadOnly(req: Request, _res: Response, next: NextFunction): void {
54
+ this.auth(req, 'read');
55
+ next();
56
+ }
58
57
 
59
- this.authReadWrite = (req: Request, res: Response, next: NextFunction): void => {
60
- this.auth(req, 'write');
61
- next();
62
- };
58
+ protected authReadWrite(req: Request, _res: Response, next: NextFunction): void {
59
+ this.auth(req, 'write');
60
+ next();
61
+ }
62
+
63
+ private initApi() {
64
+ this.apiApp = express();
65
+
66
+ this.apiApp.use(express.json());
67
+ this.apiApp.use(express.text());
63
68
 
64
- this.apiApp.use((req: Request, res: Response, next: NextFunction) => {
69
+ this.apiApp.use((req: Request, _res: Response, next: NextFunction) => {
65
70
  RequestContext.bind(req, this.options.mockUuid);
66
71
  next();
67
72
  });
68
73
 
69
- this.apiApp.get('/rule', this.authReadOnly, async (req, res) => {
74
+ this.apiApp.get('/rule', this.authReadOnly.bind, async (_req, res) => {
70
75
  res.send(stringify(await getRuleExecutor(this.options.mockUuid).getRules()));
71
76
  });
72
77
 
73
78
  this.apiApp.get('/rule/:ruleId', this.authReadOnly, async (req, res) => {
79
+ if (!req.params.ruleId) {
80
+ throw new AppError({ httpCode: HttpCode.BAD_REQUEST, message: 'missing ruleId' });
81
+ }
74
82
  res.send(stringify(await getRuleExecutor(this.options.mockUuid).getRule(req.params.ruleId)));
75
83
  });
76
84
 
77
85
  this.apiApp.get('/rule/:ruleId/disable', this.authReadWrite, (req, res) => {
86
+ if (!req.params.ruleId) {
87
+ throw new AppError({ httpCode: HttpCode.BAD_REQUEST, message: 'missing ruleId' });
88
+ }
78
89
  getRuleExecutor(this.options.mockUuid).disableRule(req.params.ruleId);
79
90
  res.send();
80
91
  });
81
92
 
82
93
  this.apiApp.get('/rule/:ruleId/enable', this.authReadWrite, (req, res) => {
94
+ if (!req.params.ruleId) {
95
+ throw new AppError({ httpCode: HttpCode.BAD_REQUEST, message: 'missing ruleId' });
96
+ }
83
97
  getRuleExecutor(this.options.mockUuid).enableRule(req.params.ruleId);
84
98
  res.send();
85
99
  });
@@ -98,35 +112,41 @@ export class API {
98
112
  );
99
113
 
100
114
  this.apiApp.get('/rule/:ruleId/remove', this.authReadWrite, async (req, res) => {
115
+ if (!req.params.ruleId) {
116
+ throw new AppError({ httpCode: HttpCode.BAD_REQUEST, message: 'missing ruleId' });
117
+ }
101
118
  await getRuleExecutor(this.options.mockUuid).removeRule(req.params.ruleId);
102
119
  res.send();
103
120
  });
104
121
 
105
- this.apiApp.get('/traffic', this.authReadOnly, (req, res) => {
106
- const serializedTraffic: Record<string, Stuntman.LogEntry> = {};
107
- for (const [key, value] of this.trafficStore.entries()) {
108
- serializedTraffic[key] = value;
122
+ this.apiApp.get('/traffic', this.authReadOnly, (_req, res) => {
123
+ const serializedTraffic: Stuntman.LogEntry[] = [];
124
+ for (const value of this.trafficStore.values()) {
125
+ serializedTraffic.push(value);
109
126
  }
110
127
  res.json(serializedTraffic);
111
128
  });
112
129
 
113
130
  this.apiApp.get('/traffic/:ruleIdOrLabel', this.authReadOnly, (req, res) => {
114
- const serializedTraffic: Record<string, Stuntman.LogEntry> = {};
115
- for (const [key, value] of this.trafficStore.entries()) {
131
+ if (!req.params.ruleIdOrLabel) {
132
+ throw new AppError({ httpCode: HttpCode.BAD_REQUEST, message: 'missing ruleIdOrLabel' });
133
+ }
134
+ const serializedTraffic: Stuntman.LogEntry[] = [];
135
+ for (const value of this.trafficStore.values()) {
116
136
  if (value.mockRuleId === req.params.ruleIdOrLabel || (value.labels || []).includes(req.params.ruleIdOrLabel)) {
117
- serializedTraffic[key] = value;
137
+ serializedTraffic.push(value);
118
138
  }
119
139
  }
120
140
  res.json(serializedTraffic);
121
141
  });
122
142
 
123
- if (!webGuiOptions?.disabled) {
143
+ if (!this.webGuiOptions.disabled) {
124
144
  this.apiApp.set('views', __dirname + '/webgui');
125
145
  this.apiApp.set('view engine', 'pug');
126
146
  this.initWebGui();
127
147
  }
128
148
 
129
- this.apiApp.all(/.*/, (req: Request, res: Response) => res.status(404).send());
149
+ this.apiApp.all(/.*/, (_req: Request, res: Response) => res.status(404).send());
130
150
 
131
151
  this.apiApp.use((error: Error | AppError, req: Request, res: Response) => {
132
152
  const ctx: RequestContext | null = RequestContext.get(req);
@@ -152,7 +172,10 @@ export class API {
152
172
  }
153
173
 
154
174
  private initWebGui() {
155
- this.apiApp.get('/webgui/rules', this.authReadOnly, async (req, res) => {
175
+ if (!this.apiApp) {
176
+ throw new Error('initialization error');
177
+ }
178
+ this.apiApp.get('/webgui/rules', this.authReadOnly, async (_req, res) => {
156
179
  const rules: Record<string, string> = {};
157
180
  for (const rule of await getRuleExecutor(this.options.mockUuid).getRules()) {
158
181
  rules[rule.id] = serializeJavascript(liveRuleToRule(rule), { unsafe: true });
@@ -160,7 +183,7 @@ export class API {
160
183
  res.render('rules', { rules: escapedSerialize(rules), INDEX_DTS, ruleKeys: Object.keys(rules) });
161
184
  });
162
185
 
163
- this.apiApp.get('/webgui/traffic', this.authReadOnly, async (req, res) => {
186
+ this.apiApp.get('/webgui/traffic', this.authReadOnly, async (_req, res) => {
164
187
  const serializedTraffic: Stuntman.LogEntry[] = [];
165
188
  for (const value of this.trafficStore.values()) {
166
189
  serializedTraffic.push(value);
@@ -190,13 +213,7 @@ export class API {
190
213
  id: rule.id,
191
214
  matches: rule.matches,
192
215
  ttlSeconds: rule.ttlSeconds,
193
- ...(rule.actions && {
194
- actions: {
195
- ...(rule.actions.mockResponse
196
- ? { mockResponse: rule.actions.mockResponse }
197
- : { modifyRequest: rule.actions.modifyRequest, modifyResponse: rule.actions.modifyResponse }),
198
- },
199
- }),
216
+ actions: rule.actions,
200
217
  ...(rule.disableAfterUse !== undefined && { disableAfterUse: rule.disableAfterUse }),
201
218
  ...(rule.isEnabled !== undefined && { isEnabled: rule.isEnabled }),
202
219
  ...(rule.labels !== undefined && { labels: rule.labels }),
@@ -214,6 +231,10 @@ export class API {
214
231
  if (this.server) {
215
232
  throw new Error('mock server already started');
216
233
  }
234
+ this.initApi();
235
+ if (!this.apiApp) {
236
+ throw new Error('initialization error');
237
+ }
217
238
  this.server = this.apiApp.listen(this.options.port, () => {
218
239
  logger.info(`API listening on ${this.options.port}`);
219
240
  });
package/src/api/utils.ts CHANGED
@@ -11,6 +11,7 @@ export const deserializeRule = (serializedRule: Stuntman.SerializedRule): Stuntm
11
11
  id: serializedRule.id,
12
12
  matches: (req: Stuntman.Request) => new Function('____arg0', serializedRule.matches.remoteFn)(req),
13
13
  ttlSeconds: serializedRule.ttlSeconds,
14
+ actions: { mockResponse: { status: 200 } },
14
15
  ...(serializedRule.disableAfterUse !== undefined && { disableAfterUse: serializedRule.disableAfterUse }),
15
16
  ...(serializedRule.removeAfterUse !== undefined && { removeAfterUse: serializedRule.removeAfterUse }),
16
17
  ...(serializedRule.labels !== undefined && { labels: serializedRule.labels }),
@@ -18,37 +19,36 @@ export const deserializeRule = (serializedRule: Stuntman.SerializedRule): Stuntm
18
19
  ...(serializedRule.isEnabled !== undefined && { isEnabled: serializedRule.isEnabled }),
19
20
  ...(serializedRule.storeTraffic !== undefined && { storeTraffic: serializedRule.storeTraffic }),
20
21
  };
21
- if (serializedRule.actions) {
22
- // TODO store the original localFn and variables sent from client for web UI editing maybe
23
- if (serializedRule.actions.mockResponse) {
24
- rule.actions = {
25
- mockResponse:
26
- 'remoteFn' in serializedRule.actions.mockResponse
27
- ? (req: Stuntman.Request) =>
28
- new Function(
29
- '____arg0',
30
- (serializedRule.actions?.mockResponse as Stuntman.SerializedRemotableFunction).remoteFn
31
- )(req)
32
- : serializedRule.actions.mockResponse,
33
- };
34
- } else {
35
- rule.actions = {};
36
- if (serializedRule.actions.modifyRequest) {
37
- rule.actions.modifyRequest = (req: Stuntman.Request) =>
22
+ // TODO store the original localFn and variables sent from client for web UI editing maybe
23
+ if (serializedRule.actions.mockResponse) {
24
+ rule.actions = {
25
+ mockResponse:
26
+ 'remoteFn' in serializedRule.actions.mockResponse
27
+ ? (req: Stuntman.Request) =>
28
+ new Function(
29
+ '____arg0',
30
+ (serializedRule.actions.mockResponse as Stuntman.SerializedRemotableFunction).remoteFn
31
+ )(req)
32
+ : serializedRule.actions.mockResponse,
33
+ };
34
+ } else {
35
+ rule.actions = {
36
+ ...(serializedRule.actions.modifyRequest && {
37
+ modifyRequest: ((req: Stuntman.Request) =>
38
38
  new Function(
39
39
  '____arg0',
40
- (serializedRule.actions?.modifyRequest as Stuntman.SerializedRemotableFunction).remoteFn
41
- )(req);
42
- }
43
- if (serializedRule.actions.modifyResponse) {
44
- rule.actions.modifyResponse = (req: Stuntman.Request, res: Stuntman.Response) =>
40
+ (serializedRule.actions.modifyRequest as Stuntman.SerializedRemotableFunction).remoteFn
41
+ )(req)) as Stuntman.RequestManipulationFn,
42
+ }),
43
+ ...(serializedRule.actions.modifyResponse && {
44
+ modifyResponse: ((req: Stuntman.Request, res: Stuntman.Response) =>
45
45
  new Function(
46
46
  '____arg0',
47
47
  '____arg1',
48
- (serializedRule.actions?.modifyResponse as Stuntman.SerializedRemotableFunction).remoteFn
49
- )(req, res);
50
- }
51
- }
48
+ (serializedRule.actions.modifyResponse as Stuntman.SerializedRemotableFunction).remoteFn
49
+ )(req, res)) as Stuntman.ResponseManipulationFn,
50
+ }),
51
+ } as Stuntman.Actions;
52
52
  }
53
53
  logger.debug(rule, 'deserialized rule');
54
54
  return rule;
@@ -14,44 +14,53 @@ export const validateSerializedRuleProperties = (rule: Stuntman.SerializedRule):
14
14
  if (rule.priority && typeof rule.priority !== 'number') {
15
15
  throw new AppError({ httpCode: HttpCode.BAD_REQUEST, message: 'invalid rule.priority' });
16
16
  }
17
- if (typeof rule.actions !== 'undefined') {
18
- if (typeof rule.actions !== 'object') {
19
- throw new AppError({ httpCode: HttpCode.BAD_REQUEST, message: 'invalid rule.actions' });
17
+ if (typeof rule.actions !== 'object') {
18
+ throw new AppError({ httpCode: HttpCode.BAD_REQUEST, message: 'invalid rule.actions - not an object' });
19
+ }
20
+ if (
21
+ rule.actions.proxyPass !== true &&
22
+ typeof rule.actions.mockResponse !== 'object' &&
23
+ typeof rule.actions.modifyRequest !== 'object' &&
24
+ typeof rule.actions.modifyResponse !== 'object'
25
+ ) {
26
+ throw new AppError({
27
+ httpCode: HttpCode.BAD_REQUEST,
28
+ message: 'invalid rule.actions - missing one of: proxyPass, mockResponse, modifyRequest, modifyResponse',
29
+ });
30
+ }
31
+ if (typeof rule.actions.mockResponse !== 'undefined') {
32
+ if (typeof rule.actions.mockResponse !== 'object') {
33
+ throw new AppError({ httpCode: HttpCode.BAD_REQUEST, message: 'invalid rule.actions.mockResponse' });
20
34
  }
21
- if (typeof rule.actions.mockResponse !== 'undefined') {
22
- if (typeof rule.actions.mockResponse !== 'object') {
23
- throw new AppError({ httpCode: HttpCode.BAD_REQUEST, message: 'invalid rule.actions.mockResponse' });
35
+ if ('remoteFn' in rule.actions.mockResponse && typeof rule.actions.mockResponse.remoteFn !== 'string') {
36
+ throw new AppError({ httpCode: HttpCode.BAD_REQUEST, message: 'invalid rule.actions.mockResponse' });
37
+ } else if ('status' in rule.actions.mockResponse) {
38
+ if (typeof rule.actions.mockResponse.status !== 'number') {
39
+ throw new AppError({
40
+ httpCode: HttpCode.BAD_REQUEST,
41
+ message: 'invalid rule.actions.mockResponse.status',
42
+ });
24
43
  }
25
- if ('remoteFn' in rule.actions.mockResponse && typeof rule.actions.mockResponse.remoteFn !== 'string') {
26
- throw new AppError({ httpCode: HttpCode.BAD_REQUEST, message: 'invalid rule.actions.mockResponse' });
27
- } else if ('status' in rule.actions.mockResponse) {
28
- if (typeof rule.actions.mockResponse.status !== 'number') {
29
- throw new AppError({
30
- httpCode: HttpCode.BAD_REQUEST,
31
- message: 'invalid rule.actions.mockResponse.status',
32
- });
33
- }
34
- if (
35
- typeof rule.actions.mockResponse.rawHeaders !== 'undefined' &&
36
- (!Array.isArray(rule.actions.mockResponse.rawHeaders) ||
37
- rule.actions.mockResponse.rawHeaders.some((header) => typeof header !== 'string'))
38
- ) {
39
- throw new AppError({
40
- httpCode: HttpCode.BAD_REQUEST,
41
- message: 'invalid rule.actions.mockResponse.rawHeaders',
42
- });
43
- }
44
- if (typeof rule.actions.mockResponse.body !== 'undefined' && typeof rule.actions.mockResponse.body !== 'string') {
45
- throw new AppError({ httpCode: HttpCode.BAD_REQUEST, message: 'invalid rule.actions.mockResponse.body' });
46
- }
44
+ if (
45
+ typeof rule.actions.mockResponse.rawHeaders !== 'undefined' &&
46
+ (!Array.isArray(rule.actions.mockResponse.rawHeaders) ||
47
+ rule.actions.mockResponse.rawHeaders.some((header) => typeof header !== 'string'))
48
+ ) {
49
+ throw new AppError({
50
+ httpCode: HttpCode.BAD_REQUEST,
51
+ message: 'invalid rule.actions.mockResponse.rawHeaders',
52
+ });
53
+ }
54
+ if (typeof rule.actions.mockResponse.body !== 'undefined' && typeof rule.actions.mockResponse.body !== 'string') {
55
+ throw new AppError({ httpCode: HttpCode.BAD_REQUEST, message: 'invalid rule.actions.mockResponse.body' });
47
56
  }
48
57
  }
49
- if (typeof rule.actions.modifyRequest !== 'undefined' && typeof rule.actions.modifyRequest.remoteFn !== 'string') {
50
- throw new AppError({ httpCode: HttpCode.BAD_REQUEST, message: 'invalid rule.actions.modifyRequest' });
51
- }
52
- if (typeof rule.actions.modifyResponse !== 'undefined' && typeof rule.actions.modifyResponse.remoteFn !== 'string') {
53
- throw new AppError({ httpCode: HttpCode.BAD_REQUEST, message: 'invalid rule.actions.modifyResponse' });
54
- }
58
+ }
59
+ if (typeof rule.actions.modifyRequest !== 'undefined' && typeof rule.actions.modifyRequest.remoteFn !== 'string') {
60
+ throw new AppError({ httpCode: HttpCode.BAD_REQUEST, message: 'invalid rule.actions.modifyRequest' });
61
+ }
62
+ if (typeof rule.actions.modifyResponse !== 'undefined' && typeof rule.actions.modifyResponse.remoteFn !== 'string') {
63
+ throw new AppError({ httpCode: HttpCode.BAD_REQUEST, message: 'invalid rule.actions.modifyResponse' });
55
64
  }
56
65
  if (
57
66
  typeof rule.disableAfterUse !== 'undefined' &&
@@ -73,7 +82,7 @@ export const validateSerializedRuleProperties = (rule: Stuntman.SerializedRule):
73
82
  ) {
74
83
  throw new AppError({ httpCode: HttpCode.BAD_REQUEST, message: 'invalid rule.labels' });
75
84
  }
76
- if (rule.actions?.mockResponse && rule.actions.modifyResponse) {
85
+ if (rule.actions.mockResponse && rule.actions.modifyResponse) {
77
86
  throw new AppError({
78
87
  httpCode: HttpCode.BAD_REQUEST,
79
88
  message: 'rule.actions.mockResponse and rule.actions.modifyResponse are mutually exclusive',
@@ -22,6 +22,7 @@ html
22
22
  div(style='margin-left: 240px')
23
23
  #container(style='height: 400px')
24
24
  script.
25
+ /* eslint no-undef: 0 */
25
26
  const uuidv4 = () => {
26
27
  function getRandomSymbol(symbol) {
27
28
  var array;
@@ -72,7 +73,6 @@ html
72
73
  }
73
74
  const editor = monaco.editor.create(document.getElementById('container'), {
74
75
  theme: 'vs-dark',
75
- autoIndent: true,
76
76
  formatOnPaste: true,
77
77
  formatOnType: true,
78
78
  automaticLayout: true,
@@ -120,6 +120,8 @@ html
120
120
  body: ruleFunctionText + '\n return STUNTMAN_RULE;',
121
121
  }).then((response) => {
122
122
  if (response.ok) {
123
+ const newUrl = `${window.location.protocol}//${window.location.host}${window.location.pathname}?ruleId=${encodeURIComponent(newId)}`;
124
+ window.history.pushState({ path: newUrl }, '', newUrl);
123
125
  window.location.reload();
124
126
  return;
125
127
  }
@@ -133,7 +135,7 @@ html
133
135
 
134
136
  window.newRule = () => {
135
137
  const ruleId = uuidv4();
136
- const emptyRule = `import type * as Stuntman from \'stuntman\';\n\nvar STUNTMAN_RULE: Stuntman.Rule = { id: '${ruleId}', matches: (req: Stuntman.Request) => true, ttlSeconds: 600 };`;
138
+ const emptyRule = `import type * as Stuntman from 'stuntman';\n\nvar STUNTMAN_RULE: Stuntman.Rule = { id: '${ruleId}', matches: (req: Stuntman.Request) => true, ttlSeconds: 600, actions: { mockResponse: { status: '200', body: '${ruleId}' }} };`;
137
139
  models[ruleId] = monaco.editor.createModel(emptyRule, 'typescript', `file:///${ruleId}.ts`);
138
140
  const ruleKeyNode = document.getElementById('ruleKeys').firstChild;
139
141
  const ruleKeyNodeClone = ruleKeyNode.cloneNode(true);
@@ -21,8 +21,8 @@ button.rule {
21
21
  }
22
22
 
23
23
  ul.no-bullets {
24
- list-style-type: none; /* Remove bullets */
25
- padding: 0; /* Remove padding */
26
- margin: 0; /* Remove margins */
24
+ list-style-type: none;
25
+ padding: 0;
26
+ margin: 0;
27
27
  text-align: left;
28
28
  }
@@ -12,6 +12,7 @@ html
12
12
  div(style='margin-left: 220px')
13
13
  #container(style='height: 800px')
14
14
  script.
15
+ /* eslint no-undef: 0 */
15
16
  require.config({
16
17
  paths: {
17
18
  vs: 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.35.0/min/vs',
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { Mock } from '../mock';
4
- import { serverConfig } from '@stuntman/shared';
4
+ import { stuntmanConfig } from '@stuntman/shared';
5
5
 
6
- const mock = new Mock(serverConfig);
6
+ const mock = new Mock(stuntmanConfig);
7
7
 
8
8
  mock.start();
package/src/ipUtils.ts CHANGED
@@ -55,7 +55,7 @@ export class IPUtils {
55
55
  return;
56
56
  }
57
57
  logger.debug({ ip: addresses, hostname }, 'resolved hostname');
58
- resolve([addresses[0], ...addresses.slice(1)]);
58
+ resolve([addresses[0]!, ...addresses.slice(1)]);
59
59
  };
60
60
  if (options?.useExternalDns) {
61
61
  if (!this.externalDns) {