@constela/start 0.3.0 → 0.4.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/dist/chunk-JXIOHPG5.js +399 -0
- package/dist/cli/index.js +30 -6
- package/dist/index.d.ts +214 -3
- package/dist/index.js +490 -413
- package/package.json +6 -5
package/dist/index.js
CHANGED
|
@@ -1,404 +1,41 @@
|
|
|
1
|
+
import {
|
|
2
|
+
build,
|
|
3
|
+
createDevServer,
|
|
4
|
+
filePathToPattern,
|
|
5
|
+
getMimeType,
|
|
6
|
+
isPathSafe,
|
|
7
|
+
resolveStaticFile,
|
|
8
|
+
scanRoutes
|
|
9
|
+
} from "./chunk-JXIOHPG5.js";
|
|
1
10
|
import {
|
|
2
11
|
generateHydrationScript,
|
|
3
12
|
renderPage,
|
|
4
13
|
wrapHtml
|
|
5
14
|
} from "./chunk-QLDID7EZ.js";
|
|
6
15
|
|
|
7
|
-
// src/
|
|
8
|
-
import
|
|
9
|
-
import {
|
|
10
|
-
import { join } from "path";
|
|
11
|
-
function filePathToPattern(filePath, _routesDir) {
|
|
12
|
-
let normalized = filePath.replace(/\\/g, "/");
|
|
13
|
-
normalized = normalized.replace(/\.(ts|tsx|js|jsx)$/, "");
|
|
14
|
-
const segments = normalized.split("/");
|
|
15
|
-
const processedSegments = segments.map((segment) => {
|
|
16
|
-
if (segment.startsWith("[...") && segment.endsWith("]")) {
|
|
17
|
-
return "*";
|
|
18
|
-
}
|
|
19
|
-
if (segment.startsWith("[") && segment.endsWith("]")) {
|
|
20
|
-
const paramName = segment.slice(1, -1);
|
|
21
|
-
return `:${paramName}`;
|
|
22
|
-
}
|
|
23
|
-
return segment;
|
|
24
|
-
});
|
|
25
|
-
if (processedSegments.at(-1) === "index") {
|
|
26
|
-
processedSegments.pop();
|
|
27
|
-
}
|
|
28
|
-
const path = "/" + processedSegments.join("/");
|
|
29
|
-
if (path === "/") {
|
|
30
|
-
return "/";
|
|
31
|
-
}
|
|
32
|
-
return path.endsWith("/") ? path.slice(0, -1) : path;
|
|
33
|
-
}
|
|
34
|
-
function extractParams(filePath) {
|
|
35
|
-
const params = [];
|
|
36
|
-
const normalized = filePath.replace(/\\/g, "/");
|
|
37
|
-
const regex = /\[(?:\.\.\.)?([^\]]+)\]/g;
|
|
38
|
-
let match;
|
|
39
|
-
while ((match = regex.exec(normalized)) !== null) {
|
|
40
|
-
const paramName = match[1];
|
|
41
|
-
if (paramName !== void 0) {
|
|
42
|
-
params.push(paramName);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
return params;
|
|
46
|
-
}
|
|
47
|
-
function determineRouteType(filePath) {
|
|
48
|
-
const normalized = filePath.replace(/\\/g, "/");
|
|
49
|
-
const fileName = normalized.split("/").pop() ?? "";
|
|
50
|
-
if (fileName.startsWith("_middleware.")) {
|
|
51
|
-
return "middleware";
|
|
52
|
-
}
|
|
53
|
-
if (normalized.startsWith("api/") || normalized.includes("/api/")) {
|
|
54
|
-
return "api";
|
|
55
|
-
}
|
|
56
|
-
return "page";
|
|
57
|
-
}
|
|
58
|
-
function shouldIncludeRoute(filePath) {
|
|
59
|
-
const normalized = filePath.replace(/\\/g, "/");
|
|
60
|
-
const fileName = normalized.split("/").pop() ?? "";
|
|
61
|
-
if (fileName.endsWith(".d.ts")) {
|
|
62
|
-
return false;
|
|
63
|
-
}
|
|
64
|
-
if (fileName.startsWith("_") && !fileName.startsWith("_middleware.")) {
|
|
65
|
-
return false;
|
|
66
|
-
}
|
|
67
|
-
return true;
|
|
68
|
-
}
|
|
69
|
-
async function scanRoutes(routesDir) {
|
|
70
|
-
if (!existsSync(routesDir)) {
|
|
71
|
-
throw new Error(`Routes directory does not exist: ${routesDir}`);
|
|
72
|
-
}
|
|
73
|
-
const stats = statSync(routesDir);
|
|
74
|
-
if (!stats.isDirectory()) {
|
|
75
|
-
throw new Error(`Routes path is not a directory: ${routesDir}`);
|
|
76
|
-
}
|
|
77
|
-
const files = await fg("**/*.{ts,tsx,js,jsx}", {
|
|
78
|
-
cwd: routesDir,
|
|
79
|
-
ignore: ["node_modules/**", "**/*.d.ts"],
|
|
80
|
-
onlyFiles: true,
|
|
81
|
-
followSymbolicLinks: false
|
|
82
|
-
});
|
|
83
|
-
const routes = [];
|
|
84
|
-
for (const filePath of files) {
|
|
85
|
-
if (!shouldIncludeRoute(filePath)) {
|
|
86
|
-
continue;
|
|
87
|
-
}
|
|
88
|
-
const route = {
|
|
89
|
-
file: join(routesDir, filePath),
|
|
90
|
-
pattern: filePathToPattern(filePath, routesDir),
|
|
91
|
-
type: determineRouteType(filePath),
|
|
92
|
-
params: extractParams(filePath)
|
|
93
|
-
};
|
|
94
|
-
routes.push(route);
|
|
95
|
-
}
|
|
96
|
-
routes.sort((a, b) => {
|
|
97
|
-
return compareRoutes(a, b);
|
|
98
|
-
});
|
|
99
|
-
return routes;
|
|
100
|
-
}
|
|
101
|
-
function compareRoutes(a, b) {
|
|
102
|
-
if (a.type === "middleware" && b.type !== "middleware") return -1;
|
|
103
|
-
if (b.type === "middleware" && a.type !== "middleware") return 1;
|
|
104
|
-
const segmentsA = a.pattern.split("/").filter(Boolean);
|
|
105
|
-
const segmentsB = b.pattern.split("/").filter(Boolean);
|
|
106
|
-
const minLen = Math.min(segmentsA.length, segmentsB.length);
|
|
107
|
-
for (let i = 0; i < minLen; i++) {
|
|
108
|
-
const segA = segmentsA[i] ?? "";
|
|
109
|
-
const segB = segmentsB[i] ?? "";
|
|
110
|
-
const typeA = getSegmentType(segA);
|
|
111
|
-
const typeB = getSegmentType(segB);
|
|
112
|
-
if (typeA !== typeB) {
|
|
113
|
-
return typeA - typeB;
|
|
114
|
-
}
|
|
115
|
-
if (typeA === 0 && segA !== segB) {
|
|
116
|
-
return segA.localeCompare(segB);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
if (segmentsA.length !== segmentsB.length) {
|
|
120
|
-
return segmentsA.length - segmentsB.length;
|
|
121
|
-
}
|
|
122
|
-
return a.pattern.localeCompare(b.pattern);
|
|
123
|
-
}
|
|
124
|
-
function getSegmentType(segment) {
|
|
125
|
-
if (segment === "*") return 2;
|
|
126
|
-
if (segment.startsWith(":")) return 1;
|
|
127
|
-
return 0;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// src/dev/server.ts
|
|
131
|
-
import { createServer } from "http";
|
|
132
|
-
import { createReadStream } from "fs";
|
|
133
|
-
import { join as join3 } from "path";
|
|
16
|
+
// src/build/ssg.ts
|
|
17
|
+
import { mkdir, writeFile } from "fs/promises";
|
|
18
|
+
import { join, dirname } from "path";
|
|
134
19
|
|
|
135
|
-
// src/
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
var MIME_TYPES = {
|
|
139
|
-
// Images
|
|
140
|
-
".ico": "image/x-icon",
|
|
141
|
-
".png": "image/png",
|
|
142
|
-
".jpg": "image/jpeg",
|
|
143
|
-
".jpeg": "image/jpeg",
|
|
144
|
-
".gif": "image/gif",
|
|
145
|
-
".svg": "image/svg+xml",
|
|
146
|
-
".webp": "image/webp",
|
|
147
|
-
// Fonts
|
|
148
|
-
".woff": "font/woff",
|
|
149
|
-
".woff2": "font/woff2",
|
|
150
|
-
".ttf": "font/ttf",
|
|
151
|
-
".otf": "font/otf",
|
|
152
|
-
".eot": "application/vnd.ms-fontobject",
|
|
153
|
-
// Web assets
|
|
154
|
-
".css": "text/css",
|
|
155
|
-
".js": "text/javascript",
|
|
156
|
-
".json": "application/json",
|
|
157
|
-
".txt": "text/plain",
|
|
158
|
-
".html": "text/html",
|
|
159
|
-
".xml": "application/xml",
|
|
160
|
-
// Other
|
|
161
|
-
".pdf": "application/pdf",
|
|
162
|
-
".mp3": "audio/mpeg",
|
|
163
|
-
".mp4": "video/mp4",
|
|
164
|
-
".webm": "video/webm",
|
|
165
|
-
".map": "application/json"
|
|
166
|
-
};
|
|
167
|
-
var DEFAULT_MIME_TYPE = "application/octet-stream";
|
|
168
|
-
function isPathSafe(pathname) {
|
|
169
|
-
if (!pathname) {
|
|
170
|
-
return false;
|
|
171
|
-
}
|
|
172
|
-
if (!pathname.startsWith("/")) {
|
|
173
|
-
return false;
|
|
174
|
-
}
|
|
175
|
-
if (pathname.includes("\\")) {
|
|
176
|
-
return false;
|
|
177
|
-
}
|
|
178
|
-
if (pathname.includes("//")) {
|
|
179
|
-
return false;
|
|
180
|
-
}
|
|
181
|
-
if (pathname.includes("\0") || pathname.includes("%00")) {
|
|
182
|
-
return false;
|
|
183
|
-
}
|
|
184
|
-
let decoded;
|
|
185
|
-
try {
|
|
186
|
-
decoded = pathname;
|
|
187
|
-
let prevDecoded = "";
|
|
188
|
-
while (decoded !== prevDecoded) {
|
|
189
|
-
prevDecoded = decoded;
|
|
190
|
-
decoded = decodeURIComponent(decoded);
|
|
191
|
-
}
|
|
192
|
-
} catch {
|
|
193
|
-
return false;
|
|
194
|
-
}
|
|
195
|
-
if (decoded.includes("\0")) {
|
|
196
|
-
return false;
|
|
197
|
-
}
|
|
198
|
-
if (decoded.includes("..")) {
|
|
199
|
-
return false;
|
|
200
|
-
}
|
|
201
|
-
const segments = decoded.split("/").filter(Boolean);
|
|
202
|
-
for (const segment of segments) {
|
|
203
|
-
if (segment.startsWith(".")) {
|
|
204
|
-
return false;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
return true;
|
|
20
|
+
// src/utils/resolve-page.ts
|
|
21
|
+
function isPageExportFunction(exported) {
|
|
22
|
+
return typeof exported === "function";
|
|
208
23
|
}
|
|
209
|
-
function
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
return true;
|
|
216
|
-
}
|
|
217
|
-
if (!pathname.startsWith("/")) {
|
|
218
|
-
return true;
|
|
219
|
-
}
|
|
220
|
-
if (pathname.includes("\\")) {
|
|
221
|
-
return true;
|
|
222
|
-
}
|
|
223
|
-
if (pathname.includes("//")) {
|
|
224
|
-
return true;
|
|
225
|
-
}
|
|
226
|
-
if (pathname.includes("\0") || pathname.includes("%00")) {
|
|
227
|
-
return true;
|
|
228
|
-
}
|
|
229
|
-
let decoded;
|
|
230
|
-
try {
|
|
231
|
-
decoded = pathname;
|
|
232
|
-
let prevDecoded = "";
|
|
233
|
-
while (decoded !== prevDecoded) {
|
|
234
|
-
prevDecoded = decoded;
|
|
235
|
-
decoded = decodeURIComponent(decoded);
|
|
236
|
-
}
|
|
237
|
-
} catch {
|
|
238
|
-
return true;
|
|
239
|
-
}
|
|
240
|
-
if (decoded.includes("\0")) {
|
|
241
|
-
return true;
|
|
242
|
-
}
|
|
243
|
-
const segments = decoded.split("/").filter(Boolean);
|
|
244
|
-
for (const segment of segments) {
|
|
245
|
-
if (segment.startsWith(".") && segment !== "..") {
|
|
246
|
-
return true;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
if (decoded.startsWith("/..")) {
|
|
250
|
-
return true;
|
|
251
|
-
}
|
|
252
|
-
return false;
|
|
253
|
-
}
|
|
254
|
-
function resolveStaticFile(pathname, publicDir) {
|
|
255
|
-
if (hasObviousAttackPattern(pathname)) {
|
|
256
|
-
return {
|
|
257
|
-
exists: false,
|
|
258
|
-
filePath: null,
|
|
259
|
-
mimeType: null,
|
|
260
|
-
error: "path_traversal"
|
|
261
|
-
};
|
|
262
|
-
}
|
|
263
|
-
let decodedPathname;
|
|
264
|
-
try {
|
|
265
|
-
decodedPathname = decodeURIComponent(pathname);
|
|
266
|
-
} catch {
|
|
267
|
-
return {
|
|
268
|
-
exists: false,
|
|
269
|
-
filePath: null,
|
|
270
|
-
mimeType: null,
|
|
271
|
-
error: "path_traversal"
|
|
272
|
-
};
|
|
273
|
-
}
|
|
274
|
-
const relativePath = decodedPathname.slice(1);
|
|
275
|
-
const resolvedPath = normalize(join2(publicDir, relativePath));
|
|
276
|
-
const absolutePublicDir = resolve(publicDir);
|
|
277
|
-
const publicDirWithSep = absolutePublicDir.endsWith("/") ? absolutePublicDir : absolutePublicDir + "/";
|
|
278
|
-
if (!resolvedPath.startsWith(publicDirWithSep) && resolvedPath !== absolutePublicDir) {
|
|
279
|
-
return {
|
|
280
|
-
exists: false,
|
|
281
|
-
filePath: null,
|
|
282
|
-
mimeType: null,
|
|
283
|
-
error: "outside_public"
|
|
284
|
-
};
|
|
285
|
-
}
|
|
286
|
-
const mimeType = getMimeType(resolvedPath);
|
|
287
|
-
let exists = false;
|
|
288
|
-
if (existsSync2(resolvedPath)) {
|
|
289
|
-
try {
|
|
290
|
-
const stats = statSync2(resolvedPath);
|
|
291
|
-
exists = stats.isFile();
|
|
292
|
-
} catch {
|
|
293
|
-
exists = false;
|
|
24
|
+
async function resolvePageExport(pageDefault, params, expectedParams) {
|
|
25
|
+
if (expectedParams) {
|
|
26
|
+
for (const key of expectedParams) {
|
|
27
|
+
if (!(key in params)) {
|
|
28
|
+
throw new Error(`Missing required route param: ${key}`);
|
|
29
|
+
}
|
|
294
30
|
}
|
|
295
31
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
filePath: resolvedPath,
|
|
299
|
-
mimeType
|
|
300
|
-
};
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
// src/dev/server.ts
|
|
304
|
-
var DEFAULT_PORT = 3e3;
|
|
305
|
-
var DEFAULT_HOST = "localhost";
|
|
306
|
-
var DEFAULT_PUBLIC_DIR = "public";
|
|
307
|
-
async function createDevServer(options = {}) {
|
|
308
|
-
const {
|
|
309
|
-
port = DEFAULT_PORT,
|
|
310
|
-
host = DEFAULT_HOST,
|
|
311
|
-
routesDir: _routesDir = "src/routes",
|
|
312
|
-
publicDir = join3(process.cwd(), DEFAULT_PUBLIC_DIR)
|
|
313
|
-
} = options;
|
|
314
|
-
let httpServer = null;
|
|
315
|
-
let actualPort = port;
|
|
316
|
-
const devServer = {
|
|
317
|
-
get port() {
|
|
318
|
-
return actualPort;
|
|
319
|
-
},
|
|
320
|
-
async listen() {
|
|
321
|
-
return new Promise((resolve2, reject) => {
|
|
322
|
-
httpServer = createServer((req, res) => {
|
|
323
|
-
const url = new URL(req.url ?? "/", `http://${req.headers.host}`);
|
|
324
|
-
const pathname = url.pathname;
|
|
325
|
-
const staticResult = resolveStaticFile(pathname, publicDir);
|
|
326
|
-
if (staticResult.error === "path_traversal" || staticResult.error === "outside_public") {
|
|
327
|
-
res.writeHead(403, { "Content-Type": "text/plain" });
|
|
328
|
-
res.end("Forbidden");
|
|
329
|
-
return;
|
|
330
|
-
}
|
|
331
|
-
if (staticResult.exists && staticResult.filePath && staticResult.mimeType) {
|
|
332
|
-
res.writeHead(200, { "Content-Type": staticResult.mimeType });
|
|
333
|
-
const stream = createReadStream(staticResult.filePath);
|
|
334
|
-
stream.pipe(res);
|
|
335
|
-
stream.on("error", () => {
|
|
336
|
-
if (!res.headersSent) {
|
|
337
|
-
res.writeHead(500, { "Content-Type": "text/plain" });
|
|
338
|
-
}
|
|
339
|
-
res.end("Internal Server Error");
|
|
340
|
-
});
|
|
341
|
-
return;
|
|
342
|
-
}
|
|
343
|
-
if (staticResult.filePath && !staticResult.exists) {
|
|
344
|
-
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
345
|
-
res.end("Not Found");
|
|
346
|
-
return;
|
|
347
|
-
}
|
|
348
|
-
res.writeHead(200, { "Content-Type": "text/html" });
|
|
349
|
-
res.end("<html><body>Constela Dev Server</body></html>");
|
|
350
|
-
});
|
|
351
|
-
httpServer.on("error", (err) => {
|
|
352
|
-
reject(err);
|
|
353
|
-
});
|
|
354
|
-
httpServer.listen(port, host, () => {
|
|
355
|
-
const address = httpServer?.address();
|
|
356
|
-
if (address) {
|
|
357
|
-
actualPort = address.port;
|
|
358
|
-
}
|
|
359
|
-
resolve2();
|
|
360
|
-
});
|
|
361
|
-
});
|
|
362
|
-
},
|
|
363
|
-
async close() {
|
|
364
|
-
return new Promise((resolve2, reject) => {
|
|
365
|
-
if (!httpServer) {
|
|
366
|
-
resolve2();
|
|
367
|
-
return;
|
|
368
|
-
}
|
|
369
|
-
httpServer.close((err) => {
|
|
370
|
-
if (err) {
|
|
371
|
-
reject(err);
|
|
372
|
-
} else {
|
|
373
|
-
httpServer = null;
|
|
374
|
-
resolve2();
|
|
375
|
-
}
|
|
376
|
-
});
|
|
377
|
-
});
|
|
378
|
-
}
|
|
379
|
-
};
|
|
380
|
-
return devServer;
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
// src/build/index.ts
|
|
384
|
-
async function build(options) {
|
|
385
|
-
const outDir = options?.outDir ?? "dist";
|
|
386
|
-
const routesDir = options?.routesDir ?? "src/routes";
|
|
387
|
-
let routes = [];
|
|
388
|
-
try {
|
|
389
|
-
const scannedRoutes = await scanRoutes(routesDir);
|
|
390
|
-
routes = scannedRoutes.map((r) => r.pattern);
|
|
391
|
-
} catch {
|
|
32
|
+
if (isPageExportFunction(pageDefault)) {
|
|
33
|
+
return await pageDefault(params);
|
|
392
34
|
}
|
|
393
|
-
return
|
|
394
|
-
outDir,
|
|
395
|
-
routes
|
|
396
|
-
};
|
|
35
|
+
return pageDefault;
|
|
397
36
|
}
|
|
398
37
|
|
|
399
38
|
// src/build/ssg.ts
|
|
400
|
-
import { mkdir, writeFile } from "fs/promises";
|
|
401
|
-
import { join as join4, dirname } from "path";
|
|
402
39
|
var defaultProgram = {
|
|
403
40
|
version: "1.0",
|
|
404
41
|
state: {},
|
|
@@ -410,24 +47,12 @@ var defaultProgram = {
|
|
|
410
47
|
children: [{ kind: "text", value: { expr: "lit", value: "" } }]
|
|
411
48
|
}
|
|
412
49
|
};
|
|
413
|
-
var testStaticPaths = {
|
|
414
|
-
"/users/:id": {
|
|
415
|
-
paths: [{ params: { id: "1" } }, { params: { id: "2" } }]
|
|
416
|
-
},
|
|
417
|
-
"/posts/:slug": {
|
|
418
|
-
paths: [{ params: { slug: "hello-world" } }]
|
|
419
|
-
},
|
|
420
|
-
"/posts/:year/:month": {
|
|
421
|
-
paths: [{ params: { year: "2024", month: "01" } }]
|
|
422
|
-
}
|
|
423
|
-
};
|
|
424
|
-
var consumedPatterns = /* @__PURE__ */ new Set();
|
|
425
50
|
function getOutputPath(pattern, outDir) {
|
|
426
51
|
if (pattern === "/") {
|
|
427
|
-
return
|
|
52
|
+
return join(outDir, "index.html");
|
|
428
53
|
}
|
|
429
54
|
const segments = pattern.slice(1).split("/");
|
|
430
|
-
return
|
|
55
|
+
return join(outDir, ...segments, "index.html");
|
|
431
56
|
}
|
|
432
57
|
function resolvePattern(pattern, params) {
|
|
433
58
|
let resolved = pattern;
|
|
@@ -444,15 +69,13 @@ async function tryLoadModule(filePath) {
|
|
|
444
69
|
return null;
|
|
445
70
|
}
|
|
446
71
|
}
|
|
447
|
-
async function getStaticPathsForRoute(route, module) {
|
|
72
|
+
async function getStaticPathsForRoute(route, module, staticPathsProvider) {
|
|
448
73
|
if (module?.getStaticPaths) {
|
|
449
74
|
const result = await module.getStaticPaths();
|
|
450
75
|
return result;
|
|
451
76
|
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
consumedPatterns.add(route.pattern);
|
|
455
|
-
return testData;
|
|
77
|
+
if (staticPathsProvider) {
|
|
78
|
+
return await staticPathsProvider(route.pattern);
|
|
456
79
|
}
|
|
457
80
|
return null;
|
|
458
81
|
}
|
|
@@ -471,19 +94,21 @@ async function generateSinglePage(pattern, outDir, program, params = {}) {
|
|
|
471
94
|
await writeFile(outputPath, html, "utf-8");
|
|
472
95
|
return outputPath;
|
|
473
96
|
}
|
|
474
|
-
async function generateStaticPages(routes, outDir) {
|
|
97
|
+
async function generateStaticPages(routes, outDir, options = {}) {
|
|
98
|
+
const { staticPathsProvider } = options;
|
|
475
99
|
const generatedPaths = [];
|
|
476
100
|
const pageRoutes = routes.filter((r) => r.type === "page");
|
|
477
101
|
for (const route of pageRoutes) {
|
|
478
102
|
const isDynamic = route.params.length > 0;
|
|
479
103
|
const module = await tryLoadModule(route.file);
|
|
480
|
-
const
|
|
104
|
+
const pageExport = module?.default ?? defaultProgram;
|
|
481
105
|
if (isDynamic) {
|
|
482
|
-
const staticPaths = await getStaticPathsForRoute(route, module);
|
|
106
|
+
const staticPaths = await getStaticPathsForRoute(route, module, staticPathsProvider);
|
|
483
107
|
if (!staticPaths) {
|
|
484
108
|
continue;
|
|
485
109
|
}
|
|
486
110
|
for (const pathData of staticPaths.paths) {
|
|
111
|
+
const program = await resolvePageExport(pageExport, pathData.params, route.params);
|
|
487
112
|
const resolvedPattern = resolvePattern(route.pattern, pathData.params);
|
|
488
113
|
const filePath = await generateSinglePage(
|
|
489
114
|
resolvedPattern,
|
|
@@ -494,6 +119,7 @@ async function generateStaticPages(routes, outDir) {
|
|
|
494
119
|
generatedPaths.push(filePath);
|
|
495
120
|
}
|
|
496
121
|
} else {
|
|
122
|
+
const program = await resolvePageExport(pageExport, {});
|
|
497
123
|
const filePath = await generateSinglePage(route.pattern, outDir, program);
|
|
498
124
|
generatedPaths.push(filePath);
|
|
499
125
|
}
|
|
@@ -605,7 +231,7 @@ function createErrorResponse(error) {
|
|
|
605
231
|
}
|
|
606
232
|
function createAdapter(options) {
|
|
607
233
|
const { routes, loadModule = defaultLoadModule } = options;
|
|
608
|
-
async function
|
|
234
|
+
async function fetch2(request) {
|
|
609
235
|
try {
|
|
610
236
|
const url = new URL(request.url);
|
|
611
237
|
let pathname = url.pathname;
|
|
@@ -640,7 +266,7 @@ function createAdapter(options) {
|
|
|
640
266
|
return await handler(ctx);
|
|
641
267
|
} else {
|
|
642
268
|
const pageModule = module;
|
|
643
|
-
const program = pageModule.default;
|
|
269
|
+
const program = await resolvePageExport(pageModule.default, matchedParams, matchedRoute.params);
|
|
644
270
|
const content = await renderPage(program, {
|
|
645
271
|
url: request.url,
|
|
646
272
|
params: matchedParams,
|
|
@@ -657,9 +283,448 @@ function createAdapter(options) {
|
|
|
657
283
|
return createErrorResponse(error);
|
|
658
284
|
}
|
|
659
285
|
}
|
|
660
|
-
return { fetch };
|
|
286
|
+
return { fetch: fetch2 };
|
|
661
287
|
}
|
|
288
|
+
|
|
289
|
+
// src/layout/resolver.ts
|
|
290
|
+
import { existsSync, statSync } from "fs";
|
|
291
|
+
import { join as join2, basename } from "path";
|
|
292
|
+
import fg from "fast-glob";
|
|
293
|
+
import { isLayoutProgram } from "@constela/core";
|
|
294
|
+
async function scanLayouts(layoutsDir) {
|
|
295
|
+
if (!existsSync(layoutsDir)) {
|
|
296
|
+
throw new Error(`Layouts directory does not exist: ${layoutsDir}`);
|
|
297
|
+
}
|
|
298
|
+
const stat = statSync(layoutsDir);
|
|
299
|
+
if (!stat.isDirectory()) {
|
|
300
|
+
throw new Error(`Path is not a directory: ${layoutsDir}`);
|
|
301
|
+
}
|
|
302
|
+
const files = await fg(["**/*.ts", "**/*.tsx"], {
|
|
303
|
+
cwd: layoutsDir,
|
|
304
|
+
ignore: ["**/_*", "**/*.d.ts"]
|
|
305
|
+
});
|
|
306
|
+
const layouts = files.filter((file) => {
|
|
307
|
+
const name = basename(file);
|
|
308
|
+
if (name.startsWith("_")) return false;
|
|
309
|
+
if (name.endsWith(".d.ts")) return false;
|
|
310
|
+
if (!name.endsWith(".ts") && !name.endsWith(".tsx")) return false;
|
|
311
|
+
return true;
|
|
312
|
+
}).map((file) => {
|
|
313
|
+
const name = basename(file).replace(/\.(tsx?|ts)$/, "");
|
|
314
|
+
return {
|
|
315
|
+
name,
|
|
316
|
+
file: join2(layoutsDir, file)
|
|
317
|
+
};
|
|
318
|
+
});
|
|
319
|
+
return layouts;
|
|
320
|
+
}
|
|
321
|
+
function resolveLayout(layoutName, layouts) {
|
|
322
|
+
return layouts.find((l) => l.name === layoutName);
|
|
323
|
+
}
|
|
324
|
+
async function loadLayout(layoutFile) {
|
|
325
|
+
try {
|
|
326
|
+
const module = await import(layoutFile);
|
|
327
|
+
const exported = module.default || module;
|
|
328
|
+
if (!isLayoutProgram(exported)) {
|
|
329
|
+
throw new Error(`File is not a valid layout: ${layoutFile}`);
|
|
330
|
+
}
|
|
331
|
+
return exported;
|
|
332
|
+
} catch (error) {
|
|
333
|
+
if (error instanceof Error && error.message.includes("not a valid layout")) {
|
|
334
|
+
throw error;
|
|
335
|
+
}
|
|
336
|
+
throw new Error(`Failed to load layout: ${layoutFile}`);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
var LayoutResolver = class {
|
|
340
|
+
layoutsDir;
|
|
341
|
+
layouts = [];
|
|
342
|
+
loadedLayouts = /* @__PURE__ */ new Map();
|
|
343
|
+
initialized = false;
|
|
344
|
+
constructor(layoutsDir) {
|
|
345
|
+
this.layoutsDir = layoutsDir;
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Initialize the resolver by scanning the layouts directory
|
|
349
|
+
*/
|
|
350
|
+
async initialize() {
|
|
351
|
+
try {
|
|
352
|
+
this.layouts = await scanLayouts(this.layoutsDir);
|
|
353
|
+
this.initialized = true;
|
|
354
|
+
} catch {
|
|
355
|
+
this.layouts = [];
|
|
356
|
+
this.initialized = true;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Check if a layout exists
|
|
361
|
+
*/
|
|
362
|
+
hasLayout(name) {
|
|
363
|
+
return this.layouts.some((l) => l.name === name);
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Get a layout by name
|
|
367
|
+
*
|
|
368
|
+
* @param name - Layout name
|
|
369
|
+
* @returns The layout program or undefined if not found
|
|
370
|
+
*/
|
|
371
|
+
async getLayout(name) {
|
|
372
|
+
const cached = this.loadedLayouts.get(name);
|
|
373
|
+
if (cached) {
|
|
374
|
+
return cached;
|
|
375
|
+
}
|
|
376
|
+
const scanned = resolveLayout(name, this.layouts);
|
|
377
|
+
if (!scanned) {
|
|
378
|
+
return void 0;
|
|
379
|
+
}
|
|
380
|
+
const layout = await loadLayout(scanned.file);
|
|
381
|
+
this.loadedLayouts.set(name, layout);
|
|
382
|
+
return layout;
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Compose a page with its layout
|
|
386
|
+
*
|
|
387
|
+
* @param page - Page program to compose
|
|
388
|
+
* @returns Composed program (or original if no layout)
|
|
389
|
+
* @throws Error if specified layout is not found
|
|
390
|
+
*/
|
|
391
|
+
async composeWithLayout(page) {
|
|
392
|
+
const layoutName = page.route?.layout;
|
|
393
|
+
if (!layoutName) {
|
|
394
|
+
return page;
|
|
395
|
+
}
|
|
396
|
+
if (!this.hasLayout(layoutName)) {
|
|
397
|
+
const available = this.layouts.map((l) => l.name).join(", ");
|
|
398
|
+
throw new Error(
|
|
399
|
+
`Layout '${layoutName}' not found. Available layouts: ${available || "none"}`
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
const layout = await this.getLayout(layoutName);
|
|
403
|
+
if (!layout) {
|
|
404
|
+
throw new Error(`Layout '${layoutName}' not found`);
|
|
405
|
+
}
|
|
406
|
+
return page;
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Get all scanned layouts
|
|
410
|
+
*/
|
|
411
|
+
getAll() {
|
|
412
|
+
return [...this.layouts];
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
// src/data/loader.ts
|
|
417
|
+
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
418
|
+
import { join as join3 } from "path";
|
|
419
|
+
import fg2 from "fast-glob";
|
|
420
|
+
function parseYaml(content) {
|
|
421
|
+
const result = {};
|
|
422
|
+
const lines = content.split("\n");
|
|
423
|
+
const stack = [{ indent: -2, obj: result }];
|
|
424
|
+
for (let i = 0; i < lines.length; i++) {
|
|
425
|
+
const line = lines[i];
|
|
426
|
+
if (!line || line.trim() === "" || line.trim().startsWith("#")) continue;
|
|
427
|
+
const arrayMatch = line.match(/^(\s*)-\s*(.*)$/);
|
|
428
|
+
if (arrayMatch) {
|
|
429
|
+
const [, indentStr2, rest] = arrayMatch;
|
|
430
|
+
const indent2 = indentStr2?.length ?? 0;
|
|
431
|
+
while (stack.length > 1 && indent2 <= stack[stack.length - 1].indent) {
|
|
432
|
+
stack.pop();
|
|
433
|
+
}
|
|
434
|
+
const parent = stack[stack.length - 1];
|
|
435
|
+
const key2 = parent.key;
|
|
436
|
+
if (key2) {
|
|
437
|
+
if (!Array.isArray(parent.obj[key2])) {
|
|
438
|
+
parent.obj[key2] = [];
|
|
439
|
+
}
|
|
440
|
+
const arr = parent.obj[key2];
|
|
441
|
+
const objMatch = rest?.match(/^([\w-]+):\s*(.*)$/);
|
|
442
|
+
if (objMatch) {
|
|
443
|
+
const [, k, v] = objMatch;
|
|
444
|
+
const newObj = {};
|
|
445
|
+
if (v?.trim()) {
|
|
446
|
+
newObj[k] = parseValue(v);
|
|
447
|
+
}
|
|
448
|
+
arr.push(newObj);
|
|
449
|
+
stack.push({ indent: indent2, obj: newObj, key: k, isArray: true });
|
|
450
|
+
} else if (rest?.trim()) {
|
|
451
|
+
arr.push(parseValue(rest.trim()));
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
const match = line.match(/^(\s*)([\w-]+):\s*(.*)$/);
|
|
457
|
+
if (!match) continue;
|
|
458
|
+
const [, indentStr, key, value] = match;
|
|
459
|
+
const indent = indentStr?.length ?? 0;
|
|
460
|
+
while (stack.length > 1 && indent <= stack[stack.length - 1].indent) {
|
|
461
|
+
stack.pop();
|
|
462
|
+
}
|
|
463
|
+
let targetObj;
|
|
464
|
+
const currentTop = stack[stack.length - 1];
|
|
465
|
+
if (currentTop.isArray) {
|
|
466
|
+
targetObj = currentTop.obj;
|
|
467
|
+
} else if (currentTop.key) {
|
|
468
|
+
if (!currentTop.obj[currentTop.key]) {
|
|
469
|
+
currentTop.obj[currentTop.key] = {};
|
|
470
|
+
}
|
|
471
|
+
targetObj = currentTop.obj[currentTop.key];
|
|
472
|
+
} else {
|
|
473
|
+
targetObj = currentTop.obj;
|
|
474
|
+
}
|
|
475
|
+
if (value?.trim() === "" || value === void 0) {
|
|
476
|
+
const newObj = {};
|
|
477
|
+
targetObj[key] = newObj;
|
|
478
|
+
stack.push({ indent, obj: targetObj, key });
|
|
479
|
+
} else {
|
|
480
|
+
targetObj[key] = parseValue(value);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
return result;
|
|
484
|
+
}
|
|
485
|
+
function parseValue(value) {
|
|
486
|
+
const trimmed = value.trim();
|
|
487
|
+
if (trimmed === "true") return true;
|
|
488
|
+
if (trimmed === "false") return false;
|
|
489
|
+
if (trimmed === "null" || trimmed === "~") return null;
|
|
490
|
+
if (/^-?\d+$/.test(trimmed)) return parseInt(trimmed, 10);
|
|
491
|
+
if (/^-?\d+\.\d+$/.test(trimmed)) return parseFloat(trimmed);
|
|
492
|
+
if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
|
|
493
|
+
return trimmed.slice(1, -1);
|
|
494
|
+
}
|
|
495
|
+
return trimmed;
|
|
496
|
+
}
|
|
497
|
+
function transformMdx(content) {
|
|
498
|
+
const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
499
|
+
if (!match) {
|
|
500
|
+
return { frontmatter: {}, content: content.trim() };
|
|
501
|
+
}
|
|
502
|
+
const frontmatter = parseYaml(match[1]);
|
|
503
|
+
return { frontmatter, content: match[2].trim() };
|
|
504
|
+
}
|
|
505
|
+
function transformYaml(content) {
|
|
506
|
+
return parseYaml(content);
|
|
507
|
+
}
|
|
508
|
+
function transformCsv(content) {
|
|
509
|
+
const lines = content.trim().split("\n");
|
|
510
|
+
if (lines.length === 0) return [];
|
|
511
|
+
const headerLine = lines[0];
|
|
512
|
+
const headers = parseCSVLine(headerLine);
|
|
513
|
+
const result = [];
|
|
514
|
+
for (let i = 1; i < lines.length; i++) {
|
|
515
|
+
const line = lines[i];
|
|
516
|
+
if (line.trim() === "") continue;
|
|
517
|
+
const values = parseCSVLine(line);
|
|
518
|
+
const row = {};
|
|
519
|
+
for (let j = 0; j < headers.length; j++) {
|
|
520
|
+
row[headers[j].trim()] = (values[j] ?? "").trim();
|
|
521
|
+
}
|
|
522
|
+
result.push(row);
|
|
523
|
+
}
|
|
524
|
+
return result;
|
|
525
|
+
}
|
|
526
|
+
function parseCSVLine(line) {
|
|
527
|
+
const result = [];
|
|
528
|
+
let current = "";
|
|
529
|
+
let inQuotes = false;
|
|
530
|
+
for (let i = 0; i < line.length; i++) {
|
|
531
|
+
const char = line[i];
|
|
532
|
+
if (char === '"') {
|
|
533
|
+
inQuotes = !inQuotes;
|
|
534
|
+
} else if (char === "," && !inQuotes) {
|
|
535
|
+
result.push(current);
|
|
536
|
+
current = "";
|
|
537
|
+
} else {
|
|
538
|
+
current += char;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
result.push(current);
|
|
542
|
+
return result;
|
|
543
|
+
}
|
|
544
|
+
function applyTransform(content, transform, filename) {
|
|
545
|
+
if (!transform) {
|
|
546
|
+
if (filename.endsWith(".json")) {
|
|
547
|
+
return JSON.parse(content);
|
|
548
|
+
}
|
|
549
|
+
return content;
|
|
550
|
+
}
|
|
551
|
+
switch (transform) {
|
|
552
|
+
case "mdx":
|
|
553
|
+
return transformMdx(content);
|
|
554
|
+
case "yaml":
|
|
555
|
+
return transformYaml(content);
|
|
556
|
+
case "csv":
|
|
557
|
+
return transformCsv(content);
|
|
558
|
+
default:
|
|
559
|
+
return content;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
async function loadGlob(baseDir, pattern, transform) {
|
|
563
|
+
const files = await fg2(pattern, { cwd: baseDir });
|
|
564
|
+
const results = [];
|
|
565
|
+
for (const file of files) {
|
|
566
|
+
const fullPath = join3(baseDir, file);
|
|
567
|
+
const content = readFileSync(fullPath, "utf-8");
|
|
568
|
+
if (transform === "mdx") {
|
|
569
|
+
const transformed = transformMdx(content);
|
|
570
|
+
results.push({
|
|
571
|
+
file,
|
|
572
|
+
raw: content,
|
|
573
|
+
frontmatter: transformed.frontmatter,
|
|
574
|
+
content: transformed.content
|
|
575
|
+
});
|
|
576
|
+
} else {
|
|
577
|
+
results.push({
|
|
578
|
+
file,
|
|
579
|
+
raw: content
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
return results;
|
|
584
|
+
}
|
|
585
|
+
async function loadFile(baseDir, filePath, transform) {
|
|
586
|
+
const fullPath = join3(baseDir, filePath);
|
|
587
|
+
if (!existsSync2(fullPath)) {
|
|
588
|
+
throw new Error(`File not found: ${fullPath}`);
|
|
589
|
+
}
|
|
590
|
+
const content = readFileSync(fullPath, "utf-8");
|
|
591
|
+
return applyTransform(content, transform, filePath);
|
|
592
|
+
}
|
|
593
|
+
async function loadApi(url, transform) {
|
|
594
|
+
try {
|
|
595
|
+
const response = await fetch(url);
|
|
596
|
+
if (!response.ok) {
|
|
597
|
+
throw new Error(`api request failed: ${response.status} ${response.statusText}`);
|
|
598
|
+
}
|
|
599
|
+
if (transform === "csv") {
|
|
600
|
+
const text = await response.text();
|
|
601
|
+
return transformCsv(text);
|
|
602
|
+
}
|
|
603
|
+
return await response.json();
|
|
604
|
+
} catch (error) {
|
|
605
|
+
if (error instanceof Error && error.message.includes("api request failed")) {
|
|
606
|
+
throw error;
|
|
607
|
+
}
|
|
608
|
+
throw new Error(`Network error: ${error.message}`);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
function evaluateParamExpression(expr, item) {
|
|
612
|
+
switch (expr.expr) {
|
|
613
|
+
case "lit":
|
|
614
|
+
return String(expr.value);
|
|
615
|
+
case "var":
|
|
616
|
+
if (expr.name === "item") {
|
|
617
|
+
if (expr.path) {
|
|
618
|
+
return getNestedValue(item, expr.path);
|
|
619
|
+
}
|
|
620
|
+
return String(item);
|
|
621
|
+
}
|
|
622
|
+
return "";
|
|
623
|
+
case "get":
|
|
624
|
+
if (expr.base.expr === "var" && expr.base.name === "item") {
|
|
625
|
+
return getNestedValue(item, expr.path);
|
|
626
|
+
}
|
|
627
|
+
return "";
|
|
628
|
+
default:
|
|
629
|
+
return "";
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
function getNestedValue(obj, path) {
|
|
633
|
+
const parts = path.split(".");
|
|
634
|
+
let current = obj;
|
|
635
|
+
for (const part of parts) {
|
|
636
|
+
if (current === null || current === void 0) return "";
|
|
637
|
+
if (typeof current !== "object") return "";
|
|
638
|
+
current = current[part];
|
|
639
|
+
}
|
|
640
|
+
return current !== void 0 && current !== null ? String(current) : "";
|
|
641
|
+
}
|
|
642
|
+
async function generateStaticPaths(data, staticPathsDef) {
|
|
643
|
+
const paths = [];
|
|
644
|
+
for (const item of data) {
|
|
645
|
+
const params = {};
|
|
646
|
+
for (const [paramName, paramExpr] of Object.entries(staticPathsDef.params)) {
|
|
647
|
+
params[paramName] = evaluateParamExpression(paramExpr, item);
|
|
648
|
+
}
|
|
649
|
+
paths.push({ params, data: item });
|
|
650
|
+
}
|
|
651
|
+
return paths;
|
|
652
|
+
}
|
|
653
|
+
var DataLoader = class {
|
|
654
|
+
cache = /* @__PURE__ */ new Map();
|
|
655
|
+
projectRoot;
|
|
656
|
+
constructor(projectRoot) {
|
|
657
|
+
this.projectRoot = projectRoot;
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* Load a single data source
|
|
661
|
+
*/
|
|
662
|
+
async loadDataSource(name, dataSource) {
|
|
663
|
+
if (this.cache.has(name)) {
|
|
664
|
+
return this.cache.get(name);
|
|
665
|
+
}
|
|
666
|
+
let data;
|
|
667
|
+
switch (dataSource.type) {
|
|
668
|
+
case "glob":
|
|
669
|
+
if (!dataSource.pattern) {
|
|
670
|
+
throw new Error(`Glob data source '${name}' requires pattern`);
|
|
671
|
+
}
|
|
672
|
+
data = await loadGlob(this.projectRoot, dataSource.pattern, dataSource.transform);
|
|
673
|
+
break;
|
|
674
|
+
case "file":
|
|
675
|
+
if (!dataSource.path) {
|
|
676
|
+
throw new Error(`File data source '${name}' requires path`);
|
|
677
|
+
}
|
|
678
|
+
data = await loadFile(this.projectRoot, dataSource.path, dataSource.transform);
|
|
679
|
+
break;
|
|
680
|
+
case "api":
|
|
681
|
+
if (!dataSource.url) {
|
|
682
|
+
throw new Error(`API data source '${name}' requires url`);
|
|
683
|
+
}
|
|
684
|
+
data = await loadApi(dataSource.url, dataSource.transform);
|
|
685
|
+
break;
|
|
686
|
+
default:
|
|
687
|
+
throw new Error(`Unknown data source type: ${dataSource.type}`);
|
|
688
|
+
}
|
|
689
|
+
this.cache.set(name, data);
|
|
690
|
+
return data;
|
|
691
|
+
}
|
|
692
|
+
/**
|
|
693
|
+
* Load all data sources
|
|
694
|
+
*/
|
|
695
|
+
async loadAllDataSources(dataSources) {
|
|
696
|
+
const result = {};
|
|
697
|
+
for (const [name, source] of Object.entries(dataSources)) {
|
|
698
|
+
result[name] = await this.loadDataSource(name, source);
|
|
699
|
+
}
|
|
700
|
+
return result;
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* Clear cache for a specific data source or all caches
|
|
704
|
+
*/
|
|
705
|
+
clearCache(name) {
|
|
706
|
+
if (name) {
|
|
707
|
+
this.cache.delete(name);
|
|
708
|
+
} else {
|
|
709
|
+
this.cache.clear();
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
/**
|
|
713
|
+
* Clear all cache entries
|
|
714
|
+
*/
|
|
715
|
+
clearAllCache() {
|
|
716
|
+
this.cache.clear();
|
|
717
|
+
}
|
|
718
|
+
/**
|
|
719
|
+
* Get the current cache size
|
|
720
|
+
*/
|
|
721
|
+
getCacheSize() {
|
|
722
|
+
return this.cache.size;
|
|
723
|
+
}
|
|
724
|
+
};
|
|
662
725
|
export {
|
|
726
|
+
DataLoader,
|
|
727
|
+
LayoutResolver,
|
|
663
728
|
build,
|
|
664
729
|
createAPIHandler,
|
|
665
730
|
createAdapter,
|
|
@@ -667,8 +732,20 @@ export {
|
|
|
667
732
|
createMiddlewareChain,
|
|
668
733
|
filePathToPattern,
|
|
669
734
|
generateStaticPages,
|
|
735
|
+
generateStaticPaths,
|
|
670
736
|
getMimeType,
|
|
737
|
+
isPageExportFunction,
|
|
671
738
|
isPathSafe,
|
|
739
|
+
loadApi,
|
|
740
|
+
loadFile,
|
|
741
|
+
loadGlob,
|
|
742
|
+
loadLayout,
|
|
743
|
+
resolveLayout,
|
|
744
|
+
resolvePageExport,
|
|
672
745
|
resolveStaticFile,
|
|
673
|
-
|
|
746
|
+
scanLayouts,
|
|
747
|
+
scanRoutes,
|
|
748
|
+
transformCsv,
|
|
749
|
+
transformMdx,
|
|
750
|
+
transformYaml
|
|
674
751
|
};
|