underpost 3.2.9 → 3.2.10
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/.github/workflows/npmpkg.ci.yml +1 -0
- package/.github/workflows/pwa-microservices-template-test.ci.yml +1 -1
- package/.github/workflows/release.cd.yml +1 -0
- package/.vscode/settings.json +10 -5
- package/CHANGELOG.md +122 -1
- package/CLI-HELP.md +22 -7
- package/README.md +37 -8
- package/bin/build.js +26 -9
- package/bin/deploy.js +20 -21
- package/bin/file.js +31 -13
- package/bin/index.js +2 -1
- package/bin/vs.js +1 -1
- package/bump.config.js +26 -0
- package/conf.js +20 -4
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +1 -1
- package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/dd-test-development/deployment.yaml +4 -2
- package/manifests/kind-config-dev.yaml +8 -0
- package/manifests/mongodb/pv-pvc.yaml +44 -8
- package/manifests/mongodb/statefulset.yaml +55 -68
- package/package.json +27 -12
- package/scripts/k3s-node-setup.sh +28 -9
- package/src/api/core/core.router.js +19 -14
- package/src/api/core/core.service.js +5 -5
- package/src/api/default/default.router.js +22 -18
- package/src/api/default/default.service.js +5 -5
- package/src/api/document/document.router.js +28 -23
- package/src/api/document/document.service.js +100 -23
- package/src/api/file/file.router.js +19 -13
- package/src/api/file/file.service.js +9 -7
- package/src/api/test/test.router.js +17 -12
- package/src/api/types.js +24 -0
- package/src/api/user/guest.service.js +5 -4
- package/src/api/user/user.router.js +297 -288
- package/src/api/user/user.service.js +100 -35
- package/src/cli/baremetal.js +20 -11
- package/src/cli/cluster.js +196 -55
- package/src/cli/db.js +59 -60
- package/src/cli/deploy.js +273 -159
- package/src/cli/fs.js +3 -1
- package/src/cli/index.js +16 -9
- package/src/cli/ipfs.js +4 -6
- package/src/cli/kubectl.js +4 -1
- package/src/cli/lxd.js +217 -135
- package/src/cli/release.js +289 -131
- package/src/cli/repository.js +58 -7
- package/src/cli/run.js +152 -25
- package/src/cli/test.js +9 -3
- package/src/client/Default.index.js +9 -3
- package/src/client/components/core/Auth.js +4 -0
- package/src/client/components/core/PanelForm.js +56 -52
- package/src/client/components/core/Worker.js +162 -363
- package/src/client/sw/core.sw.js +174 -112
- package/src/db/DataBaseProvider.js +120 -20
- package/src/db/mongo/MongoBootstrap.js +587 -0
- package/src/db/mongo/MongooseDB.js +126 -22
- package/src/index.js +1 -1
- package/src/runtime/express/Express.js +2 -2
- package/src/runtime/wp/Wp.js +8 -5
- package/src/server/auth.js +2 -2
- package/src/server/client-build-docs.js +1 -1
- package/src/server/client-build.js +94 -129
- package/src/server/conf.js +20 -65
- package/src/server/process.js +180 -19
- package/src/server/runtime.js +1 -1
- package/src/server/start.js +12 -4
- package/src/ws/IoInterface.js +16 -16
- package/src/ws/core/channels/core.ws.chat.js +11 -11
- package/src/ws/core/channels/core.ws.mailer.js +29 -29
- package/src/ws/core/channels/core.ws.stream.js +19 -19
- package/src/ws/core/core.ws.connection.js +8 -8
- package/src/ws/core/core.ws.server.js +6 -5
- package/src/ws/default/channels/default.ws.main.js +10 -10
- package/src/ws/default/default.ws.connection.js +4 -4
- package/src/ws/default/default.ws.server.js +4 -3
- package/src/client/ssr/email/DefaultRecoverEmail.js +0 -21
- package/src/client/ssr/email/DefaultVerifyEmail.js +0 -17
- /package/src/client/ssr/{offline → views}/Maintenance.js +0 -0
- /package/src/client/ssr/{offline → views}/NoNetworkConnection.js +0 -0
- /package/src/client/ssr/{pages → views}/Test.js +0 -0
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import mongoose from 'mongoose';
|
|
2
|
-
import { loggerFactory } from '../../server/logger.js';
|
|
3
2
|
import { getCapVariableName } from '../../client/components/core/CommonJs.js';
|
|
4
3
|
|
|
5
4
|
/**
|
|
@@ -8,7 +7,33 @@ import { getCapVariableName } from '../../client/components/core/CommonJs.js';
|
|
|
8
7
|
* @namespace MongooseDBService
|
|
9
8
|
*/
|
|
10
9
|
|
|
11
|
-
const
|
|
10
|
+
const MONGODB_SERVICE_NAME = 'mongodb-service';
|
|
11
|
+
const MONGODB_STATEFULSET_NAME = 'mongodb';
|
|
12
|
+
const MONGODB_DEFAULT_AUTH_SOURCE = 'admin';
|
|
13
|
+
const MONGODB_DEFAULT_REPLICA_SET = 'rs0';
|
|
14
|
+
const MONGODB_DEFAULT_REPLICA_COUNT = 3;
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Resolves MongoDB replica hosts from explicit input or StatefulSet defaults.
|
|
20
|
+
* @param {{hostList?: string, replicaCount?: number}} [options] - Host resolution options.
|
|
21
|
+
* @returns {Array<string>} Normalized host:port entries.
|
|
22
|
+
*/
|
|
23
|
+
const resolveMongoReplicaHosts = ({ hostList = '', replicaCount = MONGODB_DEFAULT_REPLICA_COUNT }) => {
|
|
24
|
+
if (hostList) {
|
|
25
|
+
return hostList
|
|
26
|
+
.split(',')
|
|
27
|
+
.map((host) => host.trim())
|
|
28
|
+
.filter(Boolean)
|
|
29
|
+
.map((host) => (host.includes(':') ? host : `${host}:27017`));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return Array.from(
|
|
33
|
+
{ length: replicaCount },
|
|
34
|
+
(_, index) => `${MONGODB_STATEFULSET_NAME}-${index}.${MONGODB_SERVICE_NAME}:27017`,
|
|
35
|
+
);
|
|
36
|
+
};
|
|
12
37
|
|
|
13
38
|
/**
|
|
14
39
|
* @class
|
|
@@ -23,35 +48,106 @@ const logger = loggerFactory(import.meta);
|
|
|
23
48
|
* 3. No built-in defaults — both `host` and `name` are required from the caller or environment.
|
|
24
49
|
*/
|
|
25
50
|
class MongooseDBService {
|
|
51
|
+
/**
|
|
52
|
+
* Normalizes Mongo host inputs into plain host:port entries.
|
|
53
|
+
* @param {Array<string>|string} hosts - Host input as list or comma-separated string.
|
|
54
|
+
* @returns {Array<string>} Normalized host:port entries.
|
|
55
|
+
*/
|
|
56
|
+
normalizeHosts(hosts) {
|
|
57
|
+
const hostEntries = Array.isArray(hosts) ? hosts : `${hosts || ''}`.split(',');
|
|
58
|
+
|
|
59
|
+
return hostEntries
|
|
60
|
+
.map((entry) => `${entry || ''}`.trim())
|
|
61
|
+
.filter(Boolean)
|
|
62
|
+
.map((entry) => entry.replace(/^mongodb:\/\//, '').replace(/\/.*$/, ''));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Normalizes connection config from object or legacy host/name signature.
|
|
67
|
+
* @param {object|string} configOrHost - Connection config object or host string.
|
|
68
|
+
* @param {string} [name] - Legacy DB name when using host string input.
|
|
69
|
+
* @returns {{authSource: string, dbName: string, hosts: Array<string>, password: string, replicaSet: string, user: string}} Normalized config.
|
|
70
|
+
*/
|
|
71
|
+
normalizeConfig(configOrHost, name) {
|
|
72
|
+
const config =
|
|
73
|
+
typeof configOrHost === 'object' && configOrHost !== null
|
|
74
|
+
? { ...configOrHost }
|
|
75
|
+
: { host: configOrHost, name };
|
|
76
|
+
|
|
77
|
+
const rawHosts = config.host || process.env.DB_HOST;
|
|
78
|
+
const hosts = this.normalizeHosts(rawHosts);
|
|
79
|
+
const dbName = config.name || process.env.DB_NAME;
|
|
80
|
+
|
|
81
|
+
if (!hosts.length || !dbName) {
|
|
82
|
+
const missing = [!hosts.length && 'host (db.host|DB_HOST)', !dbName && 'name (db.name|DB_NAME)']
|
|
83
|
+
.filter(Boolean)
|
|
84
|
+
.join(', ');
|
|
85
|
+
throw new Error(`MongooseDBService.connect: missing required parameter(s): ${missing}`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const user = config.user || process.env.DB_USER || '';
|
|
89
|
+
const password = config.password || process.env.DB_PASSWORD || '';
|
|
90
|
+
const replicaSet =
|
|
91
|
+
config.replicaSet || process.env.DB_REPLICA_SET || (hosts.length > 1 ? MONGODB_DEFAULT_REPLICA_SET : '');
|
|
92
|
+
const authSource =
|
|
93
|
+
config.authSource || process.env.DB_AUTH_SOURCE || (user ? MONGODB_DEFAULT_AUTH_SOURCE : '');
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
authSource,
|
|
97
|
+
dbName,
|
|
98
|
+
hosts,
|
|
99
|
+
password,
|
|
100
|
+
replicaSet,
|
|
101
|
+
user,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Builds a MongoDB URI from normalized config options.
|
|
107
|
+
* @param {object|string} configOrHost - Connection config object or host string.
|
|
108
|
+
* @param {string} [name] - Legacy DB name when using host string input.
|
|
109
|
+
* @returns {string} MongoDB connection URI.
|
|
110
|
+
*/
|
|
111
|
+
buildUri(configOrHost, name) {
|
|
112
|
+
const config = this.normalizeConfig(configOrHost, name);
|
|
113
|
+
const credentials = config.user && config.password
|
|
114
|
+
? `${encodeURIComponent(config.user)}:${encodeURIComponent(config.password)}@`
|
|
115
|
+
: '';
|
|
116
|
+
const query = new URLSearchParams();
|
|
117
|
+
|
|
118
|
+
if (config.replicaSet) query.set('replicaSet', config.replicaSet);
|
|
119
|
+
if (config.authSource) query.set('authSource', config.authSource);
|
|
120
|
+
|
|
121
|
+
return `mongodb://${credentials}${config.hosts.join(',')}/${config.dbName}${query.size ? `?${query.toString()}` : ''}`;
|
|
122
|
+
}
|
|
123
|
+
|
|
26
124
|
/**
|
|
27
125
|
* Establishes a Mongoose connection to the specified MongoDB instance.
|
|
28
126
|
*
|
|
29
127
|
* @async
|
|
30
|
-
* @param {string}
|
|
31
|
-
*
|
|
32
|
-
* @param {string} name - The database name.
|
|
33
|
-
*
|
|
128
|
+
* @param {object|string} configOrHost - Either a db config object or a legacy host string.
|
|
129
|
+
* @param {string} [configOrHost.host] - Legacy single host or comma-separated host list.
|
|
130
|
+
* @param {string} [configOrHost.name] - The database name.
|
|
131
|
+
* @param {string} [configOrHost.replicaSet] - The MongoDB replica set name.
|
|
132
|
+
* @param {string} [configOrHost.authSource] - The authentication database.
|
|
133
|
+
* @param {string} [configOrHost.user] - The MongoDB username.
|
|
134
|
+
* @param {string} [configOrHost.password] - The MongoDB password.
|
|
135
|
+
* @param {string} [name] - Legacy database name when a host string is passed.
|
|
34
136
|
* @returns {Promise<mongoose.Connection>} A promise that resolves to the established Mongoose connection object.
|
|
35
137
|
* @throws {Error} If neither the argument nor the corresponding environment variable supplies a value.
|
|
36
138
|
*/
|
|
37
|
-
async connect(
|
|
38
|
-
|
|
39
|
-
name = name || process.env.DB_NAME;
|
|
40
|
-
|
|
41
|
-
if (!host || !name) {
|
|
42
|
-
const missing = [!host && 'host (DB_HOST)', !name && 'name (DB_NAME)'].filter(Boolean).join(', ');
|
|
43
|
-
throw new Error(`MongooseDBService.connect: missing required parameter(s): ${missing}`);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const uri = `${host}/${name}`;
|
|
47
|
-
// logger.info('MongooseDB connect', { host, name, uri });
|
|
139
|
+
async connect(configOrHost, name) {
|
|
140
|
+
const uri = this.buildUri(configOrHost, name);
|
|
48
141
|
return await mongoose
|
|
49
142
|
.createConnection(uri, {
|
|
143
|
+
autoIndex: process.env.NODE_ENV !== 'production',
|
|
144
|
+
heartbeatFrequencyMS: 10000,
|
|
145
|
+
maxPoolSize: 20,
|
|
146
|
+
minPoolSize: 2,
|
|
147
|
+
retryReads: true,
|
|
148
|
+
retryWrites: true,
|
|
50
149
|
serverSelectionTimeoutMS: 5000,
|
|
51
|
-
|
|
52
|
-
// directConnection: true,
|
|
53
|
-
// useNewUrlParser: true,
|
|
54
|
-
// useUnifiedTopology: true,
|
|
150
|
+
socketTimeoutMS: 45000,
|
|
55
151
|
})
|
|
56
152
|
.asPromise();
|
|
57
153
|
}
|
|
@@ -87,4 +183,12 @@ class MongooseDBService {
|
|
|
87
183
|
*/
|
|
88
184
|
const MongooseDB = new MongooseDBService();
|
|
89
185
|
|
|
90
|
-
export {
|
|
186
|
+
export {
|
|
187
|
+
MongooseDB,
|
|
188
|
+
MongooseDBService as MongooseDBClass,
|
|
189
|
+
MONGODB_DEFAULT_REPLICA_COUNT,
|
|
190
|
+
MONGODB_DEFAULT_REPLICA_SET,
|
|
191
|
+
MONGODB_SERVICE_NAME,
|
|
192
|
+
MONGODB_STATEFULSET_NAME,
|
|
193
|
+
resolveMongoReplicaHosts
|
|
194
|
+
};
|
package/src/index.js
CHANGED
|
@@ -15,7 +15,7 @@ import { createServer } from 'http';
|
|
|
15
15
|
import { loggerFactory, loggerMiddleware } from '../../server/logger.js';
|
|
16
16
|
import { getCapVariableName, newInstance } from '../../client/components/core/CommonJs.js';
|
|
17
17
|
import { MailerProvider } from '../../mailer/MailerProvider.js';
|
|
18
|
-
import {
|
|
18
|
+
import { DataBaseProviderService } from '../../db/DataBaseProvider.js';
|
|
19
19
|
import { createPeerServer } from '../../server/peer.js';
|
|
20
20
|
import { createValkeyConnection } from '../../server/valkey.js';
|
|
21
21
|
import { applySecurity, authMiddlewareFactory } from '../../server/auth.js';
|
|
@@ -192,7 +192,7 @@ class ExpressService {
|
|
|
192
192
|
}
|
|
193
193
|
|
|
194
194
|
// Database and Valkey connections
|
|
195
|
-
if (db && apis) await
|
|
195
|
+
if (db && apis) await DataBaseProviderService.load({ apis, host, path, db });
|
|
196
196
|
|
|
197
197
|
if (valkey) await createValkeyConnection({ host, path }, valkey);
|
|
198
198
|
|
package/src/runtime/wp/Wp.js
CHANGED
|
@@ -56,10 +56,11 @@ class WpService {
|
|
|
56
56
|
* to `/usr/local/bin/wp` if it is not already present.
|
|
57
57
|
*/
|
|
58
58
|
static ensureWpCli() {
|
|
59
|
-
const existing = shellExec(`PATH="${LAMPP_BIN}:$PATH" which wp
|
|
59
|
+
const existing = shellExec(`PATH="${LAMPP_BIN}:$PATH" which wp`, {
|
|
60
60
|
stdout: true,
|
|
61
61
|
silent: true,
|
|
62
62
|
disableLog: true,
|
|
63
|
+
silentOnError: true,
|
|
63
64
|
});
|
|
64
65
|
if (existing && existing.trim()) return;
|
|
65
66
|
logger.info('WP-CLI not found — installing to /usr/local/bin/wp');
|
|
@@ -76,10 +77,11 @@ class WpService {
|
|
|
76
77
|
*/
|
|
77
78
|
static ensureSendmail() {
|
|
78
79
|
const sendmailPath = '/usr/sbin/sendmail';
|
|
79
|
-
const existing = shellExec(`test -x "${sendmailPath}" && echo ok
|
|
80
|
+
const existing = shellExec(`test -x "${sendmailPath}" && echo ok`, {
|
|
80
81
|
stdout: true,
|
|
81
82
|
silent: true,
|
|
82
83
|
disableLog: true,
|
|
84
|
+
silentOnError: true,
|
|
83
85
|
});
|
|
84
86
|
if (existing && existing.trim() === 'ok') return;
|
|
85
87
|
logger.info('sendmail stub missing — creating no-op at /usr/sbin/sendmail');
|
|
@@ -494,7 +496,7 @@ Thumbs.db
|
|
|
494
496
|
* `git clone` yields a fully working site without needing a fresh install.
|
|
495
497
|
*
|
|
496
498
|
* Safe to call repeatedly — `git commit` is a no-op when the working tree
|
|
497
|
-
* is clean (
|
|
499
|
+
* is clean (`silentOnError: true` swallows the non-zero exit gracefully).
|
|
498
500
|
*
|
|
499
501
|
* @param {object} opts
|
|
500
502
|
* @param {string} opts.siteRoot - Absolute path to the WordPress root.
|
|
@@ -514,7 +516,8 @@ Thumbs.db
|
|
|
514
516
|
|
|
515
517
|
logger.info(`${host}: persisting site to repository`);
|
|
516
518
|
shellExec(
|
|
517
|
-
`cd "${siteRoot}" && git add -A && git commit -m "wp provision ${host} $(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
519
|
+
`cd "${siteRoot}" && git add -A && git commit -m "wp provision ${host} $(date -u +%Y-%m-%dT%H:%M:%SZ)"`,
|
|
520
|
+
{ silentOnError: true },
|
|
518
521
|
);
|
|
519
522
|
shellExec(`cd "${siteRoot}" && underpost push . ${githubOrg}/${repoName} -f`);
|
|
520
523
|
logger.info(`${host}: initial commit pushed to ${githubOrg}/${repoName}`);
|
|
@@ -627,7 +630,7 @@ if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROT
|
|
|
627
630
|
|
|
628
631
|
// MariaDB export is handled by the shared db.js backup flow — no duplicate dump here.
|
|
629
632
|
if (fs.existsSync(path.join(siteRoot, '.git'))) {
|
|
630
|
-
shellExec(`cd "${siteRoot}" && git add -A && git commit -m "wp backup $(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
633
|
+
shellExec(`cd "${siteRoot}" && git add -A && git commit -m "wp backup $(date -u +%Y-%m-%dT%H:%M:%SZ)"`, { silentOnError: true });
|
|
631
634
|
shellExec(`cd "${siteRoot}" && underpost push . ${githubOrg}/${repository.split('/').pop().split('.')[0]}`);
|
|
632
635
|
logger.info(`backup: git push done for ${siteRoot}`);
|
|
633
636
|
} else {
|
package/src/server/auth.js
CHANGED
|
@@ -20,7 +20,7 @@ import rateLimit from 'express-rate-limit';
|
|
|
20
20
|
import slowDown from 'express-slow-down';
|
|
21
21
|
import cors from 'cors';
|
|
22
22
|
import cookieParser from 'cookie-parser';
|
|
23
|
-
import {
|
|
23
|
+
import { DataBaseProviderService } from '../db/DataBaseProvider.js';
|
|
24
24
|
import { isDevProxyContext } from './conf.js';
|
|
25
25
|
|
|
26
26
|
const logger = loggerFactory(import.meta);
|
|
@@ -229,7 +229,7 @@ const authMiddlewareFactory = (options = { host: '', path: '' }) => {
|
|
|
229
229
|
|
|
230
230
|
// Non-guest verify session exists
|
|
231
231
|
if (payload.jwtid && payload.role !== 'guest') {
|
|
232
|
-
const User =
|
|
232
|
+
const User = DataBaseProviderService.getModel('user', { host: payload.host, path: payload.path });
|
|
233
233
|
const user = await User.findOne({ _id: payload._id, 'activeSessions._id': payload.jwtid }).lean();
|
|
234
234
|
|
|
235
235
|
if (!user) {
|
|
@@ -399,7 +399,7 @@ const buildCoverage = async ({ docs, docsDestination }) => {
|
|
|
399
399
|
shellExec(`cd ${coveragePath} && npm run coverage`, { silent: true });
|
|
400
400
|
} else if (pkg.scripts && pkg.scripts.test) {
|
|
401
401
|
logger.info('generating coverage via test', coveragePath);
|
|
402
|
-
shellExec(`cd ${coveragePath} && npm test`, { silent: true });
|
|
402
|
+
shellExec(`cd ${coveragePath} && npm test`, { silent: true, silentOnError: true });
|
|
403
403
|
}
|
|
404
404
|
}
|
|
405
405
|
}
|
|
@@ -724,23 +724,24 @@ const buildClient = async (
|
|
|
724
724
|
const ssrPath = path === '/' ? path : `${path}/`;
|
|
725
725
|
const Render = await ssrFactory();
|
|
726
726
|
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
727
|
+
const swSrcPath = `./src/client/sw/core.sw.js`;
|
|
728
|
+
const swPublicPath = `${rootClientPath}/sw.js`;
|
|
729
|
+
const swShouldRebuild =
|
|
730
|
+
views && !(enableLiveRebuild && !options.liveClientBuildPaths.find((p) => p.srcBuildPath === swSrcPath));
|
|
731
|
+
// Transformed SW JS is held in memory; it gets prepended with renderPayload
|
|
732
|
+
// and written once below, after PRE_CACHED_RESOURCES are known.
|
|
733
|
+
let swTransformedJs = '';
|
|
734
|
+
if (swShouldRebuild) {
|
|
735
|
+
swTransformedJs = await transformClientJs(swSrcPath, {
|
|
736
|
+
dists,
|
|
737
|
+
proxyPath: path,
|
|
738
|
+
baseHost,
|
|
739
|
+
minify: minifyBuild,
|
|
740
|
+
externalizeBareImports: false,
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
743
|
|
|
744
|
+
if (views) {
|
|
744
745
|
if (
|
|
745
746
|
!(
|
|
746
747
|
enableLiveRebuild &&
|
|
@@ -750,9 +751,8 @@ const buildClient = async (
|
|
|
750
751
|
)
|
|
751
752
|
)
|
|
752
753
|
for (const view of views) {
|
|
753
|
-
const buildPath = `${
|
|
754
|
-
|
|
755
|
-
}${view.path === '/' ? view.path : `${view.path}/`}`;
|
|
754
|
+
const buildPath = `${rootClientPath[rootClientPath.length - 1] === '/' ? rootClientPath.slice(0, -1) : rootClientPath
|
|
755
|
+
}${view.path === '/' ? view.path : `${view.path}/`}`;
|
|
756
756
|
|
|
757
757
|
if (!fs.existsSync(buildPath)) fs.mkdirSync(buildPath, { recursive: true });
|
|
758
758
|
|
|
@@ -768,9 +768,8 @@ const buildClient = async (
|
|
|
768
768
|
fs.writeFileSync(`${buildPath}${buildId}.js`, jsSrc, 'utf8');
|
|
769
769
|
const title = metadata.title ? metadata.title : title;
|
|
770
770
|
|
|
771
|
-
const canonicalURL = `https://${host}${path}${
|
|
772
|
-
|
|
773
|
-
}`;
|
|
771
|
+
const canonicalURL = `https://${host}${path}${view.path === '/' ? (path === '/' ? '' : '/') : path === '/' ? `${view.path.slice(1)}/` : `${view.path}/`
|
|
772
|
+
}`;
|
|
774
773
|
|
|
775
774
|
let ssrHeadComponents = ``;
|
|
776
775
|
let ssrBodyComponents = ``;
|
|
@@ -904,12 +903,12 @@ const buildClient = async (
|
|
|
904
903
|
`${buildPath}index.html`,
|
|
905
904
|
minifyBuild
|
|
906
905
|
? await minify(htmlSrc, {
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
906
|
+
minifyCSS: true,
|
|
907
|
+
minifyJS: true,
|
|
908
|
+
collapseBooleanAttributes: true,
|
|
909
|
+
collapseInlineTagWhitespace: true,
|
|
910
|
+
collapseWhitespace: true,
|
|
911
|
+
})
|
|
913
912
|
: htmlSrc,
|
|
914
913
|
'utf8',
|
|
915
914
|
);
|
|
@@ -967,119 +966,85 @@ Sitemap: ${sitemapBaseUrl}/sitemap.xml`,
|
|
|
967
966
|
}
|
|
968
967
|
|
|
969
968
|
if (client) {
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
...(isDevelopment ? { dev: true } : undefined),
|
|
1001
|
-
},
|
|
1002
|
-
renderApi: {
|
|
1003
|
-
JSONweb,
|
|
1004
|
-
},
|
|
1005
|
-
});
|
|
1006
|
-
|
|
1007
|
-
const buildPath = `${
|
|
1008
|
-
rootClientPath[rootClientPath.length - 1] === '/' ? rootClientPath.slice(0, -1) : rootClientPath
|
|
1009
|
-
}${page.path === '/' ? page.path : `${page.path}/`}`;
|
|
1010
|
-
|
|
1011
|
-
// Install-time precache is intentionally restricted to SSR offline pages.
|
|
1012
|
-
// All other routes/assets are loaded lazily at runtime.
|
|
1013
|
-
if (pageType === 'offline') {
|
|
1014
|
-
PRE_CACHED_RESOURCES.push(toPrecacheIndexUrl(page.path));
|
|
1015
|
-
}
|
|
1016
|
-
|
|
1017
|
-
if (!fs.existsSync(buildPath)) fs.mkdirSync(buildPath, { recursive: true });
|
|
1018
|
-
|
|
1019
|
-
const buildHtmlPath = `${buildPath}index.html`;
|
|
1020
|
-
|
|
1021
|
-
logger.info('ssr page build', buildHtmlPath);
|
|
1022
|
-
|
|
1023
|
-
fs.writeFileSync(
|
|
1024
|
-
buildHtmlPath,
|
|
1025
|
-
minifyBuild
|
|
1026
|
-
? await minify(htmlSrc, {
|
|
1027
|
-
minifyCSS: true,
|
|
1028
|
-
minifyJS: true,
|
|
1029
|
-
collapseBooleanAttributes: true,
|
|
1030
|
-
collapseInlineTagWhitespace: true,
|
|
1031
|
-
collapseWhitespace: true,
|
|
1032
|
-
})
|
|
1033
|
-
: htmlSrc,
|
|
1034
|
-
'utf8',
|
|
1035
|
-
);
|
|
1036
|
-
}
|
|
1037
|
-
}
|
|
1038
|
-
}
|
|
969
|
+
const proxyPrefix = path === '/' ? '' : path;
|
|
970
|
+
const buildIndexUrl = (routePath) => `${proxyPrefix}${routePath === '/' ? '' : routePath}/index.html`;
|
|
971
|
+
|
|
972
|
+
// SSR views: a single declarative array. The role of each view (regular
|
|
973
|
+
// page vs. offline/maintenance fallback) is expressed by per-entry flags;
|
|
974
|
+
// fallback-flagged views are also precached so the SW can serve them
|
|
975
|
+
// when the network is unreachable.
|
|
976
|
+
const ssrClientConf = confSSR[getCapVariableName(client)] || {};
|
|
977
|
+
const ssrViews = Array.isArray(ssrClientConf.views) ? ssrClientConf.views : [];
|
|
978
|
+
const PRE_CACHED_RESOURCES = [];
|
|
979
|
+
let offlineFallbackUrl = null;
|
|
980
|
+
let maintenanceFallbackUrl = null;
|
|
981
|
+
|
|
982
|
+
for (const view of ssrViews) {
|
|
983
|
+
const SsrComponent = await ssrFactory(`./src/client/ssr/views/${view.client}.js`);
|
|
984
|
+
|
|
985
|
+
const htmlSrc = Render({
|
|
986
|
+
title: view.title,
|
|
987
|
+
ssrPath,
|
|
988
|
+
ssrHeadComponents: '<base target="_top">',
|
|
989
|
+
ssrBodyComponents: SsrComponent(),
|
|
990
|
+
renderPayload: {
|
|
991
|
+
apiBaseProxyPath,
|
|
992
|
+
apiBaseHost,
|
|
993
|
+
apiBasePath: process.env.BASE_API,
|
|
994
|
+
version: Underpost.version,
|
|
995
|
+
...(isDevelopment ? { dev: true } : undefined),
|
|
996
|
+
},
|
|
997
|
+
renderApi: { JSONweb },
|
|
998
|
+
});
|
|
1039
999
|
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
const ssrClientConf = confSSR[getCapVariableName(client)] || {};
|
|
1043
|
-
const ssrOfflinePages = Array.isArray(ssrClientConf.offline) ? ssrClientConf.offline : [];
|
|
1044
|
-
const normalizeSsrRoutePath = (candidatePath, fallbackPath) => {
|
|
1045
|
-
const value =
|
|
1046
|
-
typeof candidatePath === 'string' && candidatePath.trim().length > 0
|
|
1047
|
-
? candidatePath.trim()
|
|
1048
|
-
: fallbackPath;
|
|
1049
|
-
const withLeadingSlash = value.startsWith('/') ? value : `/${value}`;
|
|
1050
|
-
const withoutTrailingSlash = withLeadingSlash.replace(/\/+$/, '');
|
|
1051
|
-
return withoutTrailingSlash.length > 0 ? withoutTrailingSlash : '/';
|
|
1052
|
-
};
|
|
1000
|
+
const buildPath = `${rootClientPath[rootClientPath.length - 1] === '/' ? rootClientPath.slice(0, -1) : rootClientPath
|
|
1001
|
+
}${view.path === '/' ? view.path : `${view.path}/`}`;
|
|
1053
1002
|
|
|
1054
|
-
const
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1003
|
+
const indexUrl = buildIndexUrl(view.path);
|
|
1004
|
+
if (view.offlineDefault) {
|
|
1005
|
+
offlineFallbackUrl = indexUrl;
|
|
1006
|
+
PRE_CACHED_RESOURCES.push(indexUrl);
|
|
1007
|
+
}
|
|
1008
|
+
if (view.maintenanceDefault) {
|
|
1009
|
+
maintenanceFallbackUrl = indexUrl;
|
|
1010
|
+
PRE_CACHED_RESOURCES.push(indexUrl);
|
|
1011
|
+
}
|
|
1060
1012
|
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
page?.client === 'Maintenance' ||
|
|
1065
|
-
/maintenance/i.test(`${page?.title || ''} ${page?.client || ''} ${page?.path || ''}`),
|
|
1066
|
-
) || ssrOfflinePages[1];
|
|
1013
|
+
if (!fs.existsSync(buildPath)) fs.mkdirSync(buildPath, { recursive: true });
|
|
1014
|
+
const buildHtmlPath = `${buildPath}index.html`;
|
|
1015
|
+
logger.info('ssr view build', buildHtmlPath);
|
|
1067
1016
|
|
|
1068
|
-
|
|
1069
|
-
|
|
1017
|
+
fs.writeFileSync(
|
|
1018
|
+
buildHtmlPath,
|
|
1019
|
+
minifyBuild
|
|
1020
|
+
? await minify(htmlSrc, {
|
|
1021
|
+
minifyCSS: true,
|
|
1022
|
+
minifyJS: true,
|
|
1023
|
+
collapseBooleanAttributes: true,
|
|
1024
|
+
collapseInlineTagWhitespace: true,
|
|
1025
|
+
collapseWhitespace: true,
|
|
1026
|
+
})
|
|
1027
|
+
: htmlSrc,
|
|
1028
|
+
'utf8',
|
|
1029
|
+
);
|
|
1030
|
+
}
|
|
1070
1031
|
|
|
1032
|
+
if (swShouldRebuild) {
|
|
1033
|
+
const cacheScope = path === '/' ? 'root' : path.replaceAll('/', '_');
|
|
1071
1034
|
const renderPayload = {
|
|
1072
1035
|
PRE_CACHED_RESOURCES: uniqueArray(PRE_CACHED_RESOURCES),
|
|
1073
1036
|
PROXY_PATH: path,
|
|
1074
|
-
CACHE_PREFIX: `engine-core
|
|
1075
|
-
|
|
1076
|
-
|
|
1037
|
+
CACHE_PREFIX: `engine-core-${cacheScope}`,
|
|
1038
|
+
OFFLINE_URL: offlineFallbackUrl || buildIndexUrl('/offline'),
|
|
1039
|
+
MAINTENANCE_URL: maintenanceFallbackUrl || buildIndexUrl('/maintenance'),
|
|
1077
1040
|
};
|
|
1041
|
+
|
|
1042
|
+
// Single write: prepend the payload prelude to the transformed SW JS.
|
|
1078
1043
|
fs.writeFileSync(
|
|
1079
|
-
|
|
1044
|
+
swPublicPath,
|
|
1080
1045
|
`self.renderPayload = ${JSONweb(renderPayload)};
|
|
1081
1046
|
self.__WB_DISABLE_DEV_LOGS = true;
|
|
1082
|
-
${
|
|
1047
|
+
${swTransformedJs}`,
|
|
1083
1048
|
'utf8',
|
|
1084
1049
|
);
|
|
1085
1050
|
}
|