@brightsideoy/kama 0.1.0-alpha.142 → 0.1.0-alpha.145

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 (57) hide show
  1. package/.wrangler/state/v3/cache/default/blobs/4de32ec3e619aa3f9acad3fb1a2528209381347d1cbb86e939e08724f5b35bc4000001a0b2b5b216 +0 -0
  2. package/.wrangler/state/v3/cache/default/blobs/5f895e5810a26a6319bbadde9d751e5b10faae6e141db1c454c5f53b3e725591000001a0b2b624a4 +0 -0
  3. package/.wrangler/state/v3/cache/default/blobs/74744186327d8aaf1c6078e50bc561135fd90690c96bd4b733035d0cb3fedb94000001a0b2b5be6b +0 -0
  4. package/.wrangler/state/v3/cache/default/blobs/79c1c81c1ddcd49f49eb27b9197ae0160c3a7981f308aee2ae972653de1b3668000001a0b2b60e76 +0 -0
  5. package/.wrangler/state/v3/cache/default/blobs/7cd29a3bd95a88e7e896a4ae253758cd9e4bde78fc371077f3fef974cd11c64d000001a0b098ff88 +0 -0
  6. package/.wrangler/state/v3/cache/default/blobs/7ecc4caad58c7ee119c02adb84132eaa691b9a171f932ab1c17e0f95c3fed2a8000001a0b2b5bf5e +0 -0
  7. package/.wrangler/state/v3/cache/default/blobs/81928c274111d2c3cbd3d7174136a2fdce83e267da8f8d0caf60d650396197f5000001a0b098de9d +0 -0
  8. package/.wrangler/state/v3/cache/default/blobs/8c3aa6f6625d7c0d682cb498e6715355aec1905909b04ead5e85adbca3989b19000001a0b098e4ac +0 -0
  9. package/.wrangler/state/v3/cache/default/blobs/97205d84c3e4399f6f51295e8381b21f2a8810cdaecc0ab13f2cadf712fae5f4000001a0b339609d +0 -0
  10. package/.wrangler/state/v3/cache/default/blobs/b48d02100b8f1a9520a209628a0778a0f9e0f4ce0643f8d59b3888bbd0f85fc2000001a0b2b63647 +0 -0
  11. package/.wrangler/state/v3/cache/default/blobs/cf30c018e70dad1c6859277e4361da657b79adf34e60cb05885eb32951fbbafc000001a0b09b168f +0 -0
  12. package/.wrangler/state/v3/cache/default/blobs/d0d796d0fe4efd55f61e0c3b8d30170a89703669a52854687186a66304adbf8a000001a0b2b6244d +0 -0
  13. package/.wrangler/state/v3/cache/miniflare-CacheObject/9f458c07675338a7426a7b81ac4fb1baf92d034efbcaaf4336379640ed744ded.sqlite +0 -0
  14. package/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite +0 -0
  15. package/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite-shm +0 -0
  16. package/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite-wal +0 -0
  17. package/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/7cfce387cae19c0dfbb9cc4f2a9c353f60ca0a80fabd0b2e2837d152b58feaaa.sqlite +0 -0
  18. package/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/metadata.sqlite +0 -0
  19. package/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/metadata.sqlite-shm +0 -0
  20. package/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/metadata.sqlite-wal +0 -0
  21. package/.wrangler/state/v3/kv/miniflare-KVNamespaceObject/metadata.sqlite +0 -0
  22. package/.wrangler/state/v3/kv/miniflare-KVNamespaceObject/metadata.sqlite-shm +0 -0
  23. package/.wrangler/state/v3/kv/miniflare-KVNamespaceObject/metadata.sqlite-wal +0 -0
  24. package/.wrangler/state/v3/observability/miniflare-wobs-trace-store/a590acd76969f996ec6e4b599c3c09f58c283a76f2d61392b5d3046caf557602.sqlite +0 -0
  25. package/.wrangler/state/v3/observability/miniflare-wobs-trace-store/a590acd76969f996ec6e4b599c3c09f58c283a76f2d61392b5d3046caf557602.sqlite-shm +0 -0
  26. package/.wrangler/state/v3/observability/miniflare-wobs-trace-store/a590acd76969f996ec6e4b599c3c09f58c283a76f2d61392b5d3046caf557602.sqlite-wal +0 -0
  27. package/.wrangler/state/v3/observability/miniflare-wobs-trace-store/metadata.sqlite +0 -0
  28. package/.wrangler/state/v3/observability/miniflare-wobs-trace-store/metadata.sqlite-shm +0 -0
  29. package/.wrangler/state/v3/observability/miniflare-wobs-trace-store/metadata.sqlite-wal +0 -0
  30. package/.wrangler/state/v3/r2/kama-media/blobs/1c7705701fba41f9a2683025d40393711daffc55d32172088a62d8999017e9c6000001a0b339607a +0 -0
  31. package/.wrangler/state/v3/r2/kama-media/blobs/1ee266c53f586a4a57a1d21be291e34bb2e2236c15e43ae30497f6054290f01d000001a0b2b6248c +0 -0
  32. package/.wrangler/state/v3/r2/kama-media/blobs/6db1f8204b6d125f0911b3e42022258cc7e1c37a95c2ce7f5c7f14da31d408ec000001a0b2b60e1f +0 -0
  33. package/.wrangler/state/v3/r2/kama-media/blobs/97b86f251c5d30df5e1eab8662cab83f5f7c4583ad14fd2d5ea99f16fdcfd6ef000001a0b2b62482 +0 -0
  34. package/.wrangler/state/v3/r2/miniflare-R2BucketObject/9ffe30cff37d40b366f64100a8f041c0a1e5aee78708ba90b9ef4ca6a6b2d883.sqlite +0 -0
  35. package/.wrangler/state/v3/r2/miniflare-R2BucketObject/metadata.sqlite +0 -0
  36. package/.wrangler/state/v3/r2/miniflare-R2BucketObject/metadata.sqlite-shm +0 -0
  37. package/.wrangler/state/v3/r2/miniflare-R2BucketObject/metadata.sqlite-wal +0 -0
  38. package/README.md +16 -37
  39. package/bin/cli.js +190 -147
  40. package/definitions/_page.yaml +52 -0
  41. package/dist/assets/index-BSBvGvQz.css +1 -0
  42. package/dist/assets/index-RzfH6iq3.js +162 -0
  43. package/dist/dist-CZEgSuN0.js +8674 -0
  44. package/dist/index.html +2 -2
  45. package/dist/index.js +6856 -11229
  46. package/dist/schema-compiler.js +38 -0
  47. package/npm-test.sh +75 -0
  48. package/package.json +14 -10
  49. package/schema.sql +3 -2
  50. package/templates/astro-starter/src/components/blocks/ImageBlock.astro +20 -56
  51. package/templates/astro-starter/src/components/global/Header.astro +19 -12
  52. package/templates/astro-starter/src/components/ui/KamaImage.astro +49 -73
  53. package/templates/astro-starter/src/pages/[...slug].astro +16 -43
  54. package/templates/astro-starter/src/pages/media/[...path].ts +8 -1
  55. package/wrangler.json +22 -0
  56. package/dist/assets/index-Bv7fPRX7.css +0 -1
  57. package/dist/assets/index-SWBz-Fr1.js +0 -162
@@ -0,0 +1,38 @@
1
+ import { fileURLToPath as e, pathToFileURL as t } from "node:url";
2
+ import { existsSync as n, mkdirSync as r, readFileSync as i, writeFileSync as a } from "node:fs";
3
+ import { dirname as o, join as s } from "node:path";
4
+ //#region scripts/schema-compiler.ts
5
+ var c = e(import.meta.url), l = o(c);
6
+ async function u() {
7
+ let e = process.cwd(), o = !1;
8
+ try {
9
+ JSON.parse(i(s(e, "package.json"), "utf-8")).name === "@brightsideoy/kama" && (o = !0);
10
+ } catch {}
11
+ let c;
12
+ if (o) {
13
+ let t = s(e, "server", "db");
14
+ n(t) || r(t, { recursive: !0 }), c = s(t, "bundled-definitions.js");
15
+ } else {
16
+ let t = s(e, "kama-worker");
17
+ n(t) || r(t, { recursive: !0 }), c = s(t, "bundled-definitions.js");
18
+ }
19
+ let u, d = s(l, "../server/schema/engine.ts"), f = s(l, "../dist/index.js");
20
+ if (n(d)) u = (await import(t(d).href)).compileDefinitions;
21
+ else if (n(f)) u = (await import(t(f).href)).compileDefinitions;
22
+ else try {
23
+ u = (await import("./dist-CZEgSuN0.js")).compileDefinitions;
24
+ } catch (e) {
25
+ console.error("❌ Could not resolve Kama Schema Engine:", e), process.exit(1);
26
+ }
27
+ u || (console.error("❌ compileDefinitions function missing from resolved module"), process.exit(1));
28
+ let p = u(), m = Object.keys(p?.nodes || {}).length, h = Object.keys(p?.blocks || {}).length, g = `// AUTO-GENERATED BUNDLE - DO NOT EDIT
29
+ const defs = ${JSON.stringify(p, null, 2)};
30
+ export const BUNDLED_DEFINITIONS = defs;
31
+ export default defs;
32
+ `;
33
+ a(c, g, "utf-8"), console.log(`✅ Schema securely compiled using engine.ts to ${c} (${m} nodes, ${h} blocks)`);
34
+ }
35
+ u().catch((e) => {
36
+ console.error("❌ Schema Compiler Runtime Error:", e), process.exit(1);
37
+ });
38
+ //#endregion
package/npm-test.sh ADDED
@@ -0,0 +1,75 @@
1
+ #!/bin/bash
2
+
3
+ # Exit immediately if any command fails
4
+ set -e
5
+
6
+ # Define absolute test directory path
7
+ TEST_DIR="$(cd .. && pwd)/npm-test"
8
+
9
+ echo "🧹 1. Nuking old test environment & caches..."
10
+ rm -rf "$TEST_DIR"
11
+ mkdir -p "$TEST_DIR"
12
+ rm -rf ~/.npm/_npx/
13
+
14
+ # 🎯 FIX 1: Nuke the global npm cache so npx doesn't silently use an old cached version of the tarball!
15
+ npm cache clean --force
16
+
17
+ # Ensure CLI binary permissions are intact
18
+ chmod +x bin/cli.js
19
+
20
+ echo "🔥 2. Building & packing KAMA package locally..."
21
+ npm run build --loglevel error
22
+ npm pack --loglevel error
23
+
24
+ # Find the newly created tarball
25
+ TARBALL=$(ls -1t *.tgz | head -n 1)
26
+
27
+ if [ -z "$TARBALL" ]; then
28
+ echo "❌ Error: Failed to find the packed .tgz file."
29
+ exit 1
30
+ fi
31
+
32
+ mv "$TARBALL" "$TEST_DIR/"
33
+ cd "$TEST_DIR"
34
+
35
+ echo "🚀 3. Initializing test workspace..."
36
+ echo "------------------------------------------------------"
37
+ npx --yes --package "$TEST_DIR/$TARBALL" kama init
38
+ echo "------------------------------------------------------"
39
+
40
+ # Detect workspace root
41
+ WORKSPACE_DIR="."
42
+ if [ ! -f "package.json" ]; then
43
+ WORKSPACE_DIR=$(find . -mindepth 2 -maxdepth 2 -name "package.json" | head -n 1 | xargs dirname)
44
+ fi
45
+
46
+ if [ ! -f "$WORKSPACE_DIR/package.json" ]; then
47
+ echo "❌ Could not find package.json. Aborting test."
48
+ exit 1
49
+ fi
50
+
51
+ cd "$WORKSPACE_DIR"
52
+
53
+ echo "📦 4. Installing workspace dependencies quietly..."
54
+ npm install --loglevel error
55
+ npm install "$TEST_DIR/$TARBALL" --save --loglevel error
56
+
57
+ if [ -d "kama-worker" ]; then
58
+ npm install "$TEST_DIR/$TARBALL" -w kama-worker --save --loglevel error
59
+ fi
60
+
61
+ # 🎯 FIX 2: You MUST inject the local tarball into the site too!
62
+ # Otherwise, Astro will download an old Kama package from the internet.
63
+ if [ -d "sites/kama-starter" ]; then
64
+ npm install "$TEST_DIR/$TARBALL" -w sites/kama-starter --save --loglevel error
65
+ fi
66
+
67
+ echo ""
68
+ echo "======================================================"
69
+ echo "✨ KAMA ENVIRONMENT BOOTSTRAPPED SUCCESSFULLY!"
70
+ echo " - Backend Worker: http://localhost:8787"
71
+ echo " - Astro Site: http://localhost:4321"
72
+ echo "======================================================"
73
+ echo ""
74
+
75
+ npm run dev --loglevel error
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brightsideoy/kama",
3
- "version": "0.1.0-alpha.142",
3
+ "version": "0.1.0-alpha.145",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -15,15 +15,19 @@
15
15
  "create-kama": "bin/cli.js"
16
16
  },
17
17
  "scripts": {
18
- "build:schema": "tsx scripts/bundle-schemas.ts",
18
+ "predev": "npm run bundle:definitions && npm run build:schema",
19
+ "dev": "npm run dev:local",
20
+ "dev:local": "concurrently \"vite\" \"KAMA_RUNTIME=node tsx server/dev.ts\"",
21
+ "dev:cf": "concurrently \"vite\" \"wrangler dev server/index.ts\"",
22
+ "bundle:definitions": "node bin/cli.js bundle",
23
+ "build:schema": "tsx scripts/build-schema.ts",
24
+ "build:seed": "tsx scripts/build-seed.ts",
19
25
  "build:server": "vite build",
20
26
  "build:ui": "vite build --config vite.config.ui.ts",
21
- "build": "npm run build:schema && npm run build:server && npm run build:ui",
27
+ "build": "npm run bundle:definitions && npm run build:schema && npm run build:server && npm run build:ui",
22
28
  "build:cms": "npm run build",
23
- "predev": "npm run build:schema",
24
- "dev": "concurrently \"vite\" \"tsx watch server/dev.ts\"",
25
- "prepack": "npm run build && chmod +x bin/cli.js",
26
- "serve": "tsx server/dev.ts"
29
+ "prepublishOnly": "npm run build",
30
+ "serve": "wrangler dev server/index.ts"
27
31
  },
28
32
  "publishConfig": {
29
33
  "access": "public"
@@ -41,7 +45,6 @@
41
45
  "@tiptap/pm": "^3.30.0",
42
46
  "@tiptap/starter-kit": "^3.30.0",
43
47
  "@tiptap/vue-3": "^3.30.0",
44
- "better-sqlite3": "^13.0.3",
45
48
  "drizzle-orm": "^0.45.2",
46
49
  "hono": "^4.13.2",
47
50
  "locale-codes": "^1.3.1",
@@ -56,7 +59,7 @@
56
59
  "devDependencies": {
57
60
  "@iconify-json/heroicons": "^1.2.3",
58
61
  "@iconify-json/lucide": "^1.2.123",
59
- "@types/better-sqlite3": "^9.6.0",
62
+ "@libsql/client": "^0.14.0",
60
63
  "@types/node": "^26.2.0",
61
64
  "@types/sharp": "^0.31.1",
62
65
  "@types/yaml": "^1.9.6",
@@ -67,6 +70,7 @@
67
70
  "typescript": "~5.7.2",
68
71
  "unplugin-icons": "^23.0.1",
69
72
  "vite": "^8.2.1",
70
- "vue-tsc": "^2.2.0"
73
+ "vue-tsc": "^2.2.0",
74
+ "wrangler": "^4.134.0"
71
75
  }
72
76
  }
package/schema.sql CHANGED
@@ -1,4 +1,5 @@
1
- -- Kama Core System Schema
1
+ -- Kama Core System Schema
2
+ -- Auto-generated from server/db/schema.ts
2
3
 
3
4
  CREATE TABLE IF NOT EXISTS "content_schemas" (
4
5
  id TEXT PRIMARY KEY,
@@ -197,7 +198,7 @@ CREATE INDEX IF NOT EXISTS idx_node_channels_channel ON node_channels(channelId,
197
198
  CREATE INDEX IF NOT EXISTS idx_node_channels_node ON node_channels(nodeId, channelId);
198
199
  CREATE INDEX IF NOT EXISTS idx_node_assets_node_asset ON node_assets(nodeId, assetId);
199
200
  CREATE INDEX IF NOT EXISTS idx_node_assets_asset_node ON node_assets(assetId, nodeId);
200
- CREATE INDEX IF NOT EXISTS idx_node_revisions_node ON node_revisions(nodeId, version);
201
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_node_revisions_version ON node_revisions(nodeId, version);
201
202
  CREATE INDEX IF NOT EXISTS idx_assets_system_created ON assets(isSystemVariant, createdAt DESC);
202
203
  CREATE INDEX IF NOT EXISTS idx_assets_system_mime ON assets(isSystemVariant, mimeType);
203
204
  CREATE INDEX IF NOT EXISTS idx_assets_deleted ON assets(deletedAt);
@@ -32,18 +32,15 @@ interface Props {
32
32
  [key: string]: CropVariant | undefined;
33
33
  };
34
34
  };
35
-
36
35
  image_meta?: {
37
36
  cropCoordinates?: {
38
37
  width?: number;
39
38
  height?: number;
40
39
  };
41
40
  };
42
-
43
41
  width?: number | string;
44
42
  height?: number | string;
45
43
  };
46
-
47
44
  region?: string;
48
45
  isFirst?: boolean;
49
46
  }
@@ -51,44 +48,22 @@ interface Props {
51
48
  const { data, region = "main", isFirst = false } = Astro.props;
52
49
 
53
50
  const img = data?.image;
51
+ const isValid = Boolean(img && (img.url || img.path || img.variants));
54
52
 
55
- if (!img || (!img.url && !img.path && !img.variants)) {
56
- return null;
57
- }
58
-
59
- /**
60
- * Determine whether this is likely to be LCP.
61
- */
62
53
  const isLcp = region === "header" || (region === "main" && isFirst);
63
54
 
64
- /**
65
- * Determine grid column count from the region name.
66
- */
67
55
  let columnCount = 1;
68
-
69
56
  if (region.includes("_")) {
70
57
  columnCount = 2;
71
-
72
- if (region.includes("_middle") || region.includes("_col3")) {
73
- columnCount = 3;
74
- }
75
-
76
- if (region.includes("_col4")) {
77
- columnCount = 4;
78
- }
58
+ if (region.includes("_middle") || region.includes("_col3")) columnCount = 3;
59
+ if (region.includes("_col4")) columnCount = 4;
79
60
  }
80
61
 
81
- /**
82
- * Default single-column layout.
83
- */
84
62
  let maxWidth = "1352";
85
63
  let targetColumnWidth = 1352;
86
64
  let optimizedWidths = [768, 1352];
87
65
  let sizes = "(max-width: 1399px) calc(100vw - 48px), 1352px";
88
66
 
89
- /**
90
- * Two-column layout.
91
- */
92
67
  if (columnCount === 2) {
93
68
  maxWidth = "660";
94
69
  targetColumnWidth = 660;
@@ -97,12 +72,7 @@ if (columnCount === 2) {
97
72
  "(max-width: 767px) calc(100vw - 48px), " +
98
73
  "(max-width: 1399px) calc((100vw - 80px) / 2), " +
99
74
  "660px";
100
- }
101
-
102
- /**
103
- * Three-column layout.
104
- */
105
- else if (columnCount === 3) {
75
+ } else if (columnCount === 3) {
106
76
  maxWidth = "429";
107
77
  targetColumnWidth = 429;
108
78
  optimizedWidths = [300, 429];
@@ -110,12 +80,7 @@ else if (columnCount === 3) {
110
80
  "(max-width: 767px) calc(100vw - 48px), " +
111
81
  "(max-width: 1399px) calc((100vw - 112px) / 3), " +
112
82
  "429px";
113
- }
114
-
115
- /**
116
- * Four-column layout.
117
- */
118
- else if (columnCount === 4) {
83
+ } else if (columnCount === 4) {
119
84
  maxWidth = "314";
120
85
  targetColumnWidth = 314;
121
86
  optimizedWidths = [264, 314];
@@ -126,22 +91,21 @@ else if (columnCount === 4) {
126
91
  }
127
92
  ---
128
93
 
129
- <div class="cms-block cms-block-image" style={`--max-w: ${maxWidth}px;`}>
130
- <KamaImage
131
- image={img}
132
- sizes={sizes}
133
- desktopWidths={optimizedWidths}
134
- idealWidth={targetColumnWidth}
135
- width={data.width || data.image_meta?.cropCoordinates?.width || img.width}
136
- height={data.height ||
137
- data.image_meta?.cropCoordinates?.height ||
138
- img.height}
139
- class="free-crop-image"
140
- loading={isLcp ? "eager" : "lazy"}
141
- fetchpriority={isLcp ? "high" : "auto"}
142
- decoding={isLcp ? "sync" : "async"}
143
- />
144
- </div>
94
+ {
95
+ isValid && (
96
+ <div class="cms-block cms-block-image" style={`--max-w: ${maxWidth}px;`}>
97
+ <KamaImage
98
+ image={img}
99
+ sizes={sizes}
100
+ desktopWidths={optimizedWidths}
101
+ idealWidth={targetColumnWidth}
102
+ class="free-crop-image"
103
+ loading={isLcp ? "eager" : "lazy"}
104
+ fetchpriority={isLcp ? "high" : "auto"}
105
+ />
106
+ </div>
107
+ )
108
+ }
145
109
 
146
110
  <style>
147
111
  .cms-block-image {
@@ -1,5 +1,5 @@
1
1
  ---
2
- // src/components/global/Header.astro
2
+ // templates/astro-starter/src/components/global/Header.astro
3
3
  import NavigationMenu from "@/components/global/NavigationMenu.astro";
4
4
  import { getTree, getTreeId, getChannelId } from "@/lib/cms";
5
5
  import type { TreeNode } from "@/lib/cms";
@@ -9,12 +9,12 @@ interface Props {
9
9
  tree?: TreeNode[];
10
10
  }
11
11
 
12
- const { node, tree } = Astro.props;
13
-
14
- let rawTree = tree || [];
12
+ const props = Astro.props as Props;
13
+ const currentPropsNode = props.node || {};
14
+ let rawTree: TreeNode[] = props.tree || [];
15
15
 
16
16
  // Fallback: If the layout didn't pass the tree, fetch it directly
17
- if (rawTree.length === 0) {
17
+ if (!rawTree || rawTree.length === 0) {
18
18
  const locals = Astro.locals as Record<string, any>;
19
19
  const runtimeEnv = locals?.runtime?.env || {};
20
20
  const cmsWorker = runtimeEnv.CMS_WORKER;
@@ -27,17 +27,24 @@ if (rawTree.length === 0) {
27
27
  return fetch(input, init);
28
28
  };
29
29
 
30
- rawTree = await getTree(getTreeId(), getChannelId(), customFetch);
30
+ try {
31
+ rawTree = await getTree(getTreeId(), getChannelId(), customFetch);
32
+ } catch (e) {
33
+ rawTree = [];
34
+ }
31
35
  }
32
36
 
33
37
  const homeSlug = import.meta.env.PUBLIC_HOME_SLUG || "home";
34
- const homeNodeInTree = rawTree.find(n => n.slug === homeSlug) || rawTree[0];
38
+ const homeNodeInTree =
39
+ rawTree.find(n => n.slug === homeSlug) || rawTree[0] || {};
35
40
 
36
- const siteName =
37
- node?.logoText ||
38
- homeNodeInTree?.logoText ||
39
- import.meta.env.PUBLIC_SITE_NAME ||
40
- "KAMA";
41
+ let siteName = currentPropsNode.logoText;
42
+ if (!siteName && homeNodeInTree && "logoText" in homeNodeInTree) {
43
+ siteName = homeNodeInTree.logoText as string;
44
+ }
45
+ if (!siteName) {
46
+ siteName = import.meta.env.PUBLIC_SITE_NAME || "KAMA";
47
+ }
41
48
  ---
42
49
 
43
50
  <header class="site-header">
@@ -1,8 +1,6 @@
1
1
  ---
2
2
  // src/components/ui/KamaImage.astro
3
-
4
3
  import { getMediaUrl } from "@/lib/cms";
5
- // 🎯 FIX: Updated import path to match our new folder structure
6
4
  import { buildAgnosticImageUrl } from "@/lib/media/media-url";
7
5
 
8
6
  interface Props {
@@ -25,25 +23,18 @@ const {
25
23
  fetchpriority = "auto",
26
24
  } = Astro.props;
27
25
 
28
- if (!image || (!image.url && !image.path && !image.variants)) {
29
- return null;
30
- }
26
+ const isValidImage = Boolean(
27
+ image && (image.url || image.path || image.variants),
28
+ );
31
29
 
32
- const variants = image.variants || {};
30
+ const variants = image?.variants || {};
33
31
 
34
- /**
35
- * Resolve CMS media URLs.
36
- */
37
32
  const rawMobileUrl = getMediaUrl(variants.mobile?.url);
38
33
  const rawTabletUrl = getMediaUrl(variants.tablet?.url);
39
34
  const rawDesktopUrl = getMediaUrl(
40
- variants.standard?.url || image.url || image.path,
35
+ variants.standard?.url || image?.url || image?.path,
41
36
  );
42
37
 
43
- /**
44
- * Resolve dimensions.
45
- * Crop dimensions take precedence over original image dimensions.
46
- */
47
38
  const mobileW = Number(variants.mobile?.cropCoordinates?.width || 768);
48
39
  const mobileH = Number(variants.mobile?.cropCoordinates?.height || 576);
49
40
 
@@ -51,32 +42,25 @@ const tabletW = Number(variants.tablet?.cropCoordinates?.width || 1024);
51
42
  const tabletH = Number(variants.tablet?.cropCoordinates?.height || 512);
52
43
 
53
44
  const baseWidth = Number(
54
- variants.standard?.cropCoordinates?.width || image.width || idealWidth,
45
+ variants.standard?.cropCoordinates?.width || image?.width || idealWidth,
55
46
  );
56
47
  const baseHeight = Number(
57
48
  variants.standard?.cropCoordinates?.height ||
58
- image.height ||
49
+ image?.height ||
59
50
  Math.round(idealWidth * 0.5),
60
51
  );
61
52
 
62
- /**
63
- * Build responsive srcset using query-based media proxy URLs (/media/foo.jpg?w=768&q=75&f=auto)
64
- */
65
53
  function buildAgnosticSrcset(
66
54
  url: string,
67
55
  targetWidths: number[],
68
56
  quality = 75,
69
57
  ): string {
70
58
  if (!url) return "";
71
-
72
59
  return targetWidths
73
60
  .map(width => `${buildAgnosticImageUrl(url, width, quality)} ${width}w`)
74
61
  .join(", ");
75
62
  }
76
63
 
77
- /**
78
- * Build srcsets.
79
- */
80
64
  const mobileSrcset = rawMobileUrl
81
65
  ? buildAgnosticSrcset(rawMobileUrl, [400, 768])
82
66
  : null;
@@ -89,61 +73,53 @@ const desktopSrcset = rawDesktopUrl
89
73
  ? buildAgnosticSrcset(rawDesktopUrl, desktopWidths)
90
74
  : null;
91
75
 
92
- /**
93
- * Fallback image URL: /media/foo.jpg?w=1352&q=75&f=auto
94
- */
95
76
  const absoluteFallback = rawDesktopUrl
96
77
  ? buildAgnosticImageUrl(rawDesktopUrl, idealWidth, 75)
97
78
  : "";
98
79
  ---
99
80
 
100
- <picture>
101
- {/* Mobile Variant (< 768px) */}
102
- {
103
- mobileSrcset && (
104
- <source
105
- media="(max-width: 767px)"
106
- srcset={mobileSrcset}
107
- sizes="(max-width: 767px) calc(100vw - 48px), 768px"
108
- width={mobileW}
109
- height={mobileH}
110
- />
111
- )
112
- }
113
-
114
- {/* Tablet Variant (768px - 1024px) */}
115
- {
116
- tabletSrcset && (
117
- <source
118
- media="(min-width: 768px) and (max-width: 1024px)"
119
- srcset={tabletSrcset}
120
- sizes="(max-width: 1024px) calc(100vw - 48px), 1024px"
121
- width={tabletW}
122
- height={tabletH}
123
- />
124
- )
125
- }
126
-
127
- {/* Desktop Variant (> 1024px) */}
128
- {
129
- desktopSrcset && (
130
- <source
131
- srcset={desktopSrcset}
132
- sizes={sizes}
81
+ {
82
+ isValidImage && (
83
+ <picture>
84
+ {Boolean(mobileSrcset) && (
85
+ <source
86
+ media="(max-width: 767px)"
87
+ srcset={mobileSrcset as string}
88
+ sizes="(max-width: 767px) calc(100vw - 48px), 768px"
89
+ width={mobileW}
90
+ height={mobileH}
91
+ />
92
+ )}
93
+
94
+ {Boolean(tabletSrcset) && (
95
+ <source
96
+ media="(min-width: 768px) and (max-width: 1024px)"
97
+ srcset={tabletSrcset as string}
98
+ sizes="(max-width: 1024px) calc(100vw - 48px), 1024px"
99
+ width={tabletW}
100
+ height={tabletH}
101
+ />
102
+ )}
103
+
104
+ {Boolean(desktopSrcset) && (
105
+ <source
106
+ srcset={desktopSrcset as string}
107
+ sizes={sizes}
108
+ width={baseWidth}
109
+ height={baseHeight}
110
+ />
111
+ )}
112
+
113
+ <img
114
+ src={absoluteFallback}
115
+ alt={image.alt || image.filename || ""}
133
116
  width={baseWidth}
134
117
  height={baseHeight}
118
+ loading={loading}
119
+ fetchpriority={fetchpriority}
120
+ decoding={loading === "eager" ? "sync" : "async"}
121
+ class={className}
135
122
  />
136
- )
137
- }
138
-
139
- <img
140
- src={absoluteFallback}
141
- alt={image.alt || image.filename || ""}
142
- width={baseWidth}
143
- height={baseHeight}
144
- loading={loading}
145
- fetchpriority={fetchpriority}
146
- decoding={loading === "eager" ? "sync" : "async"}
147
- class={className}
148
- />
149
- </picture>
123
+ </picture>
124
+ )
125
+ }