webspresso 0.0.96 → 0.0.98
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/.agents/ADMIN.md +153 -5
- package/.agents/ARCHITECTURE.md +10 -0
- package/.agents/CLI.md +195 -1
- package/.agents/PLUGINS.md +153 -9
- package/.agents/QUEUE.md +43 -0
- package/.agents/skills/webspresso-admin-and-plugins/SKILL.md +122 -0
- package/.agents/skills/webspresso-framework/SKILL.md +56 -0
- package/.agents/skills/webspresso-orm-and-database/SKILL.md +119 -0
- package/.agents/skills/webspresso-routing-and-api/SKILL.md +114 -0
- package/.agents/skills/webspresso-services/SKILL.md +101 -0
- package/bin/commands/add-esbuild.js +80 -0
- package/bin/commands/add-tailwind.js +76 -31
- package/bin/commands/admin-password.js +2 -1
- package/bin/commands/admin-setup.js +1 -1
- package/bin/commands/agents-init.js +51 -11
- package/bin/commands/analytics-prune.js +57 -0
- package/bin/commands/api.js +207 -15
- package/bin/commands/audit-prune.js +1 -1
- package/bin/commands/build.js +99 -0
- package/bin/commands/dev.js +64 -35
- package/bin/commands/doctor.js +26 -0
- package/bin/commands/email-prune.js +47 -0
- package/bin/commands/favicon-generate.js +1 -1
- package/bin/commands/make-job.js +86 -0
- package/bin/commands/make-model.js +250 -0
- package/bin/commands/new.js +45 -28
- package/bin/commands/openapi.js +102 -0
- package/bin/commands/orm-map.js +5 -4
- package/bin/commands/page.js +6 -3
- package/bin/commands/polar-migrate.js +1 -1
- package/bin/commands/queue-prune.js +59 -0
- package/bin/commands/routes.js +52 -0
- package/bin/commands/seed.js +1 -1
- package/bin/commands/service.js +6 -3
- package/bin/commands/typegen.js +106 -0
- package/bin/commands/upload-prune.js +79 -0
- package/bin/webspresso.js +22 -0
- package/core/applySchema.js +13 -6
- package/core/auth/hash.js +31 -17
- package/core/compression/middleware.js +23 -17
- package/core/openapi/build-from-api-routes.js +160 -29
- package/core/orm/cache/fingerprint.js +15 -1
- package/core/orm/cache/memory-provider.js +27 -2
- package/core/orm/index.js +12 -7
- package/core/orm/model.js +49 -7
- package/core/queue/adapters/database.js +94 -0
- package/core/queue/adapters/memory.js +62 -0
- package/core/queue/adapters/redis.js +80 -0
- package/core/queue/manager.js +123 -0
- package/core/ssr/stream.js +11 -0
- package/index.d.ts +660 -10
- package/index.js +44 -96
- package/package.json +32 -12
- package/plugins/admin-panel/api.js +279 -17
- package/plugins/admin-panel/app.js +124 -31
- package/plugins/admin-panel/client/parts/01-state-api-breadcrumb.js +314 -9
- package/plugins/admin-panel/client/parts/04-field-renderers.js +264 -67
- package/plugins/admin-panel/client/parts/05-rich-text-file-helpers.js +10 -5
- package/plugins/admin-panel/client/parts/06-login-setup-forms.js +119 -28
- package/plugins/admin-panel/client/parts/07-model-list.js +49 -8
- package/plugins/admin-panel/client/parts/08-record-list.js +243 -23
- package/plugins/admin-panel/client/parts/09-record-form.js +59 -12
- package/plugins/admin-panel/client/parts/10-export-registry.js +23 -0
- package/plugins/admin-panel/core/admin-module.js +13 -7
- package/plugins/admin-panel/core/api-extensions.js +34 -2
- package/plugins/admin-panel/core/registry.js +96 -16
- package/plugins/admin-panel/index.js +96 -0
- package/plugins/admin-panel/lib/login-rate-limiter.js +119 -0
- package/plugins/admin-panel/modules/admin-users.js +16 -1
- package/plugins/admin-panel/modules/bulk-actions.js +13 -5
- package/plugins/admin-panel/modules/custom-pages.js +492 -3
- package/plugins/admin-panel/modules/dashboard.js +283 -0
- package/plugins/admin-panel/modules/menu.js +56 -18
- package/plugins/admin-panel/modules/user-management.js +24 -6
- package/plugins/audit-log/admin-component.js +11 -0
- package/plugins/audit-log/api-handlers.js +34 -0
- package/plugins/audit-log/index.js +129 -3
- package/plugins/audit-log/purge.js +60 -10
- package/plugins/basic-auth/index.js +3 -1
- package/plugins/content/client/inline-edit.js +12 -12
- package/plugins/cors.js +6 -7
- package/plugins/dashboard/app.js +152 -27
- package/plugins/dashboard/index.js +143 -39
- package/plugins/dashboard/styles.js +78 -0
- package/plugins/data-exchange/export-xlsx.js +9 -2
- package/plugins/data-exchange/import.js +9 -2
- package/plugins/data-exchange/index.js +6 -3
- package/plugins/email/index.js +132 -2
- package/plugins/email/purge.js +60 -10
- package/plugins/file-manager/core.js +1 -1
- package/plugins/file-manager/security.js +33 -23
- package/plugins/index.js +49 -87
- package/plugins/mcp/transports/sse.js +4 -2
- package/plugins/orm-cache-admin/index.js +4 -2
- package/plugins/queue/index.js +11 -0
- package/plugins/schema-explorer/admin-component.js +714 -0
- package/plugins/schema-explorer/relations-graph.js +205 -0
- package/plugins/schema-explorer.js +248 -69
- package/plugins/site-analytics/admin-component.js +30 -8
- package/plugins/site-analytics/api-handlers.js +42 -0
- package/plugins/site-analytics/index.js +147 -2
- package/plugins/site-analytics/purge.js +168 -0
- package/plugins/swagger.js +261 -13
- package/plugins/upload/index.js +26 -0
- package/plugins/upload/orphan-pruner.js +292 -0
- package/src/api/api-loader.js +3 -1
- package/src/bundle/index.js +517 -0
- package/src/bundle/manifest.js +165 -0
- package/src/dev/banner.js +296 -0
- package/src/dev/colors.js +70 -0
- package/src/dev/index.js +62 -0
- package/src/dev/profiler.js +354 -0
- package/src/dev/typegen.js +497 -0
- package/src/dev-reload/index.js +611 -0
- package/src/file-router.js +213 -63
- package/src/helpers.js +958 -98
- package/src/njk-frontmatter.js +12 -0
- package/src/pages/page-loader.js +61 -11
- package/src/server.js +629 -63
- package/src/services/discovery.js +6 -1
- package/src/services/index.js +6 -13
package/src/file-router.js
CHANGED
|
@@ -8,12 +8,13 @@ const path = require('path');
|
|
|
8
8
|
const { ZodError } = require('zod');
|
|
9
9
|
const { compileSchema, invalidateSchema } = require('../core/compileSchema');
|
|
10
10
|
const { applySchema } = require('../core/applySchema');
|
|
11
|
-
const { createHelpers } = require('./helpers');
|
|
11
|
+
const { createHelpers, getAssetManager } = require('./helpers');
|
|
12
12
|
const {
|
|
13
13
|
loadNjkRouteTemplate,
|
|
14
14
|
parseNjkFrontmatter,
|
|
15
15
|
frontmatterToPatches,
|
|
16
16
|
clearNjkFrontmatterCaches,
|
|
17
|
+
registerClearHook,
|
|
17
18
|
} = require('./njk-frontmatter');
|
|
18
19
|
const { renderStream } = require('../core/ssr/stream');
|
|
19
20
|
|
|
@@ -27,12 +28,30 @@ const i18nCache = new Map();
|
|
|
27
28
|
// Cache for merged i18n (key: `${pagesDir}::${routeDir}::${locale}`, value: { globalMtime, routeMtime, data })
|
|
28
29
|
const mergedI18nCache = new Map();
|
|
29
30
|
|
|
31
|
+
// Cache for translator instances in production
|
|
32
|
+
const translatorCache = new Map();
|
|
33
|
+
|
|
34
|
+
function clearTranslatorCache() {
|
|
35
|
+
translatorCache.clear();
|
|
36
|
+
}
|
|
37
|
+
|
|
30
38
|
// Cache for route configs in production
|
|
31
39
|
const configCache = new Map();
|
|
32
40
|
|
|
33
41
|
// Dev-only: avoid require() on every SSR request when the .js file is unchanged (mtime)
|
|
34
42
|
const routeConfigDevCache = new Map();
|
|
35
43
|
|
|
44
|
+
// Cache for compiled frontmatter string templates (key: fullPath, value: compiled Template)
|
|
45
|
+
const compiledStringTemplateCache = new Map();
|
|
46
|
+
|
|
47
|
+
function clearCompiledStringTemplateCache() {
|
|
48
|
+
compiledStringTemplateCache.clear();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (typeof registerClearHook === 'function') {
|
|
52
|
+
registerClearHook(clearCompiledStringTemplateCache);
|
|
53
|
+
}
|
|
54
|
+
|
|
36
55
|
// Cache for API filename -> { method, baseName } (basename keys; stable per process)
|
|
37
56
|
const methodFromFilenameCache = new Map();
|
|
38
57
|
|
|
@@ -43,6 +62,8 @@ const MAX_LOCALE_LEN = 16;
|
|
|
43
62
|
|
|
44
63
|
let cachedSupportedLocalesRaw = null;
|
|
45
64
|
let cachedSupportedLocaleSet = null;
|
|
65
|
+
let cachedDefaultLocaleKey = null;
|
|
66
|
+
let cachedDefaultLocale = 'en';
|
|
46
67
|
|
|
47
68
|
/** @returns {Set<string>} */
|
|
48
69
|
function parseSupportedLocaleSet() {
|
|
@@ -57,6 +78,27 @@ function parseSupportedLocaleSet() {
|
|
|
57
78
|
return cachedSupportedLocaleSet;
|
|
58
79
|
}
|
|
59
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Get default locale matching supported set
|
|
83
|
+
* @param {Set<string>} [supported]
|
|
84
|
+
* @returns {string}
|
|
85
|
+
*/
|
|
86
|
+
function getDefaultLocale(supported = parseSupportedLocaleSet()) {
|
|
87
|
+
const rawDef = process.env.DEFAULT_LOCALE || 'en';
|
|
88
|
+
const rawSupp = process.env.SUPPORTED_LOCALES || 'en';
|
|
89
|
+
const key = rawDef + '|' + rawSupp;
|
|
90
|
+
if (key === cachedDefaultLocaleKey && cachedDefaultLocale) {
|
|
91
|
+
return cachedDefaultLocale;
|
|
92
|
+
}
|
|
93
|
+
cachedDefaultLocaleKey = key;
|
|
94
|
+
const defaultCand = normalizeLocaleCandidate(rawDef);
|
|
95
|
+
cachedDefaultLocale =
|
|
96
|
+
pickMatchingLocale(defaultCand, supported)
|
|
97
|
+
?? (supported.has('en') ? 'en' : [...supported][0])
|
|
98
|
+
?? 'en';
|
|
99
|
+
return cachedDefaultLocale;
|
|
100
|
+
}
|
|
101
|
+
|
|
60
102
|
/**
|
|
61
103
|
* @param {string} raw
|
|
62
104
|
* @returns {string|null}
|
|
@@ -271,29 +313,31 @@ function extractMethodFromFilename(filename) {
|
|
|
271
313
|
|
|
272
314
|
/**
|
|
273
315
|
* Whether `load()` return values for `stylesheets` and `scripts` are promoted to
|
|
274
|
-
* `pageHead` in Nunjucks (see `createApp({ pageAssets })`)
|
|
275
|
-
*
|
|
276
|
-
* @
|
|
316
|
+
* `pageHead` in Nunjucks (see `createApp({ pageAssets })`), with optional automatic
|
|
317
|
+
* per-route script/stylesheet discovery.
|
|
318
|
+
* @param {boolean|{enabled?: boolean, stylesheets?: boolean, scripts?: boolean, auto?: boolean}|null|undefined} raw
|
|
319
|
+
* @returns {{ enabled: boolean, stylesheets: boolean, scripts: boolean, auto: boolean }}
|
|
277
320
|
*/
|
|
278
321
|
function resolvePageAssets(raw) {
|
|
279
322
|
if (raw === true) {
|
|
280
|
-
return { enabled: true, stylesheets: true, scripts: true };
|
|
323
|
+
return { enabled: true, stylesheets: true, scripts: true, auto: true };
|
|
281
324
|
}
|
|
282
325
|
if (raw == null || raw === false) {
|
|
283
|
-
return { enabled: false, stylesheets: false, scripts: false };
|
|
326
|
+
return { enabled: false, stylesheets: false, scripts: false, auto: false };
|
|
284
327
|
}
|
|
285
328
|
if (typeof raw === 'object') {
|
|
286
329
|
const on = raw.enabled !== false;
|
|
287
330
|
if (!on) {
|
|
288
|
-
return { enabled: false, stylesheets: false, scripts: false };
|
|
331
|
+
return { enabled: false, stylesheets: false, scripts: false, auto: false };
|
|
289
332
|
}
|
|
290
333
|
return {
|
|
291
334
|
enabled: true,
|
|
292
335
|
stylesheets: raw.stylesheets !== false,
|
|
293
336
|
scripts: raw.scripts !== false,
|
|
337
|
+
auto: raw.auto !== false,
|
|
294
338
|
};
|
|
295
339
|
}
|
|
296
|
-
return { enabled: false, stylesheets: false, scripts: false };
|
|
340
|
+
return { enabled: false, stylesheets: false, scripts: false, auto: false };
|
|
297
341
|
}
|
|
298
342
|
|
|
299
343
|
/**
|
|
@@ -306,11 +350,13 @@ function toList(v) {
|
|
|
306
350
|
}
|
|
307
351
|
|
|
308
352
|
/**
|
|
309
|
-
* @param {{ enabled: boolean, stylesheets: boolean, scripts: boolean }} cfg
|
|
353
|
+
* @param {{ enabled: boolean, stylesheets: boolean, scripts: boolean, auto?: boolean }} cfg
|
|
310
354
|
* @param {Object} data
|
|
355
|
+
* @param {{ file?: string, routePath?: string }} [routeInfo={}]
|
|
356
|
+
* @param {Object|null} [assetManager=null]
|
|
311
357
|
* @returns {{ data: Object, pageHead: { stylesheets: unknown[], scripts: unknown[] }|null, pageAssets: boolean }}
|
|
312
358
|
*/
|
|
313
|
-
function applyPageAssetsToTemplateData(cfg, data) {
|
|
359
|
+
function applyPageAssetsToTemplateData(cfg, data, routeInfo = {}, assetManager = null) {
|
|
314
360
|
if (!cfg || !cfg.enabled) {
|
|
315
361
|
return { data, pageHead: null, pageAssets: false };
|
|
316
362
|
}
|
|
@@ -325,6 +371,40 @@ function applyPageAssetsToTemplateData(cfg, data) {
|
|
|
325
371
|
scriptItems = toList(out.scripts);
|
|
326
372
|
delete out.scripts;
|
|
327
373
|
}
|
|
374
|
+
|
|
375
|
+
// Auto-discover page assets from route if enabled
|
|
376
|
+
if (cfg.auto && assetManager && (routeInfo.file || routeInfo.routePath)) {
|
|
377
|
+
const candidates = typeof assetManager.getRouteAssetCandidates === 'function'
|
|
378
|
+
? assetManager.getRouteAssetCandidates(routeInfo.file, routeInfo.routePath)
|
|
379
|
+
: [];
|
|
380
|
+
|
|
381
|
+
if (cfg.stylesheets) {
|
|
382
|
+
for (const cand of candidates) {
|
|
383
|
+
if (typeof assetManager.hasPageCss === 'function' && assetManager.hasPageCss(cand)) {
|
|
384
|
+
const cssUrl = assetManager.pageCssUrl(cand);
|
|
385
|
+
const already = styles.some((s) => (typeof s === 'string' ? s === cssUrl : s?.href === cssUrl));
|
|
386
|
+
if (!already) {
|
|
387
|
+
styles.push(cssUrl);
|
|
388
|
+
}
|
|
389
|
+
break;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (cfg.scripts) {
|
|
395
|
+
for (const cand of candidates) {
|
|
396
|
+
if (typeof assetManager.hasPageJs === 'function' && assetManager.hasPageJs(cand)) {
|
|
397
|
+
const jsUrl = assetManager.pageJsUrl(cand);
|
|
398
|
+
const already = scriptItems.some((s) => (typeof s === 'string' ? s === jsUrl : s?.src === jsUrl));
|
|
399
|
+
if (!already) {
|
|
400
|
+
scriptItems.push({ src: jsUrl, defer: true });
|
|
401
|
+
}
|
|
402
|
+
break;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
328
408
|
return {
|
|
329
409
|
data: out,
|
|
330
410
|
pageHead: { stylesheets: styles, scripts: scriptItems },
|
|
@@ -492,15 +572,25 @@ function getPluralRules(locale) {
|
|
|
492
572
|
return pr;
|
|
493
573
|
}
|
|
494
574
|
|
|
575
|
+
const KEY_PARTS_CACHE = new Map();
|
|
576
|
+
|
|
495
577
|
function resolveTranslationValue(dict, key) {
|
|
496
|
-
if (!dict || typeof dict !== 'object') return undefined;
|
|
578
|
+
if (!dict || typeof dict !== 'object' || typeof key !== 'string') return undefined;
|
|
497
579
|
if (dict[key] !== undefined) return dict[key];
|
|
580
|
+
if (!key.includes('.')) return undefined;
|
|
581
|
+
|
|
582
|
+
let parts = KEY_PARTS_CACHE.get(key);
|
|
583
|
+
if (!parts) {
|
|
584
|
+
parts = key.split('.');
|
|
585
|
+
if (KEY_PARTS_CACHE.size < 1000) {
|
|
586
|
+
KEY_PARTS_CACHE.set(key, parts);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
498
589
|
|
|
499
|
-
const parts = key.split('.');
|
|
500
590
|
let curr = dict;
|
|
501
|
-
for (
|
|
591
|
+
for (let i = 0; i < parts.length; i++) {
|
|
502
592
|
if (curr && typeof curr === 'object') {
|
|
503
|
-
curr = curr[
|
|
593
|
+
curr = curr[parts[i]];
|
|
504
594
|
} else {
|
|
505
595
|
return undefined;
|
|
506
596
|
}
|
|
@@ -726,25 +816,37 @@ function loadGlobalHooks(pagesDir, isDev) {
|
|
|
726
816
|
* @param {string} hookName - Hook name
|
|
727
817
|
* @param {Object} ctx - Context object
|
|
728
818
|
*/
|
|
729
|
-
|
|
730
|
-
if (hooks
|
|
731
|
-
|
|
819
|
+
function executeHook(hooks, hookName, ctx, ...extra) {
|
|
820
|
+
if (!hooks || typeof hooks[hookName] !== 'function') {
|
|
821
|
+
return;
|
|
732
822
|
}
|
|
823
|
+
return hooks[hookName](ctx, ...extra);
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
function runHooks(globalHooks, routeHooks, hookName, ctx, ...extra) {
|
|
827
|
+
const gFn = globalHooks ? globalHooks[hookName] : undefined;
|
|
828
|
+
const rFn = routeHooks ? routeHooks[hookName] : undefined;
|
|
829
|
+
if (typeof gFn !== 'function' && typeof rFn !== 'function') return;
|
|
830
|
+
return (async () => {
|
|
831
|
+
if (typeof gFn === 'function') {
|
|
832
|
+
const gRes = gFn(ctx, ...extra);
|
|
833
|
+
if (gRes && typeof gRes.then === 'function') await gRes;
|
|
834
|
+
}
|
|
835
|
+
if (typeof rFn === 'function') {
|
|
836
|
+
const rRes = rFn(ctx, ...extra);
|
|
837
|
+
if (rRes && typeof rRes.then === 'function') await rRes;
|
|
838
|
+
}
|
|
839
|
+
})();
|
|
733
840
|
}
|
|
734
841
|
|
|
735
842
|
/**
|
|
736
843
|
* Detect locale from request
|
|
737
844
|
* @param {Object} req - Express request
|
|
845
|
+
* @param {Set<string>} [supported] - Optional precomputed supported locales set
|
|
846
|
+
* @param {string} [def] - Optional precomputed default locale
|
|
738
847
|
* @returns {string} Locale code
|
|
739
848
|
*/
|
|
740
|
-
function detectLocale(req) {
|
|
741
|
-
const supported = parseSupportedLocaleSet();
|
|
742
|
-
const defaultCand = normalizeLocaleCandidate(process.env.DEFAULT_LOCALE || 'en');
|
|
743
|
-
const def =
|
|
744
|
-
pickMatchingLocale(defaultCand, supported)
|
|
745
|
-
?? (supported.has('en') ? 'en' : [...supported][0])
|
|
746
|
-
?? 'en';
|
|
747
|
-
|
|
849
|
+
function detectLocale(req, supported = parseSupportedLocaleSet(), def = getDefaultLocale(supported)) {
|
|
748
850
|
if (req.query && req.query.lang != null && req.query.lang !== '') {
|
|
749
851
|
const q = normalizeLocaleCandidate(String(req.query.lang));
|
|
750
852
|
const hit = pickMatchingLocale(q, supported);
|
|
@@ -753,7 +855,9 @@ function detectLocale(req) {
|
|
|
753
855
|
}
|
|
754
856
|
}
|
|
755
857
|
|
|
756
|
-
const acceptLanguage = req.get
|
|
858
|
+
const acceptLanguage = typeof req.get === 'function'
|
|
859
|
+
? req.get('Accept-Language')
|
|
860
|
+
: (req.headers && (req.headers['accept-language'] || req.headers['Accept-Language']));
|
|
757
861
|
if (acceptLanguage) {
|
|
758
862
|
const langPart = acceptLanguage.split(',')[0];
|
|
759
863
|
const a = normalizeLocaleCandidate(langPart);
|
|
@@ -1062,10 +1166,10 @@ function mountPages(app, options) {
|
|
|
1062
1166
|
}
|
|
1063
1167
|
}
|
|
1064
1168
|
// Same instance as createApp({ db }) / getAppContext().db — available to handler & route middleware
|
|
1065
|
-
if (db != null) {
|
|
1169
|
+
if (db != null && !req.db) {
|
|
1066
1170
|
req.db = db;
|
|
1067
1171
|
}
|
|
1068
|
-
if (serviceRegistry != null) {
|
|
1172
|
+
if (serviceRegistry != null && !req.service) {
|
|
1069
1173
|
req.service = (name, input, opts) =>
|
|
1070
1174
|
serviceRegistry.call(name, input, { req, res, db }, opts);
|
|
1071
1175
|
}
|
|
@@ -1157,20 +1261,38 @@ function mountPages(app, options) {
|
|
|
1157
1261
|
}
|
|
1158
1262
|
}
|
|
1159
1263
|
// Detect locale
|
|
1160
|
-
const
|
|
1161
|
-
const defaultLocale =
|
|
1264
|
+
const supported = parseSupportedLocaleSet();
|
|
1265
|
+
const defaultLocale = getDefaultLocale(supported);
|
|
1266
|
+
const locale = detectLocale(req, supported, defaultLocale);
|
|
1162
1267
|
|
|
1163
1268
|
// Load translations (primary + fallback)
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1269
|
+
let t;
|
|
1270
|
+
if (!isDev) {
|
|
1271
|
+
const transKey = `${absolutePagesDir}::${route.routeDir}::${locale}::${defaultLocale}`;
|
|
1272
|
+
t = translatorCache.get(transKey);
|
|
1273
|
+
if (!t) {
|
|
1274
|
+
const translations = loadI18n(absolutePagesDir, route.routeDir, locale, false);
|
|
1275
|
+
const fallbackTranslations = (locale !== defaultLocale)
|
|
1276
|
+
? loadI18n(absolutePagesDir, route.routeDir, defaultLocale, false)
|
|
1277
|
+
: EMPTY_OBJECT;
|
|
1278
|
+
t = createTranslator(translations, {
|
|
1279
|
+
locale,
|
|
1280
|
+
fallbackTranslations,
|
|
1281
|
+
fallbackLocale: defaultLocale,
|
|
1282
|
+
});
|
|
1283
|
+
translatorCache.set(transKey, t);
|
|
1284
|
+
}
|
|
1285
|
+
} else {
|
|
1286
|
+
const translations = loadI18n(absolutePagesDir, route.routeDir, locale, true);
|
|
1287
|
+
const fallbackTranslations = (locale !== defaultLocale)
|
|
1288
|
+
? loadI18n(absolutePagesDir, route.routeDir, defaultLocale, true)
|
|
1289
|
+
: EMPTY_OBJECT;
|
|
1290
|
+
t = createTranslator(translations, {
|
|
1291
|
+
locale,
|
|
1292
|
+
fallbackTranslations,
|
|
1293
|
+
fallbackLocale: defaultLocale,
|
|
1294
|
+
});
|
|
1295
|
+
}
|
|
1174
1296
|
|
|
1175
1297
|
// Load route config
|
|
1176
1298
|
const config = isDev ? loadRouteConfig(route.configPath, true) : mountConfig;
|
|
@@ -1188,6 +1310,8 @@ function mountPages(app, options) {
|
|
|
1188
1310
|
}
|
|
1189
1311
|
|
|
1190
1312
|
const njkTpl = loadNjkRouteTemplate(route.fullPath, isDev);
|
|
1313
|
+
const metaTitle = t('meta.title');
|
|
1314
|
+
const metaDesc = t('meta.description');
|
|
1191
1315
|
|
|
1192
1316
|
const ctx = {
|
|
1193
1317
|
req,
|
|
@@ -1206,8 +1330,8 @@ function mountPages(app, options) {
|
|
|
1206
1330
|
},
|
|
1207
1331
|
data: { ...njkTpl.dataPatch },
|
|
1208
1332
|
meta: {
|
|
1209
|
-
title:
|
|
1210
|
-
description:
|
|
1333
|
+
title: metaTitle !== 'meta.title' ? metaTitle : null,
|
|
1334
|
+
description: metaDesc !== 'meta.description' ? metaDesc : null,
|
|
1211
1335
|
indexable: true,
|
|
1212
1336
|
canonical: null,
|
|
1213
1337
|
...njkTpl.metaPatch,
|
|
@@ -1219,16 +1343,16 @@ function mountPages(app, options) {
|
|
|
1219
1343
|
req.context = ctx;
|
|
1220
1344
|
|
|
1221
1345
|
// Execute hooks: onRequest
|
|
1222
|
-
|
|
1223
|
-
|
|
1346
|
+
let hookPromise = runHooks(globalHooks, routeHooks, 'onRequest', ctx);
|
|
1347
|
+
if (hookPromise) await hookPromise;
|
|
1224
1348
|
|
|
1225
1349
|
// Execute hooks: onRoute
|
|
1226
|
-
|
|
1227
|
-
|
|
1350
|
+
hookPromise = runHooks(globalHooks, routeHooks, 'onRoute', ctx);
|
|
1351
|
+
if (hookPromise) await hookPromise;
|
|
1228
1352
|
|
|
1229
1353
|
// Execute hooks: beforeMiddleware
|
|
1230
|
-
|
|
1231
|
-
|
|
1354
|
+
hookPromise = runHooks(globalHooks, routeHooks, 'beforeMiddleware', ctx);
|
|
1355
|
+
if (hookPromise) await hookPromise;
|
|
1232
1356
|
|
|
1233
1357
|
// Run route middleware (chain fixed at route registration; edit middleware in dev → restart)
|
|
1234
1358
|
if (preResolvedPageMw.length) {
|
|
@@ -1243,8 +1367,8 @@ function mountPages(app, options) {
|
|
|
1243
1367
|
}
|
|
1244
1368
|
|
|
1245
1369
|
// Execute hooks: afterMiddleware
|
|
1246
|
-
|
|
1247
|
-
|
|
1370
|
+
hookPromise = runHooks(globalHooks, routeHooks, 'afterMiddleware', ctx);
|
|
1371
|
+
if (hookPromise) await hookPromise;
|
|
1248
1372
|
|
|
1249
1373
|
// Execute hooks: beforeLoad
|
|
1250
1374
|
if (pluginManager && db) {
|
|
@@ -1253,8 +1377,8 @@ function mountPages(app, options) {
|
|
|
1253
1377
|
ctx.content = contentApi.getContentService(db);
|
|
1254
1378
|
}
|
|
1255
1379
|
}
|
|
1256
|
-
|
|
1257
|
-
|
|
1380
|
+
hookPromise = runHooks(globalHooks, routeHooks, 'beforeLoad', ctx);
|
|
1381
|
+
if (hookPromise) await hookPromise;
|
|
1258
1382
|
|
|
1259
1383
|
// Run load function
|
|
1260
1384
|
if (config?.load && typeof config.load === 'function') {
|
|
@@ -1265,8 +1389,8 @@ function mountPages(app, options) {
|
|
|
1265
1389
|
}
|
|
1266
1390
|
|
|
1267
1391
|
// Execute hooks: afterLoad
|
|
1268
|
-
|
|
1269
|
-
|
|
1392
|
+
hookPromise = runHooks(globalHooks, routeHooks, 'afterLoad', ctx);
|
|
1393
|
+
if (hookPromise) await hookPromise;
|
|
1270
1394
|
|
|
1271
1395
|
// Run meta function
|
|
1272
1396
|
if (config?.meta && typeof config.meta === 'function') {
|
|
@@ -1277,10 +1401,18 @@ function mountPages(app, options) {
|
|
|
1277
1401
|
}
|
|
1278
1402
|
|
|
1279
1403
|
// Execute hooks: beforeRender
|
|
1280
|
-
|
|
1281
|
-
|
|
1404
|
+
hookPromise = runHooks(globalHooks, routeHooks, 'beforeRender', ctx);
|
|
1405
|
+
if (hookPromise) await hookPromise;
|
|
1282
1406
|
|
|
1283
|
-
const pageAssetBundle = applyPageAssetsToTemplateData(
|
|
1407
|
+
const pageAssetBundle = applyPageAssetsToTemplateData(
|
|
1408
|
+
pageAssetsResolved,
|
|
1409
|
+
ctx.data,
|
|
1410
|
+
{
|
|
1411
|
+
file: route.file,
|
|
1412
|
+
routePath: route.routePath,
|
|
1413
|
+
},
|
|
1414
|
+
ctx.fsy?.assetManager || getAssetManager()
|
|
1415
|
+
);
|
|
1284
1416
|
ctx.data = pageAssetBundle.data;
|
|
1285
1417
|
const renderContext = {
|
|
1286
1418
|
...ctx.data,
|
|
@@ -1314,10 +1446,24 @@ function mountPages(app, options) {
|
|
|
1314
1446
|
});
|
|
1315
1447
|
}
|
|
1316
1448
|
|
|
1317
|
-
let html
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
:
|
|
1449
|
+
let html;
|
|
1450
|
+
if (njkTpl.useStringRender && njkTpl.templateBody != null) {
|
|
1451
|
+
if (typeof nunjucks.Template === 'function') {
|
|
1452
|
+
let compiled = isDev ? null : compiledStringTemplateCache.get(route.fullPath);
|
|
1453
|
+
if (!compiled || (isDev && compiled._templateBody !== njkTpl.templateBody)) {
|
|
1454
|
+
compiled = new nunjucks.Template(njkTpl.templateBody, nunjucks, route.fullPath);
|
|
1455
|
+
compiled._templateBody = njkTpl.templateBody;
|
|
1456
|
+
compiledStringTemplateCache.set(route.fullPath, compiled);
|
|
1457
|
+
}
|
|
1458
|
+
html = compiled.render(renderContext);
|
|
1459
|
+
} else if (typeof nunjucks.renderString === 'function') {
|
|
1460
|
+
html = nunjucks.renderString(njkTpl.templateBody, renderContext, { path: route.fullPath });
|
|
1461
|
+
} else {
|
|
1462
|
+
html = nunjucks.render(templatePath, renderContext);
|
|
1463
|
+
}
|
|
1464
|
+
} else {
|
|
1465
|
+
html = nunjucks.render(templatePath, renderContext);
|
|
1466
|
+
}
|
|
1321
1467
|
|
|
1322
1468
|
if (pluginManager) {
|
|
1323
1469
|
const contentApi = pluginManager.getPluginAPI('content');
|
|
@@ -1328,8 +1474,8 @@ function mountPages(app, options) {
|
|
|
1328
1474
|
|
|
1329
1475
|
// Execute hooks: afterRender
|
|
1330
1476
|
ctx.html = html;
|
|
1331
|
-
|
|
1332
|
-
|
|
1477
|
+
hookPromise = runHooks(globalHooks, routeHooks, 'afterRender', ctx);
|
|
1478
|
+
if (hookPromise) await hookPromise;
|
|
1333
1479
|
|
|
1334
1480
|
if (route.routePath === '/404' || route.file === '404.njk') {
|
|
1335
1481
|
res.status(404);
|
|
@@ -1402,5 +1548,9 @@ module.exports = {
|
|
|
1402
1548
|
frontmatterToPatches,
|
|
1403
1549
|
loadNjkRouteTemplate,
|
|
1404
1550
|
clearNjkFrontmatterCaches,
|
|
1551
|
+
compiledStringTemplateCache,
|
|
1552
|
+
clearCompiledStringTemplateCache,
|
|
1553
|
+
translatorCache,
|
|
1554
|
+
clearTranslatorCache,
|
|
1405
1555
|
};
|
|
1406
1556
|
|