@moku-labs/web 0.2.0 → 0.3.0
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/chunk-D7D4PA-g.mjs +13 -0
- package/dist/index.cjs +732 -280
- package/dist/index.d.cts +24 -25
- package/dist/index.d.mts +23 -24
- package/dist/index.mjs +649 -189
- package/package.json +8 -8
- package/dist/chunk-DQk6qfdC.mjs +0 -18
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as __exportAll } from "./chunk-
|
|
1
|
+
import { t as __exportAll } from "./chunk-D7D4PA-g.mjs";
|
|
2
2
|
import { createCoreConfig, createCorePlugin } from "@moku-labs/core";
|
|
3
3
|
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
4
4
|
import { cp, mkdir, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
|
|
@@ -23,7 +23,6 @@ import { Resvg } from "@resvg/resvg-js";
|
|
|
23
23
|
import satori from "satori";
|
|
24
24
|
import { jsx } from "preact/jsx-runtime";
|
|
25
25
|
import { renderToString } from "preact-render-to-string";
|
|
26
|
-
|
|
27
26
|
//#region src/plugins/env/api.ts
|
|
28
27
|
/** Error prefix for all env API failures. */
|
|
29
28
|
const ERROR_PREFIX$13 = "[web]";
|
|
@@ -44,26 +43,75 @@ const ERROR_PREFIX$13 = "[web]";
|
|
|
44
43
|
function createEnvApi(ctx) {
|
|
45
44
|
const { resolved, publicMap } = ctx.state;
|
|
46
45
|
return {
|
|
46
|
+
/**
|
|
47
|
+
* Reads a resolved variable.
|
|
48
|
+
*
|
|
49
|
+
* @param key - Variable name.
|
|
50
|
+
* @returns The value, or `undefined` if not present.
|
|
51
|
+
* @example
|
|
52
|
+
* ```ts
|
|
53
|
+
* api.get("PUBLIC_API_URL");
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
47
56
|
get(key) {
|
|
48
57
|
return resolved.get(key);
|
|
49
58
|
},
|
|
59
|
+
/**
|
|
60
|
+
* Reads a variable that must exist.
|
|
61
|
+
*
|
|
62
|
+
* @param key - Variable name.
|
|
63
|
+
* @returns The value.
|
|
64
|
+
* @throws {Error} If the variable is undefined.
|
|
65
|
+
* @example
|
|
66
|
+
* ```ts
|
|
67
|
+
* api.require("DEPLOY_TOKEN");
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
50
70
|
require(key) {
|
|
51
71
|
const value = resolved.get(key);
|
|
52
72
|
if (value === void 0) throw new Error(`${ERROR_PREFIX$13} env: required variable "${key}" is not defined.`);
|
|
53
73
|
return value;
|
|
54
74
|
},
|
|
75
|
+
/**
|
|
76
|
+
* Tests presence of a resolved variable.
|
|
77
|
+
*
|
|
78
|
+
* @param key - Variable name.
|
|
79
|
+
* @returns `true` if a value is present.
|
|
80
|
+
* @example
|
|
81
|
+
* ```ts
|
|
82
|
+
* api.has("PUBLIC_API_URL");
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
55
85
|
has(key) {
|
|
56
86
|
return resolved.has(key);
|
|
57
87
|
},
|
|
88
|
+
/**
|
|
89
|
+
* Returns all public variables as a frozen plain object — a fresh copy,
|
|
90
|
+
* never the raw state map.
|
|
91
|
+
*
|
|
92
|
+
* @returns A frozen `Record` of public variable names to values.
|
|
93
|
+
* @example
|
|
94
|
+
* ```ts
|
|
95
|
+
* const payload = { ...api.getPublic() };
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
58
98
|
getPublic() {
|
|
59
99
|
return Object.freeze(Object.fromEntries(publicMap));
|
|
60
100
|
},
|
|
101
|
+
/**
|
|
102
|
+
* Returns the already-frozen map of public variables.
|
|
103
|
+
*
|
|
104
|
+
* @returns The frozen public map.
|
|
105
|
+
* @example
|
|
106
|
+
* ```ts
|
|
107
|
+
* [...api.getPublicMap()];
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
61
110
|
getPublicMap() {
|
|
62
111
|
return publicMap;
|
|
63
112
|
}
|
|
64
113
|
};
|
|
65
114
|
}
|
|
66
|
-
|
|
67
115
|
//#endregion
|
|
68
116
|
//#region src/plugins/env/state.ts
|
|
69
117
|
/**
|
|
@@ -83,7 +131,6 @@ function createEnvState() {
|
|
|
83
131
|
publicMap: /* @__PURE__ */ new Map()
|
|
84
132
|
};
|
|
85
133
|
}
|
|
86
|
-
|
|
87
134
|
//#endregion
|
|
88
135
|
//#region src/plugins/env/validate.ts
|
|
89
136
|
/** Error message thrown by every frozen-map mutator. */
|
|
@@ -202,7 +249,6 @@ function validateSchema(ctx) {
|
|
|
202
249
|
freezeMap(state.resolved);
|
|
203
250
|
freezeMap(state.publicMap);
|
|
204
251
|
}
|
|
205
|
-
|
|
206
252
|
//#endregion
|
|
207
253
|
//#region src/plugins/env/providers.ts
|
|
208
254
|
/**
|
|
@@ -271,6 +317,15 @@ function parseDotenv(text) {
|
|
|
271
317
|
function dotenv(path = DEFAULT_DOTENV_PATH) {
|
|
272
318
|
return {
|
|
273
319
|
name: `dotenv:${path}`,
|
|
320
|
+
/**
|
|
321
|
+
* Reads and parses the dotenv file fresh from disk; `{}` if it is missing.
|
|
322
|
+
*
|
|
323
|
+
* @returns The parsed environment record, or `{}` when the file is absent.
|
|
324
|
+
* @example
|
|
325
|
+
* ```ts
|
|
326
|
+
* dotenv(".env.local").load();
|
|
327
|
+
* ```
|
|
328
|
+
*/
|
|
274
329
|
load() {
|
|
275
330
|
if (!existsSync(path)) return {};
|
|
276
331
|
return parseDotenv(readFileSync(path, "utf8"));
|
|
@@ -290,24 +345,20 @@ function dotenv(path = DEFAULT_DOTENV_PATH) {
|
|
|
290
345
|
function processEnv() {
|
|
291
346
|
return {
|
|
292
347
|
name: "process-env",
|
|
348
|
+
/**
|
|
349
|
+
* Returns a shallow copy of `process.env` at call time.
|
|
350
|
+
*
|
|
351
|
+
* @returns A fresh shallow copy of `process.env`.
|
|
352
|
+
* @example
|
|
353
|
+
* ```ts
|
|
354
|
+
* processEnv().load();
|
|
355
|
+
* ```
|
|
356
|
+
*/
|
|
293
357
|
load() {
|
|
294
358
|
return { ...process.env };
|
|
295
359
|
}
|
|
296
360
|
};
|
|
297
361
|
}
|
|
298
|
-
|
|
299
|
-
//#endregion
|
|
300
|
-
//#region src/plugins/env/index.ts
|
|
301
|
-
/**
|
|
302
|
-
* @file Core plugin: universal env injection — schema + providers + PUBLIC_ cross-validation at onInit.
|
|
303
|
-
* @see README.md
|
|
304
|
-
*/
|
|
305
|
-
/** Plugin config defaults (R6 typed const). `providers: []` — framework sets `[dotenv(), processEnv()]` via the 4-level cascade. */
|
|
306
|
-
const defaultEnvConfig = {
|
|
307
|
-
schema: {},
|
|
308
|
-
providers: [],
|
|
309
|
-
publicPrefix: "PUBLIC_"
|
|
310
|
-
};
|
|
311
362
|
/**
|
|
312
363
|
* Core plugin that resolves, validates, and freezes the environment at `onInit`,
|
|
313
364
|
* exposing a read-only accessor at `ctx.env`. No `onStart`/`onStop` — holds no resource.
|
|
@@ -318,12 +369,15 @@ const defaultEnvConfig = {
|
|
|
318
369
|
* ```
|
|
319
370
|
*/
|
|
320
371
|
const envPlugin = createCorePlugin("env", {
|
|
321
|
-
config:
|
|
372
|
+
config: {
|
|
373
|
+
schema: {},
|
|
374
|
+
providers: [],
|
|
375
|
+
publicPrefix: "PUBLIC_"
|
|
376
|
+
},
|
|
322
377
|
createState: createEnvState,
|
|
323
378
|
api: createEnvApi,
|
|
324
379
|
onInit: validateSchema
|
|
325
380
|
});
|
|
326
|
-
|
|
327
381
|
//#endregion
|
|
328
382
|
//#region src/plugins/log/expect.ts
|
|
329
383
|
/**
|
|
@@ -434,10 +488,33 @@ function describePartial(partial) {
|
|
|
434
488
|
*/
|
|
435
489
|
function createExpectChain(entries) {
|
|
436
490
|
const chain = {
|
|
491
|
+
/**
|
|
492
|
+
* Assert at least one entry has `event`, optionally matching `partial`.
|
|
493
|
+
*
|
|
494
|
+
* @param event - Event name to find.
|
|
495
|
+
* @param partial - Optional partial data shape (subset-matched).
|
|
496
|
+
* @returns The same chain for chaining.
|
|
497
|
+
* @throws {LogExpectAssertionError} When no matching entry exists.
|
|
498
|
+
* @example
|
|
499
|
+
* ```ts
|
|
500
|
+
* chain.toHaveEvent("build:phase", { status: "start" });
|
|
501
|
+
* ```
|
|
502
|
+
*/
|
|
437
503
|
toHaveEvent(event, partial) {
|
|
438
504
|
if (!entries.some((entry) => entryMatches(entry, event, partial))) throw new LogExpectAssertionError(`Expected trace to contain event "${event}"${describePartial(partial)}, but none was found.`);
|
|
439
505
|
return chain;
|
|
440
506
|
},
|
|
507
|
+
/**
|
|
508
|
+
* Assert all of `events` appear in the trace in the given relative order.
|
|
509
|
+
*
|
|
510
|
+
* @param events - Ordered list of event names (gaps allowed).
|
|
511
|
+
* @returns The same chain for chaining.
|
|
512
|
+
* @throws {LogExpectAssertionError} When the ordering cannot be satisfied.
|
|
513
|
+
* @example
|
|
514
|
+
* ```ts
|
|
515
|
+
* chain.toHaveEventInOrder(["build:phase", "build:complete"]);
|
|
516
|
+
* ```
|
|
517
|
+
*/
|
|
441
518
|
toHaveEventInOrder(events) {
|
|
442
519
|
let cursor = 0;
|
|
443
520
|
for (const [position, event] of events.entries()) {
|
|
@@ -451,6 +528,18 @@ function createExpectChain(entries) {
|
|
|
451
528
|
}
|
|
452
529
|
return chain;
|
|
453
530
|
},
|
|
531
|
+
/**
|
|
532
|
+
* Assert NO entry has `event` (optionally narrowed by `partial`).
|
|
533
|
+
*
|
|
534
|
+
* @param event - Event name that must be absent.
|
|
535
|
+
* @param partial - Optional partial data shape; only matching entries violate.
|
|
536
|
+
* @returns The same chain for chaining.
|
|
537
|
+
* @throws {LogExpectAssertionError} When a matching entry exists.
|
|
538
|
+
* @example
|
|
539
|
+
* ```ts
|
|
540
|
+
* chain.toNotHaveEvent("deploy:failed");
|
|
541
|
+
* ```
|
|
542
|
+
*/
|
|
454
543
|
toNotHaveEvent(event, partial) {
|
|
455
544
|
const offending = entries.findIndex((entry) => entryMatches(entry, event, partial));
|
|
456
545
|
if (offending !== -1) throw new LogExpectAssertionError(`Expected trace to NOT contain event "${event}"${describePartial(partial)}, but found one at index ${offending}.`);
|
|
@@ -459,7 +548,6 @@ function createExpectChain(entries) {
|
|
|
459
548
|
};
|
|
460
549
|
return chain;
|
|
461
550
|
}
|
|
462
|
-
|
|
463
551
|
//#endregion
|
|
464
552
|
//#region src/plugins/log/api.ts
|
|
465
553
|
/**
|
|
@@ -528,33 +616,110 @@ function mergeError(data, error) {
|
|
|
528
616
|
function createLogApi(ctx) {
|
|
529
617
|
const { state } = ctx;
|
|
530
618
|
return {
|
|
619
|
+
/**
|
|
620
|
+
* Append an `info` entry and fan it out to every sink.
|
|
621
|
+
*
|
|
622
|
+
* @param event - Event identifier (convention: `domain:action`).
|
|
623
|
+
* @param data - Optional structured payload.
|
|
624
|
+
* @example
|
|
625
|
+
* ```ts
|
|
626
|
+
* log.info("content:ready", { count: 12 });
|
|
627
|
+
* ```
|
|
628
|
+
*/
|
|
531
629
|
info(event, data) {
|
|
532
630
|
append(state, "info", event, data);
|
|
533
631
|
},
|
|
632
|
+
/**
|
|
633
|
+
* Append a `debug` entry and fan it out to every sink.
|
|
634
|
+
*
|
|
635
|
+
* @param event - Event identifier (convention: `domain:action`).
|
|
636
|
+
* @param data - Optional structured payload.
|
|
637
|
+
* @example
|
|
638
|
+
* ```ts
|
|
639
|
+
* log.debug("router:match", { path: "/blog/" });
|
|
640
|
+
* ```
|
|
641
|
+
*/
|
|
534
642
|
debug(event, data) {
|
|
535
643
|
append(state, "debug", event, data);
|
|
536
644
|
},
|
|
645
|
+
/**
|
|
646
|
+
* Append a `warn` entry and fan it out to every sink.
|
|
647
|
+
*
|
|
648
|
+
* @param event - Event identifier (convention: `domain:action`).
|
|
649
|
+
* @param data - Optional structured payload.
|
|
650
|
+
* @example
|
|
651
|
+
* ```ts
|
|
652
|
+
* log.warn("build:skip", { reason: "no sitemap" });
|
|
653
|
+
* ```
|
|
654
|
+
*/
|
|
537
655
|
warn(event, data) {
|
|
538
656
|
append(state, "warn", event, data);
|
|
539
657
|
},
|
|
658
|
+
/**
|
|
659
|
+
* Append an `error` entry. When `error` is provided, its `message`/`stack`
|
|
660
|
+
* are merged into `data` under an `error` key (existing keys preserved);
|
|
661
|
+
* otherwise `data` is recorded as-is.
|
|
662
|
+
*
|
|
663
|
+
* @param event - Event identifier (convention: `domain:action`).
|
|
664
|
+
* @param data - Optional structured payload.
|
|
665
|
+
* @param error - Optional originating Error to merge into `data`.
|
|
666
|
+
* @example
|
|
667
|
+
* ```ts
|
|
668
|
+
* log.error("deploy:failed", { target: "cf" }, err);
|
|
669
|
+
* ```
|
|
670
|
+
*/
|
|
540
671
|
error(event, data, error) {
|
|
541
672
|
append(state, "error", event, error === void 0 ? data : mergeError(data, error));
|
|
542
673
|
},
|
|
674
|
+
/**
|
|
675
|
+
* Return a frozen snapshot (fresh copy) of the entries recorded so far.
|
|
676
|
+
*
|
|
677
|
+
* @returns A readonly, frozen copy of the recorded entries.
|
|
678
|
+
* @example
|
|
679
|
+
* ```ts
|
|
680
|
+
* const entries = log.trace();
|
|
681
|
+
* ```
|
|
682
|
+
*/
|
|
543
683
|
trace() {
|
|
544
684
|
return Object.freeze([...state.entries]);
|
|
545
685
|
},
|
|
686
|
+
/**
|
|
687
|
+
* Return a fluent assertion chain bound to the live entries array.
|
|
688
|
+
*
|
|
689
|
+
* @returns A fresh {@link ExpectChain} reading `state.entries` live.
|
|
690
|
+
* @example
|
|
691
|
+
* ```ts
|
|
692
|
+
* log.expect().toHaveEvent("build:complete");
|
|
693
|
+
* ```
|
|
694
|
+
*/
|
|
546
695
|
expect() {
|
|
547
696
|
return createExpectChain(state.entries);
|
|
548
697
|
},
|
|
698
|
+
/**
|
|
699
|
+
* Register an additional output sink at runtime.
|
|
700
|
+
*
|
|
701
|
+
* @param sink - The sink to add to the fan-out list.
|
|
702
|
+
* @example
|
|
703
|
+
* ```ts
|
|
704
|
+
* log.addSink({ write: (e) => stream.write(JSON.stringify(e)) });
|
|
705
|
+
* ```
|
|
706
|
+
*/
|
|
549
707
|
addSink(sink) {
|
|
550
708
|
state.sinks.push(sink);
|
|
551
709
|
},
|
|
710
|
+
/**
|
|
711
|
+
* Clear all recorded entries while keeping registered sinks.
|
|
712
|
+
*
|
|
713
|
+
* @example
|
|
714
|
+
* ```ts
|
|
715
|
+
* log.reset();
|
|
716
|
+
* ```
|
|
717
|
+
*/
|
|
552
718
|
reset() {
|
|
553
719
|
state.entries.length = 0;
|
|
554
720
|
}
|
|
555
721
|
};
|
|
556
722
|
}
|
|
557
|
-
|
|
558
723
|
//#endregion
|
|
559
724
|
//#region src/plugins/log/sinks.ts
|
|
560
725
|
/**
|
|
@@ -569,7 +734,17 @@ function createLogApi(ctx) {
|
|
|
569
734
|
* ```
|
|
570
735
|
*/
|
|
571
736
|
function consoleSink() {
|
|
572
|
-
return {
|
|
737
|
+
return {
|
|
738
|
+
/**
|
|
739
|
+
* Route a single entry to the console channel matching its level.
|
|
740
|
+
*
|
|
741
|
+
* @param entry - The entry to emit.
|
|
742
|
+
* @example
|
|
743
|
+
* ```ts
|
|
744
|
+
* sink.write({ level: "warn", event: "build:skip", ts: Date.now() });
|
|
745
|
+
* ```
|
|
746
|
+
*/
|
|
747
|
+
write(entry) {
|
|
573
748
|
if (entry.level === "error") console.error(entry);
|
|
574
749
|
else if (entry.level === "warn") console.warn(entry);
|
|
575
750
|
else console.log(entry);
|
|
@@ -590,7 +765,6 @@ function consoleSink() {
|
|
|
590
765
|
function installDefaultSinks(ctx) {
|
|
591
766
|
if (ctx.config.mode === "dev" || ctx.config.mode === "production") ctx.state.sinks.push(consoleSink());
|
|
592
767
|
}
|
|
593
|
-
|
|
594
768
|
//#endregion
|
|
595
769
|
//#region src/plugins/log/state.ts
|
|
596
770
|
/**
|
|
@@ -611,15 +785,6 @@ function createLogState(_ctx) {
|
|
|
611
785
|
sinks: []
|
|
612
786
|
};
|
|
613
787
|
}
|
|
614
|
-
|
|
615
|
-
//#endregion
|
|
616
|
-
//#region src/plugins/log/index.ts
|
|
617
|
-
/**
|
|
618
|
-
* @file log — Core Plugin (Standard tier): in-memory trace + expect() DSL.
|
|
619
|
-
* @see README.md
|
|
620
|
-
*/
|
|
621
|
-
/** Default config; overridden via the 4-level pluginConfigs.log merge. */
|
|
622
|
-
const defaultLogConfig = { mode: "production" };
|
|
623
788
|
/**
|
|
624
789
|
* Core logging plugin — always-on in-memory trace + `expect()` event-trace DSL.
|
|
625
790
|
* API injected as `ctx.log` on every regular plugin and surfaced as `app.log`.
|
|
@@ -628,21 +793,13 @@ const defaultLogConfig = { mode: "production" };
|
|
|
628
793
|
* @see README.md
|
|
629
794
|
*/
|
|
630
795
|
const logPlugin = createCorePlugin("log", {
|
|
631
|
-
config:
|
|
796
|
+
config: { mode: "production" },
|
|
632
797
|
createState: createLogState,
|
|
633
798
|
api: createLogApi,
|
|
634
799
|
onInit: installDefaultSinks
|
|
635
800
|
});
|
|
636
|
-
|
|
637
|
-
//#endregion
|
|
638
|
-
//#region src/config.ts
|
|
639
|
-
/**
|
|
640
|
-
* @file Framework configuration — Config + Events types, core plugin registration.
|
|
641
|
-
* @see README.md
|
|
642
|
-
*/
|
|
643
|
-
const defaultConfig$6 = { mode: "production" };
|
|
644
801
|
const coreConfig = createCoreConfig("web", {
|
|
645
|
-
config:
|
|
802
|
+
config: { mode: "production" },
|
|
646
803
|
plugins: [logPlugin, envPlugin],
|
|
647
804
|
pluginConfigs: {
|
|
648
805
|
log: { mode: "production" },
|
|
@@ -650,7 +807,6 @@ const coreConfig = createCoreConfig("web", {
|
|
|
650
807
|
}
|
|
651
808
|
});
|
|
652
809
|
const { createPlugin: createPlugin$1, createCore } = coreConfig;
|
|
653
|
-
|
|
654
810
|
//#endregion
|
|
655
811
|
//#region src/plugins/i18n/api.ts
|
|
656
812
|
/** Error prefix for all i18n lifecycle failures. */
|
|
@@ -690,21 +846,83 @@ function validateI18nConfig(ctx) {
|
|
|
690
846
|
function createI18nApi(ctx) {
|
|
691
847
|
const { config } = ctx;
|
|
692
848
|
return {
|
|
849
|
+
/**
|
|
850
|
+
* Returns the configured supported locales in declared order.
|
|
851
|
+
*
|
|
852
|
+
* @returns The configured `locales` list (priority/display order).
|
|
853
|
+
* @example
|
|
854
|
+
* ```ts
|
|
855
|
+
* api.locales(); // ["en", "uk"]
|
|
856
|
+
* ```
|
|
857
|
+
*/
|
|
693
858
|
locales() {
|
|
694
859
|
return config.locales;
|
|
695
860
|
},
|
|
861
|
+
/**
|
|
862
|
+
* Returns the fallback locale used when a requested locale is absent.
|
|
863
|
+
*
|
|
864
|
+
* @returns The configured `defaultLocale`.
|
|
865
|
+
* @example
|
|
866
|
+
* ```ts
|
|
867
|
+
* api.defaultLocale(); // "en"
|
|
868
|
+
* ```
|
|
869
|
+
*/
|
|
696
870
|
defaultLocale() {
|
|
697
871
|
return config.defaultLocale;
|
|
698
872
|
},
|
|
873
|
+
/**
|
|
874
|
+
* Membership guard: whether `x` is one of the supported locales
|
|
875
|
+
* (case-sensitive).
|
|
876
|
+
*
|
|
877
|
+
* @param x - Candidate locale code.
|
|
878
|
+
* @returns `true` if `x ∈ locales`, else `false`.
|
|
879
|
+
* @example
|
|
880
|
+
* ```ts
|
|
881
|
+
* api.isLocale("uk"); // true
|
|
882
|
+
* ```
|
|
883
|
+
*/
|
|
699
884
|
isLocale(x) {
|
|
700
885
|
return config.locales.includes(x);
|
|
701
886
|
},
|
|
887
|
+
/**
|
|
888
|
+
* Human-readable display name for a locale.
|
|
889
|
+
*
|
|
890
|
+
* @param locale - Locale code to look up.
|
|
891
|
+
* @returns The display name, or `undefined` if unmapped.
|
|
892
|
+
* @example
|
|
893
|
+
* ```ts
|
|
894
|
+
* api.localeName("uk"); // "Українська"
|
|
895
|
+
* ```
|
|
896
|
+
*/
|
|
702
897
|
localeName(locale) {
|
|
703
898
|
return config.localeNames?.[locale];
|
|
704
899
|
},
|
|
900
|
+
/**
|
|
901
|
+
* Open Graph `og:locale` value for a locale.
|
|
902
|
+
*
|
|
903
|
+
* @param locale - Locale code to look up.
|
|
904
|
+
* @returns The `og:locale` value (e.g. `"en_US"`), or `undefined` if unmapped.
|
|
905
|
+
* @example
|
|
906
|
+
* ```ts
|
|
907
|
+
* api.ogLocale("en"); // "en_US"
|
|
908
|
+
* ```
|
|
909
|
+
*/
|
|
705
910
|
ogLocale(locale) {
|
|
706
911
|
return config.ogLocaleMap?.[locale];
|
|
707
912
|
},
|
|
913
|
+
/**
|
|
914
|
+
* Translate `key` for `locale` with a deterministic fallback chain
|
|
915
|
+
* (requested locale → default locale → the key itself). The default-locale
|
|
916
|
+
* lookup is skipped when `locale === defaultLocale`.
|
|
917
|
+
*
|
|
918
|
+
* @param locale - Requested locale code.
|
|
919
|
+
* @param key - Translation key (e.g. `"nav.home"`).
|
|
920
|
+
* @returns The translated value, the default-locale value, or `key`.
|
|
921
|
+
* @example
|
|
922
|
+
* ```ts
|
|
923
|
+
* api.t("uk", "nav.home"); // "Головна"
|
|
924
|
+
* ```
|
|
925
|
+
*/
|
|
708
926
|
t(locale, key) {
|
|
709
927
|
const exact = config.translations?.[locale]?.[key];
|
|
710
928
|
if (exact !== void 0) return exact;
|
|
@@ -716,34 +934,17 @@ function createI18nApi(ctx) {
|
|
|
716
934
|
}
|
|
717
935
|
};
|
|
718
936
|
}
|
|
719
|
-
|
|
720
|
-
//#endregion
|
|
721
|
-
//#region src/plugins/i18n/index.ts
|
|
722
|
-
/**
|
|
723
|
-
* i18n — Micro tier. Multi-file layout (index wiring + api.ts + types.ts) so
|
|
724
|
-
* index.ts stays within the ≤30-line wiring-only hook; logic lives in api.ts.
|
|
725
|
-
*
|
|
726
|
-
* Locale registry + flat translation helper with default-locale fallback.
|
|
727
|
-
* Pure config-as-data: no state, no events, no lifecycle resources.
|
|
728
|
-
* Consumed read-only by content/router/head/build via `ctx.require(i18nPlugin)`.
|
|
729
|
-
*
|
|
730
|
-
* @file i18n plugin wiring harness.
|
|
731
|
-
* @see README.md
|
|
732
|
-
*/
|
|
733
|
-
/** Typed default config (R6: no inline `as`). Optional maps default to `{}` so every lookup is total. */
|
|
734
|
-
const defaultConfig$5 = {
|
|
735
|
-
locales: ["en"],
|
|
736
|
-
defaultLocale: "en",
|
|
737
|
-
localeNames: {},
|
|
738
|
-
ogLocaleMap: {},
|
|
739
|
-
translations: {}
|
|
740
|
-
};
|
|
741
937
|
const i18nPlugin = createPlugin$1("i18n", {
|
|
742
|
-
config:
|
|
938
|
+
config: {
|
|
939
|
+
locales: ["en"],
|
|
940
|
+
defaultLocale: "en",
|
|
941
|
+
localeNames: {},
|
|
942
|
+
ogLocaleMap: {},
|
|
943
|
+
translations: {}
|
|
944
|
+
},
|
|
743
945
|
onInit: validateI18nConfig,
|
|
744
946
|
api: createI18nApi
|
|
745
947
|
});
|
|
746
|
-
|
|
747
948
|
//#endregion
|
|
748
949
|
//#region src/plugins/content/pipeline/frontmatter.ts
|
|
749
950
|
/**
|
|
@@ -789,7 +990,6 @@ function parseFrontmatter(raw, config) {
|
|
|
789
990
|
body: parsed.content
|
|
790
991
|
};
|
|
791
992
|
}
|
|
792
|
-
|
|
793
993
|
//#endregion
|
|
794
994
|
//#region src/plugins/content/pipeline/plugins.ts
|
|
795
995
|
/**
|
|
@@ -946,7 +1146,6 @@ function defaultRehypePlugins() {
|
|
|
946
1146
|
sectionDividerPlugin
|
|
947
1147
|
];
|
|
948
1148
|
}
|
|
949
|
-
|
|
950
1149
|
//#endregion
|
|
951
1150
|
//#region src/plugins/content/pipeline/sanitize.ts
|
|
952
1151
|
/**
|
|
@@ -1033,7 +1232,6 @@ function buildSanitizeSchema() {
|
|
|
1033
1232
|
}
|
|
1034
1233
|
};
|
|
1035
1234
|
}
|
|
1036
|
-
|
|
1037
1235
|
//#endregion
|
|
1038
1236
|
//#region src/plugins/content/pipeline/markdown.ts
|
|
1039
1237
|
/**
|
|
@@ -1091,7 +1289,6 @@ function applyPluggable(processor, plugin) {
|
|
|
1091
1289
|
}
|
|
1092
1290
|
processor.use(plugin);
|
|
1093
1291
|
}
|
|
1094
|
-
|
|
1095
1292
|
//#endregion
|
|
1096
1293
|
//#region src/plugins/content/pipeline/reading-time.ts
|
|
1097
1294
|
/**
|
|
@@ -1117,7 +1314,6 @@ function calculateReadingTime(text) {
|
|
|
1117
1314
|
wordCount: stats.words
|
|
1118
1315
|
};
|
|
1119
1316
|
}
|
|
1120
|
-
|
|
1121
1317
|
//#endregion
|
|
1122
1318
|
//#region src/plugins/content/api.ts
|
|
1123
1319
|
/**
|
|
@@ -1329,6 +1525,18 @@ function toCard(article) {
|
|
|
1329
1525
|
*/
|
|
1330
1526
|
function createContentApi(ctx) {
|
|
1331
1527
|
return {
|
|
1528
|
+
/**
|
|
1529
|
+
* Load every article across every active locale, returning a locale-keyed
|
|
1530
|
+
* map of date-descending Article arrays. Lazily builds the processor and
|
|
1531
|
+
* discovers slugs, applies locale fallback, excludes drafts in production,
|
|
1532
|
+
* assigns `contentId` after sorting, then emits `content:ready`.
|
|
1533
|
+
*
|
|
1534
|
+
* @returns A locale-keyed map of date-descending articles.
|
|
1535
|
+
* @example
|
|
1536
|
+
* ```ts
|
|
1537
|
+
* const byLocale = await api.loadAll();
|
|
1538
|
+
* ```
|
|
1539
|
+
*/
|
|
1332
1540
|
async loadAll() {
|
|
1333
1541
|
const slugs = ctx.state.slugs ?? await discoverSlugs(ctx.config.contentDir);
|
|
1334
1542
|
ctx.state.slugs = slugs;
|
|
@@ -1356,6 +1564,19 @@ function createContentApi(ctx) {
|
|
|
1356
1564
|
});
|
|
1357
1565
|
return result;
|
|
1358
1566
|
},
|
|
1567
|
+
/**
|
|
1568
|
+
* Resolve and render a single article for one locale with locale fallback.
|
|
1569
|
+
* Throws a `[web] content` error when neither the requested nor the
|
|
1570
|
+
* default-locale file exists.
|
|
1571
|
+
*
|
|
1572
|
+
* @param slug - Article directory name.
|
|
1573
|
+
* @param locale - Requested locale code.
|
|
1574
|
+
* @returns The resolved Article.
|
|
1575
|
+
* @example
|
|
1576
|
+
* ```ts
|
|
1577
|
+
* const article = await api.load("intro", "uk");
|
|
1578
|
+
* ```
|
|
1579
|
+
*/
|
|
1359
1580
|
async load(slug, locale) {
|
|
1360
1581
|
const article = await resolveArticle(ctx, slug, locale);
|
|
1361
1582
|
if (article === null) throw new Error(`[web] content article "${slug}" not found for locale "${locale}".\n Looked for ${slug}/${locale}.md and the default-locale fallback.`);
|
|
@@ -1364,10 +1585,33 @@ function createContentApi(ctx) {
|
|
|
1364
1585
|
ctx.state.articles.set(locale, cache);
|
|
1365
1586
|
return article;
|
|
1366
1587
|
},
|
|
1588
|
+
/**
|
|
1589
|
+
* Render a raw Markdown string to HTML through the full pipeline (sanitize
|
|
1590
|
+
* last when `trustedContent` is false). Lazily builds the processor.
|
|
1591
|
+
*
|
|
1592
|
+
* @param md - Raw Markdown source.
|
|
1593
|
+
* @returns The rendered HTML string.
|
|
1594
|
+
* @example
|
|
1595
|
+
* ```ts
|
|
1596
|
+
* const html = await api.renderMarkdown("# Hi");
|
|
1597
|
+
* ```
|
|
1598
|
+
*/
|
|
1367
1599
|
async renderMarkdown(md) {
|
|
1368
1600
|
const processor = ensureProcessor(ctx.state, ctx.config);
|
|
1369
1601
|
return String(await processor.process(md));
|
|
1370
1602
|
},
|
|
1603
|
+
/**
|
|
1604
|
+
* Mark file paths stale for incremental dev rebuilds. Each non-blank path is
|
|
1605
|
+
* added to `dirtyPaths` and its derived slug cache entry is dropped so the
|
|
1606
|
+
* next `loadAll()` re-reads only those files. Empty/whitespace paths are
|
|
1607
|
+
* ignored. Emits `content:invalidated` with the accepted paths.
|
|
1608
|
+
*
|
|
1609
|
+
* @param paths - File paths to invalidate.
|
|
1610
|
+
* @example
|
|
1611
|
+
* ```ts
|
|
1612
|
+
* api.invalidate(["src/content/intro/en.md"]);
|
|
1613
|
+
* ```
|
|
1614
|
+
*/
|
|
1371
1615
|
invalidate(paths) {
|
|
1372
1616
|
const accepted = [];
|
|
1373
1617
|
for (const path of paths) {
|
|
@@ -1380,12 +1624,22 @@ function createContentApi(ctx) {
|
|
|
1380
1624
|
ctx.state.slugs = null;
|
|
1381
1625
|
ctx.emit("content:invalidated", { paths: accepted });
|
|
1382
1626
|
},
|
|
1627
|
+
/**
|
|
1628
|
+
* Project a full Article to a lightweight ArticleCard for list/grid
|
|
1629
|
+
* rendering without shipping rendered HTML.
|
|
1630
|
+
*
|
|
1631
|
+
* @param article - The source article.
|
|
1632
|
+
* @returns The card projection.
|
|
1633
|
+
* @example
|
|
1634
|
+
* ```ts
|
|
1635
|
+
* const card = api.articleToCard(article);
|
|
1636
|
+
* ```
|
|
1637
|
+
*/
|
|
1383
1638
|
articleToCard(article) {
|
|
1384
1639
|
return toCard(article);
|
|
1385
1640
|
}
|
|
1386
1641
|
};
|
|
1387
1642
|
}
|
|
1388
|
-
|
|
1389
1643
|
//#endregion
|
|
1390
1644
|
//#region src/plugins/content/config.ts
|
|
1391
1645
|
/**
|
|
@@ -1406,7 +1660,6 @@ const defaultContentConfig = {
|
|
|
1406
1660
|
extraRehypePlugins: [],
|
|
1407
1661
|
shikiTheme: "github-dark"
|
|
1408
1662
|
};
|
|
1409
|
-
|
|
1410
1663
|
//#endregion
|
|
1411
1664
|
//#region src/plugins/content/events.ts
|
|
1412
1665
|
/**
|
|
@@ -1425,7 +1678,6 @@ const contentEvents = (register) => ({
|
|
|
1425
1678
|
"content:ready": register("All articles loaded across locales"),
|
|
1426
1679
|
"content:invalidated": register("Article paths marked stale for dev rebuild")
|
|
1427
1680
|
});
|
|
1428
|
-
|
|
1429
1681
|
//#endregion
|
|
1430
1682
|
//#region src/plugins/content/state.ts
|
|
1431
1683
|
/**
|
|
@@ -1449,7 +1701,6 @@ function createContentState(_ctx) {
|
|
|
1449
1701
|
dirtyPaths: /* @__PURE__ */ new Set()
|
|
1450
1702
|
};
|
|
1451
1703
|
}
|
|
1452
|
-
|
|
1453
1704
|
//#endregion
|
|
1454
1705
|
//#region src/plugins/content/validate.ts
|
|
1455
1706
|
/**
|
|
@@ -1468,7 +1719,6 @@ function validateContentConfig(config) {
|
|
|
1468
1719
|
if (typeof config.contentDir !== "string" || config.contentDir.trim() === "") throw new Error("[web] content.contentDir is required.\n Set pluginConfigs.content.contentDir to your content directory.");
|
|
1469
1720
|
if (typeof config.trustedContent !== "boolean") throw new TypeError("[web] content.trustedContent must be a boolean.");
|
|
1470
1721
|
}
|
|
1471
|
-
|
|
1472
1722
|
//#endregion
|
|
1473
1723
|
//#region src/plugins/content/index.ts
|
|
1474
1724
|
/**
|
|
@@ -1487,7 +1737,6 @@ const contentPlugin = createPlugin$1("content", {
|
|
|
1487
1737
|
onInit: (ctx) => validateContentConfig(ctx.config),
|
|
1488
1738
|
api: contentApi
|
|
1489
1739
|
});
|
|
1490
|
-
|
|
1491
1740
|
//#endregion
|
|
1492
1741
|
//#region src/plugins/site/api.ts
|
|
1493
1742
|
/** Error prefix for all site lifecycle/validation failures. */
|
|
@@ -1579,50 +1828,80 @@ function validateSiteConfig(ctx) {
|
|
|
1579
1828
|
function createSiteApi(ctx) {
|
|
1580
1829
|
const { config } = ctx;
|
|
1581
1830
|
return {
|
|
1831
|
+
/**
|
|
1832
|
+
* Returns the configured site name.
|
|
1833
|
+
*
|
|
1834
|
+
* @returns The human-readable site name from `config.name`.
|
|
1835
|
+
* @example
|
|
1836
|
+
* ```ts
|
|
1837
|
+
* api.name(); // "My Blog"
|
|
1838
|
+
* ```
|
|
1839
|
+
*/
|
|
1582
1840
|
name() {
|
|
1583
1841
|
return config.name;
|
|
1584
1842
|
},
|
|
1843
|
+
/**
|
|
1844
|
+
* Returns the configured absolute base URL of the site.
|
|
1845
|
+
*
|
|
1846
|
+
* @returns The base URL from `config.url`.
|
|
1847
|
+
* @example
|
|
1848
|
+
* ```ts
|
|
1849
|
+
* api.url(); // "https://blog.dev"
|
|
1850
|
+
* ```
|
|
1851
|
+
*/
|
|
1585
1852
|
url() {
|
|
1586
1853
|
return config.url;
|
|
1587
1854
|
},
|
|
1855
|
+
/**
|
|
1856
|
+
* Returns the configured site author/byline.
|
|
1857
|
+
*
|
|
1858
|
+
* @returns The author from `config.author`.
|
|
1859
|
+
* @example
|
|
1860
|
+
* ```ts
|
|
1861
|
+
* api.author(); // "Alex"
|
|
1862
|
+
* ```
|
|
1863
|
+
*/
|
|
1588
1864
|
author() {
|
|
1589
1865
|
return config.author;
|
|
1590
1866
|
},
|
|
1867
|
+
/**
|
|
1868
|
+
* Returns the configured site description.
|
|
1869
|
+
*
|
|
1870
|
+
* @returns The description from `config.description`.
|
|
1871
|
+
* @example
|
|
1872
|
+
* ```ts
|
|
1873
|
+
* api.description(); // "A personal blog about web frameworks."
|
|
1874
|
+
* ```
|
|
1875
|
+
*/
|
|
1591
1876
|
description() {
|
|
1592
1877
|
return config.description;
|
|
1593
1878
|
},
|
|
1879
|
+
/**
|
|
1880
|
+
* Joins a path against the configured base `url` to produce an absolute
|
|
1881
|
+
* canonical URL. An empty path (or "/") returns the base URL unchanged.
|
|
1882
|
+
*
|
|
1883
|
+
* @param path - Relative path for the page, e.g. "/about/".
|
|
1884
|
+
* @returns The absolute canonical URL.
|
|
1885
|
+
* @example
|
|
1886
|
+
* ```ts
|
|
1887
|
+
* api.canonical("/about/"); // "https://blog.dev/about/"
|
|
1888
|
+
* ```
|
|
1889
|
+
*/
|
|
1594
1890
|
canonical(path) {
|
|
1595
1891
|
return joinCanonical(config.url, path);
|
|
1596
1892
|
}
|
|
1597
1893
|
};
|
|
1598
1894
|
}
|
|
1599
|
-
|
|
1600
|
-
//#endregion
|
|
1601
|
-
//#region src/plugins/site/index.ts
|
|
1602
|
-
/**
|
|
1603
|
-
* site — Micro tier. Multi-file layout (index wiring + api.ts + types.ts) so
|
|
1604
|
-
* index.ts stays within the ≤30-line wiring-only hook; logic lives in api.ts.
|
|
1605
|
-
*
|
|
1606
|
-
* Holds global, frozen site metadata (name, url, author, description) and
|
|
1607
|
-
* constructs canonical URLs. Consumed by router/head/build via
|
|
1608
|
-
* `ctx.require(sitePlugin)`. No events, no dependencies, no state.
|
|
1609
|
-
*
|
|
1610
|
-
* @file site plugin wiring harness.
|
|
1611
|
-
* @see README.md
|
|
1612
|
-
*/
|
|
1613
|
-
/** Typed default config (R6: no inline `as`). Consumers override via `pluginConfigs.site`. */
|
|
1614
|
-
const defaultConfig$4 = {
|
|
1615
|
-
name: "",
|
|
1616
|
-
url: "",
|
|
1617
|
-
author: "",
|
|
1618
|
-
description: ""
|
|
1619
|
-
};
|
|
1620
1895
|
const sitePlugin = createPlugin$1("site", {
|
|
1621
|
-
config:
|
|
1896
|
+
config: {
|
|
1897
|
+
name: "",
|
|
1898
|
+
url: "",
|
|
1899
|
+
author: "",
|
|
1900
|
+
description: ""
|
|
1901
|
+
},
|
|
1622
1902
|
onInit: validateSiteConfig,
|
|
1623
1903
|
api: createSiteApi
|
|
1624
1904
|
});
|
|
1625
|
-
|
|
1626
1905
|
//#endregion
|
|
1627
1906
|
//#region src/plugins/router/builders/match.ts
|
|
1628
1907
|
/**
|
|
@@ -1692,7 +1971,6 @@ function matchRoute(compiled, pathname) {
|
|
|
1692
1971
|
}
|
|
1693
1972
|
return null;
|
|
1694
1973
|
}
|
|
1695
|
-
|
|
1696
1974
|
//#endregion
|
|
1697
1975
|
//#region src/plugins/router/api.ts
|
|
1698
1976
|
/**
|
|
@@ -1755,23 +2033,63 @@ function toTypedRoute(entry) {
|
|
|
1755
2033
|
function createApi$4(ctx) {
|
|
1756
2034
|
const { state } = ctx;
|
|
1757
2035
|
return {
|
|
2036
|
+
/**
|
|
2037
|
+
* Match a pathname against the compiled route table (specificity-sorted).
|
|
2038
|
+
*
|
|
2039
|
+
* @param pathname - URL pathname, e.g. `/en/hello/`.
|
|
2040
|
+
* @returns `{ params, route }` for the most specific match, or `null`.
|
|
2041
|
+
* @example
|
|
2042
|
+
* ```ts
|
|
2043
|
+
* api.match("/en/hello/");
|
|
2044
|
+
* ```
|
|
2045
|
+
*/
|
|
1758
2046
|
match(pathname) {
|
|
1759
2047
|
return matchRoute(readTable(state).compiled, pathname);
|
|
1760
2048
|
},
|
|
2049
|
+
/**
|
|
2050
|
+
* Build a URL for a named route from params.
|
|
2051
|
+
*
|
|
2052
|
+
* @param routeName - Route name key from the route map.
|
|
2053
|
+
* @param params - Param values to substitute into the pattern.
|
|
2054
|
+
* @returns The resolved URL string (e.g. `/en/hello/`).
|
|
2055
|
+
* @throws {Error} If `routeName` is unknown.
|
|
2056
|
+
* @example
|
|
2057
|
+
* ```ts
|
|
2058
|
+
* api.toUrl("article", { lang: "en", slug: "hello" });
|
|
2059
|
+
* ```
|
|
2060
|
+
*/
|
|
1761
2061
|
toUrl(routeName, params) {
|
|
1762
2062
|
const entry = readTable(state).byName.get(routeName);
|
|
1763
2063
|
if (!entry) throw new Error(`${ERROR_PREFIX$9}: unknown route name "${routeName}".`);
|
|
1764
2064
|
return entry.toUrl(params);
|
|
1765
2065
|
},
|
|
2066
|
+
/**
|
|
2067
|
+
* All resolved routes as typed URL utilities, in specificity order.
|
|
2068
|
+
*
|
|
2069
|
+
* @returns A fresh read-only array of resolved typed routes.
|
|
2070
|
+
* @example
|
|
2071
|
+
* ```ts
|
|
2072
|
+
* for (const r of api.entries()) r.toUrl({ slug: "x" });
|
|
2073
|
+
* ```
|
|
2074
|
+
*/
|
|
1766
2075
|
entries() {
|
|
1767
2076
|
return readTable(state).compiled.map((entry) => toTypedRoute(entry));
|
|
1768
2077
|
},
|
|
2078
|
+
/**
|
|
2079
|
+
* The typed route set for build-time consumption (declaration order). An API
|
|
2080
|
+
* return, NOT a config readback — preserves per-route types despite erasure.
|
|
2081
|
+
*
|
|
2082
|
+
* @returns A fresh read-only array of the typed route definitions.
|
|
2083
|
+
* @example
|
|
2084
|
+
* ```ts
|
|
2085
|
+
* for (const def of api.manifest()) def._handlers.load?.({}, "en");
|
|
2086
|
+
* ```
|
|
2087
|
+
*/
|
|
1769
2088
|
manifest() {
|
|
1770
2089
|
return [...readTable(state).byName.values()].map((entry) => entry.definition);
|
|
1771
2090
|
}
|
|
1772
2091
|
};
|
|
1773
2092
|
}
|
|
1774
|
-
|
|
1775
2093
|
//#endregion
|
|
1776
2094
|
//#region src/plugins/router/builders/compile.ts
|
|
1777
2095
|
/** Shared `[web]` error prefix for router validation failures. */
|
|
@@ -1933,9 +2251,31 @@ function compileRoute(name, definition, input) {
|
|
|
1933
2251
|
dynamicSegmentCount: countDynamicSegments(pattern),
|
|
1934
2252
|
matchers,
|
|
1935
2253
|
matchFn: createMatchFunction(matchers, input.defaultLocale),
|
|
2254
|
+
/**
|
|
2255
|
+
* Build a URL for this route from params.
|
|
2256
|
+
*
|
|
2257
|
+
* @param params - Param values to substitute.
|
|
2258
|
+
* @returns The resolved relative URL.
|
|
2259
|
+
* @example
|
|
2260
|
+
* ```ts
|
|
2261
|
+
* entry.toUrl({ slug: "x" });
|
|
2262
|
+
* ```
|
|
2263
|
+
*/
|
|
1936
2264
|
toUrl(params) {
|
|
1937
2265
|
return buildUrl(pattern, params, input.baseUrl);
|
|
1938
2266
|
},
|
|
2267
|
+
/**
|
|
2268
|
+
* Build the output file path for this route from params. Honors a custom
|
|
2269
|
+
* `.toFile()` override (captured in `_handlers.toFile`) when present, falling
|
|
2270
|
+
* back to the pattern-derived `…/index.html` path otherwise.
|
|
2271
|
+
*
|
|
2272
|
+
* @param params - Param values to substitute.
|
|
2273
|
+
* @returns The output file path.
|
|
2274
|
+
* @example
|
|
2275
|
+
* ```ts
|
|
2276
|
+
* entry.toFile({ slug: "x" });
|
|
2277
|
+
* ```
|
|
2278
|
+
*/
|
|
1939
2279
|
toFile(params) {
|
|
1940
2280
|
return definition._handlers.toFile?.(params) ?? buildFilePath(pattern, params);
|
|
1941
2281
|
},
|
|
@@ -1996,7 +2336,6 @@ function buildRouterTable(config, baseUrl, locales, defaultLocale) {
|
|
|
1996
2336
|
defaultLocale
|
|
1997
2337
|
});
|
|
1998
2338
|
}
|
|
1999
|
-
|
|
2000
2339
|
//#endregion
|
|
2001
2340
|
//#region src/plugins/router/builders/route-builder.ts
|
|
2002
2341
|
/**
|
|
@@ -2042,28 +2381,108 @@ function route(pattern) {
|
|
|
2042
2381
|
pattern: carrier.pattern,
|
|
2043
2382
|
_meta: carrier._meta,
|
|
2044
2383
|
_handlers: carrier._handlers,
|
|
2384
|
+
/**
|
|
2385
|
+
* Attach a data loader; widens the data generic for downstream handlers.
|
|
2386
|
+
*
|
|
2387
|
+
* @param loader - The loader producing this route's data.
|
|
2388
|
+
* @returns The same builder, with the data generic widened.
|
|
2389
|
+
* @example
|
|
2390
|
+
* ```ts
|
|
2391
|
+
* route("/{slug}/").load(({ slug }) => ({ slug }));
|
|
2392
|
+
* ```
|
|
2393
|
+
*/
|
|
2045
2394
|
load(loader) {
|
|
2046
2395
|
return set("load", loader);
|
|
2047
2396
|
},
|
|
2397
|
+
/**
|
|
2398
|
+
* Attach a layout wrapper component.
|
|
2399
|
+
*
|
|
2400
|
+
* @param component - The layout component.
|
|
2401
|
+
* @returns The same builder for chaining.
|
|
2402
|
+
* @example
|
|
2403
|
+
* ```ts
|
|
2404
|
+
* route("/").layout((children) => children);
|
|
2405
|
+
* ```
|
|
2406
|
+
*/
|
|
2048
2407
|
layout(component) {
|
|
2049
2408
|
return set("layout", component);
|
|
2050
2409
|
},
|
|
2410
|
+
/**
|
|
2411
|
+
* Attach the page render handler.
|
|
2412
|
+
*
|
|
2413
|
+
* @param handler - The render handler.
|
|
2414
|
+
* @returns The same builder for chaining.
|
|
2415
|
+
* @example
|
|
2416
|
+
* ```ts
|
|
2417
|
+
* route("/").render(() => null);
|
|
2418
|
+
* ```
|
|
2419
|
+
*/
|
|
2051
2420
|
render(handler) {
|
|
2052
2421
|
return set("render", handler);
|
|
2053
2422
|
},
|
|
2423
|
+
/**
|
|
2424
|
+
* Attach the head/SEO handler.
|
|
2425
|
+
*
|
|
2426
|
+
* @param handler - The head handler.
|
|
2427
|
+
* @returns The same builder for chaining.
|
|
2428
|
+
* @example
|
|
2429
|
+
* ```ts
|
|
2430
|
+
* route("/").head(() => ({ title: "Home" }));
|
|
2431
|
+
* ```
|
|
2432
|
+
*/
|
|
2054
2433
|
head(handler) {
|
|
2055
2434
|
return set("head", handler);
|
|
2056
2435
|
},
|
|
2436
|
+
/**
|
|
2437
|
+
* Attach a static-generation param producer.
|
|
2438
|
+
*
|
|
2439
|
+
* @param handler - The param producer.
|
|
2440
|
+
* @returns The same builder for chaining.
|
|
2441
|
+
* @example
|
|
2442
|
+
* ```ts
|
|
2443
|
+
* route("/{slug}/").generate(() => [{ slug: "x" }]);
|
|
2444
|
+
* ```
|
|
2445
|
+
*/
|
|
2057
2446
|
generate(handler) {
|
|
2058
2447
|
return set("generate", handler);
|
|
2059
2448
|
},
|
|
2449
|
+
/**
|
|
2450
|
+
* Merge an arbitrary metadata bag into the route's `_meta`.
|
|
2451
|
+
*
|
|
2452
|
+
* @param meta - Metadata to merge.
|
|
2453
|
+
* @returns The same builder for chaining.
|
|
2454
|
+
* @example
|
|
2455
|
+
* ```ts
|
|
2456
|
+
* route("/").meta({ activeTab: "home" });
|
|
2457
|
+
* ```
|
|
2458
|
+
*/
|
|
2060
2459
|
meta(meta) {
|
|
2061
2460
|
Object.assign(carrier._meta, meta);
|
|
2062
2461
|
return builder;
|
|
2063
2462
|
},
|
|
2463
|
+
/**
|
|
2464
|
+
* Attach a JSON serializer for the route's data.
|
|
2465
|
+
*
|
|
2466
|
+
* @param handler - The JSON serializer.
|
|
2467
|
+
* @returns The same builder for chaining.
|
|
2468
|
+
* @example
|
|
2469
|
+
* ```ts
|
|
2470
|
+
* route("/api/").toJson(() => ({ ok: true }));
|
|
2471
|
+
* ```
|
|
2472
|
+
*/
|
|
2064
2473
|
toJson(handler) {
|
|
2065
2474
|
return set("toJson", handler);
|
|
2066
2475
|
},
|
|
2476
|
+
/**
|
|
2477
|
+
* Override the output file-path producer.
|
|
2478
|
+
*
|
|
2479
|
+
* @param handler - The file-path producer.
|
|
2480
|
+
* @returns The same builder for chaining.
|
|
2481
|
+
* @example
|
|
2482
|
+
* ```ts
|
|
2483
|
+
* route("/feed/").toFile(() => "feed.xml");
|
|
2484
|
+
* ```
|
|
2485
|
+
*/
|
|
2067
2486
|
toFile(handler) {
|
|
2068
2487
|
return set("toFile", handler);
|
|
2069
2488
|
}
|
|
@@ -2084,7 +2503,6 @@ function route(pattern) {
|
|
|
2084
2503
|
function defineRoutes(routes) {
|
|
2085
2504
|
return routes;
|
|
2086
2505
|
}
|
|
2087
|
-
|
|
2088
2506
|
//#endregion
|
|
2089
2507
|
//#region src/plugins/router/state.ts
|
|
2090
2508
|
/**
|
|
@@ -2103,25 +2521,16 @@ function defineRoutes(routes) {
|
|
|
2103
2521
|
function createState$4(_ctx) {
|
|
2104
2522
|
return { table: null };
|
|
2105
2523
|
}
|
|
2106
|
-
|
|
2107
|
-
//#endregion
|
|
2108
|
-
//#region src/plugins/router/index.ts
|
|
2109
|
-
/**
|
|
2110
|
-
* @file router — Complex plugin wiring (logic in builders/, api.ts, state.ts).
|
|
2111
|
-
* @see README.md
|
|
2112
|
-
*/
|
|
2113
|
-
/** Default router config: empty route map (validated in onInit), hybrid mode. */
|
|
2114
|
-
const defaultConfig$3 = {
|
|
2115
|
-
routes: {},
|
|
2116
|
-
mode: "hybrid"
|
|
2117
|
-
};
|
|
2118
2524
|
const routerPlugin = createPlugin$1("router", {
|
|
2119
2525
|
depends: [sitePlugin, i18nPlugin],
|
|
2120
2526
|
helpers: {
|
|
2121
2527
|
route,
|
|
2122
2528
|
defineRoutes
|
|
2123
2529
|
},
|
|
2124
|
-
config:
|
|
2530
|
+
config: {
|
|
2531
|
+
routes: {},
|
|
2532
|
+
mode: "hybrid"
|
|
2533
|
+
},
|
|
2125
2534
|
createState: createState$4,
|
|
2126
2535
|
api: createApi$4,
|
|
2127
2536
|
onInit(ctx) {
|
|
@@ -2130,7 +2539,6 @@ const routerPlugin = createPlugin$1("router", {
|
|
|
2130
2539
|
ctx.state.table = buildRouterTable(ctx.config, baseUrl, i18n.locales(), i18n.defaultLocale());
|
|
2131
2540
|
}
|
|
2132
2541
|
});
|
|
2133
|
-
|
|
2134
2542
|
//#endregion
|
|
2135
2543
|
//#region src/plugins/head/primitives.ts
|
|
2136
2544
|
/** OG/Twitter article-meta property prefixes (factored to satisfy no-duplicate-string). */
|
|
@@ -2294,7 +2702,6 @@ function buildArticleHead(articleMeta, canonicalUrl) {
|
|
|
2294
2702
|
elements.push(jsonLd(ld));
|
|
2295
2703
|
return elements;
|
|
2296
2704
|
}
|
|
2297
|
-
|
|
2298
2705
|
//#endregion
|
|
2299
2706
|
//#region src/plugins/head/compose.ts
|
|
2300
2707
|
/**
|
|
@@ -2455,7 +2862,6 @@ function serializeElement(element) {
|
|
|
2455
2862
|
function serializeHead(elements) {
|
|
2456
2863
|
return elements.map((element) => serializeElement(element)).join("");
|
|
2457
2864
|
}
|
|
2458
|
-
|
|
2459
2865
|
//#endregion
|
|
2460
2866
|
//#region src/plugins/head/api.ts
|
|
2461
2867
|
/**
|
|
@@ -2497,7 +2903,19 @@ function readDefaults(state) {
|
|
|
2497
2903
|
* ```
|
|
2498
2904
|
*/
|
|
2499
2905
|
function createApi$3(ctx) {
|
|
2500
|
-
return {
|
|
2906
|
+
return {
|
|
2907
|
+
/**
|
|
2908
|
+
* Compose the final `<head>` inner HTML for a route (pulled by `build`).
|
|
2909
|
+
*
|
|
2910
|
+
* @param route - The resolved route descriptor (incl. its `.head()` HeadConfig).
|
|
2911
|
+
* @param data - The page data object passed to the route's loader/render.
|
|
2912
|
+
* @returns The serialized inner HTML of `<head>`.
|
|
2913
|
+
* @example
|
|
2914
|
+
* ```ts
|
|
2915
|
+
* api.render(route, { title: "Post" });
|
|
2916
|
+
* ```
|
|
2917
|
+
*/
|
|
2918
|
+
render(route, data) {
|
|
2501
2919
|
return serializeHead(composeHead({
|
|
2502
2920
|
route,
|
|
2503
2921
|
data,
|
|
@@ -2508,7 +2926,6 @@ function createApi$3(ctx) {
|
|
|
2508
2926
|
}));
|
|
2509
2927
|
} };
|
|
2510
2928
|
}
|
|
2511
|
-
|
|
2512
2929
|
//#endregion
|
|
2513
2930
|
//#region src/plugins/head/config.ts
|
|
2514
2931
|
/** Error prefix for all head config-validation failures. */
|
|
@@ -2563,7 +2980,6 @@ function normalizeHeadConfig(config) {
|
|
|
2563
2980
|
if (config.twitterHandle !== void 0) defaults.twitterHandle = config.twitterHandle;
|
|
2564
2981
|
return Object.freeze(defaults);
|
|
2565
2982
|
}
|
|
2566
|
-
|
|
2567
2983
|
//#endregion
|
|
2568
2984
|
//#region src/plugins/head/helpers.ts
|
|
2569
2985
|
/**
|
|
@@ -2592,7 +3008,6 @@ const headHelpers = {
|
|
|
2592
3008
|
feedLink,
|
|
2593
3009
|
buildArticleHead
|
|
2594
3010
|
};
|
|
2595
|
-
|
|
2596
3011
|
//#endregion
|
|
2597
3012
|
//#region src/plugins/head/state.ts
|
|
2598
3013
|
/**
|
|
@@ -2613,7 +3028,6 @@ const headHelpers = {
|
|
|
2613
3028
|
function createState$3(_ctx) {
|
|
2614
3029
|
return { defaults: null };
|
|
2615
3030
|
}
|
|
2616
|
-
|
|
2617
3031
|
//#endregion
|
|
2618
3032
|
//#region src/plugins/head/index.ts
|
|
2619
3033
|
/**
|
|
@@ -2634,7 +3048,6 @@ const headPlugin = createPlugin$1("head", {
|
|
|
2634
3048
|
ctx.state.defaults = normalizeHeadConfig(ctx.config);
|
|
2635
3049
|
}
|
|
2636
3050
|
});
|
|
2637
|
-
|
|
2638
3051
|
//#endregion
|
|
2639
3052
|
//#region src/plugins/build/phases/bundle.ts
|
|
2640
3053
|
/**
|
|
@@ -2736,7 +3149,6 @@ async function bundle(ctx, options = {}) {
|
|
|
2736
3149
|
await runOne(ctx, runner, "css", cssEntrypoints, path.join(outDir, "assets"), minify);
|
|
2737
3150
|
await runOne(ctx, runner, "js", jsEntrypoints, path.join(outDir, "assets"), minify);
|
|
2738
3151
|
}
|
|
2739
|
-
|
|
2740
3152
|
//#endregion
|
|
2741
3153
|
//#region src/plugins/build/phases/content.ts
|
|
2742
3154
|
/**
|
|
@@ -2779,7 +3191,6 @@ function readCachedContent(ctx) {
|
|
|
2779
3191
|
const cached = ctx.state.buildCache.get(CONTENT_CACHE_KEY);
|
|
2780
3192
|
return cached instanceof Map ? cached : /* @__PURE__ */ new Map();
|
|
2781
3193
|
}
|
|
2782
|
-
|
|
2783
3194
|
//#endregion
|
|
2784
3195
|
//#region src/plugins/build/phases/feeds.ts
|
|
2785
3196
|
/**
|
|
@@ -2861,7 +3272,6 @@ async function generateFeeds(ctx) {
|
|
|
2861
3272
|
ctx.log.debug("build:feeds", { items: guids.length });
|
|
2862
3273
|
return result;
|
|
2863
3274
|
}
|
|
2864
|
-
|
|
2865
3275
|
//#endregion
|
|
2866
3276
|
//#region src/plugins/build/phases/images.ts
|
|
2867
3277
|
/**
|
|
@@ -2901,7 +3311,6 @@ async function processImages(ctx, options = {}) {
|
|
|
2901
3311
|
ctx.log.debug("build:images", { copied });
|
|
2902
3312
|
return copied;
|
|
2903
3313
|
}
|
|
2904
|
-
|
|
2905
3314
|
//#endregion
|
|
2906
3315
|
//#region src/plugins/build/phases/og-images.tsx
|
|
2907
3316
|
/**
|
|
@@ -2915,8 +3324,6 @@ const DEFAULT_SIZE = {
|
|
|
2915
3324
|
width: 1200,
|
|
2916
3325
|
height: 630
|
|
2917
3326
|
};
|
|
2918
|
-
/** The fixed concurrency bound for the OG render pool. */
|
|
2919
|
-
const OG_CONCURRENCY = 4;
|
|
2920
3327
|
/** Recognized font file extensions. */
|
|
2921
3328
|
const FONT_EXTENSIONS$1 = [
|
|
2922
3329
|
".ttf",
|
|
@@ -3037,7 +3444,7 @@ async function generateOgImages(ctx, options = {}) {
|
|
|
3037
3444
|
const articles = selectArticles(readCachedContent(ctx));
|
|
3038
3445
|
const cache = ctx.state.ogImageHashCache;
|
|
3039
3446
|
await loadDiskCache(ctx.config.outDir, cache);
|
|
3040
|
-
const limit = pLimit(
|
|
3447
|
+
const limit = pLimit(4);
|
|
3041
3448
|
let active = 0;
|
|
3042
3449
|
let peakConcurrency = 0;
|
|
3043
3450
|
let rendered = 0;
|
|
@@ -3110,7 +3517,6 @@ async function persistDiskCache(outDir, cache) {
|
|
|
3110
3517
|
await mkdir(dir, { recursive: true });
|
|
3111
3518
|
await writeFile(path.join(dir, "og-images.json"), JSON.stringify(Object.fromEntries(cache)), "utf8");
|
|
3112
3519
|
}
|
|
3113
|
-
|
|
3114
3520
|
//#endregion
|
|
3115
3521
|
//#region src/plugins/build/phases/pages.tsx
|
|
3116
3522
|
/**
|
|
@@ -3278,7 +3684,6 @@ async function renderPages(ctx) {
|
|
|
3278
3684
|
rootHtml: root?.html ?? null
|
|
3279
3685
|
};
|
|
3280
3686
|
}
|
|
3281
|
-
|
|
3282
3687
|
//#endregion
|
|
3283
3688
|
//#region src/plugins/build/phases/sitemap.ts
|
|
3284
3689
|
/**
|
|
@@ -3360,7 +3765,6 @@ async function generateSitemap(ctx) {
|
|
|
3360
3765
|
robots
|
|
3361
3766
|
};
|
|
3362
3767
|
}
|
|
3363
|
-
|
|
3364
3768
|
//#endregion
|
|
3365
3769
|
//#region src/plugins/build/pipeline.ts
|
|
3366
3770
|
/**
|
|
@@ -3491,7 +3895,6 @@ async function runPipeline(ctx, options) {
|
|
|
3491
3895
|
phaseContext.emit("build:complete", result);
|
|
3492
3896
|
return result;
|
|
3493
3897
|
}
|
|
3494
|
-
|
|
3495
3898
|
//#endregion
|
|
3496
3899
|
//#region src/plugins/build/api.ts
|
|
3497
3900
|
/**
|
|
@@ -3530,9 +3933,29 @@ const defaultConfig$1 = {
|
|
|
3530
3933
|
*/
|
|
3531
3934
|
function createApi$2(ctx) {
|
|
3532
3935
|
return {
|
|
3936
|
+
/**
|
|
3937
|
+
* Run the full SSG pipeline and write the site to disk.
|
|
3938
|
+
*
|
|
3939
|
+
* @param options - Optional run overrides.
|
|
3940
|
+
* @param options.outDir - Override the configured output directory for this run.
|
|
3941
|
+
* @returns The build result (outDir, pageCount, durationMs).
|
|
3942
|
+
* @example
|
|
3943
|
+
* ```ts
|
|
3944
|
+
* await api.run({ outDir: "./preview" });
|
|
3945
|
+
* ```
|
|
3946
|
+
*/
|
|
3533
3947
|
run(options) {
|
|
3534
3948
|
return runPipeline(ctx, options);
|
|
3535
3949
|
},
|
|
3950
|
+
/**
|
|
3951
|
+
* List the phases in execution order (introspection / tooling).
|
|
3952
|
+
*
|
|
3953
|
+
* @returns A fresh array of the static ordered phase names.
|
|
3954
|
+
* @example
|
|
3955
|
+
* ```ts
|
|
3956
|
+
* api.phases();
|
|
3957
|
+
* ```
|
|
3958
|
+
*/
|
|
3536
3959
|
phases() {
|
|
3537
3960
|
return [...PHASE_ORDER];
|
|
3538
3961
|
}
|
|
@@ -3567,7 +3990,6 @@ function validateConfig$1(config) {
|
|
|
3567
3990
|
if (typeof config.outDir !== "string" || config.outDir.trim().length === 0) throw new Error(`${ERROR_PREFIX$5}.outDir: must be a non-empty string.`);
|
|
3568
3991
|
if (config.ogImage) validateFonts(config.ogImage);
|
|
3569
3992
|
}
|
|
3570
|
-
|
|
3571
3993
|
//#endregion
|
|
3572
3994
|
//#region src/plugins/build/events.ts
|
|
3573
3995
|
/**
|
|
@@ -3587,7 +4009,6 @@ function createEvents(register) {
|
|
|
3587
4009
|
"build:complete": register("Emitted once after a successful build run")
|
|
3588
4010
|
};
|
|
3589
4011
|
}
|
|
3590
|
-
|
|
3591
4012
|
//#endregion
|
|
3592
4013
|
//#region src/plugins/build/state.ts
|
|
3593
4014
|
/**
|
|
@@ -3614,7 +4035,6 @@ function createState$2(ctx) {
|
|
|
3614
4035
|
ogImageHashCache: /* @__PURE__ */ new Map()
|
|
3615
4036
|
};
|
|
3616
4037
|
}
|
|
3617
|
-
|
|
3618
4038
|
//#endregion
|
|
3619
4039
|
//#region src/plugins/build/index.ts
|
|
3620
4040
|
/**
|
|
@@ -3635,7 +4055,6 @@ const buildPlugin = createPlugin$1("build", {
|
|
|
3635
4055
|
api: createApi$2,
|
|
3636
4056
|
onInit: (ctx) => validateConfig$1(ctx.config)
|
|
3637
4057
|
});
|
|
3638
|
-
|
|
3639
4058
|
//#endregion
|
|
3640
4059
|
//#region src/plugins/deploy/wrangler.ts
|
|
3641
4060
|
/**
|
|
@@ -3813,8 +4232,6 @@ const ERROR_SIGNATURES = [
|
|
|
3813
4232
|
advice: "A network failure occurred. Check connectivity and retry."
|
|
3814
4233
|
}
|
|
3815
4234
|
];
|
|
3816
|
-
/** Number of trailing characters of scrubbed stderr to surface on an unknown failure. */
|
|
3817
|
-
const STDERR_TAIL_LENGTH = 500;
|
|
3818
4235
|
/**
|
|
3819
4236
|
* Map a non-zero wrangler exit and scrubbed stderr to an actionable error
|
|
3820
4237
|
* `code` + message. Matching is case-insensitive against the scrubbed stderr;
|
|
@@ -3835,7 +4252,7 @@ function classifyWranglerError(exitCode, scrubbedStderr) {
|
|
|
3835
4252
|
};
|
|
3836
4253
|
return {
|
|
3837
4254
|
code: "ERR_DEPLOY_WRANGLER_FAILED",
|
|
3838
|
-
message: `${ERROR_PREFIX$4}: wrangler failed (exit ${exitCode}).\n ${scrubbedStderr.trim().slice(-
|
|
4255
|
+
message: `${ERROR_PREFIX$4}: wrangler failed (exit ${exitCode}).\n ${scrubbedStderr.trim().slice(-500)}`
|
|
3839
4256
|
};
|
|
3840
4257
|
}
|
|
3841
4258
|
/**
|
|
@@ -3894,7 +4311,6 @@ async function runWrangler(input) {
|
|
|
3894
4311
|
exitCode
|
|
3895
4312
|
};
|
|
3896
4313
|
}
|
|
3897
|
-
|
|
3898
4314
|
//#endregion
|
|
3899
4315
|
//#region src/plugins/deploy/generators/github-workflow.ts
|
|
3900
4316
|
/**
|
|
@@ -3949,7 +4365,6 @@ jobs:
|
|
|
3949
4365
|
command: pages deploy dist --project-name ${input.slug}
|
|
3950
4366
|
`;
|
|
3951
4367
|
}
|
|
3952
|
-
|
|
3953
4368
|
//#endregion
|
|
3954
4369
|
//#region src/plugins/deploy/generators/wrangler-config.ts
|
|
3955
4370
|
/**
|
|
@@ -3993,7 +4408,6 @@ async function readWranglerConfig(cwd) {
|
|
|
3993
4408
|
return null;
|
|
3994
4409
|
}
|
|
3995
4410
|
}
|
|
3996
|
-
|
|
3997
4411
|
//#endregion
|
|
3998
4412
|
//#region src/plugins/deploy/init.ts
|
|
3999
4413
|
/**
|
|
@@ -4097,7 +4511,6 @@ async function reconcile(input) {
|
|
|
4097
4511
|
await writeFile(path.join(cwd, relativePath), expected, "utf8");
|
|
4098
4512
|
result.written.push(relativePath);
|
|
4099
4513
|
}
|
|
4100
|
-
|
|
4101
4514
|
//#endregion
|
|
4102
4515
|
//#region src/plugins/deploy/preflight.ts
|
|
4103
4516
|
/**
|
|
@@ -4191,7 +4604,6 @@ async function runPreflight(config, root, env = process.env) {
|
|
|
4191
4604
|
if (stats.fileCount > limit) throw deployError("ERR_DEPLOY_TOO_MANY_FILES", `${ERROR_PREFIX$3}: outDir contains ${stats.fileCount} files; the limit is ${limit}.\n Raise it with ${MAX_FILES_ENV} (paid tier) or reduce the output.`);
|
|
4192
4605
|
if (stats.oversizePath !== null) throw deployError("ERR_DEPLOY_FILE_TOO_LARGE", `${ERROR_PREFIX$3}: file ${JSON.stringify(stats.oversizePath)} exceeds the 25 MiB per-file limit.`);
|
|
4193
4606
|
}
|
|
4194
|
-
|
|
4195
4607
|
//#endregion
|
|
4196
4608
|
//#region src/plugins/deploy/slug.ts
|
|
4197
4609
|
/**
|
|
@@ -4232,7 +4644,6 @@ function toSlug(name) {
|
|
|
4232
4644
|
slug = slug.slice(0, end);
|
|
4233
4645
|
return slug.length > 0 ? slug : FALLBACK_SLUG;
|
|
4234
4646
|
}
|
|
4235
|
-
|
|
4236
4647
|
//#endregion
|
|
4237
4648
|
//#region src/plugins/deploy/api.ts
|
|
4238
4649
|
/** Error prefix for deploy config/validation failures (spec/11 Part-3). */
|
|
@@ -4272,6 +4683,15 @@ function validateConfig(ctx) {
|
|
|
4272
4683
|
*/
|
|
4273
4684
|
function createApi$1(ctx) {
|
|
4274
4685
|
return {
|
|
4686
|
+
/**
|
|
4687
|
+
* Deploy the built outDir to Cloudflare Pages via the wrangler subprocess.
|
|
4688
|
+
*
|
|
4689
|
+
* @param options - Optional branch override and build toggle.
|
|
4690
|
+
* @returns The deploy result (url, deploymentId, branch, durationMs).
|
|
4691
|
+
* @throws {Error} With a `code` from the deploy error taxonomy on any failure.
|
|
4692
|
+
* @example
|
|
4693
|
+
* await api.run();
|
|
4694
|
+
*/
|
|
4275
4695
|
async run(options = {}) {
|
|
4276
4696
|
const root = process.cwd();
|
|
4277
4697
|
const slug = toSlug(ctx.require(sitePlugin).name());
|
|
@@ -4311,10 +4731,25 @@ function createApi$1(ctx) {
|
|
|
4311
4731
|
});
|
|
4312
4732
|
return result;
|
|
4313
4733
|
},
|
|
4734
|
+
/**
|
|
4735
|
+
* Return the most recent successful deploy result, or null if none occurred.
|
|
4736
|
+
*
|
|
4737
|
+
* @returns A frozen snapshot of the last DeployResult, or null.
|
|
4738
|
+
* @example
|
|
4739
|
+
* const last = api.getLastDeployment();
|
|
4740
|
+
*/
|
|
4314
4741
|
getLastDeployment() {
|
|
4315
4742
|
const last = ctx.state.lastDeployment;
|
|
4316
4743
|
return last ? Object.freeze({ ...last }) : null;
|
|
4317
4744
|
},
|
|
4745
|
+
/**
|
|
4746
|
+
* Generate deploy scaffolding (wrangler.jsonc + optional GitHub workflow).
|
|
4747
|
+
*
|
|
4748
|
+
* @param options - Optional ci toggle and check (drift-only) mode.
|
|
4749
|
+
* @returns Which files were written, skipped, or would drift.
|
|
4750
|
+
* @example
|
|
4751
|
+
* await api.init({ ci: true });
|
|
4752
|
+
*/
|
|
4318
4753
|
async init(options = {}) {
|
|
4319
4754
|
const slug = toSlug(ctx.require(sitePlugin).name());
|
|
4320
4755
|
return writeScaffolding({
|
|
@@ -4326,7 +4761,6 @@ function createApi$1(ctx) {
|
|
|
4326
4761
|
}
|
|
4327
4762
|
};
|
|
4328
4763
|
}
|
|
4329
|
-
|
|
4330
4764
|
//#endregion
|
|
4331
4765
|
//#region src/plugins/deploy/defaults.ts
|
|
4332
4766
|
/**
|
|
@@ -4346,7 +4780,6 @@ const defaultConfig = {
|
|
|
4346
4780
|
compatibilityDate: "2024-01-01",
|
|
4347
4781
|
ci: false
|
|
4348
4782
|
};
|
|
4349
|
-
|
|
4350
4783
|
//#endregion
|
|
4351
4784
|
//#region src/plugins/deploy/events.ts
|
|
4352
4785
|
/**
|
|
@@ -4361,7 +4794,6 @@ const defaultConfig = {
|
|
|
4361
4794
|
* ```
|
|
4362
4795
|
*/
|
|
4363
4796
|
const deployEvents = (register) => ({ "deploy:complete": register("Deployment completed successfully") });
|
|
4364
|
-
|
|
4365
4797
|
//#endregion
|
|
4366
4798
|
//#region src/plugins/deploy/state.ts
|
|
4367
4799
|
/**
|
|
@@ -4399,7 +4831,6 @@ function createState$1(_ctx) {
|
|
|
4399
4831
|
spawn: defaultSpawn
|
|
4400
4832
|
};
|
|
4401
4833
|
}
|
|
4402
|
-
|
|
4403
4834
|
//#endregion
|
|
4404
4835
|
//#region src/plugins/deploy/index.ts
|
|
4405
4836
|
/**
|
|
@@ -4417,7 +4848,6 @@ const deployPlugin = createPlugin$1("deploy", {
|
|
|
4417
4848
|
onInit: validateConfig,
|
|
4418
4849
|
api: createApi$1
|
|
4419
4850
|
});
|
|
4420
|
-
|
|
4421
4851
|
//#endregion
|
|
4422
4852
|
//#region src/plugins/spa/api.ts
|
|
4423
4853
|
/**
|
|
@@ -4432,19 +4862,39 @@ const deployPlugin = createPlugin$1("deploy", {
|
|
|
4432
4862
|
*/
|
|
4433
4863
|
function createApi(ctx) {
|
|
4434
4864
|
return {
|
|
4865
|
+
/**
|
|
4866
|
+
* Register a component definition (last-registered-wins); warns on collision.
|
|
4867
|
+
*
|
|
4868
|
+
* @param component - The component definition created via `createComponent`.
|
|
4869
|
+
* @example
|
|
4870
|
+
* app.spa.register(counter);
|
|
4871
|
+
*/
|
|
4435
4872
|
register(component) {
|
|
4436
4873
|
if (ctx.state.registeredComponents.has(component.name)) ctx.log.warn("spa:component-collision", { name: component.name });
|
|
4437
4874
|
ctx.state.kernel?.register(component);
|
|
4438
4875
|
},
|
|
4876
|
+
/**
|
|
4877
|
+
* Programmatically navigate to a path (client runtime; no-op without a DOM).
|
|
4878
|
+
*
|
|
4879
|
+
* @param path - Target path (pathname, optionally with search/hash).
|
|
4880
|
+
* @example
|
|
4881
|
+
* app.spa.navigate("/about");
|
|
4882
|
+
*/
|
|
4439
4883
|
navigate(path) {
|
|
4440
4884
|
ctx.state.kernel?.processNav(path);
|
|
4441
4885
|
},
|
|
4886
|
+
/**
|
|
4887
|
+
* Read the current resolved URL.
|
|
4888
|
+
*
|
|
4889
|
+
* @returns The current pathname + search.
|
|
4890
|
+
* @example
|
|
4891
|
+
* app.spa.current();
|
|
4892
|
+
*/
|
|
4442
4893
|
current() {
|
|
4443
4894
|
return ctx.state.currentUrl;
|
|
4444
4895
|
}
|
|
4445
4896
|
};
|
|
4446
4897
|
}
|
|
4447
|
-
|
|
4448
4898
|
//#endregion
|
|
4449
4899
|
//#region src/plugins/spa/events.ts
|
|
4450
4900
|
/**
|
|
@@ -4464,7 +4914,6 @@ function spaEvents(register) {
|
|
|
4464
4914
|
"spa:component-unmount": register("A component instance detached from an element.")
|
|
4465
4915
|
};
|
|
4466
4916
|
}
|
|
4467
|
-
|
|
4468
4917
|
//#endregion
|
|
4469
4918
|
//#region src/plugins/spa/types.ts
|
|
4470
4919
|
var types_exports$7 = /* @__PURE__ */ __exportAll({ COMPONENT_HOOK_NAMES: () => COMPONENT_HOOK_NAMES });
|
|
@@ -4477,11 +4926,7 @@ const COMPONENT_HOOK_NAMES = [
|
|
|
4477
4926
|
"onUnMount",
|
|
4478
4927
|
"onDestroy"
|
|
4479
4928
|
];
|
|
4480
|
-
|
|
4481
|
-
//#endregion
|
|
4482
|
-
//#region src/plugins/spa/components.ts
|
|
4483
|
-
/** The set of legal hook names, frozen for O(1) membership checks. */
|
|
4484
|
-
const HOOK_NAME_SET = new Set(COMPONENT_HOOK_NAMES);
|
|
4929
|
+
new Set(COMPONENT_HOOK_NAMES);
|
|
4485
4930
|
/**
|
|
4486
4931
|
* Extracts the page data payload from the inline `script#__DATA__` element.
|
|
4487
4932
|
* Returns an empty object when the script is absent, empty, or invalid JSON.
|
|
@@ -4649,7 +5094,6 @@ function notifyNavEnd(state) {
|
|
|
4649
5094
|
const data = typeof document === "undefined" ? {} : extractPageData(document);
|
|
4650
5095
|
for (const [element, instance] of state.instances) if (instance.persistent) runHook(instance, "onNavEnd", makeContext(element, data));
|
|
4651
5096
|
}
|
|
4652
|
-
|
|
4653
5097
|
//#endregion
|
|
4654
5098
|
//#region src/plugins/spa/head.ts
|
|
4655
5099
|
/** Single-element head selectors synced by replace/append/remove on navigation. */
|
|
@@ -4724,7 +5168,6 @@ function syncHead(_head, doc) {
|
|
|
4724
5168
|
for (const selector of META_SELECTORS) syncElement(selector, doc);
|
|
4725
5169
|
for (const selector of REPLACE_ALL_SELECTORS) replaceAllBySelector(selector, doc);
|
|
4726
5170
|
}
|
|
4727
|
-
|
|
4728
5171
|
//#endregion
|
|
4729
5172
|
//#region src/plugins/spa/progress.ts
|
|
4730
5173
|
/** Delay before the bar appears, so fast navigations show no indicator. */
|
|
@@ -4808,7 +5251,6 @@ function createProgressBar(enabled) {
|
|
|
4808
5251
|
done
|
|
4809
5252
|
};
|
|
4810
5253
|
}
|
|
4811
|
-
|
|
4812
5254
|
//#endregion
|
|
4813
5255
|
//#region src/plugins/spa/router.ts
|
|
4814
5256
|
/**
|
|
@@ -5043,7 +5485,6 @@ function attachRouter(handlers) {
|
|
|
5043
5485
|
const navigation = getNavigation();
|
|
5044
5486
|
return navigation ? attachNavigationApi(navigation, handlers) : attachHistoryFallback(handlers);
|
|
5045
5487
|
}
|
|
5046
|
-
|
|
5047
5488
|
//#endregion
|
|
5048
5489
|
//#region src/plugins/spa/state.ts
|
|
5049
5490
|
/** Error prefix for spa config-validation failures (spec/11 Part-3). */
|
|
@@ -5117,7 +5558,6 @@ function createState(_ctx) {
|
|
|
5117
5558
|
kernel: null
|
|
5118
5559
|
};
|
|
5119
5560
|
}
|
|
5120
|
-
|
|
5121
5561
|
//#endregion
|
|
5122
5562
|
//#region src/plugins/spa/kernel.ts
|
|
5123
5563
|
/**
|
|
@@ -5226,10 +5666,22 @@ function createSpaKernel(state, config, emit, deps) {
|
|
|
5226
5666
|
onError: handleError
|
|
5227
5667
|
};
|
|
5228
5668
|
return {
|
|
5669
|
+
/**
|
|
5670
|
+
* Register config components and seed currentUrl from the document.
|
|
5671
|
+
*
|
|
5672
|
+
* @example
|
|
5673
|
+
* kernel.init();
|
|
5674
|
+
*/
|
|
5229
5675
|
init() {
|
|
5230
5676
|
for (const component of resolved.components) registerComponent(state, component);
|
|
5231
5677
|
state.currentUrl = currentLocationUrl();
|
|
5232
5678
|
},
|
|
5679
|
+
/**
|
|
5680
|
+
* Boot navigation interception + initial scan (throws if already started).
|
|
5681
|
+
*
|
|
5682
|
+
* @example
|
|
5683
|
+
* kernel.boot();
|
|
5684
|
+
*/
|
|
5233
5685
|
boot() {
|
|
5234
5686
|
if (typeof document === "undefined") return;
|
|
5235
5687
|
if (state.started) throw new Error(`${ERROR_PREFIX} spa kernel already started.\n Call app.stop() before booting again (single boot per app).`);
|
|
@@ -5239,16 +5691,42 @@ function createSpaKernel(state, config, emit, deps) {
|
|
|
5239
5691
|
scanAndMount(state, emit, resolved.swapSelector);
|
|
5240
5692
|
state.started = true;
|
|
5241
5693
|
},
|
|
5694
|
+
/**
|
|
5695
|
+
* Register a component definition (last-registered-wins).
|
|
5696
|
+
*
|
|
5697
|
+
* @param component - The component definition to register.
|
|
5698
|
+
* @example
|
|
5699
|
+
* kernel.register(counter);
|
|
5700
|
+
*/
|
|
5242
5701
|
register(component) {
|
|
5243
5702
|
registerComponent(state, component);
|
|
5244
5703
|
},
|
|
5704
|
+
/**
|
|
5705
|
+
* Process a navigation to `path` (fetch then swap; full reload on error).
|
|
5706
|
+
*
|
|
5707
|
+
* @param path - The target path to navigate to.
|
|
5708
|
+
* @example
|
|
5709
|
+
* kernel.processNav("/about");
|
|
5710
|
+
*/
|
|
5245
5711
|
processNav(path) {
|
|
5246
5712
|
if (typeof document === "undefined") return;
|
|
5247
5713
|
performNavigation(path, handlers).catch(() => {});
|
|
5248
5714
|
},
|
|
5715
|
+
/**
|
|
5716
|
+
* Scan the swap region and mount components for matching elements.
|
|
5717
|
+
*
|
|
5718
|
+
* @example
|
|
5719
|
+
* kernel.scan();
|
|
5720
|
+
*/
|
|
5249
5721
|
scan() {
|
|
5250
5722
|
scanAndMount(state, emit, resolved.swapSelector);
|
|
5251
5723
|
},
|
|
5724
|
+
/**
|
|
5725
|
+
* Tear down router listeners, dispose all instances, reset boot state.
|
|
5726
|
+
*
|
|
5727
|
+
* @example
|
|
5728
|
+
* kernel.dispose();
|
|
5729
|
+
*/
|
|
5252
5730
|
dispose() {
|
|
5253
5731
|
state.destroyRouter?.();
|
|
5254
5732
|
state.destroyRouter = null;
|
|
@@ -5277,7 +5755,6 @@ function initSpa(ctx) {
|
|
|
5277
5755
|
kernelRef.current = kernel;
|
|
5278
5756
|
kernel.init();
|
|
5279
5757
|
}
|
|
5280
|
-
|
|
5281
5758
|
//#endregion
|
|
5282
5759
|
//#region src/plugins/spa/lifecycle.ts
|
|
5283
5760
|
/** Router/instance teardown captured during onStart (undefined when stopped). */
|
|
@@ -5325,7 +5802,6 @@ function disposeSpa() {
|
|
|
5325
5802
|
logRef = void 0;
|
|
5326
5803
|
}
|
|
5327
5804
|
}
|
|
5328
|
-
|
|
5329
5805
|
//#endregion
|
|
5330
5806
|
//#region src/plugins/spa/index.ts
|
|
5331
5807
|
/**
|
|
@@ -5349,42 +5825,28 @@ const spaPlugin = createPlugin$1("spa", {
|
|
|
5349
5825
|
},
|
|
5350
5826
|
onStop: disposeSpa
|
|
5351
5827
|
});
|
|
5352
|
-
|
|
5353
5828
|
//#endregion
|
|
5354
5829
|
//#region src/plugins/build/types.ts
|
|
5355
5830
|
var types_exports = /* @__PURE__ */ __exportAll({});
|
|
5356
|
-
|
|
5357
5831
|
//#endregion
|
|
5358
5832
|
//#region src/plugins/content/types.ts
|
|
5359
5833
|
var types_exports$1 = /* @__PURE__ */ __exportAll({});
|
|
5360
|
-
|
|
5361
5834
|
//#endregion
|
|
5362
5835
|
//#region src/plugins/deploy/types.ts
|
|
5363
5836
|
var types_exports$2 = /* @__PURE__ */ __exportAll({});
|
|
5364
|
-
|
|
5365
5837
|
//#endregion
|
|
5366
5838
|
//#region src/plugins/env/types.ts
|
|
5367
5839
|
var types_exports$3 = /* @__PURE__ */ __exportAll({});
|
|
5368
|
-
|
|
5369
5840
|
//#endregion
|
|
5370
5841
|
//#region src/plugins/head/types.ts
|
|
5371
5842
|
var types_exports$4 = /* @__PURE__ */ __exportAll({});
|
|
5372
|
-
|
|
5373
5843
|
//#endregion
|
|
5374
5844
|
//#region src/plugins/log/types.ts
|
|
5375
5845
|
var types_exports$5 = /* @__PURE__ */ __exportAll({});
|
|
5376
|
-
|
|
5377
5846
|
//#endregion
|
|
5378
5847
|
//#region src/plugins/router/types.ts
|
|
5379
5848
|
var types_exports$6 = /* @__PURE__ */ __exportAll({});
|
|
5380
|
-
|
|
5381
|
-
//#endregion
|
|
5382
|
-
//#region src/index.ts
|
|
5383
|
-
/**
|
|
5384
|
-
* @file `@moku-labs/web` — a Moku Layer-2 content static-site + SPA framework.
|
|
5385
|
-
* @see README.md
|
|
5386
|
-
*/
|
|
5387
|
-
const framework = createCore(coreConfig, {
|
|
5849
|
+
const { createApp, createPlugin } = createCore(coreConfig, {
|
|
5388
5850
|
plugins: [
|
|
5389
5851
|
sitePlugin,
|
|
5390
5852
|
i18nPlugin,
|
|
@@ -5397,7 +5859,5 @@ const framework = createCore(coreConfig, {
|
|
|
5397
5859
|
],
|
|
5398
5860
|
pluginConfigs: {}
|
|
5399
5861
|
});
|
|
5400
|
-
const { createApp, createPlugin } = framework;
|
|
5401
|
-
|
|
5402
5862
|
//#endregion
|
|
5403
|
-
export { types_exports as Build, types_exports$1 as Content, types_exports$2 as Deploy, types_exports$3 as Env, types_exports$4 as Head, types_exports$5 as Log, types_exports$6 as Router, types_exports$7 as Spa, buildArticleHead, buildPlugin, canonical, contentPlugin, createApp, createPlugin, defineRoutes, deployPlugin, envPlugin, feedLink, headPlugin, hreflang, i18nPlugin, jsonLd, logPlugin, meta, og, route, routerPlugin, sitePlugin, spaPlugin, twitter };
|
|
5863
|
+
export { types_exports as Build, types_exports$1 as Content, types_exports$2 as Deploy, types_exports$3 as Env, types_exports$4 as Head, types_exports$5 as Log, types_exports$6 as Router, types_exports$7 as Spa, buildArticleHead, buildPlugin, canonical, contentPlugin, createApp, createPlugin, defineRoutes, deployPlugin, envPlugin, feedLink, headPlugin, hreflang, i18nPlugin, jsonLd, logPlugin, meta, og, route, routerPlugin, sitePlugin, spaPlugin, twitter };
|