@symbo.ls/mermaid 3.14.0
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 +75 -0
- package/bin/start.js +60 -0
- package/index.js +21 -0
- package/package.json +38 -0
- package/src/bundle.js +118 -0
- package/src/render.js +411 -0
- package/src/route.js +80 -0
- package/src/server.js +190 -0
- package/src/template.html +22 -0
- package/src/utils/deepDestringify.js +149 -0
- package/src/utils/funcql-transpile.js +346 -0
- package/src/utils/logger.js +131 -0
- package/src/utils/vartranformer.js +30 -0
package/README.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# @symbo.ls/mermaid
|
|
2
|
+
|
|
3
|
+
Self-sustaining server runner for Symbols projects. Renders DOMQL projects from JSON to HTML, with optional [brender](../brender) SSR.
|
|
4
|
+
|
|
5
|
+
Works in three modes:
|
|
6
|
+
|
|
7
|
+
1. **Standalone** — `mermaid` CLI / `createMermaidApp({ jsonPath })` Express app
|
|
8
|
+
2. **Pure render** — `renderProjectHtml(data, opts)` returns an HTML string in any runtime
|
|
9
|
+
3. **Pluggable data source** — `createMermaidApp({ getProjectData })` lets you wire KV / API / DB
|
|
10
|
+
|
|
11
|
+
For the production hosting service (Cloudflare Workers + KV/API + custom domains), see [@symbo.ls/mermaid-server](../../../server/packages/mermaid-server) which builds on top of this package.
|
|
12
|
+
|
|
13
|
+
## Quick start
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# Run a project from a local JSON file
|
|
17
|
+
npx mermaid --json ./app.json
|
|
18
|
+
# open http://localhost:3333
|
|
19
|
+
|
|
20
|
+
# Or as a library
|
|
21
|
+
node -e "import('@symbo.ls/mermaid').then(m => m.createMermaidApp({ jsonPath: './app.json' }).listen(3333))"
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## API
|
|
25
|
+
|
|
26
|
+
### `createMermaidApp(options)`
|
|
27
|
+
|
|
28
|
+
Returns an Express application.
|
|
29
|
+
|
|
30
|
+
| Option | Type | Description |
|
|
31
|
+
| ---------------- | -------- | --------------------------------------------------------------------- |
|
|
32
|
+
| `jsonPath` | string | Path to local JSON file (or directory containing `app.json`) |
|
|
33
|
+
| `appkeyOverride` | string | Force a specific appkey for all requests |
|
|
34
|
+
| `channel` | string | `production` (default) \| `staging` \| `development` |
|
|
35
|
+
| `getProjectData` | function | `async (req, ctx) => { data, appkey?, channel?, isProd? }` |
|
|
36
|
+
| `cors` | boolean | Enable CORS middleware (default `true`) |
|
|
37
|
+
| `editor` | object | `{ bundle: string }` to inject `@symbo.ls/preview-editor` in non-prod |
|
|
38
|
+
|
|
39
|
+
### `renderProjectHtml(data, options)`
|
|
40
|
+
|
|
41
|
+
Pure runtime-agnostic renderer. Returns `{ html, status, contentType }`.
|
|
42
|
+
|
|
43
|
+
| Option | Type | Description |
|
|
44
|
+
| ----------------------- | ------- | ------------------------------------------------------ |
|
|
45
|
+
| `appkey` | string | identifier for metadata + comments |
|
|
46
|
+
| `pathname` | string | request pathname |
|
|
47
|
+
| `channel` | string | `production` \| `staging` \| `development` |
|
|
48
|
+
| `isProd` | boolean | controls SEO indexing (custom domains only) |
|
|
49
|
+
| `req` | object | Express-like request (used by bundle context) |
|
|
50
|
+
| `userId` | string | injects `window.__SYMBOLS_AUTH__` |
|
|
51
|
+
| `smblsIife` | string | preloaded smbls IIFE source (CF Workers / build-time) |
|
|
52
|
+
| `editorBundle` | string | preview-editor bundle to inject (non-prod only) |
|
|
53
|
+
| `analyzedScript` | string | analytics tracking `<script>` to inject |
|
|
54
|
+
| `indexHtml` | string | override the index.html template |
|
|
55
|
+
| `ssrCache` | object | pre-computed SSR cache (skips runtime brender) |
|
|
56
|
+
| `ProjectDataService` | object | passed to `getBundleScript` for shared-library merging |
|
|
57
|
+
|
|
58
|
+
### `loadJsonData(jsonPath)`
|
|
59
|
+
|
|
60
|
+
Synchronously load and parse a project JSON file.
|
|
61
|
+
|
|
62
|
+
### Other exports
|
|
63
|
+
|
|
64
|
+
`renderNotPublishedHtml`, `transpileFunctionStrings`, `getBundleScript`, `getSmblsIife`, `normalizeRoute`, `resolvePageRoute`, `extractRouteParams`, `STATIC_EXT`, `deepDestringifyFunctions`, `stripFunctionStrings`, `generateSitemap` (if brender is available), `replaceVariablesInFileSync`, `transpileFuncql`.
|
|
65
|
+
|
|
66
|
+
## Environment variables
|
|
67
|
+
|
|
68
|
+
| Variable | Default | Description |
|
|
69
|
+
| ----------- | ---------------------------------- | ---------------------------- |
|
|
70
|
+
| `PORT` | `3333` | HTTP port (CLI only) |
|
|
71
|
+
| `JSON_PATH` | `./app.json` | JSON data path (CLI only) |
|
|
72
|
+
| `APPKEY` | — | Override appkey (CLI only) |
|
|
73
|
+
| `NODE_ENV` | `development` | Channel selection |
|
|
74
|
+
| `LOG_LEVEL` | `debug` (dev) / `info` (prod) | Log verbosity |
|
|
75
|
+
| `BRENDER` | enabled | Set to `false` to skip SSR |
|
package/bin/start.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* mermaid CLI — start a standalone Symbols project server from a JSON file.
|
|
4
|
+
*
|
|
5
|
+
* mermaid # serve ./app.json on :3333
|
|
6
|
+
* mermaid --json ./project-data.json # custom JSON path
|
|
7
|
+
* mermaid --port 4000 # custom port
|
|
8
|
+
* mermaid --appkey demo/site # force a specific appkey
|
|
9
|
+
*
|
|
10
|
+
* Env:
|
|
11
|
+
* PORT, JSON_PATH, APPKEY, NODE_ENV, LOG_LEVEL
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { createMermaidApp, loadJsonData } from '../src/server.js';
|
|
15
|
+
import log from '../src/utils/logger.js';
|
|
16
|
+
|
|
17
|
+
const args = process.argv.slice(2);
|
|
18
|
+
function arg(name, fallback) {
|
|
19
|
+
const i = args.indexOf(`--${name}`);
|
|
20
|
+
return i !== -1 ? args[i + 1] : fallback;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const PORT = parseInt(arg('port', process.env.PORT || '3333'), 10);
|
|
24
|
+
const JSON_PATH = arg('json', process.env.JSON_PATH || './app.json');
|
|
25
|
+
const APPKEY = arg('appkey', process.env.APPKEY || null);
|
|
26
|
+
const NODE_ENV = process.env.NODE_ENV || 'development';
|
|
27
|
+
const LOG_LEVEL = process.env.LOG_LEVEL || (NODE_ENV === 'production' ? 'info' : 'debug');
|
|
28
|
+
|
|
29
|
+
log.level = LOG_LEVEL;
|
|
30
|
+
|
|
31
|
+
log.info(`[MERMAID] Starting standalone runner...`);
|
|
32
|
+
log.info(`[MERMAID] Environment: ${NODE_ENV}`);
|
|
33
|
+
log.info(`[MERMAID] JSON: ${JSON_PATH}`);
|
|
34
|
+
if (APPKEY) log.info(`[MERMAID] Locked to appkey: ${APPKEY}`);
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
// Validate JSON exists up front
|
|
38
|
+
loadJsonData(JSON_PATH);
|
|
39
|
+
} catch (err) {
|
|
40
|
+
log.error(`[MERMAID] ${err.message}`);
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const app = createMermaidApp({
|
|
45
|
+
jsonPath: JSON_PATH,
|
|
46
|
+
appkeyOverride: APPKEY,
|
|
47
|
+
channel: NODE_ENV === 'production' ? 'production' : 'development',
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const server = app.listen(PORT, () => {
|
|
51
|
+
log.info(`[MERMAID] Running on http://localhost:${PORT}`);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const shutdown = (signal) => {
|
|
55
|
+
log.info(`[MERMAID] ${signal} received, shutting down...`);
|
|
56
|
+
server.close();
|
|
57
|
+
process.exit(0);
|
|
58
|
+
};
|
|
59
|
+
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
|
60
|
+
process.on('SIGINT', () => shutdown('SIGINT'));
|
package/index.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
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/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@symbo.ls/mermaid",
|
|
3
|
+
"version": "3.14.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"license": "CC-BY-NC-4.0",
|
|
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
|
+
"exports": {
|
|
9
|
+
".": "./index.js",
|
|
10
|
+
"./server": "./src/server.js",
|
|
11
|
+
"./render": "./src/render.js",
|
|
12
|
+
"./bundle": "./src/bundle.js",
|
|
13
|
+
"./route": "./src/route.js",
|
|
14
|
+
"./utils/deepDestringify": "./src/utils/deepDestringify.js",
|
|
15
|
+
"./utils/funcql-transpile": "./src/utils/funcql-transpile.js",
|
|
16
|
+
"./utils/vartranformer": "./src/utils/vartranformer.js",
|
|
17
|
+
"./utils/logger": "./src/utils/logger.js",
|
|
18
|
+
"./template.html": "./src/template.html"
|
|
19
|
+
},
|
|
20
|
+
"files": ["index.js", "src", "README.md"],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"start": "node bin/start.js"
|
|
23
|
+
},
|
|
24
|
+
"bin": {
|
|
25
|
+
"mermaid": "./bin/start.js"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@symbo.ls/utils": "^3.14.0",
|
|
29
|
+
"cors": "^2.8.5",
|
|
30
|
+
"express": "^4.21.2"
|
|
31
|
+
},
|
|
32
|
+
"optionalDependencies": {
|
|
33
|
+
"@symbo.ls/brender": "^3.14.0"
|
|
34
|
+
},
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"access": "public"
|
|
37
|
+
}
|
|
38
|
+
}
|
package/src/bundle.js
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
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
|
+
export function getSmblsIife() {
|
|
10
|
+
return smblsIife;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Generate the client-side bundle script that creates the Smbls app.
|
|
15
|
+
* Uses the same approach as templates/start.js runFromJson:
|
|
16
|
+
* Smbls.deepDestringifyFunctions(data) -> Smbls.create(app, context)
|
|
17
|
+
*/
|
|
18
|
+
export function getBundleScript(appKey, data, req, { ProjectDataService } = {}) {
|
|
19
|
+
log.debug(`[BUNDLE] getBundleScript started for ${appKey}`);
|
|
20
|
+
|
|
21
|
+
if (!data) {
|
|
22
|
+
log.warn('[BUNDLE] No data provided, returning empty');
|
|
23
|
+
return '';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Ensure data is fully merged before bundling
|
|
27
|
+
try {
|
|
28
|
+
if (data.merged) {
|
|
29
|
+
Object.assign(data, data.merged);
|
|
30
|
+
log.debug(`[BUNDLE] Applied precomputed merged data for project ${data.id}`);
|
|
31
|
+
} else if (
|
|
32
|
+
ProjectDataService &&
|
|
33
|
+
Array.isArray(data.sharedLibraries) &&
|
|
34
|
+
data.sharedLibraries.length > 0
|
|
35
|
+
) {
|
|
36
|
+
const mergedData = ProjectDataService.mergeSharedLibraries(
|
|
37
|
+
data,
|
|
38
|
+
data.sharedLibraries,
|
|
39
|
+
data.projectType,
|
|
40
|
+
);
|
|
41
|
+
Object.assign(data, mergedData);
|
|
42
|
+
log.debug(`[BUNDLE] Merged ${data.sharedLibraries.length} shared libraries`);
|
|
43
|
+
}
|
|
44
|
+
} catch (mergeError) {
|
|
45
|
+
log.error(`[BUNDLE] Failed to ensure merged data: ${mergeError.message}`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Serialize the project data as JSON (same as templates __mockedData approach)
|
|
49
|
+
const jsonData = JSON.stringify(data);
|
|
50
|
+
|
|
51
|
+
// Determine liveSync setting
|
|
52
|
+
const release = req?.query?.release || false;
|
|
53
|
+
const version = req?.query?.version;
|
|
54
|
+
const isSymboLs = typeof appKey === 'string' && appKey.endsWith('symbo.ls');
|
|
55
|
+
const shouldDisableLiveSync = Boolean(release) || Boolean(version) || !isSymboLs;
|
|
56
|
+
|
|
57
|
+
const settingsStr = isSymboLs
|
|
58
|
+
? JSON.stringify({ ...data.settings, liveSync: !shouldDisableLiveSync, inspect: true })
|
|
59
|
+
: 'null';
|
|
60
|
+
|
|
61
|
+
// Generate the client script - same pattern as templates/start.js runFromJson
|
|
62
|
+
const script = `
|
|
63
|
+
var context = Smbls.deepDestringifyFunctions(${jsonData});
|
|
64
|
+
context.packageManager = 'esm.sh';
|
|
65
|
+
if (context.db && context.db.createClient) delete context.db.createClient;
|
|
66
|
+
if (context.fetch && context.fetch.createClient) delete context.fetch.createClient;
|
|
67
|
+
|
|
68
|
+
// Runtime defaults (these are baked into the starter-kit/frank but missing from server data)
|
|
69
|
+
if (context.useReset === undefined) context.useReset = true;
|
|
70
|
+
if (context.useVariable === undefined) context.useVariable = true;
|
|
71
|
+
if (context.useFontImport === undefined) context.useFontImport = true;
|
|
72
|
+
if (context.useIconSprite === undefined) context.useIconSprite = true;
|
|
73
|
+
if (context.useSvgSprite === undefined) context.useSvgSprite = true;
|
|
74
|
+
if (context.useDefaultConfig === undefined) context.useDefaultConfig = true;
|
|
75
|
+
if (context.useDocumentTheme === undefined) context.useDocumentTheme = true;
|
|
76
|
+
if (!context.routerOptions) context.routerOptions = { scrollToTop: true };
|
|
77
|
+
|
|
78
|
+
var app = context.app || {};
|
|
79
|
+
delete context.app;
|
|
80
|
+
var settings = ${settingsStr};
|
|
81
|
+
if (settings) context.editor = settings;
|
|
82
|
+
|
|
83
|
+
// When brender pre-rendered this page, adopt the existing DOM nodes
|
|
84
|
+
// instead of tearing them down. The [data-ssr] wrapper is moved out of the
|
|
85
|
+
// way so pre-rendered children land at document.body level — matching the
|
|
86
|
+
// level at which DOMQL creates its own elements. The __BRENDER__ flag and
|
|
87
|
+
// __BR_REGISTRY__ map were injected into <head> by the server.
|
|
88
|
+
// Theme initialization: set data-theme before app renders to avoid FOUC
|
|
89
|
+
if (context.useDocumentTheme !== false) {
|
|
90
|
+
(function() {
|
|
91
|
+
try {
|
|
92
|
+
var t = null;
|
|
93
|
+
var key = context.themeStorageKey;
|
|
94
|
+
if (key) { try { t = localStorage.getItem(key) } catch(e) {} }
|
|
95
|
+
if (!t) t = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
|
96
|
+
if (t) document.documentElement.setAttribute('data-theme', t);
|
|
97
|
+
} catch(e) {}
|
|
98
|
+
})();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
var ssrShell = document.querySelector('[data-ssr]');
|
|
102
|
+
if (window.__BRENDER__ && ssrShell) {
|
|
103
|
+
// Move pre-rendered children to body before DOMQL skeleton creation
|
|
104
|
+
var frag = document.createDocumentFragment();
|
|
105
|
+
while (ssrShell.firstChild) frag.appendChild(ssrShell.firstChild);
|
|
106
|
+
document.body.insertBefore(frag, ssrShell);
|
|
107
|
+
ssrShell.remove();
|
|
108
|
+
} else if (ssrShell) {
|
|
109
|
+
// No brender hydration — remove empty/stale SSR shell
|
|
110
|
+
ssrShell.remove();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
Smbls.create(app, context);
|
|
114
|
+
`;
|
|
115
|
+
|
|
116
|
+
log.debug(`[BUNDLE] Script generated (${script.length} bytes)`);
|
|
117
|
+
return script;
|
|
118
|
+
}
|