@awesomeness-js/server 1.0.0
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/.editorconfig +4 -0
- package/.gitattributes +2 -0
- package/.jshintrc +3 -0
- package/.vscode/settings.json +17 -0
- package/CLA.md +9 -0
- package/CONTRIBUTING.md +18 -0
- package/LICENSE +13 -0
- package/NOTICE +15 -0
- package/README.md +15 -0
- package/SECURITY.md +7 -0
- package/config.js +29 -0
- package/eslint.config.js +101 -0
- package/example-.awesomeness/applicationMap.js +4 -0
- package/example-.awesomeness/beforeRouteMiddleware.js +15 -0
- package/example-.awesomeness/checkSession.js +15 -0
- package/example-.awesomeness/config.js +40 -0
- package/example-.awesomeness/hostMap.js +44 -0
- package/example-.awesomeness/initDB.js +65 -0
- package/example-.awesomeness/setupDB/applications.js +47 -0
- package/example-.awesomeness/setupDB/users.js +65 -0
- package/example-.awesomeness/setupDB/websites.js +49 -0
- package/example-.awesomeness/specialRoutes.js +7 -0
- package/example-.awesomeness/wsHandler.js +13 -0
- package/index.js +22 -0
- package/package.json +34 -0
- package/server/applicationMap.js +33 -0
- package/server/awesomenessNormalizeRequest.js +131 -0
- package/server/brotliJsonResponse.js +28 -0
- package/server/checkAccess.js +34 -0
- package/server/componentDependencies.js +301 -0
- package/server/errors.js +11 -0
- package/server/fetchPage.js +269 -0
- package/server/getMD.js +22 -0
- package/server/koa/attachAwesomenessRequest.js +24 -0
- package/server/koa/cors.js +22 -0
- package/server/koa/errorHandler.js +32 -0
- package/server/koa/finalFormat.js +34 -0
- package/server/koa/jsonBodyParser.js +172 -0
- package/server/koa/routeRequest.js +288 -0
- package/server/koa/serverUp.js +7 -0
- package/server/koa/staticFiles.js +97 -0
- package/server/koa/timeout.js +42 -0
- package/server/pageInfo.js +121 -0
- package/server/reRoute.js +54 -0
- package/server/resolveRealCasePath.js +56 -0
- package/server/specialPaths.js +107 -0
- package/server/validateRequest.js +127 -0
- package/server/ws/handlers.js +67 -0
- package/server/ws/index.js +50 -0
- package/start.js +122 -0
- package/vitest.config.js +15 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { getConfig } from "../config.js";
|
|
2
|
+
|
|
3
|
+
export default function(host){
|
|
4
|
+
|
|
5
|
+
const awesomenessConfig = getConfig();
|
|
6
|
+
|
|
7
|
+
if (
|
|
8
|
+
awesomenessConfig.applicationMap
|
|
9
|
+
&& awesomenessConfig.applicationMap[host]
|
|
10
|
+
) {
|
|
11
|
+
|
|
12
|
+
return awesomenessConfig.applicationMap[host];
|
|
13
|
+
|
|
14
|
+
} else {
|
|
15
|
+
|
|
16
|
+
// use the root domain
|
|
17
|
+
const parts = host.split('.');
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
if(parts.length > 2){
|
|
21
|
+
|
|
22
|
+
return parts.slice(-2).join('.');
|
|
23
|
+
|
|
24
|
+
} else {
|
|
25
|
+
|
|
26
|
+
return host;
|
|
27
|
+
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { getConfig } from "../config.js";
|
|
2
|
+
import applicationMap from './applicationMap.js';
|
|
3
|
+
import reRoute from './reRoute.js';
|
|
4
|
+
|
|
5
|
+
async function awesomenessNormalizeRequest({
|
|
6
|
+
req = {}
|
|
7
|
+
} = {}) {
|
|
8
|
+
|
|
9
|
+
const awesomenessConfig = getConfig();
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
const domain = req.headers.host;
|
|
13
|
+
|
|
14
|
+
// If the request is from API Gateway, use the requestContext to get the method
|
|
15
|
+
const method = req.method ?? req.requestContext?.http?.method;
|
|
16
|
+
const ip = req.ip ?? req.requestContext?.http?.sourceIp;
|
|
17
|
+
const userAgent = req.headers.userAgent;
|
|
18
|
+
|
|
19
|
+
let host = domain.toLowerCase();
|
|
20
|
+
|
|
21
|
+
const domainParts = host.split('.');
|
|
22
|
+
const mainDomain = domainParts.slice(-2).join('.').toLowerCase();
|
|
23
|
+
|
|
24
|
+
let subDomain = '';
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
if (domainParts.length > 2) {
|
|
28
|
+
|
|
29
|
+
subDomain = domainParts.slice(0, -2).join('.');
|
|
30
|
+
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
host = awesomenessConfig.hostMap({
|
|
34
|
+
host,
|
|
35
|
+
mainDomain
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const applicationName = applicationMap(host);
|
|
39
|
+
|
|
40
|
+
let path = req.path ?? req.requestContext?.http?.path ?? '/';
|
|
41
|
+
let pageRoute = path.replace('/', '');
|
|
42
|
+
|
|
43
|
+
const slug = pageRoute.split('/').pop().split('.')[0].split('?')[0];
|
|
44
|
+
|
|
45
|
+
// top level keys
|
|
46
|
+
const topLevelKeys = [ 'awesomenessType', 'meta', 'device' ];
|
|
47
|
+
|
|
48
|
+
const newData = {};
|
|
49
|
+
const parts = req?.request?.body ?? {};
|
|
50
|
+
|
|
51
|
+
for (const key in parts) {
|
|
52
|
+
|
|
53
|
+
if (!topLevelKeys.includes(key)) {
|
|
54
|
+
|
|
55
|
+
newData[key] = parts[key];
|
|
56
|
+
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const urlParams = {};
|
|
62
|
+
|
|
63
|
+
const queryString = req?.request?.url || '';
|
|
64
|
+
|
|
65
|
+
if (queryString.includes('?')) {
|
|
66
|
+
|
|
67
|
+
const queryPart = queryString.split('?').slice(1).join('?').split('#')[0];
|
|
68
|
+
const queryPairs = queryPart.split('&');
|
|
69
|
+
|
|
70
|
+
for (const pair of queryPairs) {
|
|
71
|
+
|
|
72
|
+
const [ key, value ] = pair.split('=');
|
|
73
|
+
|
|
74
|
+
urlParams[decodeURIComponent(key)] = decodeURIComponent(value || '');
|
|
75
|
+
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const awesomenessRequest = {
|
|
81
|
+
|
|
82
|
+
// request info
|
|
83
|
+
headers: { ... req.headers },
|
|
84
|
+
ip,
|
|
85
|
+
userAgent,
|
|
86
|
+
method,
|
|
87
|
+
// our site info - fucked with
|
|
88
|
+
host,
|
|
89
|
+
mainDomain,
|
|
90
|
+
domain,
|
|
91
|
+
subDomain,
|
|
92
|
+
site: host, // might not be host with special mapping
|
|
93
|
+
application: applicationName,
|
|
94
|
+
path,
|
|
95
|
+
pageRoute,
|
|
96
|
+
slug,
|
|
97
|
+
|
|
98
|
+
// stuff to return
|
|
99
|
+
meta: {},
|
|
100
|
+
|
|
101
|
+
// stuff from sender
|
|
102
|
+
testing: req?.request?.body?.testing ?? false,
|
|
103
|
+
meta: req?.request?.body?.meta ?? {
|
|
104
|
+
components: {},
|
|
105
|
+
pages: {}
|
|
106
|
+
},
|
|
107
|
+
device: req?.request?.body?.device ?? {},
|
|
108
|
+
data: newData,
|
|
109
|
+
awesomenessType: req?.request?.body?.awesomenessType ?? 'generic',
|
|
110
|
+
|
|
111
|
+
urlParams,
|
|
112
|
+
|
|
113
|
+
// just because
|
|
114
|
+
_RAW: req
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
awesomenessRequest.reRoute = (destination)=>{
|
|
118
|
+
|
|
119
|
+
return reRoute({
|
|
120
|
+
goToPage: destination,
|
|
121
|
+
awesomenessRequest
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
return awesomenessRequest;
|
|
127
|
+
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export { awesomenessNormalizeRequest };
|
|
131
|
+
export default awesomenessNormalizeRequest;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { brotliCompressSync, constants } from 'zlib';
|
|
2
|
+
|
|
3
|
+
function brotliJsonResponse(data, {
|
|
4
|
+
|
|
5
|
+
} = {}) {
|
|
6
|
+
|
|
7
|
+
const json = JSON.stringify(data);
|
|
8
|
+
|
|
9
|
+
const compressed = brotliCompressSync(Buffer.from(json), {
|
|
10
|
+
params: {
|
|
11
|
+
[constants.BROTLI_PARAM_QUALITY]: 5
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
statusCode: 200,
|
|
17
|
+
isBase64Encoded: true,
|
|
18
|
+
headers: {
|
|
19
|
+
'Content-Type': 'application/json',
|
|
20
|
+
'Content-Encoding': 'br'
|
|
21
|
+
},
|
|
22
|
+
body: compressed.toString('base64')
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export { brotliJsonResponse };
|
|
28
|
+
export default brotliJsonResponse;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export default async ({
|
|
2
|
+
permissionsAllowed,
|
|
3
|
+
awesomenessRequest,
|
|
4
|
+
}) => {
|
|
5
|
+
|
|
6
|
+
const userPermissions = awesomenessRequest.user?.permissions || [];
|
|
7
|
+
|
|
8
|
+
// has at lease one of the permissions
|
|
9
|
+
const hasPermission = permissionsAllowed.some((rp) => userPermissions.includes(rp) || rp === '*');
|
|
10
|
+
|
|
11
|
+
if(!hasPermission){
|
|
12
|
+
|
|
13
|
+
if(
|
|
14
|
+
process.env.NODE_ENV === 'development'
|
|
15
|
+
&& awesomenessConfig.byPassAccessRequirementsInDev === true
|
|
16
|
+
){
|
|
17
|
+
|
|
18
|
+
console.log('by passing access requirement. 2 - development env && byPassAccessRequirementsInDev is true');
|
|
19
|
+
|
|
20
|
+
} else {
|
|
21
|
+
|
|
22
|
+
throw {
|
|
23
|
+
message: 'user does not have permission',
|
|
24
|
+
permissionsAllowed,
|
|
25
|
+
userPermissions
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return true;
|
|
33
|
+
|
|
34
|
+
};
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { fileURLToPath } from "url";
|
|
3
|
+
import { each, getAllFiles } from "@awesomeness-js/utils";
|
|
4
|
+
import { readFileSync } from "fs";
|
|
5
|
+
|
|
6
|
+
function urlToFsPath(u) {
|
|
7
|
+
|
|
8
|
+
if (!(u instanceof URL)) {
|
|
9
|
+
|
|
10
|
+
throw new TypeError("componentLocations must be an array of URL objects");
|
|
11
|
+
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return fileURLToPath(u);
|
|
15
|
+
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function extractUiFirstParts(str) {
|
|
19
|
+
|
|
20
|
+
const regex = /ui\.([a-zA-Z0-9_]+)(?:\.[a-zA-Z0-9_.]*)?\(/g;
|
|
21
|
+
const matches = new Set();
|
|
22
|
+
let match;
|
|
23
|
+
|
|
24
|
+
while ((match = regex.exec(str)) !== null) {
|
|
25
|
+
|
|
26
|
+
matches.add(match[1]);
|
|
27
|
+
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return [ ...matches ];
|
|
31
|
+
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function componentDependencies(allComponents, {
|
|
35
|
+
componentLocations = [],
|
|
36
|
+
namespace = "ui",
|
|
37
|
+
showDetails = false,
|
|
38
|
+
ignore = [ "*.css.js", "*.css.php" ],
|
|
39
|
+
} = {}) {
|
|
40
|
+
|
|
41
|
+
if (!Array.isArray(componentLocations) || componentLocations.length === 0) {
|
|
42
|
+
|
|
43
|
+
throw new TypeError("componentLocations must be a non-empty array of URL objects");
|
|
44
|
+
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let componentsToProcess = [ ...allComponents ];
|
|
48
|
+
const out = {};
|
|
49
|
+
|
|
50
|
+
while (componentsToProcess.length > 0) {
|
|
51
|
+
|
|
52
|
+
const newComponentsToProcess = [];
|
|
53
|
+
|
|
54
|
+
componentsToProcess.forEach((component) => {
|
|
55
|
+
|
|
56
|
+
// Build roots in priority order; last is default because it’s last
|
|
57
|
+
const candidateRoots = componentLocations.map((baseUrl) => {
|
|
58
|
+
|
|
59
|
+
// baseUrl should point at a directory; we resolve component under it
|
|
60
|
+
const componentUrl = new URL(`./${component}/`, baseUrl);
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
return path.resolve(urlToFsPath(componentUrl));
|
|
64
|
+
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
let allFiles;
|
|
68
|
+
let chosenRoot;
|
|
69
|
+
|
|
70
|
+
for (const root of candidateRoots) {
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
|
|
74
|
+
allFiles = getAllFiles(".", {
|
|
75
|
+
dir: root,
|
|
76
|
+
ignore
|
|
77
|
+
});
|
|
78
|
+
chosenRoot = root;
|
|
79
|
+
break; // first match wins
|
|
80
|
+
|
|
81
|
+
} catch {
|
|
82
|
+
// try next
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (!allFiles) {
|
|
88
|
+
|
|
89
|
+
throw {
|
|
90
|
+
message: "component does not exist (no location matched)",
|
|
91
|
+
component,
|
|
92
|
+
tried: candidateRoots,
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
allFiles.forEach((file) => {
|
|
98
|
+
|
|
99
|
+
const normalizedPath = path.normalize(file);
|
|
100
|
+
const pathSegments = normalizedPath.split(path.sep);
|
|
101
|
+
|
|
102
|
+
const variableIndex = pathSegments.indexOf(component);
|
|
103
|
+
|
|
104
|
+
let fileNameFull = pathSegments[pathSegments.length - 1];
|
|
105
|
+
const fileTypeArr = fileNameFull.split(".");
|
|
106
|
+
const fileType = fileTypeArr[fileTypeArr.length - 1];
|
|
107
|
+
const fileName = fileTypeArr.slice(0, -1).join(".");
|
|
108
|
+
|
|
109
|
+
out[component] = out[component] || {};
|
|
110
|
+
out[component][fileType] = out[component][fileType] || {};
|
|
111
|
+
|
|
112
|
+
let arrAfterComponent = pathSegments.slice(variableIndex + 1);
|
|
113
|
+
|
|
114
|
+
arrAfterComponent.pop();
|
|
115
|
+
|
|
116
|
+
let tail = "";
|
|
117
|
+
|
|
118
|
+
if (fileType === "js" || fileType === "css") {
|
|
119
|
+
|
|
120
|
+
if (arrAfterComponent.length > 0) {
|
|
121
|
+
|
|
122
|
+
tail =
|
|
123
|
+
fileName === "index"
|
|
124
|
+
? "." + arrAfterComponent.join(".")
|
|
125
|
+
: `.${arrAfterComponent.join(".")}.${fileName}`;
|
|
126
|
+
|
|
127
|
+
} else {
|
|
128
|
+
|
|
129
|
+
tail = fileName === "index" ? "" : `.${fileName}`;
|
|
130
|
+
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const componentName = `${namespace}.${component}${tail}`;
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
|
|
139
|
+
const fileContent = readFileSync(file, "utf-8");
|
|
140
|
+
const lines = fileContent.split("\n");
|
|
141
|
+
let fileWithImportsStripped = "";
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
|
|
145
|
+
const newTest = extractUiFirstParts(fileContent);
|
|
146
|
+
|
|
147
|
+
if (newTest.length > 0) {
|
|
148
|
+
|
|
149
|
+
newTest.forEach((newComp) => {
|
|
150
|
+
|
|
151
|
+
if (!allComponents.includes(newComp)) {
|
|
152
|
+
|
|
153
|
+
allComponents.push(newComp);
|
|
154
|
+
newComponentsToProcess.push(newComp);
|
|
155
|
+
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
} catch (error) {
|
|
163
|
+
|
|
164
|
+
console.error("Error extracting UI parts:", error);
|
|
165
|
+
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
lines.forEach((line) => {
|
|
169
|
+
|
|
170
|
+
if (line.startsWith("import ui")) {
|
|
171
|
+
|
|
172
|
+
if (line.includes(`import ui from '#ui'; // `)) {
|
|
173
|
+
|
|
174
|
+
const imports = line.split(`import ui from '#ui'; // `);
|
|
175
|
+
|
|
176
|
+
if (imports.length > 1) {
|
|
177
|
+
|
|
178
|
+
const importComponents = imports[1].split(",").map((c) => c.trim());
|
|
179
|
+
|
|
180
|
+
importComponents.forEach((importComponent) => {
|
|
181
|
+
|
|
182
|
+
if (!allComponents.includes(importComponent)) {
|
|
183
|
+
|
|
184
|
+
allComponents.push(importComponent);
|
|
185
|
+
newComponentsToProcess.push(importComponent);
|
|
186
|
+
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
} else if (
|
|
196
|
+
line.startsWith("// awesomeness import")
|
|
197
|
+
|| line.startsWith("/* awesomeness @import")
|
|
198
|
+
) {
|
|
199
|
+
|
|
200
|
+
const importPathMatch = line.match(/['"]([^'"]+)['"]/);
|
|
201
|
+
|
|
202
|
+
if (importPathMatch) {
|
|
203
|
+
|
|
204
|
+
const importedComponentName = importPathMatch[1].replace(/;$/, "").trim();
|
|
205
|
+
|
|
206
|
+
if (!allComponents.includes(importedComponentName)) {
|
|
207
|
+
|
|
208
|
+
allComponents.push(importedComponentName);
|
|
209
|
+
newComponentsToProcess.push(importedComponentName);
|
|
210
|
+
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
} else {
|
|
216
|
+
|
|
217
|
+
fileWithImportsStripped += `${line}\n`;
|
|
218
|
+
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
if (fileType === "js") {
|
|
224
|
+
|
|
225
|
+
if (
|
|
226
|
+
fileWithImportsStripped.startsWith("(function")
|
|
227
|
+
|| fileWithImportsStripped.startsWith("((")
|
|
228
|
+
) {
|
|
229
|
+
|
|
230
|
+
fileWithImportsStripped = `;${fileWithImportsStripped}`;
|
|
231
|
+
|
|
232
|
+
} else {
|
|
233
|
+
|
|
234
|
+
fileWithImportsStripped = fileWithImportsStripped.replace(
|
|
235
|
+
"export default ",
|
|
236
|
+
`${componentName} = `
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
out[component][fileType][componentName] = fileWithImportsStripped;
|
|
244
|
+
|
|
245
|
+
} catch (err) {
|
|
246
|
+
|
|
247
|
+
console.log("Failed to get dependencies", { component });
|
|
248
|
+
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
if (out[component]) {
|
|
254
|
+
|
|
255
|
+
each(out[component], (files, type) => {
|
|
256
|
+
|
|
257
|
+
if (type === "js" && !files[`${namespace}.${component}`]) {
|
|
258
|
+
|
|
259
|
+
files[`${namespace}.${component}`] = `${namespace}.${component} = {}; `;
|
|
260
|
+
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
files = Object.keys(files)
|
|
264
|
+
.sort()
|
|
265
|
+
.reduce((obj, key) => {
|
|
266
|
+
|
|
267
|
+
obj[key] = files[key];
|
|
268
|
+
|
|
269
|
+
return obj;
|
|
270
|
+
|
|
271
|
+
}, {});
|
|
272
|
+
|
|
273
|
+
if (showDetails) {
|
|
274
|
+
|
|
275
|
+
out[component][type + "_details"] = files;
|
|
276
|
+
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
out[component][type] = ` ${Object.values(files).join("\n")} `;
|
|
280
|
+
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
componentsToProcess = componentsToProcess.filter((f) => f !== component);
|
|
286
|
+
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
if (newComponentsToProcess.length) {
|
|
290
|
+
|
|
291
|
+
componentsToProcess = componentsToProcess.concat(newComponentsToProcess);
|
|
292
|
+
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return out;
|
|
298
|
+
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export { componentDependencies };
|
package/server/errors.js
ADDED