hono 0.5.2 → 0.5.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/README.md +34 -14
- package/dist/context.js +2 -2
- package/dist/hono.d.ts +14 -5
- package/dist/hono.js +4 -0
- package/dist/router/reg-exp-router/node.d.ts +1 -3
- package/dist/router/reg-exp-router/router.d.ts +1 -9
- package/dist/router/reg-exp-router/router.js +66 -48
- package/package.json +10 -1
package/README.md
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
# Hono
|
|
1
|
+
# Hono\[炎\]
|
|
2
|
+
|
|
3
|
+
<p>
|
|
4
|
+
<a href="https://github.com/yusukebe/hono/blob/master/README.md">English</a>
|
|
5
|
+
·
|
|
6
|
+
<a href="https://github.com/yusukebe/hono/blob/master/docs/README.ja.md">日本語</a>
|
|
7
|
+
</p>
|
|
2
8
|
|
|
3
9
|
[](https://github.com/yusukebe/hono/actions)
|
|
4
10
|
[](https://github.com/yusukebe/hono/blob/master/LICENSE)
|
|
@@ -8,7 +14,7 @@
|
|
|
8
14
|
[](https://github.com/yusukebe/hono/pulse)
|
|
9
15
|
[](https://github.com/yusukebe/hono/commits/master)
|
|
10
16
|
|
|
11
|
-
Hono[炎] - _**means flame🔥 in Japanese**_ - is small, simple, and ultrafast web framework for
|
|
17
|
+
Hono[炎] - _**means flame🔥 in Japanese**_ - is small, simple, and ultrafast web framework for Cloudflare Workers and Fastly Compute@Edge.
|
|
12
18
|
|
|
13
19
|
```js
|
|
14
20
|
import { Hono } from 'hono'
|
|
@@ -22,7 +28,7 @@ app.fire()
|
|
|
22
28
|
## Features
|
|
23
29
|
|
|
24
30
|
- **Ultrafast** - the router does not use linear loops.
|
|
25
|
-
- **Zero-dependencies** - using only Web standard API.
|
|
31
|
+
- **Zero-dependencies** - using only Service Worker and Web standard API.
|
|
26
32
|
- **Middleware** - built-in middleware and ability to extend with your own middleware.
|
|
27
33
|
- **Optimized** - for Cloudflare Workers.
|
|
28
34
|
|
|
@@ -53,13 +59,13 @@ Now, the named path parameter has types.
|
|
|
53
59
|
|
|
54
60
|
You can install Hono from the npm registry.
|
|
55
61
|
|
|
56
|
-
```
|
|
62
|
+
```
|
|
57
63
|
$ yarn add hono
|
|
58
64
|
```
|
|
59
65
|
|
|
60
66
|
or
|
|
61
67
|
|
|
62
|
-
```
|
|
68
|
+
```
|
|
63
69
|
$ npm install hono
|
|
64
70
|
```
|
|
65
71
|
|
|
@@ -75,6 +81,7 @@ An instance of `Hono` has these methods.
|
|
|
75
81
|
- app.**onError**(err, handler)
|
|
76
82
|
- app.**fire**()
|
|
77
83
|
- app.**fetch**(request, env, event)
|
|
84
|
+
- app.**request**(path, option)
|
|
78
85
|
|
|
79
86
|
## Routing
|
|
80
87
|
|
|
@@ -149,6 +156,8 @@ app.get('/fetch-url', async (c) => {
|
|
|
149
156
|
|
|
150
157
|
### Built-in Middleware
|
|
151
158
|
|
|
159
|
+
Hono has built-in middleware.
|
|
160
|
+
|
|
152
161
|
```js
|
|
153
162
|
import { Hono } from 'hono'
|
|
154
163
|
import { poweredBy } from 'hono/powered-by'
|
|
@@ -213,7 +222,7 @@ app.onError((err, c) => {
|
|
|
213
222
|
|
|
214
223
|
## Context
|
|
215
224
|
|
|
216
|
-
To handle Request and Reponse, you can use Context object.
|
|
225
|
+
To handle Request and Reponse, you can use `Context` object.
|
|
217
226
|
|
|
218
227
|
### c.req
|
|
219
228
|
|
|
@@ -303,7 +312,7 @@ app.get('/', (c) => {
|
|
|
303
312
|
|
|
304
313
|
### c.notFound()
|
|
305
314
|
|
|
306
|
-
Return the `
|
|
315
|
+
Return the `Not Found` Response.
|
|
307
316
|
|
|
308
317
|
```js
|
|
309
318
|
app.get('/notfound', (c) => {
|
|
@@ -379,6 +388,17 @@ export default app
|
|
|
379
388
|
*/
|
|
380
389
|
```
|
|
381
390
|
|
|
391
|
+
## request
|
|
392
|
+
|
|
393
|
+
`request` is a useful method for testing.
|
|
394
|
+
|
|
395
|
+
```js
|
|
396
|
+
test('GET /hello is ok', async () => {
|
|
397
|
+
const res = await app.request('http://localhost/hello')
|
|
398
|
+
expect(res.status).toBe(200)
|
|
399
|
+
})
|
|
400
|
+
```
|
|
401
|
+
|
|
382
402
|
## Cloudflare Workers with Hono
|
|
383
403
|
|
|
384
404
|
Using [Wrangler](https://developers.cloudflare.com/workers/cli-wrangler/) or [Miniflare](https://miniflare.dev), you can develop the application locally and publish it with few commands.
|
|
@@ -400,10 +420,10 @@ Let's write your first code for Cloudflare Workers with Hono.
|
|
|
400
420
|
|
|
401
421
|
Make a npm skeleton directory.
|
|
402
422
|
|
|
403
|
-
```
|
|
404
|
-
mkdir hono-example
|
|
405
|
-
cd hono-example
|
|
406
|
-
npm init -y
|
|
423
|
+
```
|
|
424
|
+
$ mkdir hono-example
|
|
425
|
+
$ cd hono-example
|
|
426
|
+
$ npm init -y
|
|
407
427
|
```
|
|
408
428
|
|
|
409
429
|
### 2. `wrangler init`
|
|
@@ -426,7 +446,7 @@ Would you like to create a Worker at src/index.js? (y/n) <--- n
|
|
|
426
446
|
|
|
427
447
|
Install `hono` from the npm registry.
|
|
428
448
|
|
|
429
|
-
```
|
|
449
|
+
```
|
|
430
450
|
$ npm i hono
|
|
431
451
|
```
|
|
432
452
|
|
|
@@ -448,7 +468,7 @@ app.fire()
|
|
|
448
468
|
|
|
449
469
|
Run the development server locally. Then, access `http://127.0.0.1:8787/` in your Web browser.
|
|
450
470
|
|
|
451
|
-
```
|
|
471
|
+
```
|
|
452
472
|
$ npx wrangler@beta dev index.js
|
|
453
473
|
```
|
|
454
474
|
|
|
@@ -456,7 +476,7 @@ $ npx wrangler@beta dev index.js
|
|
|
456
476
|
|
|
457
477
|
Deploy to Cloudflare. That's all!
|
|
458
478
|
|
|
459
|
-
```
|
|
479
|
+
```
|
|
460
480
|
$ npx wrangler@beta publish index.js
|
|
461
481
|
```
|
|
462
482
|
|
package/dist/context.js
CHANGED
|
@@ -35,8 +35,8 @@ class Context {
|
|
|
35
35
|
this._statusText = (0, http_status_1.getStatusText)(number);
|
|
36
36
|
}
|
|
37
37
|
newResponse(data, init = {}) {
|
|
38
|
-
init.status = init.status || this._status;
|
|
39
|
-
init.statusText = init.statusText || this._statusText;
|
|
38
|
+
init.status = init.status || this._status || 200;
|
|
39
|
+
init.statusText = init.statusText || this._statusText || (0, http_status_1.getStatusText)(init.status);
|
|
40
40
|
init.headers = Object.assign(Object.assign({}, this._headers), init.headers);
|
|
41
41
|
// Content-Length
|
|
42
42
|
let length = 0;
|
package/dist/hono.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/// <reference types="@cloudflare/workers-types" />
|
|
2
2
|
import { Context } from './context';
|
|
3
3
|
import type { Env } from './context';
|
|
4
|
-
import type {
|
|
4
|
+
import type { Router } from './router';
|
|
5
5
|
declare global {
|
|
6
6
|
interface Request<ParamKeyType = string> {
|
|
7
7
|
param: (key: ParamKeyType) => string;
|
|
@@ -26,25 +26,34 @@ export declare class Hono {
|
|
|
26
26
|
middlewareRouters: Router<MiddlewareHandler>[];
|
|
27
27
|
tempPath: string;
|
|
28
28
|
constructor(init?: Partial<Pick<Hono, 'routerClass' | 'strict'>>);
|
|
29
|
-
notFoundHandler
|
|
30
|
-
errorHandler
|
|
29
|
+
private notFoundHandler;
|
|
30
|
+
private errorHandler;
|
|
31
31
|
get<Path extends string>(path: Path, handler: Handler<ParamKeys<Path>>): Hono;
|
|
32
|
+
get(path: string, handler: Handler<string>): Hono;
|
|
32
33
|
post<Path extends string>(path: Path, handler: Handler<ParamKeys<Path>>): Hono;
|
|
34
|
+
post(path: string, handler: Handler<string>): Hono;
|
|
33
35
|
put<Path extends string>(path: Path, handler: Handler<ParamKeys<Path>>): Hono;
|
|
36
|
+
put(path: string, handler: Handler<string>): Hono;
|
|
34
37
|
head<Path extends string>(path: Path, handler: Handler<ParamKeys<Path>>): Hono;
|
|
38
|
+
head(path: string, handler: Handler<string>): Hono;
|
|
35
39
|
delete<Path extends string>(path: Path, handler: Handler<ParamKeys<Path>>): Hono;
|
|
40
|
+
delete(path: string, handler: Handler<string>): Hono;
|
|
36
41
|
options<Path extends string>(path: Path, handler: Handler<ParamKeys<Path>>): Hono;
|
|
42
|
+
options(path: string, handler: Handler<string>): Hono;
|
|
37
43
|
patch<Path extends string>(path: Path, handler: Handler<ParamKeys<Path>>): Hono;
|
|
44
|
+
patch(path: string, handler: Handler<string>): Hono;
|
|
38
45
|
all<Path extends string>(path: Path, handler: Handler<ParamKeys<Path>>): Hono;
|
|
46
|
+
all(path: string, handler: Handler<string>): Hono;
|
|
39
47
|
route(path: string): Hono;
|
|
40
48
|
use(path: string, middleware: MiddlewareHandler): void;
|
|
41
49
|
onError(handler: ErrorHandler): Hono;
|
|
42
50
|
notFound(handler: NotFoundHandler): Hono;
|
|
43
|
-
addRoute
|
|
44
|
-
matchRoute
|
|
51
|
+
private addRoute;
|
|
52
|
+
private matchRoute;
|
|
45
53
|
dispatch(request: Request, env?: Env, event?: FetchEvent): Promise<Response>;
|
|
46
54
|
handleEvent(event: FetchEvent): Promise<Response>;
|
|
47
55
|
fetch(request: Request, env?: Env, event?: FetchEvent): Promise<Response>;
|
|
56
|
+
request(input: RequestInfo, requestInit?: RequestInit): Promise<Response>;
|
|
48
57
|
fire(): void;
|
|
49
58
|
}
|
|
50
59
|
export {};
|
package/dist/hono.js
CHANGED
|
@@ -128,6 +128,10 @@ class Hono {
|
|
|
128
128
|
async fetch(request, env, event) {
|
|
129
129
|
return this.dispatch(request, env, event);
|
|
130
130
|
}
|
|
131
|
+
request(input, requestInit) {
|
|
132
|
+
const req = new Request(input, requestInit);
|
|
133
|
+
return this.dispatch(req);
|
|
134
|
+
}
|
|
131
135
|
fire() {
|
|
132
136
|
addEventListener('fetch', (event) => {
|
|
133
137
|
event.respondWith(this.handleEvent(event));
|
|
@@ -5,9 +5,7 @@ export interface Context {
|
|
|
5
5
|
export declare class Node {
|
|
6
6
|
index?: number;
|
|
7
7
|
varIndex?: number;
|
|
8
|
-
children:
|
|
9
|
-
[key: string]: Node;
|
|
10
|
-
};
|
|
8
|
+
children: Record<string, Node>;
|
|
11
9
|
insert(tokens: readonly string[], index: number, paramMap: ParamMap, context: Context): void;
|
|
12
10
|
buildRegExpStr(): string;
|
|
13
11
|
}
|
|
@@ -1,15 +1,7 @@
|
|
|
1
1
|
import { Router, Result } from '../../router';
|
|
2
|
-
import type { ParamMap, ReplacementMap } from './trie';
|
|
3
2
|
declare type Route<T> = [string, T];
|
|
4
|
-
declare type HandlerData<T> = [T, ParamMap];
|
|
5
|
-
declare type Matcher<T> = [RegExp | true, ReplacementMap, HandlerData<T>[]];
|
|
6
3
|
export declare class RegExpRouter<T> extends Router<T> {
|
|
7
|
-
routes?:
|
|
8
|
-
[method: string]: Route<T>[];
|
|
9
|
-
};
|
|
10
|
-
matchers?: {
|
|
11
|
-
[method: string]: Matcher<T> | null;
|
|
12
|
-
};
|
|
4
|
+
routes?: Record<string, Route<T>[]>;
|
|
13
5
|
add(method: string, path: string, handler: T): void;
|
|
14
6
|
match(method: string, path: string): Result<T> | null;
|
|
15
7
|
private buildAllMatchers;
|
|
@@ -3,11 +3,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.RegExpRouter = void 0;
|
|
4
4
|
const router_1 = require("../../router");
|
|
5
5
|
const trie_1 = require("./trie");
|
|
6
|
+
const regExpMatchAll = new RegExp('');
|
|
7
|
+
const emptyParam = {};
|
|
6
8
|
class RegExpRouter extends router_1.Router {
|
|
7
9
|
constructor() {
|
|
8
10
|
super(...arguments);
|
|
9
11
|
this.routes = {};
|
|
10
|
-
this.matchers = null;
|
|
11
12
|
}
|
|
12
13
|
add(method, path, handler) {
|
|
13
14
|
var _a;
|
|
@@ -18,43 +19,54 @@ class RegExpRouter extends router_1.Router {
|
|
|
18
19
|
this.routes[method].push([path, handler]);
|
|
19
20
|
}
|
|
20
21
|
match(method, path) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
if (
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
if (!replacementMap) {
|
|
38
|
-
// there is only one route and no capture
|
|
39
|
-
return new router_1.Result(handlers[0][0], {});
|
|
40
|
-
}
|
|
41
|
-
const index = match.indexOf('', 1);
|
|
42
|
-
const [handler, paramMap] = handlers[replacementMap[index]];
|
|
43
|
-
const params = {};
|
|
44
|
-
for (let i = 0; i < paramMap.length; i++) {
|
|
45
|
-
params[paramMap[i][0]] = match[paramMap[i][1]];
|
|
22
|
+
const matchers = this.buildAllMatchers();
|
|
23
|
+
let match;
|
|
24
|
+
// Optimization for middleware
|
|
25
|
+
const methods = Object.keys(matchers);
|
|
26
|
+
if (methods.length === 1 && methods[0] === router_1.METHOD_NAME_OF_ALL) {
|
|
27
|
+
const [regexp, handlers] = matchers[router_1.METHOD_NAME_OF_ALL];
|
|
28
|
+
if (handlers.length === 1) {
|
|
29
|
+
const result = new router_1.Result(handlers[0][0], emptyParam);
|
|
30
|
+
if (regexp === regExpMatchAll) {
|
|
31
|
+
match = () => result;
|
|
32
|
+
}
|
|
33
|
+
else if (handlers.length === 1 && !handlers[0][1]) {
|
|
34
|
+
match = (_, path) => (regexp.test(path) ? result : null);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
46
37
|
}
|
|
47
|
-
|
|
38
|
+
match || (match = (method, path) => {
|
|
39
|
+
const matcher = matchers[method] || matchers[router_1.METHOD_NAME_OF_ALL];
|
|
40
|
+
if (!matcher) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
const match = path.match(matcher[0]);
|
|
44
|
+
if (!match) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
const index = match.indexOf('', 1);
|
|
48
|
+
const [handler, paramMap] = matcher[1][index];
|
|
49
|
+
if (!paramMap) {
|
|
50
|
+
return new router_1.Result(handler, emptyParam);
|
|
51
|
+
}
|
|
52
|
+
const params = {};
|
|
53
|
+
for (let i = 0; i < paramMap.length; i++) {
|
|
54
|
+
params[paramMap[i][0]] = match[paramMap[i][1]];
|
|
55
|
+
}
|
|
56
|
+
return new router_1.Result(handler, params);
|
|
57
|
+
});
|
|
58
|
+
this.match = match;
|
|
59
|
+
return this.match(method, path);
|
|
48
60
|
}
|
|
49
61
|
buildAllMatchers() {
|
|
50
|
-
|
|
62
|
+
const matchers = {};
|
|
51
63
|
Object.keys(this.routes).forEach((method) => {
|
|
52
|
-
this.buildMatcher(method);
|
|
64
|
+
matchers[method] = this.buildMatcher(method);
|
|
53
65
|
});
|
|
54
66
|
delete this.routes; // to reduce memory usage
|
|
67
|
+
return matchers;
|
|
55
68
|
}
|
|
56
69
|
buildMatcher(method) {
|
|
57
|
-
this.matchers || (this.matchers = {});
|
|
58
70
|
const trie = new trie_1.Trie();
|
|
59
71
|
const handlers = [];
|
|
60
72
|
const targetMethods = [method];
|
|
@@ -63,34 +75,40 @@ class RegExpRouter extends router_1.Router {
|
|
|
63
75
|
}
|
|
64
76
|
const routes = targetMethods.flatMap((method) => this.routes[method] || []);
|
|
65
77
|
if (routes.length === 0) {
|
|
66
|
-
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
if (routes.length === 1 && routes[0][0] === '*') {
|
|
70
|
-
this.matchers[method] = [true, null, [[routes[0][1], []]]];
|
|
71
|
-
return;
|
|
78
|
+
return null;
|
|
72
79
|
}
|
|
73
|
-
if (
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
80
|
+
if (method === router_1.METHOD_NAME_OF_ALL) {
|
|
81
|
+
if (routes.length === 1 && routes[0][0] === '*') {
|
|
82
|
+
return [regExpMatchAll, [[routes[0][1], null]]];
|
|
83
|
+
}
|
|
84
|
+
if (routes.length === 1 && !routes[0][0].match(/:/)) {
|
|
85
|
+
// there is only one route and no capture
|
|
86
|
+
const tmp = routes[0][0].endsWith('*')
|
|
87
|
+
? routes[0][0].replace(/\/\*$/, '(?:$|/)') // /path/to/* => /path/to(?:$|/)
|
|
88
|
+
: `${routes[0][0]}$`; // /path/to/action => /path/to/action$
|
|
89
|
+
const regExpStr = `^${tmp.replace(/\*/g, '[^/]+')}`; // /prefix/*/path/to => /prefix/[^/]+/path/to
|
|
90
|
+
return [new RegExp(regExpStr), [[routes[0][1], null]]];
|
|
91
|
+
}
|
|
81
92
|
}
|
|
82
93
|
for (let i = 0; i < routes.length; i++) {
|
|
83
94
|
const paramMap = trie.insert(routes[i][0], i);
|
|
84
|
-
handlers[i] = [routes[i][1], paramMap];
|
|
95
|
+
handlers[i] = [routes[i][1], Object.keys(paramMap).length !== 0 ? paramMap : null];
|
|
85
96
|
}
|
|
86
97
|
const [regexp, indexReplacementMap, paramReplacementMap] = trie.buildRegExp();
|
|
87
98
|
for (let i = 0; i < handlers.length; i++) {
|
|
88
99
|
const paramMap = handlers[i][1];
|
|
89
|
-
|
|
90
|
-
|
|
100
|
+
if (paramMap) {
|
|
101
|
+
for (let i = 0; i < paramMap.length; i++) {
|
|
102
|
+
paramMap[i][1] = paramReplacementMap[paramMap[i][1]];
|
|
103
|
+
}
|
|
91
104
|
}
|
|
92
105
|
}
|
|
93
|
-
|
|
106
|
+
const handlerMap = [];
|
|
107
|
+
// using `in` because indexReplacementMap is a sparse array
|
|
108
|
+
for (const i in indexReplacementMap) {
|
|
109
|
+
handlerMap[i] = handlers[indexReplacementMap[i]];
|
|
110
|
+
}
|
|
111
|
+
return [regexp, handlerMap];
|
|
94
112
|
}
|
|
95
113
|
}
|
|
96
114
|
exports.RegExpRouter = RegExpRouter;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hono",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.5",
|
|
4
4
|
"description": "[炎] Ultrafast web framework for Cloudflare Workers.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -20,10 +20,12 @@
|
|
|
20
20
|
"./body-parse": "./dist/middleware/body-parse/body-parse.js",
|
|
21
21
|
"./cookie": "./dist/middleware/cookie/cookie.js",
|
|
22
22
|
"./cors": "./dist/middleware/cors/cors.js",
|
|
23
|
+
"./etag": "./dist/middleware/etag/etag.js",
|
|
23
24
|
"./logger": "./dist/middleware/logger/logger.js",
|
|
24
25
|
"./mustache": "./dist/middleware/mustache/mustache.js",
|
|
25
26
|
"./powered-by": "./dist/middleware/powered-by/powered-by.js",
|
|
26
27
|
"./serve-static": "./dist/middleware/serve-static/serve-static.js",
|
|
28
|
+
"./router/trie-router": "./dist/router/trie-router/router.js",
|
|
27
29
|
"./router/reg-exp-router": "./dist/router/reg-exp-router/index.js"
|
|
28
30
|
},
|
|
29
31
|
"typesVersions": {
|
|
@@ -40,6 +42,9 @@
|
|
|
40
42
|
"cors": [
|
|
41
43
|
"./dist/middleware/cors/cors.d.ts"
|
|
42
44
|
],
|
|
45
|
+
"etag": [
|
|
46
|
+
"./dist/middleware/etag/etag.d.ts"
|
|
47
|
+
],
|
|
43
48
|
"logger": [
|
|
44
49
|
"./dist/middleware/logger/logger.d.ts"
|
|
45
50
|
],
|
|
@@ -52,6 +57,9 @@
|
|
|
52
57
|
"serve-static": [
|
|
53
58
|
"./dist/middleware/serve-static/serve-static.d.ts"
|
|
54
59
|
],
|
|
60
|
+
"router/trie-router": [
|
|
61
|
+
"./dist/router/trie-router/router.d.ts"
|
|
62
|
+
],
|
|
55
63
|
"router/reg-exp-router": [
|
|
56
64
|
"./dist/router/reg-exp-router/router.d.ts"
|
|
57
65
|
]
|
|
@@ -98,6 +106,7 @@
|
|
|
98
106
|
"jest-environment-miniflare": "^2.0.0",
|
|
99
107
|
"mustache": "^4.2.0",
|
|
100
108
|
"prettier": "^2.5.1",
|
|
109
|
+
"prettier-plugin-md-nocjsp": "^1.2.0",
|
|
101
110
|
"rimraf": "^3.0.2",
|
|
102
111
|
"ts-jest": "^27.1.2",
|
|
103
112
|
"typescript": "^4.5.5"
|