@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/src/api/api.ts DELETED
@@ -1,194 +0,0 @@
1
- import http from 'http';
2
- import express, { NextFunction, Request, Response, Express as ExpressServer } from 'express';
3
- import { v4 as uuidv4 } from 'uuid';
4
- import { getTrafficStore } from '../storage';
5
- import { ruleExecutor } from '../ruleExecutor';
6
- import { logger, AppError, HttpCode, MAX_RULE_TTL_SECONDS, stringify, INDEX_DTS } from '@stuntman/shared';
7
- import type * as Stuntman from '@stuntman/shared';
8
- import RequestContext from '../requestContext';
9
- import serializeJavascript from 'serialize-javascript';
10
- import LRUCache from 'lru-cache';
11
- import { validateDeserializedRule } from './validatiors';
12
- import { deserializeRule, escapedSerialize, liveRuleToRule } from './utils';
13
-
14
- type ApiOptions = Stuntman.ApiConfig & {
15
- mockUuid: string;
16
- };
17
-
18
- export class API {
19
- protected options: Required<ApiOptions>;
20
- protected apiApp: ExpressServer;
21
- trafficStore: LRUCache<string, Stuntman.LogEntry>;
22
- server: http.Server | null = null;
23
-
24
- constructor(options: ApiOptions, webGuiOptions?: Stuntman.WebGuiConfig) {
25
- this.options = options;
26
-
27
- this.trafficStore = getTrafficStore(this.options.mockUuid);
28
- this.apiApp = express();
29
-
30
- this.apiApp.use(express.json());
31
- this.apiApp.use(express.text());
32
-
33
- this.apiApp.use((req: Request, res: Response, next: NextFunction) => {
34
- RequestContext.bind(req, this.options.mockUuid);
35
- next();
36
- });
37
-
38
- this.apiApp.get('/rule', async (req, res) => {
39
- res.send(stringify(await ruleExecutor.getRules()));
40
- });
41
-
42
- this.apiApp.get('/rule/:ruleId', async (req, res) => {
43
- res.send(stringify(await ruleExecutor.getRule(req.params.ruleId)));
44
- });
45
-
46
- this.apiApp.get('/rule/:ruleId/disable', (req, res) => {
47
- ruleExecutor.disableRule(req.params.ruleId);
48
- res.send();
49
- });
50
-
51
- this.apiApp.get('/rule/:ruleId/enable', (req, res) => {
52
- ruleExecutor.enableRule(req.params.ruleId);
53
- res.send();
54
- });
55
-
56
- this.apiApp.post('/rule', async (req: Request<object, string, Stuntman.SerializedRule>, res) => {
57
- const deserializedRule = deserializeRule(req.body);
58
- validateDeserializedRule(deserializedRule);
59
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
60
- // @ts-ignore
61
- const rule = await ruleExecutor.addRule(deserializedRule);
62
- res.send(stringify(rule));
63
- });
64
-
65
- this.apiApp.get('/rule/:ruleId/remove', async (req, res) => {
66
- await ruleExecutor.removeRule(req.params.ruleId);
67
- res.send();
68
- });
69
-
70
- this.apiApp.get('/traffic', (req, res) => {
71
- const serializedTraffic: Record<string, Stuntman.LogEntry> = {};
72
- for (const [key, value] of this.trafficStore.entries()) {
73
- serializedTraffic[key] = value;
74
- }
75
- res.json(serializedTraffic);
76
- });
77
-
78
- this.apiApp.get('/traffic/:ruleIdOrLabel', (req, res) => {
79
- const serializedTraffic: Record<string, Stuntman.LogEntry> = {};
80
- for (const [key, value] of this.trafficStore.entries()) {
81
- if (value.mockRuleId === req.params.ruleIdOrLabel || (value.labels || []).includes(req.params.ruleIdOrLabel)) {
82
- serializedTraffic[key] = value;
83
- }
84
- }
85
- res.json(serializedTraffic);
86
- });
87
-
88
- this.apiApp.use((error: Error | AppError, req: Request, res: Response, _next: NextFunction) => {
89
- const ctx: RequestContext | null = RequestContext.get(req);
90
- const uuid = ctx?.uuid || uuidv4();
91
- if (error instanceof AppError && error.isOperational && res) {
92
- logger.error(error);
93
- res.status(error.httpCode).json({
94
- error: { message: error.message, httpCode: error.httpCode, stack: error.stack },
95
- });
96
- return;
97
- }
98
- logger.error({ ...error, uuid }, 'Unexpected error');
99
- if (res) {
100
- res.status(HttpCode.INTERNAL_SERVER_ERROR).json({
101
- error: { message: error.message, httpCode: HttpCode.INTERNAL_SERVER_ERROR, uuid },
102
- });
103
- return;
104
- }
105
- console.log('Application encountered a critical error. Exiting');
106
- process.exit(1);
107
- });
108
-
109
- this.apiApp.set('views', __dirname + '/webgui');
110
- this.apiApp.set('view engine', 'pug');
111
-
112
- if (!webGuiOptions?.disabled) {
113
- this.initWebGui();
114
- }
115
- }
116
-
117
- private initWebGui() {
118
- this.apiApp.get('/webgui/rules', async (req, res) => {
119
- const rules: Record<string, string> = {};
120
- for (const rule of await ruleExecutor.getRules()) {
121
- rules[rule.id] = serializeJavascript(liveRuleToRule(rule), { unsafe: true });
122
- }
123
- res.render('rules', { rules: escapedSerialize(rules), INDEX_DTS, ruleKeys: Object.keys(rules) });
124
- });
125
-
126
- this.apiApp.get('/webgui/traffic', async (req, res) => {
127
- const serializedTraffic: Stuntman.LogEntry[] = [];
128
- for (const value of this.trafficStore.values()) {
129
- serializedTraffic.push(value);
130
- }
131
- res.render('traffic', {
132
- traffic: JSON.stringify(
133
- serializedTraffic.sort((a, b) => b.originalRequest.timestamp - a.originalRequest.timestamp)
134
- ),
135
- });
136
- });
137
-
138
- // TODO make webui way better and safer, nicer formatting, eslint/prettier, blackjack and hookers
139
-
140
- this.apiApp.post('/webgui/rules/unsafesave', async (req, res) => {
141
- const rule: Stuntman.Rule = new Function(req.body)();
142
- if (
143
- !rule ||
144
- !rule.id ||
145
- typeof rule.matches !== 'function' ||
146
- typeof rule.ttlSeconds !== 'number' ||
147
- rule.ttlSeconds > MAX_RULE_TTL_SECONDS
148
- ) {
149
- throw new AppError({ httpCode: HttpCode.BAD_REQUEST, message: 'Invalid rule' });
150
- }
151
- await ruleExecutor.addRule(
152
- {
153
- id: rule.id,
154
- matches: rule.matches,
155
- ttlSeconds: rule.ttlSeconds,
156
- ...(rule.actions && {
157
- actions: {
158
- ...(rule.actions.mockResponse
159
- ? { mockResponse: rule.actions.mockResponse }
160
- : { modifyRequest: rule.actions.modifyRequest, modifyResponse: rule.actions.modifyResponse }),
161
- },
162
- }),
163
- ...(rule.disableAfterUse !== undefined && { disableAfterUse: rule.disableAfterUse }),
164
- ...(rule.isEnabled !== undefined && { isEnabled: rule.isEnabled }),
165
- ...(rule.labels !== undefined && { labels: rule.labels }),
166
- ...(rule.priority !== undefined && { priority: rule.priority }),
167
- ...(rule.removeAfterUse !== undefined && { removeAfterUse: rule.removeAfterUse }),
168
- ...(rule.storeTraffic !== undefined && { storeTraffic: rule.storeTraffic }),
169
- },
170
- true
171
- );
172
- res.send();
173
- });
174
- }
175
-
176
- public start() {
177
- if (this.server) {
178
- throw new Error('mock server already started');
179
- }
180
- this.server = this.apiApp.listen(this.options.port, () => {
181
- logger.info(`API listening on ${this.options.port}`);
182
- });
183
- }
184
-
185
- public stop() {
186
- if (!this.server) {
187
- throw new Error('mock server not started');
188
- }
189
- this.server.close((error) => {
190
- logger.warn(error, 'problem closing server');
191
- this.server = null;
192
- });
193
- }
194
- }
package/src/api/utils.ts DELETED
@@ -1,69 +0,0 @@
1
- import serializeJavascript from 'serialize-javascript';
2
- import type * as Stuntman from '@stuntman/shared';
3
- import { logger } from '@stuntman/shared';
4
- import { validateSerializedRuleProperties } from './validatiors';
5
-
6
- // TODO
7
- export const deserializeRule = (serializedRule: Stuntman.SerializedRule): Stuntman.Rule => {
8
- logger.debug(serializedRule, 'attempt to deserialize rule');
9
- validateSerializedRuleProperties(serializedRule);
10
- const rule: Stuntman.Rule = {
11
- id: serializedRule.id,
12
- matches: (req: Stuntman.Request) => new Function('____arg0', serializedRule.matches.remoteFn)(req),
13
- ttlSeconds: serializedRule.ttlSeconds,
14
- ...(serializedRule.disableAfterUse !== undefined && { disableAfterUse: serializedRule.disableAfterUse }),
15
- ...(serializedRule.removeAfterUse !== undefined && { removeAfterUse: serializedRule.removeAfterUse }),
16
- ...(serializedRule.labels !== undefined && { labels: serializedRule.labels }),
17
- ...(serializedRule.priority !== undefined && { priority: serializedRule.priority }),
18
- ...(serializedRule.isEnabled !== undefined && { isEnabled: serializedRule.isEnabled }),
19
- ...(serializedRule.storeTraffic !== undefined && { storeTraffic: serializedRule.storeTraffic }),
20
- };
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) =>
38
- new Function(
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) =>
45
- new Function(
46
- '____arg0',
47
- '____arg1',
48
- (serializedRule.actions?.modifyResponse as Stuntman.SerializedRemotableFunction).remoteFn
49
- )(req, res);
50
- }
51
- }
52
- }
53
- logger.debug(rule, 'deserialized rule');
54
- return rule;
55
- };
56
-
57
- export const escapedSerialize = (obj: any) =>
58
- serializeJavascript(obj).replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/\n/g, "\\n'\n+ '");
59
-
60
- export const liveRuleToRule = (liveRule: Stuntman.LiveRule) => {
61
- const ruleClone: Stuntman.Rule = { ...liveRule };
62
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
63
- // @ts-ignore
64
- delete ruleClone.counter;
65
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
66
- // @ts-ignore
67
- delete ruleClone.createdTimestamp;
68
- return ruleClone;
69
- };
@@ -1,123 +0,0 @@
1
- import { AppError, HttpCode, MAX_RULE_TTL_SECONDS, MIN_RULE_TTL_SECONDS, logger, RawHeaders } from '@stuntman/shared';
2
- import type * as Stuntman from '@stuntman/shared';
3
-
4
- export const validateSerializedRuleProperties = (rule: Stuntman.SerializedRule): void => {
5
- if (!rule) {
6
- throw new AppError({ httpCode: HttpCode.BAD_REQUEST, message: 'invalid serialized rule' });
7
- }
8
- if (typeof rule.id !== 'string') {
9
- throw new AppError({ httpCode: HttpCode.BAD_REQUEST, message: 'invalid rule.id' });
10
- }
11
- if (typeof rule.matches !== 'object' || !('remoteFn' in rule.matches) || typeof rule.matches.remoteFn !== 'string') {
12
- throw new AppError({ httpCode: HttpCode.BAD_REQUEST, message: 'invalid rule.matches' });
13
- }
14
- if (rule.priority && typeof rule.priority !== 'number') {
15
- throw new AppError({ httpCode: HttpCode.BAD_REQUEST, message: 'invalid rule.priority' });
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' });
20
- }
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' });
24
- }
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
- }
47
- }
48
- }
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
- }
55
- }
56
- if (
57
- typeof rule.disableAfterUse !== 'undefined' &&
58
- typeof rule.disableAfterUse !== 'boolean' &&
59
- (typeof rule.disableAfterUse !== 'number' || rule.disableAfterUse < 0)
60
- ) {
61
- throw new AppError({ httpCode: HttpCode.BAD_REQUEST, message: 'invalid rule.disableAfterUse' });
62
- }
63
- if (
64
- typeof rule.removeAfterUse !== 'undefined' &&
65
- typeof rule.removeAfterUse !== 'boolean' &&
66
- (typeof rule.removeAfterUse !== 'number' || rule.removeAfterUse < 0)
67
- ) {
68
- throw new AppError({ httpCode: HttpCode.BAD_REQUEST, message: 'invalid rule.removeAfterUse' });
69
- }
70
- if (
71
- typeof rule.labels !== 'undefined' &&
72
- (!Array.isArray(rule.labels) || rule.labels.some((label) => typeof label !== 'string'))
73
- ) {
74
- throw new AppError({ httpCode: HttpCode.BAD_REQUEST, message: 'invalid rule.labels' });
75
- }
76
- if (rule.actions?.mockResponse && rule.actions.modifyResponse) {
77
- throw new AppError({
78
- httpCode: HttpCode.BAD_REQUEST,
79
- message: 'rule.actions.mockResponse and rule.actions.modifyResponse are mutually exclusive',
80
- });
81
- }
82
- if (!rule.ttlSeconds || rule.ttlSeconds < MIN_RULE_TTL_SECONDS || rule.ttlSeconds > MAX_RULE_TTL_SECONDS) {
83
- throw new AppError({
84
- httpCode: HttpCode.BAD_REQUEST,
85
- message: `rule.ttlSeconds should be within ${MIN_RULE_TTL_SECONDS} and ${MAX_RULE_TTL_SECONDS}`,
86
- });
87
- }
88
- if (typeof rule.isEnabled !== 'undefined' && typeof rule.isEnabled !== 'boolean') {
89
- throw new AppError({ httpCode: HttpCode.BAD_REQUEST, message: 'invalid rule.isEnabled' });
90
- }
91
- if (typeof rule.storeTraffic !== 'undefined' && typeof rule.storeTraffic !== 'boolean') {
92
- throw new AppError({ httpCode: HttpCode.BAD_REQUEST, message: 'invalid rule.storeTraffic' });
93
- }
94
- };
95
-
96
- export const validateDeserializedRule = (deserializedRule: Stuntman.Rule) => {
97
- // TODO validate other functions ?
98
- let matchValidationResult: Stuntman.RuleMatchResult;
99
- try {
100
- matchValidationResult = deserializedRule.matches({
101
- id: 'validation',
102
- method: 'GET',
103
- rawHeaders: new RawHeaders(),
104
- timestamp: Date.now(),
105
- url: 'http://dummy.invalid/',
106
- });
107
- } catch (error: any) {
108
- logger.error({ ruleId: deserializedRule.id }, error);
109
- throw new AppError({
110
- httpCode: HttpCode.UNPROCESSABLE_ENTITY,
111
- message: 'match function returned invalid value',
112
- });
113
- }
114
- if (
115
- matchValidationResult !== true &&
116
- matchValidationResult !== false &&
117
- matchValidationResult.result !== true &&
118
- matchValidationResult.result !== false
119
- ) {
120
- logger.error({ ruleId: deserializedRule.id, matchValidationResult }, 'match function retruned invalid value');
121
- throw new AppError({ httpCode: HttpCode.UNPROCESSABLE_ENTITY, message: 'match function returned invalid value' });
122
- }
123
- };
@@ -1,145 +0,0 @@
1
- doctype html
2
- html
3
- head
4
- title Stuntman - rule editor
5
- script(src='https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.35.0/min/vs/loader.min.js')
6
- style
7
- include style.css
8
- body(style='color: rgb(204, 204, 204); background-color: rgb(50, 50, 50)')
9
- button#newRule(type='button', onclick='window.newRule()') New rule
10
- button#saveRule(type='button', onclick='window.saveRule()', disabled) Save rule
11
- div(style='width: 100%; overflow: hidden')
12
- div(style='width: 230px; float: left')
13
- h3 Rules
14
- ul#ruleKeys.no-bullets
15
- each ruleId in ruleKeys
16
- li
17
- button.rule(
18
- type='button',
19
- onclick='window.setRuleModel(this.getAttribute("data-rule-id"))',
20
- data-rule-id=ruleId
21
- )= ruleId
22
- div(style='margin-left: 240px')
23
- #container(style='height: 400px')
24
- script.
25
- const uuidv4 = () => {
26
- function getRandomSymbol(symbol) {
27
- var array;
28
-
29
- if (symbol === 'y') {
30
- array = ['8', '9', 'a', 'b'];
31
- return array[Math.floor(Math.random() * array.length)];
32
- }
33
-
34
- array = new Uint8Array(1);
35
- window.crypto.getRandomValues(array);
36
- return (array[0] % 16).toString(16);
37
- }
38
-
39
- return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, getRandomSymbol);
40
- };
41
- require.config({
42
- paths: {
43
- vs: 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.35.0/min/vs',
44
- },
45
- });
46
- require(['vs/editor/editor.main'], function () {
47
- monaco.languages.typescript.javascriptDefaults.setEagerModelSync(true);
48
- monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({
49
- noSemanticValidation: false,
50
- noSyntaxValidation: false,
51
- });
52
-
53
- // compiler options
54
- monaco.languages.typescript.javascriptDefaults.setCompilerOptions({
55
- target: monaco.languages.typescript.ScriptTarget.ES6,
56
- allowNonTsExtensions: true,
57
- noLib: true,
58
- moduleResolution: monaco.languages.typescript.ModuleResolutionKind.NodeJs,
59
- module: monaco.languages.typescript.ModuleKind.CommonJS,
60
- noEmit: true,
61
- checkJs: true,
62
- allowJs: true,
63
- isolatedModules: true,
64
- typeRoots: ['node_modules/@types'],
65
- });
66
-
67
- monaco.languages.typescript.typescriptDefaults.addExtraLib(`!{INDEX_DTS}`, 'file:///node_modules/@types/stuntman/index.d.ts');
68
- const models = {};
69
- const rules = eval('(!{rules})');
70
- for (const ruleId of Object.keys(rules)) {
71
- models[ruleId] = monaco.editor.createModel("import type * as Stuntman from 'stuntman';\n\nvar STUNTMAN_RULE: Stuntman.Rule = " + rules[ruleId] + ';', 'typescript', `file:///${ruleId}.ts`);
72
- }
73
- const editor = monaco.editor.create(document.getElementById('container'), {
74
- theme: 'vs-dark',
75
- autoIndent: true,
76
- formatOnPaste: true,
77
- formatOnType: true,
78
- automaticLayout: true,
79
- autoIndent: true,
80
- tabSize: 2,
81
- });
82
- editor.onDidChangeModel((event) => {
83
- const isInternal = /^file:\/\/\/internal\/.+/.test(event.newModelUrl);
84
- if (isInternal) {
85
- document.getElementById('saveRule').setAttribute('disabled', 'true');
86
- } else {
87
- document.getElementById('saveRule').removeAttribute('disabled');
88
- }
89
-
90
- setTimeout(() => {
91
- editor.getAction('editor.action.formatDocument').run();
92
- }, 100);
93
- });
94
-
95
- window.setRuleModel = (ruleId) => {
96
- if (history.pushState) {
97
- const newUrl = `${window.location.protocol}//${window.location.host}${window.location.pathname}?ruleId=${encodeURIComponent(ruleId)}`;
98
- window.history.pushState({ path: newUrl }, '', newUrl);
99
- }
100
- editor.setModel(models[ruleId]);
101
- };
102
-
103
- const urlSearchParams = new URLSearchParams(window.location.search);
104
- if (urlSearchParams.has('ruleId') && urlSearchParams.get('ruleId') in models) {
105
- editor.setModel(models[urlSearchParams.get('ruleId')]);
106
- }
107
-
108
- window.saveRule = () => {
109
- document.getElementById('saveRule').setAttribute('disabled', 'true');
110
- const modelUri = editor.getModel().uri;
111
- const result = monaco.languages.typescript.getTypeScriptWorker();
112
- result.then((worker) => {
113
- worker(modelUri).then(function (client) {
114
- client.getEmitOutput(modelUri.toString()).then((output) => {
115
- const ruleFunctionText = output.outputFiles[0].text.replace(/^export .+$/im, '');
116
- const newId = new Function(ruleFunctionText + '\n return STUNTMAN_RULE;')().id;
117
- fetch('/webgui/rules/unsafesave', {
118
- method: 'POST',
119
- headers: { 'content-type': 'text/plain' },
120
- body: ruleFunctionText + '\n return STUNTMAN_RULE;',
121
- }).then((response) => {
122
- if (response.ok) {
123
- window.location.reload();
124
- return;
125
- }
126
- alert('Error when saving rule');
127
- document.getElementById('saveRule').removeAttribute('disabled');
128
- });
129
- });
130
- });
131
- });
132
- };
133
-
134
- window.newRule = () => {
135
- 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 };`;
137
- models[ruleId] = monaco.editor.createModel(emptyRule, 'typescript', `file:///${ruleId}.ts`);
138
- const ruleKeyNode = document.getElementById('ruleKeys').firstChild;
139
- const ruleKeyNodeClone = ruleKeyNode.cloneNode(true);
140
- ruleKeyNodeClone.getElementsByTagName('button')[0].setAttribute('data-rule-id', ruleId);
141
- ruleKeyNodeClone.getElementsByTagName('button')[0].innerText = ruleId;
142
- document.getElementById('ruleKeys').appendChild(ruleKeyNodeClone);
143
- window.setRuleModel(ruleId);
144
- };
145
- });
@@ -1,28 +0,0 @@
1
- div#container {
2
- resize: vertical;
3
- overflow: auto;
4
- }
5
-
6
- body {
7
- font-family: Menlo, Monaco, 'Courier New', monospace;
8
- }
9
-
10
- button.rule {
11
- font-family: Menlo, Monaco, 'Courier New', monospace;
12
- background: none !important;
13
- border: none;
14
- padding: 0 !important;
15
- color: #aaa;
16
- text-decoration: underline;
17
- cursor: pointer;
18
- margin-top: 10px;
19
- font-size: x-small;
20
- text-align: left;
21
- }
22
-
23
- ul.no-bullets {
24
- list-style-type: none; /* Remove bullets */
25
- padding: 0; /* Remove padding */
26
- margin: 0; /* Remove margins */
27
- text-align: left;
28
- }
@@ -1,37 +0,0 @@
1
- doctype html
2
- html
3
- head
4
- title Stuntman - rule editor
5
- script(src='https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.35.0/min/vs/loader.min.js')
6
- style
7
- include style.css
8
- body(style='color: rgb(204, 204, 204); background-color: rgb(50, 50, 50)')
9
- div(style='width: 100%; overflow: hidden')
10
- div(style='width: 200px; float: left')
11
- h3 Traffic log
12
- div(style='margin-left: 220px')
13
- #container(style='height: 800px')
14
- script.
15
- require.config({
16
- paths: {
17
- vs: 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.35.0/min/vs',
18
- },
19
- });
20
- require(['vs/editor/editor.main'], function () {
21
- const traffic = !{ traffic };
22
- const model = monaco.editor.createModel(JSON.stringify(traffic, null, 2), 'json');
23
- const editor = monaco.editor.create(document.getElementById('container'), {
24
- theme: 'vs-dark',
25
- autoIndent: true,
26
- formatOnPaste: true,
27
- formatOnType: true,
28
- automaticLayout: true,
29
- readOnly: true,
30
- });
31
- editor.onDidChangeModel((event) => {
32
- setTimeout(() => {
33
- editor.getAction('editor.action.formatDocument').run();
34
- }, 100);
35
- });
36
- editor.setModel(model);
37
- });
@@ -1,8 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { Mock } from '../mock';
4
- import { serverConfig } from '@stuntman/shared';
5
-
6
- const mock = new Mock(serverConfig);
7
-
8
- mock.start();
package/src/index.ts DELETED
@@ -1 +0,0 @@
1
- export { Mock as StuntmanMock } from './mock';