typescript-mock-server 0.0.10 → 0.0.13
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 +21 -3
- package/package.json +19 -11
- package/src/command-line.ts +9 -0
- package/src/impl/command-line-impl.ts +33 -0
- package/src/impl/logger-impl.ts +39 -0
- package/src/impl/typescript-mock-server-impl.ts +95 -0
- package/src/index.ts +4 -84
- package/src/logger.ts +7 -0
- package/src/models/config.ts +8 -0
- package/src/models/registered-endpoint.ts +4 -0
- package/src/types/http-verbs.ts +1 -0
- package/src/typescript-mock-server.ts +3 -0
- package/tms-models/users/get.ts +9 -0
package/README.md
CHANGED
|
@@ -5,9 +5,9 @@ have to update your mock, otherwise you will receive compile errors.
|
|
|
5
5
|
|
|
6
6
|
# Quickstart
|
|
7
7
|
The easiest way to check out this stub/mock server is by installing it as a (dev)dependency and then
|
|
8
|
-
add a script to you scripts section: `npm run --prefix node_modules/typescript-mock-server start -- --path
|
|
8
|
+
add a script to you scripts section: `npm run --prefix node_modules/typescript-mock-server start -- --path=/tms-models`.
|
|
9
9
|
Your models should export a data const and your file should be named as `^(get|post){1}(-\d)?.ts$`.
|
|
10
|
-
Changes are being picked up automatically, so no need for a restart.
|
|
10
|
+
Changes are being picked up automatically, so no need for a restart. When you add files, you have to restart.
|
|
11
11
|
|
|
12
12
|
Check out the [working example project](https://github.com/GuyT07/typescript-mock-server-examle) and [the source](https://github.com/GuyT07/typescript-mock-server/tree/main/tms-models/users).
|
|
13
13
|
|
|
@@ -58,6 +58,13 @@ export const data: User[] = [{
|
|
|
58
58
|
lastName: 'Development',
|
|
59
59
|
creationDate: newDate()
|
|
60
60
|
}];
|
|
61
|
+
|
|
62
|
+
export const config: Config = {
|
|
63
|
+
server: {
|
|
64
|
+
delay: 2000,
|
|
65
|
+
statusCode: 418
|
|
66
|
+
}
|
|
67
|
+
}
|
|
61
68
|
```
|
|
62
69
|
|
|
63
70
|
## Dependencies
|
|
@@ -73,10 +80,21 @@ Following dependencies are being used:
|
|
|
73
80
|
- [x] Support other server port
|
|
74
81
|
- [x] Improve paths/way to start
|
|
75
82
|
- [ ] Support different headers/configurations (delays, status codes, ...)
|
|
76
|
-
- [x] Support
|
|
83
|
+
- [x] Support most used HTTP methods
|
|
77
84
|
- [ ] Add tests
|
|
78
85
|
- [ ] Refactor, split up in separate classes (first check if people actually want to use the tool)
|
|
79
86
|
- [ ] Setup CI/CD (+code quality + coverage tooling)
|
|
80
87
|
- [ ] Setup website
|
|
81
88
|
- [ ] Create a JVM compatible version
|
|
89
|
+
- [ ] Create interface to force implementation of required properties and make it more stable
|
|
90
|
+
- [ ] Improve error handling (missing properties etc.)
|
|
91
|
+
- [ ] Create an optional persistent state
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
## Release notes (will be moved to GitHub in the future)
|
|
95
|
+
- v0.0.11 - Add items to roadmap, bug fixes
|
|
96
|
+
- v0.0.10 - Support multiple http verbs
|
|
97
|
+
|
|
98
|
+
## Bug fix contributors
|
|
99
|
+
- Path fix, from now on current working directory is used. Credits to [Spoodyman](https://github.com/spoodyman)
|
|
82
100
|
|
package/package.json
CHANGED
|
@@ -1,35 +1,43 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "typescript-mock-server",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.13",
|
|
4
4
|
"description": "Simple mock server that can be used in front end development. Instead of creating json files you can just publish TypeScript objects as json",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
7
|
-
"example": "ts-node-dev src/index.ts --path
|
|
8
|
-
"start": "ts-node-dev src/index.ts"
|
|
7
|
+
"example": "ts-node-dev src/index.ts --path=tms-models --port=5000",
|
|
8
|
+
"start": "ts-node-dev src/index.ts",
|
|
9
|
+
"update-deps": "npm update",
|
|
10
|
+
"publish-to-npm": "npm publish"
|
|
9
11
|
},
|
|
10
12
|
"repository": {
|
|
11
13
|
"type": "git",
|
|
12
14
|
"url": "git+https://github.com/GuyT07/typescript-mock-server.git"
|
|
13
15
|
},
|
|
14
|
-
"keywords": [
|
|
16
|
+
"keywords": [
|
|
17
|
+
"mock server",
|
|
18
|
+
"testing",
|
|
19
|
+
"stub",
|
|
20
|
+
"typescript"
|
|
21
|
+
],
|
|
15
22
|
"author": "Guy Theuws",
|
|
16
23
|
"license": "ISC",
|
|
17
24
|
"bugs": {
|
|
18
25
|
"url": "https://github.com/GuyT07/typescript-mock-server/issues"
|
|
19
26
|
},
|
|
20
|
-
"homepage": "https://
|
|
27
|
+
"homepage": "https://genydev.nl",
|
|
21
28
|
"funding": {
|
|
22
|
-
"url"
|
|
29
|
+
"url": "https://genydev.nl"
|
|
23
30
|
},
|
|
24
31
|
"devDependencies": {
|
|
25
32
|
"prettier": "^2.6.2"
|
|
26
33
|
},
|
|
27
34
|
"dependencies": {
|
|
28
|
-
"express": "^4.17.
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
35
|
+
"@types/express": "^4.17.13",
|
|
36
|
+
"@types/node": "^18.0.5",
|
|
37
|
+
"express": "^4.18.1",
|
|
38
|
+
"ts-node-dev": "2.0.0",
|
|
39
|
+
"tslog": "^3.3.3",
|
|
40
|
+
"typescript": "^4.7.4"
|
|
33
41
|
},
|
|
34
42
|
"prettier": {
|
|
35
43
|
"arrowParens": "avoid",
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Command, CommandLine } from '../command-line';
|
|
2
|
+
import { Logger } from '../logger';
|
|
3
|
+
import { LoggerImpl } from './logger-impl';
|
|
4
|
+
|
|
5
|
+
export class CommandLineImpl implements CommandLine {
|
|
6
|
+
|
|
7
|
+
private arguments: Map<Command, string> = new Map<Command, string>();
|
|
8
|
+
private log: Logger = new LoggerImpl();
|
|
9
|
+
|
|
10
|
+
constructor() {
|
|
11
|
+
this.parseCommandLineArguments();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
getCommands(): Map<Command, string> {
|
|
15
|
+
return this.arguments;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
getCommand(command: Command): string | undefined {
|
|
19
|
+
return this.arguments.get(command);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
private parseCommandLineArguments(): void {
|
|
23
|
+
process.argv.slice(2).map((element) => {
|
|
24
|
+
const matches = element.match('--([a-zA-Z0-9]+)=(.*)');
|
|
25
|
+
if (matches) {
|
|
26
|
+
const value = matches[2].replace(/^['"]/, '').replace(/['"]$/, '');
|
|
27
|
+
this.arguments.set(matches[1] as Command, value);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
this.log.debug(`Passed arguments ${[...this.arguments.keys()].join(',')}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Logger as LoggerInterface } from '../logger';
|
|
2
|
+
import { Logger } from 'tslog';
|
|
3
|
+
|
|
4
|
+
export class LoggerImpl implements LoggerInterface {
|
|
5
|
+
|
|
6
|
+
private readonly log: Logger;
|
|
7
|
+
|
|
8
|
+
constructor() {
|
|
9
|
+
this.log = new Logger({ignoreStackLevels: 4, displayFunctionName: false});
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
private static getArgumentsToPass(args: unknown[]): unknown | unknown[] {
|
|
13
|
+
return LoggerImpl.getNumberOfArguments(args) === 1 ? args[0] : args;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
private static getNumberOfArguments(args: unknown[]): number {
|
|
17
|
+
return args.length;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
debug(...args: unknown[]): void {
|
|
21
|
+
this.log.debug(LoggerImpl.getArgumentsToPass(args));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
error(...args: unknown[]): void {
|
|
25
|
+
this.log.error(LoggerImpl.getArgumentsToPass(args));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
trace(...args: unknown[]): void {
|
|
29
|
+
this.log.trace(LoggerImpl.getArgumentsToPass(args));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
warn(...args: unknown[]): void {
|
|
33
|
+
this.log.warn(LoggerImpl.getArgumentsToPass(args));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
info(...args: unknown[]): void {
|
|
37
|
+
this.log.info(LoggerImpl.getArgumentsToPass(args));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import express, { Express } from 'express';
|
|
2
|
+
import { CommandLineImpl } from './command-line-impl';
|
|
3
|
+
import { Command, CommandLine } from '../command-line';
|
|
4
|
+
import { RegisteredEndpoint } from '../models/registered-endpoint';
|
|
5
|
+
import { HttpVerb } from '../types/http-verbs';
|
|
6
|
+
import { Dirent } from 'fs';
|
|
7
|
+
import { opendir } from 'fs/promises';
|
|
8
|
+
import { LoggerImpl } from './logger-impl';
|
|
9
|
+
import { Logger } from '../logger';
|
|
10
|
+
import { TypescriptMockServer } from '../typescript-mock-server';
|
|
11
|
+
|
|
12
|
+
export class TypescriptMockServerImpl implements TypescriptMockServer{
|
|
13
|
+
|
|
14
|
+
private readonly log: Logger = new LoggerImpl();
|
|
15
|
+
private readonly commandLine: CommandLine = new CommandLineImpl();
|
|
16
|
+
private readonly app: Express;
|
|
17
|
+
private readonly basePath;
|
|
18
|
+
private registeredEndpoints: RegisteredEndpoint[] = [];
|
|
19
|
+
|
|
20
|
+
constructor() {
|
|
21
|
+
this.app = express();
|
|
22
|
+
this.basePath = this.getPath();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
private static async loadModule(moduleName: string) {
|
|
26
|
+
return await import(moduleName);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
public start() {
|
|
30
|
+
this.log.info(`basePath: ${this.basePath}`);
|
|
31
|
+
this.readRoutes(this.basePath).catch(error => this.log.error(error));
|
|
32
|
+
const port = this.commandLine.getCommand(Command.PORT) || 3000;
|
|
33
|
+
this.app.listen(port, () => {
|
|
34
|
+
this.log.info(`App is listening on port ${port}!`);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
private async readRoutes(path: string) {
|
|
39
|
+
const dir = await opendir(path);
|
|
40
|
+
for await (const dirent of dir) {
|
|
41
|
+
if (dirent.isDirectory()) {
|
|
42
|
+
await this.readRoutes(`${path}/${dirent.name}`);
|
|
43
|
+
} else {
|
|
44
|
+
this.handleFile(path, dirent);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
this.registeredEndpoints.forEach(endpoint => this.log.info(`${endpoint.httpVerb.toUpperCase()} http://localhost:${this.commandLine.getCommand(Command.PORT)}${endpoint.endpoint}`));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private handleFile(path: string, dirent: Dirent) {
|
|
51
|
+
const httpVerb = (dirent.name.indexOf('-') > -1 ? dirent.name.split('-')[0] : dirent.name.split('.')[0]) as HttpVerb;
|
|
52
|
+
this.handleRequest(path, dirent, httpVerb);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private addEndpoint(endpoint: string, httpVerb: HttpVerb, model: any) {
|
|
56
|
+
this.app[httpVerb](endpoint, (req, res) => {
|
|
57
|
+
if (model?.config?.server?.statusCode) {
|
|
58
|
+
res.statusCode = model?.config?.server?.statusCode;
|
|
59
|
+
}
|
|
60
|
+
if (model?.config?.server?.delay) {
|
|
61
|
+
setTimeout(() => res.send(model.data), model?.config?.server?.delay);
|
|
62
|
+
} else {
|
|
63
|
+
return res.send(model.data);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private handleRequest(path: string, dirent: Dirent, httpVerb: HttpVerb) {
|
|
69
|
+
const endpoint = this.convertFileNameToEndpoint(path, dirent, httpVerb);
|
|
70
|
+
const modulePath = `${path}/${dirent.name}`;
|
|
71
|
+
this.registeredEndpoints.push({ httpVerb, endpoint });
|
|
72
|
+
TypescriptMockServerImpl.loadModule(modulePath)
|
|
73
|
+
.then(model => this.addEndpoint(endpoint, httpVerb, model))
|
|
74
|
+
.catch(error => this.log.error(error));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
private convertFileNameToEndpoint(path: string, dirent: Dirent, httpVerb: HttpVerb): string {
|
|
78
|
+
const endpoint = `${path.replace(this.basePath, '')}/${dirent.name}`
|
|
79
|
+
.replace('.ts', '')
|
|
80
|
+
.replace(httpVerb, '')
|
|
81
|
+
.replace('-', '');
|
|
82
|
+
if (endpoint.endsWith('/')) {
|
|
83
|
+
return endpoint.substring(0, endpoint.length - 1);
|
|
84
|
+
}
|
|
85
|
+
return endpoint;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private getPath(): string {
|
|
89
|
+
if (!this.commandLine.getCommands().has(Command.PATH)) {
|
|
90
|
+
this.log.warn(`Path parameter not set, fallback to default tms-models`);
|
|
91
|
+
return 'tms-models';
|
|
92
|
+
}
|
|
93
|
+
return `${process.cwd()}/${this.commandLine.getCommand(Command.PATH)}`;
|
|
94
|
+
}
|
|
95
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,88 +1,8 @@
|
|
|
1
1
|
#!./node_modules/.bin/ts-node-dev
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import
|
|
3
|
+
import { TypescriptMockServer } from './typescript-mock-server';
|
|
4
|
+
import { TypescriptMockServerImpl } from './impl/typescript-mock-server-impl';
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
const server: TypescriptMockServer = new TypescriptMockServerImpl();
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
const args = {};
|
|
10
|
-
process.argv.slice(2).map((element) => {
|
|
11
|
-
const matches = element.match('--([a-zA-Z0-9]+)=(.*)');
|
|
12
|
-
if (matches) {
|
|
13
|
-
// @ts-ignore
|
|
14
|
-
args[matches[1]] = matches[2]
|
|
15
|
-
.replace(/^['"]/, '').replace(/['"]$/, '');
|
|
16
|
-
}
|
|
17
|
-
});
|
|
18
|
-
return args;
|
|
19
|
-
})();
|
|
20
|
-
|
|
21
|
-
console.log(`Passed arguments %o`, argv);
|
|
22
|
-
|
|
23
|
-
// Create a new express app instance
|
|
24
|
-
const app: Express = express();
|
|
25
|
-
|
|
26
|
-
const { path, port } = argv;
|
|
27
|
-
|
|
28
|
-
const basePath = `${path}`;
|
|
29
|
-
|
|
30
|
-
interface RegisteredEndpoint {
|
|
31
|
-
httpVerb: string;
|
|
32
|
-
endpoint: string;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const registeredEndpoints: RegisteredEndpoint[] = [];
|
|
36
|
-
|
|
37
|
-
console.log('basePath:' + basePath);
|
|
38
|
-
|
|
39
|
-
async function readRoutes(path: string) {
|
|
40
|
-
const dir = await fs.promises.opendir(path);
|
|
41
|
-
for await (const dirent of dir) {
|
|
42
|
-
if (dirent.isDirectory()) {
|
|
43
|
-
await readRoutes(`${path}/${dirent.name}`);
|
|
44
|
-
} else {
|
|
45
|
-
handleFile(path, dirent);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
registeredEndpoints.forEach(endpoint => console.log(`${endpoint.httpVerb.toUpperCase()} http://localhost:${port}${endpoint.endpoint}`))
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
readRoutes(basePath).catch(console.error);
|
|
52
|
-
|
|
53
|
-
app.listen(port || 3000, function() {
|
|
54
|
-
console.log(`App is listening on port ${port || 3000}!`);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
async function loadModule(moduleName: string) {
|
|
58
|
-
return await import(moduleName);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function handleFile(path: string, dirent: fs.Dirent) {
|
|
62
|
-
const httpVerb = (dirent.name.indexOf('-') > -1 ? dirent.name.split('-')[0] : dirent.name.split('.')[0]) as HttpVerb;
|
|
63
|
-
handleRequest(path, dirent, httpVerb);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function addEndpoint(endpoint: string, httpVerb: HttpVerb, model: any) {
|
|
67
|
-
app[httpVerb](endpoint, (req, res) => res.send(model.data));
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function handleRequest(path: string, dirent: fs.Dirent, httpVerb: HttpVerb) {
|
|
71
|
-
const endpoint = convertFileNameToEndpoint(path, dirent, httpVerb);
|
|
72
|
-
const modulePath = `${path}/${dirent.name}`;
|
|
73
|
-
registeredEndpoints.push({httpVerb, endpoint});
|
|
74
|
-
loadModule(modulePath)
|
|
75
|
-
.then(model => addEndpoint(endpoint, httpVerb, model))
|
|
76
|
-
.catch(err => console.error(err));
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function convertFileNameToEndpoint(path: string, dirent: fs.Dirent, httpVerb: HttpVerb): string {
|
|
80
|
-
const endpoint = `${path.replace(basePath, '')}/${dirent.name}`
|
|
81
|
-
.replace('.ts', '')
|
|
82
|
-
.replace(httpVerb, '')
|
|
83
|
-
.replace('-', '');
|
|
84
|
-
if (endpoint.endsWith('/')) {
|
|
85
|
-
return endpoint.substring(0, endpoint.length - 1);
|
|
86
|
-
}
|
|
87
|
-
return endpoint;
|
|
88
|
-
}
|
|
8
|
+
server.start();
|
package/src/logger.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type HttpVerb = 'get' | 'post' | 'put' | 'delete' | 'patch' | 'options' | 'head';
|
package/tms-models/users/get.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { Config } from '../../src/models/config';
|
|
2
|
+
|
|
1
3
|
export interface User {
|
|
2
4
|
id: number;
|
|
3
5
|
firstName: string;
|
|
@@ -18,3 +20,10 @@ export const data: User[] = [{
|
|
|
18
20
|
lastName: 'Development',
|
|
19
21
|
creationDate: newDate()
|
|
20
22
|
}];
|
|
23
|
+
|
|
24
|
+
export const config: Config = {
|
|
25
|
+
server: {
|
|
26
|
+
delay: 2000,
|
|
27
|
+
statusCode: 418
|
|
28
|
+
}
|
|
29
|
+
}
|