@constela/start 0.4.1 → 1.1.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/index.d.ts +77 -9
- package/dist/index.js +497 -24
- package/package.json +14 -13
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CompiledProgram } from '@constela/compiler';
|
|
1
|
+
import { CompiledProgram, CompiledNode } from '@constela/compiler';
|
|
2
2
|
import { LayoutProgram, Program, StaticPathsDefinition, DataSource } from '@constela/core';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -357,6 +357,47 @@ declare class LayoutResolver {
|
|
|
357
357
|
getAll(): ScannedLayout[];
|
|
358
358
|
}
|
|
359
359
|
|
|
360
|
+
/**
|
|
361
|
+
* MDX to Constela AST Pipeline
|
|
362
|
+
*
|
|
363
|
+
* Transforms MDX content into CompiledProgram for Constela runtime.
|
|
364
|
+
* Uses unified/remark for parsing and gray-matter for frontmatter.
|
|
365
|
+
*/
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Component definition for custom MDX components
|
|
369
|
+
*/
|
|
370
|
+
interface ComponentDef$1 {
|
|
371
|
+
params?: Record<string, {
|
|
372
|
+
type: string;
|
|
373
|
+
required?: boolean;
|
|
374
|
+
}>;
|
|
375
|
+
view: CompiledNode;
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Options for MDX to Constela transformation
|
|
379
|
+
*/
|
|
380
|
+
interface MDXToConstelaOptions {
|
|
381
|
+
components?: Record<string, ComponentDef$1>;
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Transform MDX source to CompiledProgram
|
|
385
|
+
*
|
|
386
|
+
* @param source - MDX source string
|
|
387
|
+
* @param options - Transformation options
|
|
388
|
+
* @returns CompiledProgram
|
|
389
|
+
*/
|
|
390
|
+
declare function mdxToConstela(source: string, options?: MDXToConstelaOptions): Promise<CompiledProgram>;
|
|
391
|
+
/**
|
|
392
|
+
* Transform MDX content (without frontmatter) to CompiledNode
|
|
393
|
+
* For use when frontmatter has already been extracted
|
|
394
|
+
*
|
|
395
|
+
* @param content - MDX content string (frontmatter already removed)
|
|
396
|
+
* @param options - Transformation options
|
|
397
|
+
* @returns CompiledNode
|
|
398
|
+
*/
|
|
399
|
+
declare function mdxContentToNode$1(content: string, options?: MDXToConstelaOptions): Promise<CompiledNode>;
|
|
400
|
+
|
|
360
401
|
/**
|
|
361
402
|
* Data Loader for Build-time Content Loading
|
|
362
403
|
*
|
|
@@ -374,17 +415,35 @@ interface GlobResult {
|
|
|
374
415
|
frontmatter?: Record<string, unknown>;
|
|
375
416
|
content?: string;
|
|
376
417
|
}
|
|
418
|
+
interface MdxGlobResult {
|
|
419
|
+
file: string;
|
|
420
|
+
raw: string;
|
|
421
|
+
frontmatter: Record<string, unknown>;
|
|
422
|
+
content: CompiledNode;
|
|
423
|
+
slug: string;
|
|
424
|
+
}
|
|
425
|
+
interface ComponentDef {
|
|
426
|
+
params?: Record<string, {
|
|
427
|
+
type: string;
|
|
428
|
+
required?: boolean;
|
|
429
|
+
}>;
|
|
430
|
+
view: CompiledNode;
|
|
431
|
+
}
|
|
432
|
+
declare const mdxContentToNode: typeof mdxContentToNode$1;
|
|
377
433
|
interface StaticPath {
|
|
378
434
|
params: Record<string, string>;
|
|
379
435
|
data?: unknown;
|
|
380
436
|
}
|
|
381
437
|
/**
|
|
382
|
-
*
|
|
438
|
+
* Load component definitions from a JSON file
|
|
383
439
|
*/
|
|
384
|
-
declare function
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
440
|
+
declare function loadComponentDefinitions(baseDir: string, componentsPath: string): Record<string, ComponentDef>;
|
|
441
|
+
/**
|
|
442
|
+
* Transform MDX content - parse frontmatter and compile content to CompiledNode
|
|
443
|
+
*/
|
|
444
|
+
declare function transformMdx(content: string, file: string, options?: {
|
|
445
|
+
components?: Record<string, ComponentDef>;
|
|
446
|
+
}): Promise<MdxGlobResult>;
|
|
388
447
|
/**
|
|
389
448
|
* Transform YAML content
|
|
390
449
|
*/
|
|
@@ -396,7 +455,9 @@ declare function transformCsv(content: string): Record<string, string>[];
|
|
|
396
455
|
/**
|
|
397
456
|
* Load files matching a glob pattern
|
|
398
457
|
*/
|
|
399
|
-
declare function loadGlob(baseDir: string, pattern: string, transform?: string
|
|
458
|
+
declare function loadGlob(baseDir: string, pattern: string, transform?: string, options?: {
|
|
459
|
+
components?: Record<string, ComponentDef>;
|
|
460
|
+
}): Promise<GlobResult[] | MdxGlobResult[]>;
|
|
400
461
|
/**
|
|
401
462
|
* Load a single file
|
|
402
463
|
*/
|
|
@@ -414,12 +475,19 @@ declare function generateStaticPaths(data: unknown[], staticPathsDef: StaticPath
|
|
|
414
475
|
*/
|
|
415
476
|
declare class DataLoader {
|
|
416
477
|
private cache;
|
|
478
|
+
private componentCache;
|
|
417
479
|
private projectRoot;
|
|
418
480
|
constructor(projectRoot: string);
|
|
481
|
+
/**
|
|
482
|
+
* Resolve components from string path or import reference
|
|
483
|
+
*/
|
|
484
|
+
private resolveComponents;
|
|
419
485
|
/**
|
|
420
486
|
* Load a single data source
|
|
421
487
|
*/
|
|
422
|
-
loadDataSource(name: string, dataSource: DataSource
|
|
488
|
+
loadDataSource(name: string, dataSource: DataSource, context?: {
|
|
489
|
+
imports?: Record<string, unknown>;
|
|
490
|
+
}): Promise<unknown>;
|
|
423
491
|
/**
|
|
424
492
|
* Load all data sources
|
|
425
493
|
*/
|
|
@@ -438,4 +506,4 @@ declare class DataLoader {
|
|
|
438
506
|
getCacheSize(): number;
|
|
439
507
|
}
|
|
440
508
|
|
|
441
|
-
export { type APIContext, type APIModule, type BuildOptions, type ConstelaConfig, DataLoader, type DevServerOptions, type GenerateStaticPagesOptions, type GlobResult, type LayoutInfo, LayoutResolver, type Middleware, type MiddlewareContext, type MiddlewareNext, type PageExportFunction, type PageModule, type ScannedLayout, type ScannedRoute, type StaticFileResult, type StaticPath, type StaticPathsProvider, type StaticPathsResult, build, createAPIHandler, createAdapter, createDevServer, createMiddlewareChain, filePathToPattern, generateStaticPages, generateStaticPaths, getMimeType, isPageExportFunction, isPathSafe, loadApi, loadFile, loadGlob, loadLayout, resolveLayout, resolvePageExport, resolveStaticFile, scanLayouts, scanRoutes, transformCsv, transformMdx, transformYaml };
|
|
509
|
+
export { type APIContext, type APIModule, type BuildOptions, type ComponentDef$1 as ComponentDef, type ConstelaConfig, DataLoader, type DevServerOptions, type GenerateStaticPagesOptions, type GlobResult, type LayoutInfo, LayoutResolver, type MDXToConstelaOptions, type MdxGlobResult, type Middleware, type MiddlewareContext, type MiddlewareNext, type PageExportFunction, type PageModule, type ScannedLayout, type ScannedRoute, type StaticFileResult, type StaticPath, type StaticPathsProvider, type StaticPathsResult, build, createAPIHandler, createAdapter, createDevServer, createMiddlewareChain, filePathToPattern, generateStaticPages, generateStaticPaths, getMimeType, isPageExportFunction, isPathSafe, loadApi, loadComponentDefinitions, loadFile, loadGlob, loadLayout, mdxContentToNode, mdxToConstela, resolveLayout, resolvePageExport, resolveStaticFile, scanLayouts, scanRoutes, transformCsv, transformMdx, transformYaml };
|
package/dist/index.js
CHANGED
|
@@ -415,8 +415,408 @@ var LayoutResolver = class {
|
|
|
415
415
|
|
|
416
416
|
// src/data/loader.ts
|
|
417
417
|
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
418
|
-
import { join as join3 } from "path";
|
|
418
|
+
import { basename as basename2, extname, join as join3 } from "path";
|
|
419
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
|
+
var DISALLOWED_PATTERNS = [
|
|
464
|
+
/\bfunction\b/i,
|
|
465
|
+
/\b(eval|Function|setTimeout|setInterval)\b/,
|
|
466
|
+
/\bimport\b/,
|
|
467
|
+
/\brequire\b/,
|
|
468
|
+
/\bfetch\b/,
|
|
469
|
+
/\bwindow\b/,
|
|
470
|
+
/\bdocument\b/,
|
|
471
|
+
/\bglobal\b/,
|
|
472
|
+
/\bprocess\b/,
|
|
473
|
+
/\b__proto__\b/,
|
|
474
|
+
/\bconstructor\b/,
|
|
475
|
+
/\bprototype\b/
|
|
476
|
+
];
|
|
477
|
+
function isSafeLiteral(value) {
|
|
478
|
+
return !DISALLOWED_PATTERNS.some((pattern) => pattern.test(value));
|
|
479
|
+
}
|
|
480
|
+
function safeEvalLiteral(value) {
|
|
481
|
+
try {
|
|
482
|
+
return JSON.parse(value);
|
|
483
|
+
} catch {
|
|
484
|
+
}
|
|
485
|
+
if (!isSafeLiteral(value)) {
|
|
486
|
+
return null;
|
|
487
|
+
}
|
|
488
|
+
try {
|
|
489
|
+
const fn = new Function(`return (${value});`);
|
|
490
|
+
return fn();
|
|
491
|
+
} catch {
|
|
492
|
+
return null;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
function parseAttributeValue(attr) {
|
|
496
|
+
if (attr.value === null) {
|
|
497
|
+
return lit(true);
|
|
498
|
+
}
|
|
499
|
+
if (typeof attr.value === "string") {
|
|
500
|
+
return lit(attr.value);
|
|
501
|
+
}
|
|
502
|
+
if (attr.value.type === "mdxJsxAttributeValueExpression") {
|
|
503
|
+
const exprValue = attr.value.value.trim();
|
|
504
|
+
if (exprValue === "true") return lit(true);
|
|
505
|
+
if (exprValue === "false") return lit(false);
|
|
506
|
+
if (exprValue === "null") return lit(null);
|
|
507
|
+
const num = Number(exprValue);
|
|
508
|
+
if (!Number.isNaN(num)) return lit(num);
|
|
509
|
+
if (exprValue.startsWith("[") || exprValue.startsWith("{")) {
|
|
510
|
+
const parsed = safeEvalLiteral(exprValue);
|
|
511
|
+
if (parsed !== null && parsed !== void 0) {
|
|
512
|
+
return lit(parsed);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
return lit(exprValue);
|
|
516
|
+
}
|
|
517
|
+
return lit(null);
|
|
518
|
+
}
|
|
519
|
+
function transformNode(node, ctx) {
|
|
520
|
+
switch (node.type) {
|
|
521
|
+
case "heading":
|
|
522
|
+
return elementNode(
|
|
523
|
+
`h${node.depth}`,
|
|
524
|
+
void 0,
|
|
525
|
+
transformChildren(node.children, ctx)
|
|
526
|
+
);
|
|
527
|
+
case "paragraph":
|
|
528
|
+
return elementNode("p", void 0, transformChildren(node.children, ctx));
|
|
529
|
+
case "text":
|
|
530
|
+
return textNode(node.value);
|
|
531
|
+
case "emphasis":
|
|
532
|
+
return elementNode("em", void 0, transformChildren(node.children, ctx));
|
|
533
|
+
case "strong":
|
|
534
|
+
return elementNode("strong", void 0, transformChildren(node.children, ctx));
|
|
535
|
+
case "link": {
|
|
536
|
+
const props = {
|
|
537
|
+
href: lit(node.url)
|
|
538
|
+
};
|
|
539
|
+
if (node["title"]) {
|
|
540
|
+
props["title"] = lit(node["title"]);
|
|
541
|
+
}
|
|
542
|
+
return elementNode("a", props, transformChildren(node.children, ctx));
|
|
543
|
+
}
|
|
544
|
+
case "inlineCode":
|
|
545
|
+
return elementNode("code", void 0, [textNode(node.value)]);
|
|
546
|
+
case "code": {
|
|
547
|
+
const lang = node.lang || "text";
|
|
548
|
+
return codeNode(lang, node.value);
|
|
549
|
+
}
|
|
550
|
+
case "blockquote":
|
|
551
|
+
return elementNode("blockquote", void 0, transformChildren(node.children, ctx));
|
|
552
|
+
case "list": {
|
|
553
|
+
const tag = node.ordered ? "ol" : "ul";
|
|
554
|
+
return elementNode(tag, void 0, transformChildren(node.children, ctx));
|
|
555
|
+
}
|
|
556
|
+
case "listItem": {
|
|
557
|
+
const children = [];
|
|
558
|
+
for (const child of node.children) {
|
|
559
|
+
if (child.type === "paragraph") {
|
|
560
|
+
children.push(...transformChildren(child.children, ctx));
|
|
561
|
+
} else {
|
|
562
|
+
const transformed = transformNode(child, ctx);
|
|
563
|
+
if (transformed) {
|
|
564
|
+
if (Array.isArray(transformed)) {
|
|
565
|
+
children.push(...transformed);
|
|
566
|
+
} else {
|
|
567
|
+
children.push(transformed);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
return elementNode("li", void 0, children);
|
|
573
|
+
}
|
|
574
|
+
case "thematicBreak":
|
|
575
|
+
return elementNode("hr");
|
|
576
|
+
case "break":
|
|
577
|
+
return elementNode("br");
|
|
578
|
+
case "image": {
|
|
579
|
+
const props = {
|
|
580
|
+
src: lit(node.url)
|
|
581
|
+
};
|
|
582
|
+
if (node["alt"]) {
|
|
583
|
+
props["alt"] = lit(node["alt"]);
|
|
584
|
+
}
|
|
585
|
+
if (node["title"]) {
|
|
586
|
+
props["title"] = lit(node["title"]);
|
|
587
|
+
}
|
|
588
|
+
return elementNode("img", props);
|
|
589
|
+
}
|
|
590
|
+
case "html":
|
|
591
|
+
return textNode(node.value);
|
|
592
|
+
// MDX JSX elements
|
|
593
|
+
case "mdxJsxFlowElement":
|
|
594
|
+
case "mdxJsxTextElement":
|
|
595
|
+
return transformJsxElement(node, ctx);
|
|
596
|
+
// MDX expressions
|
|
597
|
+
case "mdxFlowExpression":
|
|
598
|
+
case "mdxTextExpression": {
|
|
599
|
+
const exprNode = node;
|
|
600
|
+
const value = exprNode.value.trim();
|
|
601
|
+
if (value === "") return null;
|
|
602
|
+
if (value === "true") return textNode("true");
|
|
603
|
+
if (value === "false") return textNode("false");
|
|
604
|
+
if (value === "null") return textNode("null");
|
|
605
|
+
const num = Number(value);
|
|
606
|
+
if (!Number.isNaN(num)) return textNode(String(num));
|
|
607
|
+
return textNode(value);
|
|
608
|
+
}
|
|
609
|
+
// GFM extensions
|
|
610
|
+
case "table":
|
|
611
|
+
return elementNode("table", void 0, transformChildren(node.children, ctx));
|
|
612
|
+
case "tableRow":
|
|
613
|
+
return elementNode("tr", void 0, transformChildren(node.children, ctx));
|
|
614
|
+
case "tableCell":
|
|
615
|
+
return elementNode("td", void 0, transformChildren(node.children, ctx));
|
|
616
|
+
case "delete":
|
|
617
|
+
return elementNode("del", void 0, transformChildren(node.children, ctx));
|
|
618
|
+
default:
|
|
619
|
+
return null;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
function transformJsxElement(node, ctx) {
|
|
623
|
+
const name = node.name;
|
|
624
|
+
if (!name) {
|
|
625
|
+
const children2 = transformChildren(node.children, ctx);
|
|
626
|
+
return wrapNodes(children2);
|
|
627
|
+
}
|
|
628
|
+
if (isCustomComponent(name)) {
|
|
629
|
+
const def = ctx.components[name];
|
|
630
|
+
if (!def) {
|
|
631
|
+
throw new Error(`Undefined component: ${name}`);
|
|
632
|
+
}
|
|
633
|
+
const props2 = {};
|
|
634
|
+
for (const attr of node.attributes) {
|
|
635
|
+
if (attr.type === "mdxJsxAttribute") {
|
|
636
|
+
props2[attr.name] = parseAttributeValue(attr);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
const children2 = transformChildren(node.children, ctx);
|
|
640
|
+
return applyComponentView(def.view, props2, children2);
|
|
641
|
+
}
|
|
642
|
+
const props = {};
|
|
643
|
+
for (const attr of node.attributes) {
|
|
644
|
+
if (attr.type === "mdxJsxAttribute") {
|
|
645
|
+
props[attr.name] = parseAttributeValue(attr);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
const children = transformChildren(node.children, ctx);
|
|
649
|
+
return elementNode(name, props, children);
|
|
650
|
+
}
|
|
651
|
+
function substituteExpression(expr, props) {
|
|
652
|
+
const exprAny = expr;
|
|
653
|
+
if (exprAny.expr === "param") {
|
|
654
|
+
const paramExpr = expr;
|
|
655
|
+
const propValue = props[paramExpr.name];
|
|
656
|
+
if (!propValue) {
|
|
657
|
+
return { expr: "lit", value: null };
|
|
658
|
+
}
|
|
659
|
+
if (paramExpr.path && propValue.expr === "var") {
|
|
660
|
+
const varExpr = propValue;
|
|
661
|
+
return {
|
|
662
|
+
expr: "var",
|
|
663
|
+
name: varExpr.name,
|
|
664
|
+
path: paramExpr.path
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
return propValue;
|
|
668
|
+
}
|
|
669
|
+
if (expr.expr === "bin") {
|
|
670
|
+
const binExpr = expr;
|
|
671
|
+
return {
|
|
672
|
+
expr: "bin",
|
|
673
|
+
op: binExpr.op,
|
|
674
|
+
left: substituteExpression(binExpr.left, props),
|
|
675
|
+
right: substituteExpression(binExpr.right, props)
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
if (expr.expr === "not") {
|
|
679
|
+
const notExpr = expr;
|
|
680
|
+
return {
|
|
681
|
+
expr: "not",
|
|
682
|
+
operand: substituteExpression(notExpr.operand, props)
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
if (expr.expr === "cond") {
|
|
686
|
+
const condExpr = expr;
|
|
687
|
+
return {
|
|
688
|
+
expr: "cond",
|
|
689
|
+
if: substituteExpression(condExpr.if, props),
|
|
690
|
+
then: substituteExpression(condExpr.then, props),
|
|
691
|
+
else: substituteExpression(condExpr.else, props)
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
if (expr.expr === "get") {
|
|
695
|
+
const getExpr = expr;
|
|
696
|
+
return {
|
|
697
|
+
expr: "get",
|
|
698
|
+
base: substituteExpression(getExpr.base, props),
|
|
699
|
+
path: getExpr.path
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
return expr;
|
|
703
|
+
}
|
|
704
|
+
function applyComponentView(view, props, children) {
|
|
705
|
+
return substituteInNode(view, props, children);
|
|
706
|
+
}
|
|
707
|
+
function substituteInNode(node, props, children) {
|
|
708
|
+
switch (node.kind) {
|
|
709
|
+
case "each": {
|
|
710
|
+
const eachNode = node;
|
|
711
|
+
const result = {
|
|
712
|
+
kind: "each",
|
|
713
|
+
items: substituteExpression(eachNode.items, props),
|
|
714
|
+
as: eachNode.as,
|
|
715
|
+
body: substituteInNode(eachNode.body, props, children)
|
|
716
|
+
};
|
|
717
|
+
if (eachNode.index) {
|
|
718
|
+
result.index = eachNode.index;
|
|
719
|
+
}
|
|
720
|
+
if (eachNode.key) {
|
|
721
|
+
result.key = substituteExpression(eachNode.key, props);
|
|
722
|
+
}
|
|
723
|
+
return result;
|
|
724
|
+
}
|
|
725
|
+
case "text": {
|
|
726
|
+
const textNode2 = node;
|
|
727
|
+
return {
|
|
728
|
+
kind: "text",
|
|
729
|
+
value: substituteExpression(textNode2.value, props)
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
case "if": {
|
|
733
|
+
const ifNode = node;
|
|
734
|
+
const result = {
|
|
735
|
+
kind: "if",
|
|
736
|
+
condition: substituteExpression(ifNode.condition, props),
|
|
737
|
+
then: substituteInNode(ifNode.then, props, children)
|
|
738
|
+
};
|
|
739
|
+
if (ifNode.else) {
|
|
740
|
+
result.else = substituteInNode(ifNode.else, props, children);
|
|
741
|
+
}
|
|
742
|
+
return result;
|
|
743
|
+
}
|
|
744
|
+
case "element": {
|
|
745
|
+
const elem = node;
|
|
746
|
+
const newProps = {};
|
|
747
|
+
if (elem.props) {
|
|
748
|
+
for (const [key, value] of Object.entries(elem.props)) {
|
|
749
|
+
newProps[key] = substituteExpression(value, props);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
let newChildren;
|
|
753
|
+
if (elem.children) {
|
|
754
|
+
newChildren = [];
|
|
755
|
+
for (const child of elem.children) {
|
|
756
|
+
if (child.kind === "slot") {
|
|
757
|
+
newChildren.push(...children);
|
|
758
|
+
} else {
|
|
759
|
+
newChildren.push(substituteInNode(child, props, children));
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
return elementNode(
|
|
764
|
+
elem.tag,
|
|
765
|
+
Object.keys(newProps).length > 0 ? newProps : void 0,
|
|
766
|
+
newChildren && newChildren.length > 0 ? newChildren : void 0
|
|
767
|
+
);
|
|
768
|
+
}
|
|
769
|
+
case "markdown":
|
|
770
|
+
case "code":
|
|
771
|
+
return node;
|
|
772
|
+
default:
|
|
773
|
+
return node;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
function transformChildren(children, ctx) {
|
|
777
|
+
const result = [];
|
|
778
|
+
for (const child of children) {
|
|
779
|
+
const transformed = transformNode(child, ctx);
|
|
780
|
+
if (transformed) {
|
|
781
|
+
if (Array.isArray(transformed)) {
|
|
782
|
+
result.push(...transformed);
|
|
783
|
+
} else {
|
|
784
|
+
result.push(transformed);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
return result;
|
|
789
|
+
}
|
|
790
|
+
function transformRoot(root, ctx) {
|
|
791
|
+
const nodes = transformChildren(root.children, ctx);
|
|
792
|
+
return wrapNodes(nodes);
|
|
793
|
+
}
|
|
794
|
+
async function mdxToConstela(source, options) {
|
|
795
|
+
const { content, data: _frontmatter } = matter(source);
|
|
796
|
+
const processor = unified().use(remarkParse).use(remarkGfm).use(remarkMdx);
|
|
797
|
+
const tree = processor.parse(content);
|
|
798
|
+
const ctx = {
|
|
799
|
+
components: options?.components ?? {}
|
|
800
|
+
};
|
|
801
|
+
const view = transformRoot(tree, ctx);
|
|
802
|
+
return {
|
|
803
|
+
version: "1.0",
|
|
804
|
+
state: {},
|
|
805
|
+
actions: {},
|
|
806
|
+
view
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
async function mdxContentToNode(content, options) {
|
|
810
|
+
const processor = unified().use(remarkParse).use(remarkGfm).use(remarkMdx);
|
|
811
|
+
const tree = processor.parse(content);
|
|
812
|
+
const ctx = {
|
|
813
|
+
components: options?.components ?? {}
|
|
814
|
+
};
|
|
815
|
+
return transformRoot(tree, ctx);
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
// src/data/loader.ts
|
|
819
|
+
var mdxContentToNode2 = mdxContentToNode;
|
|
420
820
|
function parseYaml(content) {
|
|
421
821
|
const result = {};
|
|
422
822
|
const lines = content.split("\n");
|
|
@@ -494,13 +894,46 @@ function parseValue(value) {
|
|
|
494
894
|
}
|
|
495
895
|
return trimmed;
|
|
496
896
|
}
|
|
497
|
-
function
|
|
897
|
+
function loadComponentDefinitions(baseDir, componentsPath) {
|
|
898
|
+
const fullPath = join3(baseDir, componentsPath);
|
|
899
|
+
const resolvedBase = join3(baseDir, "");
|
|
900
|
+
const resolvedPath = join3(fullPath, "");
|
|
901
|
+
if (!resolvedPath.startsWith(resolvedBase)) {
|
|
902
|
+
throw new Error(`Invalid component path: path traversal detected`);
|
|
903
|
+
}
|
|
904
|
+
if (!existsSync2(fullPath)) {
|
|
905
|
+
throw new Error(`MDX components file not found: ${fullPath}`);
|
|
906
|
+
}
|
|
907
|
+
const content = readFileSync(fullPath, "utf-8");
|
|
908
|
+
try {
|
|
909
|
+
return JSON.parse(content);
|
|
910
|
+
} catch {
|
|
911
|
+
throw new Error(`Invalid JSON in MDX components file: ${fullPath}`);
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
async function transformMdx(content, file, options) {
|
|
498
915
|
const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
499
|
-
|
|
500
|
-
|
|
916
|
+
let frontmatter = {};
|
|
917
|
+
let mdxContent;
|
|
918
|
+
if (match) {
|
|
919
|
+
frontmatter = parseYaml(match[1]);
|
|
920
|
+
mdxContent = match[2].trim();
|
|
921
|
+
} else {
|
|
922
|
+
mdxContent = content.trim();
|
|
501
923
|
}
|
|
502
|
-
const
|
|
503
|
-
|
|
924
|
+
const compiledContent = await mdxContentToNode(
|
|
925
|
+
mdxContent,
|
|
926
|
+
options?.components ? { components: options.components } : void 0
|
|
927
|
+
);
|
|
928
|
+
const fmSlug = frontmatter["slug"];
|
|
929
|
+
const slug = typeof fmSlug === "string" ? fmSlug : basename2(file, extname(file));
|
|
930
|
+
return {
|
|
931
|
+
file,
|
|
932
|
+
raw: content,
|
|
933
|
+
frontmatter,
|
|
934
|
+
content: compiledContent,
|
|
935
|
+
slug
|
|
936
|
+
};
|
|
504
937
|
}
|
|
505
938
|
function transformYaml(content) {
|
|
506
939
|
return parseYaml(content);
|
|
@@ -550,7 +983,7 @@ function applyTransform(content, transform, filename) {
|
|
|
550
983
|
}
|
|
551
984
|
switch (transform) {
|
|
552
985
|
case "mdx":
|
|
553
|
-
|
|
986
|
+
throw new Error("MDX transform for single files is not supported via loadFile. Use loadGlob instead.");
|
|
554
987
|
case "yaml":
|
|
555
988
|
return transformYaml(content);
|
|
556
989
|
case "csv":
|
|
@@ -559,26 +992,26 @@ function applyTransform(content, transform, filename) {
|
|
|
559
992
|
return content;
|
|
560
993
|
}
|
|
561
994
|
}
|
|
562
|
-
async function loadGlob(baseDir, pattern, transform) {
|
|
995
|
+
async function loadGlob(baseDir, pattern, transform, options) {
|
|
563
996
|
const files = await fg2(pattern, { cwd: baseDir });
|
|
997
|
+
if (transform === "mdx") {
|
|
998
|
+
const results2 = [];
|
|
999
|
+
for (const file of files) {
|
|
1000
|
+
const fullPath = join3(baseDir, file);
|
|
1001
|
+
const content = readFileSync(fullPath, "utf-8");
|
|
1002
|
+
const transformed = await transformMdx(content, file, options);
|
|
1003
|
+
results2.push(transformed);
|
|
1004
|
+
}
|
|
1005
|
+
return results2;
|
|
1006
|
+
}
|
|
564
1007
|
const results = [];
|
|
565
1008
|
for (const file of files) {
|
|
566
1009
|
const fullPath = join3(baseDir, file);
|
|
567
1010
|
const content = readFileSync(fullPath, "utf-8");
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
raw: content,
|
|
573
|
-
frontmatter: transformed.frontmatter,
|
|
574
|
-
content: transformed.content
|
|
575
|
-
});
|
|
576
|
-
} else {
|
|
577
|
-
results.push({
|
|
578
|
-
file,
|
|
579
|
-
raw: content
|
|
580
|
-
});
|
|
581
|
-
}
|
|
1011
|
+
results.push({
|
|
1012
|
+
file,
|
|
1013
|
+
raw: content
|
|
1014
|
+
});
|
|
582
1015
|
}
|
|
583
1016
|
return results;
|
|
584
1017
|
}
|
|
@@ -652,24 +1085,61 @@ async function generateStaticPaths(data, staticPathsDef) {
|
|
|
652
1085
|
}
|
|
653
1086
|
var DataLoader = class {
|
|
654
1087
|
cache = /* @__PURE__ */ new Map();
|
|
1088
|
+
componentCache = /* @__PURE__ */ new Map();
|
|
655
1089
|
projectRoot;
|
|
656
1090
|
constructor(projectRoot) {
|
|
657
1091
|
this.projectRoot = projectRoot;
|
|
658
1092
|
}
|
|
1093
|
+
/**
|
|
1094
|
+
* Resolve components from string path or import reference
|
|
1095
|
+
*/
|
|
1096
|
+
resolveComponents(ref, imports) {
|
|
1097
|
+
if (typeof ref === "string") {
|
|
1098
|
+
if (this.componentCache.has(ref)) {
|
|
1099
|
+
return this.componentCache.get(ref);
|
|
1100
|
+
}
|
|
1101
|
+
const defs = loadComponentDefinitions(this.projectRoot, ref);
|
|
1102
|
+
this.componentCache.set(ref, defs);
|
|
1103
|
+
return defs;
|
|
1104
|
+
}
|
|
1105
|
+
if (ref.expr === "import") {
|
|
1106
|
+
if (!imports) {
|
|
1107
|
+
throw new Error(`Import context required for component reference "${ref.name}"`);
|
|
1108
|
+
}
|
|
1109
|
+
const imported = imports[ref.name];
|
|
1110
|
+
if (!imported || typeof imported !== "object") {
|
|
1111
|
+
throw new Error(`Component import "${ref.name}" not found or invalid`);
|
|
1112
|
+
}
|
|
1113
|
+
return imported;
|
|
1114
|
+
}
|
|
1115
|
+
return {};
|
|
1116
|
+
}
|
|
659
1117
|
/**
|
|
660
1118
|
* Load a single data source
|
|
661
1119
|
*/
|
|
662
|
-
async loadDataSource(name, dataSource) {
|
|
1120
|
+
async loadDataSource(name, dataSource, context) {
|
|
663
1121
|
if (this.cache.has(name)) {
|
|
664
1122
|
return this.cache.get(name);
|
|
665
1123
|
}
|
|
1124
|
+
let componentDefs;
|
|
1125
|
+
if (dataSource.transform === "mdx" && dataSource.components) {
|
|
1126
|
+
componentDefs = this.resolveComponents(
|
|
1127
|
+
dataSource.components,
|
|
1128
|
+
context?.imports
|
|
1129
|
+
);
|
|
1130
|
+
}
|
|
666
1131
|
let data;
|
|
667
1132
|
switch (dataSource.type) {
|
|
668
1133
|
case "glob":
|
|
669
1134
|
if (!dataSource.pattern) {
|
|
670
1135
|
throw new Error(`Glob data source '${name}' requires pattern`);
|
|
671
1136
|
}
|
|
672
|
-
data = await loadGlob(
|
|
1137
|
+
data = await loadGlob(
|
|
1138
|
+
this.projectRoot,
|
|
1139
|
+
dataSource.pattern,
|
|
1140
|
+
dataSource.transform,
|
|
1141
|
+
componentDefs ? { components: componentDefs } : void 0
|
|
1142
|
+
);
|
|
673
1143
|
break;
|
|
674
1144
|
case "file":
|
|
675
1145
|
if (!dataSource.path) {
|
|
@@ -737,9 +1207,12 @@ export {
|
|
|
737
1207
|
isPageExportFunction,
|
|
738
1208
|
isPathSafe,
|
|
739
1209
|
loadApi,
|
|
1210
|
+
loadComponentDefinitions,
|
|
740
1211
|
loadFile,
|
|
741
1212
|
loadGlob,
|
|
742
1213
|
loadLayout,
|
|
1214
|
+
mdxContentToNode2 as mdxContentToNode,
|
|
1215
|
+
mdxToConstela,
|
|
743
1216
|
resolveLayout,
|
|
744
1217
|
resolvePageExport,
|
|
745
1218
|
resolveStaticFile,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@constela/start",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Meta-framework for Constela applications",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -30,24 +30,25 @@
|
|
|
30
30
|
"node": ">=20.0.0"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"hono": "^4.0.0",
|
|
34
|
-
"vite": "^6.0.0",
|
|
35
33
|
"commander": "^12.0.0",
|
|
36
34
|
"fast-glob": "^3.3.0",
|
|
37
|
-
"unified": "^11.0.0",
|
|
38
|
-
"remark-parse": "^11.0.0",
|
|
39
|
-
"remark-mdx": "^3.0.0",
|
|
40
|
-
"remark-gfm": "^4.0.0",
|
|
41
35
|
"gray-matter": "^4.0.0",
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
36
|
+
"hono": "^4.0.0",
|
|
37
|
+
"remark-gfm": "^4.0.0",
|
|
38
|
+
"remark-mdx": "^3.0.0",
|
|
39
|
+
"remark-parse": "^11.0.0",
|
|
40
|
+
"unified": "^11.0.0",
|
|
41
|
+
"vite": "^6.0.0",
|
|
42
|
+
"@constela/compiler": "0.7.0",
|
|
43
|
+
"@constela/server": "3.0.0",
|
|
44
|
+
"@constela/runtime": "0.9.2",
|
|
45
|
+
"@constela/router": "7.0.0",
|
|
46
|
+
"@constela/core": "0.7.0"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
|
-
"
|
|
49
|
+
"@types/mdast": "^4.0.4",
|
|
50
50
|
"tsup": "^8.0.0",
|
|
51
|
+
"typescript": "^5.3.0",
|
|
51
52
|
"vitest": "^2.0.0"
|
|
52
53
|
},
|
|
53
54
|
"license": "MIT",
|