@constela/start 1.0.0 → 1.2.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-6AN7W7KZ.js +2553 -0
- package/dist/chunk-PUTC5BCP.js +112 -0
- package/dist/cli/index.d.ts +9 -2
- package/dist/cli/index.js +98 -10
- package/dist/index.d.ts +24 -8
- package/dist/index.js +39 -783
- package/dist/runtime/entry-client.d.ts +10 -1
- package/dist/runtime/entry-client.js +19 -2
- package/dist/runtime/entry-server.d.ts +30 -5
- package/dist/runtime/entry-server.js +1 -1
- package/package.json +6 -5
- package/dist/chunk-JXIOHPG5.js +0 -399
- package/dist/chunk-QLDID7EZ.js +0 -49
package/dist/index.js
CHANGED
|
@@ -1,17 +1,32 @@
|
|
|
1
1
|
import {
|
|
2
|
+
DataLoader,
|
|
3
|
+
LayoutResolver,
|
|
2
4
|
build,
|
|
3
5
|
createDevServer,
|
|
4
6
|
filePathToPattern,
|
|
7
|
+
generateStaticPaths,
|
|
5
8
|
getMimeType,
|
|
6
9
|
isPathSafe,
|
|
10
|
+
loadApi,
|
|
11
|
+
loadComponentDefinitions,
|
|
12
|
+
loadFile,
|
|
13
|
+
loadGlob,
|
|
14
|
+
loadLayout,
|
|
15
|
+
mdxContentToNode,
|
|
16
|
+
mdxToConstela,
|
|
17
|
+
resolveLayout,
|
|
7
18
|
resolveStaticFile,
|
|
8
|
-
|
|
9
|
-
|
|
19
|
+
scanLayouts,
|
|
20
|
+
scanRoutes,
|
|
21
|
+
transformCsv,
|
|
22
|
+
transformMdx,
|
|
23
|
+
transformYaml
|
|
24
|
+
} from "./chunk-6AN7W7KZ.js";
|
|
10
25
|
import {
|
|
11
26
|
generateHydrationScript,
|
|
12
27
|
renderPage,
|
|
13
28
|
wrapHtml
|
|
14
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-PUTC5BCP.js";
|
|
15
30
|
|
|
16
31
|
// src/build/ssg.ts
|
|
17
32
|
import { mkdir, writeFile } from "fs/promises";
|
|
@@ -89,7 +104,12 @@ async function generateSinglePage(pattern, outDir, program, params = {}) {
|
|
|
89
104
|
query: new URLSearchParams()
|
|
90
105
|
};
|
|
91
106
|
const content = await renderPage(program, ctx);
|
|
92
|
-
const
|
|
107
|
+
const routeContext = {
|
|
108
|
+
params,
|
|
109
|
+
query: {},
|
|
110
|
+
path: pattern
|
|
111
|
+
};
|
|
112
|
+
const hydrationScript = generateHydrationScript(program, void 0, routeContext);
|
|
93
113
|
const html = wrapHtml(content, hydrationScript);
|
|
94
114
|
await writeFile(outputPath, html, "utf-8");
|
|
95
115
|
return outputPath;
|
|
@@ -109,6 +129,12 @@ async function generateStaticPages(routes, outDir, options = {}) {
|
|
|
109
129
|
}
|
|
110
130
|
for (const pathData of staticPaths.paths) {
|
|
111
131
|
const program = await resolvePageExport(pageExport, pathData.params, route.params);
|
|
132
|
+
if (pathData.data !== void 0) {
|
|
133
|
+
program.importData = {
|
|
134
|
+
...program.importData,
|
|
135
|
+
__pathData: pathData.data
|
|
136
|
+
};
|
|
137
|
+
}
|
|
112
138
|
const resolvedPattern = resolvePattern(route.pattern, pathData.params);
|
|
113
139
|
const filePath = await generateSinglePage(
|
|
114
140
|
resolvedPattern,
|
|
@@ -231,7 +257,7 @@ function createErrorResponse(error) {
|
|
|
231
257
|
}
|
|
232
258
|
function createAdapter(options) {
|
|
233
259
|
const { routes, loadModule = defaultLoadModule } = options;
|
|
234
|
-
async function
|
|
260
|
+
async function fetch(request) {
|
|
235
261
|
try {
|
|
236
262
|
const url = new URL(request.url);
|
|
237
263
|
let pathname = url.pathname;
|
|
@@ -272,7 +298,12 @@ function createAdapter(options) {
|
|
|
272
298
|
params: matchedParams,
|
|
273
299
|
query: url.searchParams
|
|
274
300
|
});
|
|
275
|
-
const
|
|
301
|
+
const routeContext = {
|
|
302
|
+
params: matchedParams,
|
|
303
|
+
query: Object.fromEntries(url.searchParams.entries()),
|
|
304
|
+
path: url.pathname
|
|
305
|
+
};
|
|
306
|
+
const hydrationScript = generateHydrationScript(program, void 0, routeContext);
|
|
276
307
|
const html = wrapHtml(content, hydrationScript);
|
|
277
308
|
return new Response(html, {
|
|
278
309
|
status: 200,
|
|
@@ -283,783 +314,8 @@ function createAdapter(options) {
|
|
|
283
314
|
return createErrorResponse(error);
|
|
284
315
|
}
|
|
285
316
|
}
|
|
286
|
-
return { fetch
|
|
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
|
-
}
|
|
317
|
+
return { fetch };
|
|
338
318
|
}
|
|
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 { basename as basename2, extname, join as join3 } from "path";
|
|
419
|
-
import fg2 from "fast-glob";
|
|
420
|
-
|
|
421
|
-
// src/build/mdx.ts
|
|
422
|
-
import { unified } from "unified";
|
|
423
|
-
import remarkParse from "remark-parse";
|
|
424
|
-
import remarkMdx from "remark-mdx";
|
|
425
|
-
import remarkGfm from "remark-gfm";
|
|
426
|
-
import matter from "gray-matter";
|
|
427
|
-
function lit(value) {
|
|
428
|
-
return { expr: "lit", value };
|
|
429
|
-
}
|
|
430
|
-
function textNode(value) {
|
|
431
|
-
return { kind: "text", value: lit(value) };
|
|
432
|
-
}
|
|
433
|
-
function elementNode(tag, props, children) {
|
|
434
|
-
const node = { kind: "element", tag };
|
|
435
|
-
if (props && Object.keys(props).length > 0) {
|
|
436
|
-
node.props = props;
|
|
437
|
-
}
|
|
438
|
-
if (children && children.length > 0) {
|
|
439
|
-
node.children = children;
|
|
440
|
-
}
|
|
441
|
-
return node;
|
|
442
|
-
}
|
|
443
|
-
function codeNode(language, content) {
|
|
444
|
-
return {
|
|
445
|
-
kind: "code",
|
|
446
|
-
language: lit(language),
|
|
447
|
-
content: lit(content)
|
|
448
|
-
};
|
|
449
|
-
}
|
|
450
|
-
function wrapNodes(nodes) {
|
|
451
|
-
if (nodes.length === 0) {
|
|
452
|
-
return elementNode("div");
|
|
453
|
-
}
|
|
454
|
-
if (nodes.length === 1 && nodes[0]) {
|
|
455
|
-
return nodes[0];
|
|
456
|
-
}
|
|
457
|
-
return elementNode("div", void 0, nodes);
|
|
458
|
-
}
|
|
459
|
-
function isCustomComponent(name) {
|
|
460
|
-
if (!name) return false;
|
|
461
|
-
return /^[A-Z]/.test(name);
|
|
462
|
-
}
|
|
463
|
-
function parseAttributeValue(attr) {
|
|
464
|
-
if (attr.value === null) {
|
|
465
|
-
return lit(true);
|
|
466
|
-
}
|
|
467
|
-
if (typeof attr.value === "string") {
|
|
468
|
-
return lit(attr.value);
|
|
469
|
-
}
|
|
470
|
-
if (attr.value.type === "mdxJsxAttributeValueExpression") {
|
|
471
|
-
const exprValue = attr.value.value.trim();
|
|
472
|
-
if (exprValue === "true") return lit(true);
|
|
473
|
-
if (exprValue === "false") return lit(false);
|
|
474
|
-
if (exprValue === "null") return lit(null);
|
|
475
|
-
const num = Number(exprValue);
|
|
476
|
-
if (!Number.isNaN(num)) return lit(num);
|
|
477
|
-
return lit(exprValue);
|
|
478
|
-
}
|
|
479
|
-
return lit(null);
|
|
480
|
-
}
|
|
481
|
-
function transformNode(node, ctx) {
|
|
482
|
-
switch (node.type) {
|
|
483
|
-
case "heading":
|
|
484
|
-
return elementNode(
|
|
485
|
-
`h${node.depth}`,
|
|
486
|
-
void 0,
|
|
487
|
-
transformChildren(node.children, ctx)
|
|
488
|
-
);
|
|
489
|
-
case "paragraph":
|
|
490
|
-
return elementNode("p", void 0, transformChildren(node.children, ctx));
|
|
491
|
-
case "text":
|
|
492
|
-
return textNode(node.value);
|
|
493
|
-
case "emphasis":
|
|
494
|
-
return elementNode("em", void 0, transformChildren(node.children, ctx));
|
|
495
|
-
case "strong":
|
|
496
|
-
return elementNode("strong", void 0, transformChildren(node.children, ctx));
|
|
497
|
-
case "link": {
|
|
498
|
-
const props = {
|
|
499
|
-
href: lit(node.url)
|
|
500
|
-
};
|
|
501
|
-
if (node["title"]) {
|
|
502
|
-
props["title"] = lit(node["title"]);
|
|
503
|
-
}
|
|
504
|
-
return elementNode("a", props, transformChildren(node.children, ctx));
|
|
505
|
-
}
|
|
506
|
-
case "inlineCode":
|
|
507
|
-
return elementNode("code", void 0, [textNode(node.value)]);
|
|
508
|
-
case "code": {
|
|
509
|
-
const lang = node.lang || "text";
|
|
510
|
-
return codeNode(lang, node.value);
|
|
511
|
-
}
|
|
512
|
-
case "blockquote":
|
|
513
|
-
return elementNode("blockquote", void 0, transformChildren(node.children, ctx));
|
|
514
|
-
case "list": {
|
|
515
|
-
const tag = node.ordered ? "ol" : "ul";
|
|
516
|
-
return elementNode(tag, void 0, transformChildren(node.children, ctx));
|
|
517
|
-
}
|
|
518
|
-
case "listItem": {
|
|
519
|
-
const children = [];
|
|
520
|
-
for (const child of node.children) {
|
|
521
|
-
if (child.type === "paragraph") {
|
|
522
|
-
children.push(...transformChildren(child.children, ctx));
|
|
523
|
-
} else {
|
|
524
|
-
const transformed = transformNode(child, ctx);
|
|
525
|
-
if (transformed) {
|
|
526
|
-
if (Array.isArray(transformed)) {
|
|
527
|
-
children.push(...transformed);
|
|
528
|
-
} else {
|
|
529
|
-
children.push(transformed);
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
return elementNode("li", void 0, children);
|
|
535
|
-
}
|
|
536
|
-
case "thematicBreak":
|
|
537
|
-
return elementNode("hr");
|
|
538
|
-
case "break":
|
|
539
|
-
return elementNode("br");
|
|
540
|
-
case "image": {
|
|
541
|
-
const props = {
|
|
542
|
-
src: lit(node.url)
|
|
543
|
-
};
|
|
544
|
-
if (node["alt"]) {
|
|
545
|
-
props["alt"] = lit(node["alt"]);
|
|
546
|
-
}
|
|
547
|
-
if (node["title"]) {
|
|
548
|
-
props["title"] = lit(node["title"]);
|
|
549
|
-
}
|
|
550
|
-
return elementNode("img", props);
|
|
551
|
-
}
|
|
552
|
-
case "html":
|
|
553
|
-
return textNode(node.value);
|
|
554
|
-
// MDX JSX elements
|
|
555
|
-
case "mdxJsxFlowElement":
|
|
556
|
-
case "mdxJsxTextElement":
|
|
557
|
-
return transformJsxElement(node, ctx);
|
|
558
|
-
// MDX expressions
|
|
559
|
-
case "mdxFlowExpression":
|
|
560
|
-
case "mdxTextExpression": {
|
|
561
|
-
const exprNode = node;
|
|
562
|
-
const value = exprNode.value.trim();
|
|
563
|
-
if (value === "") return null;
|
|
564
|
-
if (value === "true") return textNode("true");
|
|
565
|
-
if (value === "false") return textNode("false");
|
|
566
|
-
if (value === "null") return textNode("null");
|
|
567
|
-
const num = Number(value);
|
|
568
|
-
if (!Number.isNaN(num)) return textNode(String(num));
|
|
569
|
-
return textNode(value);
|
|
570
|
-
}
|
|
571
|
-
// GFM extensions
|
|
572
|
-
case "table":
|
|
573
|
-
return elementNode("table", void 0, transformChildren(node.children, ctx));
|
|
574
|
-
case "tableRow":
|
|
575
|
-
return elementNode("tr", void 0, transformChildren(node.children, ctx));
|
|
576
|
-
case "tableCell":
|
|
577
|
-
return elementNode("td", void 0, transformChildren(node.children, ctx));
|
|
578
|
-
case "delete":
|
|
579
|
-
return elementNode("del", void 0, transformChildren(node.children, ctx));
|
|
580
|
-
default:
|
|
581
|
-
return null;
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
function transformJsxElement(node, ctx) {
|
|
585
|
-
const name = node.name;
|
|
586
|
-
if (!name) {
|
|
587
|
-
const children2 = transformChildren(node.children, ctx);
|
|
588
|
-
return wrapNodes(children2);
|
|
589
|
-
}
|
|
590
|
-
if (isCustomComponent(name)) {
|
|
591
|
-
const def = ctx.components[name];
|
|
592
|
-
if (!def) {
|
|
593
|
-
throw new Error(`Undefined component: ${name}`);
|
|
594
|
-
}
|
|
595
|
-
const props2 = {};
|
|
596
|
-
for (const attr of node.attributes) {
|
|
597
|
-
if (attr.type === "mdxJsxAttribute") {
|
|
598
|
-
props2[attr.name] = parseAttributeValue(attr);
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
const children2 = transformChildren(node.children, ctx);
|
|
602
|
-
return applyComponentView(def.view, props2, children2);
|
|
603
|
-
}
|
|
604
|
-
const props = {};
|
|
605
|
-
for (const attr of node.attributes) {
|
|
606
|
-
if (attr.type === "mdxJsxAttribute") {
|
|
607
|
-
props[attr.name] = parseAttributeValue(attr);
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
const children = transformChildren(node.children, ctx);
|
|
611
|
-
return elementNode(name, props, children);
|
|
612
|
-
}
|
|
613
|
-
function applyComponentView(view, props, children) {
|
|
614
|
-
return substituteInNode(view, props, children);
|
|
615
|
-
}
|
|
616
|
-
function substituteInNode(node, props, children) {
|
|
617
|
-
if (node.kind === "element") {
|
|
618
|
-
const elem = node;
|
|
619
|
-
const newProps = elem.props ? { ...elem.props } : {};
|
|
620
|
-
for (const [key, value] of Object.entries(props)) {
|
|
621
|
-
if (!(key in newProps)) {
|
|
622
|
-
newProps[key] = value;
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
let newChildren;
|
|
626
|
-
if (elem.children) {
|
|
627
|
-
newChildren = [];
|
|
628
|
-
for (const child of elem.children) {
|
|
629
|
-
if (child.kind === "slot") {
|
|
630
|
-
newChildren.push(...children);
|
|
631
|
-
} else {
|
|
632
|
-
newChildren.push(substituteInNode(child, props, children));
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
return elementNode(
|
|
637
|
-
elem.tag,
|
|
638
|
-
Object.keys(newProps).length > 0 ? newProps : void 0,
|
|
639
|
-
newChildren && newChildren.length > 0 ? newChildren : void 0
|
|
640
|
-
);
|
|
641
|
-
}
|
|
642
|
-
return node;
|
|
643
|
-
}
|
|
644
|
-
function transformChildren(children, ctx) {
|
|
645
|
-
const result = [];
|
|
646
|
-
for (const child of children) {
|
|
647
|
-
const transformed = transformNode(child, ctx);
|
|
648
|
-
if (transformed) {
|
|
649
|
-
if (Array.isArray(transformed)) {
|
|
650
|
-
result.push(...transformed);
|
|
651
|
-
} else {
|
|
652
|
-
result.push(transformed);
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
return result;
|
|
657
|
-
}
|
|
658
|
-
function transformRoot(root, ctx) {
|
|
659
|
-
const nodes = transformChildren(root.children, ctx);
|
|
660
|
-
return wrapNodes(nodes);
|
|
661
|
-
}
|
|
662
|
-
async function mdxToConstela(source, options) {
|
|
663
|
-
const { content, data: _frontmatter } = matter(source);
|
|
664
|
-
const processor = unified().use(remarkParse).use(remarkGfm).use(remarkMdx);
|
|
665
|
-
const tree = processor.parse(content);
|
|
666
|
-
const ctx = {
|
|
667
|
-
components: options?.components ?? {}
|
|
668
|
-
};
|
|
669
|
-
const view = transformRoot(tree, ctx);
|
|
670
|
-
return {
|
|
671
|
-
version: "1.0",
|
|
672
|
-
state: {},
|
|
673
|
-
actions: {},
|
|
674
|
-
view
|
|
675
|
-
};
|
|
676
|
-
}
|
|
677
|
-
async function mdxContentToNode(content, options) {
|
|
678
|
-
const processor = unified().use(remarkParse).use(remarkGfm).use(remarkMdx);
|
|
679
|
-
const tree = processor.parse(content);
|
|
680
|
-
const ctx = {
|
|
681
|
-
components: options?.components ?? {}
|
|
682
|
-
};
|
|
683
|
-
return transformRoot(tree, ctx);
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
// src/data/loader.ts
|
|
687
|
-
var mdxContentToNode2 = mdxContentToNode;
|
|
688
|
-
function parseYaml(content) {
|
|
689
|
-
const result = {};
|
|
690
|
-
const lines = content.split("\n");
|
|
691
|
-
const stack = [{ indent: -2, obj: result }];
|
|
692
|
-
for (let i = 0; i < lines.length; i++) {
|
|
693
|
-
const line = lines[i];
|
|
694
|
-
if (!line || line.trim() === "" || line.trim().startsWith("#")) continue;
|
|
695
|
-
const arrayMatch = line.match(/^(\s*)-\s*(.*)$/);
|
|
696
|
-
if (arrayMatch) {
|
|
697
|
-
const [, indentStr2, rest] = arrayMatch;
|
|
698
|
-
const indent2 = indentStr2?.length ?? 0;
|
|
699
|
-
while (stack.length > 1 && indent2 <= stack[stack.length - 1].indent) {
|
|
700
|
-
stack.pop();
|
|
701
|
-
}
|
|
702
|
-
const parent = stack[stack.length - 1];
|
|
703
|
-
const key2 = parent.key;
|
|
704
|
-
if (key2) {
|
|
705
|
-
if (!Array.isArray(parent.obj[key2])) {
|
|
706
|
-
parent.obj[key2] = [];
|
|
707
|
-
}
|
|
708
|
-
const arr = parent.obj[key2];
|
|
709
|
-
const objMatch = rest?.match(/^([\w-]+):\s*(.*)$/);
|
|
710
|
-
if (objMatch) {
|
|
711
|
-
const [, k, v] = objMatch;
|
|
712
|
-
const newObj = {};
|
|
713
|
-
if (v?.trim()) {
|
|
714
|
-
newObj[k] = parseValue(v);
|
|
715
|
-
}
|
|
716
|
-
arr.push(newObj);
|
|
717
|
-
stack.push({ indent: indent2, obj: newObj, key: k, isArray: true });
|
|
718
|
-
} else if (rest?.trim()) {
|
|
719
|
-
arr.push(parseValue(rest.trim()));
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
continue;
|
|
723
|
-
}
|
|
724
|
-
const match = line.match(/^(\s*)([\w-]+):\s*(.*)$/);
|
|
725
|
-
if (!match) continue;
|
|
726
|
-
const [, indentStr, key, value] = match;
|
|
727
|
-
const indent = indentStr?.length ?? 0;
|
|
728
|
-
while (stack.length > 1 && indent <= stack[stack.length - 1].indent) {
|
|
729
|
-
stack.pop();
|
|
730
|
-
}
|
|
731
|
-
let targetObj;
|
|
732
|
-
const currentTop = stack[stack.length - 1];
|
|
733
|
-
if (currentTop.isArray) {
|
|
734
|
-
targetObj = currentTop.obj;
|
|
735
|
-
} else if (currentTop.key) {
|
|
736
|
-
if (!currentTop.obj[currentTop.key]) {
|
|
737
|
-
currentTop.obj[currentTop.key] = {};
|
|
738
|
-
}
|
|
739
|
-
targetObj = currentTop.obj[currentTop.key];
|
|
740
|
-
} else {
|
|
741
|
-
targetObj = currentTop.obj;
|
|
742
|
-
}
|
|
743
|
-
if (value?.trim() === "" || value === void 0) {
|
|
744
|
-
const newObj = {};
|
|
745
|
-
targetObj[key] = newObj;
|
|
746
|
-
stack.push({ indent, obj: targetObj, key });
|
|
747
|
-
} else {
|
|
748
|
-
targetObj[key] = parseValue(value);
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
return result;
|
|
752
|
-
}
|
|
753
|
-
function parseValue(value) {
|
|
754
|
-
const trimmed = value.trim();
|
|
755
|
-
if (trimmed === "true") return true;
|
|
756
|
-
if (trimmed === "false") return false;
|
|
757
|
-
if (trimmed === "null" || trimmed === "~") return null;
|
|
758
|
-
if (/^-?\d+$/.test(trimmed)) return parseInt(trimmed, 10);
|
|
759
|
-
if (/^-?\d+\.\d+$/.test(trimmed)) return parseFloat(trimmed);
|
|
760
|
-
if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
|
|
761
|
-
return trimmed.slice(1, -1);
|
|
762
|
-
}
|
|
763
|
-
return trimmed;
|
|
764
|
-
}
|
|
765
|
-
function loadComponentDefinitions(baseDir, componentsPath) {
|
|
766
|
-
const fullPath = join3(baseDir, componentsPath);
|
|
767
|
-
const resolvedBase = join3(baseDir, "");
|
|
768
|
-
const resolvedPath = join3(fullPath, "");
|
|
769
|
-
if (!resolvedPath.startsWith(resolvedBase)) {
|
|
770
|
-
throw new Error(`Invalid component path: path traversal detected`);
|
|
771
|
-
}
|
|
772
|
-
if (!existsSync2(fullPath)) {
|
|
773
|
-
throw new Error(`MDX components file not found: ${fullPath}`);
|
|
774
|
-
}
|
|
775
|
-
const content = readFileSync(fullPath, "utf-8");
|
|
776
|
-
try {
|
|
777
|
-
return JSON.parse(content);
|
|
778
|
-
} catch {
|
|
779
|
-
throw new Error(`Invalid JSON in MDX components file: ${fullPath}`);
|
|
780
|
-
}
|
|
781
|
-
}
|
|
782
|
-
async function transformMdx(content, file, options) {
|
|
783
|
-
const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
784
|
-
let frontmatter = {};
|
|
785
|
-
let mdxContent;
|
|
786
|
-
if (match) {
|
|
787
|
-
frontmatter = parseYaml(match[1]);
|
|
788
|
-
mdxContent = match[2].trim();
|
|
789
|
-
} else {
|
|
790
|
-
mdxContent = content.trim();
|
|
791
|
-
}
|
|
792
|
-
const compiledContent = await mdxContentToNode(
|
|
793
|
-
mdxContent,
|
|
794
|
-
options?.components ? { components: options.components } : void 0
|
|
795
|
-
);
|
|
796
|
-
const fmSlug = frontmatter["slug"];
|
|
797
|
-
const slug = typeof fmSlug === "string" ? fmSlug : basename2(file, extname(file));
|
|
798
|
-
return {
|
|
799
|
-
file,
|
|
800
|
-
raw: content,
|
|
801
|
-
frontmatter,
|
|
802
|
-
content: compiledContent,
|
|
803
|
-
slug
|
|
804
|
-
};
|
|
805
|
-
}
|
|
806
|
-
function transformYaml(content) {
|
|
807
|
-
return parseYaml(content);
|
|
808
|
-
}
|
|
809
|
-
function transformCsv(content) {
|
|
810
|
-
const lines = content.trim().split("\n");
|
|
811
|
-
if (lines.length === 0) return [];
|
|
812
|
-
const headerLine = lines[0];
|
|
813
|
-
const headers = parseCSVLine(headerLine);
|
|
814
|
-
const result = [];
|
|
815
|
-
for (let i = 1; i < lines.length; i++) {
|
|
816
|
-
const line = lines[i];
|
|
817
|
-
if (line.trim() === "") continue;
|
|
818
|
-
const values = parseCSVLine(line);
|
|
819
|
-
const row = {};
|
|
820
|
-
for (let j = 0; j < headers.length; j++) {
|
|
821
|
-
row[headers[j].trim()] = (values[j] ?? "").trim();
|
|
822
|
-
}
|
|
823
|
-
result.push(row);
|
|
824
|
-
}
|
|
825
|
-
return result;
|
|
826
|
-
}
|
|
827
|
-
function parseCSVLine(line) {
|
|
828
|
-
const result = [];
|
|
829
|
-
let current = "";
|
|
830
|
-
let inQuotes = false;
|
|
831
|
-
for (let i = 0; i < line.length; i++) {
|
|
832
|
-
const char = line[i];
|
|
833
|
-
if (char === '"') {
|
|
834
|
-
inQuotes = !inQuotes;
|
|
835
|
-
} else if (char === "," && !inQuotes) {
|
|
836
|
-
result.push(current);
|
|
837
|
-
current = "";
|
|
838
|
-
} else {
|
|
839
|
-
current += char;
|
|
840
|
-
}
|
|
841
|
-
}
|
|
842
|
-
result.push(current);
|
|
843
|
-
return result;
|
|
844
|
-
}
|
|
845
|
-
function applyTransform(content, transform, filename) {
|
|
846
|
-
if (!transform) {
|
|
847
|
-
if (filename.endsWith(".json")) {
|
|
848
|
-
return JSON.parse(content);
|
|
849
|
-
}
|
|
850
|
-
return content;
|
|
851
|
-
}
|
|
852
|
-
switch (transform) {
|
|
853
|
-
case "mdx":
|
|
854
|
-
throw new Error("MDX transform for single files is not supported via loadFile. Use loadGlob instead.");
|
|
855
|
-
case "yaml":
|
|
856
|
-
return transformYaml(content);
|
|
857
|
-
case "csv":
|
|
858
|
-
return transformCsv(content);
|
|
859
|
-
default:
|
|
860
|
-
return content;
|
|
861
|
-
}
|
|
862
|
-
}
|
|
863
|
-
async function loadGlob(baseDir, pattern, transform, options) {
|
|
864
|
-
const files = await fg2(pattern, { cwd: baseDir });
|
|
865
|
-
if (transform === "mdx") {
|
|
866
|
-
const results2 = [];
|
|
867
|
-
for (const file of files) {
|
|
868
|
-
const fullPath = join3(baseDir, file);
|
|
869
|
-
const content = readFileSync(fullPath, "utf-8");
|
|
870
|
-
const transformed = await transformMdx(content, file, options);
|
|
871
|
-
results2.push(transformed);
|
|
872
|
-
}
|
|
873
|
-
return results2;
|
|
874
|
-
}
|
|
875
|
-
const results = [];
|
|
876
|
-
for (const file of files) {
|
|
877
|
-
const fullPath = join3(baseDir, file);
|
|
878
|
-
const content = readFileSync(fullPath, "utf-8");
|
|
879
|
-
results.push({
|
|
880
|
-
file,
|
|
881
|
-
raw: content
|
|
882
|
-
});
|
|
883
|
-
}
|
|
884
|
-
return results;
|
|
885
|
-
}
|
|
886
|
-
async function loadFile(baseDir, filePath, transform) {
|
|
887
|
-
const fullPath = join3(baseDir, filePath);
|
|
888
|
-
if (!existsSync2(fullPath)) {
|
|
889
|
-
throw new Error(`File not found: ${fullPath}`);
|
|
890
|
-
}
|
|
891
|
-
const content = readFileSync(fullPath, "utf-8");
|
|
892
|
-
return applyTransform(content, transform, filePath);
|
|
893
|
-
}
|
|
894
|
-
async function loadApi(url, transform) {
|
|
895
|
-
try {
|
|
896
|
-
const response = await fetch(url);
|
|
897
|
-
if (!response.ok) {
|
|
898
|
-
throw new Error(`api request failed: ${response.status} ${response.statusText}`);
|
|
899
|
-
}
|
|
900
|
-
if (transform === "csv") {
|
|
901
|
-
const text = await response.text();
|
|
902
|
-
return transformCsv(text);
|
|
903
|
-
}
|
|
904
|
-
return await response.json();
|
|
905
|
-
} catch (error) {
|
|
906
|
-
if (error instanceof Error && error.message.includes("api request failed")) {
|
|
907
|
-
throw error;
|
|
908
|
-
}
|
|
909
|
-
throw new Error(`Network error: ${error.message}`);
|
|
910
|
-
}
|
|
911
|
-
}
|
|
912
|
-
function evaluateParamExpression(expr, item) {
|
|
913
|
-
switch (expr.expr) {
|
|
914
|
-
case "lit":
|
|
915
|
-
return String(expr.value);
|
|
916
|
-
case "var":
|
|
917
|
-
if (expr.name === "item") {
|
|
918
|
-
if (expr.path) {
|
|
919
|
-
return getNestedValue(item, expr.path);
|
|
920
|
-
}
|
|
921
|
-
return String(item);
|
|
922
|
-
}
|
|
923
|
-
return "";
|
|
924
|
-
case "get":
|
|
925
|
-
if (expr.base.expr === "var" && expr.base.name === "item") {
|
|
926
|
-
return getNestedValue(item, expr.path);
|
|
927
|
-
}
|
|
928
|
-
return "";
|
|
929
|
-
default:
|
|
930
|
-
return "";
|
|
931
|
-
}
|
|
932
|
-
}
|
|
933
|
-
function getNestedValue(obj, path) {
|
|
934
|
-
const parts = path.split(".");
|
|
935
|
-
let current = obj;
|
|
936
|
-
for (const part of parts) {
|
|
937
|
-
if (current === null || current === void 0) return "";
|
|
938
|
-
if (typeof current !== "object") return "";
|
|
939
|
-
current = current[part];
|
|
940
|
-
}
|
|
941
|
-
return current !== void 0 && current !== null ? String(current) : "";
|
|
942
|
-
}
|
|
943
|
-
async function generateStaticPaths(data, staticPathsDef) {
|
|
944
|
-
const paths = [];
|
|
945
|
-
for (const item of data) {
|
|
946
|
-
const params = {};
|
|
947
|
-
for (const [paramName, paramExpr] of Object.entries(staticPathsDef.params)) {
|
|
948
|
-
params[paramName] = evaluateParamExpression(paramExpr, item);
|
|
949
|
-
}
|
|
950
|
-
paths.push({ params, data: item });
|
|
951
|
-
}
|
|
952
|
-
return paths;
|
|
953
|
-
}
|
|
954
|
-
var DataLoader = class {
|
|
955
|
-
cache = /* @__PURE__ */ new Map();
|
|
956
|
-
componentCache = /* @__PURE__ */ new Map();
|
|
957
|
-
projectRoot;
|
|
958
|
-
constructor(projectRoot) {
|
|
959
|
-
this.projectRoot = projectRoot;
|
|
960
|
-
}
|
|
961
|
-
/**
|
|
962
|
-
* Resolve components from string path or import reference
|
|
963
|
-
*/
|
|
964
|
-
resolveComponents(ref, imports) {
|
|
965
|
-
if (typeof ref === "string") {
|
|
966
|
-
if (this.componentCache.has(ref)) {
|
|
967
|
-
return this.componentCache.get(ref);
|
|
968
|
-
}
|
|
969
|
-
const defs = loadComponentDefinitions(this.projectRoot, ref);
|
|
970
|
-
this.componentCache.set(ref, defs);
|
|
971
|
-
return defs;
|
|
972
|
-
}
|
|
973
|
-
if (ref.expr === "import") {
|
|
974
|
-
if (!imports) {
|
|
975
|
-
throw new Error(`Import context required for component reference "${ref.name}"`);
|
|
976
|
-
}
|
|
977
|
-
const imported = imports[ref.name];
|
|
978
|
-
if (!imported || typeof imported !== "object") {
|
|
979
|
-
throw new Error(`Component import "${ref.name}" not found or invalid`);
|
|
980
|
-
}
|
|
981
|
-
return imported;
|
|
982
|
-
}
|
|
983
|
-
return {};
|
|
984
|
-
}
|
|
985
|
-
/**
|
|
986
|
-
* Load a single data source
|
|
987
|
-
*/
|
|
988
|
-
async loadDataSource(name, dataSource, context) {
|
|
989
|
-
if (this.cache.has(name)) {
|
|
990
|
-
return this.cache.get(name);
|
|
991
|
-
}
|
|
992
|
-
let componentDefs;
|
|
993
|
-
if (dataSource.transform === "mdx" && dataSource.components) {
|
|
994
|
-
componentDefs = this.resolveComponents(
|
|
995
|
-
dataSource.components,
|
|
996
|
-
context?.imports
|
|
997
|
-
);
|
|
998
|
-
}
|
|
999
|
-
let data;
|
|
1000
|
-
switch (dataSource.type) {
|
|
1001
|
-
case "glob":
|
|
1002
|
-
if (!dataSource.pattern) {
|
|
1003
|
-
throw new Error(`Glob data source '${name}' requires pattern`);
|
|
1004
|
-
}
|
|
1005
|
-
data = await loadGlob(
|
|
1006
|
-
this.projectRoot,
|
|
1007
|
-
dataSource.pattern,
|
|
1008
|
-
dataSource.transform,
|
|
1009
|
-
componentDefs ? { components: componentDefs } : void 0
|
|
1010
|
-
);
|
|
1011
|
-
break;
|
|
1012
|
-
case "file":
|
|
1013
|
-
if (!dataSource.path) {
|
|
1014
|
-
throw new Error(`File data source '${name}' requires path`);
|
|
1015
|
-
}
|
|
1016
|
-
data = await loadFile(this.projectRoot, dataSource.path, dataSource.transform);
|
|
1017
|
-
break;
|
|
1018
|
-
case "api":
|
|
1019
|
-
if (!dataSource.url) {
|
|
1020
|
-
throw new Error(`API data source '${name}' requires url`);
|
|
1021
|
-
}
|
|
1022
|
-
data = await loadApi(dataSource.url, dataSource.transform);
|
|
1023
|
-
break;
|
|
1024
|
-
default:
|
|
1025
|
-
throw new Error(`Unknown data source type: ${dataSource.type}`);
|
|
1026
|
-
}
|
|
1027
|
-
this.cache.set(name, data);
|
|
1028
|
-
return data;
|
|
1029
|
-
}
|
|
1030
|
-
/**
|
|
1031
|
-
* Load all data sources
|
|
1032
|
-
*/
|
|
1033
|
-
async loadAllDataSources(dataSources) {
|
|
1034
|
-
const result = {};
|
|
1035
|
-
for (const [name, source] of Object.entries(dataSources)) {
|
|
1036
|
-
result[name] = await this.loadDataSource(name, source);
|
|
1037
|
-
}
|
|
1038
|
-
return result;
|
|
1039
|
-
}
|
|
1040
|
-
/**
|
|
1041
|
-
* Clear cache for a specific data source or all caches
|
|
1042
|
-
*/
|
|
1043
|
-
clearCache(name) {
|
|
1044
|
-
if (name) {
|
|
1045
|
-
this.cache.delete(name);
|
|
1046
|
-
} else {
|
|
1047
|
-
this.cache.clear();
|
|
1048
|
-
}
|
|
1049
|
-
}
|
|
1050
|
-
/**
|
|
1051
|
-
* Clear all cache entries
|
|
1052
|
-
*/
|
|
1053
|
-
clearAllCache() {
|
|
1054
|
-
this.cache.clear();
|
|
1055
|
-
}
|
|
1056
|
-
/**
|
|
1057
|
-
* Get the current cache size
|
|
1058
|
-
*/
|
|
1059
|
-
getCacheSize() {
|
|
1060
|
-
return this.cache.size;
|
|
1061
|
-
}
|
|
1062
|
-
};
|
|
1063
319
|
export {
|
|
1064
320
|
DataLoader,
|
|
1065
321
|
LayoutResolver,
|
|
@@ -1079,7 +335,7 @@ export {
|
|
|
1079
335
|
loadFile,
|
|
1080
336
|
loadGlob,
|
|
1081
337
|
loadLayout,
|
|
1082
|
-
|
|
338
|
+
mdxContentToNode,
|
|
1083
339
|
mdxToConstela,
|
|
1084
340
|
resolveLayout,
|
|
1085
341
|
resolvePageExport,
|