underpost 3.2.4 → 3.2.8
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/release.cd.yml +1 -2
- package/CHANGELOG.md +268 -1
- package/CLI-HELP.md +26 -13
- package/Dockerfile +0 -4
- package/README.md +3 -3
- package/bin/build.js +13 -3
- package/bin/deploy.js +570 -1
- package/bin/file.js +5 -0
- package/conf.js +11 -2
- package/jsconfig.json +1 -1
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +2 -3
- package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +2 -3
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -6
- package/manifests/deployment/dd-test-development/deployment.yaml +136 -66
- package/manifests/deployment/dd-test-development/proxy.yaml +41 -5
- package/package.json +20 -11
- package/src/api/core/core.controller.js +10 -10
- package/src/api/core/core.service.js +10 -10
- package/src/api/default/default.controller.js +10 -10
- package/src/api/default/default.service.js +10 -10
- package/src/api/document/document.controller.js +12 -12
- package/src/api/document/document.model.js +10 -16
- package/src/api/file/file.controller.js +8 -8
- package/src/api/file/file.model.js +10 -10
- package/src/api/file/file.service.js +36 -36
- package/src/api/test/test.controller.js +8 -8
- package/src/api/test/test.service.js +8 -8
- package/src/api/user/guest.service.js +99 -0
- package/src/api/user/user.controller.js +6 -6
- package/src/api/user/user.model.js +8 -13
- package/src/api/user/user.service.js +3 -20
- package/src/cli/deploy.js +33 -30
- package/src/cli/fs.js +62 -5
- package/src/cli/image.js +43 -1
- package/src/cli/index.js +5 -1
- package/src/cli/release.js +58 -2
- package/src/cli/repository.js +35 -3
- package/src/cli/run.js +304 -38
- package/src/cli/ssh.js +1 -1
- package/src/cli/static.js +43 -115
- package/src/client/Default.index.js +21 -33
- package/src/client/components/core/404.js +4 -4
- package/src/client/components/core/500.js +4 -4
- package/src/client/components/core/Account.js +73 -60
- package/src/client/components/core/AgGrid.js +23 -33
- package/src/client/components/core/Alert.js +12 -13
- package/src/client/components/core/AppStore.js +1 -1
- package/src/client/components/core/Auth.js +20 -32
- package/src/client/components/core/Badge.js +7 -13
- package/src/client/components/core/BtnIcon.js +15 -17
- package/src/client/components/core/CalendarCore.js +42 -63
- package/src/client/components/core/Chat.js +13 -15
- package/src/client/components/core/ClientEvents.js +87 -0
- package/src/client/components/core/ColorPaletteElement.js +309 -0
- package/src/client/components/core/Content.js +17 -14
- package/src/client/components/core/Css.js +15 -71
- package/src/client/components/core/CssCore.js +12 -16
- package/src/client/components/core/D3Chart.js +4 -4
- package/src/client/components/core/Docs.js +60 -59
- package/src/client/components/core/DropDown.js +69 -91
- package/src/client/components/core/EventBus.js +92 -0
- package/src/client/components/core/EventsUI.js +14 -17
- package/src/client/components/core/FileExplorer.js +102 -234
- package/src/client/components/core/FullScreen.js +47 -75
- package/src/client/components/core/Input.js +24 -69
- package/src/client/components/core/Keyboard.js +25 -18
- package/src/client/components/core/KeyboardAvoidance.js +145 -0
- package/src/client/components/core/LoadingAnimation.js +25 -31
- package/src/client/components/core/LogIn.js +41 -41
- package/src/client/components/core/LogOut.js +23 -14
- package/src/client/components/core/Modal.js +397 -176
- package/src/client/components/core/NotificationManager.js +14 -18
- package/src/client/components/core/Panel.js +54 -50
- package/src/client/components/core/PanelForm.js +25 -125
- package/src/client/components/core/Polyhedron.js +110 -214
- package/src/client/components/core/PublicProfile.js +39 -32
- package/src/client/components/core/Recover.js +52 -48
- package/src/client/components/core/Responsive.js +88 -32
- package/src/client/components/core/RichText.js +9 -18
- package/src/client/components/core/Router.js +24 -3
- package/src/client/components/core/SearchBox.js +37 -37
- package/src/client/components/core/SignUp.js +39 -30
- package/src/client/components/core/SocketIo.js +31 -2
- package/src/client/components/core/SocketIoHandler.js +6 -6
- package/src/client/components/core/ToggleSwitch.js +8 -20
- package/src/client/components/core/ToolTip.js +5 -17
- package/src/client/components/core/Translate.js +56 -59
- package/src/client/components/core/Validator.js +26 -16
- package/src/client/components/core/Wallet.js +15 -26
- package/src/client/components/core/Worker.js +140 -25
- package/src/client/components/core/windowGetDimensions.js +7 -7
- package/src/client/components/default/{MenuDefault.js → AppShellDefault.js} +87 -87
- package/src/client/components/default/CssDefault.js +12 -12
- package/src/client/components/default/LogInDefault.js +6 -4
- package/src/client/components/default/LogOutDefault.js +6 -4
- package/src/client/components/default/RouterDefault.js +47 -0
- package/src/client/components/default/SettingsDefault.js +4 -4
- package/src/client/components/default/SignUpDefault.js +6 -4
- package/src/client/components/default/TranslateDefault.js +3 -3
- package/src/client/services/core/core.service.js +17 -49
- package/src/client/services/default/default.management.js +139 -242
- package/src/client/services/default/default.service.js +10 -16
- package/src/client/services/document/document.service.js +14 -19
- package/src/client/services/file/file.service.js +8 -13
- package/src/client/services/test/test.service.js +8 -13
- package/src/client/services/user/guest.service.js +79 -0
- package/src/client/services/user/user.management.js +5 -5
- package/src/client/services/user/user.service.js +14 -20
- package/src/client/ssr/body/404.js +3 -3
- package/src/client/ssr/body/500.js +3 -3
- package/src/client/ssr/body/CacheControl.js +5 -2
- package/src/client/ssr/body/DefaultSplashScreen.js +19 -12
- package/src/client/ssr/mailer/DefaultRecoverEmail.js +19 -20
- package/src/client/ssr/mailer/DefaultVerifyEmail.js +15 -16
- package/src/client/ssr/offline/Maintenance.js +12 -11
- package/src/client/ssr/offline/NoNetworkConnection.js +3 -3
- package/src/client/ssr/pages/Test.js +2 -2
- package/src/client/sw/core.sw.js +212 -0
- package/src/index.js +1 -1
- package/src/runtime/express/Dockerfile +4 -4
- package/src/runtime/lampp/Dockerfile +8 -7
- package/src/runtime/wp/Dockerfile +11 -17
- package/src/server/backup.js +1 -2
- package/src/server/client-build-docs.js +45 -46
- package/src/server/client-build.js +334 -60
- package/src/server/client-formatted.js +47 -16
- package/src/server/conf.js +29 -13
- package/src/server/cron.js +6 -8
- package/src/server/dns.js +2 -1
- package/src/server/ipfs-client.js +232 -91
- package/src/server/process.js +13 -27
- package/src/server/start.js +6 -3
- package/src/server/valkey.js +134 -235
- package/tsconfig.docs.json +15 -0
- package/typedoc.json +20 -0
- package/jsdoc.json +0 -52
- package/src/client/components/core/ColorPalette.js +0 -5267
- package/src/client/components/core/JoyStick.js +0 -80
- package/src/client/components/default/RoutesDefault.js +0 -49
- package/src/client/sw/default.sw.js +0 -127
- package/src/client/sw/template.sw.js +0 -84
|
@@ -38,6 +38,8 @@ import { ssrFactory } from './ssr.js';
|
|
|
38
38
|
* @memberof clientBuild
|
|
39
39
|
*/
|
|
40
40
|
const copyNonExistingFiles = (src, dest) => {
|
|
41
|
+
if (dir.basename(src) === '.git') return;
|
|
42
|
+
|
|
41
43
|
// Ensure source exists
|
|
42
44
|
if (!fs.existsSync(src)) {
|
|
43
45
|
throw new Error(`Source directory does not exist: ${src}`);
|
|
@@ -74,6 +76,224 @@ const copyNonExistingFiles = (src, dest) => {
|
|
|
74
76
|
}
|
|
75
77
|
};
|
|
76
78
|
|
|
79
|
+
const splitFileByMb = ({ filePath, partSizeMb, logger }) => {
|
|
80
|
+
const partSizeBytes = Math.floor(Number(partSizeMb) * 1024 * 1024);
|
|
81
|
+
if (!Number.isFinite(partSizeBytes) || partSizeBytes <= 0) {
|
|
82
|
+
throw new Error(`Invalid --split value: ${partSizeMb}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Clean ALL stale part files (any naming variant) before writing new ones
|
|
86
|
+
const zipDir = dir.dirname(filePath);
|
|
87
|
+
const zipBase = dir.basename(filePath);
|
|
88
|
+
if (fs.existsSync(zipDir)) {
|
|
89
|
+
fs.readdirSync(zipDir)
|
|
90
|
+
.filter((name) => name.startsWith(`${zipBase}.part`) || name.startsWith(`${zipBase}-part`))
|
|
91
|
+
.forEach((name) => fs.removeSync(dir.join(zipDir, name)));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const fileBuffer = fs.readFileSync(filePath);
|
|
95
|
+
const partPaths = [];
|
|
96
|
+
|
|
97
|
+
for (let offset = 0, partIndex = 0; offset < fileBuffer.length; offset += partSizeBytes, partIndex++) {
|
|
98
|
+
const partBuffer = fileBuffer.subarray(offset, offset + partSizeBytes);
|
|
99
|
+
const partPath = `${filePath}.part${String(partIndex + 1).padStart(3, '0')}`;
|
|
100
|
+
fs.writeFileSync(partPath, partBuffer);
|
|
101
|
+
partPaths.push(partPath);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
logger.warn('split zip', {
|
|
105
|
+
filePath,
|
|
106
|
+
partSizeMb: Number(partSizeMb),
|
|
107
|
+
parts: partPaths.length,
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
return partPaths;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const getZipPartPaths = (zipPath) => {
|
|
114
|
+
const zipDir = dir.dirname(zipPath);
|
|
115
|
+
const zipBase = dir.basename(zipPath);
|
|
116
|
+
const partPrefixDot = `${zipBase}.part`;
|
|
117
|
+
const partPrefixDash = `${zipBase}-part`;
|
|
118
|
+
|
|
119
|
+
const parsePartIndex = (rawSuffix) => {
|
|
120
|
+
// Strip optional .zip suffix added by pull/download (e.g. '001.zip' → '001')
|
|
121
|
+
const digits = rawSuffix.replace(/\.zip$/i, '');
|
|
122
|
+
return /^\d+$/.test(digits) ? Number(digits) : NaN;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const getPartIndex = (name) => {
|
|
126
|
+
if (name.startsWith(partPrefixDot)) return parsePartIndex(name.slice(partPrefixDot.length));
|
|
127
|
+
if (name.startsWith(partPrefixDash)) return parsePartIndex(name.slice(partPrefixDash.length));
|
|
128
|
+
return NaN;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
return fs
|
|
132
|
+
.readdirSync(zipDir)
|
|
133
|
+
.filter((name) => Number.isFinite(getPartIndex(name)))
|
|
134
|
+
.sort((a, b) => getPartIndex(a) - getPartIndex(b))
|
|
135
|
+
.map((name) => dir.join(zipDir, name));
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const resolveClientBuildZip = (buildPrefix) => {
|
|
139
|
+
const normalizedPrefix = buildPrefix.replace(/\.zip(?:[.-]part\d+|[.-]part\*)?$/, '').replace(/[.-]part\*$/, '');
|
|
140
|
+
const candidatePrefixes = uniqueArray([
|
|
141
|
+
normalizedPrefix,
|
|
142
|
+
normalizedPrefix.endsWith('-') ? normalizedPrefix : `${normalizedPrefix}-`,
|
|
143
|
+
]);
|
|
144
|
+
|
|
145
|
+
for (const prefix of candidatePrefixes) {
|
|
146
|
+
const zipPath = `${prefix}.zip`;
|
|
147
|
+
if (fs.existsSync(zipPath)) {
|
|
148
|
+
return {
|
|
149
|
+
buildPrefix: prefix,
|
|
150
|
+
zipPath,
|
|
151
|
+
partPaths: [],
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const partPaths = fs.existsSync(dir.dirname(zipPath)) ? getZipPartPaths(zipPath) : [];
|
|
156
|
+
if (partPaths.length > 0) {
|
|
157
|
+
return {
|
|
158
|
+
buildPrefix: prefix,
|
|
159
|
+
zipPath,
|
|
160
|
+
partPaths,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const searchDir = dir.dirname(normalizedPrefix);
|
|
166
|
+
const prefixBase = dir.basename(normalizedPrefix);
|
|
167
|
+
if (!fs.existsSync(searchDir)) {
|
|
168
|
+
throw new Error(`Build directory not found: ${searchDir}`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const matches = uniqueArray(
|
|
172
|
+
fs
|
|
173
|
+
.readdirSync(searchDir)
|
|
174
|
+
.filter((name) => name.startsWith(prefixBase) && /\.zip(?:[.-]part\d+)?$/.test(name))
|
|
175
|
+
.map((name) => name.replace(/[.-]part\d+$/, '')),
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
if (matches.length === 1) {
|
|
179
|
+
const zipPath = dir.join(searchDir, matches[0]);
|
|
180
|
+
const partPaths = getZipPartPaths(zipPath);
|
|
181
|
+
return {
|
|
182
|
+
buildPrefix: zipPath.replace(/\.zip$/, ''),
|
|
183
|
+
zipPath,
|
|
184
|
+
partPaths,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (matches.length > 1) {
|
|
189
|
+
throw new Error(
|
|
190
|
+
`Multiple build zip matches found for '${buildPrefix}': ${matches.join(', ')}. Use a more specific --unzip path.`,
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
throw new Error(`No build zip or split parts found for: ${buildPrefix}`);
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Merges split ZIP parts back into a single ZIP file.
|
|
199
|
+
* @param {object} options
|
|
200
|
+
* @param {string} options.buildPrefix - The build prefix path (e.g. build/underpost.net/underpost.net-).
|
|
201
|
+
* @param {object} options.logger - Logger instance.
|
|
202
|
+
* @returns {{ zipPath: string, partPaths: string[], mergedBytes: number }}
|
|
203
|
+
*/
|
|
204
|
+
const mergeClientBuildZip = ({ buildPrefix, logger }) => {
|
|
205
|
+
// Normalize to get the zip path, then look for parts directly (bypassing resolveClientBuildZip
|
|
206
|
+
// which prefers an existing monolithic zip over parts).
|
|
207
|
+
const normalizedPrefix = buildPrefix.replace(/\.zip(?:[.-]part\d+)?$/, '').replace(/[-.]$/, '') + '-';
|
|
208
|
+
const candidatePrefixes = uniqueArray([buildPrefix, buildPrefix.endsWith('-') ? buildPrefix : `${buildPrefix}-`]);
|
|
209
|
+
|
|
210
|
+
let zipPath;
|
|
211
|
+
let partPaths = [];
|
|
212
|
+
|
|
213
|
+
for (const prefix of candidatePrefixes) {
|
|
214
|
+
const candidate = prefix.endsWith('.zip') ? prefix : `${prefix}.zip`;
|
|
215
|
+
const parts = getZipPartPaths(candidate);
|
|
216
|
+
if (parts.length > 0) {
|
|
217
|
+
zipPath = candidate;
|
|
218
|
+
partPaths = parts;
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (partPaths.length === 0) {
|
|
224
|
+
// Fall back to resolveClientBuildZip for the zipPath
|
|
225
|
+
const resolved = resolveClientBuildZip(buildPrefix);
|
|
226
|
+
zipPath = resolved.zipPath;
|
|
227
|
+
logger.warn('merge-zip: no split parts found, nothing to merge', { buildPrefix, zipPath });
|
|
228
|
+
return { zipPath, partPaths, mergedBytes: 0 };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// For each part, extract raw bytes: if the part file is a Cloudinary wrapper zip
|
|
232
|
+
// (downloaded via pull without --omit-unzip or with --omit-unzip keeping the .zip),
|
|
233
|
+
// extract the inner entry rather than using the wrapper bytes.
|
|
234
|
+
const readPartBytes = (partPath) => {
|
|
235
|
+
const rawBytes = fs.readFileSync(partPath);
|
|
236
|
+
// Check for ZIP magic bytes (PK\x03\x04)
|
|
237
|
+
if (rawBytes[0] === 0x50 && rawBytes[1] === 0x4b && rawBytes[2] === 0x03 && rawBytes[3] === 0x04) {
|
|
238
|
+
try {
|
|
239
|
+
const wrapperZip = new AdmZip(rawBytes);
|
|
240
|
+
const entries = wrapperZip.getEntries();
|
|
241
|
+
// The inner entry is the original part file (without the outer .zip wrapper)
|
|
242
|
+
const partBase = dir.basename(partPath).replace(/\.zip$/i, '');
|
|
243
|
+
const entry = entries.find((e) => e.entryName === partBase || e.entryName.endsWith('/' + partBase));
|
|
244
|
+
if (entry) {
|
|
245
|
+
return entry.getData();
|
|
246
|
+
}
|
|
247
|
+
// Fallback: single-entry archive
|
|
248
|
+
if (entries.length === 1) {
|
|
249
|
+
return entries[0].getData();
|
|
250
|
+
}
|
|
251
|
+
} catch (_) {
|
|
252
|
+
// Not a valid zip or extraction failed — use raw bytes
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return rawBytes;
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
const mergedBuffer = Buffer.concat(partPaths.map(readPartBytes));
|
|
259
|
+
fs.writeFileSync(zipPath, mergedBuffer);
|
|
260
|
+
|
|
261
|
+
logger.warn('merge-zip: merged split parts into zip', {
|
|
262
|
+
zipPath,
|
|
263
|
+
parts: partPaths.length,
|
|
264
|
+
mergedBytes: mergedBuffer.length,
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
return { zipPath, partPaths, mergedBytes: mergedBuffer.length };
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
const unzipClientBuild = ({ buildPrefix, logger }) => {
|
|
271
|
+
const { zipPath, partPaths, buildPrefix: resolvedBuildPrefix } = resolveClientBuildZip(buildPrefix);
|
|
272
|
+
const outputPath = resolvedBuildPrefix.replace(/-$/, '');
|
|
273
|
+
|
|
274
|
+
fs.removeSync(outputPath);
|
|
275
|
+
fs.mkdirSync(outputPath, { recursive: true });
|
|
276
|
+
|
|
277
|
+
const zip =
|
|
278
|
+
partPaths.length > 0
|
|
279
|
+
? new AdmZip(Buffer.concat(partPaths.map((partPath) => fs.readFileSync(partPath))))
|
|
280
|
+
: new AdmZip(zipPath);
|
|
281
|
+
|
|
282
|
+
zip.extractAllTo(outputPath, true);
|
|
283
|
+
|
|
284
|
+
logger.warn('unzip build', {
|
|
285
|
+
source: partPaths.length > 0 ? partPaths : [zipPath],
|
|
286
|
+
outputPath,
|
|
287
|
+
splitParts: partPaths.length,
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
return {
|
|
291
|
+
outputPath,
|
|
292
|
+
zipPath,
|
|
293
|
+
partPaths,
|
|
294
|
+
};
|
|
295
|
+
};
|
|
296
|
+
|
|
77
297
|
/** @type {string} Default XSL sitemap template used when no `sitemap` source file exists in the public directory. */
|
|
78
298
|
const defaultSitemapXsl = `<?xml version="1.0" encoding="UTF-8"?>
|
|
79
299
|
<xsl:stylesheet version="1.0"
|
|
@@ -233,6 +453,7 @@ const defaultSitemapXsl = `<?xml version="1.0" encoding="UTF-8"?>
|
|
|
233
453
|
* @param {Array} options.liveClientBuildPaths - List of paths to build incrementally.
|
|
234
454
|
* @param {Array} options.instances - List of instances to build.
|
|
235
455
|
* @param {boolean} options.buildZip - Whether to create zip files of the builds.
|
|
456
|
+
* @param {string|number} options.split - Optional zip split size in MB.
|
|
236
457
|
* @param {boolean} options.fullBuild - Whether to perform a full build.
|
|
237
458
|
* @param {boolean} options.iconsBuild - Whether to build icons.
|
|
238
459
|
* @returns {Promise<void>} - Promise that resolves when the build is complete.
|
|
@@ -245,6 +466,7 @@ const buildClient = async (
|
|
|
245
466
|
liveClientBuildPaths: [],
|
|
246
467
|
instances: [],
|
|
247
468
|
buildZip: false,
|
|
469
|
+
split: '',
|
|
248
470
|
fullBuild: false,
|
|
249
471
|
iconsBuild: false,
|
|
250
472
|
},
|
|
@@ -311,35 +533,18 @@ const buildClient = async (
|
|
|
311
533
|
|
|
312
534
|
buildAcmeChallengePath(acmeChallengeFullPath);
|
|
313
535
|
|
|
314
|
-
if (publicClientId && publicClientId.startsWith('html-website-templates')) {
|
|
315
|
-
if (!fs.existsSync(`/home/dd/html-website-templates/`))
|
|
316
|
-
shellExec(`cd /home/dd && git clone https://github.com/designmodo/html-website-templates.git`);
|
|
317
|
-
if (!fs.existsSync(`${rootClientPath}/index.php`)) {
|
|
318
|
-
fs.copySync(`/home/dd/html-website-templates/${publicClientId.split('-publicClientId-')[1]}`, rootClientPath);
|
|
319
|
-
Underpost.repo.initLocalRepo({ path: rootClientPath });
|
|
320
|
-
shellExec(`cd ${rootClientPath} && git add . && git commit -m "Base template implementation"`);
|
|
321
|
-
// git remote add origin git@github.com:<username>/<repo>.git
|
|
322
|
-
fs.writeFileSync(`${rootClientPath}/.git/.htaccess`, `Deny from all`, 'utf8');
|
|
323
|
-
}
|
|
324
|
-
return;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
536
|
fs.removeSync(rootClientPath);
|
|
328
537
|
|
|
329
538
|
if (fs.existsSync(`./src/client/public/${publicClientId}`)) {
|
|
330
539
|
if (iconsBuild === true) await buildIcons({ publicClientId, metadata });
|
|
331
540
|
|
|
332
|
-
fs.copySync(
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
filter: function (name) {
|
|
336
|
-
console.log(name);
|
|
337
|
-
return true;
|
|
338
|
-
},
|
|
339
|
-
} */,
|
|
340
|
-
);
|
|
541
|
+
fs.copySync(`./src/client/public/${publicClientId}`, rootClientPath, {
|
|
542
|
+
filter: (sourcePath) => !sourcePath.split(dir.sep).includes('.git'),
|
|
543
|
+
});
|
|
341
544
|
} else if (fs.existsSync(`./engine-private/src/client/public/${publicClientId}`)) {
|
|
342
|
-
fs.copySync(`./engine-private/src/client/public/${publicClientId}`, rootClientPath
|
|
545
|
+
fs.copySync(`./engine-private/src/client/public/${publicClientId}`, rootClientPath, {
|
|
546
|
+
filter: (sourcePath) => !sourcePath.split(dir.sep).includes('.git'),
|
|
547
|
+
});
|
|
343
548
|
}
|
|
344
549
|
if (dists)
|
|
345
550
|
for (const dist of dists) {
|
|
@@ -463,28 +668,18 @@ const buildClient = async (
|
|
|
463
668
|
for (const module of services) {
|
|
464
669
|
if (!fs.existsSync(`${rootClientPath}/services/${module}`))
|
|
465
670
|
fs.mkdirSync(`${rootClientPath}/services/${module}`, { recursive: true });
|
|
671
|
+
const moduleDir = `./src/client/services/${module}`;
|
|
672
|
+
if (!fs.existsSync(moduleDir)) continue;
|
|
466
673
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
674
|
+
const serviceFiles = fs
|
|
675
|
+
.readdirSync(moduleDir)
|
|
676
|
+
.filter((name) => name.endsWith('.service.js') || name.endsWith('.management.js'))
|
|
677
|
+
.sort();
|
|
471
678
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
basePath: 'services',
|
|
476
|
-
module,
|
|
477
|
-
baseHost,
|
|
478
|
-
minify: minifyBuild,
|
|
479
|
-
});
|
|
480
|
-
fs.writeFileSync(jsPublicPath, jsSrc, 'utf8');
|
|
481
|
-
}
|
|
482
|
-
}
|
|
679
|
+
for (const serviceFile of serviceFiles) {
|
|
680
|
+
const jsSrcPath = `${moduleDir}/${serviceFile}`;
|
|
681
|
+
const jsPublicPath = `${rootClientPath}/services/${module}/${serviceFile}`;
|
|
483
682
|
|
|
484
|
-
for (const module of services) {
|
|
485
|
-
if (fs.existsSync(`./src/client/services/${module}/${module}.management.js`)) {
|
|
486
|
-
const jsSrcPath = `./src/client/services/${module}/${module}.management.js`;
|
|
487
|
-
const jsPublicPath = `${rootClientPath}/services/${module}/${module}.management.js`;
|
|
488
683
|
if (enableLiveRebuild && !options.liveClientBuildPaths.find((p) => p.srcBuildPath === jsSrcPath)) continue;
|
|
489
684
|
|
|
490
685
|
const jsSrc = await transformClientJs(jsSrcPath, {
|
|
@@ -497,6 +692,30 @@ const buildClient = async (
|
|
|
497
692
|
});
|
|
498
693
|
fs.writeFileSync(jsPublicPath, jsSrc, 'utf8');
|
|
499
694
|
}
|
|
695
|
+
|
|
696
|
+
// Auto-build guest module files when user module is processed
|
|
697
|
+
if (module === 'user') {
|
|
698
|
+
const guestModuleDir = './src/client/services/user';
|
|
699
|
+
const guestServicePath = `${guestModuleDir}/guest.service.js`;
|
|
700
|
+
if (fs.existsSync(guestServicePath)) {
|
|
701
|
+
if (!fs.existsSync(`${rootClientPath}/services/user`))
|
|
702
|
+
fs.mkdirSync(`${rootClientPath}/services/user`, { recursive: true });
|
|
703
|
+
|
|
704
|
+
const guestJsPublicPath = `${rootClientPath}/services/user/guest.service.js`;
|
|
705
|
+
|
|
706
|
+
if (!enableLiveRebuild || options.liveClientBuildPaths.find((p) => p.srcBuildPath === guestServicePath)) {
|
|
707
|
+
const guestJsSrc = await transformClientJs(guestServicePath, {
|
|
708
|
+
dists,
|
|
709
|
+
proxyPath: path,
|
|
710
|
+
basePath: 'services',
|
|
711
|
+
module: 'user',
|
|
712
|
+
baseHost,
|
|
713
|
+
minify: minifyBuild,
|
|
714
|
+
});
|
|
715
|
+
fs.writeFileSync(guestJsPublicPath, guestJsSrc, 'utf8');
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
}
|
|
500
719
|
}
|
|
501
720
|
}
|
|
502
721
|
|
|
@@ -506,14 +725,18 @@ const buildClient = async (
|
|
|
506
725
|
const Render = await ssrFactory();
|
|
507
726
|
|
|
508
727
|
if (views) {
|
|
509
|
-
const jsSrcPath =
|
|
510
|
-
? `./src/client/sw/${publicClientId}.sw.js`
|
|
511
|
-
: `./src/client/sw/default.sw.js`;
|
|
728
|
+
const jsSrcPath = `./src/client/sw/core.sw.js`;
|
|
512
729
|
|
|
513
730
|
const jsPublicPath = `${rootClientPath}/sw.js`;
|
|
514
731
|
|
|
515
732
|
if (!(enableLiveRebuild && !options.liveClientBuildPaths.find((p) => p.srcBuildPath === jsSrcPath))) {
|
|
516
|
-
const jsSrc = await transformClientJs(jsSrcPath, {
|
|
733
|
+
const jsSrc = await transformClientJs(jsSrcPath, {
|
|
734
|
+
dists,
|
|
735
|
+
proxyPath: path,
|
|
736
|
+
baseHost,
|
|
737
|
+
minify: minifyBuild,
|
|
738
|
+
externalizeBareImports: false,
|
|
739
|
+
});
|
|
517
740
|
|
|
518
741
|
fs.writeFileSync(jsPublicPath, jsSrc, 'utf8');
|
|
519
742
|
}
|
|
@@ -746,16 +969,18 @@ Sitemap: ${sitemapBaseUrl}/sitemap.xml`,
|
|
|
746
969
|
if (client) {
|
|
747
970
|
let PRE_CACHED_RESOURCES = [];
|
|
748
971
|
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
972
|
+
const normalizePrecacheRoutePath = (candidatePath) => {
|
|
973
|
+
const routePath =
|
|
974
|
+
typeof candidatePath === 'string' && candidatePath.trim().length > 0 ? candidatePath.trim() : '/offline';
|
|
975
|
+
const withLeadingSlash = routePath.startsWith('/') ? routePath : `/${routePath}`;
|
|
976
|
+
const withoutTrailingSlash = withLeadingSlash.replace(/\/+$/, '');
|
|
977
|
+
return withoutTrailingSlash.length > 0 ? withoutTrailingSlash : '/';
|
|
978
|
+
};
|
|
979
|
+
|
|
980
|
+
const toPrecacheIndexUrl = (routePath) => {
|
|
981
|
+
const normalizedRoutePath = normalizePrecacheRoutePath(routePath);
|
|
982
|
+
return `${path === '/' ? '' : path}${normalizedRoutePath === '/' ? '' : normalizedRoutePath}/index.html`;
|
|
983
|
+
};
|
|
759
984
|
|
|
760
985
|
for (const pageType of ['offline', 'pages']) {
|
|
761
986
|
if (confSSR[getCapVariableName(client)] && confSSR[getCapVariableName(client)][pageType]) {
|
|
@@ -783,7 +1008,11 @@ Sitemap: ${sitemapBaseUrl}/sitemap.xml`,
|
|
|
783
1008
|
rootClientPath[rootClientPath.length - 1] === '/' ? rootClientPath.slice(0, -1) : rootClientPath
|
|
784
1009
|
}${page.path === '/' ? page.path : `${page.path}/`}`;
|
|
785
1010
|
|
|
786
|
-
|
|
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
|
+
}
|
|
787
1016
|
|
|
788
1017
|
if (!fs.existsSync(buildPath)) fs.mkdirSync(buildPath, { recursive: true });
|
|
789
1018
|
|
|
@@ -809,13 +1038,47 @@ Sitemap: ${sitemapBaseUrl}/sitemap.xml`,
|
|
|
809
1038
|
}
|
|
810
1039
|
|
|
811
1040
|
{
|
|
1041
|
+
const cacheScope = path === '/' ? 'root' : path.replaceAll('/', '_');
|
|
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
|
+
};
|
|
1053
|
+
|
|
1054
|
+
const offlineSsrPage =
|
|
1055
|
+
ssrOfflinePages.find(
|
|
1056
|
+
(page) =>
|
|
1057
|
+
page?.client === 'NoNetworkConnection' ||
|
|
1058
|
+
/no\s*network|offline/i.test(`${page?.title || ''} ${page?.client || ''} ${page?.path || ''}`),
|
|
1059
|
+
) || ssrOfflinePages[0];
|
|
1060
|
+
|
|
1061
|
+
const maintenanceSsrPage =
|
|
1062
|
+
ssrOfflinePages.find(
|
|
1063
|
+
(page) =>
|
|
1064
|
+
page?.client === 'Maintenance' ||
|
|
1065
|
+
/maintenance/i.test(`${page?.title || ''} ${page?.client || ''} ${page?.path || ''}`),
|
|
1066
|
+
) || ssrOfflinePages[1];
|
|
1067
|
+
|
|
1068
|
+
const offlinePath = normalizeSsrRoutePath(offlineSsrPage?.path, '/offline');
|
|
1069
|
+
const maintenancePath = normalizeSsrRoutePath(maintenanceSsrPage?.path, '/maintenance');
|
|
1070
|
+
|
|
812
1071
|
const renderPayload = {
|
|
813
1072
|
PRE_CACHED_RESOURCES: uniqueArray(PRE_CACHED_RESOURCES),
|
|
814
1073
|
PROXY_PATH: path,
|
|
1074
|
+
CACHE_PREFIX: `engine-core-v3-${cacheScope}`,
|
|
1075
|
+
OFFLINE_PATH: offlinePath,
|
|
1076
|
+
MAINTENANCE_PATH: maintenancePath,
|
|
815
1077
|
};
|
|
816
1078
|
fs.writeFileSync(
|
|
817
1079
|
`${rootClientPath}/sw.js`,
|
|
818
1080
|
`self.renderPayload = ${JSONweb(renderPayload)};
|
|
1081
|
+
self.__WB_DISABLE_DEV_LOGS = true;
|
|
819
1082
|
${fs.readFileSync(`${rootClientPath}/sw.js`, 'utf8')}`,
|
|
820
1083
|
'utf8',
|
|
821
1084
|
);
|
|
@@ -838,13 +1101,24 @@ ${fs.readFileSync(`${rootClientPath}/sw.js`, 'utf8')}`,
|
|
|
838
1101
|
}
|
|
839
1102
|
|
|
840
1103
|
const buildId = `${host}-${path.replaceAll('/', '')}`;
|
|
1104
|
+
const zipPath = `./build/${buildId}.zip`;
|
|
841
1105
|
|
|
842
|
-
logger.warn('write zip',
|
|
1106
|
+
logger.warn('write zip', zipPath);
|
|
843
1107
|
|
|
844
|
-
zip.writeZip(
|
|
1108
|
+
zip.writeZip(zipPath);
|
|
1109
|
+
|
|
1110
|
+
if (options.split) {
|
|
1111
|
+
splitFileByMb({
|
|
1112
|
+
filePath: zipPath,
|
|
1113
|
+
partSizeMb: options.split,
|
|
1114
|
+
logger,
|
|
1115
|
+
});
|
|
1116
|
+
fs.removeSync(zipPath);
|
|
1117
|
+
logger.warn('removed original zip after split', { zipPath });
|
|
1118
|
+
}
|
|
845
1119
|
}
|
|
846
1120
|
}
|
|
847
1121
|
}
|
|
848
1122
|
};
|
|
849
1123
|
|
|
850
|
-
export { buildClient, copyNonExistingFiles };
|
|
1124
|
+
export { buildClient, copyNonExistingFiles, unzipClientBuild, mergeClientBuildZip };
|
|
@@ -27,6 +27,13 @@ const escapeRegExp = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
|
27
27
|
*/
|
|
28
28
|
const srcFormatted = (src) => src.replace(/(?<=[\s({[,;=+!?:^])(html|css)`/g, '`');
|
|
29
29
|
|
|
30
|
+
const resolveBrowserImportPath = (basePrefix, relativePath) => {
|
|
31
|
+
if (/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(basePrefix)) {
|
|
32
|
+
return new URL(relativePath, basePrefix.endsWith('/') ? basePrefix : `${basePrefix}/`).toString();
|
|
33
|
+
}
|
|
34
|
+
return path.posix.normalize(`${basePrefix}${relativePath}`);
|
|
35
|
+
};
|
|
36
|
+
|
|
30
37
|
/**
|
|
31
38
|
* Converts a JavaScript object into a string that can be embedded in client-side code
|
|
32
39
|
* and parsed back into an object (e.g., 'JSON.parse(`{...}`)').
|
|
@@ -52,7 +59,14 @@ const JSONweb = (data) => {
|
|
|
52
59
|
* @returns {import('esbuild').Plugin}
|
|
53
60
|
* @memberof clientFormatted
|
|
54
61
|
*/
|
|
55
|
-
const importRewritePlugin = ({
|
|
62
|
+
const importRewritePlugin = ({
|
|
63
|
+
dists = [],
|
|
64
|
+
proxyPath,
|
|
65
|
+
basePath = '',
|
|
66
|
+
module = '',
|
|
67
|
+
baseHost = '',
|
|
68
|
+
externalizeBareImports = true,
|
|
69
|
+
}) => ({
|
|
56
70
|
name: 'import-rewrite',
|
|
57
71
|
setup(build) {
|
|
58
72
|
const prefix = `${baseHost}${proxyPath !== '/' ? `${proxyPath}/` : '/'}`;
|
|
@@ -69,26 +83,35 @@ const importRewritePlugin = ({ dists = [], proxyPath, basePath = '', module = ''
|
|
|
69
83
|
}
|
|
70
84
|
}
|
|
71
85
|
|
|
72
|
-
// Rewrite relative imports to absolute paths based on proxy path and module
|
|
86
|
+
// Rewrite app-relative imports to absolute paths based on proxy path and module.
|
|
87
|
+
// Do not touch node_modules relative imports so esbuild can bundle package internals.
|
|
73
88
|
build.onResolve({ filter: /^\.\.?\// }, (args) => {
|
|
74
|
-
const
|
|
75
|
-
if (
|
|
76
|
-
return
|
|
77
|
-
path: `${basePrefix}${module ? `${module}/` : ''}${args.path.slice(2)}`,
|
|
78
|
-
external: true,
|
|
79
|
-
};
|
|
89
|
+
const normalizedImporter = (args.importer || '').replace(/\\/g, '/');
|
|
90
|
+
if (!normalizedImporter.includes('/src/client/')) {
|
|
91
|
+
return;
|
|
80
92
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
93
|
+
|
|
94
|
+
// Extract the path relative to /src/client/
|
|
95
|
+
// Handle cases where the path might have duplicates or be in various formats
|
|
96
|
+
const srcClientIndex = normalizedImporter.lastIndexOf('/src/client/');
|
|
97
|
+
if (srcClientIndex === -1) {
|
|
98
|
+
return;
|
|
86
99
|
}
|
|
100
|
+
const importerFromClientRoot = normalizedImporter.substring(srcClientIndex + '/src/client/'.length);
|
|
101
|
+
const importerDir = path.posix.dirname(importerFromClientRoot);
|
|
102
|
+
const resolvedFromClientRoot = path.posix.normalize(path.posix.join(importerDir, args.path));
|
|
103
|
+
const result = resolveBrowserImportPath(prefix, resolvedFromClientRoot);
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
path: result,
|
|
107
|
+
external: true,
|
|
108
|
+
};
|
|
87
109
|
});
|
|
88
110
|
|
|
89
|
-
//
|
|
111
|
+
// For client app modules we externalize bare imports; for SW builds we let esbuild bundle them.
|
|
90
112
|
build.onResolve({ filter: /.*/ }, (args) => {
|
|
91
113
|
if (args.kind === 'entry-point') return;
|
|
114
|
+
if (!externalizeBareImports) return;
|
|
92
115
|
return { path: args.path, external: true };
|
|
93
116
|
});
|
|
94
117
|
},
|
|
@@ -111,7 +134,15 @@ const importRewritePlugin = ({ dists = [], proxyPath, basePath = '', module = ''
|
|
|
111
134
|
*/
|
|
112
135
|
const transformClientJs = async (
|
|
113
136
|
srcPath,
|
|
114
|
-
{
|
|
137
|
+
{
|
|
138
|
+
dists = [],
|
|
139
|
+
proxyPath,
|
|
140
|
+
basePath = '',
|
|
141
|
+
module = '',
|
|
142
|
+
baseHost = '',
|
|
143
|
+
minify: shouldMinify = false,
|
|
144
|
+
externalizeBareImports = true,
|
|
145
|
+
} = {},
|
|
115
146
|
) => {
|
|
116
147
|
const src = fs.readFileSync(srcPath, 'utf8');
|
|
117
148
|
const stripped = srcFormatted(src);
|
|
@@ -130,7 +161,7 @@ const transformClientJs = async (
|
|
|
130
161
|
target: 'esnext',
|
|
131
162
|
minify: shouldMinify,
|
|
132
163
|
logLevel: 'warning',
|
|
133
|
-
plugins: [importRewritePlugin({ dists, proxyPath, basePath, module, baseHost })],
|
|
164
|
+
plugins: [importRewritePlugin({ dists, proxyPath, basePath, module, baseHost, externalizeBareImports })],
|
|
134
165
|
});
|
|
135
166
|
|
|
136
167
|
return result.outputFiles[0].text;
|