express-generator-typescript 1.9.2 → 1.9.5
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/lib/auth-proj/.eslintrc.json +1 -10
- package/lib/auth-proj/src/pre-start/env/development.env +1 -1
- package/lib/auth-proj/src/pre-start/index.ts +1 -1
- package/lib/auth-proj/src/routes/middleware.ts +14 -2
- package/lib/auth-proj/src/server.ts +3 -2
- package/lib/auth-proj/src/types/express/index.d.ts +11 -10
- package/lib/project-files/.eslintrc.json +1 -10
- package/lib/project-files/src/index.ts +1 -1
- package/lib/project-files/src/pre-start/index.ts +1 -1
- package/lib/project-files/src/public/scripts/index.js +1 -2
- package/lib/project-files/src/repos/mock-orm.ts +12 -2
- package/lib/project-files/src/server.ts +3 -2
- package/lib/project-files/src/shared/functions.ts +2 -2
- package/lib/project-files/src/types/express/index.d.ts +12 -0
- package/lib/socket-io/.eslintrc.json +1 -10
- package/lib/socket-io/package.json +1 -1
- package/lib/socket-io/src/index.ts +1 -1
- package/lib/socket-io/src/pre-start/index.ts +1 -1
- package/lib/socket-io/src/repos/mock-orm.ts +15 -5
- package/lib/socket-io/src/routes/api.ts +1 -1
- package/lib/socket-io/src/routes/chat-router.ts +32 -16
- package/lib/socket-io/src/routes/middleware.ts +22 -6
- package/lib/socket-io/src/server.ts +3 -2
- package/lib/socket-io/src/types/express/index.d.ts +23 -0
- package/package.json +1 -1
- package/lib/auth-proj/package-lock.json +0 -6770
- package/lib/socket-io/src/services/chat-service.ts +0 -42
|
@@ -21,15 +21,6 @@
|
|
|
21
21
|
"no-console": 1,
|
|
22
22
|
"no-extra-boolean-cast": 0,
|
|
23
23
|
"semi": 1,
|
|
24
|
-
"@typescript-eslint/
|
|
25
|
-
"@typescript-eslint/explicit-module-boundary-types": 0,
|
|
26
|
-
"@typescript-eslint/no-explicit-any": 0,
|
|
27
|
-
"@typescript-eslint/no-floating-promises": 0,
|
|
28
|
-
"@typescript-eslint/no-unsafe-member-access": 0,
|
|
29
|
-
"@typescript-eslint/no-unsafe-assignment": 0,
|
|
30
|
-
"@typescript-eslint/no-misused-promises": 0,
|
|
31
|
-
"@typescript-eslint/no-unsafe-argument": 0,
|
|
32
|
-
"@typescript-eslint/no-unsafe-return": 0,
|
|
33
|
-
"@typescript-eslint/no-unsafe-call": 0
|
|
24
|
+
"@typescript-eslint/no-misused-promises": 0
|
|
34
25
|
}
|
|
35
26
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import StatusCodes from 'http-status-codes';
|
|
2
2
|
import { Request, Response, NextFunction } from 'express';
|
|
3
|
+
import { JwtPayload } from 'jsonwebtoken';
|
|
3
4
|
|
|
4
|
-
import { UserRoles } from '@models/user-model';
|
|
5
|
+
import { IUser, UserRoles } from '@models/user-model';
|
|
5
6
|
import envVars from 'src/shared/env-vars';
|
|
6
7
|
import jwtUtil from '@util/jwt-util';
|
|
7
8
|
|
|
@@ -12,6 +13,16 @@ const { UNAUTHORIZED } = StatusCodes;
|
|
|
12
13
|
const jwtNotPresentErr = 'JWT not present in signed cookie.';
|
|
13
14
|
|
|
14
15
|
|
|
16
|
+
// **** Types **** //
|
|
17
|
+
|
|
18
|
+
export interface ISessionUser extends JwtPayload {
|
|
19
|
+
id: number;
|
|
20
|
+
email: string;
|
|
21
|
+
name: string;
|
|
22
|
+
role: IUser['role'];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
15
26
|
// **** Functions **** //
|
|
16
27
|
|
|
17
28
|
/**
|
|
@@ -28,7 +39,7 @@ export async function adminMw(req: Request, res: Response, next: NextFunction) {
|
|
|
28
39
|
// Make sure user role is an admin
|
|
29
40
|
const clientData = await jwtUtil.decode(jwt);
|
|
30
41
|
if (typeof clientData === 'object' && clientData.role === UserRoles.Admin) {
|
|
31
|
-
res.locals.sessionUser = clientData;
|
|
42
|
+
res.locals.sessionUser = clientData as ISessionUser;
|
|
32
43
|
next();
|
|
33
44
|
} else {
|
|
34
45
|
throw Error(jwtNotPresentErr);
|
|
@@ -44,3 +55,4 @@ export async function adminMw(req: Request, res: Response, next: NextFunction) {
|
|
|
44
55
|
return res.status(UNAUTHORIZED).json({ error });
|
|
45
56
|
}
|
|
46
57
|
}
|
|
58
|
+
|
|
@@ -3,7 +3,7 @@ import morgan from 'morgan';
|
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import helmet from 'helmet';
|
|
5
5
|
import StatusCodes from 'http-status-codes';
|
|
6
|
-
import express, { Request, Response } from 'express';
|
|
6
|
+
import express, { Request, Response, NextFunction } from 'express';
|
|
7
7
|
|
|
8
8
|
import 'express-async-errors';
|
|
9
9
|
|
|
@@ -41,7 +41,8 @@ if (envVars.nodeEnv === 'production') {
|
|
|
41
41
|
app.use('/api', BaseRouter);
|
|
42
42
|
|
|
43
43
|
// Error handling
|
|
44
|
-
|
|
44
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
45
|
+
app.use((err: Error | CustomError, req: Request, res: Response, _: NextFunction) => {
|
|
45
46
|
logger.err(err, true);
|
|
46
47
|
const status = (err instanceof CustomError ? err.HttpStatus : StatusCodes.BAD_REQUEST);
|
|
47
48
|
return res.status(status).json({
|
|
@@ -1,22 +1,23 @@
|
|
|
1
|
-
import { IUser } from
|
|
1
|
+
import { IUser } from '@models/user-model';
|
|
2
|
+
import { ISessionUser } from '@routes/middleware';
|
|
2
3
|
|
|
3
4
|
|
|
4
5
|
declare module 'express' {
|
|
6
|
+
|
|
5
7
|
export interface Request {
|
|
6
8
|
signedCookies: Record<string, string>,
|
|
7
9
|
body: {
|
|
8
10
|
user?: IUser
|
|
9
11
|
email?: string;
|
|
10
12
|
password?: string;
|
|
13
|
+
message?: string;
|
|
14
|
+
socketId?: string;
|
|
11
15
|
};
|
|
12
16
|
}
|
|
13
|
-
}
|
|
14
17
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
// }
|
|
22
|
-
// }
|
|
18
|
+
export interface Response {
|
|
19
|
+
locals: {
|
|
20
|
+
sessionUser: ISessionUser;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -21,15 +21,6 @@
|
|
|
21
21
|
"no-console": 1,
|
|
22
22
|
"no-extra-boolean-cast": 0,
|
|
23
23
|
"semi": 1,
|
|
24
|
-
"@typescript-eslint/
|
|
25
|
-
"@typescript-eslint/explicit-module-boundary-types": 0,
|
|
26
|
-
"@typescript-eslint/no-explicit-any": 0,
|
|
27
|
-
"@typescript-eslint/no-floating-promises": 0,
|
|
28
|
-
"@typescript-eslint/no-unsafe-member-access": 0,
|
|
29
|
-
"@typescript-eslint/no-unsafe-assignment": 0,
|
|
30
|
-
"@typescript-eslint/no-misused-promises": 0,
|
|
31
|
-
"@typescript-eslint/no-unsafe-argument": 0,
|
|
32
|
-
"@typescript-eslint/no-unsafe-return": 0,
|
|
33
|
-
"@typescript-eslint/no-unsafe-call": 0
|
|
24
|
+
"@typescript-eslint/no-misused-promises": 0
|
|
34
25
|
}
|
|
35
26
|
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
|
2
|
+
|
|
1
3
|
import jsonfile from 'jsonfile';
|
|
4
|
+
import { IUser } from '@models/user-model';
|
|
2
5
|
|
|
3
6
|
|
|
4
7
|
// **** Variables **** //
|
|
@@ -6,19 +9,26 @@ import jsonfile from 'jsonfile';
|
|
|
6
9
|
const dbFilePath = 'src/repos/database.json';
|
|
7
10
|
|
|
8
11
|
|
|
12
|
+
// **** Types **** //
|
|
13
|
+
|
|
14
|
+
interface IDb {
|
|
15
|
+
users: IUser[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
9
19
|
// **** Functions **** //
|
|
10
20
|
|
|
11
21
|
/**
|
|
12
22
|
* Fetch the json from the file.
|
|
13
23
|
*/
|
|
14
|
-
function openDb(): Promise<
|
|
24
|
+
function openDb(): Promise<IDb> {
|
|
15
25
|
return jsonfile.readFile(dbFilePath);
|
|
16
26
|
}
|
|
17
27
|
|
|
18
28
|
/**
|
|
19
29
|
* Update the file.
|
|
20
30
|
*/
|
|
21
|
-
function saveDb(db:
|
|
31
|
+
function saveDb(db: IDb): Promise<void> {
|
|
22
32
|
return jsonfile.writeFile(dbFilePath, db);
|
|
23
33
|
}
|
|
24
34
|
|
|
@@ -4,7 +4,7 @@ import path from 'path';
|
|
|
4
4
|
import helmet from 'helmet';
|
|
5
5
|
import logger from 'jet-logger';
|
|
6
6
|
|
|
7
|
-
import express, {
|
|
7
|
+
import express, { Request, Response, NextFunction } from 'express';
|
|
8
8
|
import StatusCodes from 'http-status-codes';
|
|
9
9
|
import 'express-async-errors';
|
|
10
10
|
|
|
@@ -42,7 +42,8 @@ if (envVars.nodeEnv === 'production') {
|
|
|
42
42
|
app.use('/api', apiRouter);
|
|
43
43
|
|
|
44
44
|
// Error handling
|
|
45
|
-
|
|
45
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
46
|
+
app.use((err: Error | CustomError, req: Request, res: Response, _: NextFunction) => {
|
|
46
47
|
logger.err(err, true);
|
|
47
48
|
const status = (err instanceof CustomError ? err.HttpStatus : StatusCodes.BAD_REQUEST);
|
|
48
49
|
return res.status(status).json({
|
|
@@ -8,11 +8,11 @@ export function pErr(err?: Error): void {
|
|
|
8
8
|
if (!!err) {
|
|
9
9
|
logger.err(err);
|
|
10
10
|
}
|
|
11
|
-
}
|
|
11
|
+
}
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Get a random number between 1 and 1,000,000,000,000
|
|
15
15
|
*/
|
|
16
16
|
export function getRandomInt(): number {
|
|
17
17
|
return Math.floor(Math.random() * 1_000_000_000_000);
|
|
18
|
-
}
|
|
18
|
+
}
|
|
@@ -21,15 +21,6 @@
|
|
|
21
21
|
"no-console": 1,
|
|
22
22
|
"no-extra-boolean-cast": 0,
|
|
23
23
|
"semi": 1,
|
|
24
|
-
"@typescript-eslint/
|
|
25
|
-
"@typescript-eslint/explicit-module-boundary-types": 0,
|
|
26
|
-
"@typescript-eslint/no-explicit-any": 0,
|
|
27
|
-
"@typescript-eslint/no-floating-promises": 0,
|
|
28
|
-
"@typescript-eslint/no-unsafe-member-access": 0,
|
|
29
|
-
"@typescript-eslint/no-unsafe-assignment": 0,
|
|
30
|
-
"@typescript-eslint/no-misused-promises": 0,
|
|
31
|
-
"@typescript-eslint/no-unsafe-argument": 0,
|
|
32
|
-
"@typescript-eslint/no-unsafe-return": 0,
|
|
33
|
-
"@typescript-eslint/no-unsafe-call": 0
|
|
24
|
+
"@typescript-eslint/no-misused-promises": 0
|
|
34
25
|
}
|
|
35
26
|
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
|
2
|
+
|
|
1
3
|
import jsonfile from 'jsonfile';
|
|
4
|
+
import { IUser } from '@models/user-model';
|
|
2
5
|
|
|
3
6
|
|
|
4
7
|
// **** Variables **** //
|
|
@@ -6,19 +9,26 @@ import jsonfile from 'jsonfile';
|
|
|
6
9
|
const dbFilePath = 'src/repos/database.json';
|
|
7
10
|
|
|
8
11
|
|
|
12
|
+
// **** Types **** //
|
|
13
|
+
|
|
14
|
+
interface IDb {
|
|
15
|
+
users: IUser[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
9
19
|
// **** Functions **** //
|
|
10
20
|
|
|
11
21
|
/**
|
|
12
22
|
* Fetch the json from the file.
|
|
13
23
|
*/
|
|
14
|
-
function openDb(): Promise<
|
|
24
|
+
function openDb(): Promise<IDb> {
|
|
15
25
|
return jsonfile.readFile(dbFilePath);
|
|
16
26
|
}
|
|
17
27
|
|
|
18
28
|
/**
|
|
19
29
|
* Update the file.
|
|
20
30
|
*/
|
|
21
|
-
function saveDb(db:
|
|
31
|
+
function saveDb(db: IDb): Promise<void> {
|
|
22
32
|
return jsonfile.writeFile(dbFilePath, db);
|
|
23
33
|
}
|
|
24
34
|
|
|
@@ -26,6 +36,6 @@ function saveDb(db: Record<string, any>): Promise<void> {
|
|
|
26
36
|
// **** Export default **** //
|
|
27
37
|
|
|
28
38
|
export default {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
} as const;
|
|
39
|
+
openDb,
|
|
40
|
+
saveDb,
|
|
41
|
+
} as const;
|
|
@@ -11,7 +11,7 @@ const apiRouter = Router();
|
|
|
11
11
|
// Add api routes
|
|
12
12
|
apiRouter.use('/auth', authRouter);
|
|
13
13
|
apiRouter.use('/users', authMw, userRouter);
|
|
14
|
-
apiRouter.use('/chat', authMw, chatRouter)
|
|
14
|
+
apiRouter.use('/chat', authMw, chatRouter);
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
// **** Export default **** //
|
|
@@ -2,7 +2,6 @@ import SocketIO from 'socket.io';
|
|
|
2
2
|
import StatusCodes from 'http-status-codes';
|
|
3
3
|
import { Router, Request, Response } from 'express';
|
|
4
4
|
|
|
5
|
-
import chatService from '@services/chat-service';
|
|
6
5
|
import { ParamMissingError, RoomNotFoundError } from '@shared/errors';
|
|
7
6
|
|
|
8
7
|
|
|
@@ -18,26 +17,27 @@ export const p = {
|
|
|
18
17
|
emit: '/emit-message',
|
|
19
18
|
} as const;
|
|
20
19
|
|
|
20
|
+
export const socketRoomName = 'jet-logger-chat-room';
|
|
21
|
+
|
|
21
22
|
|
|
22
23
|
// **** Routes **** //
|
|
23
24
|
|
|
24
25
|
/**
|
|
25
26
|
* Connect to socket room.
|
|
26
27
|
*/
|
|
27
|
-
router.get(p.connect, (req: Request, res: Response) => {
|
|
28
|
+
router.get(p.connect, async (req: Request, res: Response) => {
|
|
28
29
|
const { socketId } = req.params;
|
|
29
30
|
// Check params
|
|
30
31
|
if (!socketId) {
|
|
31
32
|
throw new ParamMissingError();
|
|
32
33
|
}
|
|
33
|
-
// Get
|
|
34
|
-
const
|
|
35
|
-
const socket = io.sockets.sockets.get(socketId);
|
|
36
|
-
if (!socket) {
|
|
37
|
-
throw new RoomNotFoundError();
|
|
38
|
-
}
|
|
34
|
+
// Get socket
|
|
35
|
+
const socket = getSocket(req, socketId);
|
|
39
36
|
// Connect
|
|
40
|
-
|
|
37
|
+
await Promise.all([
|
|
38
|
+
socket.leave(socketRoomName),
|
|
39
|
+
socket.join(socketRoomName),
|
|
40
|
+
]);
|
|
41
41
|
// Return
|
|
42
42
|
return res.status(OK).end();
|
|
43
43
|
});
|
|
@@ -52,14 +52,15 @@ router.post(p.emit, (req: Request, res: Response) => {
|
|
|
52
52
|
if (!socketId || !message) {
|
|
53
53
|
throw new ParamMissingError();
|
|
54
54
|
}
|
|
55
|
-
// Get
|
|
56
|
-
const
|
|
57
|
-
const socket = io.sockets.sockets.get(socketId);
|
|
58
|
-
if (!socket) {
|
|
59
|
-
throw new RoomNotFoundError();
|
|
60
|
-
}
|
|
55
|
+
// Get socket
|
|
56
|
+
const socket = getSocket(req, socketId);
|
|
61
57
|
// Connect
|
|
62
|
-
|
|
58
|
+
const room = socket.to(socketRoomName);
|
|
59
|
+
room.emit('emit-msg', {
|
|
60
|
+
timestamp: Date.now(),
|
|
61
|
+
content: message,
|
|
62
|
+
senderName: sessionUser.name,
|
|
63
|
+
});
|
|
63
64
|
// Return
|
|
64
65
|
return res.status(OK).json({
|
|
65
66
|
senderName: sessionUser.name,
|
|
@@ -67,6 +68,21 @@ router.post(p.emit, (req: Request, res: Response) => {
|
|
|
67
68
|
});
|
|
68
69
|
|
|
69
70
|
|
|
71
|
+
// **** Helpers **** //
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Get the socket from the Express request object.
|
|
75
|
+
*/
|
|
76
|
+
function getSocket(req: Request, socketId: string): SocketIO.Socket {
|
|
77
|
+
const io = req.app.get('socketio') as SocketIO.Server;
|
|
78
|
+
const socket = io.sockets.sockets.get(socketId);
|
|
79
|
+
if (!socket) {
|
|
80
|
+
throw new RoomNotFoundError();
|
|
81
|
+
}
|
|
82
|
+
return socket;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
|
|
70
86
|
// **** Export default **** //
|
|
71
87
|
|
|
72
88
|
export default router;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import StatusCodes from 'http-status-codes';
|
|
2
2
|
import { Request, Response, NextFunction } from 'express';
|
|
3
|
+
import { JwtPayload } from 'jsonwebtoken';
|
|
3
4
|
|
|
4
5
|
import { cookieProps } from '@routes/auth-router';
|
|
6
|
+
import { IUser } from '@models/user-model';
|
|
5
7
|
import jwtUtil from '@util/jwt-util';
|
|
6
8
|
|
|
7
9
|
|
|
@@ -11,6 +13,16 @@ const { UNAUTHORIZED } = StatusCodes;
|
|
|
11
13
|
const jwtNotPresentErr = 'JWT not present in signed cookie.';
|
|
12
14
|
|
|
13
15
|
|
|
16
|
+
// **** Types **** //
|
|
17
|
+
|
|
18
|
+
export interface ISessionUser extends JwtPayload {
|
|
19
|
+
id: number;
|
|
20
|
+
email: string;
|
|
21
|
+
name: string;
|
|
22
|
+
role: IUser['role'];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
14
26
|
// **** Functions **** //
|
|
15
27
|
|
|
16
28
|
/**
|
|
@@ -25,15 +37,19 @@ export async function authMw(req: Request, res: Response, next: NextFunction) {
|
|
|
25
37
|
}
|
|
26
38
|
// Make sure user role is an admin
|
|
27
39
|
const clientData = await jwtUtil.decode(jwt);
|
|
28
|
-
if (!!clientData) {
|
|
29
|
-
res.locals.sessionUser = clientData;
|
|
40
|
+
if (!!clientData && typeof clientData !== 'string') {
|
|
41
|
+
res.locals.sessionUser = clientData as ISessionUser;
|
|
30
42
|
next();
|
|
31
43
|
} else {
|
|
32
44
|
throw Error(jwtNotPresentErr);
|
|
33
45
|
}
|
|
34
46
|
} catch (err) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
47
|
+
let error;
|
|
48
|
+
if (typeof err === 'string') {
|
|
49
|
+
error = err;
|
|
50
|
+
} else if (err instanceof Error) {
|
|
51
|
+
error = err.message;
|
|
52
|
+
}
|
|
53
|
+
return res.status(UNAUTHORIZED).json({ error });
|
|
38
54
|
}
|
|
39
|
-
}
|
|
55
|
+
}
|
|
@@ -40,7 +40,8 @@ if (envVars.nodeEnv === 'production') {
|
|
|
40
40
|
app.use('/api', BaseRouter);
|
|
41
41
|
|
|
42
42
|
// Error handling
|
|
43
|
-
|
|
43
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
44
|
+
app.use((err: Error | CustomError, req: Request, res: Response, _: NextFunction) => {
|
|
44
45
|
logger.err(err, true);
|
|
45
46
|
const status = (err instanceof CustomError ? err.HttpStatus : StatusCodes.BAD_REQUEST);
|
|
46
47
|
return res.status(status).json({
|
|
@@ -57,7 +58,7 @@ const staticDir = path.join(__dirname, 'public');
|
|
|
57
58
|
app.use(express.static(staticDir));
|
|
58
59
|
|
|
59
60
|
// Login page
|
|
60
|
-
app.get('/', (
|
|
61
|
+
app.get('/', (_: Request, res: Response) => {
|
|
61
62
|
return res.sendFile('login.html', {root: viewsDir});
|
|
62
63
|
});
|
|
63
64
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { IUser } from '@models/user-model';
|
|
2
|
+
import { ISessionUser } from '@routes/middleware';
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
declare module 'express' {
|
|
6
|
+
|
|
7
|
+
export interface Request {
|
|
8
|
+
signedCookies: Record<string, string>,
|
|
9
|
+
body: {
|
|
10
|
+
user?: IUser
|
|
11
|
+
email?: string;
|
|
12
|
+
password?: string;
|
|
13
|
+
message?: string;
|
|
14
|
+
socketId?: string;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface Response {
|
|
19
|
+
locals: {
|
|
20
|
+
sessionUser: ISessionUser;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "express-generator-typescript",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.5",
|
|
4
4
|
"description": "Generate new Express applications similar to express-generate which but sets it up to use TypeScript instead",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"express-generator-typescript": "node bin/cli.js",
|