@cruxplug/spa 0.1.2 → 0.1.4

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/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
  </div>
9
9
 
10
10
  <div align="center">
11
- <img src="https://img.shields.io/badge/v-0.1.2-black"/>
11
+ <img src="https://img.shields.io/badge/v-0.1.4-black"/>
12
12
  <a href="https://github.com/cruxjs-org"><img src="https://img.shields.io/badge/🔥-@cruxjs-black"/></a>
13
13
  <br>
14
14
  <img src="https://img.shields.io/badge/coverage-~%25-brightgreen" alt="Test Coverage" />
package/dist/index.cjs CHANGED
@@ -1,15 +1,15 @@
1
- 'use strict';var server=require('@minejs/server'),i18n=require('@minejs/i18n');function d(e,t="",n="en"){if(!e)return t;if(e.includes(".")&&/^[a-z0-9._]+$/.test(e))try{return server.tLang(n,e,void 0)||e}catch{return e}return e}function g(e,t="",n="en"){return d(e,t,n)}function w(e,t="en"){return !e||e.length===0?"":e.map(r=>d(r,"",t)).filter(Boolean).join(", ")}function y(e,t="en"){if(!e)return "Page";let n=d(e,"",t);try{return v(n,t)}catch{return n}}function v(e,t="en"){let n=server.tLang(t,"app.name",void 0);return i18n.isRTL()?`${n} - ${e}`:`${e} - ${n}`}function S(e,t,n="en"){let r=e.canonical||`${t.baseUrl}${e.path}`,a=e.robots||t.defaultRobots||"index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1",i=y(e.title,n),c=g(e.description,t.defaultDescription||"A modern single-page application",n),o=w(e.keywords||t.defaultKeywords,n),s=g(e.expertise,"",n),p=g(e.experience,"",n),l=g(e.authority,"",n),u=e.contentType==="article"?"article":"website";return `<!-- \u{1F50D} Core SEO Meta Tags -->
1
+ 'use strict';var server=require('@minejs/server'),i18n=require('@minejs/i18n');async function d(t,e="",n="en"){if(!t)return e;if(t.includes(".")&&/^[a-z0-9._]+$/.test(t))try{return await server.tLangAsync(n,t,void 0)||t}catch{return t}return t}async function m(t,e="",n="en"){return await d(t,e,n)}async function E(t,e="en"){return !t||t.length===0?"":t.map(async r=>await d(r,"",e)).filter(Boolean).join(", ")}async function P(t,e="en"){if(!t)return "Page";let n=await d(t,"",e);try{return v(await n,e)}catch{return n}}async function v(t,e="en"){let n=await server.tLangAsync(e,"app.name",void 0);return i18n.isRTL()?`${n} - ${t}`:`${t} - ${n}`}async function S(t,e,n="en"){let r=t.canonical||`${e.baseUrl}${t.path}`,o=t.robots||e.defaultRobots||"index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1",i=await P(t.title,n),s=await m(t.description,e.defaultDescription||"A modern single-page application",n),a=await E(t.keywords||e.defaultKeywords,n),c=await m(t.expertise,"",n),p=await m(t.experience,"",n),l=await m(t.authority,"",n),u=t.contentType==="article"?"article":"website";return `<!-- \u{1F50D} Core SEO Meta Tags -->
2
2
  <meta charset="UTF-8" />
3
3
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
4
4
  <title>${i}</title>
5
- <meta name="description" content="${c}" />
6
- ${o?`<meta name="keywords" content="${o}" />`:""}
7
- <meta name="robots" content="${a}" />
5
+ <meta name="description" content="${s}" />
6
+ ${a?`<meta name="keywords" content="${a}" />`:""}
7
+ <meta name="robots" content="${o}" />
8
8
  <meta name="language" content="en" />
9
9
  <meta http-equiv="content-language" content="en-us" />
10
10
  <!-- \u{1F465} E-E-A-T Signals for AI Search -->
11
- ${t.author?`<meta name="author" content="${t.author}" />`:""}
12
- ${s?`<meta name="expertise" content="${s}" />`:""}
11
+ ${e.author?`<meta name="author" content="${e.author}" />`:""}
12
+ ${c?`<meta name="expertise" content="${c}" />`:""}
13
13
  ${p?`<meta name="experience" content="${p}" />`:""}
14
14
  ${l?`<meta name="authority" content="${l}" />`:""}
15
15
  <!-- \u{1F4F1} Mobile & Performance -->
@@ -19,7 +19,7 @@
19
19
  <meta name="theme-color" content="#000000" />
20
20
  <!-- \u{1F517} Canonical & Prefetch -->
21
21
  <link rel="canonical" href="${r}" />
22
- ${(e.clientScriptPath||t.clientScriptPath)?.map(m=>`<link rel="prefetch" href="${m}" />`).join(`
22
+ ${(t.clientScriptPath||e.clientScriptPath)?.map(g=>`<link rel="prefetch" href="${g}" />`).join(`
23
23
  `)}
24
24
  <!-- \u26A1 Performance & Security -->
25
25
  <meta name="format-detection" content="telephone=no" />
@@ -27,31 +27,31 @@
27
27
  <!-- \u{1F4D8} Open Graph Protocol (Social Media) -->
28
28
  <meta property="og:type" content="${u}" />
29
29
  <meta property="og:title" content="${i}" />
30
- <meta property="og:description" content="${c}" />
30
+ <meta property="og:description" content="${s}" />
31
31
  <meta property="og:url" content="${r}" />
32
32
  <meta property="og:locale" content="en_US" />
33
- ${t.author?`<meta property="og:site_name" content="${t.author}" />`:""}
34
- ${e.ogImage?`<meta property="og:image" content="${e.ogImage}" />`:""}
35
- ${e.ogImage?`<meta property="og:image:alt" content="${i}" />`:""}
36
- `}function x(e,t,n="WebPage",r="en"){let a=e.canonical||`${t.baseUrl}${e.path}`,i=y(e.title,r),c=g(e.description,t.defaultDescription,r),o=g(e.expertise,"",r),s=g(e.experience,"",r),p=g(e.authority,"",r),l={"@context":"https://schema.org","@type":n,name:i,url:a,description:c,inLanguage:"en",...e.contentType&&{genre:e.contentType},...t.author&&{author:{"@type":"Person",name:t.author,...t.authorUrl&&{url:t.authorUrl}}},...(o||s||p)&&{creator:{"@type":"Person",name:t.author||"Unknown",...o&&{expertise:o},...s&&{experience:s},...p&&{authority:p}}}};return `<script type="application/ld+json">
33
+ ${e.author?`<meta property="og:site_name" content="${e.author}" />`:""}
34
+ ${t.ogImage?`<meta property="og:image" content="${t.ogImage}" />`:""}
35
+ ${t.ogImage?`<meta property="og:image:alt" content="${i}" />`:""}
36
+ `}async function x(t,e,n="WebPage",r="en"){let o=t.canonical||`${e.baseUrl}${t.path}`,i=await P(t.title,r),s=await m(t.description,e.defaultDescription,r),a=await m(t.expertise,""),c=await m(t.experience,""),p=await m(t.authority,""),l={"@context":"https://schema.org","@type":n,name:i,url:o,description:s,inLanguage:"en",...t.contentType&&{genre:t.contentType},...e.author&&{author:{"@type":"Person",name:e.author,...e.authorUrl&&{url:e.authorUrl}}},...(await a||await c||p)&&{creator:{"@type":"Person",name:e.author||"Unknown",...a&&{expertise:a},...c&&{experience:c},...p&&{authority:p}}}};return `<script type="application/ld+json">
37
37
  ${JSON.stringify(l,null,2).split(`
38
- `).map(m=>m&&` ${m}`).join(`
38
+ `).map(g=>g&&` ${g}`).join(`
39
39
  `)}
40
- </script>`}var R=["area","base","br","col","embed","hr","img","input","link","meta","param","source","track","wbr"];function k(e){let t=[],n=/(<[^>]+>)|([^<]+)/g,r;for(;(r=n.exec(e))!==null;)r[1]?t.push({type:"tag",value:r[1]}):r[2]&&r[2].trim().length>0&&t.push({type:"text",value:r[2]});return t}function L(e,t=4){let n=" ".repeat(t),r=[],a=0;for(let i=0;i<e.length;i++){let c=e[i],o=c.value.trim();o.startsWith("</")&&(a=Math.max(0,a-1));let s=n.repeat(a);if(!(c.type==="text"&&o.length===0)&&(c.type==="text"&&r.length>0&&!r[r.length-1].trim().endsWith(">")?r[r.length-1]+=o:r.push(s+o),o.startsWith("<")&&!o.startsWith("</"))){if(o.startsWith("<!DOCTYPE")||o.startsWith("<!--"))continue;let p=o.match(/<([A-Za-z][A-Za-z0-9\\-]*)/i),l=p?p[1].toLowerCase():"",u=R.includes(l),m=o.endsWith("/>");!u&&!m&&a++;}}return r}function A(e,t=4){let n=e.split(`
41
- `).map(i=>i.trim()).filter(i=>i.length>0).join(""),r=k(n);return L(r,t).join(`
42
- `)}function M(e){let t={defaultLanguage:e?.defaultLanguage||"en",supportedLanguages:e?.supportedLanguages||["en"],basePath:e?.basePath||"i18n/",fileExtension:e?.fileExtension||"json"},n=t.basePath;return n=n.replace(/\\/g,"/"),n.includes("shared/static")?n="static"+(n.split("shared/static")[1]||""):n.startsWith("./")?n=n.slice(2):n.startsWith("/")&&(n=n.slice(1)),n.endsWith("/")||(n+="/"),`<meta name="app-i18n" content='${JSON.stringify({defaultLanguage:t.defaultLanguage,supportedLanguages:t.supportedLanguages,basePath:n,fileExtension:t.fileExtension})}' />`}function f(e,t,n,r="en"){let a=e.clientScriptPath||t.clientScriptPath,i=e.clientStylePath||t.clientStylePath||[],c=a.map(u=>`<script type="module" src="${u}"></script>`).join(`
43
- `),o=i.map(u=>`<link rel="stylesheet" href="${u}" />`).join(`
44
- `),s=M(n);s||console.warn("[SPA] WARNING: i18n meta tag is empty!");let p=`<!DOCTYPE html>
40
+ </script>`}var R=["area","base","br","col","embed","hr","img","input","link","meta","param","source","track","wbr"];function k(t){let e=[],n=/(<[^>]+>)|([^<]+)/g,r;for(;(r=n.exec(t))!==null;)r[1]?e.push({type:"tag",value:r[1]}):r[2]&&r[2].trim().length>0&&e.push({type:"text",value:r[2]});return e}function L(t,e=4){let n=" ".repeat(e),r=[],o=0;for(let i=0;i<t.length;i++){let s=t[i],a=s.value.trim();a.startsWith("</")&&(o=Math.max(0,o-1));let c=n.repeat(o);if(!(s.type==="text"&&a.length===0)&&(s.type==="text"&&r.length>0&&!r[r.length-1].trim().endsWith(">")?r[r.length-1]+=a:r.push(c+a),a.startsWith("<")&&!a.startsWith("</"))){if(a.startsWith("<!DOCTYPE")||a.startsWith("<!--"))continue;let p=a.match(/<([A-Za-z][A-Za-z0-9\\-]*)/i),l=p?p[1].toLowerCase():"",u=R.includes(l),g=a.endsWith("/>");!u&&!g&&o++;}}return r}function w(t,e=4){let n=t.split(`
41
+ `).map(i=>i.trim()).filter(i=>i.length>0).join(""),r=k(n);return L(r,e).join(`
42
+ `)}function M(t){let e={defaultLanguage:t?.defaultLanguage||"en",supportedLanguages:t?.supportedLanguages||["en"],basePath:t?.basePath||"i18n/",fileExtension:t?.fileExtension||"json"},n=e.basePath;return n=n.replace(/\\/g,"/"),n.includes("shared/static")?n="static"+(n.split("shared/static")[1]||""):n.startsWith("./")?n=n.slice(2):n.startsWith("/")&&(n=n.slice(1)),n.endsWith("/")||(n+="/"),`<meta name="app-i18n" content='${JSON.stringify({defaultLanguage:e.defaultLanguage,supportedLanguages:e.supportedLanguages,basePath:n,fileExtension:e.fileExtension})}' />`}async function y(t,e,n,r="en"){let o=t.clientScriptPath||e.clientScriptPath,i=t.clientStylePath||e.clientStylePath||[],s=o.map(u=>`<script type="module" src="${u}"></script>`).join(`
43
+ `),a=i.map(u=>`<link rel="stylesheet" href="${u}" />`).join(`
44
+ `),c=M(n);c||console.warn("[SPA] WARNING: i18n meta tag is empty!");let p=`<!DOCTYPE html>
45
45
  <html>
46
46
  <head>
47
- ${S(e,t,r)}
48
- ${x(e,t,e.contentType==="article"?"Article":"WebPage",r)}
49
- ${s}
50
- ${o}
47
+ ${await S(t,e,r)}
48
+ ${await x(t,e,t.contentType==="article"?"Article":"WebPage",r)}
49
+ ${c}
50
+ ${a}
51
51
  </head>
52
52
  <body>
53
53
  <div id="app"></div>
54
- ${c}
54
+ ${s}
55
55
  </body>
56
- </html>`;p.includes("app-i18n")||console.error("[SPA] ERROR: i18n meta tag NOT in raw HTML!");let l=A(p);return l.includes("app-i18n")||console.error("[SPA] ERROR: i18n meta tag LOST during formatting!"),l}function h(e,t,n){return {method:"GET",path:e.path,handler:r=>{let a=f(e,t,n,r.lang||"en");return r.html(a)}}}function _(){return global.__cruxjs_i18n_config}function $(e){global.__cruxjs_i18n_config=e;}function j(e,t,n,r){if(r.startsWith("/api/"))return {status:e,headers:{"Content-Type":"application/json"},body:JSON.stringify({error:`Error ${e}`})};if(t.has(e)){let a=t.get(e),i=_();console.log(`[Errors] Generating error page ${e} with i18n:`,!!i);let c=f(a,n,i);return {status:e,headers:{"Content-Type":"text/html; charset=utf-8"},body:c}}return console.log(`[Errors] No custom error page for ${e}, returning fallback`),{status:e,headers:{"Content-Type":"text/plain"},body:`Error ${e}`}}function b(e,t){return (n,r)=>{let a=j(n,e,t,r);return new Response(a.body,{status:a.status,headers:a.headers})}}function T(){return {statusCode:404,title:"404 - Page Not Found",path:"/404",description:"The page you are looking for could not be found.",keywords:["404","not found","error"],robots:"noindex, nofollow"}}function K(e,t){let n=[],r=new Map;$(t?.i18n);let a=t?.i18n;if(e.pages&&e.pages.length>0)for(let o of e.pages)n.push(h(o,e,a));if(e.errorPages&&e.errorPages.length>0)for(let o of e.errorPages)r.set(o.statusCode,o),n.push(h(o,e,a));if(e.enableAutoNotFound&&!r.has(404)){let o=T();r.set(404,o),n.push(h(o,e,a));}let i=b(r,e);return {name:"@cruxplug/SPA",version:"0.1.2",routes:n,__spaErrorHandler:i,onRegister:async o=>{if(console.log(`[SPA Plugin] Registered ${n.length} SPA routes`),r.size>0){let s=Array.from(r.keys()).join(", ");console.log(`[SPA Plugin] Error pages configured for: ${s}`);}},onAwake:async o=>{console.log("[SPA Plugin] Awake phase - SPA routes ready");},onStart:async o=>{console.log("[SPA Plugin] Start phase - serving SPA");},onReady:async o=>{console.log("[SPA Plugin] Ready phase - SPA is fully operational");}}}exports.serverSPA=K;//# sourceMappingURL=index.cjs.map
56
+ </html>`;p.includes("app-i18n")||console.error("[SPA] ERROR: i18n meta tag NOT in raw HTML!");let l=w(p);return l.includes("app-i18n")||console.error("[SPA] ERROR: i18n meta tag LOST during formatting!"),l}function h(t,e,n){return {method:"GET",path:t.path,handler:async r=>{let o=await y(t,e,n,r.lang||"en");return r.html(o)}}}function _(){return global.__cruxjs_i18n_config}function $(t){global.__cruxjs_i18n_config=t;}async function j(t,e,n,r){if(r.startsWith("/api/"))return {status:t,headers:{"Content-Type":"application/json"},body:JSON.stringify({error:`Error ${t}`})};if(e.has(t)){let o=e.get(t),i=_();console.log(`[Errors] Generating error page ${t} with i18n:`,!!i);let s=await y(o,n,i);return {status:t,headers:{"Content-Type":"text/html; charset=utf-8"},body:s}}return console.log(`[Errors] No custom error page for ${t}, returning fallback`),{status:t,headers:{"Content-Type":"text/plain"},body:`Error ${t}`}}function A(t,e){return async(n,r)=>{let o=await j(n,t,e,r);return new Response(o.body,{status:o.status,headers:o.headers})}}function b(){return {statusCode:404,title:"404 - Page Not Found",path:"/404",description:"The page you are looking for could not be found.",keywords:["404","not found","error"],robots:"noindex, nofollow"}}function K(t,e){let n=[],r=new Map;$(e?.i18n);let o=e?.i18n;if(t.pages&&t.pages.length>0)for(let a of t.pages)n.push(h(a,t,o));if(t.errorPages&&t.errorPages.length>0)for(let a of t.errorPages)r.set(a.statusCode,a),n.push(h(a,t,o));if(t.enableAutoNotFound&&!r.has(404)){let a=b();r.set(404,a),n.push(h(a,t,o));}let i=A(r,t);return {name:"@cruxplug/SPA",version:"0.1.4",routes:n,__spaErrorHandler:i,onRegister:async a=>{if(r.size>0){Array.from(r.keys()).join(", ");}},onAwake:async a=>{},onStart:async a=>{},onReady:async a=>{}}}exports.serverSPA=K;//# sourceMappingURL=index.cjs.map
57
57
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/utils/seo.ts","../src/utils/htmlFormatter.ts","../src/utils/spa.ts","../src/utils/errors.ts","../src/index.ts"],"names":["parseValue","value","defaultValue","lang","tLang","resolveMetaValue","resolveKeywords","keywords","kw","resolvePageTitle","title","parsedTitle","genPageTitle","val","appName","isRTL","generateSEOMetaTags","config","baseConfig","canonicalUrl","robots","description","expertise","experience","authority","ogType","script","generateStructuredData","pageConfig","contentType","schema","line","VOID_ELEMENTS","tokenize","html","tokens","regex","match","processTokens","indentSize","indent","output","indentLevel","token","padding","tagMatch","tagName","isVoidElement","isSelfClosing","formatHTML","normalized","generateI18nMetaTag","i18nConfig","browserPath","generateSPAHTML","clientScripts","clientStyles","scriptTags","styleTags","style","i18nMetaTag","rawHTML","formatted","createSPARoute","c","getGlobalI18nConfig","setGlobalI18nConfig","buildErrorResponse","statusCode","errorPageMap","path","errorConfig","createErrorHandler","errorResponse","createDefault404Page","serverSPA","appConfig","routes","errorPageConfig","defaultErrorPage","errorHandler","app","statusCodes","ctx"],"mappings":"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","file":"index.cjs","sourcesContent":["// src/utils/seo.ts\r\n//\r\n// Made with ❤️ by Maysara.\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ PACK ════════════════════════════════════════╗\r\n\r\n import type { SPAPageConfig, ServerSPAPluginConfig } from '../types';\r\n import { tLang } from '@minejs/server';\r\n import { isRTL } from '@minejs/i18n';\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ UTIL ════════════════════════════════════════╗\r\n\r\n /**\r\n * Smart parser for string values\r\n *\r\n * Intelligently detects if a string is a translation key or plain text:\r\n * - Translation key format (e.g., 'meta.home.title'): must contain dots, attempts translation via t(key)\r\n * - Plain text (e.g., 'My Title', 'cruxjs', 'framework'): returns as-is\r\n *\r\n * Key format: MUST contain at least one dot (.) to be considered a translation key\r\n * Examples:\r\n * - 'meta.home.title' → translation key → tries t('meta.home.title')\r\n * - 'cruxjs' → plain text → returns 'cruxjs'\r\n * - 'My Title' → plain text → returns 'My Title'\r\n *\r\n * @param value - The string to parse\r\n * @param defaultValue - Fallback if value is empty\r\n * @returns Translated value or original string\r\n */\r\n function parseValue(value: string | undefined, defaultValue: string = '', lang: string = 'en'): string {\r\n if (!value) return defaultValue;\r\n\r\n // Check if value looks like a translation key (e.g., 'meta.home.title')\r\n // Key pattern: MUST contain at least one dot AND be lowercase/numeric/underscores/dots\r\n const isTranslationKey = value.includes('.') && /^[a-z0-9._]+$/.test(value);\r\n\r\n if (isTranslationKey) {\r\n try {\r\n const translated = tLang(lang, value, undefined);\r\n // If translation returns a value, use it; otherwise fall back to original\r\n return translated || value;\r\n } catch {\r\n // If translation fails, return the original value\r\n return value;\r\n }\r\n }\r\n\r\n // Not a translation key format, return as direct text\r\n return value;\r\n }\r\n\r\n /**\r\n * Resolve meta tag values with smart translation detection\r\n * Uses parseValue() to automatically detect and translate\r\n */\r\n function resolveMetaValue(value: string | undefined, defaultValue: string = '', lang: string = 'en'): string {\r\n return parseValue(value, defaultValue, lang);\r\n }\r\n\r\n /**\r\n * Resolve keywords with smart translation detection\r\n * Uses parseValue() on each keyword to automatically detect and translate\r\n */\r\n function resolveKeywords(keywords: string[] | undefined, lang: string = 'en'): string {\r\n if (!keywords || keywords.length === 0) return '';\r\n\r\n const resolved = keywords\r\n .map(kw => parseValue(kw, '', lang))\r\n .filter(Boolean);\r\n\r\n return resolved.join(', ');\r\n }\r\n\r\n /**\r\n * Generate page title with translation support\r\n *\r\n * Uses genPageTitle() from @minejs/i18n for RTL-aware titles\r\n * First parses the value to detect and translate if needed\r\n */\r\n function resolvePageTitle(title: string | undefined, lang: string = 'en'): string {\r\n if (!title) return 'Page';\r\n\r\n // First parse the value to detect translation keys\r\n const parsedTitle = parseValue(title, '', lang);\r\n // Use genPageTitle for RTL-aware title generation\r\n try {\r\n return genPageTitle(parsedTitle, lang);\r\n } catch {\r\n // Fallback: if genPageTitle fails, use parsed value\r\n return parsedTitle;\r\n }\r\n }\r\n\r\n /**\r\n * Generate page title with proper RTL handling\r\n *\r\n * @example\r\n * // English: \"Profile - MyApp\"\r\n * // Arabic: \"MyApp - الملف الشخصي\"\r\n */\r\n export function genPageTitle(val: string, lang: string = 'en'): string {\r\n const appName = tLang(lang, 'app.name', undefined);\r\n return isRTL() ? `${appName} - ${val}` : `${val} - ${appName}`;\r\n }\r\n\r\n\r\n /**\r\n * Generate SEO Meta Tags with E-E-A-T signals and translation support\r\n *\r\n * Includes:\r\n * - Core SEO metadata (charset, viewport, description, keywords, robots)\r\n * - E-E-A-T signals (expertise, experience, authority)\r\n * - Mobile optimization (web app capable, status bar style)\r\n * - Performance & security (prefetch, x-ua-compatible)\r\n * - Open Graph protocol tags\r\n * - Translation support for all meta values\r\n */\r\n export function generateSEOMetaTags(\r\n config: SPAPageConfig,\r\n baseConfig: ServerSPAPluginConfig,\r\n lang: string = 'en'\r\n ): string {\r\n const canonicalUrl = config.canonical || `${baseConfig.baseUrl}${config.path}`;\r\n const robots = config.robots || baseConfig.defaultRobots || 'index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1';\r\n\r\n // Resolve translated values\r\n const title = resolvePageTitle(config.title, lang);\r\n const description = resolveMetaValue(config.description, baseConfig.defaultDescription || 'A modern single-page application', lang);\r\n const keywords = resolveKeywords(config.keywords || baseConfig.defaultKeywords, lang);\r\n const expertise = resolveMetaValue(config.expertise, '', lang);\r\n const experience = resolveMetaValue(config.experience, '', lang);\r\n const authority = resolveMetaValue(config.authority, '', lang);\r\n\r\n // Determine OG type based on content type\r\n const ogType = config.contentType === 'article' ? 'article' : 'website';\r\n\r\n return `<!-- 🔍 Core SEO Meta Tags -->\r\n <meta charset=\"UTF-8\" />\r\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\r\n <title>${title}</title>\r\n <meta name=\"description\" content=\"${description}\" />\r\n ${keywords ? `<meta name=\"keywords\" content=\"${keywords}\" />` : ''}\r\n <meta name=\"robots\" content=\"${robots}\" />\r\n <meta name=\"language\" content=\"en\" />\r\n <meta http-equiv=\"content-language\" content=\"en-us\" />\r\n <!-- 👥 E-E-A-T Signals for AI Search -->\r\n ${baseConfig.author ? `<meta name=\"author\" content=\"${baseConfig.author}\" />` : ''}\r\n ${expertise ? `<meta name=\"expertise\" content=\"${expertise}\" />` : ''}\r\n ${experience ? `<meta name=\"experience\" content=\"${experience}\" />` : ''}\r\n ${authority ? `<meta name=\"authority\" content=\"${authority}\" />` : ''}\r\n <!-- 📱 Mobile & Performance -->\r\n <meta name=\"mobile-web-app-capable\" content=\"yes\" />\r\n <meta name=\"apple-mobile-web-app-capable\" content=\"yes\" />\r\n <meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black-translucent\" />\r\n <meta name=\"theme-color\" content=\"#000000\" />\r\n <!-- 🔗 Canonical & Prefetch -->\r\n <link rel=\"canonical\" href=\"${canonicalUrl}\" />\r\n ${(config.clientScriptPath || baseConfig.clientScriptPath)?.map(script => `<link rel=\"prefetch\" href=\"${script}\" />`).join('\\n')}\r\n <!-- ⚡ Performance & Security -->\r\n <meta name=\"format-detection\" content=\"telephone=no\" />\r\n <meta http-equiv=\"x-ua-compatible\" content=\"IE=edge\" />\r\n <!-- 📘 Open Graph Protocol (Social Media) -->\r\n <meta property=\"og:type\" content=\"${ogType}\" />\r\n <meta property=\"og:title\" content=\"${title}\" />\r\n <meta property=\"og:description\" content=\"${description}\" />\r\n <meta property=\"og:url\" content=\"${canonicalUrl}\" />\r\n <meta property=\"og:locale\" content=\"en_US\" />\r\n ${baseConfig.author ? `<meta property=\"og:site_name\" content=\"${baseConfig.author}\" />` : ''}\r\n ${config.ogImage ? `<meta property=\"og:image\" content=\"${config.ogImage}\" />` : ''}\r\n ${config.ogImage ? `<meta property=\"og:image:alt\" content=\"${title}\" />` : ''}\r\n `;\r\n }\r\n\r\n /**\r\n * Generate JSON-LD Structured Data\r\n *\r\n * Creates schema.org compatible structured data for:\r\n * - Rich snippets in search results\r\n * - AI overviews and knowledge panels\r\n * - Better indexing and SEO\r\n *\r\n * Supports multiple content types: WebPage, Article, Product, Service, etc.\r\n * Handles translation keys in meta values\r\n */\r\n export function generateStructuredData(\r\n pageConfig: SPAPageConfig,\r\n baseConfig: ServerSPAPluginConfig,\r\n contentType: string = 'WebPage',\r\n lang: string = 'en'\r\n ): string {\r\n const canonicalUrl = pageConfig.canonical || `${baseConfig.baseUrl}${pageConfig.path}`;\r\n\r\n // Resolve translated values for structured data\r\n const title = resolvePageTitle(pageConfig.title, lang);\r\n const description = resolveMetaValue(pageConfig.description, baseConfig.defaultDescription, lang);\r\n const expertise = resolveMetaValue(pageConfig.expertise, '', lang);\r\n const experience = resolveMetaValue(pageConfig.experience, '', lang);\r\n const authority = resolveMetaValue(pageConfig.authority, '', lang);\r\n\r\n const schema = {\r\n '@context': 'https://schema.org',\r\n '@type': contentType,\r\n 'name': title,\r\n 'url': canonicalUrl,\r\n 'description': description,\r\n 'inLanguage': 'en',\r\n ...(pageConfig.contentType && { 'genre': pageConfig.contentType }),\r\n ...(baseConfig.author && {\r\n 'author': {\r\n '@type': 'Person',\r\n 'name': baseConfig.author,\r\n ...(baseConfig.authorUrl && { 'url': baseConfig.authorUrl })\r\n }\r\n }),\r\n ...((expertise || experience || authority) && {\r\n 'creator': {\r\n '@type': 'Person',\r\n 'name': baseConfig.author || 'Unknown',\r\n ...(expertise && { 'expertise': expertise }),\r\n ...(experience && { 'experience': experience }),\r\n ...(authority && { 'authority': authority })\r\n }\r\n })\r\n };\r\n\r\n const jsonStr = JSON.stringify(schema, null, 2)\r\n .split('\\n')\r\n .map(line => line ? ` ${line}` : line)\r\n .join('\\n');\r\n return `<script type=\"application/ld+json\">\\n${jsonStr}\\n</script>`;\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n","// src/utils/htmlFormatter.ts\r\n//\r\n// Made with ❤️ by Maysara.\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ CORE ════════════════════════════════════════╗\r\n\r\n const VOID_ELEMENTS = ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr'];\r\n\r\n interface Token {\r\n type: 'tag' | 'text' | 'comment';\r\n value: string;\r\n }\r\n\r\n /**\r\n * Tokenize HTML into tags, text, and comments\r\n */\r\n function tokenize(html: string): Token[] {\r\n const tokens: Token[] = [];\r\n const regex = /(<[^>]+>)|([^<]+)/g;\r\n let match;\r\n\r\n while ((match = regex.exec(html)) !== null) {\r\n if (match[1]) {\r\n // It's a tag\r\n tokens.push({ type: 'tag', value: match[1] });\r\n } else if (match[2] && match[2].trim().length > 0) {\r\n // It's text content (not just whitespace)\r\n tokens.push({ type: 'text', value: match[2] });\r\n }\r\n }\r\n\r\n return tokens;\r\n }\r\n\r\n /**\r\n * Process tokens and apply indentation\r\n */\r\n function processTokens(tokens: Token[], indentSize: number = 4): string[] {\r\n const indent = ' '.repeat(indentSize);\r\n const output: string[] = [];\r\n let indentLevel = 0;\r\n\r\n // eslint-disable-next-line @typescript-eslint/prefer-for-of\r\n for (let i = 0; i < tokens.length; i++) {\r\n const token = tokens[i];\r\n const value = token.value.trim();\r\n\r\n // Decrease indent for closing tags BEFORE adding\r\n if (value.startsWith('</')) {\r\n indentLevel = Math.max(0, indentLevel - 1);\r\n }\r\n\r\n const padding = indent.repeat(indentLevel);\r\n\r\n // Skip empty text nodes\r\n if (token.type === 'text' && value.length === 0) {\r\n continue;\r\n }\r\n\r\n // Add to output\r\n if (token.type === 'text') {\r\n // Text nodes - check if previous line exists\r\n if (output.length > 0 && !output[output.length - 1].trim().endsWith('>')) {\r\n // Append to previous line if it doesn't end with tag\r\n output[output.length - 1] += value;\r\n } else {\r\n output.push(padding + value);\r\n }\r\n } else {\r\n // Tags and comments\r\n output.push(padding + value);\r\n }\r\n\r\n // Increase indent for OPENING tags ONLY (not closing, not void elements, not DOCTYPE/comments)\r\n if (value.startsWith('<') && !value.startsWith('</')) {\r\n // Skip DOCTYPE and comments - they don't increase indent\r\n if (value.startsWith('<!DOCTYPE') || value.startsWith('<!--')) {\r\n continue;\r\n }\r\n\r\n // Extract tag name\r\n const tagMatch = value.match(/<([A-Za-z][A-Za-z0-9\\\\-]*)/i);\r\n const tagName = tagMatch ? tagMatch[1].toLowerCase() : '';\r\n\r\n // Check if it's a void/self-closing element\r\n const isVoidElement = VOID_ELEMENTS.includes(tagName);\r\n const isSelfClosing = value.endsWith('/>');\r\n\r\n // Only increase indent if it's NOT a void element AND NOT self-closing\r\n if (!isVoidElement && !isSelfClosing) {\r\n indentLevel++;\r\n }\r\n }\r\n }\r\n\r\n return output;\r\n }\r\n\r\n /**\r\n * Format HTML with proper indentation and cleanup\r\n *\r\n * - Tokenizes HTML into tags and content\r\n * - Applies proper indentation (4 spaces per level)\r\n * - Handles void elements correctly\r\n * - Preserves script and style tag content\r\n * - Maintains tag hierarchy\r\n *\r\n * @param html Raw HTML string\r\n * @param indentSize Number of spaces per indent level (default: 4)\r\n * @returns Formatted HTML with perfect indentation\r\n */\r\n export function formatHTML(html: string, indentSize: number = 4): string {\r\n // Remove existing whitespace and normalize\r\n const normalized = html\r\n .split('\\n')\r\n .map(line => line.trim())\r\n .filter(line => line.length > 0)\r\n .join('');\r\n\r\n // Tokenize the HTML\r\n const tokens = tokenize(normalized);\r\n\r\n // Process tokens with indentation\r\n const lines = processTokens(tokens, indentSize);\r\n\r\n // Join and return\r\n return lines.join('\\n');\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n","// src/utils/spa.ts\r\n//\r\n// Made with ❤️ by Maysara.\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ PACK ════════════════════════════════════════╗\r\n\r\n import type { RouteDefinition, AppContext, AppConfig } from '@cruxjs/base';\r\n import type { SPAPageConfig, ServerSPAPluginConfig } from '../types';\r\n import { generateSEOMetaTags, generateStructuredData } from './seo';\r\n import { formatHTML } from './htmlFormatter';\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ UTIL ════════════════════════════════════════╗\r\n\r\n /**\r\n * Generate i18n meta tag for client injection\r\n *\r\n * The browser.tsx template will read this meta tag and inject\r\n * i18n config into the ClientManager config automatically.\r\n *\r\n * Convert server-side filesystem paths to browser-accessible URLs:\r\n * ./src/shared/static/dist/i18n → static/dist/i18n/\r\n *\r\n * Note: NO leading slash - i18n library needs to fetch as URL (not file path)\r\n * Always returns a meta tag - never empty!\r\n */\r\n function generateI18nMetaTag(i18nConfig: AppConfig['i18n']): string {\r\n // Provide defaults if config is missing or incomplete\r\n const config = {\r\n defaultLanguage: i18nConfig?.defaultLanguage || 'en',\r\n supportedLanguages: i18nConfig?.supportedLanguages || ['en'],\r\n basePath: i18nConfig?.basePath || 'i18n/',\r\n fileExtension: i18nConfig?.fileExtension || 'json'\r\n };\r\n\r\n // Convert server-side path to browser URL\r\n let browserPath = config.basePath;\r\n\r\n // Convert path separators to forward slashes\r\n browserPath = browserPath.replace(/\\\\/g, '/');\r\n\r\n // Handle server-side paths like ./src/shared/static/dist/i18n\r\n if (browserPath.includes('shared/static')) {\r\n // Extract everything after \"shared/static\" and prepend static/\r\n const afterStatic = browserPath.split('shared/static')[1] || '';\r\n browserPath = 'static' + afterStatic;\r\n } else if (browserPath.startsWith('./')) {\r\n // Remove leading ./\r\n browserPath = browserPath.slice(2);\r\n } else if (browserPath.startsWith('/')) {\r\n // Remove leading /\r\n browserPath = browserPath.slice(1);\r\n }\r\n\r\n // Ensure it ends with /\r\n if (!browserPath.endsWith('/')) {\r\n browserPath += '/';\r\n }\r\n\r\n const i18nJson = JSON.stringify({\r\n defaultLanguage: config.defaultLanguage,\r\n supportedLanguages: config.supportedLanguages,\r\n basePath: browserPath,\r\n fileExtension: config.fileExtension\r\n });\r\n\r\n return `<meta name=\"app-i18n\" content='${i18nJson}' />`;\r\n }\r\n\r\n /**\r\n * Generate SPA HTML shell with SEO metadata\r\n *\r\n * Creates a complete HTML document with:\r\n * - SEO meta tags\r\n * - i18n meta tag (read by browser.tsx template)\r\n * - Structured data (JSON-LD)\r\n * - App mount point (#app)\r\n * - Client-side JavaScript and style entry points\r\n *\r\n * Output is automatically formatted with proper indentation via formatHTML()\r\n */\r\n export function generateSPAHTML(\r\n pageConfig: SPAPageConfig,\r\n baseConfig: ServerSPAPluginConfig,\r\n i18nConfig?: AppConfig['i18n'],\r\n lang: string = 'en'\r\n ): string {\r\n const clientScripts = pageConfig.clientScriptPath || baseConfig.clientScriptPath;\r\n const clientStyles = pageConfig.clientStylePath || baseConfig.clientStylePath || [];\r\n\r\n const scriptTags = clientScripts\r\n .map(script => `<script type=\"module\" src=\"${script}\"></script>`)\r\n .join('\\n');\r\n\r\n const styleTags = clientStyles\r\n .map(style => `<link rel=\"stylesheet\" href=\"${style}\" />`)\r\n .join('\\n');\r\n\r\n // Add i18n meta tag for browser.tsx to read\r\n const i18nMetaTag = generateI18nMetaTag(i18nConfig);\r\n\r\n // DEBUG: Log to see what's being generated\r\n if (!i18nMetaTag) {\r\n console.warn('[SPA] WARNING: i18n meta tag is empty!');\r\n }\r\n\r\n // Build raw HTML - GUARANTEE i18n meta tag is included!\r\n const rawHTML = `<!DOCTYPE html>\r\n<html>\r\n<head>\r\n${generateSEOMetaTags(pageConfig, baseConfig, lang)}\r\n${generateStructuredData(pageConfig, baseConfig, pageConfig.contentType === 'article' ? 'Article' : 'WebPage', lang)}\r\n${i18nMetaTag}\r\n${styleTags}\r\n</head>\r\n<body>\r\n<div id=\"app\"></div>\r\n${scriptTags}\r\n</body>\r\n</html>`;\r\n\r\n // DEBUG: Check if i18n meta tag is in raw HTML\r\n if (!rawHTML.includes('app-i18n')) {\r\n console.error('[SPA] ERROR: i18n meta tag NOT in raw HTML!');\r\n }\r\n\r\n // Format the HTML with proper indentation\r\n const formatted = formatHTML(rawHTML);\r\n\r\n // DEBUG: Check if i18n meta tag made it through formatting\r\n if (!formatted.includes('app-i18n')) {\r\n console.error('[SPA] ERROR: i18n meta tag LOST during formatting!');\r\n }\r\n\r\n return formatted;\r\n }\r\n\r\n /**\r\n * Create SPA route definition for a page\r\n *\r\n * Generates a RouteDefinition that handles GET requests\r\n * and returns the full SPA HTML shell with SEO data and i18n meta tag\r\n */\r\n export function createSPARoute(\r\n pageConfig: SPAPageConfig,\r\n baseConfig: ServerSPAPluginConfig,\r\n i18nConfig?: AppConfig['i18n']\r\n ): RouteDefinition {\r\n return {\r\n method: 'GET',\r\n path: pageConfig.path,\r\n handler: (c: AppContext) => {\r\n const html = generateSPAHTML(pageConfig, baseConfig, i18nConfig, c.lang || 'en');\r\n return c.html(html);\r\n }\r\n };\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n","/* eslint-disable @typescript-eslint/no-explicit-any */\r\n// src/utils/errors.ts\r\n//\r\n// Made with ❤️ by Maysara.\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ PACK ════════════════════════════════════════╗\r\n\r\n import type { ErrorPageConfig, ServerSPAPluginConfig } from '../types';\r\n import { generateSPAHTML } from './spa';\r\n\r\n // Get global i18n config (set by serverSPA plugin)\r\n export function getGlobalI18nConfig() {\r\n return (global as any).__cruxjs_i18n_config;\r\n }\r\n\r\n export function setGlobalI18nConfig(config: any) {\r\n (global as any).__cruxjs_i18n_config = config;\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ TYPE ════════════════════════════════════════╗\r\n\r\n /**\r\n * Error response definition\r\n */\r\n interface ErrorResponse {\r\n status: number;\r\n headers: Record<string, string>;\r\n body: string;\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ UTIL ════════════════════════════════════════╗\r\n\r\n /**\r\n * Build error response with appropriate content type\r\n *\r\n * Returns HTML for web requests and JSON for API requests\r\n * Automatically uses global i18n config (no need to pass it!)\r\n */\r\n export function buildErrorResponse(\r\n statusCode: number,\r\n errorPageMap: Map<number, ErrorPageConfig>,\r\n baseConfig: ServerSPAPluginConfig,\r\n path: string\r\n ): ErrorResponse {\r\n // API requests get JSON responses\r\n if (path.startsWith('/api/')) {\r\n return {\r\n status: statusCode,\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify({ error: `Error ${statusCode}` })\r\n };\r\n }\r\n\r\n // Try to find custom error page\r\n if (errorPageMap.has(statusCode)) {\r\n const errorConfig = errorPageMap.get(statusCode)!;\r\n // Use global i18n config - unified approach!\r\n const i18nConfig = getGlobalI18nConfig();\r\n console.log(`[Errors] Generating error page ${statusCode} with i18n:`, !!i18nConfig);\r\n const html = generateSPAHTML(errorConfig, baseConfig, i18nConfig);\r\n return {\r\n status: statusCode,\r\n headers: { 'Content-Type': 'text/html; charset=utf-8' },\r\n body: html\r\n };\r\n }\r\n\r\n console.log(`[Errors] No custom error page for ${statusCode}, returning fallback`);\r\n // Fallback: plain text error\r\n return {\r\n status: statusCode,\r\n headers: { 'Content-Type': 'text/plain' },\r\n body: `Error ${statusCode}`\r\n };\r\n }\r\n\r\n /**\r\n * Create error handler function for CruxJS\r\n *\r\n * Handles:\r\n * - 404 Not Found pages (with auto-generation support)\r\n * - Custom error pages by status code\r\n * - API vs web request differentiation\r\n * - Fallback error responses\r\n *\r\n * No need to pass i18nConfig - uses global!\r\n */\r\n export function createErrorHandler(\r\n errorPageMap: Map<number, ErrorPageConfig>,\r\n baseConfig: ServerSPAPluginConfig\r\n ): (statusCode: number, path: string) => Response {\r\n return (statusCode: number, path: string) => {\r\n const errorResponse = buildErrorResponse(statusCode, errorPageMap, baseConfig, path);\r\n return new Response(errorResponse.body, {\r\n status: errorResponse.status,\r\n headers: errorResponse.headers\r\n });\r\n };\r\n }\r\n\r\n /**\r\n * Create default 404 error page config\r\n */\r\n export function createDefault404Page(): ErrorPageConfig {\r\n return {\r\n statusCode: 404,\r\n title: '404 - Page Not Found',\r\n path: '/404',\r\n description: 'The page you are looking for could not be found.',\r\n keywords: ['404', 'not found', 'error'],\r\n robots: 'noindex, nofollow'\r\n };\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n","/* eslint-disable @typescript-eslint/no-unused-vars */\r\n/* eslint-disable @typescript-eslint/no-explicit-any */\r\n// src/index.ts\r\n//\r\n// Made with ❤️ by Maysara.\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ PACK ════════════════════════════════════════╗\r\n\r\n import type {\r\n CruxPlugin,\r\n AppInstance\r\n } from '@cruxjs/base';\r\n\r\n import * as types from './types';\r\n import { createSPARoute } from './utils/spa';\r\n import { createErrorHandler, createDefault404Page, setGlobalI18nConfig } from './utils/errors';\r\n\r\n export type * from './types';\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ CORE ════════════════════════════════════════╗\r\n\r\n /**\r\n * Create Server SPA Plugin\r\n *\r\n * Generates SPA routes with SEO/CEO metadata and structured data\r\n * Error pages are handled via CruxJS error hooks\r\n *\r\n * Features:\r\n * - Server-side rendering with full SEO support (meta tags, structured data)\r\n * - Automatic error page handling (404, 500, etc.)\r\n *\r\n * @example\r\n * ```typescript\r\n * const spaPlugin = serverSPA({\r\n * baseUrl : 'https://example.com',\r\n * clientEntry : './src/client/browser.tsx',\r\n * clientScriptPath : ['/static/dist/js/browser.js'],\r\n * clientStylePath : ['/static/dist/css/index.css'],\r\n * enableAutoNotFound : true,\r\n * pages: [\r\n * {\r\n * title : 'Home',\r\n * path : '/',\r\n * description : 'Welcome to our platform'\r\n * }\r\n * ],\r\n * errorPages: [\r\n * {\r\n * statusCode : 404,\r\n * title : '404 - Not Found',\r\n * path : '/404',\r\n * description : 'Page not found'\r\n * }\r\n * ]\r\n * });\r\n * ```\r\n */\r\n export function serverSPA(config: types.ServerSPAPluginConfig, appConfig?: any): CruxPlugin & { __spaErrorHandler?: any } {\r\n const routes = [];\r\n const errorPageMap = new Map<number, types.ErrorPageConfig>();\r\n\r\n // Store i18n config globally so ALL handlers can access it (unified!)\r\n setGlobalI18nConfig(appConfig?.i18n);\r\n const i18nConfig = appConfig?.i18n;\r\n\r\n // Generate routes from config\r\n if (config.pages && config.pages.length > 0) {\r\n for (const pageConfig of config.pages) {\r\n routes.push(createSPARoute(pageConfig, config, i18nConfig));\r\n }\r\n }\r\n\r\n // Setup error pages\r\n if (config.errorPages && config.errorPages.length > 0) {\r\n for (const errorPageConfig of config.errorPages) {\r\n errorPageMap.set(errorPageConfig.statusCode, errorPageConfig);\r\n // Register error page as a regular route too (for direct access like /404)\r\n routes.push(createSPARoute(errorPageConfig, config, i18nConfig));\r\n }\r\n }\r\n\r\n // Auto-generate 404 page if enabled and not already defined\r\n if (config.enableAutoNotFound && !errorPageMap.has(404)) {\r\n const defaultErrorPage = createDefault404Page();\r\n errorPageMap.set(404, defaultErrorPage);\r\n // Also register as a regular route\r\n routes.push(createSPARoute(defaultErrorPage, config, i18nConfig));\r\n }\r\n\r\n // Create error handler function (uses global i18nConfig automatically!)\r\n const errorHandler = createErrorHandler(errorPageMap, config);\r\n\r\n const plugin: CruxPlugin & { __spaErrorHandler?: any } = {\r\n name: '@cruxplug/SPA',\r\n version: '0.1.2',\r\n\r\n routes,\r\n\r\n // Attach error handler for CruxJS to use\r\n __spaErrorHandler: errorHandler,\r\n\r\n onRegister: async (app: AppInstance) => {\r\n console.log(`[SPA Plugin] Registered ${routes.length} SPA routes`);\r\n if (errorPageMap.size > 0) {\r\n const statusCodes = Array.from(errorPageMap.keys()).join(', ');\r\n console.log(`[SPA Plugin] Error pages configured for: ${statusCodes}`);\r\n }\r\n },\r\n\r\n onAwake: async (ctx: any) => {\r\n console.log('[SPA Plugin] Awake phase - SPA routes ready');\r\n },\r\n\r\n onStart: async (ctx: any) => {\r\n console.log('[SPA Plugin] Start phase - serving SPA');\r\n },\r\n\r\n onReady: async (ctx: any) => {\r\n console.log('[SPA Plugin] Ready phase - SPA is fully operational');\r\n }\r\n };\r\n\r\n return plugin;\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝"]}
1
+ {"version":3,"sources":["../src/utils/seo.ts","../src/utils/htmlFormatter.ts","../src/utils/spa.ts","../src/utils/errors.ts","../src/index.ts"],"names":["parseValue","value","defaultValue","lang","tLangAsync","resolveMetaValue","resolveKeywords","keywords","kw","resolvePageTitle","title","parsedTitle","genPageTitle","val","appName","isRTL","generateSEOMetaTags","config","baseConfig","canonicalUrl","robots","description","expertise","experience","authority","ogType","script","generateStructuredData","pageConfig","contentType","schema","line","VOID_ELEMENTS","tokenize","html","tokens","regex","match","processTokens","indentSize","indent","output","indentLevel","token","padding","tagMatch","tagName","isVoidElement","isSelfClosing","formatHTML","normalized","generateI18nMetaTag","i18nConfig","browserPath","generateSPAHTML","clientScripts","clientStyles","scriptTags","styleTags","style","i18nMetaTag","rawHTML","formatted","createSPARoute","c","getGlobalI18nConfig","setGlobalI18nConfig","buildErrorResponse","statusCode","errorPageMap","path","errorConfig","createErrorHandler","errorResponse","createDefault404Page","serverSPA","appConfig","routes","errorPageConfig","defaultErrorPage","errorHandler","app","ctx"],"mappings":"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","file":"index.cjs","sourcesContent":["// src/utils/seo.ts\r\n//\r\n// Made with ❤️ by Maysara.\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ PACK ════════════════════════════════════════╗\r\n\r\n import type { SPAPageConfig, ServerSPAPluginConfig } from '../types';\r\n import { tLangAsync } from '@minejs/server';\r\n import { isRTL } from '@minejs/i18n';\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ UTIL ════════════════════════════════════════╗\r\n\r\n /**\r\n * Smart parser for string values\r\n *\r\n * Intelligently detects if a string is a translation key or plain text:\r\n * - Translation key format (e.g., 'meta.home.title'): must contain dots, attempts translation via t(key)\r\n * - Plain text (e.g., 'My Title', 'cruxjs', 'framework'): returns as-is\r\n *\r\n * Key format: MUST contain at least one dot (.) to be considered a translation key\r\n * Examples:\r\n * - 'meta.home.title' → translation key → tries t('meta.home.title')\r\n * - 'cruxjs' → plain text → returns 'cruxjs'\r\n * - 'My Title' → plain text → returns 'My Title'\r\n *\r\n * @param value - The string to parse\r\n * @param defaultValue - Fallback if value is empty\r\n * @returns Translated value or original string\r\n */\r\n async function parseValue(value: string | undefined, defaultValue: string = '', lang: string = 'en'): Promise<string> {\r\n if (!value) return defaultValue;\r\n\r\n // Check if value looks like a translation key (e.g., 'meta.home.title')\r\n // Key pattern: MUST contain at least one dot AND be lowercase/numeric/underscores/dots\r\n const isTranslationKey = value.includes('.') && /^[a-z0-9._]+$/.test(value);\r\n\r\n if (isTranslationKey) {\r\n try {\r\n const translated = await tLangAsync(lang, value, undefined);\r\n // If translation returns a value, use it; otherwise fall back to original\r\n return translated || value;\r\n } catch {\r\n // If translation fails, return the original value\r\n return value;\r\n }\r\n }\r\n\r\n // Not a translation key format, return as direct text\r\n return value;\r\n }\r\n\r\n /**\r\n * Resolve meta tag values with smart translation detection\r\n * Uses parseValue() to automatically detect and translate\r\n */\r\n async function resolveMetaValue(value: string | undefined, defaultValue: string = '', lang: string = 'en'): Promise<string> {\r\n return await parseValue(value, defaultValue, lang);\r\n }\r\n\r\n /**\r\n * Resolve keywords with smart translation detection\r\n * Uses parseValue() on each keyword to automatically detect and translate\r\n */\r\n async function resolveKeywords(keywords: string[] | undefined, lang: string = 'en'): Promise<string> {\r\n if (!keywords || keywords.length === 0) return '';\r\n\r\n const resolved = keywords\r\n .map(async kw => await parseValue(kw, '', lang))\r\n .filter(Boolean);\r\n\r\n return resolved.join(', ');\r\n }\r\n\r\n /**\r\n * Generate page title with translation support\r\n *\r\n * Uses genPageTitle() from @minejs/i18n for RTL-aware titles\r\n * First parses the value to detect and translate if needed\r\n */\r\n async function resolvePageTitle(title: string | undefined, lang: string = 'en'): Promise<string> {\r\n if (!title) return 'Page';\r\n\r\n // First parse the value to detect translation keys\r\n const parsedTitle = await parseValue(title, '', lang);\r\n // Use genPageTitle for RTL-aware title generation\r\n try {\r\n return genPageTitle(await parsedTitle, lang);\r\n } catch {\r\n // Fallback: if genPageTitle fails, use parsed value\r\n return parsedTitle;\r\n }\r\n }\r\n\r\n /**\r\n * Generate page title with proper RTL handling\r\n *\r\n * @example\r\n * // English: \"Profile - MyApp\"\r\n * // Arabic: \"MyApp - الملف الشخصي\"\r\n */\r\n export async function genPageTitle(val: string, lang: string = 'en'): Promise<string> {\r\n const appName = await tLangAsync(lang, 'app.name', undefined);\r\n return isRTL() ? `${appName} - ${val}` : `${val} - ${appName}`;\r\n }\r\n\r\n\r\n /**\r\n * Generate SEO Meta Tags with E-E-A-T signals and translation support\r\n *\r\n * Includes:\r\n * - Core SEO metadata (charset, viewport, description, keywords, robots)\r\n * - E-E-A-T signals (expertise, experience, authority)\r\n * - Mobile optimization (web app capable, status bar style)\r\n * - Performance & security (prefetch, x-ua-compatible)\r\n * - Open Graph protocol tags\r\n * - Translation support for all meta values\r\n */\r\n export async function generateSEOMetaTags(\r\n config: SPAPageConfig,\r\n baseConfig: ServerSPAPluginConfig,\r\n lang: string = 'en'\r\n ): Promise<string> {\r\n const canonicalUrl = config.canonical || `${baseConfig.baseUrl}${config.path}`;\r\n const robots = config.robots || baseConfig.defaultRobots || 'index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1';\r\n\r\n // Resolve translated values\r\n const title = await resolvePageTitle(config.title, lang);\r\n const description = await resolveMetaValue(config.description, baseConfig.defaultDescription || 'A modern single-page application', lang);\r\n const keywords = await resolveKeywords(config.keywords || baseConfig.defaultKeywords, lang);\r\n const expertise = await resolveMetaValue(config.expertise, '', lang);\r\n const experience = await resolveMetaValue(config.experience, '', lang);\r\n const authority = await resolveMetaValue(config.authority, '', lang);\r\n\r\n // Determine OG type based on content type\r\n const ogType = config.contentType === 'article' ? 'article' : 'website';\r\n\r\n return `<!-- 🔍 Core SEO Meta Tags -->\r\n <meta charset=\"UTF-8\" />\r\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\r\n <title>${title}</title>\r\n <meta name=\"description\" content=\"${description}\" />\r\n ${keywords ? `<meta name=\"keywords\" content=\"${keywords}\" />` : ''}\r\n <meta name=\"robots\" content=\"${robots}\" />\r\n <meta name=\"language\" content=\"en\" />\r\n <meta http-equiv=\"content-language\" content=\"en-us\" />\r\n <!-- 👥 E-E-A-T Signals for AI Search -->\r\n ${baseConfig.author ? `<meta name=\"author\" content=\"${baseConfig.author}\" />` : ''}\r\n ${expertise ? `<meta name=\"expertise\" content=\"${expertise}\" />` : ''}\r\n ${experience ? `<meta name=\"experience\" content=\"${experience}\" />` : ''}\r\n ${authority ? `<meta name=\"authority\" content=\"${authority}\" />` : ''}\r\n <!-- 📱 Mobile & Performance -->\r\n <meta name=\"mobile-web-app-capable\" content=\"yes\" />\r\n <meta name=\"apple-mobile-web-app-capable\" content=\"yes\" />\r\n <meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black-translucent\" />\r\n <meta name=\"theme-color\" content=\"#000000\" />\r\n <!-- 🔗 Canonical & Prefetch -->\r\n <link rel=\"canonical\" href=\"${canonicalUrl}\" />\r\n ${(config.clientScriptPath || baseConfig.clientScriptPath)?.map(script => `<link rel=\"prefetch\" href=\"${script}\" />`).join('\\n')}\r\n <!-- ⚡ Performance & Security -->\r\n <meta name=\"format-detection\" content=\"telephone=no\" />\r\n <meta http-equiv=\"x-ua-compatible\" content=\"IE=edge\" />\r\n <!-- 📘 Open Graph Protocol (Social Media) -->\r\n <meta property=\"og:type\" content=\"${ogType}\" />\r\n <meta property=\"og:title\" content=\"${title}\" />\r\n <meta property=\"og:description\" content=\"${description}\" />\r\n <meta property=\"og:url\" content=\"${canonicalUrl}\" />\r\n <meta property=\"og:locale\" content=\"en_US\" />\r\n ${baseConfig.author ? `<meta property=\"og:site_name\" content=\"${baseConfig.author}\" />` : ''}\r\n ${config.ogImage ? `<meta property=\"og:image\" content=\"${config.ogImage}\" />` : ''}\r\n ${config.ogImage ? `<meta property=\"og:image:alt\" content=\"${title}\" />` : ''}\r\n `;\r\n }\r\n\r\n /**\r\n * Generate JSON-LD Structured Data\r\n *\r\n * Creates schema.org compatible structured data for:\r\n * - Rich snippets in search results\r\n * - AI overviews and knowledge panels\r\n * - Better indexing and SEO\r\n *\r\n * Supports multiple content types: WebPage, Article, Product, Service, etc.\r\n * Handles translation keys in meta values\r\n */\r\n export async function generateStructuredData(\r\n pageConfig: SPAPageConfig,\r\n baseConfig: ServerSPAPluginConfig,\r\n contentType: string = 'WebPage',\r\n lang: string = 'en'\r\n ): Promise<string> {\r\n const canonicalUrl = pageConfig.canonical || `${baseConfig.baseUrl}${pageConfig.path}`;\r\n\r\n // Resolve translated values for structured data\r\n const title = await resolvePageTitle(pageConfig.title, lang);\r\n const description = await resolveMetaValue(pageConfig.description, baseConfig.defaultDescription, lang);\r\n const expertise = await resolveMetaValue(pageConfig.expertise, '');\r\n const experience = await resolveMetaValue(pageConfig.experience, '');\r\n const authority = await resolveMetaValue(pageConfig.authority, '');\r\n\r\n const schema = {\r\n '@context': 'https://schema.org',\r\n '@type': contentType,\r\n 'name': title,\r\n 'url': canonicalUrl,\r\n 'description': description,\r\n 'inLanguage': 'en',\r\n ...(pageConfig.contentType && { 'genre': pageConfig.contentType }),\r\n ...(baseConfig.author && {\r\n 'author': {\r\n '@type': 'Person',\r\n 'name': baseConfig.author,\r\n ...(baseConfig.authorUrl && { 'url': baseConfig.authorUrl })\r\n }\r\n }),\r\n ...((await expertise || await experience || authority) && {\r\n 'creator': {\r\n '@type': 'Person',\r\n 'name': baseConfig.author || 'Unknown',\r\n ...(expertise && { 'expertise': expertise }),\r\n ...(experience && { 'experience': experience }),\r\n ...(authority && { 'authority': authority })\r\n }\r\n })\r\n };\r\n\r\n const jsonStr = JSON.stringify(schema, null, 2)\r\n .split('\\n')\r\n .map(line => line ? ` ${line}` : line)\r\n .join('\\n');\r\n return `<script type=\"application/ld+json\">\\n${jsonStr}\\n</script>`;\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n","// src/utils/htmlFormatter.ts\r\n//\r\n// Made with ❤️ by Maysara.\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ CORE ════════════════════════════════════════╗\r\n\r\n const VOID_ELEMENTS = ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr'];\r\n\r\n interface Token {\r\n type: 'tag' | 'text' | 'comment';\r\n value: string;\r\n }\r\n\r\n /**\r\n * Tokenize HTML into tags, text, and comments\r\n */\r\n function tokenize(html: string): Token[] {\r\n const tokens: Token[] = [];\r\n const regex = /(<[^>]+>)|([^<]+)/g;\r\n let match;\r\n\r\n while ((match = regex.exec(html)) !== null) {\r\n if (match[1]) {\r\n // It's a tag\r\n tokens.push({ type: 'tag', value: match[1] });\r\n } else if (match[2] && match[2].trim().length > 0) {\r\n // It's text content (not just whitespace)\r\n tokens.push({ type: 'text', value: match[2] });\r\n }\r\n }\r\n\r\n return tokens;\r\n }\r\n\r\n /**\r\n * Process tokens and apply indentation\r\n */\r\n function processTokens(tokens: Token[], indentSize: number = 4): string[] {\r\n const indent = ' '.repeat(indentSize);\r\n const output: string[] = [];\r\n let indentLevel = 0;\r\n\r\n // eslint-disable-next-line @typescript-eslint/prefer-for-of\r\n for (let i = 0; i < tokens.length; i++) {\r\n const token = tokens[i];\r\n const value = token.value.trim();\r\n\r\n // Decrease indent for closing tags BEFORE adding\r\n if (value.startsWith('</')) {\r\n indentLevel = Math.max(0, indentLevel - 1);\r\n }\r\n\r\n const padding = indent.repeat(indentLevel);\r\n\r\n // Skip empty text nodes\r\n if (token.type === 'text' && value.length === 0) {\r\n continue;\r\n }\r\n\r\n // Add to output\r\n if (token.type === 'text') {\r\n // Text nodes - check if previous line exists\r\n if (output.length > 0 && !output[output.length - 1].trim().endsWith('>')) {\r\n // Append to previous line if it doesn't end with tag\r\n output[output.length - 1] += value;\r\n } else {\r\n output.push(padding + value);\r\n }\r\n } else {\r\n // Tags and comments\r\n output.push(padding + value);\r\n }\r\n\r\n // Increase indent for OPENING tags ONLY (not closing, not void elements, not DOCTYPE/comments)\r\n if (value.startsWith('<') && !value.startsWith('</')) {\r\n // Skip DOCTYPE and comments - they don't increase indent\r\n if (value.startsWith('<!DOCTYPE') || value.startsWith('<!--')) {\r\n continue;\r\n }\r\n\r\n // Extract tag name\r\n const tagMatch = value.match(/<([A-Za-z][A-Za-z0-9\\\\-]*)/i);\r\n const tagName = tagMatch ? tagMatch[1].toLowerCase() : '';\r\n\r\n // Check if it's a void/self-closing element\r\n const isVoidElement = VOID_ELEMENTS.includes(tagName);\r\n const isSelfClosing = value.endsWith('/>');\r\n\r\n // Only increase indent if it's NOT a void element AND NOT self-closing\r\n if (!isVoidElement && !isSelfClosing) {\r\n indentLevel++;\r\n }\r\n }\r\n }\r\n\r\n return output;\r\n }\r\n\r\n /**\r\n * Format HTML with proper indentation and cleanup\r\n *\r\n * - Tokenizes HTML into tags and content\r\n * - Applies proper indentation (4 spaces per level)\r\n * - Handles void elements correctly\r\n * - Preserves script and style tag content\r\n * - Maintains tag hierarchy\r\n *\r\n * @param html Raw HTML string\r\n * @param indentSize Number of spaces per indent level (default: 4)\r\n * @returns Formatted HTML with perfect indentation\r\n */\r\n export function formatHTML(html: string, indentSize: number = 4): string {\r\n // Remove existing whitespace and normalize\r\n const normalized = html\r\n .split('\\n')\r\n .map(line => line.trim())\r\n .filter(line => line.length > 0)\r\n .join('');\r\n\r\n // Tokenize the HTML\r\n const tokens = tokenize(normalized);\r\n\r\n // Process tokens with indentation\r\n const lines = processTokens(tokens, indentSize);\r\n\r\n // Join and return\r\n return lines.join('\\n');\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n","// src/utils/spa.ts\r\n//\r\n// Made with ❤️ by Maysara.\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ PACK ════════════════════════════════════════╗\r\n\r\n import type { RouteDefinition, AppContext, AppConfig } from '@cruxjs/base';\r\n import type { SPAPageConfig, ServerSPAPluginConfig } from '../types';\r\n import { generateSEOMetaTags, generateStructuredData } from './seo';\r\n import { formatHTML } from './htmlFormatter';\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ UTIL ════════════════════════════════════════╗\r\n\r\n /**\r\n * Generate i18n meta tag for client injection\r\n *\r\n * The browser.tsx template will read this meta tag and inject\r\n * i18n config into the ClientManager config automatically.\r\n *\r\n * Convert server-side filesystem paths to browser-accessible URLs:\r\n * ./src/shared/static/dist/i18n → static/dist/i18n/\r\n *\r\n * Note: NO leading slash - i18n library needs to fetch as URL (not file path)\r\n * Always returns a meta tag - never empty!\r\n */\r\n function generateI18nMetaTag(i18nConfig: AppConfig['i18n']): string {\r\n // Provide defaults if config is missing or incomplete\r\n const config = {\r\n defaultLanguage: i18nConfig?.defaultLanguage || 'en',\r\n supportedLanguages: i18nConfig?.supportedLanguages || ['en'],\r\n basePath: i18nConfig?.basePath || 'i18n/',\r\n fileExtension: i18nConfig?.fileExtension || 'json'\r\n };\r\n\r\n // Convert server-side path to browser URL\r\n let browserPath = config.basePath;\r\n\r\n // Convert path separators to forward slashes\r\n browserPath = browserPath.replace(/\\\\/g, '/');\r\n\r\n // Handle server-side paths like ./src/shared/static/dist/i18n\r\n if (browserPath.includes('shared/static')) {\r\n // Extract everything after \"shared/static\" and prepend static/\r\n const afterStatic = browserPath.split('shared/static')[1] || '';\r\n browserPath = 'static' + afterStatic;\r\n } else if (browserPath.startsWith('./')) {\r\n // Remove leading ./\r\n browserPath = browserPath.slice(2);\r\n } else if (browserPath.startsWith('/')) {\r\n // Remove leading /\r\n browserPath = browserPath.slice(1);\r\n }\r\n\r\n // Ensure it ends with /\r\n if (!browserPath.endsWith('/')) {\r\n browserPath += '/';\r\n }\r\n\r\n const i18nJson = JSON.stringify({\r\n defaultLanguage: config.defaultLanguage,\r\n supportedLanguages: config.supportedLanguages,\r\n basePath: browserPath,\r\n fileExtension: config.fileExtension\r\n });\r\n\r\n return `<meta name=\"app-i18n\" content='${i18nJson}' />`;\r\n }\r\n\r\n /**\r\n * Generate SPA HTML shell with SEO metadata\r\n *\r\n * Creates a complete HTML document with:\r\n * - SEO meta tags\r\n * - i18n meta tag (read by browser.tsx template)\r\n * - Structured data (JSON-LD)\r\n * - App mount point (#app)\r\n * - Client-side JavaScript and style entry points\r\n *\r\n * Output is automatically formatted with proper indentation via formatHTML()\r\n */\r\n export async function generateSPAHTML(\r\n pageConfig: SPAPageConfig,\r\n baseConfig: ServerSPAPluginConfig,\r\n i18nConfig?: AppConfig['i18n'],\r\n lang: string = 'en'\r\n ): Promise<string> {\r\n const clientScripts = pageConfig.clientScriptPath || baseConfig.clientScriptPath;\r\n const clientStyles = pageConfig.clientStylePath || baseConfig.clientStylePath || [];\r\n\r\n const scriptTags = clientScripts\r\n .map(script => `<script type=\"module\" src=\"${script}\"></script>`)\r\n .join('\\n');\r\n\r\n const styleTags = clientStyles\r\n .map(style => `<link rel=\"stylesheet\" href=\"${style}\" />`)\r\n .join('\\n');\r\n\r\n // Add i18n meta tag for browser.tsx to read\r\n const i18nMetaTag = generateI18nMetaTag(i18nConfig);\r\n\r\n // DEBUG: Log to see what's being generated\r\n if (!i18nMetaTag) {\r\n console.warn('[SPA] WARNING: i18n meta tag is empty!');\r\n }\r\n\r\n // Build raw HTML - GUARANTEE i18n meta tag is included!\r\n const rawHTML = `<!DOCTYPE html>\r\n<html>\r\n<head>\r\n${await generateSEOMetaTags(pageConfig, baseConfig, lang)}\r\n${await generateStructuredData(pageConfig, baseConfig, pageConfig.contentType === 'article' ? 'Article' : 'WebPage', lang)}\r\n${i18nMetaTag}\r\n${styleTags}\r\n</head>\r\n<body>\r\n<div id=\"app\"></div>\r\n${scriptTags}\r\n</body>\r\n</html>`;\r\n\r\n // DEBUG: Check if i18n meta tag is in raw HTML\r\n if (!rawHTML.includes('app-i18n')) {\r\n console.error('[SPA] ERROR: i18n meta tag NOT in raw HTML!');\r\n }\r\n\r\n // Format the HTML with proper indentation\r\n const formatted = formatHTML(rawHTML);\r\n\r\n // DEBUG: Check if i18n meta tag made it through formatting\r\n if (!formatted.includes('app-i18n')) {\r\n console.error('[SPA] ERROR: i18n meta tag LOST during formatting!');\r\n }\r\n\r\n return formatted;\r\n }\r\n\r\n /**\r\n * Create SPA route definition for a page\r\n *\r\n * Generates a RouteDefinition that handles GET requests\r\n * and returns the full SPA HTML shell with SEO data and i18n meta tag\r\n */\r\n export function createSPARoute(\r\n pageConfig: SPAPageConfig,\r\n baseConfig: ServerSPAPluginConfig,\r\n i18nConfig?: AppConfig['i18n']\r\n ): RouteDefinition {\r\n return {\r\n method: 'GET',\r\n path: pageConfig.path,\r\n handler: async (c: AppContext) => {\r\n const html = await generateSPAHTML(pageConfig, baseConfig, i18nConfig, c.lang || 'en');\r\n return c.html(html);\r\n }\r\n };\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n","/* eslint-disable @typescript-eslint/no-explicit-any */\r\n// src/utils/errors.ts\r\n//\r\n// Made with ❤️ by Maysara.\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ PACK ════════════════════════════════════════╗\r\n\r\n import type { ErrorPageConfig, ServerSPAPluginConfig } from '../types';\r\n import { generateSPAHTML } from './spa';\r\n\r\n // Get global i18n config (set by serverSPA plugin)\r\n export function getGlobalI18nConfig() {\r\n return (global as any).__cruxjs_i18n_config;\r\n }\r\n\r\n export function setGlobalI18nConfig(config: any) {\r\n (global as any).__cruxjs_i18n_config = config;\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ TYPE ════════════════════════════════════════╗\r\n\r\n /**\r\n * Error response definition\r\n */\r\n interface ErrorResponse {\r\n status: number;\r\n headers: Record<string, string>;\r\n body: string;\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ UTIL ════════════════════════════════════════╗\r\n\r\n /**\r\n * Build error response with appropriate content type\r\n *\r\n * Returns HTML for web requests and JSON for API requests\r\n * Automatically uses global i18n config (no need to pass it!)\r\n */\r\n export async function buildErrorResponse(\r\n statusCode: number,\r\n errorPageMap: Map<number, ErrorPageConfig>,\r\n baseConfig: ServerSPAPluginConfig,\r\n path: string\r\n ): Promise<ErrorResponse> {\r\n // API requests get JSON responses\r\n if (path.startsWith('/api/')) {\r\n return {\r\n status: statusCode,\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify({ error: `Error ${statusCode}` })\r\n };\r\n }\r\n\r\n // Try to find custom error page\r\n if (errorPageMap.has(statusCode)) {\r\n const errorConfig = errorPageMap.get(statusCode)!;\r\n // Use global i18n config - unified approach!\r\n const i18nConfig = getGlobalI18nConfig();\r\n console.log(`[Errors] Generating error page ${statusCode} with i18n:`, !!i18nConfig);\r\n const html = await generateSPAHTML(errorConfig, baseConfig, i18nConfig);\r\n return {\r\n status: statusCode,\r\n headers: { 'Content-Type': 'text/html; charset=utf-8' },\r\n body: html\r\n };\r\n }\r\n\r\n console.log(`[Errors] No custom error page for ${statusCode}, returning fallback`);\r\n // Fallback: plain text error\r\n return {\r\n status: statusCode,\r\n headers: { 'Content-Type': 'text/plain' },\r\n body: `Error ${statusCode}`\r\n };\r\n }\r\n\r\n /**\r\n * Create error handler function for CruxJS\r\n *\r\n * Handles:\r\n * - 404 Not Found pages (with auto-generation support)\r\n * - Custom error pages by status code\r\n * - API vs web request differentiation\r\n * - Fallback error responses\r\n *\r\n * No need to pass i18nConfig - uses global!\r\n */\r\n export function createErrorHandler(\r\n errorPageMap: Map<number, ErrorPageConfig>,\r\n baseConfig: ServerSPAPluginConfig\r\n ): (statusCode: number, path: string) => Promise<Response> {\r\n return async (statusCode: number, path: string) => {\r\n const errorResponse = await buildErrorResponse(statusCode, errorPageMap, baseConfig, path);\r\n return new Response(errorResponse.body, {\r\n status: errorResponse.status,\r\n headers: errorResponse.headers\r\n });\r\n };\r\n }\r\n\r\n /**\r\n * Create default 404 error page config\r\n */\r\n export function createDefault404Page(): ErrorPageConfig {\r\n return {\r\n statusCode: 404,\r\n title: '404 - Page Not Found',\r\n path: '/404',\r\n description: 'The page you are looking for could not be found.',\r\n keywords: ['404', 'not found', 'error'],\r\n robots: 'noindex, nofollow'\r\n };\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n","/* eslint-disable @typescript-eslint/no-unused-vars */\r\n/* eslint-disable @typescript-eslint/no-explicit-any */\r\n// src/index.ts\r\n//\r\n// Made with ❤️ by Maysara.\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ PACK ════════════════════════════════════════╗\r\n\r\n import type {\r\n CruxPlugin,\r\n AppInstance\r\n } from '@cruxjs/base';\r\n\r\n import * as types from './types';\r\n import { createSPARoute } from './utils/spa';\r\n import { createErrorHandler, createDefault404Page, setGlobalI18nConfig } from './utils/errors';\r\n\r\n export type * from './types';\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ CORE ════════════════════════════════════════╗\r\n\r\n /**\r\n * Create Server SPA Plugin\r\n *\r\n * Generates SPA routes with SEO/CEO metadata and structured data\r\n * Error pages are handled via CruxJS error hooks\r\n *\r\n * Features:\r\n * - Server-side rendering with full SEO support (meta tags, structured data)\r\n * - Automatic error page handling (404, 500, etc.)\r\n *\r\n * @example\r\n * ```typescript\r\n * const spaPlugin = serverSPA({\r\n * baseUrl : 'https://example.com',\r\n * clientEntry : './src/client/browser.tsx',\r\n * clientScriptPath : ['/static/dist/js/browser.js'],\r\n * clientStylePath : ['/static/dist/css/index.css'],\r\n * enableAutoNotFound : true,\r\n * pages: [\r\n * {\r\n * title : 'Home',\r\n * path : '/',\r\n * description : 'Welcome to our platform'\r\n * }\r\n * ],\r\n * errorPages: [\r\n * {\r\n * statusCode : 404,\r\n * title : '404 - Not Found',\r\n * path : '/404',\r\n * description : 'Page not found'\r\n * }\r\n * ]\r\n * });\r\n * ```\r\n */\r\n export function serverSPA(config: types.ServerSPAPluginConfig, appConfig?: any): CruxPlugin & { __spaErrorHandler?: any } {\r\n const routes = [];\r\n const errorPageMap = new Map<number, types.ErrorPageConfig>();\r\n\r\n // Store i18n config globally so ALL handlers can access it (unified!)\r\n setGlobalI18nConfig(appConfig?.i18n);\r\n const i18nConfig = appConfig?.i18n;\r\n\r\n // Generate routes from config\r\n if (config.pages && config.pages.length > 0) {\r\n for (const pageConfig of config.pages) {\r\n routes.push(createSPARoute(pageConfig, config, i18nConfig));\r\n }\r\n }\r\n\r\n // Setup error pages\r\n if (config.errorPages && config.errorPages.length > 0) {\r\n for (const errorPageConfig of config.errorPages) {\r\n errorPageMap.set(errorPageConfig.statusCode, errorPageConfig);\r\n // Register error page as a regular route too (for direct access like /404)\r\n routes.push(createSPARoute(errorPageConfig, config, i18nConfig));\r\n }\r\n }\r\n\r\n // Auto-generate 404 page if enabled and not already defined\r\n if (config.enableAutoNotFound && !errorPageMap.has(404)) {\r\n const defaultErrorPage = createDefault404Page();\r\n errorPageMap.set(404, defaultErrorPage);\r\n // Also register as a regular route\r\n routes.push(createSPARoute(defaultErrorPage, config, i18nConfig));\r\n }\r\n\r\n // Create error handler function (uses global i18nConfig automatically!)\r\n const errorHandler = createErrorHandler(errorPageMap, config);\r\n\r\n const plugin: CruxPlugin & { __spaErrorHandler?: any } = {\r\n name: '@cruxplug/SPA',\r\n version: '0.1.4',\r\n\r\n routes,\r\n\r\n // Attach error handler for CruxJS to use\r\n __spaErrorHandler: errorHandler,\r\n\r\n onRegister: async (app: AppInstance) => {\r\n // console.log(`[SPA Plugin] Registered ${routes.length} SPA routes`);\r\n if (errorPageMap.size > 0) {\r\n const statusCodes = Array.from(errorPageMap.keys()).join(', ');\r\n // console.log(`[SPA Plugin] Error pages configured for: ${statusCodes}`);\r\n }\r\n },\r\n\r\n onAwake: async (ctx: any) => {\r\n // console.log('[SPA Plugin] Awake phase - SPA routes ready');\r\n },\r\n\r\n onStart: async (ctx: any) => {\r\n // console.log('[SPA Plugin] Start phase - serving SPA');\r\n },\r\n\r\n onReady: async (ctx: any) => {\r\n // console.log('[SPA Plugin] Ready phase - SPA is fully operational');\r\n }\r\n };\r\n\r\n return plugin;\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝"]}
package/dist/index.js CHANGED
@@ -1,15 +1,15 @@
1
- import {tLang}from'@minejs/server';import {isRTL}from'@minejs/i18n';function d(e,t="",n="en"){if(!e)return t;if(e.includes(".")&&/^[a-z0-9._]+$/.test(e))try{return tLang(n,e,void 0)||e}catch{return e}return e}function g(e,t="",n="en"){return d(e,t,n)}function w(e,t="en"){return !e||e.length===0?"":e.map(r=>d(r,"",t)).filter(Boolean).join(", ")}function y(e,t="en"){if(!e)return "Page";let n=d(e,"",t);try{return v(n,t)}catch{return n}}function v(e,t="en"){let n=tLang(t,"app.name",void 0);return isRTL()?`${n} - ${e}`:`${e} - ${n}`}function S(e,t,n="en"){let r=e.canonical||`${t.baseUrl}${e.path}`,a=e.robots||t.defaultRobots||"index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1",i=y(e.title,n),c=g(e.description,t.defaultDescription||"A modern single-page application",n),o=w(e.keywords||t.defaultKeywords,n),s=g(e.expertise,"",n),p=g(e.experience,"",n),l=g(e.authority,"",n),u=e.contentType==="article"?"article":"website";return `<!-- \u{1F50D} Core SEO Meta Tags -->
1
+ import {tLangAsync}from'@minejs/server';import {isRTL}from'@minejs/i18n';async function d(t,e="",n="en"){if(!t)return e;if(t.includes(".")&&/^[a-z0-9._]+$/.test(t))try{return await tLangAsync(n,t,void 0)||t}catch{return t}return t}async function m(t,e="",n="en"){return await d(t,e,n)}async function E(t,e="en"){return !t||t.length===0?"":t.map(async r=>await d(r,"",e)).filter(Boolean).join(", ")}async function P(t,e="en"){if(!t)return "Page";let n=await d(t,"",e);try{return v(await n,e)}catch{return n}}async function v(t,e="en"){let n=await tLangAsync(e,"app.name",void 0);return isRTL()?`${n} - ${t}`:`${t} - ${n}`}async function S(t,e,n="en"){let r=t.canonical||`${e.baseUrl}${t.path}`,o=t.robots||e.defaultRobots||"index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1",i=await P(t.title,n),s=await m(t.description,e.defaultDescription||"A modern single-page application",n),a=await E(t.keywords||e.defaultKeywords,n),c=await m(t.expertise,"",n),p=await m(t.experience,"",n),l=await m(t.authority,"",n),u=t.contentType==="article"?"article":"website";return `<!-- \u{1F50D} Core SEO Meta Tags -->
2
2
  <meta charset="UTF-8" />
3
3
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
4
4
  <title>${i}</title>
5
- <meta name="description" content="${c}" />
6
- ${o?`<meta name="keywords" content="${o}" />`:""}
7
- <meta name="robots" content="${a}" />
5
+ <meta name="description" content="${s}" />
6
+ ${a?`<meta name="keywords" content="${a}" />`:""}
7
+ <meta name="robots" content="${o}" />
8
8
  <meta name="language" content="en" />
9
9
  <meta http-equiv="content-language" content="en-us" />
10
10
  <!-- \u{1F465} E-E-A-T Signals for AI Search -->
11
- ${t.author?`<meta name="author" content="${t.author}" />`:""}
12
- ${s?`<meta name="expertise" content="${s}" />`:""}
11
+ ${e.author?`<meta name="author" content="${e.author}" />`:""}
12
+ ${c?`<meta name="expertise" content="${c}" />`:""}
13
13
  ${p?`<meta name="experience" content="${p}" />`:""}
14
14
  ${l?`<meta name="authority" content="${l}" />`:""}
15
15
  <!-- \u{1F4F1} Mobile & Performance -->
@@ -19,7 +19,7 @@ import {tLang}from'@minejs/server';import {isRTL}from'@minejs/i18n';function d(e
19
19
  <meta name="theme-color" content="#000000" />
20
20
  <!-- \u{1F517} Canonical & Prefetch -->
21
21
  <link rel="canonical" href="${r}" />
22
- ${(e.clientScriptPath||t.clientScriptPath)?.map(m=>`<link rel="prefetch" href="${m}" />`).join(`
22
+ ${(t.clientScriptPath||e.clientScriptPath)?.map(g=>`<link rel="prefetch" href="${g}" />`).join(`
23
23
  `)}
24
24
  <!-- \u26A1 Performance & Security -->
25
25
  <meta name="format-detection" content="telephone=no" />
@@ -27,31 +27,31 @@ import {tLang}from'@minejs/server';import {isRTL}from'@minejs/i18n';function d(e
27
27
  <!-- \u{1F4D8} Open Graph Protocol (Social Media) -->
28
28
  <meta property="og:type" content="${u}" />
29
29
  <meta property="og:title" content="${i}" />
30
- <meta property="og:description" content="${c}" />
30
+ <meta property="og:description" content="${s}" />
31
31
  <meta property="og:url" content="${r}" />
32
32
  <meta property="og:locale" content="en_US" />
33
- ${t.author?`<meta property="og:site_name" content="${t.author}" />`:""}
34
- ${e.ogImage?`<meta property="og:image" content="${e.ogImage}" />`:""}
35
- ${e.ogImage?`<meta property="og:image:alt" content="${i}" />`:""}
36
- `}function x(e,t,n="WebPage",r="en"){let a=e.canonical||`${t.baseUrl}${e.path}`,i=y(e.title,r),c=g(e.description,t.defaultDescription,r),o=g(e.expertise,"",r),s=g(e.experience,"",r),p=g(e.authority,"",r),l={"@context":"https://schema.org","@type":n,name:i,url:a,description:c,inLanguage:"en",...e.contentType&&{genre:e.contentType},...t.author&&{author:{"@type":"Person",name:t.author,...t.authorUrl&&{url:t.authorUrl}}},...(o||s||p)&&{creator:{"@type":"Person",name:t.author||"Unknown",...o&&{expertise:o},...s&&{experience:s},...p&&{authority:p}}}};return `<script type="application/ld+json">
33
+ ${e.author?`<meta property="og:site_name" content="${e.author}" />`:""}
34
+ ${t.ogImage?`<meta property="og:image" content="${t.ogImage}" />`:""}
35
+ ${t.ogImage?`<meta property="og:image:alt" content="${i}" />`:""}
36
+ `}async function x(t,e,n="WebPage",r="en"){let o=t.canonical||`${e.baseUrl}${t.path}`,i=await P(t.title,r),s=await m(t.description,e.defaultDescription,r),a=await m(t.expertise,""),c=await m(t.experience,""),p=await m(t.authority,""),l={"@context":"https://schema.org","@type":n,name:i,url:o,description:s,inLanguage:"en",...t.contentType&&{genre:t.contentType},...e.author&&{author:{"@type":"Person",name:e.author,...e.authorUrl&&{url:e.authorUrl}}},...(await a||await c||p)&&{creator:{"@type":"Person",name:e.author||"Unknown",...a&&{expertise:a},...c&&{experience:c},...p&&{authority:p}}}};return `<script type="application/ld+json">
37
37
  ${JSON.stringify(l,null,2).split(`
38
- `).map(m=>m&&` ${m}`).join(`
38
+ `).map(g=>g&&` ${g}`).join(`
39
39
  `)}
40
- </script>`}var R=["area","base","br","col","embed","hr","img","input","link","meta","param","source","track","wbr"];function k(e){let t=[],n=/(<[^>]+>)|([^<]+)/g,r;for(;(r=n.exec(e))!==null;)r[1]?t.push({type:"tag",value:r[1]}):r[2]&&r[2].trim().length>0&&t.push({type:"text",value:r[2]});return t}function L(e,t=4){let n=" ".repeat(t),r=[],a=0;for(let i=0;i<e.length;i++){let c=e[i],o=c.value.trim();o.startsWith("</")&&(a=Math.max(0,a-1));let s=n.repeat(a);if(!(c.type==="text"&&o.length===0)&&(c.type==="text"&&r.length>0&&!r[r.length-1].trim().endsWith(">")?r[r.length-1]+=o:r.push(s+o),o.startsWith("<")&&!o.startsWith("</"))){if(o.startsWith("<!DOCTYPE")||o.startsWith("<!--"))continue;let p=o.match(/<([A-Za-z][A-Za-z0-9\\-]*)/i),l=p?p[1].toLowerCase():"",u=R.includes(l),m=o.endsWith("/>");!u&&!m&&a++;}}return r}function A(e,t=4){let n=e.split(`
41
- `).map(i=>i.trim()).filter(i=>i.length>0).join(""),r=k(n);return L(r,t).join(`
42
- `)}function M(e){let t={defaultLanguage:e?.defaultLanguage||"en",supportedLanguages:e?.supportedLanguages||["en"],basePath:e?.basePath||"i18n/",fileExtension:e?.fileExtension||"json"},n=t.basePath;return n=n.replace(/\\/g,"/"),n.includes("shared/static")?n="static"+(n.split("shared/static")[1]||""):n.startsWith("./")?n=n.slice(2):n.startsWith("/")&&(n=n.slice(1)),n.endsWith("/")||(n+="/"),`<meta name="app-i18n" content='${JSON.stringify({defaultLanguage:t.defaultLanguage,supportedLanguages:t.supportedLanguages,basePath:n,fileExtension:t.fileExtension})}' />`}function f(e,t,n,r="en"){let a=e.clientScriptPath||t.clientScriptPath,i=e.clientStylePath||t.clientStylePath||[],c=a.map(u=>`<script type="module" src="${u}"></script>`).join(`
43
- `),o=i.map(u=>`<link rel="stylesheet" href="${u}" />`).join(`
44
- `),s=M(n);s||console.warn("[SPA] WARNING: i18n meta tag is empty!");let p=`<!DOCTYPE html>
40
+ </script>`}var R=["area","base","br","col","embed","hr","img","input","link","meta","param","source","track","wbr"];function k(t){let e=[],n=/(<[^>]+>)|([^<]+)/g,r;for(;(r=n.exec(t))!==null;)r[1]?e.push({type:"tag",value:r[1]}):r[2]&&r[2].trim().length>0&&e.push({type:"text",value:r[2]});return e}function L(t,e=4){let n=" ".repeat(e),r=[],o=0;for(let i=0;i<t.length;i++){let s=t[i],a=s.value.trim();a.startsWith("</")&&(o=Math.max(0,o-1));let c=n.repeat(o);if(!(s.type==="text"&&a.length===0)&&(s.type==="text"&&r.length>0&&!r[r.length-1].trim().endsWith(">")?r[r.length-1]+=a:r.push(c+a),a.startsWith("<")&&!a.startsWith("</"))){if(a.startsWith("<!DOCTYPE")||a.startsWith("<!--"))continue;let p=a.match(/<([A-Za-z][A-Za-z0-9\\-]*)/i),l=p?p[1].toLowerCase():"",u=R.includes(l),g=a.endsWith("/>");!u&&!g&&o++;}}return r}function w(t,e=4){let n=t.split(`
41
+ `).map(i=>i.trim()).filter(i=>i.length>0).join(""),r=k(n);return L(r,e).join(`
42
+ `)}function M(t){let e={defaultLanguage:t?.defaultLanguage||"en",supportedLanguages:t?.supportedLanguages||["en"],basePath:t?.basePath||"i18n/",fileExtension:t?.fileExtension||"json"},n=e.basePath;return n=n.replace(/\\/g,"/"),n.includes("shared/static")?n="static"+(n.split("shared/static")[1]||""):n.startsWith("./")?n=n.slice(2):n.startsWith("/")&&(n=n.slice(1)),n.endsWith("/")||(n+="/"),`<meta name="app-i18n" content='${JSON.stringify({defaultLanguage:e.defaultLanguage,supportedLanguages:e.supportedLanguages,basePath:n,fileExtension:e.fileExtension})}' />`}async function y(t,e,n,r="en"){let o=t.clientScriptPath||e.clientScriptPath,i=t.clientStylePath||e.clientStylePath||[],s=o.map(u=>`<script type="module" src="${u}"></script>`).join(`
43
+ `),a=i.map(u=>`<link rel="stylesheet" href="${u}" />`).join(`
44
+ `),c=M(n);c||console.warn("[SPA] WARNING: i18n meta tag is empty!");let p=`<!DOCTYPE html>
45
45
  <html>
46
46
  <head>
47
- ${S(e,t,r)}
48
- ${x(e,t,e.contentType==="article"?"Article":"WebPage",r)}
49
- ${s}
50
- ${o}
47
+ ${await S(t,e,r)}
48
+ ${await x(t,e,t.contentType==="article"?"Article":"WebPage",r)}
49
+ ${c}
50
+ ${a}
51
51
  </head>
52
52
  <body>
53
53
  <div id="app"></div>
54
- ${c}
54
+ ${s}
55
55
  </body>
56
- </html>`;p.includes("app-i18n")||console.error("[SPA] ERROR: i18n meta tag NOT in raw HTML!");let l=A(p);return l.includes("app-i18n")||console.error("[SPA] ERROR: i18n meta tag LOST during formatting!"),l}function h(e,t,n){return {method:"GET",path:e.path,handler:r=>{let a=f(e,t,n,r.lang||"en");return r.html(a)}}}function _(){return global.__cruxjs_i18n_config}function $(e){global.__cruxjs_i18n_config=e;}function j(e,t,n,r){if(r.startsWith("/api/"))return {status:e,headers:{"Content-Type":"application/json"},body:JSON.stringify({error:`Error ${e}`})};if(t.has(e)){let a=t.get(e),i=_();console.log(`[Errors] Generating error page ${e} with i18n:`,!!i);let c=f(a,n,i);return {status:e,headers:{"Content-Type":"text/html; charset=utf-8"},body:c}}return console.log(`[Errors] No custom error page for ${e}, returning fallback`),{status:e,headers:{"Content-Type":"text/plain"},body:`Error ${e}`}}function b(e,t){return (n,r)=>{let a=j(n,e,t,r);return new Response(a.body,{status:a.status,headers:a.headers})}}function T(){return {statusCode:404,title:"404 - Page Not Found",path:"/404",description:"The page you are looking for could not be found.",keywords:["404","not found","error"],robots:"noindex, nofollow"}}function K(e,t){let n=[],r=new Map;$(t?.i18n);let a=t?.i18n;if(e.pages&&e.pages.length>0)for(let o of e.pages)n.push(h(o,e,a));if(e.errorPages&&e.errorPages.length>0)for(let o of e.errorPages)r.set(o.statusCode,o),n.push(h(o,e,a));if(e.enableAutoNotFound&&!r.has(404)){let o=T();r.set(404,o),n.push(h(o,e,a));}let i=b(r,e);return {name:"@cruxplug/SPA",version:"0.1.2",routes:n,__spaErrorHandler:i,onRegister:async o=>{if(console.log(`[SPA Plugin] Registered ${n.length} SPA routes`),r.size>0){let s=Array.from(r.keys()).join(", ");console.log(`[SPA Plugin] Error pages configured for: ${s}`);}},onAwake:async o=>{console.log("[SPA Plugin] Awake phase - SPA routes ready");},onStart:async o=>{console.log("[SPA Plugin] Start phase - serving SPA");},onReady:async o=>{console.log("[SPA Plugin] Ready phase - SPA is fully operational");}}}export{K as serverSPA};//# sourceMappingURL=index.js.map
56
+ </html>`;p.includes("app-i18n")||console.error("[SPA] ERROR: i18n meta tag NOT in raw HTML!");let l=w(p);return l.includes("app-i18n")||console.error("[SPA] ERROR: i18n meta tag LOST during formatting!"),l}function h(t,e,n){return {method:"GET",path:t.path,handler:async r=>{let o=await y(t,e,n,r.lang||"en");return r.html(o)}}}function _(){return global.__cruxjs_i18n_config}function $(t){global.__cruxjs_i18n_config=t;}async function j(t,e,n,r){if(r.startsWith("/api/"))return {status:t,headers:{"Content-Type":"application/json"},body:JSON.stringify({error:`Error ${t}`})};if(e.has(t)){let o=e.get(t),i=_();console.log(`[Errors] Generating error page ${t} with i18n:`,!!i);let s=await y(o,n,i);return {status:t,headers:{"Content-Type":"text/html; charset=utf-8"},body:s}}return console.log(`[Errors] No custom error page for ${t}, returning fallback`),{status:t,headers:{"Content-Type":"text/plain"},body:`Error ${t}`}}function A(t,e){return async(n,r)=>{let o=await j(n,t,e,r);return new Response(o.body,{status:o.status,headers:o.headers})}}function b(){return {statusCode:404,title:"404 - Page Not Found",path:"/404",description:"The page you are looking for could not be found.",keywords:["404","not found","error"],robots:"noindex, nofollow"}}function K(t,e){let n=[],r=new Map;$(e?.i18n);let o=e?.i18n;if(t.pages&&t.pages.length>0)for(let a of t.pages)n.push(h(a,t,o));if(t.errorPages&&t.errorPages.length>0)for(let a of t.errorPages)r.set(a.statusCode,a),n.push(h(a,t,o));if(t.enableAutoNotFound&&!r.has(404)){let a=b();r.set(404,a),n.push(h(a,t,o));}let i=A(r,t);return {name:"@cruxplug/SPA",version:"0.1.4",routes:n,__spaErrorHandler:i,onRegister:async a=>{if(r.size>0){Array.from(r.keys()).join(", ");}},onAwake:async a=>{},onStart:async a=>{},onReady:async a=>{}}}export{K as serverSPA};//# sourceMappingURL=index.js.map
57
57
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/utils/seo.ts","../src/utils/htmlFormatter.ts","../src/utils/spa.ts","../src/utils/errors.ts","../src/index.ts"],"names":["parseValue","value","defaultValue","lang","tLang","resolveMetaValue","resolveKeywords","keywords","kw","resolvePageTitle","title","parsedTitle","genPageTitle","val","appName","isRTL","generateSEOMetaTags","config","baseConfig","canonicalUrl","robots","description","expertise","experience","authority","ogType","script","generateStructuredData","pageConfig","contentType","schema","line","VOID_ELEMENTS","tokenize","html","tokens","regex","match","processTokens","indentSize","indent","output","indentLevel","token","padding","tagMatch","tagName","isVoidElement","isSelfClosing","formatHTML","normalized","generateI18nMetaTag","i18nConfig","browserPath","generateSPAHTML","clientScripts","clientStyles","scriptTags","styleTags","style","i18nMetaTag","rawHTML","formatted","createSPARoute","c","getGlobalI18nConfig","setGlobalI18nConfig","buildErrorResponse","statusCode","errorPageMap","path","errorConfig","createErrorHandler","errorResponse","createDefault404Page","serverSPA","appConfig","routes","errorPageConfig","defaultErrorPage","errorHandler","app","statusCodes","ctx"],"mappings":"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","file":"index.js","sourcesContent":["// src/utils/seo.ts\r\n//\r\n// Made with ❤️ by Maysara.\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ PACK ════════════════════════════════════════╗\r\n\r\n import type { SPAPageConfig, ServerSPAPluginConfig } from '../types';\r\n import { tLang } from '@minejs/server';\r\n import { isRTL } from '@minejs/i18n';\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ UTIL ════════════════════════════════════════╗\r\n\r\n /**\r\n * Smart parser for string values\r\n *\r\n * Intelligently detects if a string is a translation key or plain text:\r\n * - Translation key format (e.g., 'meta.home.title'): must contain dots, attempts translation via t(key)\r\n * - Plain text (e.g., 'My Title', 'cruxjs', 'framework'): returns as-is\r\n *\r\n * Key format: MUST contain at least one dot (.) to be considered a translation key\r\n * Examples:\r\n * - 'meta.home.title' → translation key → tries t('meta.home.title')\r\n * - 'cruxjs' → plain text → returns 'cruxjs'\r\n * - 'My Title' → plain text → returns 'My Title'\r\n *\r\n * @param value - The string to parse\r\n * @param defaultValue - Fallback if value is empty\r\n * @returns Translated value or original string\r\n */\r\n function parseValue(value: string | undefined, defaultValue: string = '', lang: string = 'en'): string {\r\n if (!value) return defaultValue;\r\n\r\n // Check if value looks like a translation key (e.g., 'meta.home.title')\r\n // Key pattern: MUST contain at least one dot AND be lowercase/numeric/underscores/dots\r\n const isTranslationKey = value.includes('.') && /^[a-z0-9._]+$/.test(value);\r\n\r\n if (isTranslationKey) {\r\n try {\r\n const translated = tLang(lang, value, undefined);\r\n // If translation returns a value, use it; otherwise fall back to original\r\n return translated || value;\r\n } catch {\r\n // If translation fails, return the original value\r\n return value;\r\n }\r\n }\r\n\r\n // Not a translation key format, return as direct text\r\n return value;\r\n }\r\n\r\n /**\r\n * Resolve meta tag values with smart translation detection\r\n * Uses parseValue() to automatically detect and translate\r\n */\r\n function resolveMetaValue(value: string | undefined, defaultValue: string = '', lang: string = 'en'): string {\r\n return parseValue(value, defaultValue, lang);\r\n }\r\n\r\n /**\r\n * Resolve keywords with smart translation detection\r\n * Uses parseValue() on each keyword to automatically detect and translate\r\n */\r\n function resolveKeywords(keywords: string[] | undefined, lang: string = 'en'): string {\r\n if (!keywords || keywords.length === 0) return '';\r\n\r\n const resolved = keywords\r\n .map(kw => parseValue(kw, '', lang))\r\n .filter(Boolean);\r\n\r\n return resolved.join(', ');\r\n }\r\n\r\n /**\r\n * Generate page title with translation support\r\n *\r\n * Uses genPageTitle() from @minejs/i18n for RTL-aware titles\r\n * First parses the value to detect and translate if needed\r\n */\r\n function resolvePageTitle(title: string | undefined, lang: string = 'en'): string {\r\n if (!title) return 'Page';\r\n\r\n // First parse the value to detect translation keys\r\n const parsedTitle = parseValue(title, '', lang);\r\n // Use genPageTitle for RTL-aware title generation\r\n try {\r\n return genPageTitle(parsedTitle, lang);\r\n } catch {\r\n // Fallback: if genPageTitle fails, use parsed value\r\n return parsedTitle;\r\n }\r\n }\r\n\r\n /**\r\n * Generate page title with proper RTL handling\r\n *\r\n * @example\r\n * // English: \"Profile - MyApp\"\r\n * // Arabic: \"MyApp - الملف الشخصي\"\r\n */\r\n export function genPageTitle(val: string, lang: string = 'en'): string {\r\n const appName = tLang(lang, 'app.name', undefined);\r\n return isRTL() ? `${appName} - ${val}` : `${val} - ${appName}`;\r\n }\r\n\r\n\r\n /**\r\n * Generate SEO Meta Tags with E-E-A-T signals and translation support\r\n *\r\n * Includes:\r\n * - Core SEO metadata (charset, viewport, description, keywords, robots)\r\n * - E-E-A-T signals (expertise, experience, authority)\r\n * - Mobile optimization (web app capable, status bar style)\r\n * - Performance & security (prefetch, x-ua-compatible)\r\n * - Open Graph protocol tags\r\n * - Translation support for all meta values\r\n */\r\n export function generateSEOMetaTags(\r\n config: SPAPageConfig,\r\n baseConfig: ServerSPAPluginConfig,\r\n lang: string = 'en'\r\n ): string {\r\n const canonicalUrl = config.canonical || `${baseConfig.baseUrl}${config.path}`;\r\n const robots = config.robots || baseConfig.defaultRobots || 'index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1';\r\n\r\n // Resolve translated values\r\n const title = resolvePageTitle(config.title, lang);\r\n const description = resolveMetaValue(config.description, baseConfig.defaultDescription || 'A modern single-page application', lang);\r\n const keywords = resolveKeywords(config.keywords || baseConfig.defaultKeywords, lang);\r\n const expertise = resolveMetaValue(config.expertise, '', lang);\r\n const experience = resolveMetaValue(config.experience, '', lang);\r\n const authority = resolveMetaValue(config.authority, '', lang);\r\n\r\n // Determine OG type based on content type\r\n const ogType = config.contentType === 'article' ? 'article' : 'website';\r\n\r\n return `<!-- 🔍 Core SEO Meta Tags -->\r\n <meta charset=\"UTF-8\" />\r\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\r\n <title>${title}</title>\r\n <meta name=\"description\" content=\"${description}\" />\r\n ${keywords ? `<meta name=\"keywords\" content=\"${keywords}\" />` : ''}\r\n <meta name=\"robots\" content=\"${robots}\" />\r\n <meta name=\"language\" content=\"en\" />\r\n <meta http-equiv=\"content-language\" content=\"en-us\" />\r\n <!-- 👥 E-E-A-T Signals for AI Search -->\r\n ${baseConfig.author ? `<meta name=\"author\" content=\"${baseConfig.author}\" />` : ''}\r\n ${expertise ? `<meta name=\"expertise\" content=\"${expertise}\" />` : ''}\r\n ${experience ? `<meta name=\"experience\" content=\"${experience}\" />` : ''}\r\n ${authority ? `<meta name=\"authority\" content=\"${authority}\" />` : ''}\r\n <!-- 📱 Mobile & Performance -->\r\n <meta name=\"mobile-web-app-capable\" content=\"yes\" />\r\n <meta name=\"apple-mobile-web-app-capable\" content=\"yes\" />\r\n <meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black-translucent\" />\r\n <meta name=\"theme-color\" content=\"#000000\" />\r\n <!-- 🔗 Canonical & Prefetch -->\r\n <link rel=\"canonical\" href=\"${canonicalUrl}\" />\r\n ${(config.clientScriptPath || baseConfig.clientScriptPath)?.map(script => `<link rel=\"prefetch\" href=\"${script}\" />`).join('\\n')}\r\n <!-- ⚡ Performance & Security -->\r\n <meta name=\"format-detection\" content=\"telephone=no\" />\r\n <meta http-equiv=\"x-ua-compatible\" content=\"IE=edge\" />\r\n <!-- 📘 Open Graph Protocol (Social Media) -->\r\n <meta property=\"og:type\" content=\"${ogType}\" />\r\n <meta property=\"og:title\" content=\"${title}\" />\r\n <meta property=\"og:description\" content=\"${description}\" />\r\n <meta property=\"og:url\" content=\"${canonicalUrl}\" />\r\n <meta property=\"og:locale\" content=\"en_US\" />\r\n ${baseConfig.author ? `<meta property=\"og:site_name\" content=\"${baseConfig.author}\" />` : ''}\r\n ${config.ogImage ? `<meta property=\"og:image\" content=\"${config.ogImage}\" />` : ''}\r\n ${config.ogImage ? `<meta property=\"og:image:alt\" content=\"${title}\" />` : ''}\r\n `;\r\n }\r\n\r\n /**\r\n * Generate JSON-LD Structured Data\r\n *\r\n * Creates schema.org compatible structured data for:\r\n * - Rich snippets in search results\r\n * - AI overviews and knowledge panels\r\n * - Better indexing and SEO\r\n *\r\n * Supports multiple content types: WebPage, Article, Product, Service, etc.\r\n * Handles translation keys in meta values\r\n */\r\n export function generateStructuredData(\r\n pageConfig: SPAPageConfig,\r\n baseConfig: ServerSPAPluginConfig,\r\n contentType: string = 'WebPage',\r\n lang: string = 'en'\r\n ): string {\r\n const canonicalUrl = pageConfig.canonical || `${baseConfig.baseUrl}${pageConfig.path}`;\r\n\r\n // Resolve translated values for structured data\r\n const title = resolvePageTitle(pageConfig.title, lang);\r\n const description = resolveMetaValue(pageConfig.description, baseConfig.defaultDescription, lang);\r\n const expertise = resolveMetaValue(pageConfig.expertise, '', lang);\r\n const experience = resolveMetaValue(pageConfig.experience, '', lang);\r\n const authority = resolveMetaValue(pageConfig.authority, '', lang);\r\n\r\n const schema = {\r\n '@context': 'https://schema.org',\r\n '@type': contentType,\r\n 'name': title,\r\n 'url': canonicalUrl,\r\n 'description': description,\r\n 'inLanguage': 'en',\r\n ...(pageConfig.contentType && { 'genre': pageConfig.contentType }),\r\n ...(baseConfig.author && {\r\n 'author': {\r\n '@type': 'Person',\r\n 'name': baseConfig.author,\r\n ...(baseConfig.authorUrl && { 'url': baseConfig.authorUrl })\r\n }\r\n }),\r\n ...((expertise || experience || authority) && {\r\n 'creator': {\r\n '@type': 'Person',\r\n 'name': baseConfig.author || 'Unknown',\r\n ...(expertise && { 'expertise': expertise }),\r\n ...(experience && { 'experience': experience }),\r\n ...(authority && { 'authority': authority })\r\n }\r\n })\r\n };\r\n\r\n const jsonStr = JSON.stringify(schema, null, 2)\r\n .split('\\n')\r\n .map(line => line ? ` ${line}` : line)\r\n .join('\\n');\r\n return `<script type=\"application/ld+json\">\\n${jsonStr}\\n</script>`;\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n","// src/utils/htmlFormatter.ts\r\n//\r\n// Made with ❤️ by Maysara.\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ CORE ════════════════════════════════════════╗\r\n\r\n const VOID_ELEMENTS = ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr'];\r\n\r\n interface Token {\r\n type: 'tag' | 'text' | 'comment';\r\n value: string;\r\n }\r\n\r\n /**\r\n * Tokenize HTML into tags, text, and comments\r\n */\r\n function tokenize(html: string): Token[] {\r\n const tokens: Token[] = [];\r\n const regex = /(<[^>]+>)|([^<]+)/g;\r\n let match;\r\n\r\n while ((match = regex.exec(html)) !== null) {\r\n if (match[1]) {\r\n // It's a tag\r\n tokens.push({ type: 'tag', value: match[1] });\r\n } else if (match[2] && match[2].trim().length > 0) {\r\n // It's text content (not just whitespace)\r\n tokens.push({ type: 'text', value: match[2] });\r\n }\r\n }\r\n\r\n return tokens;\r\n }\r\n\r\n /**\r\n * Process tokens and apply indentation\r\n */\r\n function processTokens(tokens: Token[], indentSize: number = 4): string[] {\r\n const indent = ' '.repeat(indentSize);\r\n const output: string[] = [];\r\n let indentLevel = 0;\r\n\r\n // eslint-disable-next-line @typescript-eslint/prefer-for-of\r\n for (let i = 0; i < tokens.length; i++) {\r\n const token = tokens[i];\r\n const value = token.value.trim();\r\n\r\n // Decrease indent for closing tags BEFORE adding\r\n if (value.startsWith('</')) {\r\n indentLevel = Math.max(0, indentLevel - 1);\r\n }\r\n\r\n const padding = indent.repeat(indentLevel);\r\n\r\n // Skip empty text nodes\r\n if (token.type === 'text' && value.length === 0) {\r\n continue;\r\n }\r\n\r\n // Add to output\r\n if (token.type === 'text') {\r\n // Text nodes - check if previous line exists\r\n if (output.length > 0 && !output[output.length - 1].trim().endsWith('>')) {\r\n // Append to previous line if it doesn't end with tag\r\n output[output.length - 1] += value;\r\n } else {\r\n output.push(padding + value);\r\n }\r\n } else {\r\n // Tags and comments\r\n output.push(padding + value);\r\n }\r\n\r\n // Increase indent for OPENING tags ONLY (not closing, not void elements, not DOCTYPE/comments)\r\n if (value.startsWith('<') && !value.startsWith('</')) {\r\n // Skip DOCTYPE and comments - they don't increase indent\r\n if (value.startsWith('<!DOCTYPE') || value.startsWith('<!--')) {\r\n continue;\r\n }\r\n\r\n // Extract tag name\r\n const tagMatch = value.match(/<([A-Za-z][A-Za-z0-9\\\\-]*)/i);\r\n const tagName = tagMatch ? tagMatch[1].toLowerCase() : '';\r\n\r\n // Check if it's a void/self-closing element\r\n const isVoidElement = VOID_ELEMENTS.includes(tagName);\r\n const isSelfClosing = value.endsWith('/>');\r\n\r\n // Only increase indent if it's NOT a void element AND NOT self-closing\r\n if (!isVoidElement && !isSelfClosing) {\r\n indentLevel++;\r\n }\r\n }\r\n }\r\n\r\n return output;\r\n }\r\n\r\n /**\r\n * Format HTML with proper indentation and cleanup\r\n *\r\n * - Tokenizes HTML into tags and content\r\n * - Applies proper indentation (4 spaces per level)\r\n * - Handles void elements correctly\r\n * - Preserves script and style tag content\r\n * - Maintains tag hierarchy\r\n *\r\n * @param html Raw HTML string\r\n * @param indentSize Number of spaces per indent level (default: 4)\r\n * @returns Formatted HTML with perfect indentation\r\n */\r\n export function formatHTML(html: string, indentSize: number = 4): string {\r\n // Remove existing whitespace and normalize\r\n const normalized = html\r\n .split('\\n')\r\n .map(line => line.trim())\r\n .filter(line => line.length > 0)\r\n .join('');\r\n\r\n // Tokenize the HTML\r\n const tokens = tokenize(normalized);\r\n\r\n // Process tokens with indentation\r\n const lines = processTokens(tokens, indentSize);\r\n\r\n // Join and return\r\n return lines.join('\\n');\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n","// src/utils/spa.ts\r\n//\r\n// Made with ❤️ by Maysara.\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ PACK ════════════════════════════════════════╗\r\n\r\n import type { RouteDefinition, AppContext, AppConfig } from '@cruxjs/base';\r\n import type { SPAPageConfig, ServerSPAPluginConfig } from '../types';\r\n import { generateSEOMetaTags, generateStructuredData } from './seo';\r\n import { formatHTML } from './htmlFormatter';\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ UTIL ════════════════════════════════════════╗\r\n\r\n /**\r\n * Generate i18n meta tag for client injection\r\n *\r\n * The browser.tsx template will read this meta tag and inject\r\n * i18n config into the ClientManager config automatically.\r\n *\r\n * Convert server-side filesystem paths to browser-accessible URLs:\r\n * ./src/shared/static/dist/i18n → static/dist/i18n/\r\n *\r\n * Note: NO leading slash - i18n library needs to fetch as URL (not file path)\r\n * Always returns a meta tag - never empty!\r\n */\r\n function generateI18nMetaTag(i18nConfig: AppConfig['i18n']): string {\r\n // Provide defaults if config is missing or incomplete\r\n const config = {\r\n defaultLanguage: i18nConfig?.defaultLanguage || 'en',\r\n supportedLanguages: i18nConfig?.supportedLanguages || ['en'],\r\n basePath: i18nConfig?.basePath || 'i18n/',\r\n fileExtension: i18nConfig?.fileExtension || 'json'\r\n };\r\n\r\n // Convert server-side path to browser URL\r\n let browserPath = config.basePath;\r\n\r\n // Convert path separators to forward slashes\r\n browserPath = browserPath.replace(/\\\\/g, '/');\r\n\r\n // Handle server-side paths like ./src/shared/static/dist/i18n\r\n if (browserPath.includes('shared/static')) {\r\n // Extract everything after \"shared/static\" and prepend static/\r\n const afterStatic = browserPath.split('shared/static')[1] || '';\r\n browserPath = 'static' + afterStatic;\r\n } else if (browserPath.startsWith('./')) {\r\n // Remove leading ./\r\n browserPath = browserPath.slice(2);\r\n } else if (browserPath.startsWith('/')) {\r\n // Remove leading /\r\n browserPath = browserPath.slice(1);\r\n }\r\n\r\n // Ensure it ends with /\r\n if (!browserPath.endsWith('/')) {\r\n browserPath += '/';\r\n }\r\n\r\n const i18nJson = JSON.stringify({\r\n defaultLanguage: config.defaultLanguage,\r\n supportedLanguages: config.supportedLanguages,\r\n basePath: browserPath,\r\n fileExtension: config.fileExtension\r\n });\r\n\r\n return `<meta name=\"app-i18n\" content='${i18nJson}' />`;\r\n }\r\n\r\n /**\r\n * Generate SPA HTML shell with SEO metadata\r\n *\r\n * Creates a complete HTML document with:\r\n * - SEO meta tags\r\n * - i18n meta tag (read by browser.tsx template)\r\n * - Structured data (JSON-LD)\r\n * - App mount point (#app)\r\n * - Client-side JavaScript and style entry points\r\n *\r\n * Output is automatically formatted with proper indentation via formatHTML()\r\n */\r\n export function generateSPAHTML(\r\n pageConfig: SPAPageConfig,\r\n baseConfig: ServerSPAPluginConfig,\r\n i18nConfig?: AppConfig['i18n'],\r\n lang: string = 'en'\r\n ): string {\r\n const clientScripts = pageConfig.clientScriptPath || baseConfig.clientScriptPath;\r\n const clientStyles = pageConfig.clientStylePath || baseConfig.clientStylePath || [];\r\n\r\n const scriptTags = clientScripts\r\n .map(script => `<script type=\"module\" src=\"${script}\"></script>`)\r\n .join('\\n');\r\n\r\n const styleTags = clientStyles\r\n .map(style => `<link rel=\"stylesheet\" href=\"${style}\" />`)\r\n .join('\\n');\r\n\r\n // Add i18n meta tag for browser.tsx to read\r\n const i18nMetaTag = generateI18nMetaTag(i18nConfig);\r\n\r\n // DEBUG: Log to see what's being generated\r\n if (!i18nMetaTag) {\r\n console.warn('[SPA] WARNING: i18n meta tag is empty!');\r\n }\r\n\r\n // Build raw HTML - GUARANTEE i18n meta tag is included!\r\n const rawHTML = `<!DOCTYPE html>\r\n<html>\r\n<head>\r\n${generateSEOMetaTags(pageConfig, baseConfig, lang)}\r\n${generateStructuredData(pageConfig, baseConfig, pageConfig.contentType === 'article' ? 'Article' : 'WebPage', lang)}\r\n${i18nMetaTag}\r\n${styleTags}\r\n</head>\r\n<body>\r\n<div id=\"app\"></div>\r\n${scriptTags}\r\n</body>\r\n</html>`;\r\n\r\n // DEBUG: Check if i18n meta tag is in raw HTML\r\n if (!rawHTML.includes('app-i18n')) {\r\n console.error('[SPA] ERROR: i18n meta tag NOT in raw HTML!');\r\n }\r\n\r\n // Format the HTML with proper indentation\r\n const formatted = formatHTML(rawHTML);\r\n\r\n // DEBUG: Check if i18n meta tag made it through formatting\r\n if (!formatted.includes('app-i18n')) {\r\n console.error('[SPA] ERROR: i18n meta tag LOST during formatting!');\r\n }\r\n\r\n return formatted;\r\n }\r\n\r\n /**\r\n * Create SPA route definition for a page\r\n *\r\n * Generates a RouteDefinition that handles GET requests\r\n * and returns the full SPA HTML shell with SEO data and i18n meta tag\r\n */\r\n export function createSPARoute(\r\n pageConfig: SPAPageConfig,\r\n baseConfig: ServerSPAPluginConfig,\r\n i18nConfig?: AppConfig['i18n']\r\n ): RouteDefinition {\r\n return {\r\n method: 'GET',\r\n path: pageConfig.path,\r\n handler: (c: AppContext) => {\r\n const html = generateSPAHTML(pageConfig, baseConfig, i18nConfig, c.lang || 'en');\r\n return c.html(html);\r\n }\r\n };\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n","/* eslint-disable @typescript-eslint/no-explicit-any */\r\n// src/utils/errors.ts\r\n//\r\n// Made with ❤️ by Maysara.\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ PACK ════════════════════════════════════════╗\r\n\r\n import type { ErrorPageConfig, ServerSPAPluginConfig } from '../types';\r\n import { generateSPAHTML } from './spa';\r\n\r\n // Get global i18n config (set by serverSPA plugin)\r\n export function getGlobalI18nConfig() {\r\n return (global as any).__cruxjs_i18n_config;\r\n }\r\n\r\n export function setGlobalI18nConfig(config: any) {\r\n (global as any).__cruxjs_i18n_config = config;\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ TYPE ════════════════════════════════════════╗\r\n\r\n /**\r\n * Error response definition\r\n */\r\n interface ErrorResponse {\r\n status: number;\r\n headers: Record<string, string>;\r\n body: string;\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ UTIL ════════════════════════════════════════╗\r\n\r\n /**\r\n * Build error response with appropriate content type\r\n *\r\n * Returns HTML for web requests and JSON for API requests\r\n * Automatically uses global i18n config (no need to pass it!)\r\n */\r\n export function buildErrorResponse(\r\n statusCode: number,\r\n errorPageMap: Map<number, ErrorPageConfig>,\r\n baseConfig: ServerSPAPluginConfig,\r\n path: string\r\n ): ErrorResponse {\r\n // API requests get JSON responses\r\n if (path.startsWith('/api/')) {\r\n return {\r\n status: statusCode,\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify({ error: `Error ${statusCode}` })\r\n };\r\n }\r\n\r\n // Try to find custom error page\r\n if (errorPageMap.has(statusCode)) {\r\n const errorConfig = errorPageMap.get(statusCode)!;\r\n // Use global i18n config - unified approach!\r\n const i18nConfig = getGlobalI18nConfig();\r\n console.log(`[Errors] Generating error page ${statusCode} with i18n:`, !!i18nConfig);\r\n const html = generateSPAHTML(errorConfig, baseConfig, i18nConfig);\r\n return {\r\n status: statusCode,\r\n headers: { 'Content-Type': 'text/html; charset=utf-8' },\r\n body: html\r\n };\r\n }\r\n\r\n console.log(`[Errors] No custom error page for ${statusCode}, returning fallback`);\r\n // Fallback: plain text error\r\n return {\r\n status: statusCode,\r\n headers: { 'Content-Type': 'text/plain' },\r\n body: `Error ${statusCode}`\r\n };\r\n }\r\n\r\n /**\r\n * Create error handler function for CruxJS\r\n *\r\n * Handles:\r\n * - 404 Not Found pages (with auto-generation support)\r\n * - Custom error pages by status code\r\n * - API vs web request differentiation\r\n * - Fallback error responses\r\n *\r\n * No need to pass i18nConfig - uses global!\r\n */\r\n export function createErrorHandler(\r\n errorPageMap: Map<number, ErrorPageConfig>,\r\n baseConfig: ServerSPAPluginConfig\r\n ): (statusCode: number, path: string) => Response {\r\n return (statusCode: number, path: string) => {\r\n const errorResponse = buildErrorResponse(statusCode, errorPageMap, baseConfig, path);\r\n return new Response(errorResponse.body, {\r\n status: errorResponse.status,\r\n headers: errorResponse.headers\r\n });\r\n };\r\n }\r\n\r\n /**\r\n * Create default 404 error page config\r\n */\r\n export function createDefault404Page(): ErrorPageConfig {\r\n return {\r\n statusCode: 404,\r\n title: '404 - Page Not Found',\r\n path: '/404',\r\n description: 'The page you are looking for could not be found.',\r\n keywords: ['404', 'not found', 'error'],\r\n robots: 'noindex, nofollow'\r\n };\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n","/* eslint-disable @typescript-eslint/no-unused-vars */\r\n/* eslint-disable @typescript-eslint/no-explicit-any */\r\n// src/index.ts\r\n//\r\n// Made with ❤️ by Maysara.\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ PACK ════════════════════════════════════════╗\r\n\r\n import type {\r\n CruxPlugin,\r\n AppInstance\r\n } from '@cruxjs/base';\r\n\r\n import * as types from './types';\r\n import { createSPARoute } from './utils/spa';\r\n import { createErrorHandler, createDefault404Page, setGlobalI18nConfig } from './utils/errors';\r\n\r\n export type * from './types';\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ CORE ════════════════════════════════════════╗\r\n\r\n /**\r\n * Create Server SPA Plugin\r\n *\r\n * Generates SPA routes with SEO/CEO metadata and structured data\r\n * Error pages are handled via CruxJS error hooks\r\n *\r\n * Features:\r\n * - Server-side rendering with full SEO support (meta tags, structured data)\r\n * - Automatic error page handling (404, 500, etc.)\r\n *\r\n * @example\r\n * ```typescript\r\n * const spaPlugin = serverSPA({\r\n * baseUrl : 'https://example.com',\r\n * clientEntry : './src/client/browser.tsx',\r\n * clientScriptPath : ['/static/dist/js/browser.js'],\r\n * clientStylePath : ['/static/dist/css/index.css'],\r\n * enableAutoNotFound : true,\r\n * pages: [\r\n * {\r\n * title : 'Home',\r\n * path : '/',\r\n * description : 'Welcome to our platform'\r\n * }\r\n * ],\r\n * errorPages: [\r\n * {\r\n * statusCode : 404,\r\n * title : '404 - Not Found',\r\n * path : '/404',\r\n * description : 'Page not found'\r\n * }\r\n * ]\r\n * });\r\n * ```\r\n */\r\n export function serverSPA(config: types.ServerSPAPluginConfig, appConfig?: any): CruxPlugin & { __spaErrorHandler?: any } {\r\n const routes = [];\r\n const errorPageMap = new Map<number, types.ErrorPageConfig>();\r\n\r\n // Store i18n config globally so ALL handlers can access it (unified!)\r\n setGlobalI18nConfig(appConfig?.i18n);\r\n const i18nConfig = appConfig?.i18n;\r\n\r\n // Generate routes from config\r\n if (config.pages && config.pages.length > 0) {\r\n for (const pageConfig of config.pages) {\r\n routes.push(createSPARoute(pageConfig, config, i18nConfig));\r\n }\r\n }\r\n\r\n // Setup error pages\r\n if (config.errorPages && config.errorPages.length > 0) {\r\n for (const errorPageConfig of config.errorPages) {\r\n errorPageMap.set(errorPageConfig.statusCode, errorPageConfig);\r\n // Register error page as a regular route too (for direct access like /404)\r\n routes.push(createSPARoute(errorPageConfig, config, i18nConfig));\r\n }\r\n }\r\n\r\n // Auto-generate 404 page if enabled and not already defined\r\n if (config.enableAutoNotFound && !errorPageMap.has(404)) {\r\n const defaultErrorPage = createDefault404Page();\r\n errorPageMap.set(404, defaultErrorPage);\r\n // Also register as a regular route\r\n routes.push(createSPARoute(defaultErrorPage, config, i18nConfig));\r\n }\r\n\r\n // Create error handler function (uses global i18nConfig automatically!)\r\n const errorHandler = createErrorHandler(errorPageMap, config);\r\n\r\n const plugin: CruxPlugin & { __spaErrorHandler?: any } = {\r\n name: '@cruxplug/SPA',\r\n version: '0.1.2',\r\n\r\n routes,\r\n\r\n // Attach error handler for CruxJS to use\r\n __spaErrorHandler: errorHandler,\r\n\r\n onRegister: async (app: AppInstance) => {\r\n console.log(`[SPA Plugin] Registered ${routes.length} SPA routes`);\r\n if (errorPageMap.size > 0) {\r\n const statusCodes = Array.from(errorPageMap.keys()).join(', ');\r\n console.log(`[SPA Plugin] Error pages configured for: ${statusCodes}`);\r\n }\r\n },\r\n\r\n onAwake: async (ctx: any) => {\r\n console.log('[SPA Plugin] Awake phase - SPA routes ready');\r\n },\r\n\r\n onStart: async (ctx: any) => {\r\n console.log('[SPA Plugin] Start phase - serving SPA');\r\n },\r\n\r\n onReady: async (ctx: any) => {\r\n console.log('[SPA Plugin] Ready phase - SPA is fully operational');\r\n }\r\n };\r\n\r\n return plugin;\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝"]}
1
+ {"version":3,"sources":["../src/utils/seo.ts","../src/utils/htmlFormatter.ts","../src/utils/spa.ts","../src/utils/errors.ts","../src/index.ts"],"names":["parseValue","value","defaultValue","lang","tLangAsync","resolveMetaValue","resolveKeywords","keywords","kw","resolvePageTitle","title","parsedTitle","genPageTitle","val","appName","isRTL","generateSEOMetaTags","config","baseConfig","canonicalUrl","robots","description","expertise","experience","authority","ogType","script","generateStructuredData","pageConfig","contentType","schema","line","VOID_ELEMENTS","tokenize","html","tokens","regex","match","processTokens","indentSize","indent","output","indentLevel","token","padding","tagMatch","tagName","isVoidElement","isSelfClosing","formatHTML","normalized","generateI18nMetaTag","i18nConfig","browserPath","generateSPAHTML","clientScripts","clientStyles","scriptTags","styleTags","style","i18nMetaTag","rawHTML","formatted","createSPARoute","c","getGlobalI18nConfig","setGlobalI18nConfig","buildErrorResponse","statusCode","errorPageMap","path","errorConfig","createErrorHandler","errorResponse","createDefault404Page","serverSPA","appConfig","routes","errorPageConfig","defaultErrorPage","errorHandler","app","ctx"],"mappings":"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","file":"index.js","sourcesContent":["// src/utils/seo.ts\r\n//\r\n// Made with ❤️ by Maysara.\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ PACK ════════════════════════════════════════╗\r\n\r\n import type { SPAPageConfig, ServerSPAPluginConfig } from '../types';\r\n import { tLangAsync } from '@minejs/server';\r\n import { isRTL } from '@minejs/i18n';\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ UTIL ════════════════════════════════════════╗\r\n\r\n /**\r\n * Smart parser for string values\r\n *\r\n * Intelligently detects if a string is a translation key or plain text:\r\n * - Translation key format (e.g., 'meta.home.title'): must contain dots, attempts translation via t(key)\r\n * - Plain text (e.g., 'My Title', 'cruxjs', 'framework'): returns as-is\r\n *\r\n * Key format: MUST contain at least one dot (.) to be considered a translation key\r\n * Examples:\r\n * - 'meta.home.title' → translation key → tries t('meta.home.title')\r\n * - 'cruxjs' → plain text → returns 'cruxjs'\r\n * - 'My Title' → plain text → returns 'My Title'\r\n *\r\n * @param value - The string to parse\r\n * @param defaultValue - Fallback if value is empty\r\n * @returns Translated value or original string\r\n */\r\n async function parseValue(value: string | undefined, defaultValue: string = '', lang: string = 'en'): Promise<string> {\r\n if (!value) return defaultValue;\r\n\r\n // Check if value looks like a translation key (e.g., 'meta.home.title')\r\n // Key pattern: MUST contain at least one dot AND be lowercase/numeric/underscores/dots\r\n const isTranslationKey = value.includes('.') && /^[a-z0-9._]+$/.test(value);\r\n\r\n if (isTranslationKey) {\r\n try {\r\n const translated = await tLangAsync(lang, value, undefined);\r\n // If translation returns a value, use it; otherwise fall back to original\r\n return translated || value;\r\n } catch {\r\n // If translation fails, return the original value\r\n return value;\r\n }\r\n }\r\n\r\n // Not a translation key format, return as direct text\r\n return value;\r\n }\r\n\r\n /**\r\n * Resolve meta tag values with smart translation detection\r\n * Uses parseValue() to automatically detect and translate\r\n */\r\n async function resolveMetaValue(value: string | undefined, defaultValue: string = '', lang: string = 'en'): Promise<string> {\r\n return await parseValue(value, defaultValue, lang);\r\n }\r\n\r\n /**\r\n * Resolve keywords with smart translation detection\r\n * Uses parseValue() on each keyword to automatically detect and translate\r\n */\r\n async function resolveKeywords(keywords: string[] | undefined, lang: string = 'en'): Promise<string> {\r\n if (!keywords || keywords.length === 0) return '';\r\n\r\n const resolved = keywords\r\n .map(async kw => await parseValue(kw, '', lang))\r\n .filter(Boolean);\r\n\r\n return resolved.join(', ');\r\n }\r\n\r\n /**\r\n * Generate page title with translation support\r\n *\r\n * Uses genPageTitle() from @minejs/i18n for RTL-aware titles\r\n * First parses the value to detect and translate if needed\r\n */\r\n async function resolvePageTitle(title: string | undefined, lang: string = 'en'): Promise<string> {\r\n if (!title) return 'Page';\r\n\r\n // First parse the value to detect translation keys\r\n const parsedTitle = await parseValue(title, '', lang);\r\n // Use genPageTitle for RTL-aware title generation\r\n try {\r\n return genPageTitle(await parsedTitle, lang);\r\n } catch {\r\n // Fallback: if genPageTitle fails, use parsed value\r\n return parsedTitle;\r\n }\r\n }\r\n\r\n /**\r\n * Generate page title with proper RTL handling\r\n *\r\n * @example\r\n * // English: \"Profile - MyApp\"\r\n * // Arabic: \"MyApp - الملف الشخصي\"\r\n */\r\n export async function genPageTitle(val: string, lang: string = 'en'): Promise<string> {\r\n const appName = await tLangAsync(lang, 'app.name', undefined);\r\n return isRTL() ? `${appName} - ${val}` : `${val} - ${appName}`;\r\n }\r\n\r\n\r\n /**\r\n * Generate SEO Meta Tags with E-E-A-T signals and translation support\r\n *\r\n * Includes:\r\n * - Core SEO metadata (charset, viewport, description, keywords, robots)\r\n * - E-E-A-T signals (expertise, experience, authority)\r\n * - Mobile optimization (web app capable, status bar style)\r\n * - Performance & security (prefetch, x-ua-compatible)\r\n * - Open Graph protocol tags\r\n * - Translation support for all meta values\r\n */\r\n export async function generateSEOMetaTags(\r\n config: SPAPageConfig,\r\n baseConfig: ServerSPAPluginConfig,\r\n lang: string = 'en'\r\n ): Promise<string> {\r\n const canonicalUrl = config.canonical || `${baseConfig.baseUrl}${config.path}`;\r\n const robots = config.robots || baseConfig.defaultRobots || 'index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1';\r\n\r\n // Resolve translated values\r\n const title = await resolvePageTitle(config.title, lang);\r\n const description = await resolveMetaValue(config.description, baseConfig.defaultDescription || 'A modern single-page application', lang);\r\n const keywords = await resolveKeywords(config.keywords || baseConfig.defaultKeywords, lang);\r\n const expertise = await resolveMetaValue(config.expertise, '', lang);\r\n const experience = await resolveMetaValue(config.experience, '', lang);\r\n const authority = await resolveMetaValue(config.authority, '', lang);\r\n\r\n // Determine OG type based on content type\r\n const ogType = config.contentType === 'article' ? 'article' : 'website';\r\n\r\n return `<!-- 🔍 Core SEO Meta Tags -->\r\n <meta charset=\"UTF-8\" />\r\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\r\n <title>${title}</title>\r\n <meta name=\"description\" content=\"${description}\" />\r\n ${keywords ? `<meta name=\"keywords\" content=\"${keywords}\" />` : ''}\r\n <meta name=\"robots\" content=\"${robots}\" />\r\n <meta name=\"language\" content=\"en\" />\r\n <meta http-equiv=\"content-language\" content=\"en-us\" />\r\n <!-- 👥 E-E-A-T Signals for AI Search -->\r\n ${baseConfig.author ? `<meta name=\"author\" content=\"${baseConfig.author}\" />` : ''}\r\n ${expertise ? `<meta name=\"expertise\" content=\"${expertise}\" />` : ''}\r\n ${experience ? `<meta name=\"experience\" content=\"${experience}\" />` : ''}\r\n ${authority ? `<meta name=\"authority\" content=\"${authority}\" />` : ''}\r\n <!-- 📱 Mobile & Performance -->\r\n <meta name=\"mobile-web-app-capable\" content=\"yes\" />\r\n <meta name=\"apple-mobile-web-app-capable\" content=\"yes\" />\r\n <meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black-translucent\" />\r\n <meta name=\"theme-color\" content=\"#000000\" />\r\n <!-- 🔗 Canonical & Prefetch -->\r\n <link rel=\"canonical\" href=\"${canonicalUrl}\" />\r\n ${(config.clientScriptPath || baseConfig.clientScriptPath)?.map(script => `<link rel=\"prefetch\" href=\"${script}\" />`).join('\\n')}\r\n <!-- ⚡ Performance & Security -->\r\n <meta name=\"format-detection\" content=\"telephone=no\" />\r\n <meta http-equiv=\"x-ua-compatible\" content=\"IE=edge\" />\r\n <!-- 📘 Open Graph Protocol (Social Media) -->\r\n <meta property=\"og:type\" content=\"${ogType}\" />\r\n <meta property=\"og:title\" content=\"${title}\" />\r\n <meta property=\"og:description\" content=\"${description}\" />\r\n <meta property=\"og:url\" content=\"${canonicalUrl}\" />\r\n <meta property=\"og:locale\" content=\"en_US\" />\r\n ${baseConfig.author ? `<meta property=\"og:site_name\" content=\"${baseConfig.author}\" />` : ''}\r\n ${config.ogImage ? `<meta property=\"og:image\" content=\"${config.ogImage}\" />` : ''}\r\n ${config.ogImage ? `<meta property=\"og:image:alt\" content=\"${title}\" />` : ''}\r\n `;\r\n }\r\n\r\n /**\r\n * Generate JSON-LD Structured Data\r\n *\r\n * Creates schema.org compatible structured data for:\r\n * - Rich snippets in search results\r\n * - AI overviews and knowledge panels\r\n * - Better indexing and SEO\r\n *\r\n * Supports multiple content types: WebPage, Article, Product, Service, etc.\r\n * Handles translation keys in meta values\r\n */\r\n export async function generateStructuredData(\r\n pageConfig: SPAPageConfig,\r\n baseConfig: ServerSPAPluginConfig,\r\n contentType: string = 'WebPage',\r\n lang: string = 'en'\r\n ): Promise<string> {\r\n const canonicalUrl = pageConfig.canonical || `${baseConfig.baseUrl}${pageConfig.path}`;\r\n\r\n // Resolve translated values for structured data\r\n const title = await resolvePageTitle(pageConfig.title, lang);\r\n const description = await resolveMetaValue(pageConfig.description, baseConfig.defaultDescription, lang);\r\n const expertise = await resolveMetaValue(pageConfig.expertise, '');\r\n const experience = await resolveMetaValue(pageConfig.experience, '');\r\n const authority = await resolveMetaValue(pageConfig.authority, '');\r\n\r\n const schema = {\r\n '@context': 'https://schema.org',\r\n '@type': contentType,\r\n 'name': title,\r\n 'url': canonicalUrl,\r\n 'description': description,\r\n 'inLanguage': 'en',\r\n ...(pageConfig.contentType && { 'genre': pageConfig.contentType }),\r\n ...(baseConfig.author && {\r\n 'author': {\r\n '@type': 'Person',\r\n 'name': baseConfig.author,\r\n ...(baseConfig.authorUrl && { 'url': baseConfig.authorUrl })\r\n }\r\n }),\r\n ...((await expertise || await experience || authority) && {\r\n 'creator': {\r\n '@type': 'Person',\r\n 'name': baseConfig.author || 'Unknown',\r\n ...(expertise && { 'expertise': expertise }),\r\n ...(experience && { 'experience': experience }),\r\n ...(authority && { 'authority': authority })\r\n }\r\n })\r\n };\r\n\r\n const jsonStr = JSON.stringify(schema, null, 2)\r\n .split('\\n')\r\n .map(line => line ? ` ${line}` : line)\r\n .join('\\n');\r\n return `<script type=\"application/ld+json\">\\n${jsonStr}\\n</script>`;\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n","// src/utils/htmlFormatter.ts\r\n//\r\n// Made with ❤️ by Maysara.\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ CORE ════════════════════════════════════════╗\r\n\r\n const VOID_ELEMENTS = ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr'];\r\n\r\n interface Token {\r\n type: 'tag' | 'text' | 'comment';\r\n value: string;\r\n }\r\n\r\n /**\r\n * Tokenize HTML into tags, text, and comments\r\n */\r\n function tokenize(html: string): Token[] {\r\n const tokens: Token[] = [];\r\n const regex = /(<[^>]+>)|([^<]+)/g;\r\n let match;\r\n\r\n while ((match = regex.exec(html)) !== null) {\r\n if (match[1]) {\r\n // It's a tag\r\n tokens.push({ type: 'tag', value: match[1] });\r\n } else if (match[2] && match[2].trim().length > 0) {\r\n // It's text content (not just whitespace)\r\n tokens.push({ type: 'text', value: match[2] });\r\n }\r\n }\r\n\r\n return tokens;\r\n }\r\n\r\n /**\r\n * Process tokens and apply indentation\r\n */\r\n function processTokens(tokens: Token[], indentSize: number = 4): string[] {\r\n const indent = ' '.repeat(indentSize);\r\n const output: string[] = [];\r\n let indentLevel = 0;\r\n\r\n // eslint-disable-next-line @typescript-eslint/prefer-for-of\r\n for (let i = 0; i < tokens.length; i++) {\r\n const token = tokens[i];\r\n const value = token.value.trim();\r\n\r\n // Decrease indent for closing tags BEFORE adding\r\n if (value.startsWith('</')) {\r\n indentLevel = Math.max(0, indentLevel - 1);\r\n }\r\n\r\n const padding = indent.repeat(indentLevel);\r\n\r\n // Skip empty text nodes\r\n if (token.type === 'text' && value.length === 0) {\r\n continue;\r\n }\r\n\r\n // Add to output\r\n if (token.type === 'text') {\r\n // Text nodes - check if previous line exists\r\n if (output.length > 0 && !output[output.length - 1].trim().endsWith('>')) {\r\n // Append to previous line if it doesn't end with tag\r\n output[output.length - 1] += value;\r\n } else {\r\n output.push(padding + value);\r\n }\r\n } else {\r\n // Tags and comments\r\n output.push(padding + value);\r\n }\r\n\r\n // Increase indent for OPENING tags ONLY (not closing, not void elements, not DOCTYPE/comments)\r\n if (value.startsWith('<') && !value.startsWith('</')) {\r\n // Skip DOCTYPE and comments - they don't increase indent\r\n if (value.startsWith('<!DOCTYPE') || value.startsWith('<!--')) {\r\n continue;\r\n }\r\n\r\n // Extract tag name\r\n const tagMatch = value.match(/<([A-Za-z][A-Za-z0-9\\\\-]*)/i);\r\n const tagName = tagMatch ? tagMatch[1].toLowerCase() : '';\r\n\r\n // Check if it's a void/self-closing element\r\n const isVoidElement = VOID_ELEMENTS.includes(tagName);\r\n const isSelfClosing = value.endsWith('/>');\r\n\r\n // Only increase indent if it's NOT a void element AND NOT self-closing\r\n if (!isVoidElement && !isSelfClosing) {\r\n indentLevel++;\r\n }\r\n }\r\n }\r\n\r\n return output;\r\n }\r\n\r\n /**\r\n * Format HTML with proper indentation and cleanup\r\n *\r\n * - Tokenizes HTML into tags and content\r\n * - Applies proper indentation (4 spaces per level)\r\n * - Handles void elements correctly\r\n * - Preserves script and style tag content\r\n * - Maintains tag hierarchy\r\n *\r\n * @param html Raw HTML string\r\n * @param indentSize Number of spaces per indent level (default: 4)\r\n * @returns Formatted HTML with perfect indentation\r\n */\r\n export function formatHTML(html: string, indentSize: number = 4): string {\r\n // Remove existing whitespace and normalize\r\n const normalized = html\r\n .split('\\n')\r\n .map(line => line.trim())\r\n .filter(line => line.length > 0)\r\n .join('');\r\n\r\n // Tokenize the HTML\r\n const tokens = tokenize(normalized);\r\n\r\n // Process tokens with indentation\r\n const lines = processTokens(tokens, indentSize);\r\n\r\n // Join and return\r\n return lines.join('\\n');\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n","// src/utils/spa.ts\r\n//\r\n// Made with ❤️ by Maysara.\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ PACK ════════════════════════════════════════╗\r\n\r\n import type { RouteDefinition, AppContext, AppConfig } from '@cruxjs/base';\r\n import type { SPAPageConfig, ServerSPAPluginConfig } from '../types';\r\n import { generateSEOMetaTags, generateStructuredData } from './seo';\r\n import { formatHTML } from './htmlFormatter';\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ UTIL ════════════════════════════════════════╗\r\n\r\n /**\r\n * Generate i18n meta tag for client injection\r\n *\r\n * The browser.tsx template will read this meta tag and inject\r\n * i18n config into the ClientManager config automatically.\r\n *\r\n * Convert server-side filesystem paths to browser-accessible URLs:\r\n * ./src/shared/static/dist/i18n → static/dist/i18n/\r\n *\r\n * Note: NO leading slash - i18n library needs to fetch as URL (not file path)\r\n * Always returns a meta tag - never empty!\r\n */\r\n function generateI18nMetaTag(i18nConfig: AppConfig['i18n']): string {\r\n // Provide defaults if config is missing or incomplete\r\n const config = {\r\n defaultLanguage: i18nConfig?.defaultLanguage || 'en',\r\n supportedLanguages: i18nConfig?.supportedLanguages || ['en'],\r\n basePath: i18nConfig?.basePath || 'i18n/',\r\n fileExtension: i18nConfig?.fileExtension || 'json'\r\n };\r\n\r\n // Convert server-side path to browser URL\r\n let browserPath = config.basePath;\r\n\r\n // Convert path separators to forward slashes\r\n browserPath = browserPath.replace(/\\\\/g, '/');\r\n\r\n // Handle server-side paths like ./src/shared/static/dist/i18n\r\n if (browserPath.includes('shared/static')) {\r\n // Extract everything after \"shared/static\" and prepend static/\r\n const afterStatic = browserPath.split('shared/static')[1] || '';\r\n browserPath = 'static' + afterStatic;\r\n } else if (browserPath.startsWith('./')) {\r\n // Remove leading ./\r\n browserPath = browserPath.slice(2);\r\n } else if (browserPath.startsWith('/')) {\r\n // Remove leading /\r\n browserPath = browserPath.slice(1);\r\n }\r\n\r\n // Ensure it ends with /\r\n if (!browserPath.endsWith('/')) {\r\n browserPath += '/';\r\n }\r\n\r\n const i18nJson = JSON.stringify({\r\n defaultLanguage: config.defaultLanguage,\r\n supportedLanguages: config.supportedLanguages,\r\n basePath: browserPath,\r\n fileExtension: config.fileExtension\r\n });\r\n\r\n return `<meta name=\"app-i18n\" content='${i18nJson}' />`;\r\n }\r\n\r\n /**\r\n * Generate SPA HTML shell with SEO metadata\r\n *\r\n * Creates a complete HTML document with:\r\n * - SEO meta tags\r\n * - i18n meta tag (read by browser.tsx template)\r\n * - Structured data (JSON-LD)\r\n * - App mount point (#app)\r\n * - Client-side JavaScript and style entry points\r\n *\r\n * Output is automatically formatted with proper indentation via formatHTML()\r\n */\r\n export async function generateSPAHTML(\r\n pageConfig: SPAPageConfig,\r\n baseConfig: ServerSPAPluginConfig,\r\n i18nConfig?: AppConfig['i18n'],\r\n lang: string = 'en'\r\n ): Promise<string> {\r\n const clientScripts = pageConfig.clientScriptPath || baseConfig.clientScriptPath;\r\n const clientStyles = pageConfig.clientStylePath || baseConfig.clientStylePath || [];\r\n\r\n const scriptTags = clientScripts\r\n .map(script => `<script type=\"module\" src=\"${script}\"></script>`)\r\n .join('\\n');\r\n\r\n const styleTags = clientStyles\r\n .map(style => `<link rel=\"stylesheet\" href=\"${style}\" />`)\r\n .join('\\n');\r\n\r\n // Add i18n meta tag for browser.tsx to read\r\n const i18nMetaTag = generateI18nMetaTag(i18nConfig);\r\n\r\n // DEBUG: Log to see what's being generated\r\n if (!i18nMetaTag) {\r\n console.warn('[SPA] WARNING: i18n meta tag is empty!');\r\n }\r\n\r\n // Build raw HTML - GUARANTEE i18n meta tag is included!\r\n const rawHTML = `<!DOCTYPE html>\r\n<html>\r\n<head>\r\n${await generateSEOMetaTags(pageConfig, baseConfig, lang)}\r\n${await generateStructuredData(pageConfig, baseConfig, pageConfig.contentType === 'article' ? 'Article' : 'WebPage', lang)}\r\n${i18nMetaTag}\r\n${styleTags}\r\n</head>\r\n<body>\r\n<div id=\"app\"></div>\r\n${scriptTags}\r\n</body>\r\n</html>`;\r\n\r\n // DEBUG: Check if i18n meta tag is in raw HTML\r\n if (!rawHTML.includes('app-i18n')) {\r\n console.error('[SPA] ERROR: i18n meta tag NOT in raw HTML!');\r\n }\r\n\r\n // Format the HTML with proper indentation\r\n const formatted = formatHTML(rawHTML);\r\n\r\n // DEBUG: Check if i18n meta tag made it through formatting\r\n if (!formatted.includes('app-i18n')) {\r\n console.error('[SPA] ERROR: i18n meta tag LOST during formatting!');\r\n }\r\n\r\n return formatted;\r\n }\r\n\r\n /**\r\n * Create SPA route definition for a page\r\n *\r\n * Generates a RouteDefinition that handles GET requests\r\n * and returns the full SPA HTML shell with SEO data and i18n meta tag\r\n */\r\n export function createSPARoute(\r\n pageConfig: SPAPageConfig,\r\n baseConfig: ServerSPAPluginConfig,\r\n i18nConfig?: AppConfig['i18n']\r\n ): RouteDefinition {\r\n return {\r\n method: 'GET',\r\n path: pageConfig.path,\r\n handler: async (c: AppContext) => {\r\n const html = await generateSPAHTML(pageConfig, baseConfig, i18nConfig, c.lang || 'en');\r\n return c.html(html);\r\n }\r\n };\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n","/* eslint-disable @typescript-eslint/no-explicit-any */\r\n// src/utils/errors.ts\r\n//\r\n// Made with ❤️ by Maysara.\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ PACK ════════════════════════════════════════╗\r\n\r\n import type { ErrorPageConfig, ServerSPAPluginConfig } from '../types';\r\n import { generateSPAHTML } from './spa';\r\n\r\n // Get global i18n config (set by serverSPA plugin)\r\n export function getGlobalI18nConfig() {\r\n return (global as any).__cruxjs_i18n_config;\r\n }\r\n\r\n export function setGlobalI18nConfig(config: any) {\r\n (global as any).__cruxjs_i18n_config = config;\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ TYPE ════════════════════════════════════════╗\r\n\r\n /**\r\n * Error response definition\r\n */\r\n interface ErrorResponse {\r\n status: number;\r\n headers: Record<string, string>;\r\n body: string;\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ UTIL ════════════════════════════════════════╗\r\n\r\n /**\r\n * Build error response with appropriate content type\r\n *\r\n * Returns HTML for web requests and JSON for API requests\r\n * Automatically uses global i18n config (no need to pass it!)\r\n */\r\n export async function buildErrorResponse(\r\n statusCode: number,\r\n errorPageMap: Map<number, ErrorPageConfig>,\r\n baseConfig: ServerSPAPluginConfig,\r\n path: string\r\n ): Promise<ErrorResponse> {\r\n // API requests get JSON responses\r\n if (path.startsWith('/api/')) {\r\n return {\r\n status: statusCode,\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify({ error: `Error ${statusCode}` })\r\n };\r\n }\r\n\r\n // Try to find custom error page\r\n if (errorPageMap.has(statusCode)) {\r\n const errorConfig = errorPageMap.get(statusCode)!;\r\n // Use global i18n config - unified approach!\r\n const i18nConfig = getGlobalI18nConfig();\r\n console.log(`[Errors] Generating error page ${statusCode} with i18n:`, !!i18nConfig);\r\n const html = await generateSPAHTML(errorConfig, baseConfig, i18nConfig);\r\n return {\r\n status: statusCode,\r\n headers: { 'Content-Type': 'text/html; charset=utf-8' },\r\n body: html\r\n };\r\n }\r\n\r\n console.log(`[Errors] No custom error page for ${statusCode}, returning fallback`);\r\n // Fallback: plain text error\r\n return {\r\n status: statusCode,\r\n headers: { 'Content-Type': 'text/plain' },\r\n body: `Error ${statusCode}`\r\n };\r\n }\r\n\r\n /**\r\n * Create error handler function for CruxJS\r\n *\r\n * Handles:\r\n * - 404 Not Found pages (with auto-generation support)\r\n * - Custom error pages by status code\r\n * - API vs web request differentiation\r\n * - Fallback error responses\r\n *\r\n * No need to pass i18nConfig - uses global!\r\n */\r\n export function createErrorHandler(\r\n errorPageMap: Map<number, ErrorPageConfig>,\r\n baseConfig: ServerSPAPluginConfig\r\n ): (statusCode: number, path: string) => Promise<Response> {\r\n return async (statusCode: number, path: string) => {\r\n const errorResponse = await buildErrorResponse(statusCode, errorPageMap, baseConfig, path);\r\n return new Response(errorResponse.body, {\r\n status: errorResponse.status,\r\n headers: errorResponse.headers\r\n });\r\n };\r\n }\r\n\r\n /**\r\n * Create default 404 error page config\r\n */\r\n export function createDefault404Page(): ErrorPageConfig {\r\n return {\r\n statusCode: 404,\r\n title: '404 - Page Not Found',\r\n path: '/404',\r\n description: 'The page you are looking for could not be found.',\r\n keywords: ['404', 'not found', 'error'],\r\n robots: 'noindex, nofollow'\r\n };\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n","/* eslint-disable @typescript-eslint/no-unused-vars */\r\n/* eslint-disable @typescript-eslint/no-explicit-any */\r\n// src/index.ts\r\n//\r\n// Made with ❤️ by Maysara.\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ PACK ════════════════════════════════════════╗\r\n\r\n import type {\r\n CruxPlugin,\r\n AppInstance\r\n } from '@cruxjs/base';\r\n\r\n import * as types from './types';\r\n import { createSPARoute } from './utils/spa';\r\n import { createErrorHandler, createDefault404Page, setGlobalI18nConfig } from './utils/errors';\r\n\r\n export type * from './types';\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ CORE ════════════════════════════════════════╗\r\n\r\n /**\r\n * Create Server SPA Plugin\r\n *\r\n * Generates SPA routes with SEO/CEO metadata and structured data\r\n * Error pages are handled via CruxJS error hooks\r\n *\r\n * Features:\r\n * - Server-side rendering with full SEO support (meta tags, structured data)\r\n * - Automatic error page handling (404, 500, etc.)\r\n *\r\n * @example\r\n * ```typescript\r\n * const spaPlugin = serverSPA({\r\n * baseUrl : 'https://example.com',\r\n * clientEntry : './src/client/browser.tsx',\r\n * clientScriptPath : ['/static/dist/js/browser.js'],\r\n * clientStylePath : ['/static/dist/css/index.css'],\r\n * enableAutoNotFound : true,\r\n * pages: [\r\n * {\r\n * title : 'Home',\r\n * path : '/',\r\n * description : 'Welcome to our platform'\r\n * }\r\n * ],\r\n * errorPages: [\r\n * {\r\n * statusCode : 404,\r\n * title : '404 - Not Found',\r\n * path : '/404',\r\n * description : 'Page not found'\r\n * }\r\n * ]\r\n * });\r\n * ```\r\n */\r\n export function serverSPA(config: types.ServerSPAPluginConfig, appConfig?: any): CruxPlugin & { __spaErrorHandler?: any } {\r\n const routes = [];\r\n const errorPageMap = new Map<number, types.ErrorPageConfig>();\r\n\r\n // Store i18n config globally so ALL handlers can access it (unified!)\r\n setGlobalI18nConfig(appConfig?.i18n);\r\n const i18nConfig = appConfig?.i18n;\r\n\r\n // Generate routes from config\r\n if (config.pages && config.pages.length > 0) {\r\n for (const pageConfig of config.pages) {\r\n routes.push(createSPARoute(pageConfig, config, i18nConfig));\r\n }\r\n }\r\n\r\n // Setup error pages\r\n if (config.errorPages && config.errorPages.length > 0) {\r\n for (const errorPageConfig of config.errorPages) {\r\n errorPageMap.set(errorPageConfig.statusCode, errorPageConfig);\r\n // Register error page as a regular route too (for direct access like /404)\r\n routes.push(createSPARoute(errorPageConfig, config, i18nConfig));\r\n }\r\n }\r\n\r\n // Auto-generate 404 page if enabled and not already defined\r\n if (config.enableAutoNotFound && !errorPageMap.has(404)) {\r\n const defaultErrorPage = createDefault404Page();\r\n errorPageMap.set(404, defaultErrorPage);\r\n // Also register as a regular route\r\n routes.push(createSPARoute(defaultErrorPage, config, i18nConfig));\r\n }\r\n\r\n // Create error handler function (uses global i18nConfig automatically!)\r\n const errorHandler = createErrorHandler(errorPageMap, config);\r\n\r\n const plugin: CruxPlugin & { __spaErrorHandler?: any } = {\r\n name: '@cruxplug/SPA',\r\n version: '0.1.4',\r\n\r\n routes,\r\n\r\n // Attach error handler for CruxJS to use\r\n __spaErrorHandler: errorHandler,\r\n\r\n onRegister: async (app: AppInstance) => {\r\n // console.log(`[SPA Plugin] Registered ${routes.length} SPA routes`);\r\n if (errorPageMap.size > 0) {\r\n const statusCodes = Array.from(errorPageMap.keys()).join(', ');\r\n // console.log(`[SPA Plugin] Error pages configured for: ${statusCodes}`);\r\n }\r\n },\r\n\r\n onAwake: async (ctx: any) => {\r\n // console.log('[SPA Plugin] Awake phase - SPA routes ready');\r\n },\r\n\r\n onStart: async (ctx: any) => {\r\n // console.log('[SPA Plugin] Start phase - serving SPA');\r\n },\r\n\r\n onReady: async (ctx: any) => {\r\n // console.log('[SPA Plugin] Ready phase - SPA is fully operational');\r\n }\r\n };\r\n\r\n return plugin;\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cruxplug/spa",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Single Page Application (SPA) plugin for CruxJS with built-in SEO/CEO support, E-E-A-T signals, and JSON-LD structured data generation.",
5
5
  "keywords": [
6
6
  "cruxjs",
@@ -47,16 +47,16 @@
47
47
  "bun": "^1.3.3"
48
48
  },
49
49
  "dependencies": {
50
- "@cruxjs/base": "^0.1.3",
50
+ "@cruxjs/base": "^0.1.8",
51
51
  "@minejs/db": "^0.0.3",
52
- "@minejs/server": "^0.1.3",
53
- "@minejs/i18n": "^0.1.3"
52
+ "@minejs/server": "^0.1.5",
53
+ "@minejs/i18n": "^0.1.4"
54
54
  },
55
55
  "devDependencies": {
56
56
  "@eslint/js": "^9.39.2",
57
57
  "@stylistic/eslint-plugin": "^5.7.0",
58
58
  "@types/bun": "^1.3.6",
59
- "@types/node": "^20.19.29",
59
+ "@types/node": "^20.19.30",
60
60
  "bun-plugin-dts": "^0.3.0",
61
61
  "bun-types": "^1.3.6",
62
62
  "ts-node": "^10.9.2",