@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.
Files changed (3) hide show
  1. package/dist/index.d.ts +77 -9
  2. package/dist/index.js +497 -24
  3. 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
- * Transform MDX content - parse frontmatter and content
438
+ * Load component definitions from a JSON file
383
439
  */
384
- declare function transformMdx(content: string): {
385
- frontmatter: Record<string, unknown>;
386
- content: string;
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): Promise<GlobResult[]>;
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): Promise<unknown>;
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 transformMdx(content) {
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
- if (!match) {
500
- return { frontmatter: {}, content: content.trim() };
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 frontmatter = parseYaml(match[1]);
503
- return { frontmatter, content: match[2].trim() };
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
- return transformMdx(content);
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
- 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
- }
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(this.projectRoot, dataSource.pattern, dataSource.transform);
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": "0.4.1",
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
- "@constela/core": "0.6.0",
43
- "@constela/router": "6.0.0",
44
- "@constela/server": "2.0.0",
45
- "@constela/runtime": "0.9.0",
46
- "@constela/compiler": "0.6.0"
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
- "typescript": "^5.3.0",
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",