@underpostnet/underpost 2.95.3 → 2.95.7

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/src/cli/static.js CHANGED
@@ -2,55 +2,6 @@
2
2
  * Static site generation module with enhanced customization capabilities
3
3
  * @module src/cli/static.js
4
4
  * @namespace UnderpostStatic
5
- *
6
- * @example
7
- * // Basic usage - generate a simple page
8
- * import UnderpostStatic from './static.js';
9
- *
10
- * await UnderpostStatic.API.callback({
11
- * page: './src/client/ssr/body/DefaultSplashScreen.js',
12
- * title: 'My App',
13
- * outputPath: './dist/index.html'
14
- * });
15
- *
16
- * @example
17
- * // Advanced usage - full customization
18
- * await UnderpostStatic.API.callback({
19
- * page: './src/client/ssr/body/CustomPage.js',
20
- * outputPath: './dist/custom.html',
21
- * metadata: {
22
- * title: 'My Custom Page',
23
- * description: 'A fully customized static page',
24
- * keywords: ['static', 'generator', 'custom'],
25
- * author: 'John Doe',
26
- * themeColor: '#007bff',
27
- * canonicalURL: 'https://example.com/custom',
28
- * thumbnail: 'https://example.com/thumb.png'
29
- * },
30
- * scripts: {
31
- * head: [
32
- * { src: '/vendor/library.js', async: true },
33
- * { content: 'console.log("Inline script");', type: 'module' }
34
- * ],
35
- * body: [
36
- * { src: '/app.js', defer: true }
37
- * ]
38
- * },
39
- * styles: [
40
- * { href: '/custom.css' },
41
- * { content: 'body { margin: 0; }' }
42
- * ],
43
- * headComponents: [
44
- * './src/client/ssr/head/Seo.js',
45
- * './src/client/ssr/head/Pwa.js'
46
- * ],
47
- * icons: {
48
- * favicon: '/custom-favicon.ico',
49
- * appleTouchIcon: '/apple-touch-icon.png'
50
- * },
51
- * env: 'production',
52
- * minify: true
53
- * });
54
5
  */
55
6
 
56
7
  import fs from 'fs-extra';
@@ -140,6 +91,31 @@ const logger = loggerFactory(import.meta);
140
91
  * @property {Object} [microdata=[]] - Structured data (JSON-LD)
141
92
  */
142
93
 
94
+ const DefaultStaticGenerationOptions = {
95
+ page: '',
96
+ title: '',
97
+ outputPath: '',
98
+ deployId: '',
99
+ buildHost: '',
100
+ buildPath: '/',
101
+ env: 'production',
102
+ build: false,
103
+ dev: false,
104
+ minify: true,
105
+ metadata: {},
106
+ scripts: {},
107
+ styles: [],
108
+ headComponents: [],
109
+ bodyComponents: [],
110
+ icons: {},
111
+ customPayload: {},
112
+ templateHelpers: {},
113
+ configFile: '',
114
+ lang: 'en',
115
+ dir: 'ltr',
116
+ microdata: [],
117
+ };
118
+
143
119
  /**
144
120
  * Template helper functions for common SSR patterns
145
121
  * @namespace TemplateHelpers
@@ -150,19 +126,6 @@ const TemplateHelpers = {
150
126
  * @param {ScriptOptions} options - Script options
151
127
  * @returns {string} HTML script tag
152
128
  * @memberof TemplateHelpers
153
- *
154
- * @example
155
- * // External script with async
156
- * TemplateHelpers.createScriptTag({ src: '/app.js', async: true })
157
- * // Returns: <script async src="/app.js"></script>
158
- *
159
- * @example
160
- * // Inline module script
161
- * TemplateHelpers.createScriptTag({
162
- * content: 'console.log("Hello");',
163
- * type: 'module'
164
- * })
165
- * // Returns: <script type="module">console.log("Hello");</script>
166
129
  */
167
130
  createScriptTag(options) {
168
131
  const attrs = [];
@@ -194,16 +157,6 @@ const TemplateHelpers = {
194
157
  * @param {StyleOptions} options - Style options
195
158
  * @returns {string} HTML link or style tag
196
159
  * @memberof TemplateHelpers
197
- *
198
- * @example
199
- * // External stylesheet
200
- * TemplateHelpers.createStyleTag({ href: '/styles.css' })
201
- * // Returns: <link rel="stylesheet" href="/styles.css" media="all">
202
- *
203
- * @example
204
- * // Inline styles
205
- * TemplateHelpers.createStyleTag({ content: 'body { margin: 0; }' })
206
- * // Returns: <style>body { margin: 0; }</style>
207
160
  */
208
161
  createStyleTag(options) {
209
162
  if (options.content) {
@@ -224,13 +177,6 @@ const TemplateHelpers = {
224
177
  * @param {IconOptions} icons - Icon options
225
178
  * @returns {string} HTML icon link tags
226
179
  * @memberof TemplateHelpers
227
- *
228
- * @example
229
- * TemplateHelpers.createIconTags({
230
- * favicon: '/favicon.ico',
231
- * appleTouchIcon: '/apple-touch-icon.png',
232
- * manifest: '/manifest.json'
233
- * })
234
180
  */
235
181
  createIconTags(icons) {
236
182
  const tags = [];
@@ -261,13 +207,6 @@ const TemplateHelpers = {
261
207
  * @param {MetadataOptions} metadata - Metadata options
262
208
  * @returns {string} HTML meta tags
263
209
  * @memberof TemplateHelpers
264
- *
265
- * @example
266
- * TemplateHelpers.createMetaTags({
267
- * description: 'My page description',
268
- * keywords: ['web', 'app'],
269
- * author: 'John Doe'
270
- * })
271
210
  */
272
211
  createMetaTags(metadata) {
273
212
  const tags = [];
@@ -329,16 +268,6 @@ const TemplateHelpers = {
329
268
  * @param {Object[]} microdata - Array of structured data objects
330
269
  * @returns {string} HTML script tags with JSON-LD
331
270
  * @memberof TemplateHelpers
332
- *
333
- * @example
334
- * TemplateHelpers.createMicrodataTags([
335
- * {
336
- * '@context': 'https://schema.org',
337
- * '@type': 'WebSite',
338
- * 'name': 'My Site',
339
- * 'url': 'https://example.com'
340
- * }
341
- * ])
342
271
  */
343
272
  createMicrodataTags(microdata) {
344
273
  if (!microdata || !Array.isArray(microdata) || microdata.length === 0) {
@@ -410,19 +339,6 @@ const ConfigLoader = {
410
339
  * @param {string} configPath - Path to config file
411
340
  * @returns {Object} Configuration object
412
341
  * @memberof ConfigLoader
413
- *
414
- * @example
415
- * // static-config.json
416
- * {
417
- * "metadata": {
418
- * "title": "My App",
419
- * "description": "My application description"
420
- * },
421
- * "env": "production"
422
- * }
423
- *
424
- * // Usage
425
- * const config = ConfigLoader.load('./static-config.json');
426
342
  */
427
343
  load(configPath) {
428
344
  try {
@@ -466,90 +382,83 @@ class UnderpostStatic {
466
382
  * Generate static HTML file with enhanced customization options
467
383
  *
468
384
  * @param {StaticGenerationOptions} options - Options for static generation
385
+ * @param {string} [options.page] - Path to the SSR component to render
386
+ * @param {string} [options.title] - Page title (deprecated: use metadata.title)
387
+ * @param {string} [options.outputPath] - Output file path
388
+ * @param {string} [options.deployId] - Deployment identifier
389
+ * @param {string} [options.buildHost] - Build host URL
390
+ * @param {string} [options.buildPath='/'] - Build path
391
+ * @param {string} [options.env='production'] - Environment (development/production)
392
+ * @param {boolean} [options.build=false] - Whether to trigger build
393
+ * @param {boolean} [options.minify=true] - Minify HTML output
394
+ * @param {MetadataOptions} [options.metadata={}] - Comprehensive metadata options
395
+ * @param {Object} [options.scripts={}] - Script injection options
396
+ * @param {ScriptOptions[]} [options.scripts.head=[]] - Scripts for
397
+ * head section
398
+ * @param {ScriptOptions[]} [options.scripts.body=[]] - Scripts for body section
399
+ * @param {StyleOptions[]} [options.styles=[]] - Stylesheet options
400
+ * @param {string[]} [options.headComponents=[]] - Array of SSR head component paths
401
+ * @param {string[]} [options.bodyComponents=[]] - Array of SSR body component paths
402
+ * @param {IconOptions} [options.icons={}] - Icon configuration
403
+ * @param {Object} [options.customPayload={}] - Custom data to inject into renderPayload
404
+ * @param {string} [options.configFile=''] - Path to JSON config file
405
+ * @param {string} [options.lang='en'] - HTML lang attribute
406
+ * @param {string} [options.dir='ltr'] - HTML dir attribute
407
+ * @param {Object} [options.microdata=[]] - Structured data (JSON-LD)
469
408
  * @returns {Promise<void>}
470
409
  * @memberof UnderpostStatic
471
- *
472
- * @example
473
- * // Minimal usage
474
- * await UnderpostStatic.API.callback({
475
- * page: './src/client/ssr/body/DefaultSplashScreen.js',
476
- * outputPath: './dist/index.html'
477
- * });
478
- *
479
- * @example
480
- * // Full customization with metadata and scripts
481
- * await UnderpostStatic.API.callback({
482
- * page: './src/client/ssr/body/CustomPage.js',
483
- * outputPath: './dist/page.html',
484
- * metadata: {
485
- * title: 'My Custom Page',
486
- * description: 'A fully customized page',
487
- * keywords: ['custom', 'static', 'page'],
488
- * author: 'Jane Developer',
489
- * themeColor: '#4CAF50',
490
- * canonicalURL: 'https://example.com/page',
491
- * thumbnail: 'https://example.com/images/thumbnail.png',
492
- * locale: 'en-US',
493
- * siteName: 'My Website'
494
- * },
495
- * scripts: {
496
- * head: [
497
- * { src: 'https://cdn.example.com/analytics.js', async: true },
498
- * { content: 'window.config = { apiUrl: "https://api.example.com" };' }
499
- * ],
500
- * body: [
501
- * { src: '/app.js', type: 'module', defer: true }
502
- * ]
503
- * },
504
- * styles: [
505
- * { href: '/main.css' },
506
- * { content: 'body { font-family: sans-serif; }' }
507
- * ],
508
- * icons: {
509
- * favicon: '/favicon.ico',
510
- * appleTouchIcon: '/apple-touch-icon.png',
511
- * manifest: '/manifest.json'
512
- * },
513
- * headComponents: [
514
- * './src/client/ssr/head/Seo.js',
515
- * './src/client/ssr/head/Pwa.js'
516
- * ],
517
- * microdata: [
518
- * {
519
- * '@context': 'https://schema.org',
520
- * '@type': 'WebPage',
521
- * 'name': 'My Custom Page',
522
- * 'url': 'https://example.com/page'
523
- * }
524
- * ],
525
- * customPayload: {
526
- * apiEndpoint: 'https://api.example.com',
527
- * features: ['feature1', 'feature2']
528
- * },
529
- * env: 'production',
530
- * minify: true
531
- * });
532
- *
533
- * @example
534
- * // Using a config file
535
- * await UnderpostStatic.API.callback({
536
- * configFile: './static-config.json',
537
- * outputPath: './dist/index.html'
538
- * });
539
- *
540
- * @example
541
- * // Generate with build trigger
542
- * await UnderpostStatic.API.callback({
543
- * page: './src/client/ssr/body/DefaultSplashScreen.js',
544
- * outputPath: './public/index.html',
545
- * deployId: 'production-v1',
546
- * buildHost: 'example.com',
547
- * buildPath: '/',
548
- * build: true,
549
- * env: 'production'
550
- * });
551
410
  */
552
- async callback(options = {}) {
411
+ async callback(options = DefaultStaticGenerationOptions) {
412
+ // Handle config template generation
413
+ if (options.generateConfig) {
414
+ const configPath = typeof options.generateConfig === 'string' ? options.generateConfig : './static-config.json';
415
+ return UnderpostStatic.API.generateConfigTemplate(configPath);
416
+ }
417
+
418
+ // Parse comma-separated options
419
+ if (options.keywords) {
420
+ options.keywords = options.keywords.split(',').map((k) => k.trim());
421
+ }
422
+ if (options.headScripts) {
423
+ options.scripts = options.scripts || {};
424
+ options.scripts.head = options.headScripts.split(',').map((s) => ({ src: s.trim() }));
425
+ }
426
+ if (options.bodyScripts) {
427
+ options.scripts = options.scripts || {};
428
+ options.scripts.body = options.bodyScripts.split(',').map((s) => ({ src: s.trim() }));
429
+ }
430
+ if (options.styles) {
431
+ options.styles = options.styles.split(',').map((s) => ({ href: s.trim() }));
432
+ }
433
+ if (options.headComponents) {
434
+ options.headComponents = options.headComponents.split(',').map((c) => c.trim());
435
+ }
436
+ if (options.bodyComponents) {
437
+ options.bodyComponents = options.bodyComponents.split(',').map((c) => c.trim());
438
+ }
439
+
440
+ // Build metadata object from individual options
441
+ options.metadata = {
442
+ ...(options.title && { title: options.title }),
443
+ ...(options.description && { description: options.description }),
444
+ ...(options.keywords && { keywords: options.keywords }),
445
+ ...(options.author && { author: options.author }),
446
+ ...(options.themeColor && { themeColor: options.themeColor }),
447
+ ...(options.canonicalUrl && { canonicalURL: options.canonicalUrl }),
448
+ ...(options.thumbnail && { thumbnail: options.thumbnail }),
449
+ ...(options.locale && { locale: options.locale }),
450
+ ...(options.siteName && { siteName: options.siteName }),
451
+ };
452
+
453
+ // Build icons object
454
+ if (options.favicon || options.appleTouchIcon || options.manifest) {
455
+ options.icons = {
456
+ ...(options.favicon && { favicon: options.favicon }),
457
+ ...(options.appleTouchIcon && { appleTouchIcon: options.appleTouchIcon }),
458
+ ...(options.manifest && { manifest: options.manifest }),
459
+ };
460
+ }
461
+
553
462
  // Load config from file if specified
554
463
  if (options.configFile) {
555
464
  const fileConfig = ConfigLoader.load(options.configFile);
@@ -717,10 +626,6 @@ class UnderpostStatic {
717
626
  * @param {string} outputPath - Where to save the template config
718
627
  * @returns {void}
719
628
  * @memberof UnderpostStatic
720
- *
721
- * @example
722
- * // Generate a template configuration file
723
- * UnderpostStatic.API.generateConfigTemplate('./my-static-config.json');
724
629
  */
725
630
  generateConfigTemplate(outputPath = './static-config.json') {
726
631
  const template = {
@@ -465,6 +465,13 @@ const DefaultManagement = {
465
465
  const selectedRows = AgGrid.grids[gridId].getSelectedRows();
466
466
  logger.info('selectedRows', selectedRows);
467
467
  },
468
+ onRowEditingStarted: async (...args) => {
469
+ // Show only save button when editing starts
470
+ s(`.management-table-btn-save-${id}`).classList.remove('hide');
471
+ if (permissions.add) s(`.management-table-btn-add-${id}`).classList.add('hide');
472
+ if (permissions.remove) s(`.management-table-btn-clean-${id}`).classList.add('hide');
473
+ if (permissions.reload) s(`.management-table-btn-reload-${id}`).classList.add('hide');
474
+ },
468
475
  onRowValueChanged: async (...args) => {
469
476
  const [event] = args;
470
477
  logger.info('onRowValueChanged', args);
package/src/index.js CHANGED
@@ -36,7 +36,7 @@ class Underpost {
36
36
  * @type {String}
37
37
  * @memberof Underpost
38
38
  */
39
- static version = 'v2.95.3';
39
+ static version = 'v2.95.7';
40
40
  /**
41
41
  * Repository cli API
42
42
  * @static
@@ -7,7 +7,6 @@
7
7
  import fs from 'fs-extra';
8
8
  import { loggerFactory } from './logger.js';
9
9
  import { shellExec } from './process.js';
10
- import { getCronBackUpFolder } from './conf.js';
11
10
  import dotenv from 'dotenv';
12
11
 
13
12
  dotenv.config();
@@ -25,70 +24,22 @@ class BackUp {
25
24
  * @description Initiates a backup operation for the specified deployment list.
26
25
  * @param {string} deployList - The list of deployments to backup.
27
26
  * @param {Object} options - The options for the backup operation.
28
- * @param {boolean} options.itc - Whether to backup inside container data.
29
27
  * @param {boolean} options.git - Whether to backup data using Git.
30
28
  * @memberof BackUp
31
29
  */
32
- static callback = async function (deployList, options = { itc: false, git: false }) {
30
+ static callback = async function (deployList, options = { git: false }) {
33
31
  if ((!deployList || deployList === 'dd') && fs.existsSync(`./engine-private/deploy/dd.router`))
34
- deployList = fs.readFileSync(`./engine-private/deploy/dd.router`, 'utf8');
32
+ deployList = fs.readFileSync(`./engine-private/deploy/dd.router`, 'utf8').trim();
35
33
 
36
34
  logger.info('init backups callback', deployList);
37
35
  await logger.setUpInfo();
38
- const currentDate = new Date().getTime();
39
- const maxBackupRetention = 5;
40
-
41
- if (!fs.existsSync('./engine-private/cron-backups'))
42
- fs.mkdirSync('./engine-private/cron-backups', { recursive: true });
43
36
 
44
37
  for (const _deployId of deployList.split(',')) {
45
38
  const deployId = _deployId.trim();
46
39
  if (!deployId) continue;
47
40
 
48
- if (!(options.itc === true)) {
49
- shellExec(`node bin db ${options.git ? '--git ' : ''}--export ${deployId}`);
50
- continue;
51
- }
52
-
53
- const confServer = JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/conf.server.json`, 'utf8'));
54
-
55
- for (const host of Object.keys(confServer))
56
- for (const path of Object.keys(confServer[host])) {
57
- // retention policy
58
- const { db } = confServer[host][path];
59
- if (!db) continue;
60
- logger.info('Init backup', { host, path, db });
61
-
62
- const backUpPath = `${process.cwd()}/engine-private/cron-backups/${getCronBackUpFolder(host, path)}`;
63
- if (!fs.existsSync(backUpPath)) fs.mkdirSync(`${backUpPath}`, { recursive: true });
64
- // .isDirectory()
65
- const files = await fs.readdir(backUpPath, { withFileTypes: true });
66
-
67
- const currentBackupsDirs = files
68
- .map((fileObj) => parseInt(fileObj.name))
69
- .sort((a, b) => a - b)
70
- .reverse();
71
-
72
- for (const retentionPath of currentBackupsDirs.filter((t, i) => i >= maxBackupRetention - 1)) {
73
- const removePathRetention = `${backUpPath}/${retentionPath}`;
74
- logger.info('Remove backup folder', removePathRetention);
75
- fs.removeSync(removePathRetention);
76
- }
77
-
78
- fs.mkdirSync(`${backUpPath}/${currentDate}`, { recursive: true });
79
-
80
- shellExec(`node bin/db ${host}${path} export ${deployId} ${backUpPath}/${currentDate}`);
81
- }
82
- shellExec(
83
- `cd ./engine-private/cron-backups` +
84
- ` && underpost pull . ${process.env.GITHUB_USERNAME}/cron-backups` +
85
- ` && git add .` +
86
- ` && underpost cmt . backup cron-job '${new Date().toLocaleDateString()}'` +
87
- ` && underpost push . ${process.env.GITHUB_USERNAME}/cron-backups`,
88
- {
89
- silent: true,
90
- },
91
- );
41
+ logger.info('Executing database export for', deployId);
42
+ shellExec(`node bin db ${options.git ? '--git ' : ''}--export ${deployId}`);
92
43
  }
93
44
  };
94
45
  }
@@ -1227,13 +1227,12 @@ const Cmd = {
1227
1227
  * @param {string} name - The name.
1228
1228
  * @param {string} expression - The expression.
1229
1229
  * @param {object} options - The options.
1230
+ * @param {number} instances - The number of PM2 instances (default: 1).
1230
1231
  * @returns {string} - The cron command.
1231
1232
  * @memberof Cmd
1232
1233
  */
1233
- cron: (deployList, jobList, name, expression, options) =>
1234
- `pm2 start ./bin/index.js --no-autorestart --instances 1 --cron "${expression}" --name ${name} -- cron ${
1235
- options?.itc ? `--itc ` : ''
1236
- }${options?.git ? `--git ` : ''}${deployList} ${jobList}`,
1234
+ cron: (deployList, jobList, name, expression, options, instances = 1) =>
1235
+ `pm2 start ./bin/index.js --no-autorestart --instances ${instances} --cron "${expression}" --name ${name} -- cron ${options?.git ? `--git ` : ''}${deployList} ${jobList}`,
1237
1236
  };
1238
1237
 
1239
1238
  /**
@@ -1,183 +0,0 @@
1
- {
2
- "_comment": "Example static site configuration file for Underpost Static Generator",
3
- "_usage": "Use with: underpost static --config-file ./static-config-example.json",
4
-
5
- "page": "./src/client/ssr/body/DefaultSplashScreen.js",
6
- "outputPath": "./dist/index.html",
7
- "env": "production",
8
- "minify": true,
9
- "lang": "en",
10
- "dir": "ltr",
11
-
12
- "metadata": {
13
- "_comment": "SEO and social media metadata",
14
- "title": "My Awesome Application",
15
- "description": "A comprehensive example of a static site with full metadata customization",
16
- "keywords": ["static site", "generator", "underpost", "seo", "progressive web app"],
17
- "author": "Your Name or Company",
18
- "themeColor": "#4CAF50",
19
- "canonicalURL": "https://example.com",
20
- "thumbnail": "https://example.com/images/og-thumbnail.png",
21
- "locale": "en-US",
22
- "siteName": "My Awesome Site",
23
- "openGraph": {
24
- "_comment": "Additional Open Graph properties",
25
- "type": "website",
26
- "image:width": "1200",
27
- "image:height": "630"
28
- },
29
- "twitter": {
30
- "_comment": "Twitter card specific metadata",
31
- "site": "@yourhandle",
32
- "creator": "@creatorhandle"
33
- }
34
- },
35
-
36
- "scripts": {
37
- "_comment": "Scripts to inject into head and body sections",
38
- "head": [
39
- {
40
- "_comment": "External analytics script with async loading",
41
- "src": "https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID",
42
- "async": true
43
- },
44
- {
45
- "_comment": "Inline configuration script",
46
- "content": "window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); gtag('config', 'GA_MEASUREMENT_ID');",
47
- "type": "text/javascript"
48
- },
49
- {
50
- "_comment": "App configuration as inline script",
51
- "content": "window.appConfig = { apiUrl: 'https://api.example.com', version: '1.0.0', features: { analytics: true, debugging: false } };",
52
- "type": "text/javascript"
53
- }
54
- ],
55
- "body": [
56
- {
57
- "_comment": "Main application bundle as ES module",
58
- "src": "/dist/app.js",
59
- "type": "module",
60
- "defer": true
61
- },
62
- {
63
- "_comment": "Service worker registration",
64
- "content": "if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/sw.js').then(reg => console.log('SW registered', reg)).catch(err => console.log('SW error', err)); }",
65
- "type": "text/javascript"
66
- }
67
- ]
68
- },
69
-
70
- "styles": [
71
- {
72
- "_comment": "Main stylesheet",
73
- "href": "/styles/main.css"
74
- },
75
- {
76
- "_comment": "External font",
77
- "href": "https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap"
78
- },
79
- {
80
- "_comment": "Critical inline CSS for faster rendering",
81
- "content": "body { margin: 0; padding: 0; font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; } * { box-sizing: border-box; }"
82
- },
83
- {
84
- "_comment": "Print stylesheet",
85
- "href": "/styles/print.css",
86
- "media": "print"
87
- }
88
- ],
89
-
90
- "icons": {
91
- "_comment": "Icon configuration for various platforms",
92
- "favicon": "/favicon.ico",
93
- "appleTouchIcon": "/apple-touch-icon.png",
94
- "manifest": "/manifest.json",
95
- "additional": [
96
- {
97
- "rel": "icon",
98
- "type": "image/png",
99
- "sizes": "32x32",
100
- "href": "/favicon-32x32.png"
101
- },
102
- {
103
- "rel": "icon",
104
- "type": "image/png",
105
- "sizes": "16x16",
106
- "href": "/favicon-16x16.png"
107
- },
108
- {
109
- "rel": "mask-icon",
110
- "href": "/safari-pinned-tab.svg",
111
- "color": "#4CAF50"
112
- }
113
- ]
114
- },
115
-
116
- "_headComponents_comment": "SSR components disabled in this example - they require specific data structures",
117
- "headComponents": [],
118
-
119
- "_bodyComponents_comment": "Additional SSR components to include in body section",
120
- "bodyComponents": [],
121
-
122
- "microdata": [
123
- {
124
- "_comment": "Organization schema for better SEO",
125
- "@context": "https://schema.org",
126
- "@type": "Organization",
127
- "name": "My Organization",
128
- "url": "https://example.com",
129
- "logo": "https://example.com/logo.png",
130
- "contactPoint": {
131
- "@type": "ContactPoint",
132
- "telephone": "+1-555-123-4567",
133
- "contactType": "Customer Service"
134
- },
135
- "sameAs": [
136
- "https://twitter.com/yourhandle",
137
- "https://linkedin.com/company/yourcompany",
138
- "https://github.com/yourorg"
139
- ]
140
- },
141
- {
142
- "_comment": "WebSite schema",
143
- "@context": "https://schema.org",
144
- "@type": "WebSite",
145
- "name": "My Awesome Site",
146
- "url": "https://example.com",
147
- "potentialAction": {
148
- "@type": "SearchAction",
149
- "target": "https://example.com/search?q={search_term_string}",
150
- "query-input": "required name=search_term_string"
151
- }
152
- },
153
- {
154
- "_comment": "WebPage schema",
155
- "@context": "https://schema.org",
156
- "@type": "WebPage",
157
- "name": "Home Page",
158
- "description": "Welcome to our awesome site",
159
- "url": "https://example.com"
160
- }
161
- ],
162
-
163
- "customPayload": {
164
- "_comment": "Custom data injected into window.renderPayload",
165
- "apiEndpoint": "https://api.example.com",
166
- "cdnUrl": "https://cdn.example.com",
167
- "features": {
168
- "chat": true,
169
- "notifications": true,
170
- "darkMode": true
171
- },
172
- "externalServices": {
173
- "analytics": "GA_MEASUREMENT_ID",
174
- "maps": "GOOGLE_MAPS_API_KEY"
175
- }
176
- },
177
-
178
- "buildPath": "/",
179
- "deployId": "",
180
- "buildHost": "",
181
- "build": false,
182
- "dev": false
183
- }