@monkeyplus/flow 3.7.5 → 4.0.0-beta.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/dist/index.mjs ADDED
@@ -0,0 +1,1034 @@
1
+ import path, { join } from 'path';
2
+ import { applyToDefaults } from '@hapi/hoek';
3
+ import consola from 'consola';
4
+ import * as R from 'ramda';
5
+ import { flatten, dissoc, groupBy, mapObjIndexed } from 'ramda';
6
+ import { notFound } from '@hapi/boom';
7
+ import os from 'os';
8
+ import chalk from 'chalk';
9
+ import fs from 'fs-extra';
10
+ import fs$1 from 'fs';
11
+ import { createHooks } from 'hookable';
12
+ import chokidar from 'chokidar';
13
+
14
+ const name = "@monkeyplus/flow";
15
+ const version = "4.0.0-beta.10";
16
+ const description = "Utils hapi";
17
+ const author = "Andres Navarrete";
18
+ const license = "MIT";
19
+ const main = "./dist/index.cjs";
20
+ const module = "./dist/index.mjs";
21
+ const types = "./dist/index.d.ts";
22
+ const files = [
23
+ "dist/",
24
+ "types/"
25
+ ];
26
+ const exports = {
27
+ ".": {
28
+ "import": "./dist/index.mjs",
29
+ require: "./dist/index.cjs"
30
+ }
31
+ };
32
+ const scripts = {
33
+ build: "unbuild",
34
+ dev: "pnpm stub && pnpm types",
35
+ stub: "unbuild --stub",
36
+ types: "tsup ./src/index.ts --dts-only --external types && esno ../../scripts/replace",
37
+ lint: "eslint --ext .js,.ts .",
38
+ fix: "eslint --fix --ext .ts .",
39
+ prepublishOnly: "pnpm run build",
40
+ start: "esno src/index.ts",
41
+ test: "vitest"
42
+ };
43
+ const dependencies$1 = {
44
+ "@hapi/boom": "9.x.x",
45
+ "@hapi/hoek": "9.x.x",
46
+ consola: "^2.15.3",
47
+ chalk: "^4.1.2",
48
+ chokidar: "^3.5.3",
49
+ hookable: "^5.1.1",
50
+ "fs-extra": "^10.0.0",
51
+ ramda: "^0.28.0"
52
+ };
53
+ const devDependencies = {
54
+ "@types/fs-extra": "^9.0.13",
55
+ "@types/hapi__hapi": "^20.0.10",
56
+ "@types/hapi__nes": "^11.0.5",
57
+ "@types/hapi__vision": "^5.5.3",
58
+ "@types/ramda": "^0.27.64"
59
+ };
60
+ const peerDependencies = {
61
+ "@hapi/hapi": "^20.x"
62
+ };
63
+ const gitHead = "1d5387b9c77909019f36d360a0ac53ddaa40e4af";
64
+ const pkg = {
65
+ name: name,
66
+ version: version,
67
+ description: description,
68
+ author: author,
69
+ license: license,
70
+ main: main,
71
+ module: module,
72
+ types: types,
73
+ files: files,
74
+ exports: exports,
75
+ scripts: scripts,
76
+ dependencies: dependencies$1,
77
+ devDependencies: devDependencies,
78
+ peerDependencies: peerDependencies,
79
+ gitHead: gitHead
80
+ };
81
+
82
+ const logger$1 = consola.withScope(pkg.name);
83
+ const dependencies = {
84
+ "@hapi/vision": "6.x.x",
85
+ "@hapi/inert": "6.x.x",
86
+ "@hapi/h2o2": "9.x.x",
87
+ "@hapi/nes": "12.x.x"
88
+ };
89
+ const isProduction = process.env.NODE_ENV === "production";
90
+ const isGenerate = process.env.GENERATE;
91
+
92
+ const LifeCircle = {
93
+ register: (server) => {
94
+ server.ext("onPreStart", async () => {
95
+ await server.methods.flow.pages();
96
+ });
97
+ server.ext("onRequest", async (req, h) => {
98
+ await server.methods.flow.pages();
99
+ const stateRequest = {
100
+ global: {},
101
+ local: {},
102
+ context: {},
103
+ utils: {},
104
+ extensions: {}
105
+ };
106
+ req.plugins.flow = stateRequest;
107
+ const { flow: state } = server.app;
108
+ const pathRedirect = state.pages.redirects[req.path];
109
+ if (state.truncates[req.path])
110
+ return h.redirect("/").takeover();
111
+ if (pathRedirect)
112
+ return h.redirect(pathRedirect).takeover();
113
+ return h.continue;
114
+ });
115
+ server.ext("onPreHandler", async (req, h) => {
116
+ const { dynamic } = req.route.settings.plugins?.flow || {};
117
+ const flow = req.route.settings.app?.flow;
118
+ if (!dynamic)
119
+ return h.continue;
120
+ const pages = await dynamic.pages({
121
+ server,
122
+ route: req.route,
123
+ locale: flow?.locale
124
+ });
125
+ if (!pages || !pages.length)
126
+ return h.continue;
127
+ const page = pages.find((_page) => _page.url === req.params.url);
128
+ if (!page)
129
+ return notFound();
130
+ Object.assign(req.plugins.flow.local, {
131
+ [dynamic.assign || "dynamic"]: page.context ?? { a: "" }
132
+ });
133
+ req.app.dynamic = R.omit(["context"], page);
134
+ return h.continue;
135
+ });
136
+ }
137
+ };
138
+
139
+ const ServerInfo = {
140
+ register: (server) => {
141
+ const serverInfo = () => {
142
+ const interfaces = os.networkInterfaces();
143
+ const hostname = "localhost";
144
+ const protocol = "http";
145
+ console.log("");
146
+ logger$1.success(chalk.green(`Flow v${pkg.version} dev server running at:`));
147
+ console.log("");
148
+ Object.keys(interfaces).forEach((key) => (interfaces[key] || []).filter((details) => details.family === "IPv4").map((detail) => {
149
+ return {
150
+ type: detail.address.includes("127.0.0.1") ? "Local: " : "Network: ",
151
+ host: detail.address.replace("127.0.0.1", hostname)
152
+ };
153
+ }).forEach(({ type, host }) => {
154
+ const url = `${protocol}://${host}:${chalk.bold(server.info.port)}`;
155
+ console.log(` > ${type} ${chalk.cyan(url)}`);
156
+ }));
157
+ };
158
+ return { serverInfo };
159
+ }
160
+ };
161
+
162
+ function getLocalDefaults(loc, opts) {
163
+ const bundle = R.path(["locales", loc, "view", "bundle"], opts);
164
+ const view = R.path(["locales", loc, "view"], opts);
165
+ const runContext = R.path(["locales", loc, "context"], opts);
166
+ const seo = R.path(["locales", loc, "seo"], opts);
167
+ const blogInfo = R.path(["locales", loc, "blogInfo"], opts);
168
+ return {
169
+ bundle,
170
+ view,
171
+ runContext,
172
+ seo: seo || {},
173
+ blogInfo
174
+ };
175
+ }
176
+ function getLevel(locale, defLevel) {
177
+ const level = typeof defLevel === "string" ? defLevel : typeof defLevel === "object" ? defLevel.locales[locale] || defLevel.default : "";
178
+ return level;
179
+ }
180
+ const definePages = (options) => (defaultLocale) => (levelOptions = {}) => {
181
+ const prefixName = levelOptions.prefixName || "";
182
+ const pages = (locales) => locales.map(({ locale, url, seo }) => {
183
+ const page = getLocalDefaults(locale, options);
184
+ const levelBase = getLevel(locale, levelOptions.level);
185
+ const level = getLevel(locale, options.level);
186
+ const [language, ubication] = locale.split("-");
187
+ const prefixLang = language === defaultLocale.language ? "" : language;
188
+ const prefixUbication = ubication === defaultLocale.ubication ? "" : ubication;
189
+ const prefixLevel = path.posix.join("/", prefixLang, prefixUbication, levelBase, level);
190
+ const publicPath = defaultLocale.publicPath || "/";
191
+ const fullPath = path.posix.join("/", publicPath, prefixLevel, url);
192
+ const redirect = R.last(fullPath) === "/" ? path.posix.join(fullPath, "index.html") : void 0;
193
+ const app = {
194
+ localeName: path.posix.join(prefixName, locale, options.name),
195
+ name: options.name,
196
+ locale,
197
+ language,
198
+ level,
199
+ redirect,
200
+ seo: R.mergeDeepLeft(seo || {}, page.seo),
201
+ blogInfo: page.blogInfo,
202
+ urlObject: {
203
+ publicPath,
204
+ level: prefixLevel,
205
+ url,
206
+ path: fullPath,
207
+ static: R.last(fullPath) === "/" ? `${fullPath}index.html` : `${fullPath}.html`
208
+ },
209
+ view: { ...options.view, ...page.view },
210
+ runSharedContext: options.context,
211
+ runLocalContext: page.runContext
212
+ };
213
+ return app;
214
+ });
215
+ return {
216
+ name: options.name,
217
+ pages
218
+ };
219
+ };
220
+
221
+ const handlerPage = async (req, h) => {
222
+ const { configs } = req.server.plugins.flow;
223
+ const { flow } = req.route.settings.app;
224
+ const { dynamic } = req.app;
225
+ const local = {};
226
+ const global = {};
227
+ const page = R.clone({
228
+ ...R.pick(["publicPath", "path"], flow.urlObject),
229
+ ...R.pick([
230
+ "name",
231
+ "localeName",
232
+ "locale",
233
+ "level",
234
+ "template",
235
+ "bundle",
236
+ "language"
237
+ ], flow)
238
+ });
239
+ if (dynamic) {
240
+ Object.assign(page, {
241
+ path: dynamic.url,
242
+ ...R.omit(["url"], dynamic)
243
+ });
244
+ }
245
+ const utils = {
246
+ ...req.plugins.flow.utils,
247
+ getLocalUrl: (name) => {
248
+ return h.getUrl(flow.locale, name);
249
+ },
250
+ getUrl: h.getUrl
251
+ };
252
+ const seo = {
253
+ ...flow.seo,
254
+ ...req.pre.seo,
255
+ url: configs.url,
256
+ pageUrl: `${configs.url}${req.url.pathname}`
257
+ };
258
+ const contextMethod = {
259
+ seo,
260
+ req,
261
+ h,
262
+ page,
263
+ extensions: req.plugins.flow.extensions,
264
+ global: req.plugins.flow.global,
265
+ utils
266
+ };
267
+ const extraContexts = req.plugins.flow.context;
268
+ for (const key in extraContexts) {
269
+ if (extraContexts[key]?.method) {
270
+ const assign = extraContexts[key].assign;
271
+ const data = await extraContexts[key].method(contextMethod);
272
+ const _context = { [key]: data };
273
+ if (assign === "global")
274
+ Object.assign(global, _context);
275
+ else if (assign === "local")
276
+ Object.assign(local, _context);
277
+ else
278
+ Object.assign(seo, _context);
279
+ }
280
+ }
281
+ if (flow.runSharedContext) {
282
+ const _context = await flow.runSharedContext(contextMethod);
283
+ Object.assign(local, _context);
284
+ }
285
+ if (flow.runLocalContext) {
286
+ const _context = await flow.runLocalContext(contextMethod);
287
+ Object.assign(local, _context);
288
+ }
289
+ const context = {
290
+ view: flow.view,
291
+ context: Object.freeze({ ...local, ...req.plugins.flow.local }),
292
+ seo,
293
+ page,
294
+ utils: {},
295
+ hapi: { req, h },
296
+ global: Object.freeze({ ...global, ...req.plugins.flow.global })
297
+ };
298
+ if (req.query.context) {
299
+ context.utils = Object.keys(utils);
300
+ context.hapi = ["req", "h"];
301
+ return context;
302
+ }
303
+ context.utils = utils;
304
+ return h.view(`${configs.dirTemplates}${flow.view.layout || "default"}`, context);
305
+ };
306
+
307
+ const logger = consola.withScope("@monkeyplus/flow");
308
+ const defaults = {
309
+ locales: ["es-ec"],
310
+ defaultUbication: "ec",
311
+ defaultLanguage: "es",
312
+ staticDir: "static",
313
+ hmr: {
314
+ dirs: ["views"]
315
+ },
316
+ publicPath: "/",
317
+ locale: "es-ec",
318
+ relativeTo: "",
319
+ dirTemplates: "",
320
+ outputDir: "build",
321
+ url: process.env.CUSTOM_URL || process.env.URL,
322
+ engines: ["eta"],
323
+ plugins: {}
324
+ };
325
+ function getConfigs(server, options) {
326
+ const relativeTo = path.resolve(options.relativeTo);
327
+ const getObject = (object) => {
328
+ const config = applyToDefaults(defaults, { ...object, ...options });
329
+ return {
330
+ ...config,
331
+ relativeTo,
332
+ locale: `${config.defaultLanguage}-${config.defaultUbication}`
333
+ };
334
+ };
335
+ try {
336
+ const pathWeb = path.resolve(options.relativeTo, "flow.config");
337
+ logger.info("Flow apply settings from file: flow.config");
338
+ const configBase = require(pathWeb);
339
+ const config = getObject(configBase);
340
+ return config;
341
+ } catch (error) {
342
+ logger.info("Flow no config file apply default settings");
343
+ return getObject({});
344
+ }
345
+ }
346
+ const isDinamyc = (url) => /\{url?(.+)\}/.test(url);
347
+
348
+ const Decorators = {
349
+ register: (server) => {
350
+ const { flow: state } = server.app;
351
+ const { configs, helpers } = server.plugins.flow;
352
+ const flowDecorate = (route, options) => {
353
+ const { plugins, app } = route.settings;
354
+ Object.assign(plugins, { generate: ".html" });
355
+ const flow = app.flow;
356
+ const runCommons = (pageInfo) => {
357
+ helpers.addStaticPage(pageInfo.localeName, {
358
+ url: route.path,
359
+ locale: pageInfo.locale,
360
+ localeName: pageInfo.localeName,
361
+ name: pageInfo.name
362
+ });
363
+ if (pageInfo.redirect)
364
+ helpers.addRedirect(pageInfo.redirect, route.path);
365
+ };
366
+ if (flow) {
367
+ runCommons(flow);
368
+ } else {
369
+ const routes = definePages({
370
+ name: options.name,
371
+ view: options.view,
372
+ locales: {
373
+ [options.locale || configs.locale]: {}
374
+ },
375
+ context: options.context
376
+ })({
377
+ language: configs.defaultLanguage,
378
+ ubication: configs.defaultUbication,
379
+ publicPath: configs.publicPath
380
+ })().pages([
381
+ { locale: options.locale || configs.locale, url: route.path }
382
+ ]);
383
+ const pageInfo = routes[0];
384
+ runCommons(pageInfo);
385
+ Object.assign(app, { flow: pageInfo, content: options.content });
386
+ }
387
+ const extensions = plugins?.flow?.extensions || {};
388
+ if (isDinamyc(route.path)) {
389
+ if (!route.settings.plugins?.flow?.dynamic)
390
+ throw new Error("Dynamic pages require a method");
391
+ helpers.addDynamicPages(flow.localeName, ((options2) => async () => {
392
+ const _pages = await route.settings.plugins?.flow?.dynamic?.pages(options2);
393
+ const obj = {};
394
+ for (const sPage of _pages || []) {
395
+ const localeName = `${app?.flow.localeName}/${sPage.name}`;
396
+ obj[localeName] = {
397
+ locale: app?.flow.locale,
398
+ localeName,
399
+ name: localeName.replace(`${app?.flow.locale}/`, ""),
400
+ url: route.path.replace("{url*}", sPage.url),
401
+ context: sPage.context,
402
+ dynamicSlug: sPage.dynamicSlug
403
+ };
404
+ }
405
+ return obj;
406
+ })({
407
+ server,
408
+ route,
409
+ locale: app?.flow.locale
410
+ }));
411
+ }
412
+ for (const extension in extensions) {
413
+ const optionExtension = plugins?.flow?.extensions[extension];
414
+ const routeMethod = state.extensions[extension]?.routeMethod;
415
+ if (routeMethod)
416
+ routeMethod({ pageInfo: app.flow, route }, optionExtension);
417
+ }
418
+ return handlerPage;
419
+ };
420
+ const getUrl = (_locale, _name) => {
421
+ try {
422
+ const pageName = path.posix.join(_locale, _name);
423
+ const page = state.pages.all[pageName];
424
+ if (page) {
425
+ return page.url;
426
+ } else {
427
+ logger$1.warn("Not found link with name: %s", pageName);
428
+ return "/404";
429
+ }
430
+ } catch (error) {
431
+ logger$1.error(error);
432
+ return "/";
433
+ }
434
+ };
435
+ server.method("flow.getUrl", getUrl, {});
436
+ server.decorate("handler", "flow", flowDecorate);
437
+ server.decorate("toolkit", "getUrl", server.methods.flow.getUrl);
438
+ }
439
+ };
440
+
441
+ const useGenerator = {
442
+ register: (server) => {
443
+ const { configs, helpers } = server.plugins.flow;
444
+ const getFlowPages = async () => {
445
+ const files = {};
446
+ const allPages = await server.methods.flow.pages();
447
+ const pages = Object.values(allPages);
448
+ logger.success("Read %i static pages", pages.length);
449
+ for (const page of pages)
450
+ files[page.url] = ".html";
451
+ const routes = server.table();
452
+ for (const route of routes) {
453
+ const isDynamic = /\{url?(.+)\}/.test(route.path);
454
+ const generate = route.settings.plugins?.generate;
455
+ if (!isDynamic && generate) {
456
+ if (!files[route.path])
457
+ files[route.path] = generate;
458
+ }
459
+ }
460
+ return files;
461
+ };
462
+ const writeFile = async (file, payload) => {
463
+ await fs.ensureFile(file);
464
+ await fs.writeFile(file, payload, "utf8");
465
+ };
466
+ const useCreateFile = (isVirtual = false) => async (dir, url, ext) => {
467
+ if (typeof ext === "object") {
468
+ if (!isVirtual) {
469
+ const dirFile = path.dirname(url);
470
+ await fs.ensureDir(path.join(dir, dirFile));
471
+ const file = await fs.readFile(ext.file);
472
+ await fs.writeFile(path.join(dir, url), file);
473
+ }
474
+ return { type: ext.type || "assets", url };
475
+ } else {
476
+ const r = await server.inject({
477
+ url,
478
+ method: "get"
479
+ });
480
+ let file = "";
481
+ if (typeof ext === "boolean") {
482
+ if (!isVirtual) {
483
+ file = path.join(dir, url);
484
+ await writeFile(file, r.payload);
485
+ }
486
+ return { type: "assets", url };
487
+ } else if (typeof ext === "string") {
488
+ if (ext === ".html") {
489
+ if (!isVirtual) {
490
+ const isIndex = R.last(url) === "/";
491
+ const nameFile = isIndex ? `${url}index.html` : `${url}.html`;
492
+ file = path.join(dir, nameFile);
493
+ await writeFile(file, r.payload);
494
+ }
495
+ return { type: "page", url };
496
+ }
497
+ }
498
+ return { type: "asset", url };
499
+ }
500
+ };
501
+ const injectAssets = (assets) => {
502
+ for (const asset of assets) {
503
+ const dirs = asset.dirs;
504
+ for (const dir of dirs) {
505
+ const dirFiles = fs.readdirSync(path.join(asset.relativeTo, dir));
506
+ for (const file of dirFiles) {
507
+ const pathFile = path.join(asset.relativeTo, dir, file);
508
+ const ext = path.extname(pathFile);
509
+ if (!!ext) {
510
+ const pathRoute = path.posix.join("/", asset.prefix, dir, file);
511
+ const pathRouteOverride = asset.override?.[pathRoute];
512
+ server.route({
513
+ path: pathRouteOverride || pathRoute,
514
+ method: "get",
515
+ options: {
516
+ plugins: {
517
+ generate: {
518
+ isAsset: true,
519
+ file: pathFile,
520
+ type: asset.type,
521
+ isRemane: !!pathRouteOverride
522
+ }
523
+ }
524
+ },
525
+ handler: {
526
+ file: {
527
+ path: pathFile,
528
+ confine: false
529
+ }
530
+ }
531
+ });
532
+ } else {
533
+ injectAssets([
534
+ {
535
+ dirs: [path.posix.join(dir, file)],
536
+ prefix: asset.prefix,
537
+ relativeTo: asset.relativeTo,
538
+ override: asset.override,
539
+ type: asset.type || file
540
+ }
541
+ ]);
542
+ }
543
+ }
544
+ }
545
+ }
546
+ };
547
+ const runGenerate = async ({
548
+ assets = [],
549
+ ommitAssets = [],
550
+ virtualGenerate = false
551
+ }) => {
552
+ await server.methods.flow.pages();
553
+ const _assetsApp = [...Object.values(server.app.flow.generate.folders)];
554
+ const _assets = [...assets, ..._assetsApp];
555
+ if (!virtualGenerate)
556
+ injectAssets(_assets);
557
+ const dirOutut = helpers.getPath(configs.outputDir ?? "build");
558
+ const staticDirs = Object.keys(server.app.flow.generate.staticFolders).map((dir) => helpers.getPath(dir));
559
+ const files = await getFlowPages();
560
+ const pairFiles = R.toPairs(files);
561
+ const ommit = ommitAssets;
562
+ try {
563
+ await fs.remove(dirOutut);
564
+ for (const dirStatic of staticDirs)
565
+ await fs.copy(dirStatic, dirOutut);
566
+ for (const omitFile of ommit)
567
+ await fs.remove(path.join(dirOutut, omitFile));
568
+ const createFile = useCreateFile(virtualGenerate);
569
+ const files2 = await Promise.all(pairFiles.map(async (v) => {
570
+ return await createFile(dirOutut, ...v);
571
+ }));
572
+ const groups = R.groupBy((file) => {
573
+ if (file.url.includes("images/"))
574
+ return "image";
575
+ else
576
+ return (path.extname(file.url) || file.type).toLowerCase();
577
+ }, files2);
578
+ const _groups = {
579
+ pages: 0,
580
+ js: 0,
581
+ css: 0,
582
+ images: 0,
583
+ fonts: 0,
584
+ others: 0
585
+ };
586
+ for (const type in groups) {
587
+ const cp = (ext) => ext === type;
588
+ const qty = groups[type].length;
589
+ if (type === "page")
590
+ _groups.pages += qty;
591
+ else if (type === ".js")
592
+ _groups.js += qty;
593
+ else if (type === ".css")
594
+ _groups.css += qty;
595
+ else if ([".gif", ".png", ".jpg", ".jpeg", "image"].find(cp))
596
+ _groups.images += qty;
597
+ else if ([".woff", ".eot", ".ttf"].find(cp))
598
+ _groups.fonts += qty;
599
+ else
600
+ _groups.others += qty;
601
+ }
602
+ for (const type in _groups)
603
+ logger.success("%i %s generated", _groups[type], type);
604
+ const bf = server.app.flow.generate.postGenerate;
605
+ if (Object.keys(bf).length) {
606
+ logger.info("Post generate");
607
+ }
608
+ for (const key in bf)
609
+ await bf[key]();
610
+ } catch (error) {
611
+ logger.error(error);
612
+ await fs.remove(dirOutut);
613
+ throw new Error(error);
614
+ }
615
+ };
616
+ return { runGenerate };
617
+ }
618
+ };
619
+
620
+ const definePage = (pageOptions) => (levelOptions) => (configs) => {
621
+ const pages = [];
622
+ for (const locale in pageOptions.locales) {
623
+ if (configs.locales.find((_locale) => _locale === locale)) {
624
+ const singlePage = pageOptions.locales[locale];
625
+ pages.push({ url: singlePage.url, locale });
626
+ }
627
+ }
628
+ const infoPages = definePages({
629
+ name: pageOptions.name,
630
+ view: pageOptions.view,
631
+ context: pageOptions.context,
632
+ level: pageOptions.level,
633
+ locales: pageOptions.locales
634
+ })({
635
+ language: configs.defaultLanguage,
636
+ ubication: configs.defaultUbication,
637
+ publicPath: configs.publicPath
638
+ })(levelOptions).pages(pages);
639
+ const {
640
+ options: sharedOptions,
641
+ rules: sharedRules,
642
+ vhost: sharedVHost
643
+ } = pageOptions;
644
+ const sharedPre = sharedOptions?.pre || [];
645
+ const routes = infoPages.map((page) => {
646
+ const localPage = pageOptions.locales[page.locale];
647
+ const route = {
648
+ path: page.urlObject.path,
649
+ method: "get",
650
+ options: {
651
+ app: {
652
+ flow: page
653
+ },
654
+ plugins: {
655
+ flow: {
656
+ extensions: pageOptions.extensions || {},
657
+ dynamic: localPage.dynamic
658
+ }
659
+ }
660
+ },
661
+ handler: {
662
+ flow: {
663
+ name: page.localeName,
664
+ locale: page.locale,
665
+ view: page.view
666
+ }
667
+ }
668
+ };
669
+ if (sharedRules || localPage.rules)
670
+ route.rules = localPage.rules || sharedRules;
671
+ if (sharedVHost || localPage.vhost)
672
+ route.vhost = localPage.vhost || sharedVHost;
673
+ if (sharedOptions || localPage.options) {
674
+ const localOptions = localPage?.options || {};
675
+ const localPre = localPage?.options?.pre || [];
676
+ const pre = [...sharedPre, ...localPre];
677
+ const options = R.mergeDeepRight(sharedOptions || {}, localOptions);
678
+ route.options = R.mergeDeepRight(options, route.options);
679
+ if (route.options && pre.length)
680
+ route.options.pre = pre;
681
+ }
682
+ return route;
683
+ });
684
+ return routes;
685
+ };
686
+
687
+ const readPagesDir = (pathDir, folder) => {
688
+ const p = fs$1.readdirSync(pathDir);
689
+ return flatten(p.filter((f) => !f.includes(".js.map") && !f.includes(" copy.js.map") && !f.includes(" copy.js") && !f.includes(".model")).map((e) => {
690
+ if (e.includes(".js") || e.includes(".ts")) {
691
+ return {
692
+ path: path.join(pathDir, e),
693
+ prefix: folder
694
+ };
695
+ } else {
696
+ return readPagesDir(path.join(pathDir, e), e);
697
+ }
698
+ }));
699
+ };
700
+ const registerPages = async (server, dir) => {
701
+ const dirPages = path.resolve(dir || "./pages");
702
+ const listFiles = readPagesDir(dirPages);
703
+ const listPrePages = [];
704
+ for (const item of listFiles) {
705
+ try {
706
+ const module = require(item.path)?.default;
707
+ if (typeof module === "function") {
708
+ listPrePages.push(module());
709
+ } else if (typeof module === "object") {
710
+ if (Array.isArray(module)) {
711
+ module.forEach((el) => listPrePages.push(el()));
712
+ } else {
713
+ const _pages = await module.pages({ server });
714
+ if (Array.isArray(_pages))
715
+ _pages.forEach((page) => listPrePages.push(definePage(page)()));
716
+ else
717
+ listPrePages.push(definePage(_pages)());
718
+ }
719
+ }
720
+ } catch (details) {
721
+ logger$1.error(details);
722
+ }
723
+ }
724
+ for (const page of listPrePages)
725
+ await server.flow.addPage(page);
726
+ };
727
+
728
+ const RunMethods = {
729
+ register: (server) => {
730
+ const { flow: state } = server.app;
731
+ const { configs: config } = server.plugins.flow;
732
+ const registerRoute = (route) => {
733
+ const _routes = server.table();
734
+ const _route = _routes.find((v) => v.path === route.path);
735
+ if (_route)
736
+ logger$1.warn(`Duplicate route, the route ${route.path} already exists`);
737
+ else
738
+ server.route(route);
739
+ };
740
+ const addPage = (_page, extra) => {
741
+ const registerPages2 = (_pages2) => {
742
+ for (const _route of _pages2) {
743
+ if (state.routes[_route.path])
744
+ logger$1.warn("The route %s alredy exist", _route.path);
745
+ else
746
+ state.routes[_route.path] = _route;
747
+ }
748
+ };
749
+ let _pages;
750
+ if (typeof _page === "function") {
751
+ _pages = _page(config);
752
+ registerPages2(_pages);
753
+ } else {
754
+ _pages = definePage(_page)()(config);
755
+ registerPages2(_pages);
756
+ }
757
+ };
758
+ const init = async () => {
759
+ await registerPages(server);
760
+ const pages = server.app.flow.routes;
761
+ for (const key in pages) {
762
+ const page = pages[key];
763
+ registerRoute(page);
764
+ }
765
+ for (const key in state.extensions) {
766
+ const ext = state.extensions[key];
767
+ if (ext.initMethod)
768
+ await ext.initMethod();
769
+ }
770
+ logger$1.debug("Register %s pages", `${Object.values(pages).length}`);
771
+ };
772
+ return { init, addPage };
773
+ }
774
+ };
775
+
776
+ const createHmr = (options) => {
777
+ const state = {
778
+ dir: options.relativeTo || "",
779
+ dirs: options.dirs || [],
780
+ extensions: options.extensions || ["eta"]
781
+ };
782
+ let watcher;
783
+ const hooks = createHooks();
784
+ const watch = () => {
785
+ watcher = chokidar.watch(state.dirs.map((d) => path.join(d, `**/*.(${state.extensions.join("|")})`)), {
786
+ cwd: state.dir,
787
+ ignoreInitial: true,
788
+ ignored: "node_modules/**/*"
789
+ }).on("change", (path2) => {
790
+ logger$1.info("Changed file", path2);
791
+ hooks.callHook("page:refresh");
792
+ });
793
+ };
794
+ watch();
795
+ return { watcher, hooks };
796
+ };
797
+
798
+ const RegisterCommon = async (server, configs) => {
799
+ server.route({
800
+ method: "GET",
801
+ path: "/{param*}",
802
+ options: {
803
+ ext: {
804
+ onPreResponse: {
805
+ method: (req, h) => {
806
+ return h.continue;
807
+ }
808
+ }
809
+ }
810
+ },
811
+ handler: {
812
+ directory: {
813
+ path: configs.staticDir,
814
+ listing: true
815
+ }
816
+ }
817
+ });
818
+ if (!isGenerate) {
819
+ const hmr = createHmr({
820
+ dirs: configs.hmr.dirs,
821
+ relativeTo: configs.relativeTo
822
+ });
823
+ hmr.hooks.hook("page:refresh", () => {
824
+ setTimeout(() => {
825
+ logger$1.info("Refresh page");
826
+ server.publish("/_flow/hmr", { reload: true });
827
+ }, 40);
828
+ });
829
+ server.subscription("/_flow/hmr");
830
+ server.route({
831
+ path: "/_flow/hmr/client.js",
832
+ method: "get",
833
+ handler: {
834
+ file: {
835
+ path: join(__dirname, "../resources/client"),
836
+ confine: false
837
+ }
838
+ }
839
+ });
840
+ server.route({
841
+ path: "/_flow/hmr/connect.js",
842
+ method: "get",
843
+ handler: {
844
+ file: {
845
+ path: join(__dirname, "../resources/ws.js"),
846
+ confine: false
847
+ }
848
+ }
849
+ });
850
+ logger$1.debug("Enable development routes");
851
+ server.route({
852
+ path: "/_flow/sitemap",
853
+ method: "get",
854
+ handler: async () => {
855
+ const { pages: getPages } = server.methods.flow;
856
+ const urls = await getPages();
857
+ const pages = Object.values(urls).map((p) => dissoc("context", p));
858
+ return {
859
+ total: pages.length,
860
+ pages
861
+ };
862
+ }
863
+ });
864
+ server.route({
865
+ path: "/_flow/locales",
866
+ method: "get",
867
+ handler: async (req) => {
868
+ const { pages: getPages } = server.methods.flow;
869
+ const urls = await getPages();
870
+ const pages = Object.values(urls).map((p) => dissoc("context", p));
871
+ const locales = groupBy((page) => page.locale, pages);
872
+ return {
873
+ locales: req.server.plugins.flow.configs.locales,
874
+ all: mapObjIndexed((l) => {
875
+ return {
876
+ total: l.length,
877
+ pages: l
878
+ };
879
+ }, locales)
880
+ };
881
+ }
882
+ });
883
+ server.route({
884
+ path: "/_flow/configs",
885
+ method: "get",
886
+ handler: (req) => {
887
+ return req.server.plugins.flow.configs;
888
+ }
889
+ });
890
+ }
891
+ const HMR = {
892
+ head: ' <script src="/_flow/hmr/client.js"><\/script>',
893
+ body: ' <script src="/_flow/hmr/connect.js"><\/script>'
894
+ };
895
+ server.ext("onPreHandler", async (req, h) => {
896
+ if (req.route.settings.plugins?.generate === ".html") {
897
+ const { global } = req.plugins.flow;
898
+ Object.assign(global, {
899
+ hmr: isProduction || isGenerate ? { head: "", body: "" } : HMR
900
+ });
901
+ }
902
+ return h.continue;
903
+ });
904
+ };
905
+
906
+ const plugin = {
907
+ pkg,
908
+ dependencies,
909
+ register: async (server, _options) => {
910
+ const config = getConfigs(server, _options);
911
+ server.expose("configs", config);
912
+ const state = {
913
+ pages: { statics: {}, redirects: {}, dynamics: {}, all: {} },
914
+ truncates: {},
915
+ extensions: {},
916
+ generate: {
917
+ folders: {},
918
+ staticFolders: {},
919
+ postGenerate: {},
920
+ beforePackage: {}
921
+ },
922
+ engines: {},
923
+ routes: {},
924
+ plugins: {}
925
+ };
926
+ server.app.flow = state;
927
+ const { serverInfo } = ServerInfo.register(server);
928
+ const serverDecorate = {
929
+ serverInfo
930
+ };
931
+ const helpers = {
932
+ getPath: (...paths) => path.join(config.relativeTo, ...paths),
933
+ addRedirect: (key, urlPath) => {
934
+ state.pages.redirects[key] = urlPath;
935
+ },
936
+ addStaticPage: (key, url) => {
937
+ if (isDinamyc(url.url))
938
+ return void 0;
939
+ if (state.pages.statics[key]) {
940
+ logger$1.warn("The namePage %s already exist", state.pages.statics[key]);
941
+ } else {
942
+ state.pages.statics[key] = url;
943
+ }
944
+ },
945
+ addDynamicPages: (key, method) => {
946
+ if (state.pages.dynamics[key])
947
+ logger$1.warn("The dynamdcPages %s already exist", key);
948
+ else
949
+ state.pages.dynamics[key] = method;
950
+ }
951
+ };
952
+ server.expose("helpers", helpers);
953
+ server.method("flow.pages", async () => {
954
+ const pages = {
955
+ ...state.pages.statics
956
+ };
957
+ for (const key in state.pages.dynamics) {
958
+ if (Object.prototype.hasOwnProperty.call(state.pages.dynamics, key)) {
959
+ const element = state.pages.dynamics[key];
960
+ const dynamicPages = await element();
961
+ for (const keyPage in dynamicPages) {
962
+ if (Object.prototype.hasOwnProperty.call(dynamicPages, keyPage)) {
963
+ const page = dynamicPages[keyPage];
964
+ if (!pages[keyPage])
965
+ pages[keyPage] = page;
966
+ else
967
+ logger$1.warn("Duplicate page %s", keyPage);
968
+ }
969
+ }
970
+ }
971
+ }
972
+ state.pages.all = pages;
973
+ return pages;
974
+ }, {
975
+ cache: { expiresIn: 3e3, generateTimeout: 8e3 }
976
+ });
977
+ server.expose("pages", () => {
978
+ return state.pages.all;
979
+ });
980
+ server.decorate("server", "flow", serverDecorate);
981
+ Decorators.register(server);
982
+ LifeCircle.register(server);
983
+ const { runGenerate } = useGenerator.register(server);
984
+ serverDecorate.runGenerate = runGenerate;
985
+ const { addPage, init } = RunMethods.register(server);
986
+ serverDecorate.addPage = addPage;
987
+ serverDecorate.init = init;
988
+ serverDecorate.prepagePackage = async () => {
989
+ for (const key in state.generate.beforePackage)
990
+ await state.generate.beforePackage[key]();
991
+ };
992
+ serverDecorate.assignPluginOptions = (realm, options = {}, d = {}, _context) => {
993
+ const key = realm.plugin.replace("@monkeyplus/", "").replace("flow-", "");
994
+ const baseOptions = applyToDefaults(d, options);
995
+ const { plugins } = config;
996
+ if (plugins[key] && Object.values(plugins[key])) {
997
+ if (typeof plugins[key] === "boolean") {
998
+ try {
999
+ const module = require(path.resolve("./extensions", key))?.default;
1000
+ if (typeof module === "function") {
1001
+ const promise = module({ server, ..._context });
1002
+ const isPromise = !!promise && typeof promise.then === "function";
1003
+ if (isPromise) {
1004
+ return promise.then((opts2) => {
1005
+ const _opts = applyToDefaults(baseOptions, opts2 || {});
1006
+ Object.assign(plugins, { [key]: _opts });
1007
+ return _opts;
1008
+ });
1009
+ }
1010
+ Object.assign(plugins, { [key]: promise });
1011
+ return promise;
1012
+ }
1013
+ const opts = applyToDefaults(baseOptions, module || {});
1014
+ Object.assign(plugins, { [key]: opts });
1015
+ return opts;
1016
+ } catch (error) {
1017
+ logger$1.error("Error in load extension: %s", key);
1018
+ return baseOptions;
1019
+ }
1020
+ } else {
1021
+ const opts = applyToDefaults(baseOptions, plugins[key]);
1022
+ Object.assign(plugins, { [key]: opts });
1023
+ return opts;
1024
+ }
1025
+ } else {
1026
+ Object.assign(plugins, { [key]: baseOptions });
1027
+ return baseOptions;
1028
+ }
1029
+ };
1030
+ await RegisterCommon(server, config);
1031
+ }
1032
+ };
1033
+
1034
+ export { definePage, plugin };