@constela/start 1.2.15 → 1.2.17

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.
@@ -2448,13 +2448,39 @@ async function loadLayout2(layoutName, layoutsDir) {
2448
2448
  }
2449
2449
  try {
2450
2450
  const content = readFileSync5(layoutPath, "utf-8");
2451
- return JSON.parse(content);
2452
- } catch {
2451
+ const parsed = JSON.parse(content);
2452
+ if (parsed.imports && Object.keys(parsed.imports).length > 0) {
2453
+ const layoutDir = dirname5(layoutPath);
2454
+ const resolvedImports = await resolveImports(layoutDir, parsed.imports);
2455
+ return { ...parsed, importData: resolvedImports };
2456
+ }
2457
+ return parsed;
2458
+ } catch (error) {
2459
+ if (error instanceof Error && error.message.includes("not found")) {
2460
+ throw error;
2461
+ }
2453
2462
  throw new Error(`Invalid JSON in layout file: ${layoutPath}`);
2454
2463
  }
2455
2464
  }
2456
- function applyLayout(pageView, layoutView) {
2457
- return replaceSlot(layoutView, pageView);
2465
+ function applyLayout(pageView, layoutView, namedSlots) {
2466
+ return replaceSlot(layoutView, pageView, namedSlots);
2467
+ }
2468
+ function extractMdxContentSlot2(loadedData, dataSourceName, routeParams) {
2469
+ const dataSource = loadedData[dataSourceName];
2470
+ if (!Array.isArray(dataSource)) {
2471
+ return void 0;
2472
+ }
2473
+ const slug = routeParams["slug"] || "index";
2474
+ const item = dataSource.find(
2475
+ (entry) => typeof entry === "object" && entry !== null && "slug" in entry && entry.slug === slug
2476
+ );
2477
+ if (!item || typeof item !== "object" || !("content" in item)) {
2478
+ return void 0;
2479
+ }
2480
+ return { "mdx-content": item.content };
2481
+ }
2482
+ function isEventHandler(value) {
2483
+ return typeof value === "object" && value !== null && "event" in value && typeof value.event === "string" && "action" in value && typeof value.action === "string";
2458
2484
  }
2459
2485
  function normalizeProps(props) {
2460
2486
  if (!props || typeof props !== "object") {
@@ -2464,6 +2490,8 @@ function normalizeProps(props) {
2464
2490
  for (const [key, value] of Object.entries(props)) {
2465
2491
  if (value && typeof value === "object" && "expr" in value) {
2466
2492
  normalized[key] = value;
2493
+ } else if (isEventHandler(value)) {
2494
+ normalized[key] = value;
2467
2495
  } else if (value !== void 0 && value !== null) {
2468
2496
  normalized[key] = { expr: "lit", value };
2469
2497
  }
@@ -2570,59 +2598,127 @@ function substituteLayoutParamsInNode(node, layoutParams) {
2570
2598
  }
2571
2599
  return result;
2572
2600
  }
2573
- function replaceSlot(node, content) {
2601
+ function replaceSlot(node, content, namedSlots) {
2574
2602
  if (!node || typeof node !== "object") {
2575
2603
  return node;
2576
2604
  }
2577
2605
  const nodeObj = node;
2578
2606
  if (nodeObj["kind"] === "slot") {
2579
- return content;
2607
+ const slotName = nodeObj["name"];
2608
+ if (slotName && namedSlots && slotName in namedSlots) {
2609
+ return namedSlots[slotName];
2610
+ }
2611
+ if (!slotName || slotName === "default") {
2612
+ return content;
2613
+ }
2614
+ return node;
2580
2615
  }
2581
2616
  if (Array.isArray(nodeObj["children"])) {
2582
2617
  return {
2583
2618
  ...nodeObj,
2584
- children: nodeObj["children"].map((child) => replaceSlot(child, content))
2619
+ children: nodeObj["children"].map((child) => replaceSlot(child, content, namedSlots))
2585
2620
  };
2586
2621
  }
2587
2622
  return node;
2588
2623
  }
2589
- async function processLayouts(pageInfo, layoutsDir) {
2624
+ async function collectLayoutChain(layoutName, layoutsDir, visited = /* @__PURE__ */ new Set()) {
2625
+ if (visited.has(layoutName)) {
2626
+ throw new Error(`Circular layout reference detected: ${layoutName}`);
2627
+ }
2628
+ visited.add(layoutName);
2629
+ const layout = await loadLayout2(layoutName, layoutsDir);
2630
+ const chain = [layout];
2631
+ if (layout.layout) {
2632
+ const parentChain = await collectLayoutChain(layout.layout, layoutsDir, visited);
2633
+ chain.push(...parentChain);
2634
+ }
2635
+ return chain;
2636
+ }
2637
+ async function processLayouts(pageInfo, layoutsDir, routeParams = {}) {
2590
2638
  const layoutName = pageInfo.page.route?.layout;
2591
2639
  if (!layoutName || !layoutsDir) {
2592
2640
  return pageInfo;
2593
2641
  }
2594
- const layout = await loadLayout2(layoutName, layoutsDir);
2595
- const normalizedLayoutView = normalizeViewNode(structuredClone(layout.view));
2596
- let wrappedView = applyLayout(pageInfo.page.view, normalizedLayoutView);
2642
+ const layoutChain = await collectLayoutChain(layoutName, layoutsDir);
2643
+ let mergedImports = { ...pageInfo.resolvedImports };
2644
+ for (let i = layoutChain.length - 1; i >= 0; i--) {
2645
+ const layout = layoutChain[i];
2646
+ if (layout?.importData) {
2647
+ mergedImports = {
2648
+ ...layout.importData,
2649
+ ...mergedImports
2650
+ // Inner layout/page imports take precedence
2651
+ };
2652
+ }
2653
+ }
2654
+ let namedSlots;
2655
+ let effectiveRouteParams = routeParams;
2656
+ if (!routeParams["slug"] && pageInfo.page.route?.path) {
2657
+ const pathSegments = pageInfo.page.route.path.split("/").filter(Boolean);
2658
+ const lastSegment = pathSegments[pathSegments.length - 1];
2659
+ if (lastSegment && !lastSegment.startsWith(":")) {
2660
+ effectiveRouteParams = { ...routeParams, slug: lastSegment };
2661
+ }
2662
+ }
2663
+ if (pageInfo.loadedData && Object.keys(pageInfo.loadedData).length > 0) {
2664
+ for (const dataSourceName of Object.keys(pageInfo.loadedData)) {
2665
+ const slots = extractMdxContentSlot2(pageInfo.loadedData, dataSourceName, effectiveRouteParams);
2666
+ if (slots) {
2667
+ namedSlots = namedSlots ? { ...namedSlots, ...slots } : slots;
2668
+ break;
2669
+ }
2670
+ }
2671
+ }
2672
+ if (!namedSlots && pageInfo.resolvedImports && Object.keys(pageInfo.resolvedImports).length > 0) {
2673
+ for (const importName of Object.keys(pageInfo.resolvedImports)) {
2674
+ const slots = extractMdxContentSlot2(
2675
+ pageInfo.resolvedImports,
2676
+ importName,
2677
+ effectiveRouteParams
2678
+ );
2679
+ if (slots) {
2680
+ namedSlots = slots;
2681
+ break;
2682
+ }
2683
+ }
2684
+ }
2685
+ const pageWithImportData = pageInfo.page;
2686
+ if (!namedSlots && pageWithImportData.importData && Object.keys(pageWithImportData.importData).length > 0) {
2687
+ for (const importName of Object.keys(pageWithImportData.importData)) {
2688
+ const slots = extractMdxContentSlot2(
2689
+ pageWithImportData.importData,
2690
+ importName,
2691
+ effectiveRouteParams
2692
+ );
2693
+ if (slots) {
2694
+ namedSlots = slots;
2695
+ break;
2696
+ }
2697
+ }
2698
+ }
2699
+ let currentView = pageInfo.page.view;
2700
+ for (const layout of layoutChain) {
2701
+ const normalizedLayoutView = normalizeViewNode(structuredClone(layout.view));
2702
+ currentView = applyLayout(currentView, normalizedLayoutView, namedSlots);
2703
+ }
2597
2704
  const layoutParams = pageInfo.page.route?.layoutParams;
2598
2705
  if (layoutParams && Object.keys(layoutParams).length > 0) {
2599
- wrappedView = substituteLayoutParamsInNode(wrappedView, layoutParams);
2706
+ currentView = substituteLayoutParamsInNode(currentView, layoutParams);
2600
2707
  }
2601
2708
  let updatedRoute;
2602
2709
  if (pageInfo.page.route) {
2603
2710
  const { layout: _layout, ...routeWithoutLayout } = pageInfo.page.route;
2604
2711
  updatedRoute = routeWithoutLayout;
2605
2712
  }
2606
- let updatedPageInfo = {
2713
+ const updatedPageInfo = {
2607
2714
  ...pageInfo,
2715
+ resolvedImports: mergedImports,
2608
2716
  page: {
2609
2717
  ...pageInfo.page,
2610
- view: wrappedView,
2718
+ view: currentView,
2611
2719
  route: updatedRoute
2612
2720
  }
2613
2721
  };
2614
- if (layout.layout) {
2615
- const parentLayout = await loadLayout2(layout.layout, layoutsDir);
2616
- const normalizedParentLayoutView = normalizeViewNode(structuredClone(parentLayout.view));
2617
- const doubleWrappedView = applyLayout(updatedPageInfo.page.view, normalizedParentLayoutView);
2618
- updatedPageInfo = {
2619
- ...updatedPageInfo,
2620
- page: {
2621
- ...updatedPageInfo.page,
2622
- view: doubleWrappedView
2623
- }
2624
- };
2625
- }
2626
2722
  return updatedPageInfo;
2627
2723
  }
2628
2724
  async function renderPageToHtml(program, params, runtimePath, cssPath) {
@@ -2748,13 +2844,14 @@ async function build2(options) {
2748
2844
  if (!staticPathsResult || staticPathsResult.length === 0) {
2749
2845
  continue;
2750
2846
  }
2751
- if (layoutsDir) {
2752
- pageInfo = await processLayouts(pageInfo, layoutsDir);
2753
- }
2754
2847
  for (const pathEntry of staticPathsResult) {
2755
2848
  const params = pathEntry.params;
2756
2849
  const outputPath = paramsToOutputPath(route.pattern, params, outDir);
2757
- const program = await convertToCompiledProgram(pageInfo);
2850
+ let processedPageInfo = pageInfo;
2851
+ if (layoutsDir) {
2852
+ processedPageInfo = await processLayouts(pageInfo, layoutsDir, params);
2853
+ }
2854
+ const program = await convertToCompiledProgram(processedPageInfo);
2758
2855
  const html = await renderPageToHtml(program, params, runtimePath, cssPath);
2759
2856
  await mkdir2(dirname5(outputPath), { recursive: true });
2760
2857
  await writeFile(outputPath, html, "utf-8");
@@ -2767,11 +2864,22 @@ async function build2(options) {
2767
2864
  routes.push(routePath);
2768
2865
  }
2769
2866
  } else {
2770
- const outputPath = getOutputPath(relPathFromRoutesDir, outDir);
2771
2867
  const loader = new JsonPageLoader(projectRoot, { routesDir: absoluteRoutesDir });
2772
2868
  let pageInfo = await loader.loadPage(relPathFromProjectRoot);
2869
+ let outputPath;
2870
+ if (pageInfo.page.route?.path) {
2871
+ const routePath = pageInfo.page.route.path;
2872
+ const relativePath = routePath.startsWith("/") ? routePath.slice(1) : routePath;
2873
+ if (relativePath === "" || relativePath === "/") {
2874
+ outputPath = join9(outDir, "index.html");
2875
+ } else {
2876
+ outputPath = join9(outDir, relativePath, "index.html");
2877
+ }
2878
+ } else {
2879
+ outputPath = getOutputPath(relPathFromRoutesDir, outDir);
2880
+ }
2773
2881
  if (layoutsDir) {
2774
- pageInfo = await processLayouts(pageInfo, layoutsDir);
2882
+ pageInfo = await processLayouts(pageInfo, layoutsDir, {});
2775
2883
  }
2776
2884
  const program = await convertToCompiledProgram(pageInfo);
2777
2885
  const html = await renderPageToHtml(program, {}, runtimePath, cssPath);
package/dist/cli/index.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  createDevServer,
4
4
  loadConfig,
5
5
  resolveConfig
6
- } from "../chunk-7KQYNZ2A.js";
6
+ } from "../chunk-CRQMVEYP.js";
7
7
  import "../chunk-PUTC5BCP.js";
8
8
 
9
9
  // src/cli/index.ts
package/dist/index.js CHANGED
@@ -23,7 +23,7 @@ import {
23
23
  transformCsv,
24
24
  transformMdx,
25
25
  transformYaml
26
- } from "./chunk-7KQYNZ2A.js";
26
+ } from "./chunk-CRQMVEYP.js";
27
27
  import {
28
28
  generateHydrationScript,
29
29
  renderPage,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@constela/start",
3
- "version": "1.2.15",
3
+ "version": "1.2.17",
4
4
  "description": "Meta-framework for Constela applications",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -44,9 +44,9 @@
44
44
  "@tailwindcss/postcss": "^4.0.0",
45
45
  "tailwindcss": "^4.0.0",
46
46
  "@constela/core": "0.7.0",
47
- "@constela/compiler": "0.7.1",
48
- "@constela/router": "8.0.0",
49
47
  "@constela/runtime": "0.10.2",
48
+ "@constela/router": "8.0.0",
49
+ "@constela/compiler": "0.7.1",
50
50
  "@constela/server": "3.0.1"
51
51
  },
52
52
  "devDependencies": {