@brightsideoy/kama 0.1.0-alpha.121 → 0.1.0-alpha.123

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brightsideoy/kama",
3
- "version": "0.1.0-alpha.121",
3
+ "version": "0.1.0-alpha.123",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -2,10 +2,13 @@
2
2
  interface Props {
3
3
  title: string;
4
4
  slug?: string;
5
+ isHomePage?: boolean;
5
6
  }
6
7
 
7
- const { title, slug } = Astro.props;
8
- const isHome = !slug || slug === "home";
8
+ const { title, slug, isHomePage } = Astro.props;
9
+
10
+ // 🎯 Detect home page via URL pathname or explicit flag
11
+ const isHome = isHomePage || Astro.url.pathname === "/" || !slug || slug === "";
9
12
  ---
10
13
 
11
14
  <nav class="breadcrumb-nav" aria-label="Breadcrumb">
@@ -30,8 +30,17 @@ if (!rawTree || rawTree.length === 0) {
30
30
  rawTree = await getTree(getTreeId(), getChannelId(), customFetch);
31
31
  }
32
32
 
33
- // Automatically use the configured site name for the text logo
34
- const siteName = import.meta.env.PUBLIC_SITE_NAME || "KAMA";
33
+ // 🎯 Find the home node in the tree just in case `logoText` isn't set to inherit to subpages
34
+ const homeSlug = import.meta.env.PUBLIC_HOME_SLUG || "home";
35
+ const homeNodeInTree =
36
+ rawTree?.find((n: any) => n.slug === homeSlug) || rawTree?.[0];
37
+
38
+ // Automatically use logoText from the current page, fallback to the Home node in the tree, then fallback to ENV
39
+ const siteName =
40
+ node?.logoText ||
41
+ homeNodeInTree?.logoText ||
42
+ import.meta.env.PUBLIC_SITE_NAME ||
43
+ "KAMA";
35
44
  ---
36
45
 
37
46
  <header class="site-header">
@@ -11,14 +11,30 @@ const { tree = [] } = Astro.props;
11
11
  // 1. Get current path from Astro context
12
12
  const currentPath = Astro.url.pathname.replace(/\/$/, "") || "/";
13
13
 
14
+ // 🎯 Extract configurable fallback home slug
15
+ const homeSlug = String(import.meta.env.PUBLIC_HOME_SLUG || "home").trim();
16
+
17
+ // 2. Locate Home Node (either root item, slug matching homeSlug, or path === "/")
14
18
  const homeNode = tree.find(
15
- (item: TreeNode) => item.slug === "home" || item.path === "/",
19
+ (item: TreeNode) =>
20
+ item.slug === homeSlug ||
21
+ item.slug === "" ||
22
+ item.path === "/" ||
23
+ item.path === "",
16
24
  );
17
25
 
26
+ // 3. Determine top-level navigation items
27
+ // If home node has children (e.g. nested navigation), render its children; otherwise filter out the home node.
18
28
  const topLevelNavItems =
19
29
  homeNode?.children && homeNode.children.length > 0
20
30
  ? homeNode.children
21
- : tree.filter((item: TreeNode) => item.slug !== "home");
31
+ : tree.filter(
32
+ (item: TreeNode) =>
33
+ item.slug !== homeSlug &&
34
+ item.slug !== "" &&
35
+ item.path !== "/" &&
36
+ item.path !== "",
37
+ );
22
38
 
23
39
  // Normalize path comparison helper
24
40
  const normalizePath = (path?: string) => {
@@ -42,18 +42,16 @@ if (isStaticAsset) {
42
42
  return new Response(null, { status: 404 });
43
43
  }
44
44
 
45
- // 2. PATH RESOLUTION
46
- const rawSlug = Array.isArray(slug) ? slug.join("/") : slugStr;
47
- const pathParts = rawSlug.split("/").filter(part => part.trim() !== "");
48
- const pathToFetch = pathParts.length > 0 ? pathParts.join("/") : "home";
49
-
50
- // 3. ENV & WORKER RESOLUTION
45
+ // 2. ENV & WORKER RESOLUTION
51
46
  const locals = (Astro.locals as any) || {};
52
- // SAFELY EXTRACT WORKER ENV USING THE CLOUDFLARE MODULE
53
47
  const workerEnv = env || process.env || {};
54
48
  const cmsWorker = locals.cfContext?.env?.CMS_WORKER || workerEnv.CMS_WORKER;
55
49
 
56
- // CRITICAL FIX: Flattened to single line to bypass Rolldown parser bug
50
+ // Extract environment configuration & fallback slug
51
+ const envHomeSlug = import.meta.env.PUBLIC_HOME_SLUG;
52
+ const workerHomeSlug = (workerEnv as any).PUBLIC_HOME_SLUG;
53
+ const homeSlug = String(envHomeSlug || workerHomeSlug || "home").trim();
54
+
57
55
  const envApiUrl = import.meta.env.PUBLIC_API_URL;
58
56
  const workerApiUrl = workerEnv.PUBLIC_API_URL;
59
57
  const baseApiUrl = String(envApiUrl || workerApiUrl || "http://localhost:8787");
@@ -94,19 +92,36 @@ const customFetch = async (input: RequestInfo | URL, init?: RequestInit) => {
94
92
  const channelId = getChannelId();
95
93
  const treeId = getTreeId();
96
94
 
95
+ // 3. PATH RESOLUTION
96
+ const rawSlug = Array.isArray(slug) ? slug.join("/") : slugStr;
97
+ const pathParts = rawSlug.split("/").filter(part => part.trim() !== "");
98
+
97
99
  let pageData: NodeResponse | null = null;
98
100
  let navTree: TreeNode[] = [];
101
+ let targetSlug = pathParts.join("/");
99
102
 
103
+ // 🎯 FETCH TREE FIRST (to resolve root page if visiting '/')
100
104
  try {
101
- const [fetchedPage, fetchedTree] = await Promise.all([
102
- getNodeByPath(pathToFetch, channelId, isPreview, customFetch),
103
- getTree(treeId, channelId, customFetch),
104
- ]);
105
- pageData = fetchedPage;
106
- navTree = fetchedTree;
105
+ navTree = await getTree(treeId, channelId, customFetch);
106
+ } catch (err) {
107
+ console.error(`❌ [SSR Error] Failed to fetch tree "${treeId}":`, err);
108
+ }
109
+
110
+ // 🎯 DYNAMIC HOME SLUG RESOLUTION
111
+ if (!targetSlug) {
112
+ const rootTreeSlug =
113
+ navTree && navTree.length > 0
114
+ ? navTree[0].slug || (navTree[0] as any).key
115
+ : "";
116
+ targetSlug = rootTreeSlug || homeSlug;
117
+ }
118
+
119
+ // 🎯 FETCH NODE DATA
120
+ try {
121
+ pageData = await getNodeByPath(targetSlug, channelId, isPreview, customFetch);
107
122
  } catch (err) {
108
123
  console.error(
109
- `❌ [SSR Error] Failed to fetch data for path "${pathToFetch}":`,
124
+ `❌ [SSR Error] Failed to fetch page data for path "${targetSlug}":`,
110
125
  err,
111
126
  );
112
127
  }
@@ -118,9 +133,17 @@ if (!pageData || !pageData.node) {
118
133
  const node = pageData.node;
119
134
  const content = pageData.content || {};
120
135
 
136
+ const rootTreeSlug =
137
+ navTree && navTree.length > 0
138
+ ? navTree[0].slug || (navTree[0] as any).key
139
+ : "";
121
140
  const isHomePage =
122
- pathToFetch === "home" || node?.path === "/" || pathToFetch === "";
123
- const normalizedSlug = isHomePage ? "" : pathToFetch;
141
+ targetSlug === homeSlug ||
142
+ (rootTreeSlug && targetSlug === rootTreeSlug) ||
143
+ node?.path === "/" ||
144
+ pathParts.length === 0;
145
+
146
+ const normalizedSlug = isHomePage ? "" : targetSlug;
124
147
 
125
148
  const headerBlocks = content?.header || [];
126
149
  const mainBlocks = content?.content || [];
@@ -26,24 +26,32 @@ export const GET: APIRoute = async (context) => {
26
26
  return fetch(req);
27
27
  };
28
28
 
29
- const channelId = getChannelId() || import.meta.env.PUBLIC_CHANNEL_ID || "website";
30
- const treeId = getTreeId() || import.meta.env.PUBLIC_TREE_ID || "main-navigation";
29
+ const channelId = getChannelId() || import.meta.env.PUBLIC_CHANNEL_ID || "main-channel";
30
+ const treeId = getTreeId() || import.meta.env.PUBLIC_TREE_ID || "main-tree";
31
+
32
+ // 🎯 Extract configurable fallback home slug
33
+ const envHomeSlug = import.meta.env.PUBLIC_HOME_SLUG;
34
+ const workerHomeSlug = cfEnv.PUBLIC_HOME_SLUG || (typeof process !== "undefined" ? process.env?.PUBLIC_HOME_SLUG : undefined);
35
+ const homeSlug = String(envHomeSlug || workerHomeSlug || "home").trim();
31
36
 
32
37
  const rawOrigin = import.meta.env.PUBLIC_SITE_URL || context.url.origin;
33
38
  const siteOrigin = String(rawOrigin).replace(/\/$/, "");
34
39
  const brandName = import.meta.env.PUBLIC_SITE_NAME || "KAMA Starter";
35
40
 
36
41
  try {
37
- // 2. Fetch Home Node (for global context) and Navigation Tree
38
- const [homeData, rawNavTree] = await Promise.all([
39
- getNodeByPath("home", channelId, false, customFetch).catch(() => null),
40
- getTree(treeId, channelId, customFetch).catch(() => []),
41
- ]);
42
-
42
+ // 2. Fetch Navigation Tree first to determine root nodes
43
+ const rawNavTree = await getTree(treeId, channelId, customFetch).catch(() => []);
43
44
  const navTree = Array.isArray(rawNavTree)
44
45
  ? rawNavTree
45
46
  : (rawNavTree?.tree || rawNavTree?.items || rawNavTree?.data || []);
46
47
 
48
+ // Determine home page slug (first root node in tree, or fallback home slug)
49
+ const rootTreeSlug = navTree.length > 0 ? (navTree[0].slug || navTree[0].key || "") : "";
50
+ const resolvedHomeSlug = rootTreeSlug || homeSlug;
51
+
52
+ // Fetch Home Node (for global context)
53
+ const homeData = await getNodeByPath(resolvedHomeSlug, channelId, false, customFetch).catch(() => null);
54
+
47
55
  const siteSummary = homeData?.node?.abstract || homeData?.node?.summary || "Full Application Documentation Corpus";
48
56
 
49
57
  // 3. Header section matching llmstxt.org spec
@@ -82,14 +90,16 @@ export const GET: APIRoute = async (context) => {
82
90
  const nodeData = item.node || item;
83
91
  if (!nodeData || nodeData.isHidden) continue;
84
92
 
85
- const rawPath = nodeData.path || nodeData.slug || "/";
86
- const cleanPath = rawPath.startsWith("/") ? rawPath : `/${rawPath}`;
87
- const itemUrl = cleanPath === "/" ? siteOrigin : `${siteOrigin}${cleanPath}`;
93
+ const itemSlug = nodeData.slug || nodeData.path || "";
94
+ const isRootItem = itemSlug === resolvedHomeSlug || itemSlug === "" || itemSlug === "/";
95
+ const itemUrl = isRootItem ? siteOrigin : `${siteOrigin}/${itemSlug.replace(/^\//, "")}`;
88
96
  const itemTitle = nodeData.title || nodeData.label || "Untitled Page";
89
97
 
98
+ const fetchSlug = isRootItem ? resolvedHomeSlug : itemSlug.replace(/^\//, "");
99
+
90
100
  // Fetch node's block contents
91
101
  const pageData = await getNodeByPath(
92
- cleanPath.replace(/^\//, "") || "home",
102
+ fetchSlug,
93
103
  channelId,
94
104
  false,
95
105
  customFetch
@@ -27,25 +27,31 @@ export const GET: APIRoute = async (context) => {
27
27
  };
28
28
 
29
29
  // 2. Load keys from environment or fallback defaults
30
- const channelId = getChannelId() || import.meta.env.PUBLIC_CHANNEL_ID || "website";
31
- const treeId = getTreeId() || import.meta.env.PUBLIC_TREE_ID || "main-navigation";
30
+ const channelId = getChannelId() || import.meta.env.PUBLIC_CHANNEL_ID || "main-channel";
31
+ const treeId = getTreeId() || import.meta.env.PUBLIC_TREE_ID || "main-tree";
32
+
33
+ // 🎯 Extract configurable fallback home slug
34
+ const envHomeSlug = import.meta.env.PUBLIC_HOME_SLUG;
35
+ const workerHomeSlug = cfEnv.PUBLIC_HOME_SLUG || (typeof process !== "undefined" ? process.env?.PUBLIC_HOME_SLUG : undefined);
36
+ const homeSlug = String(envHomeSlug || workerHomeSlug || "home").trim();
32
37
 
33
38
  const rawOrigin = import.meta.env.PUBLIC_SITE_URL || context.url.origin;
34
39
  const siteOrigin = String(rawOrigin).replace(/\/$/, "");
35
40
  const brandName = import.meta.env.PUBLIC_SITE_NAME || "KAMA Starter";
36
41
 
37
42
  try {
38
- // 3. Parallel fetch for Home Node summary and Navigation Tree
39
- const [homeData, rawNavTree] = await Promise.all([
40
- getNodeByPath("home", channelId, false, customFetch).catch(() => null),
41
- getTree(treeId, channelId, customFetch).catch(() => []),
42
- ]);
43
-
44
- // Normalize tree data payload across direct array or wrapped responses
43
+ // 3. Parallel fetch for Navigation Tree first to resolve root node slug
44
+ const rawNavTree = await getTree(treeId, channelId, customFetch).catch(() => []);
45
45
  const navTree = Array.isArray(rawNavTree)
46
46
  ? rawNavTree
47
47
  : (rawNavTree?.tree || rawNavTree?.items || rawNavTree?.data || []);
48
48
 
49
+ const rootTreeSlug = navTree.length > 0 ? (navTree[0].slug || navTree[0].key || "") : "";
50
+ const resolvedHomeSlug = rootTreeSlug || homeSlug;
51
+
52
+ // Fetch Home Node summary
53
+ const homeData = await getNodeByPath(resolvedHomeSlug, channelId, false, customFetch).catch(() => null);
54
+
49
55
  const siteSummary = homeData?.node?.abstract || homeData?.node?.summary || "Global Content Delivery at the Edge";
50
56
 
51
57
  // 4. Build standard-compliant llms.txt Markdown
@@ -63,9 +69,9 @@ export const GET: APIRoute = async (context) => {
63
69
  const nodeData = item.node || item;
64
70
  if (!nodeData || nodeData.isHidden) continue;
65
71
 
66
- const rawPath = nodeData.path || nodeData.slug || "/";
67
- const cleanPath = rawPath.startsWith("/") ? rawPath : `/${rawPath}`;
68
- const itemUrl = cleanPath === "/" ? siteOrigin : `${siteOrigin}${cleanPath}`;
72
+ const itemSlug = nodeData.slug || nodeData.path || "";
73
+ const isRootItem = itemSlug === resolvedHomeSlug || itemSlug === "" || itemSlug === "/";
74
+ const itemUrl = isRootItem ? siteOrigin : `${siteOrigin}/${itemSlug.replace(/^\//, "")}`;
69
75
 
70
76
  const itemTitle = nodeData.title || nodeData.label || nodeData.menuTitle || "Untitled";
71
77
  const abstract = nodeData.abstract || nodeData.summary || nodeData.description;