@underpostnet/underpost 2.99.4 → 2.99.6
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/.env.development +0 -3
- package/.env.production +1 -3
- package/.env.test +0 -3
- package/README.md +3 -3
- package/baremetal/commission-workflows.json +93 -4
- package/bin/deploy.js +56 -45
- package/cli.md +45 -28
- package/examples/static-page/README.md +101 -357
- package/examples/static-page/ssr-components/CustomPage.js +1 -13
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +40 -0
- package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +40 -0
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/dd-test-development/deployment.yaml +2 -2
- package/package.json +3 -4
- package/scripts/disk-devices.sh +13 -0
- package/scripts/maas-setup.sh +13 -9
- package/scripts/rocky-kickstart.sh +294 -0
- package/src/cli/baremetal.js +657 -263
- package/src/cli/cloud-init.js +120 -120
- package/src/cli/env.js +4 -1
- package/src/cli/image.js +4 -37
- package/src/cli/index.js +56 -11
- package/src/cli/kickstart.js +149 -0
- package/src/cli/repository.js +3 -1
- package/src/cli/run.js +56 -10
- package/src/cli/secrets.js +0 -34
- package/src/cli/static.js +23 -23
- package/src/client/components/core/Docs.js +22 -3
- package/src/index.js +30 -5
- package/src/server/backup.js +11 -4
- package/src/server/client-build-docs.js +1 -1
- package/src/server/conf.js +0 -22
- package/src/server/cron.js +339 -130
- package/src/server/dns.js +10 -0
- package/src/server/logger.js +22 -27
- package/src/server/tls.js +14 -14
- package/examples/static-page/QUICK-REFERENCE.md +0 -481
- package/examples/static-page/STATIC-GENERATOR-GUIDE.md +0 -757
package/src/server/logger.js
CHANGED
|
@@ -28,11 +28,13 @@ const levels = {
|
|
|
28
28
|
debug: 4,
|
|
29
29
|
};
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
/**
|
|
32
|
+
* The `level` function determines the logging level based on the provided `logLevel` parameter or defaults to 'info'.
|
|
33
|
+
* @param {string} logLevel - The logging level to be used. If not provided, it defaults to 'info'.
|
|
34
|
+
* @returns {string} The logging level to be used for the logger.
|
|
35
|
+
* @memberof Logger
|
|
36
|
+
*/
|
|
37
|
+
const level = (logLevel = '') => logLevel || 'info';
|
|
36
38
|
|
|
37
39
|
// Define different colors for each level.
|
|
38
40
|
// Colors make the log message more visible,
|
|
@@ -98,13 +100,14 @@ const setUpInfo = async (logger = new winston.Logger()) => {
|
|
|
98
100
|
* messages.
|
|
99
101
|
* @param meta - The `meta` parameter in the `loggerFactory` function is used to extract the last part
|
|
100
102
|
* of a URL and use it to create log files in a specific directory.
|
|
103
|
+
* @param logLevel - Specify the logging level for the logger instance. e.g., 'error', 'warn', 'info', 'debug'.
|
|
101
104
|
* @returns {underpostLogger} The `loggerFactory` function returns a logger instance created using Winston logger
|
|
102
105
|
* library. The logger instance is configured with various transports for printing out messages to
|
|
103
106
|
* different destinations such as the terminal, error.log file, and all.log file. The logger instance
|
|
104
107
|
* also has a method `setUpInfo` attached to it for setting up additional information.
|
|
105
108
|
* @memberof Logger
|
|
106
109
|
*/
|
|
107
|
-
const loggerFactory = (meta = { url: '' }) => {
|
|
110
|
+
const loggerFactory = (meta = { url: '' }, logLevel = '') => {
|
|
108
111
|
meta = meta.url.split('/').pop();
|
|
109
112
|
// Define which transports the logger must use to print out messages.
|
|
110
113
|
// In this example, we are using three different transports
|
|
@@ -125,7 +128,7 @@ const loggerFactory = (meta = { url: '' }) => {
|
|
|
125
128
|
// and used to log messages.
|
|
126
129
|
const logger = winston.createLogger({
|
|
127
130
|
defaultMeta: meta,
|
|
128
|
-
level: level(),
|
|
131
|
+
level: level(logLevel),
|
|
129
132
|
levels,
|
|
130
133
|
format: format(meta),
|
|
131
134
|
transports,
|
|
@@ -147,38 +150,30 @@ const loggerFactory = (meta = { url: '' }) => {
|
|
|
147
150
|
};
|
|
148
151
|
|
|
149
152
|
/**
|
|
150
|
-
* The `loggerMiddleware` function
|
|
151
|
-
*
|
|
152
|
-
* @param
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
* @returns {Handler<any, any>} The `loggerMiddleware` function returns a middleware function that uses the Morgan library
|
|
156
|
-
* to log HTTP request information. The middleware function formats the log message using predefined
|
|
157
|
-
* tokens provided by Morgan and custom tokens like `:host` to include specific request details. The
|
|
158
|
-
* log message format includes information such as remote address, HTTP method, host, URL, status code,
|
|
159
|
-
* content length, and response time in milliseconds. The middleware
|
|
153
|
+
* The `loggerMiddleware` function is an Express middleware that uses the Morgan library to log HTTP requests.
|
|
154
|
+
* @param {Object} meta - An object containing metadata, such as the URL, to be used in the logger.
|
|
155
|
+
* @param {string} logLevel - The logging level to be used for the logger (e.g., 'error', 'warn', 'info', 'debug').
|
|
156
|
+
* @param {Function} skip - A function to determine whether to skip logging for a particular request.
|
|
157
|
+
* @returns {Function} A middleware function that can be used in an Express application to log HTTP requests.
|
|
160
158
|
* @memberof Logger
|
|
161
159
|
*/
|
|
162
|
-
const loggerMiddleware = (
|
|
160
|
+
const loggerMiddleware = (
|
|
161
|
+
meta = { url: '' },
|
|
162
|
+
logLevel = 'info',
|
|
163
|
+
skip = (req, res) => process.env.NODE_ENV === 'production',
|
|
164
|
+
) => {
|
|
163
165
|
const stream = {
|
|
164
166
|
// Use the http severity
|
|
165
|
-
write: (message) => loggerFactory(meta).http(message),
|
|
167
|
+
write: (message) => loggerFactory(meta, logLevel).http(message),
|
|
166
168
|
};
|
|
167
169
|
|
|
168
|
-
const skip = (req, res) => process.env.NODE_ENV === 'production';
|
|
169
|
-
|
|
170
170
|
morgan.token('host', function (req, res) {
|
|
171
171
|
return req.headers['host'];
|
|
172
172
|
});
|
|
173
173
|
|
|
174
174
|
return morgan(
|
|
175
|
-
// Define message format string
|
|
176
|
-
// The message format is made from tokens, and each token is
|
|
177
|
-
// defined inside the Morgan library.
|
|
178
|
-
// You can create your custom token to show what do you want from a request.
|
|
175
|
+
// Define message format string
|
|
179
176
|
`:remote-addr :method :host:url :status :res[content-length] - :response-time ms`,
|
|
180
|
-
// Options: in this case, I overwrote the stream and the skip logic.
|
|
181
|
-
// See the methods above.
|
|
182
177
|
{ stream, skip },
|
|
183
178
|
);
|
|
184
179
|
};
|
package/src/server/tls.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* @
|
|
4
|
-
* @
|
|
2
|
+
* Transport Layer Security (TLS) / SSL utilities and HTTPS server creation and management.
|
|
3
|
+
* @module src/server/tls.js
|
|
4
|
+
* @namespace UnderpostTLS
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import fs from 'fs-extra';
|
|
@@ -42,7 +42,7 @@ class TLS {
|
|
|
42
42
|
* It attempts to be permissive: accepts cert-only, cert+ca, or fullchain.
|
|
43
43
|
* @param {string} host
|
|
44
44
|
* @returns {{key?:string, cert?:string, fullchain?:string, ca?:string, dir:string}}
|
|
45
|
-
* @memberof
|
|
45
|
+
* @memberof UnderpostTLS
|
|
46
46
|
*/
|
|
47
47
|
static locateSslFiles(host = DEFAULT_HOST) {
|
|
48
48
|
const dir = SSL_BASE(host);
|
|
@@ -102,7 +102,7 @@ class TLS {
|
|
|
102
102
|
* Validate that a secure context can be built for host (key + cert or fullchain present)
|
|
103
103
|
* @param {string} host
|
|
104
104
|
* @returns {boolean}
|
|
105
|
-
* @memberof
|
|
105
|
+
* @memberof UnderpostTLS
|
|
106
106
|
*/
|
|
107
107
|
static validateSecureContext(host = DEFAULT_HOST) {
|
|
108
108
|
const files = TLS.locateSslFiles(host);
|
|
@@ -115,7 +115,7 @@ class TLS {
|
|
|
115
115
|
* If separate cert + ca are found, they will be used accordingly.
|
|
116
116
|
* @param {string} host
|
|
117
117
|
* @returns {{key:string, cert:string, ca?:string}} options
|
|
118
|
-
* @memberof
|
|
118
|
+
* @memberof UnderpostTLS
|
|
119
119
|
*/
|
|
120
120
|
static buildSecureContext(host = DEFAULT_HOST) {
|
|
121
121
|
const files = TLS.locateSslFiles(host);
|
|
@@ -141,7 +141,7 @@ class TLS {
|
|
|
141
141
|
* The function will copy existing discovered files to: key.key, crt.crt, ca_bundle.crt when possible.
|
|
142
142
|
* @param {string} host
|
|
143
143
|
* @returns {boolean} true if at least key+cert exist after operation
|
|
144
|
-
* @memberof
|
|
144
|
+
* @memberof UnderpostTLS
|
|
145
145
|
*/
|
|
146
146
|
static async buildLocalSSL(host = DEFAULT_HOST) {
|
|
147
147
|
const dir = SSL_BASE(host);
|
|
@@ -176,7 +176,7 @@ class TLS {
|
|
|
176
176
|
* @param {import('express').Application} app
|
|
177
177
|
* @param {Object<string, any>} hosts
|
|
178
178
|
* @returns {{ServerSSL?: https.Server}}
|
|
179
|
-
* @memberof
|
|
179
|
+
* @memberof UnderpostTLS
|
|
180
180
|
*/
|
|
181
181
|
static async createSslServer(app, hosts = { [DEFAULT_HOST]: {} }) {
|
|
182
182
|
let server;
|
|
@@ -214,7 +214,7 @@ class TLS {
|
|
|
214
214
|
* @param {number} port
|
|
215
215
|
* @param {Object<string, any>} proxyRouter
|
|
216
216
|
* @returns {import('express').RequestHandler}
|
|
217
|
-
* @memberof
|
|
217
|
+
* @memberof UnderpostTLS
|
|
218
218
|
*/
|
|
219
219
|
static sslRedirectMiddleware(req, res, port = 80, proxyRouter = {}) {
|
|
220
220
|
const sslRedirectUrl = `https://${req.headers.host}${req.url}`;
|
|
@@ -235,11 +235,11 @@ class TLS {
|
|
|
235
235
|
}
|
|
236
236
|
|
|
237
237
|
/**
|
|
238
|
-
* @class
|
|
238
|
+
* @class UnderpostTLS
|
|
239
239
|
* @description TLS/SSL utilities for Underpost.
|
|
240
|
-
* @memberof
|
|
240
|
+
* @memberof UnderpostTLS
|
|
241
241
|
*/
|
|
242
|
-
class
|
|
242
|
+
class UnderpostTLS {
|
|
243
243
|
static API = {
|
|
244
244
|
TLS,
|
|
245
245
|
SSL_BASE,
|
|
@@ -252,5 +252,5 @@ class UnderpostTls {
|
|
|
252
252
|
};
|
|
253
253
|
}
|
|
254
254
|
|
|
255
|
-
export { TLS, SSL_BASE,
|
|
256
|
-
export default
|
|
255
|
+
export { TLS, SSL_BASE, UnderpostTLS };
|
|
256
|
+
export default UnderpostTLS;
|
|
@@ -1,481 +0,0 @@
|
|
|
1
|
-
# Underpost Static Site Generator - Quick Reference
|
|
2
|
-
|
|
3
|
-
## Basic Commands
|
|
4
|
-
|
|
5
|
-
### Generate Config Template
|
|
6
|
-
```bash
|
|
7
|
-
underpost static --generate-config ./static-config.json
|
|
8
|
-
```
|
|
9
|
-
|
|
10
|
-
### Build from Config File
|
|
11
|
-
```bash
|
|
12
|
-
underpost static --config-file ./static-config.json
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
### Simple Page Generation
|
|
16
|
-
```bash
|
|
17
|
-
underpost static \
|
|
18
|
-
--page ./src/client/ssr/body/Page.js \
|
|
19
|
-
--output-path ./dist/index.html \
|
|
20
|
-
--title "My Page"
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
## Common CLI Patterns
|
|
24
|
-
|
|
25
|
-
### Landing Page with SEO
|
|
26
|
-
```bash
|
|
27
|
-
underpost static \
|
|
28
|
-
--page ./src/client/ssr/body/Landing.js \
|
|
29
|
-
--output-path ./dist/landing.html \
|
|
30
|
-
--title "Welcome" \
|
|
31
|
-
--description "My awesome landing page" \
|
|
32
|
-
--keywords "landing,web,app" \
|
|
33
|
-
--theme-color "#007bff" \
|
|
34
|
-
--canonical-url "https://example.com"
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
### Page with External Scripts
|
|
38
|
-
```bash
|
|
39
|
-
underpost static \
|
|
40
|
-
--page ./src/client/ssr/body/App.js \
|
|
41
|
-
--output-path ./dist/app.html \
|
|
42
|
-
--head-scripts "https://cdn.example.com/analytics.js" \
|
|
43
|
-
--body-scripts "/app.js,/vendor.js"
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
### Page with Custom Icons
|
|
47
|
-
```bash
|
|
48
|
-
underpost static \
|
|
49
|
-
--page ./src/client/ssr/body/Home.js \
|
|
50
|
-
--output-path ./dist/index.html \
|
|
51
|
-
--favicon "/favicon.ico" \
|
|
52
|
-
--apple-touch-icon "/apple-touch-icon.png" \
|
|
53
|
-
--manifest "/manifest.json"
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
### Production Build with Deployment
|
|
57
|
-
```bash
|
|
58
|
-
underpost static \
|
|
59
|
-
--page ./src/client/ssr/body/App.js \
|
|
60
|
-
--output-path ./public/index.html \
|
|
61
|
-
--deploy-id "production-v1" \
|
|
62
|
-
--build-host "example.com" \
|
|
63
|
-
--build-path "/" \
|
|
64
|
-
--build \
|
|
65
|
-
--env production
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
## Configuration File Patterns
|
|
69
|
-
|
|
70
|
-
### Minimal Config
|
|
71
|
-
```json
|
|
72
|
-
{
|
|
73
|
-
"page": "./src/client/ssr/body/Page.js",
|
|
74
|
-
"outputPath": "./dist/index.html",
|
|
75
|
-
"metadata": {
|
|
76
|
-
"title": "My Page",
|
|
77
|
-
"description": "Page description"
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
### SEO-Optimized Config
|
|
83
|
-
```json
|
|
84
|
-
{
|
|
85
|
-
"page": "./src/client/ssr/body/Page.js",
|
|
86
|
-
"outputPath": "./dist/index.html",
|
|
87
|
-
"metadata": {
|
|
88
|
-
"title": "My SEO Page",
|
|
89
|
-
"description": "Comprehensive description for SEO",
|
|
90
|
-
"keywords": ["seo", "optimization", "web"],
|
|
91
|
-
"author": "Your Name",
|
|
92
|
-
"canonicalURL": "https://example.com",
|
|
93
|
-
"thumbnail": "https://example.com/og-image.png",
|
|
94
|
-
"themeColor": "#4CAF50"
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
### With Scripts and Styles
|
|
100
|
-
```json
|
|
101
|
-
{
|
|
102
|
-
"page": "./src/client/ssr/body/Page.js",
|
|
103
|
-
"outputPath": "./dist/index.html",
|
|
104
|
-
"scripts": {
|
|
105
|
-
"head": [
|
|
106
|
-
{ "src": "https://cdn.example.com/lib.js", "async": true }
|
|
107
|
-
],
|
|
108
|
-
"body": [
|
|
109
|
-
{ "src": "/app.js", "type": "module", "defer": true }
|
|
110
|
-
]
|
|
111
|
-
},
|
|
112
|
-
"styles": [
|
|
113
|
-
{ "href": "/main.css" },
|
|
114
|
-
{ "content": "body { margin: 0; }" }
|
|
115
|
-
]
|
|
116
|
-
}
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
### PWA Configuration
|
|
120
|
-
```json
|
|
121
|
-
{
|
|
122
|
-
"page": "./src/client/ssr/body/App.js",
|
|
123
|
-
"outputPath": "./dist/index.html",
|
|
124
|
-
"metadata": {
|
|
125
|
-
"title": "My PWA",
|
|
126
|
-
"themeColor": "#007bff"
|
|
127
|
-
},
|
|
128
|
-
"icons": {
|
|
129
|
-
"favicon": "/favicon.ico",
|
|
130
|
-
"appleTouchIcon": "/apple-touch-icon.png",
|
|
131
|
-
"manifest": "/manifest.json"
|
|
132
|
-
},
|
|
133
|
-
"scripts": {
|
|
134
|
-
"body": [
|
|
135
|
-
{
|
|
136
|
-
"content": "if('serviceWorker' in navigator){navigator.serviceWorker.register('/sw.js');}"
|
|
137
|
-
}
|
|
138
|
-
]
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
### With Structured Data
|
|
144
|
-
```json
|
|
145
|
-
{
|
|
146
|
-
"page": "./src/client/ssr/body/Page.js",
|
|
147
|
-
"outputPath": "./dist/index.html",
|
|
148
|
-
"metadata": {
|
|
149
|
-
"title": "My Product"
|
|
150
|
-
},
|
|
151
|
-
"microdata": [
|
|
152
|
-
{
|
|
153
|
-
"@context": "https://schema.org",
|
|
154
|
-
"@type": "Product",
|
|
155
|
-
"name": "My Product",
|
|
156
|
-
"offers": {
|
|
157
|
-
"@type": "Offer",
|
|
158
|
-
"price": "29.99",
|
|
159
|
-
"priceCurrency": "USD"
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
]
|
|
163
|
-
}
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
## SSR Component Patterns
|
|
167
|
-
|
|
168
|
-
### Basic Component
|
|
169
|
-
```javascript
|
|
170
|
-
SrrComponent = () => html`
|
|
171
|
-
<div class="page">
|
|
172
|
-
<h1>Hello World</h1>
|
|
173
|
-
</div>
|
|
174
|
-
`;
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
### Component with Styles
|
|
178
|
-
```javascript
|
|
179
|
-
SrrComponent = () => html`
|
|
180
|
-
<div class="custom-page">
|
|
181
|
-
<h1>Styled Page</h1>
|
|
182
|
-
</div>
|
|
183
|
-
<style>
|
|
184
|
-
.custom-page {
|
|
185
|
-
padding: 20px;
|
|
186
|
-
}
|
|
187
|
-
</style>
|
|
188
|
-
`;
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
### Component with JavaScript
|
|
192
|
-
```javascript
|
|
193
|
-
SrrComponent = () => html`
|
|
194
|
-
<div class="interactive-page">
|
|
195
|
-
<button id="myButton">Click Me</button>
|
|
196
|
-
</div>
|
|
197
|
-
<script>
|
|
198
|
-
document.addEventListener('DOMContentLoaded', function() {
|
|
199
|
-
document.getElementById('myButton').addEventListener('click', function() {
|
|
200
|
-
alert('Button clicked!');
|
|
201
|
-
});
|
|
202
|
-
});
|
|
203
|
-
</script>
|
|
204
|
-
`;
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
### Component with Dynamic Content
|
|
208
|
-
```javascript
|
|
209
|
-
SrrComponent = () => html`
|
|
210
|
-
<div class="dynamic-page">
|
|
211
|
-
<h1>Current Year: ${new Date().getFullYear()}</h1>
|
|
212
|
-
<p>Generated at: ${new Date().toISOString()}</p>
|
|
213
|
-
</div>
|
|
214
|
-
`;
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
## Environment-Specific Builds
|
|
218
|
-
|
|
219
|
-
### Development
|
|
220
|
-
```bash
|
|
221
|
-
underpost static \
|
|
222
|
-
--config-file ./config.json \
|
|
223
|
-
--env development \
|
|
224
|
-
--no-minify \
|
|
225
|
-
--dev
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
### Production
|
|
229
|
-
```bash
|
|
230
|
-
underpost static \
|
|
231
|
-
--config-file ./config.json \
|
|
232
|
-
--env production \
|
|
233
|
-
--minify
|
|
234
|
-
```
|
|
235
|
-
|
|
236
|
-
## Multi-Page Generation
|
|
237
|
-
|
|
238
|
-
### Using Script (Bash)
|
|
239
|
-
```bash
|
|
240
|
-
#!/bin/bash
|
|
241
|
-
PAGES=("home" "about" "contact")
|
|
242
|
-
|
|
243
|
-
for page in "${PAGES[@]}"; do
|
|
244
|
-
underpost static \
|
|
245
|
-
--page "./src/client/ssr/body/${page}.js" \
|
|
246
|
-
--output-path "./dist/${page}.html" \
|
|
247
|
-
--title "$(echo $page | sed 's/.*/\u&/')"
|
|
248
|
-
done
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
### Using JavaScript
|
|
252
|
-
```javascript
|
|
253
|
-
import UnderpostStatic from './src/cli/static.js';
|
|
254
|
-
|
|
255
|
-
const pages = ['home', 'about', 'contact'];
|
|
256
|
-
|
|
257
|
-
for (const page of pages) {
|
|
258
|
-
await UnderpostStatic.API.callback({
|
|
259
|
-
page: `./src/client/ssr/body/${page}.js`,
|
|
260
|
-
outputPath: `./dist/${page}.html`,
|
|
261
|
-
metadata: {
|
|
262
|
-
title: page.charAt(0).toUpperCase() + page.slice(1)
|
|
263
|
-
}
|
|
264
|
-
});
|
|
265
|
-
}
|
|
266
|
-
```
|
|
267
|
-
|
|
268
|
-
## Metadata Patterns
|
|
269
|
-
|
|
270
|
-
### Blog Post
|
|
271
|
-
```json
|
|
272
|
-
{
|
|
273
|
-
"metadata": {
|
|
274
|
-
"title": "How to Build Static Sites",
|
|
275
|
-
"description": "A comprehensive guide to building static sites",
|
|
276
|
-
"keywords": ["static", "guide", "tutorial"],
|
|
277
|
-
"author": "Jane Doe"
|
|
278
|
-
},
|
|
279
|
-
"microdata": [
|
|
280
|
-
{
|
|
281
|
-
"@context": "https://schema.org",
|
|
282
|
-
"@type": "BlogPosting",
|
|
283
|
-
"headline": "How to Build Static Sites",
|
|
284
|
-
"author": { "@type": "Person", "name": "Jane Doe" },
|
|
285
|
-
"datePublished": "2024-01-01"
|
|
286
|
-
}
|
|
287
|
-
]
|
|
288
|
-
}
|
|
289
|
-
```
|
|
290
|
-
|
|
291
|
-
### E-commerce Product
|
|
292
|
-
```json
|
|
293
|
-
{
|
|
294
|
-
"metadata": {
|
|
295
|
-
"title": "Premium Widget - $29.99",
|
|
296
|
-
"description": "High-quality widget with free shipping",
|
|
297
|
-
"thumbnail": "https://shop.com/widget.jpg"
|
|
298
|
-
},
|
|
299
|
-
"microdata": [
|
|
300
|
-
{
|
|
301
|
-
"@context": "https://schema.org",
|
|
302
|
-
"@type": "Product",
|
|
303
|
-
"name": "Premium Widget",
|
|
304
|
-
"image": "https://shop.com/widget.jpg",
|
|
305
|
-
"offers": {
|
|
306
|
-
"@type": "Offer",
|
|
307
|
-
"price": "29.99",
|
|
308
|
-
"priceCurrency": "USD",
|
|
309
|
-
"availability": "https://schema.org/InStock"
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
]
|
|
313
|
-
}
|
|
314
|
-
```
|
|
315
|
-
|
|
316
|
-
### Organization
|
|
317
|
-
```json
|
|
318
|
-
{
|
|
319
|
-
"microdata": [
|
|
320
|
-
{
|
|
321
|
-
"@context": "https://schema.org",
|
|
322
|
-
"@type": "Organization",
|
|
323
|
-
"name": "My Company",
|
|
324
|
-
"url": "https://example.com",
|
|
325
|
-
"logo": "https://example.com/logo.png",
|
|
326
|
-
"sameAs": [
|
|
327
|
-
"https://twitter.com/mycompany",
|
|
328
|
-
"https://linkedin.com/company/mycompany"
|
|
329
|
-
]
|
|
330
|
-
}
|
|
331
|
-
]
|
|
332
|
-
}
|
|
333
|
-
```
|
|
334
|
-
|
|
335
|
-
## JavaScript API Usage
|
|
336
|
-
|
|
337
|
-
### Basic Generation
|
|
338
|
-
```javascript
|
|
339
|
-
import UnderpostStatic from './src/cli/static.js';
|
|
340
|
-
|
|
341
|
-
await UnderpostStatic.API.callback({
|
|
342
|
-
page: './src/client/ssr/body/Page.js',
|
|
343
|
-
outputPath: './dist/index.html',
|
|
344
|
-
metadata: { title: 'My Page' }
|
|
345
|
-
});
|
|
346
|
-
```
|
|
347
|
-
|
|
348
|
-
### Using Template Helpers
|
|
349
|
-
```javascript
|
|
350
|
-
import { TemplateHelpers } from './src/cli/static.js';
|
|
351
|
-
|
|
352
|
-
const scriptTag = TemplateHelpers.createScriptTag({
|
|
353
|
-
src: '/app.js',
|
|
354
|
-
defer: true,
|
|
355
|
-
type: 'module'
|
|
356
|
-
});
|
|
357
|
-
|
|
358
|
-
const metaTags = TemplateHelpers.createMetaTags({
|
|
359
|
-
title: 'My Page',
|
|
360
|
-
description: 'Description',
|
|
361
|
-
keywords: ['key1', 'key2']
|
|
362
|
-
});
|
|
363
|
-
```
|
|
364
|
-
|
|
365
|
-
### Validation
|
|
366
|
-
```javascript
|
|
367
|
-
import { ConfigValidator } from './src/cli/static.js';
|
|
368
|
-
|
|
369
|
-
const result = ConfigValidator.validate(options);
|
|
370
|
-
if (!result.isValid) {
|
|
371
|
-
console.error('Errors:', result.errors);
|
|
372
|
-
}
|
|
373
|
-
```
|
|
374
|
-
|
|
375
|
-
## Common Script Snippets
|
|
376
|
-
|
|
377
|
-
### Google Analytics
|
|
378
|
-
```json
|
|
379
|
-
{
|
|
380
|
-
"scripts": {
|
|
381
|
-
"head": [
|
|
382
|
-
{
|
|
383
|
-
"src": "https://www.googletagmanager.com/gtag/js?id=GA_ID",
|
|
384
|
-
"async": true
|
|
385
|
-
},
|
|
386
|
-
{
|
|
387
|
-
"content": "window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments);}gtag('js',new Date());gtag('config','GA_ID');"
|
|
388
|
-
}
|
|
389
|
-
]
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
```
|
|
393
|
-
|
|
394
|
-
### Service Worker Registration
|
|
395
|
-
```json
|
|
396
|
-
{
|
|
397
|
-
"scripts": {
|
|
398
|
-
"body": [
|
|
399
|
-
{
|
|
400
|
-
"content": "if('serviceWorker' in navigator){navigator.serviceWorker.register('/sw.js').then(reg=>console.log('SW registered',reg)).catch(err=>console.log('SW error',err));}"
|
|
401
|
-
}
|
|
402
|
-
]
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
```
|
|
406
|
-
|
|
407
|
-
### App Configuration
|
|
408
|
-
```json
|
|
409
|
-
{
|
|
410
|
-
"scripts": {
|
|
411
|
-
"head": [
|
|
412
|
-
{
|
|
413
|
-
"content": "window.appConfig={apiUrl:'https://api.example.com',version:'1.0.0',features:{analytics:true,debugging:false}};"
|
|
414
|
-
}
|
|
415
|
-
]
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
```
|
|
419
|
-
|
|
420
|
-
## Troubleshooting Quick Fixes
|
|
421
|
-
|
|
422
|
-
### Component Not Found
|
|
423
|
-
```bash
|
|
424
|
-
# Check if file exists
|
|
425
|
-
ls -la ./src/client/ssr/body/Page.js
|
|
426
|
-
|
|
427
|
-
# Use absolute path from project root
|
|
428
|
-
underpost static --page ./src/client/ssr/body/Page.js --output-path ./dist/index.html
|
|
429
|
-
```
|
|
430
|
-
|
|
431
|
-
### Invalid JSON Config
|
|
432
|
-
```bash
|
|
433
|
-
# Validate JSON
|
|
434
|
-
cat config.json | python -m json.tool
|
|
435
|
-
|
|
436
|
-
# Or use jq
|
|
437
|
-
jq . config.json
|
|
438
|
-
```
|
|
439
|
-
|
|
440
|
-
### Output Directory Missing
|
|
441
|
-
```bash
|
|
442
|
-
# Create directory first
|
|
443
|
-
mkdir -p ./dist/pages
|
|
444
|
-
|
|
445
|
-
# Then run command
|
|
446
|
-
underpost static --config-file ./config.json
|
|
447
|
-
```
|
|
448
|
-
|
|
449
|
-
## Performance Optimization
|
|
450
|
-
|
|
451
|
-
### Minification
|
|
452
|
-
```json
|
|
453
|
-
{
|
|
454
|
-
"env": "production",
|
|
455
|
-
"minify": true
|
|
456
|
-
}
|
|
457
|
-
```
|
|
458
|
-
|
|
459
|
-
### Async Loading
|
|
460
|
-
```json
|
|
461
|
-
{
|
|
462
|
-
"scripts": {
|
|
463
|
-
"head": [
|
|
464
|
-
{ "src": "/analytics.js", "async": true }
|
|
465
|
-
],
|
|
466
|
-
"body": [
|
|
467
|
-
{ "src": "/app.js", "defer": true }
|
|
468
|
-
]
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
```
|
|
472
|
-
|
|
473
|
-
### Critical CSS
|
|
474
|
-
```json
|
|
475
|
-
{
|
|
476
|
-
"styles": [
|
|
477
|
-
{ "content": "body{margin:0;font-family:sans-serif}" },
|
|
478
|
-
{ "href": "/main.css" }
|
|
479
|
-
]
|
|
480
|
-
}
|
|
481
|
-
```
|