hono 1.5.2 → 1.6.1

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 CHANGED
@@ -13,8 +13,9 @@
13
13
  [![npm type definitions](https://img.shields.io/npm/types/hono)](https://www.npmjs.com/package/hono)
14
14
  [![GitHub commit activity](https://img.shields.io/github/commit-activity/m/honojs/hono)](https://github.com/honojs/hono/pulse)
15
15
  [![GitHub last commit](https://img.shields.io/github/last-commit/honojs/hono)](https://github.com/honojs/hono/commits/master)
16
+ [![Deno badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fdeno-visualizer.danopia.net%2Fshields%2Flatest-version%2Fx%2Fhono%2Fmod.ts)](https://doc.deno.land/https/deno.land/x/hono/mod.ts)
16
17
 
17
- Hono - _**[炎] means flame🔥 in Japanese**_ - is a small, simple, and ultrafast web framework for Cloudflare Workers or Service Worker based serverless such as Fastly Compute@Edge.
18
+ Hono - _**[炎] means flame🔥 in Japanese**_ - is a small, simple, and ultrafast web framework for Cloudflare Workers, Deno, and others.
18
19
 
19
20
  ```ts
20
21
  import { Hono } from 'hono'
@@ -31,9 +32,14 @@ app.fire()
31
32
  - **Zero-dependencies** - using only Service Worker and Web Standard API.
32
33
  - **Middleware** - built-in middleware and ability to extend with your own middleware.
33
34
  - **TypeScript** - first-class TypeScript support.
34
- - **Optimized** - for Cloudflare Workers.
35
+ - **Multi-platform** - works on Cloudflare Workers, Fastly Compute@Edge, or Deno.
35
36
 
36
- ## Benchmark
37
+ ## Benchmarks
38
+
39
+ ### Cloudflare Workers
40
+
41
+ - Machine: Apple MacBook Pro, 32 GiB, M1 Pro
42
+ - Scripts: [benchmarks/handle-event](https://github.com/honojs/hono/tree/master/benchmarks/handle-event)
37
43
 
38
44
  **Hono is fastest**, compared to other routers for Cloudflare Workers.
39
45
 
@@ -47,6 +53,22 @@ Fastest is hono - regexp-router
47
53
  ✨ Done in 43.56s.
48
54
  ```
49
55
 
56
+ ### Deno
57
+
58
+ - Machine: Apple MacBook Pro, 32 GiB, M1 Pro, Deno v1.22.0
59
+ - Scripts: [benchmarks/deno](https://github.com/honojs/hono/tree/master/benchmarks/deno)
60
+ - Method: `autocannon -c 100 -d 40 -p 10 'http://127.0.0.1:8000/user/lookup/username/foo'`
61
+
62
+ **Hono is fastest**, compared to other frameworks for Deno.
63
+
64
+ | Framework | Version | Results |
65
+ | ----------------------------- | :-----: | ----------------------------------------: |
66
+ | **Hono - RegExpRouter** | 1.6.0 | **5118k requests in 40.02s, 865 MB read** |
67
+ | **Hono - TriRouter(default)** | 1.6.0 | **4932k requests in 40.02s, 833 MB read** |
68
+ | Faster | 5.7 | 3579k requests in 40.02s, 551 MB read |
69
+ | oak | 10.5.1 | 2385k requests in 40.02s, 403 MB read |
70
+ | opine | 2.2.0 | 1491k requests in 40.02s, 346 MB read |
71
+
50
72
  ## Why so fast?
51
73
 
52
74
  Routers used in Hono are really smart.
@@ -734,6 +756,27 @@ export default app
734
756
 
735
757
  - Hono Examples - <https://github.com/honojs/examples>
736
758
 
759
+ ## Deno
760
+
761
+ Hono also works with Deno. This feature is still experimental.
762
+
763
+ ```tsx
764
+ /** @jsx jsx */
765
+ import { serve } from 'https://deno.land/std@0.146.0/http/server.ts'
766
+ import { Hono, logger, poweredBy, serveStatic, jsx } from 'https://deno.land/x/hono/mod.ts'
767
+
768
+ const app = new Hono()
769
+
770
+ app.use('*', logger(), poweredBy())
771
+
772
+ app.get('/favicon.ico', serveStatic({ path: './public/favicon.ico' }))
773
+ app.get('/', (c) => {
774
+ return c.html(<h1>Hello Deno!</h1>)
775
+ })
776
+
777
+ serve(app.fire())
778
+ ```
779
+
737
780
  ## Related projects
738
781
 
739
782
  Implementation of the original router `TrieRouter` is inspired by [goblin](https://github.com/bmf-san/goblin). `RegExpRouter` is inspired by [Router::Boom](https://github.com/tokuhirom/Router-Boom). API design is inspired by [express](https://github.com/expressjs/express) and [koa](https://github.com/koajs/koa). [itty-router](https://github.com/kwhitley/itty-router), [Sunder](https://github.com/SunderJS/sunder), and [worktop](https://github.com/lukeed/worktop) are the other routers or frameworks for Cloudflare Workers.
package/dist/context.d.ts CHANGED
@@ -1,12 +1,11 @@
1
1
  /// <reference types="@cloudflare/workers-types" />
2
2
  import type { NotFoundHandler } from './hono';
3
- import type { HonoRequest } from './request';
4
3
  import type { StatusCode } from './utils/http-status';
5
4
  declare type Headers = Record<string, string>;
6
5
  export declare type Data = string | ArrayBuffer | ReadableStream;
7
6
  export declare type Env = Record<string, any>;
8
7
  export declare class Context<RequestParamKeyType extends string = string, E = Env> {
9
- req: HonoRequest<RequestParamKeyType>;
8
+ req: Request<RequestParamKeyType>;
10
9
  env: E;
11
10
  event: FetchEvent | undefined;
12
11
  executionCtx: ExecutionContext | undefined;
@@ -18,8 +17,7 @@ export declare class Context<RequestParamKeyType extends string = string, E = En
18
17
  private _headers;
19
18
  private _res;
20
19
  private notFoundHandler;
21
- render: (content: string, params?: object, options?: object) => Response | Promise<Response>;
22
- constructor(req: HonoRequest | Request, env?: E | undefined, eventOrExecutionCtx?: FetchEvent | ExecutionContext | undefined, notFoundHandler?: NotFoundHandler);
20
+ constructor(req: Request, env?: E | undefined, eventOrExecutionCtx?: FetchEvent | ExecutionContext | undefined, notFoundHandler?: NotFoundHandler);
23
21
  get res(): Response;
24
22
  set res(_res: Response);
25
23
  header(name: string, value: string): void;
@@ -30,7 +28,7 @@ export declare class Context<RequestParamKeyType extends string = string, E = En
30
28
  newResponse(data: Data | null, status: StatusCode, headers?: Headers): Response;
31
29
  body(data: Data | null, status?: StatusCode, headers?: Headers): Response;
32
30
  text(text: string, status?: StatusCode, headers?: Headers): Response;
33
- json(object: object, status?: StatusCode, headers?: Headers): Response;
31
+ json<T>(object: T, status?: StatusCode, headers?: Headers): Response;
34
32
  html(html: string, status?: StatusCode, headers?: Headers): Response;
35
33
  redirect(location: string, status?: StatusCode): Response;
36
34
  notFound(): Response | Promise<Response>;
package/dist/context.js CHANGED
@@ -1,26 +1,20 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Context = void 0;
4
- const request_1 = require("./request");
5
4
  const url_1 = require("./utils/url");
6
5
  class Context {
7
6
  constructor(req, env = undefined, eventOrExecutionCtx = undefined, notFoundHandler = () => new Response()) {
8
7
  this._status = 200;
9
8
  this._pretty = false;
10
9
  this._prettySpace = 2;
11
- if (req instanceof Request) {
12
- this.req = (0, request_1.extendHonoRequest)(req);
13
- }
14
- else {
15
- this.req = req;
16
- }
17
- if (env) {
18
- this.env = env;
19
- }
20
- this.executionCtx = eventOrExecutionCtx;
10
+ this.req = req;
11
+ this.env = env ? env : {};
21
12
  if (eventOrExecutionCtx && 'respondWith' in eventOrExecutionCtx) {
22
13
  this.event = eventOrExecutionCtx;
23
14
  }
15
+ else {
16
+ this.executionCtx = eventOrExecutionCtx;
17
+ }
24
18
  this.notFoundHandler = notFoundHandler;
25
19
  this.finalized = false;
26
20
  }
package/dist/hono.d.ts CHANGED
@@ -50,6 +50,5 @@ export declare class Hono<E extends Env = Env, P extends string = '/'> extends H
50
50
  handleEvent(event: FetchEvent): Promise<Response>;
51
51
  fetch(request: Request, env?: E, executionCtx?: ExecutionContext): Promise<Response>;
52
52
  request(input: RequestInfo, requestInit?: RequestInit): Promise<Response>;
53
- fire(): void;
54
53
  }
55
54
  export {};
package/dist/hono.js CHANGED
@@ -29,6 +29,7 @@ class Hono extends defineDynamicClass() {
29
29
  const message = 'Internal Server Error';
30
30
  return c.text(message, 500);
31
31
  };
32
+ (0, request_1.extendRequestPrototype)();
32
33
  const allMethods = [...methods, router_1.METHOD_NAME_ALL_LOWERCASE];
33
34
  allMethods.map((method) => {
34
35
  this[method] = (args1, ...args) => {
@@ -90,14 +91,13 @@ class Hono extends defineDynamicClass() {
90
91
  matchRoute(method, path) {
91
92
  return this.router.match(method, path);
92
93
  }
93
- async dispatch(request, executionCtx, env) {
94
- request = (0, request_1.extendHonoRequest)(request);
94
+ async dispatch(request, eventOrExecutionCtx, env) {
95
95
  const path = (0, url_1.getPathFromURL)(request.url, this.strict);
96
96
  const method = request.method;
97
97
  const result = this.matchRoute(method, path);
98
98
  request.paramData = result?.params;
99
99
  const handlers = result ? result.handlers : [this.notFoundHandler];
100
- const c = new context_1.Context(request, env, executionCtx, this.notFoundHandler);
100
+ const c = new context_1.Context(request, env, eventOrExecutionCtx, this.notFoundHandler);
101
101
  const composed = (0, compose_1.compose)(handlers, this.errorHandler, this.notFoundHandler);
102
102
  let context;
103
103
  try {
@@ -124,10 +124,5 @@ class Hono extends defineDynamicClass() {
124
124
  const req = input instanceof Request ? input : new Request(input, requestInit);
125
125
  return this.dispatch(req);
126
126
  }
127
- fire() {
128
- addEventListener('fetch', (event) => {
129
- event.respondWith(this.handleEvent(event));
130
- });
131
- }
132
127
  }
133
128
  exports.Hono = Hono;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,11 @@
1
1
  /// <reference path="request.d.ts" />
2
- export { Hono } from './hono';
2
+ import { Hono } from './hono';
3
3
  export type { Handler, Next } from './hono';
4
4
  export { Context } from './context';
5
5
  export type { Env } from './context';
6
+ declare module './hono' {
7
+ interface Hono {
8
+ fire(): void;
9
+ }
10
+ }
11
+ export { Hono };
package/dist/index.js CHANGED
@@ -2,8 +2,13 @@
2
2
  // eslint-disable-next-line @typescript-eslint/triple-slash-reference
3
3
  /// <reference path="./request.ts" /> Import "declare global" for the Request interface.
4
4
  Object.defineProperty(exports, "__esModule", { value: true });
5
- exports.Context = exports.Hono = void 0;
6
- var hono_1 = require("./hono");
5
+ exports.Hono = exports.Context = void 0;
6
+ const hono_1 = require("./hono");
7
7
  Object.defineProperty(exports, "Hono", { enumerable: true, get: function () { return hono_1.Hono; } });
8
8
  var context_1 = require("./context");
9
9
  Object.defineProperty(exports, "Context", { enumerable: true, get: function () { return context_1.Context; } });
10
+ hono_1.Hono.prototype.fire = function () {
11
+ addEventListener('fetch', (event) => {
12
+ void event.respondWith(this.handleEvent(event));
13
+ });
14
+ };
@@ -1,7 +1,7 @@
1
1
  import type { Context } from '../../context';
2
2
  import type { Next } from '../../hono';
3
- declare module '../../request' {
4
- interface HonoRequest {
3
+ declare global {
4
+ interface Request {
5
5
  parsedBody: any;
6
6
  }
7
7
  }
@@ -1,7 +1,7 @@
1
1
  import type { Context } from '../../context';
2
2
  import type { Next } from '../../hono';
3
- declare module '../../request' {
4
- interface HonoRequest {
3
+ declare global {
4
+ interface Request {
5
5
  cookie: {
6
6
  (name: string): string;
7
7
  (): Record<string, string>;
@@ -1,6 +1,11 @@
1
1
  /// <reference types="@cloudflare/workers-types" />
2
2
  import type { Context } from '../../context';
3
3
  import type { Next } from '../../hono';
4
+ declare module '../../context' {
5
+ interface Context {
6
+ render: (content: string, params?: object, options?: object) => Response | Promise<Response>;
7
+ }
8
+ }
4
9
  export declare type MustacheOptions = {
5
10
  root: string;
6
11
  manifest?: object | string;
@@ -7,13 +7,14 @@ exports.mustache = void 0;
7
7
  const mustache_1 = __importDefault(require("mustache"));
8
8
  const buffer_1 = require("../../utils/buffer");
9
9
  const cloudflare_1 = require("../../utils/cloudflare");
10
+ const filepath_1 = require("../../utils/filepath");
10
11
  const EXTENSION = '.mustache';
11
12
  const DEFAULT_DOCUMENT = 'index.mustache';
12
13
  const mustache = (init = { root: '' }) => {
13
14
  const { root } = init;
14
15
  return async (c, next) => {
15
16
  c.render = async (filename, params = {}, options) => {
16
- const path = (0, cloudflare_1.getKVFilePath)({
17
+ const path = (0, filepath_1.getFilePath)({
17
18
  filename: `${filename}${EXTENSION}`,
18
19
  root: root,
19
20
  defaultDocument: DEFAULT_DOCUMENT,
@@ -31,7 +32,7 @@ const mustache = (init = { root: '' }) => {
31
32
  if (options) {
32
33
  const partials = options;
33
34
  for (const key of Object.keys(partials)) {
34
- const partialPath = (0, cloudflare_1.getKVFilePath)({
35
+ const partialPath = (0, filepath_1.getFilePath)({
35
36
  filename: `${partials[key]}${EXTENSION}`,
36
37
  root: root,
37
38
  defaultDocument: DEFAULT_DOCUMENT,
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.serveStatic = void 0;
4
4
  const cloudflare_1 = require("../../utils/cloudflare");
5
+ const filepath_1 = require("../../utils/filepath");
5
6
  const mime_1 = require("../../utils/mime");
6
7
  const DEFAULT_DOCUMENT = 'index.html';
7
8
  // This middleware is available only on Cloudflare Workers.
@@ -12,7 +13,7 @@ const serveStatic = (options = { root: '' }) => {
12
13
  await next();
13
14
  }
14
15
  const url = new URL(c.req.url);
15
- const path = (0, cloudflare_1.getKVFilePath)({
16
+ const path = (0, filepath_1.getFilePath)({
16
17
  filename: options.path ?? url.pathname,
17
18
  root: options.root,
18
19
  defaultDocument: DEFAULT_DOCUMENT,
package/dist/request.d.ts CHANGED
@@ -1,20 +1,22 @@
1
- export declare class HonoRequest<ParamKeyType extends string = string> extends Request {
2
- param: {
3
- (key: ParamKeyType): string;
4
- (): Record<ParamKeyType, string>;
5
- };
6
- paramData?: Record<ParamKeyType, string>;
7
- query: {
8
- (key: string): string;
9
- (): Record<string, string>;
10
- };
11
- queries: {
12
- (key: string): string[];
13
- (): Record<string, string[]>;
14
- };
15
- header: {
16
- (name: string): string;
17
- (): Record<string, string>;
18
- };
1
+ declare global {
2
+ interface Request<ParamKeyType extends string = string> {
3
+ param: {
4
+ (key: ParamKeyType): string;
5
+ (): Record<ParamKeyType, string>;
6
+ };
7
+ paramData?: Record<ParamKeyType, string>;
8
+ query: {
9
+ (key: string): string;
10
+ (): Record<string, string>;
11
+ };
12
+ queries: {
13
+ (key: string): string[];
14
+ (): Record<string, string[]>;
15
+ };
16
+ header: {
17
+ (name: string): string;
18
+ (): Record<string, string>;
19
+ };
20
+ }
19
21
  }
20
- export declare function extendHonoRequest(request: HonoRequest): HonoRequest;
22
+ export declare function extendRequestPrototype(): void;
package/dist/request.js CHANGED
@@ -1,11 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.extendHonoRequest = exports.HonoRequest = void 0;
4
- class HonoRequest extends Request {
5
- }
6
- exports.HonoRequest = HonoRequest;
7
- function extendHonoRequest(request) {
8
- request.param = function (key) {
3
+ exports.extendRequestPrototype = void 0;
4
+ function extendRequestPrototype() {
5
+ if (!!Request.prototype.param) {
6
+ // already extended
7
+ return;
8
+ }
9
+ Request.prototype.param = function (key) {
9
10
  if (this.paramData) {
10
11
  if (key) {
11
12
  return this.paramData[key];
@@ -16,7 +17,7 @@ function extendHonoRequest(request) {
16
17
  }
17
18
  return null;
18
19
  };
19
- request.header = function (name) {
20
+ Request.prototype.header = function (name) {
20
21
  if (name) {
21
22
  return this.headers.get(name);
22
23
  }
@@ -28,7 +29,7 @@ function extendHonoRequest(request) {
28
29
  return result;
29
30
  }
30
31
  };
31
- request.query = function (key) {
32
+ Request.prototype.query = function (key) {
32
33
  const url = new URL(this.url);
33
34
  if (key) {
34
35
  return url.searchParams.get(key);
@@ -41,7 +42,7 @@ function extendHonoRequest(request) {
41
42
  return result;
42
43
  }
43
44
  };
44
- request.queries = function (key) {
45
+ Request.prototype.queries = function (key) {
45
46
  const url = new URL(this.url);
46
47
  if (key) {
47
48
  return url.searchParams.getAll(key);
@@ -54,6 +55,5 @@ function extendHonoRequest(request) {
54
55
  return result;
55
56
  }
56
57
  };
57
- return request;
58
58
  }
59
- exports.extendHonoRequest = extendHonoRequest;
59
+ exports.extendRequestPrototype = extendRequestPrototype;
@@ -22,6 +22,7 @@ class Node {
22
22
  this.order = 0;
23
23
  this.children = children || {};
24
24
  this.methods = [];
25
+ this.name = '';
25
26
  if (method && handler) {
26
27
  const m = {};
27
28
  m[method] = { handler: handler, score: 0, name: this.name };
@@ -4,10 +4,3 @@ export declare type KVAssetOptions = {
4
4
  namespace?: KVNamespace;
5
5
  };
6
6
  export declare const getContentFromKVAsset: (path: string, options?: KVAssetOptions | undefined) => Promise<ArrayBuffer | null>;
7
- declare type FilePathOptions = {
8
- filename: string;
9
- root?: string;
10
- defaultDocument?: string;
11
- };
12
- export declare const getKVFilePath: (options: FilePathOptions) => string;
13
- export {};
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getKVFilePath = exports.getContentFromKVAsset = void 0;
3
+ exports.getContentFromKVAsset = void 0;
4
4
  const getContentFromKVAsset = async (path, options) => {
5
5
  let ASSET_MANIFEST = {};
6
6
  if (options && options.manifest) {
@@ -37,25 +37,3 @@ const getContentFromKVAsset = async (path, options) => {
37
37
  return content;
38
38
  };
39
39
  exports.getContentFromKVAsset = getContentFromKVAsset;
40
- const getKVFilePath = (options) => {
41
- let filename = options.filename;
42
- let root = options.root || '';
43
- const defaultDocument = options.defaultDocument || 'index.html';
44
- if (filename.endsWith('/')) {
45
- // /top/ => /top/index.html
46
- filename = filename.concat(defaultDocument);
47
- }
48
- else if (!filename.match(/\.[a-zA-Z0-9]+$/)) {
49
- // /top => /top/index.html
50
- filename = filename.concat('/' + defaultDocument);
51
- }
52
- // /foo.html => foo.html
53
- filename = filename.replace(/^\.?\//, '');
54
- // assets/ => assets
55
- root = root.replace(/\/$/, '');
56
- // ./assets/foo.html => assets/foo.html
57
- let path = root ? root + '/' + filename : filename;
58
- path = path.replace(/^\.?\//, '');
59
- return path;
60
- };
61
- exports.getKVFilePath = getKVFilePath;
@@ -0,0 +1,7 @@
1
+ declare type FilePathOptions = {
2
+ filename: string;
3
+ root?: string;
4
+ defaultDocument?: string;
5
+ };
6
+ export declare const getFilePath: (options: FilePathOptions) => string;
7
+ export {};
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getFilePath = void 0;
4
+ const getFilePath = (options) => {
5
+ let filename = options.filename;
6
+ let root = options.root || '';
7
+ const defaultDocument = options.defaultDocument || 'index.html';
8
+ if (filename.endsWith('/')) {
9
+ // /top/ => /top/index.html
10
+ filename = filename.concat(defaultDocument);
11
+ }
12
+ else if (!filename.match(/\.[a-zA-Z0-9]+$/)) {
13
+ // /top => /top/index.html
14
+ filename = filename.concat('/' + defaultDocument);
15
+ }
16
+ // /foo.html => foo.html
17
+ filename = filename.replace(/^\.?\//, '');
18
+ // assets/ => assets
19
+ root = root.replace(/\/$/, '');
20
+ // ./assets/foo.html => assets/foo.html
21
+ let path = root ? root + '/' + filename : filename;
22
+ path = path.replace(/^\.?\//, '');
23
+ return path;
24
+ };
25
+ exports.getFilePath = getFilePath;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hono",
3
- "version": "1.5.2",
3
+ "version": "1.6.1",
4
4
  "description": "Ultrafast web framework for Cloudflare Workers.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -11,9 +11,11 @@
11
11
  "test": "jest",
12
12
  "lint": "eslint --ext js,ts src .eslintrc.js",
13
13
  "lint:fix": "eslint --ext js,ts src .eslintrc.js --fix",
14
+ "denoify": "rimraf deno_dist && denoify && rimraf 'deno_dist/**/*.test.ts'",
14
15
  "build": "rimraf dist && tsc --project tsconfig.build.esm.json && tsc --project tsconfig.build.json",
15
16
  "watch": "tsc --project tsconfig.build.json -w",
16
- "prepublishOnly": "yarn build"
17
+ "prerelease": "yarn denoify && yarn build",
18
+ "release": "np"
17
19
  },
18
20
  "exports": {
19
21
  ".": "./dist/index.js",
@@ -112,6 +114,9 @@
112
114
  "type": "git",
113
115
  "url": "https://github.com/honojs/hono.git"
114
116
  },
117
+ "publishConfig": {
118
+ "registry": "https://registry.npmjs.org"
119
+ },
115
120
  "homepage": "https://github.com/honojs/hono",
116
121
  "keywords": [
117
122
  "web",
@@ -134,6 +139,7 @@
134
139
  "@typescript-eslint/eslint-plugin": "^5.21.0",
135
140
  "@typescript-eslint/parser": "^5.21.0",
136
141
  "crypto-js": "^4.1.1",
142
+ "denoify": "^0.11.1",
137
143
  "eslint": "^8.14.0",
138
144
  "eslint-config-prettier": "^8.5.0",
139
145
  "eslint-define-config": "^1.4.0",
@@ -147,6 +153,7 @@
147
153
  "jest": "27.5.1",
148
154
  "jest-environment-miniflare": "^2.5.1",
149
155
  "mustache": "^4.2.0",
156
+ "np": "^7.6.2",
150
157
  "prettier": "^2.6.2",
151
158
  "prettier-plugin-md-nocjsp": "^1.2.0",
152
159
  "rimraf": "^3.0.2",
@@ -156,4 +163,4 @@
156
163
  "engines": {
157
164
  "node": ">=11.0.0"
158
165
  }
159
- }
166
+ }