@cruxplug/spa 0.0.7 → 0.0.8

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,10 +8,10 @@
8
8
  </div>
9
9
 
10
10
  <div align="center">
11
- <img src="https://img.shields.io/badge/v-0.0.7-black"/>
12
- <img src="https://img.shields.io/badge/🔥-@cruxplug-black"/>
11
+ <img src="https://img.shields.io/badge/v-0.0.8-black"/>
12
+ <a href="https://github.com/cruxjs-org"><img src="https://img.shields.io/badge/🔥-@cruxjs-black"/></a>
13
13
  <br>
14
- <img src="https://img.shields.io/badge/coverage----%25-brightgreen" alt="Test Coverage" />
14
+ <img src="https://img.shields.io/badge/coverage-~%25-brightgreen" alt="Test Coverage" />
15
15
  <img src="https://img.shields.io/github/issues/cruxplug-org/spa?style=flat" alt="Github Repo Issues" />
16
16
  <img src="https://img.shields.io/github/stars/cruxplug-org/spa?style=social" alt="GitHub Repo stars" />
17
17
  </div>
@@ -23,256 +23,182 @@
23
23
 
24
24
  <!-- ╔══════════════════════════════ DOC ══════════════════════════════╗ -->
25
25
 
26
- - ## Quick Start 🔥
26
+ - ## Overview 👀
27
27
 
28
- > A production-ready **CruxJS plugin** for serving Single Page Applications (SPAs) with built-in **SEO/CEO support**, **E-E-A-T signals**, and **JSON-LD structured data generation**.
28
+ - #### Why ?
29
+ > To provide a production-ready server-side SPA plugin with automatic SEO/CEO metadata generation, structured data (JSON-LD), error handling, and i18n integration for modern full-stack applications.
29
30
 
30
- > Advanced SEO metadata and AI Search optimization
31
+ - #### When ?
32
+ > When you need to:
33
+ > - Serve SPA pages with full SEO support
34
+ > - Generate OpenGraph and structured data
35
+ > - Handle error pages (404, 500, etc.)
36
+ > - Inject i18n configuration from server to client
37
+ > - Support E-E-A-T signals for AI search
38
+ > - Generate beautiful, formatted HTML
31
39
 
32
- > Mobile-first meta tags and web app capabilities
40
+ > When you use [@cruxjs/app](https://github.com/cruxjs-org/app).
33
41
 
34
- > Structured data for rich snippets and knowledge panels
42
+ <br>
43
+ <br>
35
44
 
36
- > Automatic error page handling (404, 500, etc.)
45
+ - ## Quick Start 🔥
37
46
 
38
- > Performance optimization (canonical URLs, prefetching)
47
+ > install [`hmm`](https://github.com/minejs-org/hmm) first.
39
48
 
40
- - ### Setup
49
+ ```bash
50
+ # in your terminal
51
+ hmm i @cruxjs/spa
52
+ ```
41
53
 
42
- > install [`hmm`](https://github.com/maysara-elshewehy/hmm) first.
54
+ <div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> </div>
43
55
 
44
- ```bash
45
- hmm i @cruxplug/spa
46
- ```
56
+ - #### Setup
47
57
 
48
- <div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> <br> </div>
58
+ > Create your SPA plugin configuration:
49
59
 
50
- - ### Usage
60
+ ```typescript
61
+ import { serverSPA } from '@cruxjs/spa';
51
62
 
52
- ```ts
53
- import { serverSPA } from '@cruxplug/spa';
63
+ const spaPlugin = serverSPA({
64
+ baseUrl : 'http://localhost:3000',
65
+ clientEntry : './src/app/client.ts',
66
+ clientScriptPath : ['/static/dist/js/client.js'],
67
+ clientStylePath : ['/static/dist/css/min.css'],
68
+
69
+ pages: [
70
+ {
71
+ title : 'Home - My App',
72
+ path : '/',
73
+ description : 'Welcome to our application'
74
+ }
75
+ ],
76
+
77
+ errorPages: [
78
+ {
79
+ statusCode : 404,
80
+ title : '404 - Not Found',
81
+ path : '/*',
82
+ description : 'The page you\'re looking for doesn\'t exist'
83
+ }
84
+ ],
85
+
86
+ }, appConfig);
54
87
  ```
55
88
 
56
- - #### 1. Basic Usage
89
+ <div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> </div>
90
+ <br>
57
91
 
58
- ```typescript
59
- const spaPlugin = serverSPA({
60
- baseUrl: 'https://example.com',
61
- clientEntry: './src/client/browser.tsx',
62
- clientScriptPath: ['/static/dist/js/app.js'],
63
- clientStylePath: ['/static/dist/css/style.css'],
64
- enableAutoNotFound: true,
65
- pages: [
66
- {
67
- title: 'Home',
68
- path: '/',
69
- description: 'Welcome to our platform',
70
- keywords: ['home', 'landing']
71
- },
72
- {
73
- title: 'About Us',
74
- path: '/about',
75
- description: 'Learn more about our company',
76
- contentType: 'page'
77
- }
78
- ]
79
- });
80
-
81
- app.use(spaPlugin);
82
- ```
92
+ - #### Usage
83
93
 
84
- - #### 2. With E-E-A-T Signals (for AI Search)
94
+ > Add the plugin to your application:
95
+
96
+ ```typescript
97
+ import { createApp, AppConfig } from '@cruxjs/app';
98
+ import { serverSPA } from '@cruxjs/spa';
99
+
100
+ const appConfig: AppConfig = {
101
+ debug : true,
102
+ server : { port: 3000, host: 'localhost' },
103
+ // ... rest of configuration
104
+ };
105
+
106
+ const spaPlugin = serverSPA({
107
+ baseUrl : 'http://localhost:3000',
108
+ clientEntry : './src/app/client.ts',
109
+ clientScriptPath : ['/static/dist/js/client.js'],
110
+ clientStylePath : ['/static/dist/css/min.css'],
111
+ pages : [/* ... */],
112
+ }, appConfig);
113
+
114
+ appConfig.plugins!.push(spaPlugin);
115
+
116
+ const app = createApp(appConfig);
117
+ app.start();
118
+ ```
119
+
120
+ <div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> </div>
121
+
122
+ - #### Page Configuration with Translation Support
85
123
 
86
124
  ```typescript
87
- const spaPlugin = serverSPA({
88
- baseUrl: 'https://example.com',
89
- clientScriptPath: ['/js/app.js'],
90
- clientStylePath: ['/css/min.css'],
91
- clientEntry: './src/client/index.tsx',
92
- author: 'Your Company Name',
93
- authorUrl: 'https://example.com/about',
94
- pages: [
95
- {
96
- title: 'Blog Post',
97
- path: '/blog/seo-guide',
98
- description: 'Complete SEO guide for 2026',
99
- contentType: 'article',
100
- expertise: 'SEO and digital marketing',
101
- experience: '10+ years in the industry',
102
- authority: 'Published in major tech blogs'
103
- }
104
- ]
105
- });
125
+ const pages: SPAPageConfig[] = [
126
+ {
127
+ // Page metadata with translation support
128
+ // Format: string | ['translationKey'] | ['translationKey', 'Fallback']
129
+ title : ['meta.home.title', 'Home'],
130
+ path : '/',
131
+ description : ['meta.home.desc', 'Welcome to our platform'],
132
+
133
+ // Keywords: strings are NOT translated, arrays ARE translated
134
+ keywords: [
135
+ 'home', // Direct string - no translation
136
+ ['meta.keywords.landing'], // Translate this keyword
137
+ ['meta.keywords.welcome', 'Welcome'] // With fallback
138
+ ],
139
+
140
+ // E-E-A-T Signals with translation support
141
+ expertise : 'Full-Stack Web Development',
142
+ experience : '2025+',
143
+ authority : ['meta.authority', 'CruxJS Framework'],
144
+
145
+ // Content type for AI indexing
146
+ contentType : 'page',
147
+
148
+ // OpenGraph for social media
149
+ ogImage : 'http://localhost:3000/static/img/og-home.png',
150
+ canonical : 'http://localhost:3000/'
151
+ }
152
+ ];
106
153
  ```
107
154
 
108
- - ### 3. With Custom Error Pages
155
+ - #### Error Page Configuration with Translation Support
109
156
 
110
157
  ```typescript
111
- const spaPlugin = serverSPA({
112
- baseUrl: 'https://example.com',
113
- clientScriptPath: ['/js/app.js'],
114
- clientStylePath: ['/css/min.css'],
115
- clientEntry: './src/client/index.tsx',
116
- enableAutoNotFound: true,
117
- errorPages: [
118
- {
119
- statusCode: 404,
120
- title: '404 - Page Not Found',
121
- path: '/404',
122
- description: 'The page you're looking for doesn\'t exist',
123
- robots: 'noindex, nofollow'
124
- },
125
- {
126
- statusCode: 500,
127
- title: '500 - Server Error',
128
- path: '/500',
129
- description: 'Something went wrong on our end'
130
- }
131
- ]
132
- });
158
+ const errorPages: ErrorPageConfig[] = [
159
+ {
160
+ statusCode : 404,
161
+ title : ['meta.error.title', '404 - Page Not Found'],
162
+ path : '/*',
163
+ description : ['meta.error.desc', 'The page you\'re looking for doesn\'t exist'],
164
+ keywords : ['error', '404', 'not found'], // Direct strings - no translation
165
+ robots : 'noindex, nofollow',
166
+ contentType : 'page'
167
+ },
168
+ {
169
+ statusCode : 500,
170
+ title : '500 - Server Error',
171
+ path : '/500',
172
+ description : 'Something went wrong on our end'
173
+ }
174
+ ];
133
175
  ```
134
176
 
135
177
  <br>
178
+ <br>
136
179
 
137
- - ## API Reference 🔥
138
-
139
- ### Core Plugin
140
-
141
- - #### `serverSPA(config: ServerSPAPluginConfig): CruxPlugin`
142
- > Creates and returns the SPA plugin with SEO support
143
-
144
- **Parameters:**
145
- - `baseUrl`: Base URL for canonical links and SEO (required)
146
- - `clientScriptPath`: Array of paths to client-side JS bundles (required)
147
- - `clientStylePath`: Array of paths to client-side CSS files (optional)
148
- - `clientEntry`: Path to client entry point (required)
149
- - `pages`: Array of pages to serve as SPA (optional)
150
- - `errorPages`: Array of error page configurations (optional)
151
- - `author`: Author name for structured data (optional)
152
- - `authorUrl`: Author profile URL (optional)
153
- - `enableAutoNotFound`: Auto-generate 404 page if true (optional, default: false)
154
- - `defaultDescription`: Default SEO description (optional)
155
- - `defaultKeywords`: Default SEO keywords array (optional)
156
- - `defaultRobots`: Default robots meta tag (optional)
157
-
158
- **Returns:** CruxPlugin with SEO support
159
-
160
- ```typescript
161
- const plugin = serverSPA({
162
- baseUrl: 'https://example.com',
163
- clientScriptPath: ['/js/app.js'],
164
- clientStylePath: ['/css/min.css'],
165
- clientEntry: './src/client/index.tsx',
166
- enableAutoNotFound: true,
167
- pages: [
168
- { title: 'Home', path: '/', description: 'Home page' }
169
- ]
170
- });
171
- ```
172
-
173
- ### Type Definitions
174
-
175
- - #### `SPAPageConfig`
176
- > Configuration for a single SPA page
177
-
178
- ```typescript
179
- interface SPAPageConfig {
180
- // Required
181
- title: string; // Page title
182
- path: string; // Route path
183
-
184
- // SEO
185
- description?: string; // Meta description
186
- keywords?: string[]; // Meta keywords array
187
- ogImage?: string; // Open Graph image URL
188
- canonical?: string; // Canonical URL
189
- robots?: string; // Robots meta tag
190
-
191
- // E-E-A-T Signals (Google AI Overviews)
192
- expertise?: string; // Author's expertise
193
- experience?: string; // Author's experience
194
- authority?: string; // Author's authority
195
-
196
- // Content
197
- contentType?: 'article' | 'product' | 'service' | 'app' | 'workspace' | 'page';
198
- clientScriptPath?: string[]; // Override client script paths
199
- clientStylePath?: string[]; // Override client style paths
200
- clientEntry?: string; // Override client entry
201
- }
202
- ```
203
-
204
- - #### `ErrorPageConfig`
205
- > Configuration for error pages
206
-
207
- ```typescript
208
- interface ErrorPageConfig extends SPAPageConfig {
209
- statusCode: number; // HTTP status code (404, 500, etc.)
210
- }
211
- ```
180
+ - ## API Reference 📚
212
181
 
213
- - #### `ServerSPAPluginConfig`
214
- > Main plugin configuration
182
+ - ### ..
215
183
 
216
- ```typescript
217
- interface ServerSPAPluginConfig {
218
- baseUrl: string;
219
- pages?: SPAPageConfig[];
220
- errorPages?: ErrorPageConfig[];
221
- clientEntry: string;
222
- clientScriptPath: string[]; // Array of script paths
223
- clientStylePath?: string[]; // Array of style paths
224
- author?: string;
225
- authorUrl?: string;
226
- defaultDescription?: string;
227
- defaultKeywords?: string[];
228
- defaultRobots?: string;
229
- enableAutoNotFound?: boolean;
230
- }
184
+ ```ts
185
+ ..
231
186
  ```
232
187
 
233
- ### Utility Functions (Advanced)
234
-
235
- - #### `generateSEOMetaTags(config, baseConfig): string`
236
- > Generates SEO meta tags with E-E-A-T signals
237
-
238
- **Features:**
239
- - Core SEO metadata (charset, viewport, description, keywords)
240
- - E-E-A-T signals for AI search optimization
241
- - Mobile optimization (web app capable, status bar)
242
- - Open Graph protocol tags
243
- - Performance & security headers
244
-
245
- - #### `generateStructuredData(pageConfig, baseConfig, contentType): string`
246
- > Generates JSON-LD structured data
247
188
 
248
- **Features:**
249
- - Schema.org compatible data
250
- - Support for multiple content types
251
- - Rich snippets for search results
252
- - Author and creator information
253
- - AI overview optimization
254
-
255
- - #### `generateSPAHTML(pageConfig, baseConfig): string`
256
- > Generates complete HTML document
189
+ <br>
190
+ <br>
257
191
 
258
- **Features:**
259
- - Full HTML5 shell with doctype
260
- - Integrated SEO and structured data
261
- - App mount point (#app)
262
- - Multiple module script loading (from array)
263
- - Multiple stylesheet loading (from array)
192
+ - ## Integration with @cruxjs/client 🔗
264
193
 
265
- - #### `createSPARoute(pageConfig, baseConfig): RouteDefinition`
266
- > Creates CruxJS route definition
194
+ > When combined with [@cruxjs/client](https://github.com/cruxjs-org/client):
267
195
 
268
- - #### `createErrorHandler(errorPageMap, baseConfig): Function`
269
- > Creates error handler for CruxJS
196
+ - i18n automatically injected via meta tag
197
+ - All plugin lifecycle hooks execute
198
+ - ✅ Routing immediately available
199
+ - ✅ Translations loaded before component render
200
+ - ✅ Zero boilerplate in user code
270
201
 
271
- **Features:**
272
- - Differentiates API vs web requests
273
- - JSON responses for `/api/*` routes
274
- - Custom HTML pages for web requests
275
- - Fallback error handling
276
202
 
277
203
  <!-- ╚═════════════════════════════════════════════════════════════════╝ -->
278
204
 
@@ -280,6 +206,7 @@
280
206
 
281
207
  <!-- ╔══════════════════════════════ END ══════════════════════════════╗ -->
282
208
 
209
+ <br>
283
210
  <br>
284
211
 
285
212
  ---
@@ -288,4 +215,4 @@
288
215
  <a href="https://github.com/maysara-elshewehy"><img src="https://img.shields.io/badge/by-Maysara-black"/></a>
289
216
  </div>
290
217
 
291
- <!-- ╚═════════════════════════════════════════════════════════════════╝ -->
218
+ <!-- ╚═════════════════════════════════════════════════════════════════╝ -->
package/dist/index.cjs CHANGED
@@ -1,48 +1,57 @@
1
- 'use strict';function l(e,t){let n=e.canonical||`${t.baseUrl}${e.path}`,o=e.robots||t.defaultRobots||"index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1",a=e.description||t.defaultDescription||"A modern single-page application",r=(e.keywords||t.defaultKeywords||[]).join(", ");return `
2
- <!-- \u{1F50D} Core SEO Meta Tags -->
3
- <meta charset="UTF-8" />
4
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
5
- <meta name="description" content="${a}" />
6
- ${r?`<meta name="keywords" content="${r}" />`:""}
7
- <meta name="robots" content="${o}" />
8
- <meta name="language" content="en" />
9
- <meta http-equiv="content-language" content="en-us" />
10
-
11
- <!-- \u{1F465} E-E-A-T Signals for AI Search -->
12
- ${t.author?`<meta name="author" content="${t.author}" />`:""}
13
- ${e.expertise?`<meta name="expertise" content="${e.expertise}" />`:""}
14
- ${e.experience?`<meta name="experience" content="${e.experience}" />`:""}
15
- ${e.authority?`<meta name="authority" content="${e.authority}" />`:""}
16
-
17
- <!-- \u{1F4F1} Mobile & Performance -->
18
- <meta name="mobile-web-app-capable" content="yes" />
19
- <meta name="apple-mobile-web-app-capable" content="yes" />
20
- <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
21
- <meta name="theme-color" content="#000000" />
22
-
23
- <!-- \u{1F517} Canonical & Prefetch -->
24
- <link rel="canonical" href="${n}" />
25
- ${(e.clientScriptPath||t.clientScriptPath)?.map(s=>`<link rel="prefetch" href="${s}" />`).join(`
26
- `)}
27
-
28
- <!-- \u26A1 Performance & Security -->
29
- <meta name="format-detection" content="telephone=no" />
30
- <meta http-equiv="x-ua-compatible" content="IE=edge" />
31
- ${e.ogImage?`<meta property="og:image" content="${e.ogImage}" />`:""}
32
- <meta property="og:title" content="${e.title}" />
33
- <meta property="og:description" content="${a}" />
34
- <meta property="og:url" content="${n}" />`}function c(e,t,n="WebPage"){let o=e.canonical||`${t.baseUrl}${e.path}`,a={"@context":"https://schema.org","@type":n,name:e.title,url:o,description:e.description||t.defaultDescription,inLanguage:"en",...e.contentType&&{genre:e.contentType},...t.author&&{author:{"@type":"Person",name:t.author,...t.authorUrl&&{url:t.authorUrl}}},...(e.expertise||e.experience||e.authority)&&{creator:{"@type":"Person",name:t.author||"Unknown",...e.expertise&&{expertise:e.expertise},...e.experience&&{experience:e.experience},...e.authority&&{authority:e.authority}}}};return `<script type="application/ld+json">${JSON.stringify(a,null,2)}</script>`}function p(e,t){let n=e.clientScriptPath||t.clientScriptPath,o=e.clientStylePath||t.clientStylePath||[],a=n.map(s=>` <script type="module" src="${s}"></script>`).join(`
35
- `),r=o.map(s=>` <link rel="stylesheet" href="${s}" />`).join(`
36
- `);return `<!DOCTYPE html>
1
+ 'use strict';var E=class{constructor(e){this.translations={},this.currentLanguage="en",this.defaultLanguage="en",this.fallbackLanguage="en",this.supportedLanguages=new Set(["en"]),this.rtlLanguages=new Set(["ar","he","fa","ur","yi","ji","iw","ku"]),this.listeners=new Set,e&&(this.defaultLanguage=e.defaultLanguage||"en",this.fallbackLanguage=e.fallbackLanguage||e.defaultLanguage||"en",this.currentLanguage=e.defaultLanguage||"en",this.storage=e.storage,this.onLanguageChange=e.onLanguageChange,e.supportedLanguages&&(this.supportedLanguages=new Set(e.supportedLanguages)));}async init(){if(this.storage){let e=await this.storage.get("i18n-language");e&&this.supportedLanguages.has(e)&&(this.currentLanguage=e);}}loadLanguage(e,t){this.translations[e]||(this.translations[e]={});let n=this.flattenObject(t);this.translations[e]={...this.translations[e],...n},this.supportedLanguages.add(e);}loadTranslations(e){Object.entries(e).forEach(([t,n])=>{this.loadLanguage(t,n);});}flattenObject(e,t=""){let n={};for(let a in e){if(!Object.prototype.hasOwnProperty.call(e,a))continue;let r=e[a],o=t?`${t}.${a}`:a;typeof r=="object"&&r!==null&&!Array.isArray(r)?Object.assign(n,this.flattenObject(r,o)):n[o]=String(r);}return n}t(e,t){let n=this.getTranslation(e);return t&&Object.entries(t).forEach(([a,r])=>{let o=this.getTranslation(r,r);n=n.replace(new RegExp(`\\{${a}\\}`,"g"),o);}),n}getTranslation(e,t){return this.translations[this.currentLanguage]?.[e]?this.translations[this.currentLanguage][e]:this.fallbackLanguage!==this.currentLanguage&&this.translations[this.fallbackLanguage]?.[e]?this.translations[this.fallbackLanguage][e]:this.defaultLanguage!==this.currentLanguage&&this.defaultLanguage!==this.fallbackLanguage&&this.translations[this.defaultLanguage]?.[e]?this.translations[this.defaultLanguage][e]:(console.warn(`[i18n] Translation key not found: "${e}" (lang: ${this.currentLanguage})`),t||e)}tLang(e,t,n){let a=this.currentLanguage;this.currentLanguage=t;let r=this.t(e,n);return this.currentLanguage=a,r}tParse(e,t){let n=this.t(e,t);n=n.replace(/\\n|\/n/g,"<br>");let a=[],r=/<([a-z]+)>([^<]*)<\/\1>|<([a-z]+)\s*\/?>|([^<]+)/gi,o;for(;(o=r.exec(n))!==null;)o[4]?a.push({type:"text",content:o[4]}):o[1]?a.push({type:"tag",tag:o[1],content:o[2]}):o[3]&&a.push({type:"tag",tag:o[3],content:""});return a.length>0?a:[{type:"text",content:n}]}async setLanguage(e){if(!this.supportedLanguages.has(e)){console.warn(`[i18n] Language "${e}" not supported`);return}this.currentLanguage=e,this.storage&&await this.storage.set("i18n-language",e),this.listeners.forEach(t=>t(e)),this.onLanguageChange&&this.onLanguageChange(e);}getLanguage(){return this.currentLanguage}getSupportedLanguages(){return Array.from(this.supportedLanguages)}isLanguageSupported(e){return this.supportedLanguages.has(e)}hasKey(e){return !!(this.translations[this.currentLanguage]?.[e]||this.translations[this.fallbackLanguage]?.[e]||this.translations[this.defaultLanguage]?.[e])}getTranslations(){return this.translations[this.currentLanguage]||{}}isRTL(){return this.rtlLanguages.has(this.currentLanguage.toLowerCase().substring(0,2))}isRTLLanguage(e){return this.rtlLanguages.has(e.toLowerCase().substring(0,2))}onChange(e){return this.listeners.add(e),()=>this.listeners.delete(e)}};var f=null;function y(){return f||(f=new E),f}var h=(e,t)=>y().t(e,t);var k=()=>y().isRTL();function L(e,t="page."){let n=h("app.name"),a=h(t+e);return k()?`${n} - ${a}`:`${a} - ${n}`}function c(e,t=""){if(!e)return t;if(Array.isArray(e)){let[n,a]=e;if(!n)return a||t;try{return h(n)||a||n||t}catch{return a||n||t}}try{return h(e)||e}catch{return e}}function v(e){return !e||e.length===0?"":e.map(n=>{if(typeof n=="string")return n;if(Array.isArray(n)){let[a,r]=n;if(!a)return r||"";try{return h(a)||r||a}catch{return r||a}}return ""}).filter(Boolean).join(", ")}function P(e){if(!e)return "Page";let t="";Array.isArray(e)?t=e[0]||"":t=e;try{return L(t,"")}catch{return Array.isArray(e)?e[1]||e[0]||"Page":e}}function S(e,t){let n=e.canonical||`${t.baseUrl}${e.path}`,a=e.robots||t.defaultRobots||"index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1",r=P(e.title),o=c(e.description,t.defaultDescription||"A modern single-page application"),i=v(e.keywords||t.defaultKeywords),s=c(e.expertise,""),l=c(e.experience,""),g=c(e.authority,""),u=e.contentType==="article"?"article":"website";return `<!-- \u{1F50D} Core SEO Meta Tags -->
2
+ <meta charset="UTF-8" />
3
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
4
+ <title>${r}</title>
5
+ <meta name="description" content="${o}" />
6
+ ${i?`<meta name="keywords" content="${i}" />`:""}
7
+ <meta name="robots" content="${a}" />
8
+ <meta name="language" content="en" />
9
+ <meta http-equiv="content-language" content="en-us" />
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}" />`:""}
13
+ ${l?`<meta name="experience" content="${l}" />`:""}
14
+ ${g?`<meta name="authority" content="${g}" />`:""}
15
+ <!-- \u{1F4F1} Mobile & Performance -->
16
+ <meta name="mobile-web-app-capable" content="yes" />
17
+ <meta name="apple-mobile-web-app-capable" content="yes" />
18
+ <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
19
+ <meta name="theme-color" content="#000000" />
20
+ <!-- \u{1F517} Canonical & Prefetch -->
21
+ <link rel="canonical" href="${n}" />
22
+ ${(e.clientScriptPath||t.clientScriptPath)?.map(p=>`<link rel="prefetch" href="${p}" />`).join(`
23
+ `)}
24
+ <!-- \u26A1 Performance & Security -->
25
+ <meta name="format-detection" content="telephone=no" />
26
+ <meta http-equiv="x-ua-compatible" content="IE=edge" />
27
+ <!-- \u{1F4D8} Open Graph Protocol (Social Media) -->
28
+ <meta property="og:type" content="${u}" />
29
+ <meta property="og:title" content="${r}" />
30
+ <meta property="og:description" content="${o}" />
31
+ <meta property="og:url" content="${n}" />
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="${r}" />`:""}
36
+ `}function b(e,t,n="WebPage"){let a=e.canonical||`${t.baseUrl}${e.path}`,r=P(e.title),o=c(e.description,t.defaultDescription),i=c(e.expertise,""),s=c(e.experience,""),l=c(e.authority,""),g={"@context":"https://schema.org","@type":n,name:r,url:a,description:o,inLanguage:"en",...e.contentType&&{genre:e.contentType},...t.author&&{author:{"@type":"Person",name:t.author,...t.authorUrl&&{url:t.authorUrl}}},...(i||s||l)&&{creator:{"@type":"Person",name:t.author||"Unknown",...i&&{expertise:i},...s&&{experience:s},...l&&{authority:l}}}};return `<script type="application/ld+json">
37
+ ${JSON.stringify(g,null,2).split(`
38
+ `).map(p=>p&&` ${p}`).join(`
39
+ `)}
40
+ </script>`}var j=["area","base","br","col","embed","hr","img","input","link","meta","param","source","track","wbr"];function R(e){let t=[],n=/(<[^>]+>)|([^<]+)/g,a;for(;(a=n.exec(e))!==null;)a[1]?t.push({type:"tag",value:a[1]}):a[2]&&a[2].trim().length>0&&t.push({type:"text",value:a[2]});return t}function M(e,t=4){let n=" ".repeat(t),a=[],r=0;for(let o=0;o<e.length;o++){let i=e[o],s=i.value.trim();s.startsWith("</")&&(r=Math.max(0,r-1));let l=n.repeat(r);if(!(i.type==="text"&&s.length===0)&&(i.type==="text"&&a.length>0&&!a[a.length-1].trim().endsWith(">")?a[a.length-1]+=s:a.push(l+s),s.startsWith("<")&&!s.startsWith("</"))){if(s.startsWith("<!DOCTYPE")||s.startsWith("<!--"))continue;let g=s.match(/<([A-Za-z][A-Za-z0-9\\-]*)/i),u=g?g[1].toLowerCase():"",p=j.includes(u),T=s.endsWith("/>");!p&&!T&&r++;}}return a}function w(e,t=4){let n=e.split(`
41
+ `).map(o=>o.trim()).filter(o=>o.length>0).join(""),a=R(n);return M(a,t).join(`
42
+ `)}function O(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 m(e,t,n){let a=e.clientScriptPath||t.clientScriptPath,r=e.clientStylePath||t.clientStylePath||[],o=a.map(u=>`<script type="module" src="${u}"></script>`).join(`
43
+ `),i=r.map(u=>`<link rel="stylesheet" href="${u}" />`).join(`
44
+ `),s=O(n);s||console.warn("[SPA] WARNING: i18n meta tag is empty!");let l=`<!DOCTYPE html>
37
45
  <html lang="en">
38
46
  <head>
39
- ${l(e,t)}
40
- ${c(e,t,e.contentType==="article"?"Article":"WebPage")}
41
- ${r||""}
47
+ ${S(e,t)}
48
+ ${b(e,t,e.contentType==="article"?"Article":"WebPage")}
49
+ ${s}
50
+ ${i}
42
51
  </head>
43
52
  <body>
44
- <div id="app"></div>
45
- ${a}
53
+ <div id="app"></div>
54
+ ${o}
46
55
  </body>
47
- </html>`}function i(e,t){return {method:"GET",path:e.path,handler:n=>{let o=p(e,t);return n.html(o)}}}function P(e,t,n,o){if(o.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),r=p(a,n);return {status:e,headers:{"Content-Type":"text/html; charset=utf-8"},body:r}}return {status:e,headers:{"Content-Type":"text/plain"},body:`Error ${e}`}}function u(e,t){return (n,o)=>{let a=P(n,e,t,o);return new Response(a.body,{status:a.status,headers:a.headers})}}function m(){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 $(e){let t=[],n=new Map;if(e.pages&&e.pages.length>0)for(let r of e.pages)t.push(i(r,e));if(e.errorPages&&e.errorPages.length>0)for(let r of e.errorPages)n.set(r.statusCode,r),t.push(i(r,e));if(e.enableAutoNotFound&&!n.has(404)){let r=m();n.set(404,r),t.push(i(r,e));}let o=u(n,e);return {name:"@cruxplug/SPA",version:"0.1.0",routes:t,__spaErrorHandler:o,onRegister:async r=>{if(console.log(`[SPA Plugin] Registered ${t.length} SPA routes`),n.size>0){let s=Array.from(n.keys()).join(", ");console.log(`[SPA Plugin] Error pages configured for: ${s}`);}},onAwake:async r=>{console.log("[SPA Plugin] Awake phase - SPA routes ready");},onStart:async r=>{console.log("[SPA Plugin] Start phase - serving SPA");},onReady:async r=>{console.log("[SPA Plugin] Ready phase - SPA is fully operational");}}}exports.serverSPA=$;//# sourceMappingURL=index.cjs.map
56
+ </html>`;l.includes("app-i18n")||console.error("[SPA] ERROR: i18n meta tag NOT in raw HTML!");let g=w(l);return g.includes("app-i18n")||console.error("[SPA] ERROR: i18n meta tag LOST during formatting!"),g}function d(e,t,n){return {method:"GET",path:e.path,handler:a=>{let r=m(e,t,n);return a.html(r)}}}function I(){return global.__cruxjs_i18n_config}function A(e){global.__cruxjs_i18n_config=e;}function C(e,t,n,a){if(a.startsWith("/api/"))return {status:e,headers:{"Content-Type":"application/json"},body:JSON.stringify({error:`Error ${e}`})};if(t.has(e)){let r=t.get(e),o=I();console.log(`[Errors] Generating error page ${e} with i18n:`,!!o);let i=m(r,n,o);return {status:e,headers:{"Content-Type":"text/html; charset=utf-8"},body:i}}return console.log(`[Errors] No custom error page for ${e}, returning fallback`),{status:e,headers:{"Content-Type":"text/plain"},body:`Error ${e}`}}function x(e,t){return (n,a)=>{let r=C(n,e,t,a);return new Response(r.body,{status:r.status,headers:r.headers})}}function $(){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 Y(e,t){let n=[],a=new Map;A(t?.i18n);let r=t?.i18n;if(e.pages&&e.pages.length>0)for(let s of e.pages)n.push(d(s,e,r));if(e.errorPages&&e.errorPages.length>0)for(let s of e.errorPages)a.set(s.statusCode,s),n.push(d(s,e,r));if(e.enableAutoNotFound&&!a.has(404)){let s=$();a.set(404,s),n.push(d(s,e,r));}let o=x(a,e);return {name:"@cruxplug/SPA",version:"0.1.0",routes:n,__spaErrorHandler:o,onRegister:async s=>{if(console.log(`[SPA Plugin] Registered ${n.length} SPA routes`),a.size>0){let l=Array.from(a.keys()).join(", ");console.log(`[SPA Plugin] Error pages configured for: ${l}`);}},onAwake:async s=>{console.log("[SPA Plugin] Awake phase - SPA routes ready");},onStart:async s=>{console.log("[SPA Plugin] Start phase - serving SPA");},onReady:async s=>{console.log("[SPA Plugin] Ready phase - SPA is fully operational");}}}exports.serverSPA=Y;//# sourceMappingURL=index.cjs.map
48
57
  //# sourceMappingURL=index.cjs.map