@react-router/architect 0.0.0-nightly-a5f191b5e-20240820
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/LICENSE.md +23 -0
- package/README.md +7 -0
- package/dist/__tests__/binaryTypes-test.d.ts +1 -0
- package/dist/__tests__/server-test.d.ts +1 -0
- package/dist/__tests__/setup.d.ts +1 -0
- package/dist/binaryTypes.d.ts +1 -0
- package/dist/binaryTypes.js +38 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +21 -0
- package/dist/server.d.ts +23 -0
- package/dist/server.js +97 -0
- package/dist/sessions/arcTableSessionStorage.d.ts +36 -0
- package/dist/sessions/arcTableSessionStorage.js +102 -0
- package/package.json +59 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) React Training LLC 2015-2019
|
|
4
|
+
Copyright (c) Remix Software Inc. 2020-2021
|
|
5
|
+
Copyright (c) Shopify Inc. 2022-2023
|
|
6
|
+
|
|
7
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
in the Software without restriction, including without limitation the rights
|
|
10
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
furnished to do so, subject to the following conditions:
|
|
13
|
+
|
|
14
|
+
The above copyright notice and this permission notice shall be included in all
|
|
15
|
+
copies or substantial portions of the Software.
|
|
16
|
+
|
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
23
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function isBinaryType(contentType: string | null | undefined): boolean;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @react-router/architect v0.0.0-nightly-a5f191b5e-20240820
|
|
3
|
+
*
|
|
4
|
+
* Copyright (c) Remix Software Inc.
|
|
5
|
+
*
|
|
6
|
+
* This source code is licensed under the MIT license found in the
|
|
7
|
+
* LICENSE.md file in the root directory of this source tree.
|
|
8
|
+
*
|
|
9
|
+
* @license MIT
|
|
10
|
+
*/
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Common binary MIME types
|
|
17
|
+
* @see https://github.com/architect/functions/blob/45254fc1936a1794c185aac07e9889b241a2e5c6/src/http/helpers/binary-types.js
|
|
18
|
+
*/
|
|
19
|
+
const binaryTypes = ["application/octet-stream",
|
|
20
|
+
// Docs
|
|
21
|
+
"application/epub+zip", "application/msword", "application/pdf", "application/rtf", "application/vnd.amazon.ebook", "application/vnd.ms-excel", "application/vnd.ms-powerpoint", "application/vnd.openxmlformats-officedocument.presentationml.presentation", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
22
|
+
// Fonts
|
|
23
|
+
"font/otf", "font/woff", "font/woff2",
|
|
24
|
+
// Images
|
|
25
|
+
"image/avif", "image/bmp", "image/gif", "image/jpeg", "image/png", "image/tiff", "image/vnd.microsoft.icon", "image/webp",
|
|
26
|
+
// Audio
|
|
27
|
+
"audio/3gpp", "audio/aac", "audio/basic", "audio/mpeg", "audio/ogg", "audio/wav", "audio/webm", "audio/x-aiff", "audio/x-midi", "audio/x-wav",
|
|
28
|
+
// Video
|
|
29
|
+
"video/3gpp", "video/mp2t", "video/mpeg", "video/ogg", "video/quicktime", "video/webm", "video/x-msvideo",
|
|
30
|
+
// Archives
|
|
31
|
+
"application/java-archive", "application/vnd.apple.installer+xml", "application/x-7z-compressed", "application/x-apple-diskimage", "application/x-bzip", "application/x-bzip2", "application/x-gzip", "application/x-java-archive", "application/x-rar-compressed", "application/x-tar", "application/x-zip", "application/zip"];
|
|
32
|
+
function isBinaryType(contentType) {
|
|
33
|
+
if (!contentType) return false;
|
|
34
|
+
let [test] = contentType.split(";");
|
|
35
|
+
return binaryTypes.includes(test);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
exports.isBinaryType = isBinaryType;
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @react-router/architect v0.0.0-nightly-a5f191b5e-20240820
|
|
3
|
+
*
|
|
4
|
+
* Copyright (c) Remix Software Inc.
|
|
5
|
+
*
|
|
6
|
+
* This source code is licensed under the MIT license found in the
|
|
7
|
+
* LICENSE.md file in the root directory of this source tree.
|
|
8
|
+
*
|
|
9
|
+
* @license MIT
|
|
10
|
+
*/
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
14
|
+
|
|
15
|
+
var arcTableSessionStorage = require('./sessions/arcTableSessionStorage.js');
|
|
16
|
+
var server = require('./server.js');
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
exports.createArcTableSessionStorage = arcTableSessionStorage.createArcTableSessionStorage;
|
|
21
|
+
exports.createRequestHandler = server.createRequestHandler;
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { AppLoadContext, ServerBuild } from "react-router";
|
|
2
|
+
import type { APIGatewayProxyEventHeaders, APIGatewayProxyEventV2, APIGatewayProxyHandlerV2, APIGatewayProxyStructuredResultV2 } from "aws-lambda";
|
|
3
|
+
/**
|
|
4
|
+
* A function that returns the value to use as `context` in route `loader` and
|
|
5
|
+
* `action` functions.
|
|
6
|
+
*
|
|
7
|
+
* You can think of this as an escape hatch that allows you to pass
|
|
8
|
+
* environment/platform-specific values through to your loader/action.
|
|
9
|
+
*/
|
|
10
|
+
export type GetLoadContextFunction = (event: APIGatewayProxyEventV2) => Promise<AppLoadContext> | AppLoadContext;
|
|
11
|
+
export type RequestHandler = APIGatewayProxyHandlerV2;
|
|
12
|
+
/**
|
|
13
|
+
* Returns a request handler for Architect that serves the response using
|
|
14
|
+
* React Router.
|
|
15
|
+
*/
|
|
16
|
+
export declare function createRequestHandler({ build, getLoadContext, mode, }: {
|
|
17
|
+
build: ServerBuild;
|
|
18
|
+
getLoadContext?: GetLoadContextFunction;
|
|
19
|
+
mode?: string;
|
|
20
|
+
}): RequestHandler;
|
|
21
|
+
export declare function createReactRouterRequest(event: APIGatewayProxyEventV2): Request;
|
|
22
|
+
export declare function createReactRouterHeaders(requestHeaders: APIGatewayProxyEventHeaders, requestCookies?: string[]): Headers;
|
|
23
|
+
export declare function sendReactRouterResponse(nodeResponse: Response): Promise<APIGatewayProxyStructuredResultV2>;
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @react-router/architect v0.0.0-nightly-a5f191b5e-20240820
|
|
3
|
+
*
|
|
4
|
+
* Copyright (c) Remix Software Inc.
|
|
5
|
+
*
|
|
6
|
+
* This source code is licensed under the MIT license found in the
|
|
7
|
+
* LICENSE.md file in the root directory of this source tree.
|
|
8
|
+
*
|
|
9
|
+
* @license MIT
|
|
10
|
+
*/
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
14
|
+
|
|
15
|
+
var reactRouter = require('react-router');
|
|
16
|
+
var node = require('@react-router/node');
|
|
17
|
+
var binaryTypes = require('./binaryTypes.js');
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Returns a request handler for Architect that serves the response using
|
|
21
|
+
* React Router.
|
|
22
|
+
*/
|
|
23
|
+
function createRequestHandler({
|
|
24
|
+
build,
|
|
25
|
+
getLoadContext,
|
|
26
|
+
mode = process.env.NODE_ENV
|
|
27
|
+
}) {
|
|
28
|
+
let handleRequest = reactRouter.createRequestHandler(build, mode);
|
|
29
|
+
return async event => {
|
|
30
|
+
let request = createReactRouterRequest(event);
|
|
31
|
+
let loadContext = await getLoadContext?.(event);
|
|
32
|
+
let response = await handleRequest(request, loadContext);
|
|
33
|
+
return sendReactRouterResponse(response);
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function createReactRouterRequest(event) {
|
|
37
|
+
let host = event.headers["x-forwarded-host"] || event.headers.host;
|
|
38
|
+
let search = event.rawQueryString.length ? `?${event.rawQueryString}` : "";
|
|
39
|
+
let scheme = process.env.ARC_SANDBOX ? "http" : "https";
|
|
40
|
+
let url = new URL(`${scheme}://${host}${event.rawPath}${search}`);
|
|
41
|
+
let isFormData = event.headers["content-type"]?.includes("multipart/form-data");
|
|
42
|
+
// Note: No current way to abort these for Architect, but our router expects
|
|
43
|
+
// requests to contain a signal, so it can detect aborted requests
|
|
44
|
+
let controller = new AbortController();
|
|
45
|
+
return new Request(url.href, {
|
|
46
|
+
method: event.requestContext.http.method,
|
|
47
|
+
headers: createReactRouterHeaders(event.headers, event.cookies),
|
|
48
|
+
signal: controller.signal,
|
|
49
|
+
body: event.body && event.isBase64Encoded ? isFormData ? Buffer.from(event.body, "base64") : Buffer.from(event.body, "base64").toString() : event.body
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
function createReactRouterHeaders(requestHeaders, requestCookies) {
|
|
53
|
+
let headers = new Headers();
|
|
54
|
+
for (let [header, value] of Object.entries(requestHeaders)) {
|
|
55
|
+
if (value) {
|
|
56
|
+
headers.append(header, value);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (requestCookies) {
|
|
60
|
+
headers.append("Cookie", requestCookies.join("; "));
|
|
61
|
+
}
|
|
62
|
+
return headers;
|
|
63
|
+
}
|
|
64
|
+
async function sendReactRouterResponse(nodeResponse) {
|
|
65
|
+
let cookies = [];
|
|
66
|
+
// Arc/AWS API Gateway will send back set-cookies outside of response headers.
|
|
67
|
+
for (let [key, value] of nodeResponse.headers.entries()) {
|
|
68
|
+
if (key.toLowerCase() === "set-cookie") {
|
|
69
|
+
cookies.push(value);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (cookies.length) {
|
|
73
|
+
nodeResponse.headers.delete("Set-Cookie");
|
|
74
|
+
}
|
|
75
|
+
let contentType = nodeResponse.headers.get("Content-Type");
|
|
76
|
+
let isBase64Encoded = binaryTypes.isBinaryType(contentType);
|
|
77
|
+
let body;
|
|
78
|
+
if (nodeResponse.body) {
|
|
79
|
+
if (isBase64Encoded) {
|
|
80
|
+
body = await node.readableStreamToString(nodeResponse.body, "base64");
|
|
81
|
+
} else {
|
|
82
|
+
body = await nodeResponse.text();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
statusCode: nodeResponse.status,
|
|
87
|
+
headers: Object.fromEntries(nodeResponse.headers.entries()),
|
|
88
|
+
cookies,
|
|
89
|
+
body,
|
|
90
|
+
isBase64Encoded
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
exports.createReactRouterHeaders = createReactRouterHeaders;
|
|
95
|
+
exports.createReactRouterRequest = createReactRouterRequest;
|
|
96
|
+
exports.createRequestHandler = createRequestHandler;
|
|
97
|
+
exports.sendReactRouterResponse = sendReactRouterResponse;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { SessionData, SessionStorage, SessionIdStorageStrategy } from "react-router";
|
|
2
|
+
import type { ArcTable } from "@architect/functions/types/tables";
|
|
3
|
+
interface ArcTableSessionStorageOptions {
|
|
4
|
+
/**
|
|
5
|
+
* The Cookie used to store the session id on the client, or options used
|
|
6
|
+
* to automatically create one.
|
|
7
|
+
*/
|
|
8
|
+
cookie?: SessionIdStorageStrategy["cookie"];
|
|
9
|
+
/**
|
|
10
|
+
* The table used to store sessions, or its name as it appears in your
|
|
11
|
+
* project's app.arc file.
|
|
12
|
+
*/
|
|
13
|
+
table: ArcTable<SessionData> | string;
|
|
14
|
+
/**
|
|
15
|
+
* The name of the DynamoDB attribute used to store the session ID.
|
|
16
|
+
* This should be the table's partition key.
|
|
17
|
+
*/
|
|
18
|
+
idx: string;
|
|
19
|
+
/**
|
|
20
|
+
* The name of the DynamoDB attribute used to store the expiration time.
|
|
21
|
+
* If absent, then no TTL will be stored and session records will not expire.
|
|
22
|
+
*/
|
|
23
|
+
ttl?: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Session storage using a DynamoDB table managed by Architect.
|
|
27
|
+
*
|
|
28
|
+
* Add the following lines to your project's `app.arc` file:
|
|
29
|
+
*
|
|
30
|
+
* @tables
|
|
31
|
+
* arc-sessions
|
|
32
|
+
* _idx *String
|
|
33
|
+
* _ttl TTL
|
|
34
|
+
*/
|
|
35
|
+
export declare function createArcTableSessionStorage<Data = SessionData, FlashData = Data>({ cookie, ...props }: ArcTableSessionStorageOptions): SessionStorage<Data, FlashData>;
|
|
36
|
+
export {};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @react-router/architect v0.0.0-nightly-a5f191b5e-20240820
|
|
3
|
+
*
|
|
4
|
+
* Copyright (c) Remix Software Inc.
|
|
5
|
+
*
|
|
6
|
+
* This source code is licensed under the MIT license found in the
|
|
7
|
+
* LICENSE.md file in the root directory of this source tree.
|
|
8
|
+
*
|
|
9
|
+
* @license MIT
|
|
10
|
+
*/
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
14
|
+
|
|
15
|
+
var reactRouter = require('react-router');
|
|
16
|
+
var arc = require('@architect/functions');
|
|
17
|
+
|
|
18
|
+
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
19
|
+
|
|
20
|
+
var arc__default = /*#__PURE__*/_interopDefaultLegacy(arc);
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Session storage using a DynamoDB table managed by Architect.
|
|
24
|
+
*
|
|
25
|
+
* Add the following lines to your project's `app.arc` file:
|
|
26
|
+
*
|
|
27
|
+
* @tables
|
|
28
|
+
* arc-sessions
|
|
29
|
+
* _idx *String
|
|
30
|
+
* _ttl TTL
|
|
31
|
+
*/
|
|
32
|
+
function createArcTableSessionStorage({
|
|
33
|
+
cookie,
|
|
34
|
+
...props
|
|
35
|
+
}) {
|
|
36
|
+
async function getTable() {
|
|
37
|
+
if (typeof props.table === "string") {
|
|
38
|
+
let tables = await arc__default["default"].tables();
|
|
39
|
+
return tables[props.table];
|
|
40
|
+
} else {
|
|
41
|
+
return props.table;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return reactRouter.createSessionStorage({
|
|
45
|
+
cookie,
|
|
46
|
+
async createData(data, expires) {
|
|
47
|
+
let table = await getTable();
|
|
48
|
+
while (true) {
|
|
49
|
+
let randomBytes = crypto.getRandomValues(new Uint8Array(8));
|
|
50
|
+
// This storage manages an id space of 2^64 ids, which is far greater
|
|
51
|
+
// than the maximum number of files allowed on an NTFS or ext4 volume
|
|
52
|
+
// (2^32). However, the larger id space should help to avoid collisions
|
|
53
|
+
// with existing ids when creating new sessions, which speeds things up.
|
|
54
|
+
let id = [...randomBytes].map(x => x.toString(16).padStart(2, "0")).join("");
|
|
55
|
+
if (await table.get({
|
|
56
|
+
[props.idx]: id
|
|
57
|
+
})) {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
let params = {
|
|
61
|
+
[props.idx]: id,
|
|
62
|
+
...data
|
|
63
|
+
};
|
|
64
|
+
if (props.ttl) {
|
|
65
|
+
params[props.ttl] = expires ? Math.round(expires.getTime() / 1000) : undefined;
|
|
66
|
+
}
|
|
67
|
+
await table.put(params);
|
|
68
|
+
return id;
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
async readData(id) {
|
|
72
|
+
let table = await getTable();
|
|
73
|
+
let data = await table.get({
|
|
74
|
+
[props.idx]: id
|
|
75
|
+
});
|
|
76
|
+
if (data) {
|
|
77
|
+
delete data[props.idx];
|
|
78
|
+
if (props.ttl) delete data[props.ttl];
|
|
79
|
+
}
|
|
80
|
+
return data;
|
|
81
|
+
},
|
|
82
|
+
async updateData(id, data, expires) {
|
|
83
|
+
let table = await getTable();
|
|
84
|
+
let params = {
|
|
85
|
+
[props.idx]: id,
|
|
86
|
+
...data
|
|
87
|
+
};
|
|
88
|
+
if (props.ttl) {
|
|
89
|
+
params[props.ttl] = expires ? Math.round(expires.getTime() / 1000) : undefined;
|
|
90
|
+
}
|
|
91
|
+
await table.put(params);
|
|
92
|
+
},
|
|
93
|
+
async deleteData(id) {
|
|
94
|
+
let table = await getTable();
|
|
95
|
+
await table.delete({
|
|
96
|
+
[props.idx]: id
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
exports.createArcTableSessionStorage = createArcTableSessionStorage;
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@react-router/architect",
|
|
3
|
+
"version": "0.0.0-nightly-a5f191b5e-20240820",
|
|
4
|
+
"description": "Architect server request handler for React Router",
|
|
5
|
+
"bugs": {
|
|
6
|
+
"url": "https://github.com/remix-run/react-router/issues"
|
|
7
|
+
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/remix-run/react-router",
|
|
11
|
+
"directory": "packages/react-router-architect"
|
|
12
|
+
},
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"main": "dist/index.js",
|
|
15
|
+
"typings": "dist/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"default": "./dist/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./package.json": "./package.json"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@architect/functions": "^5.2.0",
|
|
25
|
+
"@types/aws-lambda": "^8.10.82"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/lambda-tester": "^3.6.1",
|
|
29
|
+
"@types/node": "^18.17.1",
|
|
30
|
+
"lambda-tester": "^4.0.1",
|
|
31
|
+
"react": "^18.2.0",
|
|
32
|
+
"react-dom": "^18.2.0",
|
|
33
|
+
"typescript": "^5.1.0",
|
|
34
|
+
"@react-router/node": "0.0.0-nightly-a5f191b5e-20240820",
|
|
35
|
+
"react-router": "0.0.0-nightly-a5f191b5e-20240820"
|
|
36
|
+
},
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"typescript": "^5.1.0",
|
|
39
|
+
"@react-router/node": "^0.0.0-nightly-a5f191b5e-20240820",
|
|
40
|
+
"react-router": "^0.0.0-nightly-a5f191b5e-20240820"
|
|
41
|
+
},
|
|
42
|
+
"peerDependenciesMeta": {
|
|
43
|
+
"typescript": {
|
|
44
|
+
"optional": true
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"engines": {
|
|
48
|
+
"node": ">=18.0.0"
|
|
49
|
+
},
|
|
50
|
+
"files": [
|
|
51
|
+
"dist/",
|
|
52
|
+
"CHANGELOG.md",
|
|
53
|
+
"LICENSE.md",
|
|
54
|
+
"README.md"
|
|
55
|
+
],
|
|
56
|
+
"scripts": {
|
|
57
|
+
"tsc": "tsc"
|
|
58
|
+
}
|
|
59
|
+
}
|