@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,288 @@
|
|
|
1
|
+
import fetchPage from '../fetchPage.js';
|
|
2
|
+
import pageInfo from '../pageInfo.js';
|
|
3
|
+
import validateRequest from '../validateRequest.js';
|
|
4
|
+
import { staticFiles } from './staticFiles.js';
|
|
5
|
+
import finalFormat from './finalFormat.js';
|
|
6
|
+
|
|
7
|
+
async function routeRequest(ctx, next){
|
|
8
|
+
|
|
9
|
+
const awesomenessRequest = ctx.awesomenessRequest;
|
|
10
|
+
|
|
11
|
+
// make sure there is no dots in the path
|
|
12
|
+
// regex non alphanumeric or forward slash
|
|
13
|
+
if(awesomenessRequest.pageRoute.match(/[^a-zA-Z0-9\/\_\-]/)){
|
|
14
|
+
|
|
15
|
+
ctx.status = 404;
|
|
16
|
+
|
|
17
|
+
ctx.body = {
|
|
18
|
+
success: false,
|
|
19
|
+
message: 'Invalid path',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
finalFormat(awesomenessRequest, ctx);
|
|
23
|
+
|
|
24
|
+
return;
|
|
25
|
+
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
console.log(awesomenessRequest.awesomenessType, awesomenessRequest.pageRoute);
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
if(awesomenessRequest.awesomenessType === 'page'){
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
|
|
35
|
+
let {
|
|
36
|
+
about,
|
|
37
|
+
cssPath,
|
|
38
|
+
jsPath,
|
|
39
|
+
getData
|
|
40
|
+
} = await pageInfo(awesomenessRequest);
|
|
41
|
+
|
|
42
|
+
awesomenessRequest.routeInfo = about;
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
|
|
46
|
+
await validateRequest(awesomenessRequest);
|
|
47
|
+
|
|
48
|
+
} catch (error) {
|
|
49
|
+
|
|
50
|
+
ctx.status = 422;
|
|
51
|
+
|
|
52
|
+
ctx.body = {
|
|
53
|
+
success: false,
|
|
54
|
+
... error,
|
|
55
|
+
stack: error.stack,
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
finalFormat(awesomenessRequest, ctx);
|
|
59
|
+
|
|
60
|
+
return;
|
|
61
|
+
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
const page = awesomenessRequest.mdContent ? '_md' : awesomenessRequest.pageRoute;
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
|
|
69
|
+
const pageData = await fetchPage(awesomenessRequest, {
|
|
70
|
+
getData,
|
|
71
|
+
about,
|
|
72
|
+
cssPath,
|
|
73
|
+
jsPath,
|
|
74
|
+
page
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
ctx.body = {
|
|
78
|
+
success: true,
|
|
79
|
+
meta: awesomenessRequest.updatedMeta,
|
|
80
|
+
... pageData
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
finalFormat(awesomenessRequest, ctx);
|
|
84
|
+
|
|
85
|
+
return;
|
|
86
|
+
|
|
87
|
+
} catch (error) {
|
|
88
|
+
|
|
89
|
+
ctx.status = 500;
|
|
90
|
+
|
|
91
|
+
ctx.body = {
|
|
92
|
+
success: false,
|
|
93
|
+
message: 'Page exists but error fetching data.',
|
|
94
|
+
meta: awesomenessRequest.updatedMeta,
|
|
95
|
+
error,
|
|
96
|
+
page,
|
|
97
|
+
errMessage: error?.message || 'Error in getData function',
|
|
98
|
+
stack: error.stack
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
finalFormat(awesomenessRequest, ctx);
|
|
102
|
+
|
|
103
|
+
return;
|
|
104
|
+
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
} catch(error) {
|
|
110
|
+
|
|
111
|
+
ctx.status = 404;
|
|
112
|
+
|
|
113
|
+
ctx.body = {
|
|
114
|
+
success: false,
|
|
115
|
+
error,
|
|
116
|
+
message: error?.message || 'Error fetching page',
|
|
117
|
+
stack: error.stack,
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
finalFormat(awesomenessRequest, ctx);
|
|
121
|
+
|
|
122
|
+
return;
|
|
123
|
+
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
} else if(awesomenessRequest.awesomenessType === 'api'){
|
|
127
|
+
|
|
128
|
+
const siteSpecificRoute = new URL(`../../sites/${awesomenessRequest.site}/api/routes/${awesomenessRequest.pageRoute}/index.js`, import.meta.url);
|
|
129
|
+
const genericRoute = new URL(`../../api/routes/${awesomenessRequest.pageRoute}/index.js`, import.meta.url);
|
|
130
|
+
|
|
131
|
+
const siteSpecific_info = new URL(`../../sites/${awesomenessRequest.site}/api/routes/${awesomenessRequest.pageRoute}/_info.js`, import.meta.url);
|
|
132
|
+
const generic_info = new URL(`../../api/routes/${awesomenessRequest.pageRoute}/_info.js`, import.meta.url);
|
|
133
|
+
|
|
134
|
+
let routeIndex;
|
|
135
|
+
let routeInfo;
|
|
136
|
+
|
|
137
|
+
let specific = false;
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
|
|
141
|
+
routeIndex = await import(siteSpecificRoute);
|
|
142
|
+
routeInfo = await import(siteSpecific_info);
|
|
143
|
+
routeInfo = routeInfo.default;
|
|
144
|
+
|
|
145
|
+
specific = true;
|
|
146
|
+
|
|
147
|
+
} catch (error) {
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
|
|
151
|
+
routeIndex = await import(genericRoute);
|
|
152
|
+
routeInfo = await import(generic_info);
|
|
153
|
+
routeInfo = routeInfo.default;
|
|
154
|
+
|
|
155
|
+
specific = false;
|
|
156
|
+
|
|
157
|
+
} catch (error) {
|
|
158
|
+
|
|
159
|
+
ctx.status = 404;
|
|
160
|
+
|
|
161
|
+
ctx.body = {
|
|
162
|
+
success: false,
|
|
163
|
+
message: 'route not found',
|
|
164
|
+
error,
|
|
165
|
+
siteSpecificRoute,
|
|
166
|
+
genericRoute,
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
finalFormat(awesomenessRequest, ctx);
|
|
170
|
+
|
|
171
|
+
return;
|
|
172
|
+
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
|
|
181
|
+
// store routeInfo
|
|
182
|
+
awesomenessRequest.specific = specific;
|
|
183
|
+
awesomenessRequest.routeInfo = routeInfo;
|
|
184
|
+
|
|
185
|
+
// validate data
|
|
186
|
+
try {
|
|
187
|
+
|
|
188
|
+
await validateRequest(awesomenessRequest);
|
|
189
|
+
|
|
190
|
+
} catch (error) {
|
|
191
|
+
|
|
192
|
+
ctx.status = 422;
|
|
193
|
+
|
|
194
|
+
ctx.body = {
|
|
195
|
+
success: false,
|
|
196
|
+
... error,
|
|
197
|
+
stack: error.stack,
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
finalFormat(awesomenessRequest, ctx);
|
|
201
|
+
|
|
202
|
+
return;
|
|
203
|
+
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const data = await routeIndex.default(awesomenessRequest);
|
|
207
|
+
|
|
208
|
+
ctx.body = {
|
|
209
|
+
success: true,
|
|
210
|
+
meta: awesomenessRequest.updatedMeta,
|
|
211
|
+
...data
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
finalFormat(awesomenessRequest, ctx);
|
|
215
|
+
|
|
216
|
+
} catch (error) {
|
|
217
|
+
|
|
218
|
+
ctx.status = 500;
|
|
219
|
+
|
|
220
|
+
if(awesomenessRequest.status){
|
|
221
|
+
|
|
222
|
+
ctx.status = awesomenessRequest.status;
|
|
223
|
+
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
ctx.body = {
|
|
227
|
+
success: false,
|
|
228
|
+
meta: awesomenessRequest.updatedMeta,
|
|
229
|
+
message: error?.message || 'Error in route function',
|
|
230
|
+
error,
|
|
231
|
+
stack: error.stack,
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
finalFormat(awesomenessRequest, ctx);
|
|
235
|
+
|
|
236
|
+
return;
|
|
237
|
+
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
} else {
|
|
242
|
+
|
|
243
|
+
// if its a get request
|
|
244
|
+
// a static file should have been served already
|
|
245
|
+
// if the application is working correctly then ctx.awesomenessType === 'page' should be true
|
|
246
|
+
// if it is not true, they are hitting a url cold or reloading
|
|
247
|
+
// so load the app
|
|
248
|
+
|
|
249
|
+
if(awesomenessRequest.method != 'GET'){
|
|
250
|
+
|
|
251
|
+
ctx.status = 405;
|
|
252
|
+
ctx.body = {
|
|
253
|
+
success: false,
|
|
254
|
+
message: 'Method Not Allowed',
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
finalFormat(awesomenessRequest, ctx);
|
|
258
|
+
|
|
259
|
+
return;
|
|
260
|
+
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if(awesomenessRequest.path === '/test'){
|
|
264
|
+
|
|
265
|
+
ctx.body = {
|
|
266
|
+
success: true,
|
|
267
|
+
message: 'Hello, World! (just a test)',
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
finalFormat(awesomenessRequest, ctx);
|
|
271
|
+
|
|
272
|
+
return;
|
|
273
|
+
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
awesomenessRequest.path = '/';
|
|
277
|
+
await staticFiles(ctx, next);
|
|
278
|
+
|
|
279
|
+
return;
|
|
280
|
+
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
export { routeRequest };
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import send from 'koa-send';
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { getConfig } from '../../config.js';
|
|
6
|
+
|
|
7
|
+
const staticFiles = async (ctx, next) => {
|
|
8
|
+
|
|
9
|
+
const {
|
|
10
|
+
method,
|
|
11
|
+
path: reqPath,
|
|
12
|
+
site
|
|
13
|
+
} = ctx.awesomenessRequest;
|
|
14
|
+
|
|
15
|
+
const {
|
|
16
|
+
siteURL,
|
|
17
|
+
commonPublicDir
|
|
18
|
+
} = getConfig();
|
|
19
|
+
|
|
20
|
+
if (method !== "GET" && method !== "HEAD") {
|
|
21
|
+
|
|
22
|
+
await next();
|
|
23
|
+
|
|
24
|
+
return;
|
|
25
|
+
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (!(siteURL instanceof URL) || !(commonPublicDir instanceof URL)) {
|
|
29
|
+
|
|
30
|
+
ctx.throw(500, new Error("Config must provide siteURL and commonPublicDir as file: URLs"));
|
|
31
|
+
|
|
32
|
+
return;
|
|
33
|
+
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const sitesRoot = fileURLToPath(siteURL);
|
|
37
|
+
const commonRoot = fileURLToPath(commonPublicDir);
|
|
38
|
+
|
|
39
|
+
const domainRoot = path.join(sitesRoot, site, "public");
|
|
40
|
+
|
|
41
|
+
const relativePath =
|
|
42
|
+
reqPath === "/" ? "index.html" : reqPath.replace(/^\//, "");
|
|
43
|
+
|
|
44
|
+
const domainFilePath = path.join(domainRoot, relativePath);
|
|
45
|
+
const commonFilePath = path.join(commonRoot, relativePath);
|
|
46
|
+
|
|
47
|
+
let fileServed = false;
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
|
|
51
|
+
// Try to serve from the domain-specific directory
|
|
52
|
+
await fs.promises.access(domainFilePath);
|
|
53
|
+
ctx.set('X-Awesomeness-Place', 'Domain');
|
|
54
|
+
await send(ctx, relativePath, { root: domainRoot });
|
|
55
|
+
fileServed = true;
|
|
56
|
+
|
|
57
|
+
} catch (err) {
|
|
58
|
+
|
|
59
|
+
if (err.code !== 'ENOENT') {
|
|
60
|
+
|
|
61
|
+
ctx.throw(500, new Error('Internal Server Error'));
|
|
62
|
+
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!fileServed) {
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
|
|
71
|
+
// Try to serve from the common directory
|
|
72
|
+
await fs.promises.access(commonFilePath);
|
|
73
|
+
ctx.set('X-Awesomeness-Place', 'Common');
|
|
74
|
+
await send(ctx, relativePath, { root: commonRoot });
|
|
75
|
+
fileServed = true;
|
|
76
|
+
|
|
77
|
+
} catch (err2) {
|
|
78
|
+
|
|
79
|
+
if (err2.code !== 'ENOENT') {
|
|
80
|
+
|
|
81
|
+
ctx.throw(500, new Error('Internal Server Error'));
|
|
82
|
+
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (!fileServed) {
|
|
90
|
+
|
|
91
|
+
await next(); // Proceed to next middleware if no static file was served
|
|
92
|
+
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export { staticFiles };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Timeout middleware
|
|
2
|
+
const timeout = (ms) => {
|
|
3
|
+
|
|
4
|
+
return async (ctx, next) => {
|
|
5
|
+
|
|
6
|
+
// Create a promise that rejects after `ms` milliseconds
|
|
7
|
+
const timer = new Promise((_, reject) => {
|
|
8
|
+
|
|
9
|
+
const id = setTimeout(() => {
|
|
10
|
+
|
|
11
|
+
reject(new Error('Request timed out'));
|
|
12
|
+
|
|
13
|
+
}, ms);
|
|
14
|
+
|
|
15
|
+
// Clear the timer if the request finishes early
|
|
16
|
+
ctx.res.on('finish', () => clearTimeout(id));
|
|
17
|
+
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
|
|
22
|
+
// Race the timer and the next middleware
|
|
23
|
+
await Promise.race([ next(), timer ]);
|
|
24
|
+
|
|
25
|
+
} catch (err) {
|
|
26
|
+
|
|
27
|
+
// If an error occurs (such as a timeout), set the response
|
|
28
|
+
ctx.status = 408; // 408 Request Timeout
|
|
29
|
+
ctx.body = {
|
|
30
|
+
success: false,
|
|
31
|
+
message: err.message
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
return;
|
|
35
|
+
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export { timeout };
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { getConfig } from "../config.js";
|
|
2
|
+
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from "url";
|
|
5
|
+
import { existsSync } from "fs";
|
|
6
|
+
import { promises as fs } from "fs";
|
|
7
|
+
import { resolveRealCasePath } from "./resolveRealCasePath.js";
|
|
8
|
+
|
|
9
|
+
export default async function pageInfo(awesomenessRequest, { page = null } = {}) {
|
|
10
|
+
|
|
11
|
+
const awesomenessConfig = getConfig();
|
|
12
|
+
|
|
13
|
+
if (!page) {
|
|
14
|
+
|
|
15
|
+
page = awesomenessRequest.pageRoute;
|
|
16
|
+
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const slug = page.split("/").pop().split(".")[0].split("?")[0];
|
|
20
|
+
|
|
21
|
+
// awesomenessConfig.siteURL points at the /sites/ directory (as a URL)
|
|
22
|
+
const sitesRootPath =
|
|
23
|
+
awesomenessConfig.siteURL instanceof URL
|
|
24
|
+
? fileURLToPath(awesomenessConfig.siteURL)
|
|
25
|
+
: awesomenessConfig.siteURL;
|
|
26
|
+
|
|
27
|
+
const siteRootPath = path.join(sitesRootPath, awesomenessRequest.site);
|
|
28
|
+
const pagesRootPath = path.join(siteRootPath, "pages");
|
|
29
|
+
|
|
30
|
+
const mainPath = resolveRealCasePath(path.join(pagesRootPath, page));
|
|
31
|
+
|
|
32
|
+
let getDataPath = mainPath ? path.join(mainPath, "getData.js") : null;
|
|
33
|
+
let aboutPath = mainPath ? path.join(mainPath, "_info.js") : null;
|
|
34
|
+
let cssPath = mainPath ? path.join(mainPath, "css") : null;
|
|
35
|
+
let jsPath = mainPath ? path.join(mainPath, "js") : null;
|
|
36
|
+
|
|
37
|
+
let aboutExists = aboutPath ? existsSync(aboutPath) : false;
|
|
38
|
+
let getDataExists = getDataPath ? existsSync(getDataPath) : false;
|
|
39
|
+
|
|
40
|
+
if (aboutExists) {
|
|
41
|
+
|
|
42
|
+
const { default: about } = await import(pathToFileURL(aboutPath).href);
|
|
43
|
+
|
|
44
|
+
let getData = null;
|
|
45
|
+
|
|
46
|
+
if (getDataExists) {
|
|
47
|
+
|
|
48
|
+
({ default: getData } = await import(pathToFileURL(getDataPath).href));
|
|
49
|
+
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
getData,
|
|
54
|
+
about,
|
|
55
|
+
cssPath,
|
|
56
|
+
jsPath,
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// is it a simple blog page?
|
|
62
|
+
getDataPath = path.join(pagesRootPath, "_md", "getData.js");
|
|
63
|
+
aboutPath = path.join(pagesRootPath, "_md", "_info.js");
|
|
64
|
+
cssPath = path.join(pagesRootPath, "_md", "css");
|
|
65
|
+
jsPath = path.join(pagesRootPath, "_md", "js");
|
|
66
|
+
|
|
67
|
+
aboutExists = existsSync(aboutPath);
|
|
68
|
+
getDataExists = existsSync(getDataPath);
|
|
69
|
+
|
|
70
|
+
if (!aboutExists) {
|
|
71
|
+
|
|
72
|
+
throw {
|
|
73
|
+
message: "page not found",
|
|
74
|
+
aboutPath,
|
|
75
|
+
cssPath,
|
|
76
|
+
jsPath,
|
|
77
|
+
awesomenessRequest,
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const blogPageLocation = path.join(pagesRootPath, "_md", "pages", `${slug}.md`);
|
|
83
|
+
const relativePath = resolveRealCasePath(blogPageLocation);
|
|
84
|
+
|
|
85
|
+
const { default: about } = await import(pathToFileURL(aboutPath).href);
|
|
86
|
+
|
|
87
|
+
let getData = null;
|
|
88
|
+
|
|
89
|
+
if (getDataExists) {
|
|
90
|
+
|
|
91
|
+
({ default: getData } = await import(pathToFileURL(getDataPath).href));
|
|
92
|
+
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const out = {
|
|
96
|
+
getData,
|
|
97
|
+
about,
|
|
98
|
+
cssPath,
|
|
99
|
+
jsPath,
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
if (relativePath) {
|
|
103
|
+
|
|
104
|
+
out.mdContent = await fs.readFile(relativePath, "utf-8");
|
|
105
|
+
awesomenessRequest.mdContent = out.mdContent;
|
|
106
|
+
|
|
107
|
+
} else {
|
|
108
|
+
|
|
109
|
+
console.log("relative path does not exist, no content found");
|
|
110
|
+
|
|
111
|
+
throw {
|
|
112
|
+
code: 404,
|
|
113
|
+
message: "page not found. tried specific, MD, and database",
|
|
114
|
+
url: slug,
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return out;
|
|
120
|
+
|
|
121
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import specialPaths from './specialPaths.js';
|
|
2
|
+
import fetchPage from './fetchPage.js';
|
|
3
|
+
import { getConfig } from "../config.js";
|
|
4
|
+
|
|
5
|
+
export default async function reRoute({
|
|
6
|
+
goToPage,
|
|
7
|
+
awesomenessRequest
|
|
8
|
+
}){
|
|
9
|
+
|
|
10
|
+
const awesomenessConfig = getConfig();
|
|
11
|
+
|
|
12
|
+
if(!goToPage){
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
pageData: {},
|
|
16
|
+
pageRouteFixed: awesomenessRequest.pageRoute
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if(!awesomenessRequest.reRoutes){
|
|
22
|
+
|
|
23
|
+
awesomenessRequest.reRoutes = [];
|
|
24
|
+
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
awesomenessRequest.reRoutes.push(awesomenessRequest.pageRoute);
|
|
28
|
+
|
|
29
|
+
// re-route
|
|
30
|
+
awesomenessRequest.path = goToPage;
|
|
31
|
+
|
|
32
|
+
// page route - replace leading and trailing slash
|
|
33
|
+
goToPage = goToPage.replace(/^\/+|\/+$/g, '');
|
|
34
|
+
awesomenessRequest.pageRoute = goToPage;
|
|
35
|
+
|
|
36
|
+
// slug
|
|
37
|
+
const slug = goToPage.split('/').pop().split('.')[0].split('?')[0];
|
|
38
|
+
|
|
39
|
+
awesomenessRequest.slug = slug;
|
|
40
|
+
|
|
41
|
+
const routes = awesomenessConfig.specialRoutes[awesomenessRequest.site] || [];
|
|
42
|
+
|
|
43
|
+
await specialPaths(awesomenessRequest, routes);
|
|
44
|
+
|
|
45
|
+
const pageData = await fetchPage(awesomenessRequest);
|
|
46
|
+
|
|
47
|
+
const out = {
|
|
48
|
+
pageData,
|
|
49
|
+
pageRouteFixed: awesomenessRequest.pageRoute
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
return out;
|
|
53
|
+
|
|
54
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { readdirSync } from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Case-insensitive real path resolver.
|
|
6
|
+
* Works correctly on Windows, macOS, and Linux/Docker.
|
|
7
|
+
*/
|
|
8
|
+
export function resolveRealCasePath(inputPath, returnAbsolute = false) {
|
|
9
|
+
|
|
10
|
+
if (!inputPath) return null;
|
|
11
|
+
|
|
12
|
+
const absPath = path.resolve(inputPath);
|
|
13
|
+
const { root } = path.parse(absPath);
|
|
14
|
+
const parts = absPath.split(path.sep).filter(Boolean);
|
|
15
|
+
|
|
16
|
+
let current = root || path.sep;
|
|
17
|
+
|
|
18
|
+
// ✅ Only skip the first part if it's a Windows drive letter (e.g., "C:\")
|
|
19
|
+
const startIndex = process.platform === "win32" ? 1 : 0;
|
|
20
|
+
|
|
21
|
+
for (const part of parts.slice(startIndex)) {
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
|
|
25
|
+
const entries = readdirSync(current);
|
|
26
|
+
const match = entries.find((e) => e.toLowerCase() === part.toLowerCase());
|
|
27
|
+
|
|
28
|
+
if (!match) return null;
|
|
29
|
+
current = path.join(current, match);
|
|
30
|
+
|
|
31
|
+
} catch {
|
|
32
|
+
|
|
33
|
+
return null;
|
|
34
|
+
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let finalPath;
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
if (returnAbsolute) {
|
|
43
|
+
|
|
44
|
+
finalPath = current;
|
|
45
|
+
|
|
46
|
+
} else {
|
|
47
|
+
|
|
48
|
+
const rel = path.relative(process.cwd(), current);
|
|
49
|
+
|
|
50
|
+
finalPath = rel.startsWith(".") ? rel : `./${rel}`;
|
|
51
|
+
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return finalPath.split(path.sep).join("/");
|
|
55
|
+
|
|
56
|
+
}
|