@symbo.ls/mermaid 3.14.4 → 3.14.5
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/CHANGELOG.md +9 -0
- package/dist/esm/bin/start.js +2 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/src/bundle.js +97 -0
- package/dist/esm/src/render.js +61 -0
- package/dist/esm/src/route.js +1 -0
- package/dist/esm/src/server.js +1 -0
- package/dist/esm/src/utils/deepDestringify.js +1 -0
- package/dist/esm/src/utils/funcql-transpile.js +1 -0
- package/dist/esm/src/utils/logger.js +1 -0
- package/dist/esm/src/utils/vartranformer.js +1 -0
- package/package.json +62 -17
- package/index.js +0 -21
- package/src/bundle.js +0 -217
- package/src/render.js +0 -502
- package/src/route.js +0 -80
- package/src/server.js +0 -190
- package/src/template.html +0 -23
- package/src/utils/deepDestringify.js +0 -149
- package/src/utils/funcql-transpile.js +0 -346
- package/src/utils/logger.js +0 -131
- package/src/utils/vartranformer.js +0 -30
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{createMermaidApp as M,loadJsonData as d}from"../src/server.js";import o from"../src/utils/logger.js";const p=process.argv.slice(2);function e(n,E){const c=p.indexOf(`--${n}`);return c!==-1?p[c+1]:E}const i=parseInt(e("port",process.env.PORT||"3333"),10),s=e("json",process.env.JSON_PATH||"./app.json"),r=e("appkey",process.env.APPKEY||null),t="production",l=process.env.LOG_LEVEL||(t==="production"?"info":"debug");o.level=l,o.info("[MERMAID] Starting standalone runner..."),o.info(`[MERMAID] Environment: ${t}`),o.info(`[MERMAID] JSON: ${s}`),r&&o.info(`[MERMAID] Locked to appkey: ${r}`);try{d(s)}catch(n){o.error(`[MERMAID] ${n.message}`),process.exit(1)}const v=M({jsonPath:s,appkeyOverride:r,channel:t==="production"?"production":"development"}),I=v.listen(i,()=>{o.info(`[MERMAID] Running on http://localhost:${i}`)}),a=n=>{o.info(`[MERMAID] ${n} received, shutting down...`),I.close(),process.exit(0)};process.on("SIGTERM",()=>a("SIGTERM")),process.on("SIGINT",()=>a("SIGINT"));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createMermaidApp as t,loadJsonData as o}from"./src/server.js";import{renderProjectHtml as n,renderNotPublishedHtml as i,transpileFunctionStrings as p,generateSitemap as l,prefetchPageData as s}from"./src/render.js";import{getBundleScript as f,getSmblsIife as c}from"./src/bundle.js";import{normalizeRoute as d,resolvePageRoute as g,extractRouteParams as x,STATIC_EXT as S}from"./src/route.js";import{deepDestringifyFunctions as P,stripFunctionStrings as b}from"./src/utils/deepDestringify.js";import{transpile as I}from"./src/utils/funcql-transpile.js";import{default as T}from"./src/utils/vartranformer.js";import{default as y}from"./src/utils/logger.js";export{S as STATIC_EXT,t as createMermaidApp,P as deepDestringifyFunctions,x as extractRouteParams,l as generateSitemap,f as getBundleScript,c as getSmblsIife,o as loadJsonData,y as log,d as normalizeRoute,s as prefetchPageData,i as renderNotPublishedHtml,n as renderProjectHtml,T as replaceVariablesInFileSync,g as resolvePageRoute,b as stripFunctionStrings,I as transpileFuncql,p as transpileFunctionStrings};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import r from"./utils/logger.js";import h from"smbls/iife-string";r.debug(`[BUNDLE] smbls IIFE loaded from smbls/iife-string (${h.length} bytes)`);const u=n=>n.replace(/</g,"\\u003c");function v(){return h}function w(n,e,t,{ProjectDataService:i}={}){if(r.debug(`[BUNDLE] getBundleScript started for ${n}`),!e)return r.warn("[BUNDLE] No data provided, returning empty"),"";try{if(e.merged)Object.assign(e,e.merged),r.debug(`[BUNDLE] Applied precomputed merged data for project ${e.id}`);else if(i&&Array.isArray(e.sharedLibraries)&&e.sharedLibraries.length>0){const s=i.mergeSharedLibraries(e,e.sharedLibraries,e.projectType);Object.assign(e,s),r.debug(`[BUNDLE] Merged ${e.sharedLibraries.length} shared libraries`)}}catch(s){r.error(`[BUNDLE] Failed to ensure merged data: ${s.message}`)}const m=u(JSON.stringify(e)),f=t?.headers&&typeof t.headers.get=="function"&&t.headers.get("host")||t?.headers?.host||t?.host||t?.url&&(()=>{try{return new URL(t.url).host}catch{return""}})()||"",o=String(f).toLowerCase().split(":")[0],a=(()=>{if(t?.query)return t.query;try{return Object.fromEntries(new URL(t?.url||"","http://x").searchParams)}catch{return{}}})(),g=a.release||!1,p=a.version,c=o.endsWith(".symbo.ls")||o.endsWith(".preview.symbols.app")||o==="localhost"||o.endsWith(".localhost"),l=!!g||!!p||!c,b=e&&(e._id||e.projectId||e.id)||null,y=c?u(JSON.stringify({...e.settings,liveSync:!l,inspect:!0,verbose:!l,projectId:b})):"null",d=`
|
|
2
|
+
var context = Smbls.deepDestringifyFunctions(${m});
|
|
3
|
+
context.packageManager = 'esm.sh';
|
|
4
|
+
if (context.db && context.db.createClient) delete context.db.createClient;
|
|
5
|
+
if (context.fetch && context.fetch.createClient) delete context.fetch.createClient;
|
|
6
|
+
|
|
7
|
+
// Runtime defaults (these are baked into the starter-kit/frank but missing from server data)
|
|
8
|
+
if (context.useReset === undefined) context.useReset = true;
|
|
9
|
+
if (context.useVariable === undefined) context.useVariable = true;
|
|
10
|
+
if (context.useFontImport === undefined) context.useFontImport = true;
|
|
11
|
+
if (context.useIconSprite === undefined) context.useIconSprite = true;
|
|
12
|
+
if (context.useSvgSprite === undefined) context.useSvgSprite = true;
|
|
13
|
+
if (context.useDefaultConfig === undefined) context.useDefaultConfig = true;
|
|
14
|
+
if (context.useDocumentTheme === undefined) context.useDocumentTheme = true;
|
|
15
|
+
if (!context.routerOptions) context.routerOptions = { scrollToTop: true };
|
|
16
|
+
|
|
17
|
+
var app = context.app || {};
|
|
18
|
+
delete context.app;
|
|
19
|
+
var settings = ${y};
|
|
20
|
+
if (settings) context.editor = settings;
|
|
21
|
+
|
|
22
|
+
// Theme initialization: set data-theme before app renders to avoid FOUC.
|
|
23
|
+
// Mirrors the precedence in resolveAndApplyTheme (packages/smbls/src/index.js):
|
|
24
|
+
// 1. URL param ?globalTheme=<name> \u2014 embedded-iframe override
|
|
25
|
+
// 2. localStorage[themeStorageKey] \u2014 persisted user choice
|
|
26
|
+
// 3. context.globalTheme \u2014 project's configured value
|
|
27
|
+
// 4. matchMedia(prefers-color-scheme: dark) \u2014 OS fallback (when 'auto')
|
|
28
|
+
//
|
|
29
|
+
// Writing the literal 'auto' as data-theme would leave the
|
|
30
|
+
// [data-theme="dark"] / [data-theme="light"] CSS selectors unmatched and
|
|
31
|
+
// the page would render with system defaults instead of the chosen
|
|
32
|
+
// theme \u2014 so 'auto' is always resolved to a concrete light/dark value.
|
|
33
|
+
//
|
|
34
|
+
// PRIOR BUG: this script previously ignored context.globalTheme entirely
|
|
35
|
+
// and went straight to matchMedia after localStorage. A project with
|
|
36
|
+
// globalTheme: light in config.js would render in OS dark on a
|
|
37
|
+
// dark-mode device because the inline boot ran first and locked in
|
|
38
|
+
// dark before resolveAndApplyTheme could correct it.
|
|
39
|
+
//
|
|
40
|
+
// IDEMPOTENT GUARD: the SSR-side render in @symbo.ls/mermaid/render.js
|
|
41
|
+
// now bakes data-theme onto <html> for concrete-theme projects and
|
|
42
|
+
// emits an inline <head> script that runs synchronously before paint.
|
|
43
|
+
// When that path runs, data-theme is already set by the time the bundle
|
|
44
|
+
// executes \u2014 skip this block to avoid re-resolving and overwriting
|
|
45
|
+
// (which would race with URL/localStorage values the head script
|
|
46
|
+
// already honored). Kept as a safety net for older renders / cases
|
|
47
|
+
// where the head script couldn't run.
|
|
48
|
+
if (
|
|
49
|
+
context.useDocumentTheme !== false &&
|
|
50
|
+
!document.documentElement.hasAttribute('data-theme')
|
|
51
|
+
) {
|
|
52
|
+
(function() {
|
|
53
|
+
try {
|
|
54
|
+
var t = null;
|
|
55
|
+
// 1. URL param wins (used by preview/canvas iframe embedding).
|
|
56
|
+
try {
|
|
57
|
+
var p = new URLSearchParams(window.location.search).get('globalTheme');
|
|
58
|
+
if (p) t = p;
|
|
59
|
+
} catch(e) {}
|
|
60
|
+
// 2. localStorage \u2014 only consulted when no forcing source above.
|
|
61
|
+
if (!t) {
|
|
62
|
+
var key = context.themeStorageKey;
|
|
63
|
+
if (key) { try { t = localStorage.getItem(key) } catch(e) {} }
|
|
64
|
+
}
|
|
65
|
+
// 3. Project's configured globalTheme (config.js export). Honors an
|
|
66
|
+
// explicit 'light'/'dark'/<scheme> instead of falling through to OS.
|
|
67
|
+
if (!t && context.globalTheme && context.globalTheme !== 'auto') {
|
|
68
|
+
t = context.globalTheme;
|
|
69
|
+
}
|
|
70
|
+
// 4. OS preference fallback \u2014 only when nothing concrete chose.
|
|
71
|
+
if (!t || t === 'auto') {
|
|
72
|
+
t = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
|
73
|
+
}
|
|
74
|
+
if (t) document.documentElement.setAttribute('data-theme', t);
|
|
75
|
+
} catch(e) {}
|
|
76
|
+
})();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// When brender pre-rendered this page, adopt the existing DOM nodes
|
|
80
|
+
// instead of tearing them down. The [data-ssr] wrapper is moved out of
|
|
81
|
+
// the way so pre-rendered children land at document.body level \u2014 matching
|
|
82
|
+
// the level at which DOMQL creates its own elements. The __BRENDER__ flag
|
|
83
|
+
// and __BR_REGISTRY__ map were injected into <head> by the server.
|
|
84
|
+
var ssrShell = document.querySelector('[data-ssr]');
|
|
85
|
+
if (window.__BRENDER__ && ssrShell) {
|
|
86
|
+
// Move pre-rendered children to body before DOMQL skeleton creation
|
|
87
|
+
var frag = document.createDocumentFragment();
|
|
88
|
+
while (ssrShell.firstChild) frag.appendChild(ssrShell.firstChild);
|
|
89
|
+
document.body.insertBefore(frag, ssrShell);
|
|
90
|
+
ssrShell.remove();
|
|
91
|
+
} else if (ssrShell) {
|
|
92
|
+
// No brender hydration \u2014 remove empty/stale SSR shell
|
|
93
|
+
ssrShell.remove();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
Smbls.create(app, context);
|
|
97
|
+
`;return r.debug(`[BUNDLE] Script generated (${d.length} bytes)`),d}export{w as getBundleScript,v as getSmblsIife};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import b from"./utils/logger.js";import at from"./utils/vartranformer.js";import{getBundleScript as ct,getSmblsIife as lt}from"./bundle.js";import{deepDestringifyFunctions as pt}from"./utils/deepDestringify.js";import{transpile as J}from"./utils/funcql-transpile.js";import{normalizeRoute as gt,resolvePageRoute as ft}from"./route.js";import{generateMetaTags as mt,getPageMetadata as dt,getImportMapScript as ht}from"@symbo.ls/utils";const ut=process.env.BRENDER!=="false";let L=null,C=null,H=null;if(ut)try{const t=await import("@symbo.ls/brender");L=t.renderRoute,C=t.generateSitemap,H=t.prefetchPageData}catch(t){b.debug(`[MERMAID] Brender not available: ${t.message}`)}const yt=`<!doctype html>
|
|
2
|
+
<html'\${{{ ROOT_ATTRS }}}'>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
'\${{{ HEAD_BOOT }}}'
|
|
6
|
+
'\${{{ META }}}'
|
|
7
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
8
|
+
'\${{{ IMPORTMAP }}}'
|
|
9
|
+
</head>
|
|
10
|
+
<body>
|
|
11
|
+
<div data-ssr>'\${{{ BODY }}}'</div>
|
|
12
|
+
<script>
|
|
13
|
+
window.process = { env: { NODE_ENV: '\${{{ NODE_ENV }}}' } };
|
|
14
|
+
<\/script>
|
|
15
|
+
<script>
|
|
16
|
+
'\${{{ SMBLS_IIFE }}}';
|
|
17
|
+
<\/script>
|
|
18
|
+
<script>
|
|
19
|
+
'\${{{ BUNDLE }}}';
|
|
20
|
+
<\/script>
|
|
21
|
+
'\${{{ EDITOR }}}'
|
|
22
|
+
</body>
|
|
23
|
+
</html>`;function M(t,i=Array.isArray(t)?[]:{}){for(const o in t){if(!Object.prototype.hasOwnProperty.call(t,o))continue;const n=t[o];if(typeof n=="string"){const s=n.trim();if(s.startsWith("(")&&s.includes("=>")||s.startsWith("function")||s.startsWith("async ")){const l=J(n);i[o]=l?l.schema:null}else i[o]=n}else Array.isArray(n)?i[o]=n.map(s=>{if(typeof s=="string"){const a=s.trim();if(a.startsWith("(")&&a.includes("=>")||a.startsWith("function")||a.startsWith("async ")){const y=J(s);return y?y.schema:null}}return s&&typeof s=="object"?M(s):s}):n&&typeof n=="object"&&!Array.isArray(n)?i[o]=M(n):i[o]=n}return i}async function xt(t,i={}){const{appkey:o,pathname:n="/",channel:s="production",userEnv:a=s,isProd:l=!1,req:y,query:bt,userId:B,smblsIife:U,editorBundle:A,analyzedScript:z="",indexHtml:K,ssrCache:r,ProjectDataService:W}=i;if(!t)return{html:"",status:404,contentType:"text/html"};if(typeof o=="string"&&o.includes("/")){const[c,p]=o.split("/");t.key||(t.key=p),t.owner||(t.owner=c)}const w=gt(n),S=ft(t.pages,n),N=a==="production"||a==="staging";let E="",I="",e=null,O=null,x="";if(r&&N){E=r.html||"";const c=[];r.fontLinks&&c.push(r.fontLinks);const p=[r.globalCSS||"",r.resetCss||""].filter(Boolean).join(`
|
|
24
|
+
`);p&&c.push(`<style>${p}</style>`),r.css&&c.push(`<style data-emotion="smbls">${r.css}</style>`),I=c.join(`
|
|
25
|
+
`);const g=[];if(r.ssrTranslations){const d=(t.config||t.settings||{}).polyglot||t.polyglot,h=d?.storagePrefix||"",T=d?.storageLangKey||"",f=[];for(const u in r.ssrTranslations){const m=r.ssrTranslations[u];m&&typeof m=="object"&&f.push(`localStorage.setItem(${JSON.stringify(h+u)},${JSON.stringify(JSON.stringify(m))})`)}if(T){const u=d?.defaultLang||"en";f.push(`if(!localStorage.getItem(${JSON.stringify(T)}))localStorage.setItem(${JSON.stringify(T)},${JSON.stringify(u)})`)}f.length&&g.push(`<script>try{${f.join(";")}}catch(e){}<\/script>`)}g.push("<script>window.__BRENDER__=true<\/script>"),x=g.join(`
|
|
26
|
+
`),e={metadata:r.metadata,prefetchedState:r.prefetchedState,activeLang:r.activeLang}}else if(L&&N)try{const c=S?S.route:w;b.debug(`[MERMAID] Running brender for route: ${c}`);let p=!1;try{(0,eval)("1"),p=!0}catch{}if(O=p?pt(t):M(t),e=await L(O,{route:c,pathname:w}),e){E=e.html||"";const g=[];e.fontLinks&&g.push(e.fontLinks);const D=e.globalCSS||{},d=[D.rootRule||"",e.resetCss||"",D.keyframeRules||""].filter(Boolean).join(`
|
|
27
|
+
`);d&&g.push(`<style>${d}</style>`),e.css&&g.push(`<style data-emotion="smbls">${e.css}</style>`),I=g.join(`
|
|
28
|
+
`);const h=[];if(e.ssrTranslations){const f=(O?.config||{}).polyglot||O?.polyglot,u=f?.storagePrefix||"",m=f?.storageLangKey||"",v=[];for(const R in e.ssrTranslations){const P=e.ssrTranslations[R];P&&typeof P=="object"&&v.push(`localStorage.setItem(${JSON.stringify(u+R)},${JSON.stringify(JSON.stringify(P))})`)}if(m){const R=f?.defaultLang||"en";v.push(`if(!localStorage.getItem(${JSON.stringify(m)}))localStorage.setItem(${JSON.stringify(m)},${JSON.stringify(R)})`)}v.length&&h.push(`<script>try{${v.join(";")}}catch(e){}<\/script>`)}p&&e.brRegistry&&Object.keys(e.brRegistry).length&&h.push(`<script>window.__BR_REGISTRY__=${JSON.stringify(e.brRegistry)}<\/script>`),h.push("<script>window.__BRENDER__=true<\/script>"),x=h.join(`
|
|
29
|
+
`),b.debug(`[MERMAID] Brender completed: body=${E.length}b, registry=${Object.keys(e.brRegistry||{}).length}`)}}catch(c){b.warn(`[MERMAID] Brender failed, falling back to client render: ${c.message}`)}const V=ct(o,t,y,{ProjectDataService:W}),Y=ht(t,t?.settings?.cdnProvider||"esmsh"),q=t?.files&&t.files["index.html"],G=K||q?.code||yt,Q=B?`<script>window.__SYMBOLS_AUTH__=${JSON.stringify({userId:B})}<\/script>`:"",_=e?.prefetchedState,X=(t.config||t.settings||{}).polyglot||t.polyglot,Z=e?.activeLang||X?.defaultLang||"en",tt=_&&Object.keys(_).length?{state:_,lang:Z,actualPathname:n}:{actualPathname:n},et=e?.metadata&&Object.keys(e.metadata).length?e.metadata:dt(t,S?S.route:w,tt,{isProduction:l,channel:s}),nt=mt(et,l),j=U||lt();if(!j)return b.error("[MERMAID] smbls IIFE bundle not available"),{html:"Internal Server Error: smbls bundle not found",status:500,contentType:"text/plain"};const st=A&&!l?`<script defer type="text/javascript">${A}<\/script>`:"",ot=l?`<!-- built with symbols.app -->
|
|
30
|
+
`:`<!-- symbols | ${o} | ${a} | ${N?"ssr":"csr"} | noindex -->
|
|
31
|
+
`,k=t.useDocumentTheme!==!1,$=t.globalTheme&&t.globalTheme!=="auto"?String(t.globalTheme):null,F=t.themeStorageKey?String(t.themeStorageKey):"",rt=k&&$?' data-theme="'+$+'"':"",it=k?"<script>(function(){try{var t=null;try{t=new URLSearchParams(location.search).get('globalTheme')}catch(e){}"+(F?"if(!t){try{t=localStorage.getItem("+JSON.stringify(F)+")}catch(e){}}":"")+"if(!t||t==='auto'){"+($?"t="+JSON.stringify($)+";":"try{t=window.matchMedia&&window.matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light'}catch(e){}")+`}if(t&&t!=='auto')document.documentElement.setAttribute('data-theme',t);}catch(e){}})();<\/script>
|
|
32
|
+
<meta name="color-scheme" content="light dark">`:"";return{html:at(ot+G,{ROOT_ATTRS:rt,HEAD_BOOT:it,META:nt+`
|
|
33
|
+
`+I+`
|
|
34
|
+
`+x+`
|
|
35
|
+
`+Q,BUNDLE:V,BODY:E,SMBLS_IIFE:j,NODE_ENV:"'production'",IMPORTMAP:Y,EDITOR:st+z},{allowScript:!0}),status:200,contentType:"text/html"}}function _t(t,i="production",o=null){const n=i!=="production"?` (${i})`:"",s=o==="not_public",a=s?"Site Not Yet Public":"Not Published",l=s?`This site isn't public yet${n}`:`This project hasn't been published yet${n}`;return`<!doctype html>
|
|
36
|
+
<html>
|
|
37
|
+
<head>
|
|
38
|
+
<meta charset="UTF-8">
|
|
39
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
40
|
+
<meta name="robots" content="noindex">
|
|
41
|
+
<title>${a}</title>
|
|
42
|
+
<style>
|
|
43
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
44
|
+
body { min-height: 100vh; display: flex; align-items: center; justify-content: center; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: #0a0a0a; color: #fafafa; }
|
|
45
|
+
.container { text-align: center; max-width: 520px; padding: 2rem; }
|
|
46
|
+
h1 { font-size: 1.25rem; font-weight: 500; margin-bottom: 0.75rem; }
|
|
47
|
+
p { color: #888; font-size: 0.875rem; line-height: 1.6; }
|
|
48
|
+
code { background: #1a1a1a; padding: 0.1rem 0.35rem; border-radius: 3px; font-size: 0.8rem; color: #ccc; }
|
|
49
|
+
a { color: #888; text-decoration: underline; text-underline-offset: 2px; }
|
|
50
|
+
a:hover { color: #fafafa; }
|
|
51
|
+
.env { display: inline-block; font-size: 0.75rem; color: #555; background: #1a1a1a; padding: 0.2rem 0.5rem; border-radius: 4px; margin-top: 1rem; }
|
|
52
|
+
</style>
|
|
53
|
+
</head>
|
|
54
|
+
<body>
|
|
55
|
+
<div class="container">
|
|
56
|
+
<h1>${l}</h1>
|
|
57
|
+
<p>${s?"The owner has connected this domain but the project's visibility is set to private. Once they switch it to public in the workspace settings, the site will appear here automatically.":'Run <code>smbls publish</code> to deploy your project, or visit <a href="https://symbols.app">symbols.app</a> to get started.'}</p>
|
|
58
|
+
<span class="env">${t}${n}</span>
|
|
59
|
+
</div>
|
|
60
|
+
</body>
|
|
61
|
+
</html>`}export{yt as DEFAULT_TEMPLATE_HTML,C as generateSitemap,H as prefetchPageData,_t as renderNotPublishedHtml,xt as renderProjectHtml,M as transpileFunctionStrings};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function f(n){let t=n||"/";return t.startsWith("/")||(t="/"+t),t.length>1&&t.endsWith("/")&&(t=t.slice(0,-1)),t}function a(n,t){const r=n.split("/"),i=t.split("/"),s={};for(let e=0;e<r.length;e++)r[e].startsWith(":")&&(s[r[e].slice(1)]=i[e]);return s}function c(n,t){if(!n||!Object.keys(n).length)return null;const r=f(t);if(n[r])return{route:r,fallback:!1};const i=r.split("/");for(const s of Object.keys(n)){if(!s.includes(":"))continue;const e=s.split("/");if(e.length!==i.length)continue;let l=!0;for(let o=0;o<e.length;o++)if(!e[o].startsWith(":")&&e[o]!==i[o]){l=!1;break}if(l)return{route:s,fallback:!1,params:a(s,r)}}return r!=="/"&&n["/"]?{route:"/",fallback:!0}:null}const u=/\.(js|mjs|css|map|png|jpe?g|gif|svg|ico|webp|avif|woff2?|ttf|eot|otf|json|xml|txt|webmanifest|mp4|webm|mp3|ogg|wav|pdf)$/i;export{u as STATIC_EXT,a as extractRouteParams,f as normalizeRoute,c as resolvePageRoute};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import P from"path";import y from"fs";import b from"express";import D from"cors";import u from"./utils/logger.js";import{STATIC_EXT as I}from"./route.js";import{renderProjectHtml as $,renderNotPublishedHtml as w,generateSitemap as j}from"./render.js";function R(t){const e=P.resolve(t);let s=e;if(y.existsSync(e)&&y.statSync(e).isDirectory()&&(s=P.join(e,"app.json")),!y.existsSync(s))throw new Error(`JSON data file not found: ${s}`);const d=y.readFileSync(s,"utf8"),a=JSON.parse(d);return u.info(`[MERMAID] Loaded project data from ${s}`),a}function T(t,e){if(typeof t!="string")return t;if(t.includes("?")&&e){const[s,d]=t.split("?");t=s.replace(/\/$/,"");try{const a=new URLSearchParams(d);for(const[f,g]of a.entries())e.query[f]===void 0&&(e.query[f]=g)}catch(a){u.warn(`[MERMAID] Failed to parse nested query in renderUrl: ${a.message}`)}}return t.replace(/^https?:\/\//,"").split(":")[0].replace(/^www\./,"").replace(/\/$/,"")}function F(t={}){const{jsonPath:e,appkeyOverride:s,channel:d="production",getProjectData:a,cors:f=!0,editor:g}=t;let S=null;e&&(S=R(e));const c=b();return f&&c.use(D({origin:"*",methods:"GET, POST"})),c.use(b.json()),c.get("/health",(o,r)=>r.send({alive:!0,service:"mermaid",version:"3.14.0"})),c.get("*",async(o,r)=>{try{const p=o.originalUrl.split("?")[0];if(I.test(p))return p==="/favicon.ico"?r.status(204).end():r.status(404).send("Not Found");let m=s||o.query.renderUrl||o.headers.host;m=T(m,o);let i=null,l=m,v=d,M=!1;if(S)i=S,u.debug("[MERMAID] Using local JSON data");else if(a){const n=await a(o,{appkey:m});n&&(i=n.data,n.appkey&&(l=n.appkey),n.channel&&(v=n.channel),typeof n.isProd=="boolean"&&(M=n.isProd))}if(!l)return r.send("Hello, this is Mermaid (Symbols runner). Read developer documentation at https://symbols.app/developers");if(!i)return u.warn(`[MERMAID] Project not found for appkey: ${l}`),r.status(404).type("html").send(w(l,v));if(p==="/sitemap.xml"){if(j&&i.pages&&Object.keys(i.pages).length){const n=`https://${l}`,x=j(n,i.pages);return r.type("application/xml").send(x)}return r.status(404).send("No sitemap available")}const E=o.headers["x-user-id"]||"",h=await $(i,{appkey:l,pathname:p,channel:v,isProd:M,req:o,userId:E,editorBundle:g?.bundle});return r.status(h.status).set({host:"https://"+l+"/"}).type(h.contentType==="text/html"?"html":h.contentType).send(h.html)}catch(p){return u.error(`[MERMAID] Error in request: ${p}`),r.status(500).send("Internal Server Error")}}),c}export{F as createMermaidApp,R as loadJsonData};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function u(c){if(!c)return!1;const n=c.trim().replace(/\n\s*/g," ").trim();if(n===""||n==="{}"||n==="[]")return!1;const r=[/^\(\s*\{[^}]*\}\s*\)\s*=>/,/^(\([^)]*\)|[^=]*)\s*=>/,/^function[\s(]/,/^async\s+/,/^\(\s*function/,/^[a-zA-Z_$][a-zA-Z0-9_$]*\s*=>/].some(a=>a.test(n)),i=n.startsWith("{")&&!n.includes("=>"),o=n.startsWith("["),y=/^["[{]/.test(n)&&!n.includes("=>");return r&&!i&&!o&&!y}const e=c=>typeof c=="string",t=Array.isArray,f=c=>c!==null&&typeof c=="object"&&!t(c);function p(c,n=Array.isArray(c)?[]:{}){for(const s in c){if(!Object.prototype.hasOwnProperty.call(c,s))continue;const r=c[s];if(e(r)){if(u(r))try{const i=Function("return ("+r+")")();n[s]=i}catch{n[s]=r}else n[s]=r;continue}if(t(r)){n[s]=[],r.forEach(i=>{if(e(i))if(u(i))try{const o=Function("return ("+i+")")();n[s].push(o)}catch{n[s].push(i)}else n[s].push(i);else f(i)||t(i)?n[s].push(p(i)):n[s].push(i)});continue}if(f(r)){n[s]=p(r);continue}n[s]=r}return n}function l(c,n=Array.isArray(c)?[]:{}){for(const s in c){if(!Object.prototype.hasOwnProperty.call(c,s))continue;const r=c[s];e(r)?n[s]=u(r)?null:r:t(r)?n[s]=r.map(i=>e(i)&&u(i)?null:f(i)||t(i)?l(i):i):f(r)?n[s]=l(r):n[s]=r}return n}var h=p;export{p as deepDestringifyFunctions,h as default,l as stripFunctionStrings};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const w=e=>{if(typeof e!="string")return null;const n=e.trim().match(/^\(?([^)]*?)\)?\s*=>\s*([\s\S]+)$/);if(!n)return null;const c=n[1],l=n[2].trim(),t=c.split(",").map(o=>o.trim()).filter(Boolean);if(l.startsWith("{"))return null;const i=h(t),r=u(l,i);return r===null?null:{schema:r,args:t}},h=e=>{const s={};return e[0]&&(s[e[0]]="el"),e[1]&&(s[e[1]]="state"),e[2]&&(s[e[2]]="context"),s},u=(e,s)=>{const n=e.trim();if(!n)return null;const c=m(n);if(c){const t=u(c.condition,s),i=u(c.consequent,s),r=u(c.alternate,s);return t!==null&&i!==null&&r!==null?{if:[t,i,r]}:null}const l=n.match(/^(\w+)\.call\((.+)\)$/);if(l){const t=g(l[2]);if(t){const i=t.map(r=>u(r.trim(),s));if(i.every(r=>r!==null))return["call",...i]}return null}{const t=f(n,"||");if(t){const i=u(t.left,s),r=u(t.right,s);return i!==null&&r!==null?["||",i,r]:null}}{const t=f(n,"&&");if(t){const i=u(t.left,s),r=u(t.right,s);return i!==null&&r!==null?["&&",i,r]:null}}for(const t of["!==","===",">=","<=",">","<"]){const i=f(n,t);if(i){const r=u(i.left,s),o=u(i.right,s);return r!==null&&o!==null?[t,r,o]:null}}if(n.startsWith("!")){const t=u(n.slice(1).trim(),s);return t!==null&&typeof t=="string"?"!"+t:null}if(n.includes("+")&&!n.startsWith('"')&&!n.startsWith("'")){const t=a(n,"+");if(t&&t.length>=2){const i=t.map(r=>u(r.trim(),s));if(i.every(r=>r!==null)){let r=i[0];for(let o=1;o<i.length;o++)r=["+",r,i[o]];return r}}}return n.startsWith('"')&&n.endsWith('"')||n.startsWith("'")&&n.endsWith("'")?"="+n.slice(1,-1):n==="void 0"||n==="undefined"?"__undefined__":/^-?\d+(\.\d+)?$/.test(n)?Number(n):n==="true"?!0:n==="false"?!1:n==="null"?null:n==='""'||n==="''"?"":/^[\w$.[\]]+$/.test(n)&&!n.includes("(")?d(n,s):null},d=(e,s)=>{const n=e.split("."),c=n[0];return s[c]?(n[0]=s[c],n.join(".")):e},m=e=>{let s=0,n=-1,c=-1;for(let l=0;l<e.length;l++){const t=e[l];if(t==="("||t==="["||t==="{")s++;else if(t===")"||t==="]"||t==="}")s--;else if(t==="?"&&s===0&&n===-1)n=l;else if(t===":"&&s===0&&n!==-1){c=l;break}if(t==='"'||t==="'"){const i=t;for(l++;l<e.length&&e[l]!==i;)e[l]==="\\"&&l++,l++}}return n===-1||c===-1?null:{condition:e.slice(0,n).trim(),consequent:e.slice(n+1,c).trim(),alternate:e.slice(c+1).trim()}},f=(e,s)=>{let n=0;const c=s.length;for(let l=0;l<e.length-c+1;l++){const t=e[l];if(t==="("||t==="["||t==="{"?n++:(t===")"||t==="]"||t==="}")&&n--,t==='"'||t==="'"){const i=t;for(l++;l<e.length&&e[l]!==i;)e[l]==="\\"&&l++,l++;continue}if(n===0&&e.slice(l,l+c)===s){const i=l>0?e[l-1]:"",r=l+c<e.length?e[l+c]:"";if(s==="="&&(i==="!"||i==="="||r==="=")||s===">"&&i==="="||s==="<"&&r==="=")continue;return{left:e.slice(0,l).trim(),right:e.slice(l+c).trim()}}}return null},a=(e,s)=>{const n=[];let c=0,l=0;const t=s.length;for(let i=0;i<e.length;i++){const r=e[i];if(r==="("||r==="["||r==="{"?c++:(r===")"||r==="]"||r==="}")&&c--,r==='"'||r==="'"){const o=r;for(i++;i<e.length&&e[i]!==o;)e[i]==="\\"&&i++,i++;continue}c===0&&e.slice(i,i+t)===s&&(n.push(e.slice(l,i)),l=i+t)}return n.push(e.slice(l)),n.length<2?null:n},g=e=>{const s=[];let n=0,c=0;for(let l=0;l<e.length;l++){const t=e[l];if(t==="("||t==="["||t==="{"?n++:t===")"||t==="]"||t==="}"?n--:t===","&&n===0&&(s.push(e.slice(c,l)),c=l+1),t==='"'||t==="'"){const i=t;for(l++;l<e.length&&e[l]!==i;)e[l]==="\\"&&l++,l++}}return s.push(e.slice(c)),s.filter(l=>l.trim())};export{w as transpile};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const s={trace:10,debug:20,info:30,warn:40,error:50,fatal:60};let i=process.env.LOG_LEVEL||"info";Object.prototype.hasOwnProperty.call(s,i)||(i="info");let m=s[i],b=!1;const d={trace:"\x1B[34m",debug:"\x1B[36m",info:"\x1B[32m",warn:"\x1B[33m",error:"\x1B[31m",fatal:"\x1B[35m",reset:"\x1B[0m"};function c(e,o=2){let n=String(e);for(;n.length<o;)n="0"+n;return n}function O(){const e=new Date,o=e.getUTCFullYear(),n=c(e.getUTCMonth()+1),f=c(e.getUTCDate()),t=c(e.getUTCHours()),u=c(e.getUTCMinutes()),r=c(e.getUTCSeconds()),g=c(e.getUTCMilliseconds(),3);return`${o}-${n}-${f} ${t}:${u}:${r}.${g} +0000`}function h(e){return s[e]>=m}function L(e){if(!e.length)return"";try{return JSON.stringify(Object.assign({},...e))}catch{return""}}function C(e){const o=d[e],f=e==="error"||e==="fatal"?console.error:console.log;return(...t)=>{if(!h(e))return;if(!t.length){f("");return}let u="",r=[];typeof t[0]=="string"?(u=t[0],r=t.slice(1).filter(l=>typeof l=="object"&&l!==null),e==="error"&&t[1]instanceof Error&&r.push({err:{message:t[1].message,stack:t[1].stack}})):r=t.filter(l=>typeof l=="object"&&l!==null);const g=L(r),y=O(),$=e.toUpperCase();let p=`[${y}] ${$}: ${u}`;g&&(p+=` ${g}`),b&&o&&(p=`${o}${p}${d.reset}`),f(p)}}const a={};for(const e of Object.keys(s))a[e]=C(e);Object.defineProperty(a,"level",{get:()=>i,set:e=>{Object.prototype.hasOwnProperty.call(s,e)&&(i=e,m=s[e])}});const v=e=>{e?.logging?.level&&(a.level=e.logging.level),e?.env&&(b=e.env==="local"||e.env==="development")};var x=a;export{x as default,v as updateLoggerConfig};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import n from"./logger.js";function s(a,l,i={}){try{const r=/(['"])\${{{\s*([^}\s]+)\s*}}}\1/g;if(!a){const e=new Error("File data is empty");throw n.error("[REQ_FLOW] File data is empty in replaceVariablesInFileSync"),e}return a.replace(r,(e,p,t)=>{if(Object.hasOwn(l,t)){const c=l[t];return i.allowScript?c:c.replace(/<\/?script>/g,"</?script>")}else return n.warn(`[REQ_FLOW] Missing replacement value for ${JSON.stringify({name:t})}`),e})}catch(r){return n.error(`[REQ_FLOW] Error in replaceVariablesInFileSync: ${r}`),null}}export{s as default};
|
package/package.json
CHANGED
|
@@ -1,29 +1,73 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@symbo.ls/mermaid",
|
|
3
|
-
"version": "3.14.
|
|
3
|
+
"version": "3.14.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "CC-BY-NC-4.0",
|
|
6
6
|
"description": "Self-sustaining server runner for Symbols projects from JSON. Renders DOMQL projects to HTML with optional brender SSR.",
|
|
7
|
-
"main": "index.js",
|
|
8
7
|
"exports": {
|
|
9
|
-
".":
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
"./
|
|
15
|
-
"./
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
8
|
+
".": {
|
|
9
|
+
"development": "./index.js",
|
|
10
|
+
"import": "./dist/esm/index.js",
|
|
11
|
+
"default": "./dist/esm/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./package.json": "./package.json",
|
|
14
|
+
"./server": {
|
|
15
|
+
"development": "./server.js",
|
|
16
|
+
"import": "./dist/esm/server.js",
|
|
17
|
+
"default": "./dist/esm/server.js"
|
|
18
|
+
},
|
|
19
|
+
"./render": {
|
|
20
|
+
"development": "./render.js",
|
|
21
|
+
"import": "./dist/esm/render.js",
|
|
22
|
+
"default": "./dist/esm/render.js"
|
|
23
|
+
},
|
|
24
|
+
"./bundle": {
|
|
25
|
+
"development": "./bundle.js",
|
|
26
|
+
"import": "./dist/esm/bundle.js",
|
|
27
|
+
"default": "./dist/esm/bundle.js"
|
|
28
|
+
},
|
|
29
|
+
"./route": {
|
|
30
|
+
"development": "./route.js",
|
|
31
|
+
"import": "./dist/esm/route.js",
|
|
32
|
+
"default": "./dist/esm/route.js"
|
|
33
|
+
},
|
|
34
|
+
"./utils/deepDestringify": {
|
|
35
|
+
"development": "./utils/deepDestringify.js",
|
|
36
|
+
"import": "./dist/esm/utils/deepDestringify.js",
|
|
37
|
+
"default": "./dist/esm/utils/deepDestringify.js"
|
|
38
|
+
},
|
|
39
|
+
"./utils/funcql-transpile": {
|
|
40
|
+
"development": "./utils/funcql-transpile.js",
|
|
41
|
+
"import": "./dist/esm/utils/funcql-transpile.js",
|
|
42
|
+
"default": "./dist/esm/utils/funcql-transpile.js"
|
|
43
|
+
},
|
|
44
|
+
"./utils/vartranformer": {
|
|
45
|
+
"development": "./utils/vartranformer.js",
|
|
46
|
+
"import": "./dist/esm/utils/vartranformer.js",
|
|
47
|
+
"default": "./dist/esm/utils/vartranformer.js"
|
|
48
|
+
},
|
|
49
|
+
"./utils/logger": {
|
|
50
|
+
"development": "./utils/logger.js",
|
|
51
|
+
"import": "./dist/esm/utils/logger.js",
|
|
52
|
+
"default": "./dist/esm/utils/logger.js"
|
|
53
|
+
},
|
|
54
|
+
"./template.html": {
|
|
55
|
+
"development": "./template.html.js",
|
|
56
|
+
"import": "./dist/esm/template.html.js",
|
|
57
|
+
"default": "./dist/esm/template.html.js"
|
|
58
|
+
}
|
|
19
59
|
},
|
|
20
60
|
"files": [
|
|
21
|
-
"
|
|
22
|
-
"
|
|
61
|
+
"dist",
|
|
62
|
+
"*.md",
|
|
63
|
+
"LICENSE",
|
|
23
64
|
"README.md"
|
|
24
65
|
],
|
|
25
66
|
"scripts": {
|
|
26
|
-
"start": "node bin/start.js"
|
|
67
|
+
"start": "node bin/start.js",
|
|
68
|
+
"build:esm": "esbuild $(find . -type f -name \"*.js\" -not -path \"*/dist/*\" -not -path \"*/node_modules/*\" -not -path \"*/__tests__/*\" -not -path \"*/test/*\" -not -name \"*.test.js\") --target=es2022 --format=esm --outbase=. --outdir=dist/esm --minify --log-level=warning",
|
|
69
|
+
"build": "rimraf dist && npm run build:esm",
|
|
70
|
+
"prepublishOnly": "npm run build"
|
|
27
71
|
},
|
|
28
72
|
"bin": {
|
|
29
73
|
"mermaid": "./bin/start.js"
|
|
@@ -37,6 +81,7 @@
|
|
|
37
81
|
"@symbo.ls/brender": "^3.14.0"
|
|
38
82
|
},
|
|
39
83
|
"publishConfig": {
|
|
40
|
-
"access": "
|
|
41
|
-
}
|
|
84
|
+
"access": "restricted"
|
|
85
|
+
},
|
|
86
|
+
"module": "./dist/esm/index.js"
|
|
42
87
|
}
|
package/index.js
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @symbo.ls/mermaid — self-sustaining server runner for Symbols projects.
|
|
3
|
-
*
|
|
4
|
-
* Renders DOMQL projects from JSON to HTML, with optional brender SSR.
|
|
5
|
-
* Works as an Express server, a pure render function, or in CF Workers.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
export { createMermaidApp, loadJsonData } from './src/server.js';
|
|
9
|
-
export {
|
|
10
|
-
renderProjectHtml,
|
|
11
|
-
renderNotPublishedHtml,
|
|
12
|
-
transpileFunctionStrings,
|
|
13
|
-
generateSitemap,
|
|
14
|
-
prefetchPageData,
|
|
15
|
-
} from './src/render.js';
|
|
16
|
-
export { getBundleScript, getSmblsIife } from './src/bundle.js';
|
|
17
|
-
export { normalizeRoute, resolvePageRoute, extractRouteParams, STATIC_EXT } from './src/route.js';
|
|
18
|
-
export { deepDestringifyFunctions, stripFunctionStrings } from './src/utils/deepDestringify.js';
|
|
19
|
-
export { transpile as transpileFuncql } from './src/utils/funcql-transpile.js';
|
|
20
|
-
export { default as replaceVariablesInFileSync } from './src/utils/vartranformer.js';
|
|
21
|
-
export { default as log } from './src/utils/logger.js';
|
package/src/bundle.js
DELETED
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
import log from './utils/logger.js';
|
|
2
|
-
// The smbls IIFE source as a string. Single source of truth — generated by
|
|
3
|
-
// `smbls build:iife` and exported from the smbls package itself, so this works
|
|
4
|
-
// identically in Node, CF Workers, and any other runtime as a static import.
|
|
5
|
-
import smblsIife from 'smbls/iife-string';
|
|
6
|
-
|
|
7
|
-
log.debug(`[BUNDLE] smbls IIFE loaded from smbls/iife-string (${smblsIife.length} bytes)`);
|
|
8
|
-
|
|
9
|
-
// FRAMEWORK-6: any `<` in JSON that gets inlined into a `<script>` block can
|
|
10
|
-
// terminate the wrapper (`</script>`) or trigger HTML-comment / CDATA modes
|
|
11
|
-
// (`<!--`, `<![CDATA[`) inside the script-data state. Replacing every `<`
|
|
12
|
-
// with the JS unicode escape `<` neutralizes HTML interpretation while
|
|
13
|
-
// preserving the runtime string value — `<` decodes to `<` at JS parse
|
|
14
|
-
// time, so the embedded object is identical to the original. Standard
|
|
15
|
-
// mitigation: see OWASP §RULE-3.1, React's renderToString, Rails json_escape.
|
|
16
|
-
const escapeJsonForScript = (s) => s.replace(/</g, '\\u003c');
|
|
17
|
-
|
|
18
|
-
export function getSmblsIife() {
|
|
19
|
-
return smblsIife;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Generate the client-side bundle script that creates the Smbls app.
|
|
24
|
-
* Uses the same approach as templates/start.js runFromJson:
|
|
25
|
-
* Smbls.deepDestringifyFunctions(data) -> Smbls.create(app, context)
|
|
26
|
-
*/
|
|
27
|
-
export function getBundleScript(appKey, data, req, { ProjectDataService } = {}) {
|
|
28
|
-
log.debug(`[BUNDLE] getBundleScript started for ${appKey}`);
|
|
29
|
-
|
|
30
|
-
if (!data) {
|
|
31
|
-
log.warn('[BUNDLE] No data provided, returning empty');
|
|
32
|
-
return '';
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Ensure data is fully merged before bundling
|
|
36
|
-
try {
|
|
37
|
-
if (data.merged) {
|
|
38
|
-
Object.assign(data, data.merged);
|
|
39
|
-
log.debug(`[BUNDLE] Applied precomputed merged data for project ${data.id}`);
|
|
40
|
-
} else if (
|
|
41
|
-
ProjectDataService &&
|
|
42
|
-
Array.isArray(data.sharedLibraries) &&
|
|
43
|
-
data.sharedLibraries.length > 0
|
|
44
|
-
) {
|
|
45
|
-
const mergedData = ProjectDataService.mergeSharedLibraries(
|
|
46
|
-
data,
|
|
47
|
-
data.sharedLibraries,
|
|
48
|
-
data.projectType,
|
|
49
|
-
);
|
|
50
|
-
Object.assign(data, mergedData);
|
|
51
|
-
log.debug(`[BUNDLE] Merged ${data.sharedLibraries.length} shared libraries`);
|
|
52
|
-
}
|
|
53
|
-
} catch (mergeError) {
|
|
54
|
-
log.error(`[BUNDLE] Failed to ensure merged data: ${mergeError.message}`);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Serialize the project data as JSON (same as templates __mockedData approach).
|
|
58
|
-
// escapeJsonForScript: see FRAMEWORK-6 — any `</script>` (or `<!--`) inside a
|
|
59
|
-
// string value would break out of the inline `<script>` wrapper.
|
|
60
|
-
const jsonData = escapeJsonForScript(JSON.stringify(data));
|
|
61
|
-
|
|
62
|
-
/*
|
|
63
|
-
* Determine liveSync setting.
|
|
64
|
-
*
|
|
65
|
-
* Realtime is enabled on Symbols-managed dev hosts (any *.symbo.ls env,
|
|
66
|
-
* *.preview.symbols.app, and localhost / *.localhost for local dev) and
|
|
67
|
-
* disabled on custom domains so we don't leak the realtime backend to
|
|
68
|
-
* third-party hosts. Post-§45 the appkey is a bare `owner/key` slug, so
|
|
69
|
-
* gating on `appKey.endsWith('symbo.ls')` would always evaluate false
|
|
70
|
-
* (rendering all browsers blind). The signal we want is the request host.
|
|
71
|
-
*/
|
|
72
|
-
// Resolve incoming host across Express (req.headers.host),
|
|
73
|
-
// Cloudflare Workers / Fetch (req.headers is a Headers instance with .get),
|
|
74
|
-
// and plain options bags ({ host }). Falls back to req.url parsing so a
|
|
75
|
-
// missing Host header still degrades to a URL-based read instead of ''.
|
|
76
|
-
const rawHost =
|
|
77
|
-
(req?.headers && typeof req.headers.get === 'function' && req.headers.get('host')) ||
|
|
78
|
-
req?.headers?.host ||
|
|
79
|
-
req?.host ||
|
|
80
|
-
(req?.url && (() => { try { return new URL(req.url).host; } catch { return ''; } })()) ||
|
|
81
|
-
'';
|
|
82
|
-
const host = String(rawHost).toLowerCase().split(':')[0];
|
|
83
|
-
// Express exposes parsed query on req.query; CF Workers expose req.url only —
|
|
84
|
-
// pull `release` / `version` from a parsed URL when the structured form is
|
|
85
|
-
// absent so feature flags work in both runtimes.
|
|
86
|
-
const queryFrom = (() => {
|
|
87
|
-
if (req?.query) return req.query;
|
|
88
|
-
try { return Object.fromEntries(new URL(req?.url || '', 'http://x').searchParams); } catch { return {}; }
|
|
89
|
-
})();
|
|
90
|
-
const release = queryFrom.release || false;
|
|
91
|
-
const version = queryFrom.version;
|
|
92
|
-
const isSymboLs =
|
|
93
|
-
host.endsWith('.symbo.ls') ||
|
|
94
|
-
host.endsWith('.preview.symbols.app') ||
|
|
95
|
-
host === 'localhost' ||
|
|
96
|
-
host.endsWith('.localhost');
|
|
97
|
-
const shouldDisableLiveSync = Boolean(release) || Boolean(version) || !isSymboLs;
|
|
98
|
-
|
|
99
|
-
// Inject the persisted `_id` so the realtime client can hand the socket
|
|
100
|
-
// server a concrete projectId (mongo _id) on handshake. Without this, the
|
|
101
|
-
// server falls back to a `/v1/projects/by-key/...` lookup that 404s on the
|
|
102
|
-
// current API and drops the connection mid-join.
|
|
103
|
-
const projectId =
|
|
104
|
-
(data && (data._id || data.projectId || data.id)) ||
|
|
105
|
-
null;
|
|
106
|
-
const settingsStr = isSymboLs
|
|
107
|
-
? escapeJsonForScript(JSON.stringify({
|
|
108
|
-
...data.settings,
|
|
109
|
-
liveSync: !shouldDisableLiveSync,
|
|
110
|
-
inspect: true,
|
|
111
|
-
verbose: !shouldDisableLiveSync,
|
|
112
|
-
projectId,
|
|
113
|
-
}))
|
|
114
|
-
: 'null';
|
|
115
|
-
|
|
116
|
-
// Generate the client script - same pattern as templates/start.js runFromJson
|
|
117
|
-
const script = `
|
|
118
|
-
var context = Smbls.deepDestringifyFunctions(${jsonData});
|
|
119
|
-
context.packageManager = 'esm.sh';
|
|
120
|
-
if (context.db && context.db.createClient) delete context.db.createClient;
|
|
121
|
-
if (context.fetch && context.fetch.createClient) delete context.fetch.createClient;
|
|
122
|
-
|
|
123
|
-
// Runtime defaults (these are baked into the starter-kit/frank but missing from server data)
|
|
124
|
-
if (context.useReset === undefined) context.useReset = true;
|
|
125
|
-
if (context.useVariable === undefined) context.useVariable = true;
|
|
126
|
-
if (context.useFontImport === undefined) context.useFontImport = true;
|
|
127
|
-
if (context.useIconSprite === undefined) context.useIconSprite = true;
|
|
128
|
-
if (context.useSvgSprite === undefined) context.useSvgSprite = true;
|
|
129
|
-
if (context.useDefaultConfig === undefined) context.useDefaultConfig = true;
|
|
130
|
-
if (context.useDocumentTheme === undefined) context.useDocumentTheme = true;
|
|
131
|
-
if (!context.routerOptions) context.routerOptions = { scrollToTop: true };
|
|
132
|
-
|
|
133
|
-
var app = context.app || {};
|
|
134
|
-
delete context.app;
|
|
135
|
-
var settings = ${settingsStr};
|
|
136
|
-
if (settings) context.editor = settings;
|
|
137
|
-
|
|
138
|
-
// Theme initialization: set data-theme before app renders to avoid FOUC.
|
|
139
|
-
// Mirrors the precedence in resolveAndApplyTheme (packages/smbls/src/index.js):
|
|
140
|
-
// 1. URL param ?globalTheme=<name> — embedded-iframe override
|
|
141
|
-
// 2. localStorage[themeStorageKey] — persisted user choice
|
|
142
|
-
// 3. context.globalTheme — project's configured value
|
|
143
|
-
// 4. matchMedia(prefers-color-scheme: dark) — OS fallback (when 'auto')
|
|
144
|
-
//
|
|
145
|
-
// Writing the literal 'auto' as data-theme would leave the
|
|
146
|
-
// [data-theme="dark"] / [data-theme="light"] CSS selectors unmatched and
|
|
147
|
-
// the page would render with system defaults instead of the chosen
|
|
148
|
-
// theme — so 'auto' is always resolved to a concrete light/dark value.
|
|
149
|
-
//
|
|
150
|
-
// PRIOR BUG: this script previously ignored context.globalTheme entirely
|
|
151
|
-
// and went straight to matchMedia after localStorage. A project with
|
|
152
|
-
// globalTheme: light in config.js would render in OS dark on a
|
|
153
|
-
// dark-mode device because the inline boot ran first and locked in
|
|
154
|
-
// dark before resolveAndApplyTheme could correct it.
|
|
155
|
-
//
|
|
156
|
-
// IDEMPOTENT GUARD: the SSR-side render in @symbo.ls/mermaid/render.js
|
|
157
|
-
// now bakes data-theme onto <html> for concrete-theme projects and
|
|
158
|
-
// emits an inline <head> script that runs synchronously before paint.
|
|
159
|
-
// When that path runs, data-theme is already set by the time the bundle
|
|
160
|
-
// executes — skip this block to avoid re-resolving and overwriting
|
|
161
|
-
// (which would race with URL/localStorage values the head script
|
|
162
|
-
// already honored). Kept as a safety net for older renders / cases
|
|
163
|
-
// where the head script couldn't run.
|
|
164
|
-
if (
|
|
165
|
-
context.useDocumentTheme !== false &&
|
|
166
|
-
!document.documentElement.hasAttribute('data-theme')
|
|
167
|
-
) {
|
|
168
|
-
(function() {
|
|
169
|
-
try {
|
|
170
|
-
var t = null;
|
|
171
|
-
// 1. URL param wins (used by preview/canvas iframe embedding).
|
|
172
|
-
try {
|
|
173
|
-
var p = new URLSearchParams(window.location.search).get('globalTheme');
|
|
174
|
-
if (p) t = p;
|
|
175
|
-
} catch(e) {}
|
|
176
|
-
// 2. localStorage — only consulted when no forcing source above.
|
|
177
|
-
if (!t) {
|
|
178
|
-
var key = context.themeStorageKey;
|
|
179
|
-
if (key) { try { t = localStorage.getItem(key) } catch(e) {} }
|
|
180
|
-
}
|
|
181
|
-
// 3. Project's configured globalTheme (config.js export). Honors an
|
|
182
|
-
// explicit 'light'/'dark'/<scheme> instead of falling through to OS.
|
|
183
|
-
if (!t && context.globalTheme && context.globalTheme !== 'auto') {
|
|
184
|
-
t = context.globalTheme;
|
|
185
|
-
}
|
|
186
|
-
// 4. OS preference fallback — only when nothing concrete chose.
|
|
187
|
-
if (!t || t === 'auto') {
|
|
188
|
-
t = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
|
189
|
-
}
|
|
190
|
-
if (t) document.documentElement.setAttribute('data-theme', t);
|
|
191
|
-
} catch(e) {}
|
|
192
|
-
})();
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// When brender pre-rendered this page, adopt the existing DOM nodes
|
|
196
|
-
// instead of tearing them down. The [data-ssr] wrapper is moved out of
|
|
197
|
-
// the way so pre-rendered children land at document.body level — matching
|
|
198
|
-
// the level at which DOMQL creates its own elements. The __BRENDER__ flag
|
|
199
|
-
// and __BR_REGISTRY__ map were injected into <head> by the server.
|
|
200
|
-
var ssrShell = document.querySelector('[data-ssr]');
|
|
201
|
-
if (window.__BRENDER__ && ssrShell) {
|
|
202
|
-
// Move pre-rendered children to body before DOMQL skeleton creation
|
|
203
|
-
var frag = document.createDocumentFragment();
|
|
204
|
-
while (ssrShell.firstChild) frag.appendChild(ssrShell.firstChild);
|
|
205
|
-
document.body.insertBefore(frag, ssrShell);
|
|
206
|
-
ssrShell.remove();
|
|
207
|
-
} else if (ssrShell) {
|
|
208
|
-
// No brender hydration — remove empty/stale SSR shell
|
|
209
|
-
ssrShell.remove();
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
Smbls.create(app, context);
|
|
213
|
-
`;
|
|
214
|
-
|
|
215
|
-
log.debug(`[BUNDLE] Script generated (${script.length} bytes)`);
|
|
216
|
-
return script;
|
|
217
|
-
}
|