@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/index.cjs CHANGED
@@ -1,4 +1,4 @@
1
- Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  //#region \0rolldown/runtime.js
3
3
  var __create = Object.create;
4
4
  var __defProp = Object.defineProperty;
@@ -8,28 +8,20 @@ var __getProtoOf = Object.getPrototypeOf;
8
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
9
9
  var __exportAll = (all, no_symbols) => {
10
10
  let target = {};
11
- for (var name in all) {
12
- __defProp(target, name, {
13
- get: all[name],
14
- enumerable: true
15
- });
16
- }
17
- if (!no_symbols) {
18
- __defProp(target, Symbol.toStringTag, { value: "Module" });
19
- }
11
+ for (var name in all) __defProp(target, name, {
12
+ get: all[name],
13
+ enumerable: true
14
+ });
15
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
20
16
  return target;
21
17
  };
22
18
  var __copyProps = (to, from, except, desc) => {
23
- if (from && typeof from === "object" || typeof from === "function") {
24
- for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
25
- key = keys[i];
26
- if (!__hasOwnProp.call(to, key) && key !== except) {
27
- __defProp(to, key, {
28
- get: ((k) => from[k]).bind(null, key),
29
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
30
- });
31
- }
32
- }
19
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
20
+ key = keys[i];
21
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
22
+ get: ((k) => from[k]).bind(null, key),
23
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
24
+ });
33
25
  }
34
26
  return to;
35
27
  };
@@ -37,38 +29,38 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
37
29
  value: mod,
38
30
  enumerable: true
39
31
  }) : target, mod));
40
-
41
32
  //#endregion
42
33
  let _moku_labs_core = require("@moku-labs/core");
43
34
  let node_fs = require("node:fs");
44
35
  let node_fs_promises = require("node:fs/promises");
45
36
  let node_path = require("node:path");
37
+ let node_path$1 = __toESM(node_path, 1);
46
38
  node_path = __toESM(node_path);
47
39
  let gray_matter = require("gray-matter");
48
- gray_matter = __toESM(gray_matter);
40
+ gray_matter = __toESM(gray_matter, 1);
49
41
  let _shikijs_rehype = require("@shikijs/rehype");
50
- _shikijs_rehype = __toESM(_shikijs_rehype);
42
+ _shikijs_rehype = __toESM(_shikijs_rehype, 1);
51
43
  let rehype_sanitize = require("rehype-sanitize");
52
- rehype_sanitize = __toESM(rehype_sanitize);
44
+ rehype_sanitize = __toESM(rehype_sanitize, 1);
53
45
  let rehype_stringify = require("rehype-stringify");
54
- rehype_stringify = __toESM(rehype_stringify);
46
+ rehype_stringify = __toESM(rehype_stringify, 1);
55
47
  let unified = require("unified");
56
48
  let rehype_raw = require("rehype-raw");
57
- rehype_raw = __toESM(rehype_raw);
49
+ rehype_raw = __toESM(rehype_raw, 1);
58
50
  let remark_directive = require("remark-directive");
59
- remark_directive = __toESM(remark_directive);
51
+ remark_directive = __toESM(remark_directive, 1);
60
52
  let remark_frontmatter = require("remark-frontmatter");
61
- remark_frontmatter = __toESM(remark_frontmatter);
53
+ remark_frontmatter = __toESM(remark_frontmatter, 1);
62
54
  let remark_gfm = require("remark-gfm");
63
- remark_gfm = __toESM(remark_gfm);
55
+ remark_gfm = __toESM(remark_gfm, 1);
64
56
  let remark_parse = require("remark-parse");
65
- remark_parse = __toESM(remark_parse);
57
+ remark_parse = __toESM(remark_parse, 1);
66
58
  let remark_rehype = require("remark-rehype");
67
- remark_rehype = __toESM(remark_rehype);
59
+ remark_rehype = __toESM(remark_rehype, 1);
68
60
  let unist_util_visit = require("unist-util-visit");
69
61
  let hast_util_sanitize = require("hast-util-sanitize");
70
62
  let reading_time = require("reading-time");
71
- reading_time = __toESM(reading_time);
63
+ reading_time = __toESM(reading_time, 1);
72
64
  let node_crypto = require("node:crypto");
73
65
  let feed = require("feed");
74
66
  let _resvg_resvg_js = require("@resvg/resvg-js");
@@ -76,7 +68,6 @@ let satori = require("satori");
76
68
  satori = __toESM(satori);
77
69
  let preact_jsx_runtime = require("preact/jsx-runtime");
78
70
  let preact_render_to_string = require("preact-render-to-string");
79
-
80
71
  //#region src/plugins/env/api.ts
81
72
  /** Error prefix for all env API failures. */
82
73
  const ERROR_PREFIX$13 = "[web]";
@@ -97,26 +88,75 @@ const ERROR_PREFIX$13 = "[web]";
97
88
  function createEnvApi(ctx) {
98
89
  const { resolved, publicMap } = ctx.state;
99
90
  return {
91
+ /**
92
+ * Reads a resolved variable.
93
+ *
94
+ * @param key - Variable name.
95
+ * @returns The value, or `undefined` if not present.
96
+ * @example
97
+ * ```ts
98
+ * api.get("PUBLIC_API_URL");
99
+ * ```
100
+ */
100
101
  get(key) {
101
102
  return resolved.get(key);
102
103
  },
104
+ /**
105
+ * Reads a variable that must exist.
106
+ *
107
+ * @param key - Variable name.
108
+ * @returns The value.
109
+ * @throws {Error} If the variable is undefined.
110
+ * @example
111
+ * ```ts
112
+ * api.require("DEPLOY_TOKEN");
113
+ * ```
114
+ */
103
115
  require(key) {
104
116
  const value = resolved.get(key);
105
117
  if (value === void 0) throw new Error(`${ERROR_PREFIX$13} env: required variable "${key}" is not defined.`);
106
118
  return value;
107
119
  },
120
+ /**
121
+ * Tests presence of a resolved variable.
122
+ *
123
+ * @param key - Variable name.
124
+ * @returns `true` if a value is present.
125
+ * @example
126
+ * ```ts
127
+ * api.has("PUBLIC_API_URL");
128
+ * ```
129
+ */
108
130
  has(key) {
109
131
  return resolved.has(key);
110
132
  },
133
+ /**
134
+ * Returns all public variables as a frozen plain object — a fresh copy,
135
+ * never the raw state map.
136
+ *
137
+ * @returns A frozen `Record` of public variable names to values.
138
+ * @example
139
+ * ```ts
140
+ * const payload = { ...api.getPublic() };
141
+ * ```
142
+ */
111
143
  getPublic() {
112
144
  return Object.freeze(Object.fromEntries(publicMap));
113
145
  },
146
+ /**
147
+ * Returns the already-frozen map of public variables.
148
+ *
149
+ * @returns The frozen public map.
150
+ * @example
151
+ * ```ts
152
+ * [...api.getPublicMap()];
153
+ * ```
154
+ */
114
155
  getPublicMap() {
115
156
  return publicMap;
116
157
  }
117
158
  };
118
159
  }
119
-
120
160
  //#endregion
121
161
  //#region src/plugins/env/state.ts
122
162
  /**
@@ -136,7 +176,6 @@ function createEnvState() {
136
176
  publicMap: /* @__PURE__ */ new Map()
137
177
  };
138
178
  }
139
-
140
179
  //#endregion
141
180
  //#region src/plugins/env/validate.ts
142
181
  /** Error message thrown by every frozen-map mutator. */
@@ -255,7 +294,6 @@ function validateSchema(ctx) {
255
294
  freezeMap(state.resolved);
256
295
  freezeMap(state.publicMap);
257
296
  }
258
-
259
297
  //#endregion
260
298
  //#region src/plugins/env/providers.ts
261
299
  /**
@@ -324,6 +362,15 @@ function parseDotenv(text) {
324
362
  function dotenv(path = DEFAULT_DOTENV_PATH) {
325
363
  return {
326
364
  name: `dotenv:${path}`,
365
+ /**
366
+ * Reads and parses the dotenv file fresh from disk; `{}` if it is missing.
367
+ *
368
+ * @returns The parsed environment record, or `{}` when the file is absent.
369
+ * @example
370
+ * ```ts
371
+ * dotenv(".env.local").load();
372
+ * ```
373
+ */
327
374
  load() {
328
375
  if (!(0, node_fs.existsSync)(path)) return {};
329
376
  return parseDotenv((0, node_fs.readFileSync)(path, "utf8"));
@@ -343,24 +390,20 @@ function dotenv(path = DEFAULT_DOTENV_PATH) {
343
390
  function processEnv() {
344
391
  return {
345
392
  name: "process-env",
393
+ /**
394
+ * Returns a shallow copy of `process.env` at call time.
395
+ *
396
+ * @returns A fresh shallow copy of `process.env`.
397
+ * @example
398
+ * ```ts
399
+ * processEnv().load();
400
+ * ```
401
+ */
346
402
  load() {
347
403
  return { ...process.env };
348
404
  }
349
405
  };
350
406
  }
351
-
352
- //#endregion
353
- //#region src/plugins/env/index.ts
354
- /**
355
- * @file Core plugin: universal env injection — schema + providers + PUBLIC_ cross-validation at onInit.
356
- * @see README.md
357
- */
358
- /** Plugin config defaults (R6 typed const). `providers: []` — framework sets `[dotenv(), processEnv()]` via the 4-level cascade. */
359
- const defaultEnvConfig = {
360
- schema: {},
361
- providers: [],
362
- publicPrefix: "PUBLIC_"
363
- };
364
407
  /**
365
408
  * Core plugin that resolves, validates, and freezes the environment at `onInit`,
366
409
  * exposing a read-only accessor at `ctx.env`. No `onStart`/`onStop` — holds no resource.
@@ -371,12 +414,15 @@ const defaultEnvConfig = {
371
414
  * ```
372
415
  */
373
416
  const envPlugin = (0, _moku_labs_core.createCorePlugin)("env", {
374
- config: defaultEnvConfig,
417
+ config: {
418
+ schema: {},
419
+ providers: [],
420
+ publicPrefix: "PUBLIC_"
421
+ },
375
422
  createState: createEnvState,
376
423
  api: createEnvApi,
377
424
  onInit: validateSchema
378
425
  });
379
-
380
426
  //#endregion
381
427
  //#region src/plugins/log/expect.ts
382
428
  /**
@@ -487,10 +533,33 @@ function describePartial(partial) {
487
533
  */
488
534
  function createExpectChain(entries) {
489
535
  const chain = {
536
+ /**
537
+ * Assert at least one entry has `event`, optionally matching `partial`.
538
+ *
539
+ * @param event - Event name to find.
540
+ * @param partial - Optional partial data shape (subset-matched).
541
+ * @returns The same chain for chaining.
542
+ * @throws {LogExpectAssertionError} When no matching entry exists.
543
+ * @example
544
+ * ```ts
545
+ * chain.toHaveEvent("build:phase", { status: "start" });
546
+ * ```
547
+ */
490
548
  toHaveEvent(event, partial) {
491
549
  if (!entries.some((entry) => entryMatches(entry, event, partial))) throw new LogExpectAssertionError(`Expected trace to contain event "${event}"${describePartial(partial)}, but none was found.`);
492
550
  return chain;
493
551
  },
552
+ /**
553
+ * Assert all of `events` appear in the trace in the given relative order.
554
+ *
555
+ * @param events - Ordered list of event names (gaps allowed).
556
+ * @returns The same chain for chaining.
557
+ * @throws {LogExpectAssertionError} When the ordering cannot be satisfied.
558
+ * @example
559
+ * ```ts
560
+ * chain.toHaveEventInOrder(["build:phase", "build:complete"]);
561
+ * ```
562
+ */
494
563
  toHaveEventInOrder(events) {
495
564
  let cursor = 0;
496
565
  for (const [position, event] of events.entries()) {
@@ -504,6 +573,18 @@ function createExpectChain(entries) {
504
573
  }
505
574
  return chain;
506
575
  },
576
+ /**
577
+ * Assert NO entry has `event` (optionally narrowed by `partial`).
578
+ *
579
+ * @param event - Event name that must be absent.
580
+ * @param partial - Optional partial data shape; only matching entries violate.
581
+ * @returns The same chain for chaining.
582
+ * @throws {LogExpectAssertionError} When a matching entry exists.
583
+ * @example
584
+ * ```ts
585
+ * chain.toNotHaveEvent("deploy:failed");
586
+ * ```
587
+ */
507
588
  toNotHaveEvent(event, partial) {
508
589
  const offending = entries.findIndex((entry) => entryMatches(entry, event, partial));
509
590
  if (offending !== -1) throw new LogExpectAssertionError(`Expected trace to NOT contain event "${event}"${describePartial(partial)}, but found one at index ${offending}.`);
@@ -512,7 +593,6 @@ function createExpectChain(entries) {
512
593
  };
513
594
  return chain;
514
595
  }
515
-
516
596
  //#endregion
517
597
  //#region src/plugins/log/api.ts
518
598
  /**
@@ -581,33 +661,110 @@ function mergeError(data, error) {
581
661
  function createLogApi(ctx) {
582
662
  const { state } = ctx;
583
663
  return {
664
+ /**
665
+ * Append an `info` entry and fan it out to every sink.
666
+ *
667
+ * @param event - Event identifier (convention: `domain:action`).
668
+ * @param data - Optional structured payload.
669
+ * @example
670
+ * ```ts
671
+ * log.info("content:ready", { count: 12 });
672
+ * ```
673
+ */
584
674
  info(event, data) {
585
675
  append(state, "info", event, data);
586
676
  },
677
+ /**
678
+ * Append a `debug` entry and fan it out to every sink.
679
+ *
680
+ * @param event - Event identifier (convention: `domain:action`).
681
+ * @param data - Optional structured payload.
682
+ * @example
683
+ * ```ts
684
+ * log.debug("router:match", { path: "/blog/" });
685
+ * ```
686
+ */
587
687
  debug(event, data) {
588
688
  append(state, "debug", event, data);
589
689
  },
690
+ /**
691
+ * Append a `warn` entry and fan it out to every sink.
692
+ *
693
+ * @param event - Event identifier (convention: `domain:action`).
694
+ * @param data - Optional structured payload.
695
+ * @example
696
+ * ```ts
697
+ * log.warn("build:skip", { reason: "no sitemap" });
698
+ * ```
699
+ */
590
700
  warn(event, data) {
591
701
  append(state, "warn", event, data);
592
702
  },
703
+ /**
704
+ * Append an `error` entry. When `error` is provided, its `message`/`stack`
705
+ * are merged into `data` under an `error` key (existing keys preserved);
706
+ * otherwise `data` is recorded as-is.
707
+ *
708
+ * @param event - Event identifier (convention: `domain:action`).
709
+ * @param data - Optional structured payload.
710
+ * @param error - Optional originating Error to merge into `data`.
711
+ * @example
712
+ * ```ts
713
+ * log.error("deploy:failed", { target: "cf" }, err);
714
+ * ```
715
+ */
593
716
  error(event, data, error) {
594
717
  append(state, "error", event, error === void 0 ? data : mergeError(data, error));
595
718
  },
719
+ /**
720
+ * Return a frozen snapshot (fresh copy) of the entries recorded so far.
721
+ *
722
+ * @returns A readonly, frozen copy of the recorded entries.
723
+ * @example
724
+ * ```ts
725
+ * const entries = log.trace();
726
+ * ```
727
+ */
596
728
  trace() {
597
729
  return Object.freeze([...state.entries]);
598
730
  },
731
+ /**
732
+ * Return a fluent assertion chain bound to the live entries array.
733
+ *
734
+ * @returns A fresh {@link ExpectChain} reading `state.entries` live.
735
+ * @example
736
+ * ```ts
737
+ * log.expect().toHaveEvent("build:complete");
738
+ * ```
739
+ */
599
740
  expect() {
600
741
  return createExpectChain(state.entries);
601
742
  },
743
+ /**
744
+ * Register an additional output sink at runtime.
745
+ *
746
+ * @param sink - The sink to add to the fan-out list.
747
+ * @example
748
+ * ```ts
749
+ * log.addSink({ write: (e) => stream.write(JSON.stringify(e)) });
750
+ * ```
751
+ */
602
752
  addSink(sink) {
603
753
  state.sinks.push(sink);
604
754
  },
755
+ /**
756
+ * Clear all recorded entries while keeping registered sinks.
757
+ *
758
+ * @example
759
+ * ```ts
760
+ * log.reset();
761
+ * ```
762
+ */
605
763
  reset() {
606
764
  state.entries.length = 0;
607
765
  }
608
766
  };
609
767
  }
610
-
611
768
  //#endregion
612
769
  //#region src/plugins/log/sinks.ts
613
770
  /**
@@ -622,7 +779,17 @@ function createLogApi(ctx) {
622
779
  * ```
623
780
  */
624
781
  function consoleSink() {
625
- return { write(entry) {
782
+ return {
783
+ /**
784
+ * Route a single entry to the console channel matching its level.
785
+ *
786
+ * @param entry - The entry to emit.
787
+ * @example
788
+ * ```ts
789
+ * sink.write({ level: "warn", event: "build:skip", ts: Date.now() });
790
+ * ```
791
+ */
792
+ write(entry) {
626
793
  if (entry.level === "error") console.error(entry);
627
794
  else if (entry.level === "warn") console.warn(entry);
628
795
  else console.log(entry);
@@ -643,7 +810,6 @@ function consoleSink() {
643
810
  function installDefaultSinks(ctx) {
644
811
  if (ctx.config.mode === "dev" || ctx.config.mode === "production") ctx.state.sinks.push(consoleSink());
645
812
  }
646
-
647
813
  //#endregion
648
814
  //#region src/plugins/log/state.ts
649
815
  /**
@@ -664,15 +830,6 @@ function createLogState(_ctx) {
664
830
  sinks: []
665
831
  };
666
832
  }
667
-
668
- //#endregion
669
- //#region src/plugins/log/index.ts
670
- /**
671
- * @file log — Core Plugin (Standard tier): in-memory trace + expect() DSL.
672
- * @see README.md
673
- */
674
- /** Default config; overridden via the 4-level pluginConfigs.log merge. */
675
- const defaultLogConfig = { mode: "production" };
676
833
  /**
677
834
  * Core logging plugin — always-on in-memory trace + `expect()` event-trace DSL.
678
835
  * API injected as `ctx.log` on every regular plugin and surfaced as `app.log`.
@@ -681,21 +838,13 @@ const defaultLogConfig = { mode: "production" };
681
838
  * @see README.md
682
839
  */
683
840
  const logPlugin = (0, _moku_labs_core.createCorePlugin)("log", {
684
- config: defaultLogConfig,
841
+ config: { mode: "production" },
685
842
  createState: createLogState,
686
843
  api: createLogApi,
687
844
  onInit: installDefaultSinks
688
845
  });
689
-
690
- //#endregion
691
- //#region src/config.ts
692
- /**
693
- * @file Framework configuration — Config + Events types, core plugin registration.
694
- * @see README.md
695
- */
696
- const defaultConfig$6 = { mode: "production" };
697
846
  const coreConfig = (0, _moku_labs_core.createCoreConfig)("web", {
698
- config: defaultConfig$6,
847
+ config: { mode: "production" },
699
848
  plugins: [logPlugin, envPlugin],
700
849
  pluginConfigs: {
701
850
  log: { mode: "production" },
@@ -703,7 +852,6 @@ const coreConfig = (0, _moku_labs_core.createCoreConfig)("web", {
703
852
  }
704
853
  });
705
854
  const { createPlugin: createPlugin$1, createCore } = coreConfig;
706
-
707
855
  //#endregion
708
856
  //#region src/plugins/i18n/api.ts
709
857
  /** Error prefix for all i18n lifecycle failures. */
@@ -743,21 +891,83 @@ function validateI18nConfig(ctx) {
743
891
  function createI18nApi(ctx) {
744
892
  const { config } = ctx;
745
893
  return {
894
+ /**
895
+ * Returns the configured supported locales in declared order.
896
+ *
897
+ * @returns The configured `locales` list (priority/display order).
898
+ * @example
899
+ * ```ts
900
+ * api.locales(); // ["en", "uk"]
901
+ * ```
902
+ */
746
903
  locales() {
747
904
  return config.locales;
748
905
  },
906
+ /**
907
+ * Returns the fallback locale used when a requested locale is absent.
908
+ *
909
+ * @returns The configured `defaultLocale`.
910
+ * @example
911
+ * ```ts
912
+ * api.defaultLocale(); // "en"
913
+ * ```
914
+ */
749
915
  defaultLocale() {
750
916
  return config.defaultLocale;
751
917
  },
918
+ /**
919
+ * Membership guard: whether `x` is one of the supported locales
920
+ * (case-sensitive).
921
+ *
922
+ * @param x - Candidate locale code.
923
+ * @returns `true` if `x ∈ locales`, else `false`.
924
+ * @example
925
+ * ```ts
926
+ * api.isLocale("uk"); // true
927
+ * ```
928
+ */
752
929
  isLocale(x) {
753
930
  return config.locales.includes(x);
754
931
  },
932
+ /**
933
+ * Human-readable display name for a locale.
934
+ *
935
+ * @param locale - Locale code to look up.
936
+ * @returns The display name, or `undefined` if unmapped.
937
+ * @example
938
+ * ```ts
939
+ * api.localeName("uk"); // "Українська"
940
+ * ```
941
+ */
755
942
  localeName(locale) {
756
943
  return config.localeNames?.[locale];
757
944
  },
945
+ /**
946
+ * Open Graph `og:locale` value for a locale.
947
+ *
948
+ * @param locale - Locale code to look up.
949
+ * @returns The `og:locale` value (e.g. `"en_US"`), or `undefined` if unmapped.
950
+ * @example
951
+ * ```ts
952
+ * api.ogLocale("en"); // "en_US"
953
+ * ```
954
+ */
758
955
  ogLocale(locale) {
759
956
  return config.ogLocaleMap?.[locale];
760
957
  },
958
+ /**
959
+ * Translate `key` for `locale` with a deterministic fallback chain
960
+ * (requested locale → default locale → the key itself). The default-locale
961
+ * lookup is skipped when `locale === defaultLocale`.
962
+ *
963
+ * @param locale - Requested locale code.
964
+ * @param key - Translation key (e.g. `"nav.home"`).
965
+ * @returns The translated value, the default-locale value, or `key`.
966
+ * @example
967
+ * ```ts
968
+ * api.t("uk", "nav.home"); // "Головна"
969
+ * ```
970
+ */
761
971
  t(locale, key) {
762
972
  const exact = config.translations?.[locale]?.[key];
763
973
  if (exact !== void 0) return exact;
@@ -769,34 +979,17 @@ function createI18nApi(ctx) {
769
979
  }
770
980
  };
771
981
  }
772
-
773
- //#endregion
774
- //#region src/plugins/i18n/index.ts
775
- /**
776
- * i18n — Micro tier. Multi-file layout (index wiring + api.ts + types.ts) so
777
- * index.ts stays within the ≤30-line wiring-only hook; logic lives in api.ts.
778
- *
779
- * Locale registry + flat translation helper with default-locale fallback.
780
- * Pure config-as-data: no state, no events, no lifecycle resources.
781
- * Consumed read-only by content/router/head/build via `ctx.require(i18nPlugin)`.
782
- *
783
- * @file i18n plugin wiring harness.
784
- * @see README.md
785
- */
786
- /** Typed default config (R6: no inline `as`). Optional maps default to `{}` so every lookup is total. */
787
- const defaultConfig$5 = {
788
- locales: ["en"],
789
- defaultLocale: "en",
790
- localeNames: {},
791
- ogLocaleMap: {},
792
- translations: {}
793
- };
794
982
  const i18nPlugin = createPlugin$1("i18n", {
795
- config: defaultConfig$5,
983
+ config: {
984
+ locales: ["en"],
985
+ defaultLocale: "en",
986
+ localeNames: {},
987
+ ogLocaleMap: {},
988
+ translations: {}
989
+ },
796
990
  onInit: validateI18nConfig,
797
991
  api: createI18nApi
798
992
  });
799
-
800
993
  //#endregion
801
994
  //#region src/plugins/content/pipeline/frontmatter.ts
802
995
  /**
@@ -842,7 +1035,6 @@ function parseFrontmatter(raw, config) {
842
1035
  body: parsed.content
843
1036
  };
844
1037
  }
845
-
846
1038
  //#endregion
847
1039
  //#region src/plugins/content/pipeline/plugins.ts
848
1040
  /**
@@ -999,7 +1191,6 @@ function defaultRehypePlugins() {
999
1191
  sectionDividerPlugin
1000
1192
  ];
1001
1193
  }
1002
-
1003
1194
  //#endregion
1004
1195
  //#region src/plugins/content/pipeline/sanitize.ts
1005
1196
  /**
@@ -1086,7 +1277,6 @@ function buildSanitizeSchema() {
1086
1277
  }
1087
1278
  };
1088
1279
  }
1089
-
1090
1280
  //#endregion
1091
1281
  //#region src/plugins/content/pipeline/markdown.ts
1092
1282
  /**
@@ -1144,7 +1334,6 @@ function applyPluggable(processor, plugin) {
1144
1334
  }
1145
1335
  processor.use(plugin);
1146
1336
  }
1147
-
1148
1337
  //#endregion
1149
1338
  //#region src/plugins/content/pipeline/reading-time.ts
1150
1339
  /**
@@ -1170,7 +1359,6 @@ function calculateReadingTime(text) {
1170
1359
  wordCount: stats.words
1171
1360
  };
1172
1361
  }
1173
-
1174
1362
  //#endregion
1175
1363
  //#region src/plugins/content/api.ts
1176
1364
  /**
@@ -1278,7 +1466,7 @@ async function discoverSlugs(dir) {
1278
1466
  * ```
1279
1467
  */
1280
1468
  async function readArticle(ctx, slug, fileLocale, outLocale, isFallback) {
1281
- const filePath = node_path.default.join(ctx.config.contentDir, slug, `${fileLocale}.md`);
1469
+ const filePath = node_path$1.default.join(ctx.config.contentDir, slug, `${fileLocale}.md`);
1282
1470
  let raw;
1283
1471
  try {
1284
1472
  raw = await (0, node_fs_promises.readFile)(filePath, "utf8");
@@ -1382,6 +1570,18 @@ function toCard(article) {
1382
1570
  */
1383
1571
  function createContentApi(ctx) {
1384
1572
  return {
1573
+ /**
1574
+ * Load every article across every active locale, returning a locale-keyed
1575
+ * map of date-descending Article arrays. Lazily builds the processor and
1576
+ * discovers slugs, applies locale fallback, excludes drafts in production,
1577
+ * assigns `contentId` after sorting, then emits `content:ready`.
1578
+ *
1579
+ * @returns A locale-keyed map of date-descending articles.
1580
+ * @example
1581
+ * ```ts
1582
+ * const byLocale = await api.loadAll();
1583
+ * ```
1584
+ */
1385
1585
  async loadAll() {
1386
1586
  const slugs = ctx.state.slugs ?? await discoverSlugs(ctx.config.contentDir);
1387
1587
  ctx.state.slugs = slugs;
@@ -1409,6 +1609,19 @@ function createContentApi(ctx) {
1409
1609
  });
1410
1610
  return result;
1411
1611
  },
1612
+ /**
1613
+ * Resolve and render a single article for one locale with locale fallback.
1614
+ * Throws a `[web] content` error when neither the requested nor the
1615
+ * default-locale file exists.
1616
+ *
1617
+ * @param slug - Article directory name.
1618
+ * @param locale - Requested locale code.
1619
+ * @returns The resolved Article.
1620
+ * @example
1621
+ * ```ts
1622
+ * const article = await api.load("intro", "uk");
1623
+ * ```
1624
+ */
1412
1625
  async load(slug, locale) {
1413
1626
  const article = await resolveArticle(ctx, slug, locale);
1414
1627
  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.`);
@@ -1417,10 +1630,33 @@ function createContentApi(ctx) {
1417
1630
  ctx.state.articles.set(locale, cache);
1418
1631
  return article;
1419
1632
  },
1633
+ /**
1634
+ * Render a raw Markdown string to HTML through the full pipeline (sanitize
1635
+ * last when `trustedContent` is false). Lazily builds the processor.
1636
+ *
1637
+ * @param md - Raw Markdown source.
1638
+ * @returns The rendered HTML string.
1639
+ * @example
1640
+ * ```ts
1641
+ * const html = await api.renderMarkdown("# Hi");
1642
+ * ```
1643
+ */
1420
1644
  async renderMarkdown(md) {
1421
1645
  const processor = ensureProcessor(ctx.state, ctx.config);
1422
1646
  return String(await processor.process(md));
1423
1647
  },
1648
+ /**
1649
+ * Mark file paths stale for incremental dev rebuilds. Each non-blank path is
1650
+ * added to `dirtyPaths` and its derived slug cache entry is dropped so the
1651
+ * next `loadAll()` re-reads only those files. Empty/whitespace paths are
1652
+ * ignored. Emits `content:invalidated` with the accepted paths.
1653
+ *
1654
+ * @param paths - File paths to invalidate.
1655
+ * @example
1656
+ * ```ts
1657
+ * api.invalidate(["src/content/intro/en.md"]);
1658
+ * ```
1659
+ */
1424
1660
  invalidate(paths) {
1425
1661
  const accepted = [];
1426
1662
  for (const path of paths) {
@@ -1433,12 +1669,22 @@ function createContentApi(ctx) {
1433
1669
  ctx.state.slugs = null;
1434
1670
  ctx.emit("content:invalidated", { paths: accepted });
1435
1671
  },
1672
+ /**
1673
+ * Project a full Article to a lightweight ArticleCard for list/grid
1674
+ * rendering without shipping rendered HTML.
1675
+ *
1676
+ * @param article - The source article.
1677
+ * @returns The card projection.
1678
+ * @example
1679
+ * ```ts
1680
+ * const card = api.articleToCard(article);
1681
+ * ```
1682
+ */
1436
1683
  articleToCard(article) {
1437
1684
  return toCard(article);
1438
1685
  }
1439
1686
  };
1440
1687
  }
1441
-
1442
1688
  //#endregion
1443
1689
  //#region src/plugins/content/config.ts
1444
1690
  /**
@@ -1459,7 +1705,6 @@ const defaultContentConfig = {
1459
1705
  extraRehypePlugins: [],
1460
1706
  shikiTheme: "github-dark"
1461
1707
  };
1462
-
1463
1708
  //#endregion
1464
1709
  //#region src/plugins/content/events.ts
1465
1710
  /**
@@ -1478,7 +1723,6 @@ const contentEvents = (register) => ({
1478
1723
  "content:ready": register("All articles loaded across locales"),
1479
1724
  "content:invalidated": register("Article paths marked stale for dev rebuild")
1480
1725
  });
1481
-
1482
1726
  //#endregion
1483
1727
  //#region src/plugins/content/state.ts
1484
1728
  /**
@@ -1502,7 +1746,6 @@ function createContentState(_ctx) {
1502
1746
  dirtyPaths: /* @__PURE__ */ new Set()
1503
1747
  };
1504
1748
  }
1505
-
1506
1749
  //#endregion
1507
1750
  //#region src/plugins/content/validate.ts
1508
1751
  /**
@@ -1521,7 +1764,6 @@ function validateContentConfig(config) {
1521
1764
  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.");
1522
1765
  if (typeof config.trustedContent !== "boolean") throw new TypeError("[web] content.trustedContent must be a boolean.");
1523
1766
  }
1524
-
1525
1767
  //#endregion
1526
1768
  //#region src/plugins/content/index.ts
1527
1769
  /**
@@ -1540,7 +1782,6 @@ const contentPlugin = createPlugin$1("content", {
1540
1782
  onInit: (ctx) => validateContentConfig(ctx.config),
1541
1783
  api: contentApi
1542
1784
  });
1543
-
1544
1785
  //#endregion
1545
1786
  //#region src/plugins/site/api.ts
1546
1787
  /** Error prefix for all site lifecycle/validation failures. */
@@ -1632,50 +1873,80 @@ function validateSiteConfig(ctx) {
1632
1873
  function createSiteApi(ctx) {
1633
1874
  const { config } = ctx;
1634
1875
  return {
1876
+ /**
1877
+ * Returns the configured site name.
1878
+ *
1879
+ * @returns The human-readable site name from `config.name`.
1880
+ * @example
1881
+ * ```ts
1882
+ * api.name(); // "My Blog"
1883
+ * ```
1884
+ */
1635
1885
  name() {
1636
1886
  return config.name;
1637
1887
  },
1888
+ /**
1889
+ * Returns the configured absolute base URL of the site.
1890
+ *
1891
+ * @returns The base URL from `config.url`.
1892
+ * @example
1893
+ * ```ts
1894
+ * api.url(); // "https://blog.dev"
1895
+ * ```
1896
+ */
1638
1897
  url() {
1639
1898
  return config.url;
1640
1899
  },
1900
+ /**
1901
+ * Returns the configured site author/byline.
1902
+ *
1903
+ * @returns The author from `config.author`.
1904
+ * @example
1905
+ * ```ts
1906
+ * api.author(); // "Alex"
1907
+ * ```
1908
+ */
1641
1909
  author() {
1642
1910
  return config.author;
1643
1911
  },
1912
+ /**
1913
+ * Returns the configured site description.
1914
+ *
1915
+ * @returns The description from `config.description`.
1916
+ * @example
1917
+ * ```ts
1918
+ * api.description(); // "A personal blog about web frameworks."
1919
+ * ```
1920
+ */
1644
1921
  description() {
1645
1922
  return config.description;
1646
1923
  },
1924
+ /**
1925
+ * Joins a path against the configured base `url` to produce an absolute
1926
+ * canonical URL. An empty path (or "/") returns the base URL unchanged.
1927
+ *
1928
+ * @param path - Relative path for the page, e.g. "/about/".
1929
+ * @returns The absolute canonical URL.
1930
+ * @example
1931
+ * ```ts
1932
+ * api.canonical("/about/"); // "https://blog.dev/about/"
1933
+ * ```
1934
+ */
1647
1935
  canonical(path) {
1648
1936
  return joinCanonical(config.url, path);
1649
1937
  }
1650
1938
  };
1651
1939
  }
1652
-
1653
- //#endregion
1654
- //#region src/plugins/site/index.ts
1655
- /**
1656
- * site — Micro tier. Multi-file layout (index wiring + api.ts + types.ts) so
1657
- * index.ts stays within the ≤30-line wiring-only hook; logic lives in api.ts.
1658
- *
1659
- * Holds global, frozen site metadata (name, url, author, description) and
1660
- * constructs canonical URLs. Consumed by router/head/build via
1661
- * `ctx.require(sitePlugin)`. No events, no dependencies, no state.
1662
- *
1663
- * @file site plugin wiring harness.
1664
- * @see README.md
1665
- */
1666
- /** Typed default config (R6: no inline `as`). Consumers override via `pluginConfigs.site`. */
1667
- const defaultConfig$4 = {
1668
- name: "",
1669
- url: "",
1670
- author: "",
1671
- description: ""
1672
- };
1673
1940
  const sitePlugin = createPlugin$1("site", {
1674
- config: defaultConfig$4,
1941
+ config: {
1942
+ name: "",
1943
+ url: "",
1944
+ author: "",
1945
+ description: ""
1946
+ },
1675
1947
  onInit: validateSiteConfig,
1676
1948
  api: createSiteApi
1677
1949
  });
1678
-
1679
1950
  //#endregion
1680
1951
  //#region src/plugins/router/builders/match.ts
1681
1952
  /**
@@ -1745,7 +2016,6 @@ function matchRoute(compiled, pathname) {
1745
2016
  }
1746
2017
  return null;
1747
2018
  }
1748
-
1749
2019
  //#endregion
1750
2020
  //#region src/plugins/router/api.ts
1751
2021
  /**
@@ -1808,23 +2078,63 @@ function toTypedRoute(entry) {
1808
2078
  function createApi$4(ctx) {
1809
2079
  const { state } = ctx;
1810
2080
  return {
2081
+ /**
2082
+ * Match a pathname against the compiled route table (specificity-sorted).
2083
+ *
2084
+ * @param pathname - URL pathname, e.g. `/en/hello/`.
2085
+ * @returns `{ params, route }` for the most specific match, or `null`.
2086
+ * @example
2087
+ * ```ts
2088
+ * api.match("/en/hello/");
2089
+ * ```
2090
+ */
1811
2091
  match(pathname) {
1812
2092
  return matchRoute(readTable(state).compiled, pathname);
1813
2093
  },
2094
+ /**
2095
+ * Build a URL for a named route from params.
2096
+ *
2097
+ * @param routeName - Route name key from the route map.
2098
+ * @param params - Param values to substitute into the pattern.
2099
+ * @returns The resolved URL string (e.g. `/en/hello/`).
2100
+ * @throws {Error} If `routeName` is unknown.
2101
+ * @example
2102
+ * ```ts
2103
+ * api.toUrl("article", { lang: "en", slug: "hello" });
2104
+ * ```
2105
+ */
1814
2106
  toUrl(routeName, params) {
1815
2107
  const entry = readTable(state).byName.get(routeName);
1816
2108
  if (!entry) throw new Error(`${ERROR_PREFIX$9}: unknown route name "${routeName}".`);
1817
2109
  return entry.toUrl(params);
1818
2110
  },
2111
+ /**
2112
+ * All resolved routes as typed URL utilities, in specificity order.
2113
+ *
2114
+ * @returns A fresh read-only array of resolved typed routes.
2115
+ * @example
2116
+ * ```ts
2117
+ * for (const r of api.entries()) r.toUrl({ slug: "x" });
2118
+ * ```
2119
+ */
1819
2120
  entries() {
1820
2121
  return readTable(state).compiled.map((entry) => toTypedRoute(entry));
1821
2122
  },
2123
+ /**
2124
+ * The typed route set for build-time consumption (declaration order). An API
2125
+ * return, NOT a config readback — preserves per-route types despite erasure.
2126
+ *
2127
+ * @returns A fresh read-only array of the typed route definitions.
2128
+ * @example
2129
+ * ```ts
2130
+ * for (const def of api.manifest()) def._handlers.load?.({}, "en");
2131
+ * ```
2132
+ */
1822
2133
  manifest() {
1823
2134
  return [...readTable(state).byName.values()].map((entry) => entry.definition);
1824
2135
  }
1825
2136
  };
1826
2137
  }
1827
-
1828
2138
  //#endregion
1829
2139
  //#region src/plugins/router/builders/compile.ts
1830
2140
  /** Shared `[web]` error prefix for router validation failures. */
@@ -1986,9 +2296,31 @@ function compileRoute(name, definition, input) {
1986
2296
  dynamicSegmentCount: countDynamicSegments(pattern),
1987
2297
  matchers,
1988
2298
  matchFn: createMatchFunction(matchers, input.defaultLocale),
2299
+ /**
2300
+ * Build a URL for this route from params.
2301
+ *
2302
+ * @param params - Param values to substitute.
2303
+ * @returns The resolved relative URL.
2304
+ * @example
2305
+ * ```ts
2306
+ * entry.toUrl({ slug: "x" });
2307
+ * ```
2308
+ */
1989
2309
  toUrl(params) {
1990
2310
  return buildUrl(pattern, params, input.baseUrl);
1991
2311
  },
2312
+ /**
2313
+ * Build the output file path for this route from params. Honors a custom
2314
+ * `.toFile()` override (captured in `_handlers.toFile`) when present, falling
2315
+ * back to the pattern-derived `…/index.html` path otherwise.
2316
+ *
2317
+ * @param params - Param values to substitute.
2318
+ * @returns The output file path.
2319
+ * @example
2320
+ * ```ts
2321
+ * entry.toFile({ slug: "x" });
2322
+ * ```
2323
+ */
1992
2324
  toFile(params) {
1993
2325
  return definition._handlers.toFile?.(params) ?? buildFilePath(pattern, params);
1994
2326
  },
@@ -2049,7 +2381,6 @@ function buildRouterTable(config, baseUrl, locales, defaultLocale) {
2049
2381
  defaultLocale
2050
2382
  });
2051
2383
  }
2052
-
2053
2384
  //#endregion
2054
2385
  //#region src/plugins/router/builders/route-builder.ts
2055
2386
  /**
@@ -2095,28 +2426,108 @@ function route(pattern) {
2095
2426
  pattern: carrier.pattern,
2096
2427
  _meta: carrier._meta,
2097
2428
  _handlers: carrier._handlers,
2429
+ /**
2430
+ * Attach a data loader; widens the data generic for downstream handlers.
2431
+ *
2432
+ * @param loader - The loader producing this route's data.
2433
+ * @returns The same builder, with the data generic widened.
2434
+ * @example
2435
+ * ```ts
2436
+ * route("/{slug}/").load(({ slug }) => ({ slug }));
2437
+ * ```
2438
+ */
2098
2439
  load(loader) {
2099
2440
  return set("load", loader);
2100
2441
  },
2442
+ /**
2443
+ * Attach a layout wrapper component.
2444
+ *
2445
+ * @param component - The layout component.
2446
+ * @returns The same builder for chaining.
2447
+ * @example
2448
+ * ```ts
2449
+ * route("/").layout((children) => children);
2450
+ * ```
2451
+ */
2101
2452
  layout(component) {
2102
2453
  return set("layout", component);
2103
2454
  },
2455
+ /**
2456
+ * Attach the page render handler.
2457
+ *
2458
+ * @param handler - The render handler.
2459
+ * @returns The same builder for chaining.
2460
+ * @example
2461
+ * ```ts
2462
+ * route("/").render(() => null);
2463
+ * ```
2464
+ */
2104
2465
  render(handler) {
2105
2466
  return set("render", handler);
2106
2467
  },
2468
+ /**
2469
+ * Attach the head/SEO handler.
2470
+ *
2471
+ * @param handler - The head handler.
2472
+ * @returns The same builder for chaining.
2473
+ * @example
2474
+ * ```ts
2475
+ * route("/").head(() => ({ title: "Home" }));
2476
+ * ```
2477
+ */
2107
2478
  head(handler) {
2108
2479
  return set("head", handler);
2109
2480
  },
2481
+ /**
2482
+ * Attach a static-generation param producer.
2483
+ *
2484
+ * @param handler - The param producer.
2485
+ * @returns The same builder for chaining.
2486
+ * @example
2487
+ * ```ts
2488
+ * route("/{slug}/").generate(() => [{ slug: "x" }]);
2489
+ * ```
2490
+ */
2110
2491
  generate(handler) {
2111
2492
  return set("generate", handler);
2112
2493
  },
2494
+ /**
2495
+ * Merge an arbitrary metadata bag into the route's `_meta`.
2496
+ *
2497
+ * @param meta - Metadata to merge.
2498
+ * @returns The same builder for chaining.
2499
+ * @example
2500
+ * ```ts
2501
+ * route("/").meta({ activeTab: "home" });
2502
+ * ```
2503
+ */
2113
2504
  meta(meta) {
2114
2505
  Object.assign(carrier._meta, meta);
2115
2506
  return builder;
2116
2507
  },
2508
+ /**
2509
+ * Attach a JSON serializer for the route's data.
2510
+ *
2511
+ * @param handler - The JSON serializer.
2512
+ * @returns The same builder for chaining.
2513
+ * @example
2514
+ * ```ts
2515
+ * route("/api/").toJson(() => ({ ok: true }));
2516
+ * ```
2517
+ */
2117
2518
  toJson(handler) {
2118
2519
  return set("toJson", handler);
2119
2520
  },
2521
+ /**
2522
+ * Override the output file-path producer.
2523
+ *
2524
+ * @param handler - The file-path producer.
2525
+ * @returns The same builder for chaining.
2526
+ * @example
2527
+ * ```ts
2528
+ * route("/feed/").toFile(() => "feed.xml");
2529
+ * ```
2530
+ */
2120
2531
  toFile(handler) {
2121
2532
  return set("toFile", handler);
2122
2533
  }
@@ -2137,7 +2548,6 @@ function route(pattern) {
2137
2548
  function defineRoutes(routes) {
2138
2549
  return routes;
2139
2550
  }
2140
-
2141
2551
  //#endregion
2142
2552
  //#region src/plugins/router/state.ts
2143
2553
  /**
@@ -2156,25 +2566,16 @@ function defineRoutes(routes) {
2156
2566
  function createState$4(_ctx) {
2157
2567
  return { table: null };
2158
2568
  }
2159
-
2160
- //#endregion
2161
- //#region src/plugins/router/index.ts
2162
- /**
2163
- * @file router — Complex plugin wiring (logic in builders/, api.ts, state.ts).
2164
- * @see README.md
2165
- */
2166
- /** Default router config: empty route map (validated in onInit), hybrid mode. */
2167
- const defaultConfig$3 = {
2168
- routes: {},
2169
- mode: "hybrid"
2170
- };
2171
2569
  const routerPlugin = createPlugin$1("router", {
2172
2570
  depends: [sitePlugin, i18nPlugin],
2173
2571
  helpers: {
2174
2572
  route,
2175
2573
  defineRoutes
2176
2574
  },
2177
- config: defaultConfig$3,
2575
+ config: {
2576
+ routes: {},
2577
+ mode: "hybrid"
2578
+ },
2178
2579
  createState: createState$4,
2179
2580
  api: createApi$4,
2180
2581
  onInit(ctx) {
@@ -2183,7 +2584,6 @@ const routerPlugin = createPlugin$1("router", {
2183
2584
  ctx.state.table = buildRouterTable(ctx.config, baseUrl, i18n.locales(), i18n.defaultLocale());
2184
2585
  }
2185
2586
  });
2186
-
2187
2587
  //#endregion
2188
2588
  //#region src/plugins/head/primitives.ts
2189
2589
  /** OG/Twitter article-meta property prefixes (factored to satisfy no-duplicate-string). */
@@ -2347,7 +2747,6 @@ function buildArticleHead(articleMeta, canonicalUrl) {
2347
2747
  elements.push(jsonLd(ld));
2348
2748
  return elements;
2349
2749
  }
2350
-
2351
2750
  //#endregion
2352
2751
  //#region src/plugins/head/compose.ts
2353
2752
  /**
@@ -2508,7 +2907,6 @@ function serializeElement(element) {
2508
2907
  function serializeHead(elements) {
2509
2908
  return elements.map((element) => serializeElement(element)).join("");
2510
2909
  }
2511
-
2512
2910
  //#endregion
2513
2911
  //#region src/plugins/head/api.ts
2514
2912
  /**
@@ -2550,7 +2948,19 @@ function readDefaults(state) {
2550
2948
  * ```
2551
2949
  */
2552
2950
  function createApi$3(ctx) {
2553
- return { render(route, data) {
2951
+ return {
2952
+ /**
2953
+ * Compose the final `<head>` inner HTML for a route (pulled by `build`).
2954
+ *
2955
+ * @param route - The resolved route descriptor (incl. its `.head()` HeadConfig).
2956
+ * @param data - The page data object passed to the route's loader/render.
2957
+ * @returns The serialized inner HTML of `<head>`.
2958
+ * @example
2959
+ * ```ts
2960
+ * api.render(route, { title: "Post" });
2961
+ * ```
2962
+ */
2963
+ render(route, data) {
2554
2964
  return serializeHead(composeHead({
2555
2965
  route,
2556
2966
  data,
@@ -2561,7 +2971,6 @@ function createApi$3(ctx) {
2561
2971
  }));
2562
2972
  } };
2563
2973
  }
2564
-
2565
2974
  //#endregion
2566
2975
  //#region src/plugins/head/config.ts
2567
2976
  /** Error prefix for all head config-validation failures. */
@@ -2616,7 +3025,6 @@ function normalizeHeadConfig(config) {
2616
3025
  if (config.twitterHandle !== void 0) defaults.twitterHandle = config.twitterHandle;
2617
3026
  return Object.freeze(defaults);
2618
3027
  }
2619
-
2620
3028
  //#endregion
2621
3029
  //#region src/plugins/head/helpers.ts
2622
3030
  /**
@@ -2645,7 +3053,6 @@ const headHelpers = {
2645
3053
  feedLink,
2646
3054
  buildArticleHead
2647
3055
  };
2648
-
2649
3056
  //#endregion
2650
3057
  //#region src/plugins/head/state.ts
2651
3058
  /**
@@ -2666,7 +3073,6 @@ const headHelpers = {
2666
3073
  function createState$3(_ctx) {
2667
3074
  return { defaults: null };
2668
3075
  }
2669
-
2670
3076
  //#endregion
2671
3077
  //#region src/plugins/head/index.ts
2672
3078
  /**
@@ -2687,7 +3093,6 @@ const headPlugin = createPlugin$1("head", {
2687
3093
  ctx.state.defaults = normalizeHeadConfig(ctx.config);
2688
3094
  }
2689
3095
  });
2690
-
2691
3096
  //#endregion
2692
3097
  //#region src/plugins/build/phases/bundle.ts
2693
3098
  /**
@@ -2761,7 +3166,7 @@ async function runOne(ctx, runner, kind, entrypoints, outdir, minify) {
2761
3166
  });
2762
3167
  if (!result.success) throw new Error(`[web] build.bundle ${kind} build failed`);
2763
3168
  const hashed = {};
2764
- for (const output of result.outputs) hashed[node_path.default.basename(output.path)] = output.path;
3169
+ for (const output of result.outputs) hashed[node_path$1.default.basename(output.path)] = output.path;
2765
3170
  ctx.state.buildCache.set(kind, hashed);
2766
3171
  ctx.log.debug("build:bundle", {
2767
3172
  kind,
@@ -2786,10 +3191,9 @@ async function bundle(ctx, options = {}) {
2786
3191
  const { minify, outDir } = ctx.config;
2787
3192
  const cssEntrypoints = options.cssEntrypoints ?? resolveEntrypoints(CSS_ENTRY_CANDIDATES);
2788
3193
  const jsEntrypoints = options.jsEntrypoints ?? resolveEntrypoints(JS_ENTRY_CANDIDATES);
2789
- await runOne(ctx, runner, "css", cssEntrypoints, node_path.default.join(outDir, "assets"), minify);
2790
- await runOne(ctx, runner, "js", jsEntrypoints, node_path.default.join(outDir, "assets"), minify);
3194
+ await runOne(ctx, runner, "css", cssEntrypoints, node_path$1.default.join(outDir, "assets"), minify);
3195
+ await runOne(ctx, runner, "js", jsEntrypoints, node_path$1.default.join(outDir, "assets"), minify);
2791
3196
  }
2792
-
2793
3197
  //#endregion
2794
3198
  //#region src/plugins/build/phases/content.ts
2795
3199
  /**
@@ -2832,7 +3236,6 @@ function readCachedContent(ctx) {
2832
3236
  const cached = ctx.state.buildCache.get(CONTENT_CACHE_KEY);
2833
3237
  return cached instanceof Map ? cached : /* @__PURE__ */ new Map();
2834
3238
  }
2835
-
2836
3239
  //#endregion
2837
3240
  //#region src/plugins/build/phases/feeds.ts
2838
3241
  /**
@@ -2907,14 +3310,13 @@ async function generateFeeds(ctx) {
2907
3310
  };
2908
3311
  await (0, node_fs_promises.mkdir)(ctx.config.outDir, { recursive: true });
2909
3312
  await Promise.all([
2910
- (0, node_fs_promises.writeFile)(node_path.default.join(ctx.config.outDir, "feed.xml"), result.rss, "utf8"),
2911
- (0, node_fs_promises.writeFile)(node_path.default.join(ctx.config.outDir, "atom.xml"), result.atom, "utf8"),
2912
- (0, node_fs_promises.writeFile)(node_path.default.join(ctx.config.outDir, "feed.json"), result.json, "utf8")
3313
+ (0, node_fs_promises.writeFile)(node_path$1.default.join(ctx.config.outDir, "feed.xml"), result.rss, "utf8"),
3314
+ (0, node_fs_promises.writeFile)(node_path$1.default.join(ctx.config.outDir, "atom.xml"), result.atom, "utf8"),
3315
+ (0, node_fs_promises.writeFile)(node_path$1.default.join(ctx.config.outDir, "feed.json"), result.json, "utf8")
2913
3316
  ]);
2914
3317
  ctx.log.debug("build:feeds", { items: guids.length });
2915
3318
  return result;
2916
3319
  }
2917
-
2918
3320
  //#endregion
2919
3321
  //#region src/plugins/build/phases/images.ts
2920
3322
  /**
@@ -2942,7 +3344,7 @@ async function processImages(ctx, options = {}) {
2942
3344
  return 0;
2943
3345
  }
2944
3346
  const sourceDirectories = options.sourceDirectories ?? IMAGE_SOURCE_DIRECTORIES;
2945
- const target = node_path.default.join(ctx.config.outDir, "assets");
3347
+ const target = node_path$1.default.join(ctx.config.outDir, "assets");
2946
3348
  let copied = 0;
2947
3349
  for (const directory of sourceDirectories) {
2948
3350
  if (!(0, node_fs.existsSync)(directory)) continue;
@@ -2954,7 +3356,6 @@ async function processImages(ctx, options = {}) {
2954
3356
  ctx.log.debug("build:images", { copied });
2955
3357
  return copied;
2956
3358
  }
2957
-
2958
3359
  //#endregion
2959
3360
  //#region src/plugins/build/phases/og-images.tsx
2960
3361
  /**
@@ -2968,8 +3369,6 @@ const DEFAULT_SIZE = {
2968
3369
  width: 1200,
2969
3370
  height: 630
2970
3371
  };
2971
- /** The fixed concurrency bound for the OG render pool. */
2972
- const OG_CONCURRENCY = 4;
2973
3372
  /** Recognized font file extensions. */
2974
3373
  const FONT_EXTENSIONS$1 = [
2975
3374
  ".ttf",
@@ -3090,7 +3489,7 @@ async function generateOgImages(ctx, options = {}) {
3090
3489
  const articles = selectArticles(readCachedContent(ctx));
3091
3490
  const cache = ctx.state.ogImageHashCache;
3092
3491
  await loadDiskCache(ctx.config.outDir, cache);
3093
- const limit = pLimit(OG_CONCURRENCY);
3492
+ const limit = pLimit(4);
3094
3493
  let active = 0;
3095
3494
  let peakConcurrency = 0;
3096
3495
  let rendered = 0;
@@ -3163,7 +3562,6 @@ async function persistDiskCache(outDir, cache) {
3163
3562
  await (0, node_fs_promises.mkdir)(dir, { recursive: true });
3164
3563
  await (0, node_fs_promises.writeFile)(node_path.default.join(dir, "og-images.json"), JSON.stringify(Object.fromEntries(cache)), "utf8");
3165
3564
  }
3166
-
3167
3565
  //#endregion
3168
3566
  //#region src/plugins/build/phases/pages.tsx
3169
3567
  /**
@@ -3331,7 +3729,6 @@ async function renderPages(ctx) {
3331
3729
  rootHtml: root?.html ?? null
3332
3730
  };
3333
3731
  }
3334
-
3335
3732
  //#endregion
3336
3733
  //#region src/plugins/build/phases/sitemap.ts
3337
3734
  /**
@@ -3405,7 +3802,7 @@ async function generateSitemap(ctx) {
3405
3802
  const xml = serializeSitemap(urls);
3406
3803
  const robots = `User-agent: *\nAllow: /\nSitemap: ${site.canonical("/sitemap.xml")}\n`;
3407
3804
  await (0, node_fs_promises.mkdir)(ctx.config.outDir, { recursive: true });
3408
- await Promise.all([(0, node_fs_promises.writeFile)(node_path.default.join(ctx.config.outDir, "sitemap.xml"), xml, "utf8"), (0, node_fs_promises.writeFile)(node_path.default.join(ctx.config.outDir, "robots.txt"), robots, "utf8")]);
3805
+ await Promise.all([(0, node_fs_promises.writeFile)(node_path$1.default.join(ctx.config.outDir, "sitemap.xml"), xml, "utf8"), (0, node_fs_promises.writeFile)(node_path$1.default.join(ctx.config.outDir, "robots.txt"), robots, "utf8")]);
3409
3806
  ctx.log.debug("build:sitemap", { urls: urls.length });
3410
3807
  return {
3411
3808
  urls,
@@ -3413,7 +3810,6 @@ async function generateSitemap(ctx) {
3413
3810
  robots
3414
3811
  };
3415
3812
  }
3416
-
3417
3813
  //#endregion
3418
3814
  //#region src/plugins/build/pipeline.ts
3419
3815
  /**
@@ -3534,7 +3930,7 @@ async function runPipeline(ctx, options) {
3534
3930
  const pages = await withPhase(phaseContext, "pages", () => renderPages(phaseContext));
3535
3931
  await runOutputs(phaseContext);
3536
3932
  await withPhase(phaseContext, "root-index", async () => {
3537
- if (pages.rootHtml !== null) await (0, node_fs_promises.writeFile)(node_path.default.join(outDir, "index.html"), pages.rootHtml, "utf8");
3933
+ if (pages.rootHtml !== null) await (0, node_fs_promises.writeFile)(node_path$1.default.join(outDir, "index.html"), pages.rootHtml, "utf8");
3538
3934
  });
3539
3935
  const result = {
3540
3936
  outDir,
@@ -3544,7 +3940,6 @@ async function runPipeline(ctx, options) {
3544
3940
  phaseContext.emit("build:complete", result);
3545
3941
  return result;
3546
3942
  }
3547
-
3548
3943
  //#endregion
3549
3944
  //#region src/plugins/build/api.ts
3550
3945
  /**
@@ -3583,9 +3978,29 @@ const defaultConfig$1 = {
3583
3978
  */
3584
3979
  function createApi$2(ctx) {
3585
3980
  return {
3981
+ /**
3982
+ * Run the full SSG pipeline and write the site to disk.
3983
+ *
3984
+ * @param options - Optional run overrides.
3985
+ * @param options.outDir - Override the configured output directory for this run.
3986
+ * @returns The build result (outDir, pageCount, durationMs).
3987
+ * @example
3988
+ * ```ts
3989
+ * await api.run({ outDir: "./preview" });
3990
+ * ```
3991
+ */
3586
3992
  run(options) {
3587
3993
  return runPipeline(ctx, options);
3588
3994
  },
3995
+ /**
3996
+ * List the phases in execution order (introspection / tooling).
3997
+ *
3998
+ * @returns A fresh array of the static ordered phase names.
3999
+ * @example
4000
+ * ```ts
4001
+ * api.phases();
4002
+ * ```
4003
+ */
3589
4004
  phases() {
3590
4005
  return [...PHASE_ORDER];
3591
4006
  }
@@ -3620,7 +4035,6 @@ function validateConfig$1(config) {
3620
4035
  if (typeof config.outDir !== "string" || config.outDir.trim().length === 0) throw new Error(`${ERROR_PREFIX$5}.outDir: must be a non-empty string.`);
3621
4036
  if (config.ogImage) validateFonts(config.ogImage);
3622
4037
  }
3623
-
3624
4038
  //#endregion
3625
4039
  //#region src/plugins/build/events.ts
3626
4040
  /**
@@ -3640,7 +4054,6 @@ function createEvents(register) {
3640
4054
  "build:complete": register("Emitted once after a successful build run")
3641
4055
  };
3642
4056
  }
3643
-
3644
4057
  //#endregion
3645
4058
  //#region src/plugins/build/state.ts
3646
4059
  /**
@@ -3667,7 +4080,6 @@ function createState$2(ctx) {
3667
4080
  ogImageHashCache: /* @__PURE__ */ new Map()
3668
4081
  };
3669
4082
  }
3670
-
3671
4083
  //#endregion
3672
4084
  //#region src/plugins/build/index.ts
3673
4085
  /**
@@ -3688,7 +4100,6 @@ const buildPlugin = createPlugin$1("build", {
3688
4100
  api: createApi$2,
3689
4101
  onInit: (ctx) => validateConfig$1(ctx.config)
3690
4102
  });
3691
-
3692
4103
  //#endregion
3693
4104
  //#region src/plugins/deploy/wrangler.ts
3694
4105
  /**
@@ -3795,9 +4206,9 @@ function guardBranch(branch) {
3795
4206
  * assertWithinRoot("dist", process.cwd());
3796
4207
  */
3797
4208
  function assertWithinRoot(outDir, root) {
3798
- const resolved = node_path.default.isAbsolute(outDir) ? node_path.default.resolve(outDir) : node_path.default.resolve(root, outDir);
3799
- const rootResolved = node_path.default.resolve(root);
3800
- if (resolved !== rootResolved && !resolved.startsWith(rootResolved + node_path.default.sep)) throw deployError("ERR_DEPLOY_PATH_TRAVERSAL", `${ERROR_PREFIX$4}: outDir ${JSON.stringify(outDir)} resolves outside the project root.\n Point outDir at a directory inside ${JSON.stringify(rootResolved)}.`);
4209
+ const resolved = node_path$1.default.isAbsolute(outDir) ? node_path$1.default.resolve(outDir) : node_path$1.default.resolve(root, outDir);
4210
+ const rootResolved = node_path$1.default.resolve(root);
4211
+ if (resolved !== rootResolved && !resolved.startsWith(rootResolved + node_path$1.default.sep)) throw deployError("ERR_DEPLOY_PATH_TRAVERSAL", `${ERROR_PREFIX$4}: outDir ${JSON.stringify(outDir)} resolves outside the project root.\n Point outDir at a directory inside ${JSON.stringify(rootResolved)}.`);
3801
4212
  return resolved;
3802
4213
  }
3803
4214
  /**
@@ -3866,8 +4277,6 @@ const ERROR_SIGNATURES = [
3866
4277
  advice: "A network failure occurred. Check connectivity and retry."
3867
4278
  }
3868
4279
  ];
3869
- /** Number of trailing characters of scrubbed stderr to surface on an unknown failure. */
3870
- const STDERR_TAIL_LENGTH = 500;
3871
4280
  /**
3872
4281
  * Map a non-zero wrangler exit and scrubbed stderr to an actionable error
3873
4282
  * `code` + message. Matching is case-insensitive against the scrubbed stderr;
@@ -3888,7 +4297,7 @@ function classifyWranglerError(exitCode, scrubbedStderr) {
3888
4297
  };
3889
4298
  return {
3890
4299
  code: "ERR_DEPLOY_WRANGLER_FAILED",
3891
- message: `${ERROR_PREFIX$4}: wrangler failed (exit ${exitCode}).\n ${scrubbedStderr.trim().slice(-STDERR_TAIL_LENGTH)}`
4300
+ message: `${ERROR_PREFIX$4}: wrangler failed (exit ${exitCode}).\n ${scrubbedStderr.trim().slice(-500)}`
3892
4301
  };
3893
4302
  }
3894
4303
  /**
@@ -3947,7 +4356,6 @@ async function runWrangler(input) {
3947
4356
  exitCode
3948
4357
  };
3949
4358
  }
3950
-
3951
4359
  //#endregion
3952
4360
  //#region src/plugins/deploy/generators/github-workflow.ts
3953
4361
  /**
@@ -4002,7 +4410,6 @@ jobs:
4002
4410
  command: pages deploy dist --project-name ${input.slug}
4003
4411
  `;
4004
4412
  }
4005
-
4006
4413
  //#endregion
4007
4414
  //#region src/plugins/deploy/generators/wrangler-config.ts
4008
4415
  /**
@@ -4041,12 +4448,11 @@ function generateWranglerConfig(input) {
4041
4448
  */
4042
4449
  async function readWranglerConfig(cwd) {
4043
4450
  try {
4044
- return await (0, node_fs_promises.readFile)(node_path.default.join(cwd, "wrangler.jsonc"), "utf8");
4451
+ return await (0, node_fs_promises.readFile)(node_path$1.default.join(cwd, "wrangler.jsonc"), "utf8");
4045
4452
  } catch {
4046
4453
  return null;
4047
4454
  }
4048
4455
  }
4049
-
4050
4456
  //#endregion
4051
4457
  //#region src/plugins/deploy/init.ts
4052
4458
  /**
@@ -4068,7 +4474,7 @@ const WORKFLOW_PATH = ".github/workflows/deploy.yml";
4068
4474
  */
4069
4475
  async function readMaybe(cwd, relativePath) {
4070
4476
  try {
4071
- return await (0, node_fs_promises.readFile)(node_path.default.join(cwd, relativePath), "utf8");
4477
+ return await (0, node_fs_promises.readFile)(node_path$1.default.join(cwd, relativePath), "utf8");
4072
4478
  } catch {
4073
4479
  return null;
4074
4480
  }
@@ -4146,11 +4552,10 @@ async function reconcile(input) {
4146
4552
  result.skipped.push(relativePath);
4147
4553
  return;
4148
4554
  }
4149
- await (0, node_fs_promises.mkdir)(node_path.default.dirname(node_path.default.join(cwd, relativePath)), { recursive: true });
4150
- await (0, node_fs_promises.writeFile)(node_path.default.join(cwd, relativePath), expected, "utf8");
4555
+ await (0, node_fs_promises.mkdir)(node_path$1.default.dirname(node_path$1.default.join(cwd, relativePath)), { recursive: true });
4556
+ await (0, node_fs_promises.writeFile)(node_path$1.default.join(cwd, relativePath), expected, "utf8");
4151
4557
  result.written.push(relativePath);
4152
4558
  }
4153
-
4154
4559
  //#endregion
4155
4560
  //#region src/plugins/deploy/preflight.ts
4156
4561
  /**
@@ -4202,7 +4607,7 @@ async function inspectOutdir(dir) {
4202
4607
  if (current === void 0) break;
4203
4608
  const entries = await (0, node_fs_promises.readdir)(current, { withFileTypes: true });
4204
4609
  for (const entry of entries) {
4205
- const entryPath = node_path.default.join(current, entry.name);
4610
+ const entryPath = node_path$1.default.join(current, entry.name);
4206
4611
  if (entry.isDirectory()) stack.push(entryPath);
4207
4612
  else if (entry.isFile()) {
4208
4613
  result.fileCount += 1;
@@ -4230,13 +4635,13 @@ async function inspectOutdir(dir) {
4230
4635
  * await runPreflight(config, process.cwd());
4231
4636
  */
4232
4637
  async function runPreflight(config, root, env = process.env) {
4233
- const wranglerPath = node_path.default.join(root, "wrangler.jsonc");
4638
+ const wranglerPath = node_path$1.default.join(root, "wrangler.jsonc");
4234
4639
  try {
4235
4640
  await (0, node_fs_promises.stat)(wranglerPath);
4236
4641
  } catch {
4237
4642
  throw deployError("ERR_DEPLOY_NO_WRANGLER_CONFIG", `${ERROR_PREFIX$3}: wrangler.jsonc not found.\n Run \`app.deploy.init()\` to scaffold it, then retry.`);
4238
4643
  }
4239
- const stats = await inspectOutdir(node_path.default.isAbsolute(config.outDir) ? node_path.default.resolve(config.outDir) : node_path.default.resolve(root, config.outDir)).catch(() => {
4644
+ const stats = await inspectOutdir(node_path$1.default.isAbsolute(config.outDir) ? node_path$1.default.resolve(config.outDir) : node_path$1.default.resolve(root, config.outDir)).catch(() => {
4240
4645
  throw deployError("ERR_DEPLOY_EMPTY_OUTDIR", `${ERROR_PREFIX$3}: outDir ${JSON.stringify(config.outDir)} is missing.\n Run your build first, then retry.`);
4241
4646
  });
4242
4647
  if (stats.fileCount === 0) throw deployError("ERR_DEPLOY_EMPTY_OUTDIR", `${ERROR_PREFIX$3}: outDir ${JSON.stringify(config.outDir)} is empty — nothing to deploy.`);
@@ -4244,7 +4649,6 @@ async function runPreflight(config, root, env = process.env) {
4244
4649
  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.`);
4245
4650
  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.`);
4246
4651
  }
4247
-
4248
4652
  //#endregion
4249
4653
  //#region src/plugins/deploy/slug.ts
4250
4654
  /**
@@ -4285,7 +4689,6 @@ function toSlug(name) {
4285
4689
  slug = slug.slice(0, end);
4286
4690
  return slug.length > 0 ? slug : FALLBACK_SLUG;
4287
4691
  }
4288
-
4289
4692
  //#endregion
4290
4693
  //#region src/plugins/deploy/api.ts
4291
4694
  /** Error prefix for deploy config/validation failures (spec/11 Part-3). */
@@ -4325,6 +4728,15 @@ function validateConfig(ctx) {
4325
4728
  */
4326
4729
  function createApi$1(ctx) {
4327
4730
  return {
4731
+ /**
4732
+ * Deploy the built outDir to Cloudflare Pages via the wrangler subprocess.
4733
+ *
4734
+ * @param options - Optional branch override and build toggle.
4735
+ * @returns The deploy result (url, deploymentId, branch, durationMs).
4736
+ * @throws {Error} With a `code` from the deploy error taxonomy on any failure.
4737
+ * @example
4738
+ * await api.run();
4739
+ */
4328
4740
  async run(options = {}) {
4329
4741
  const root = process.cwd();
4330
4742
  const slug = toSlug(ctx.require(sitePlugin).name());
@@ -4364,10 +4776,25 @@ function createApi$1(ctx) {
4364
4776
  });
4365
4777
  return result;
4366
4778
  },
4779
+ /**
4780
+ * Return the most recent successful deploy result, or null if none occurred.
4781
+ *
4782
+ * @returns A frozen snapshot of the last DeployResult, or null.
4783
+ * @example
4784
+ * const last = api.getLastDeployment();
4785
+ */
4367
4786
  getLastDeployment() {
4368
4787
  const last = ctx.state.lastDeployment;
4369
4788
  return last ? Object.freeze({ ...last }) : null;
4370
4789
  },
4790
+ /**
4791
+ * Generate deploy scaffolding (wrangler.jsonc + optional GitHub workflow).
4792
+ *
4793
+ * @param options - Optional ci toggle and check (drift-only) mode.
4794
+ * @returns Which files were written, skipped, or would drift.
4795
+ * @example
4796
+ * await api.init({ ci: true });
4797
+ */
4371
4798
  async init(options = {}) {
4372
4799
  const slug = toSlug(ctx.require(sitePlugin).name());
4373
4800
  return writeScaffolding({
@@ -4379,7 +4806,6 @@ function createApi$1(ctx) {
4379
4806
  }
4380
4807
  };
4381
4808
  }
4382
-
4383
4809
  //#endregion
4384
4810
  //#region src/plugins/deploy/defaults.ts
4385
4811
  /**
@@ -4399,7 +4825,6 @@ const defaultConfig = {
4399
4825
  compatibilityDate: "2024-01-01",
4400
4826
  ci: false
4401
4827
  };
4402
-
4403
4828
  //#endregion
4404
4829
  //#region src/plugins/deploy/events.ts
4405
4830
  /**
@@ -4414,7 +4839,6 @@ const defaultConfig = {
4414
4839
  * ```
4415
4840
  */
4416
4841
  const deployEvents = (register) => ({ "deploy:complete": register("Deployment completed successfully") });
4417
-
4418
4842
  //#endregion
4419
4843
  //#region src/plugins/deploy/state.ts
4420
4844
  /**
@@ -4452,7 +4876,6 @@ function createState$1(_ctx) {
4452
4876
  spawn: defaultSpawn
4453
4877
  };
4454
4878
  }
4455
-
4456
4879
  //#endregion
4457
4880
  //#region src/plugins/deploy/index.ts
4458
4881
  /**
@@ -4470,7 +4893,6 @@ const deployPlugin = createPlugin$1("deploy", {
4470
4893
  onInit: validateConfig,
4471
4894
  api: createApi$1
4472
4895
  });
4473
-
4474
4896
  //#endregion
4475
4897
  //#region src/plugins/spa/api.ts
4476
4898
  /**
@@ -4485,19 +4907,39 @@ const deployPlugin = createPlugin$1("deploy", {
4485
4907
  */
4486
4908
  function createApi(ctx) {
4487
4909
  return {
4910
+ /**
4911
+ * Register a component definition (last-registered-wins); warns on collision.
4912
+ *
4913
+ * @param component - The component definition created via `createComponent`.
4914
+ * @example
4915
+ * app.spa.register(counter);
4916
+ */
4488
4917
  register(component) {
4489
4918
  if (ctx.state.registeredComponents.has(component.name)) ctx.log.warn("spa:component-collision", { name: component.name });
4490
4919
  ctx.state.kernel?.register(component);
4491
4920
  },
4921
+ /**
4922
+ * Programmatically navigate to a path (client runtime; no-op without a DOM).
4923
+ *
4924
+ * @param path - Target path (pathname, optionally with search/hash).
4925
+ * @example
4926
+ * app.spa.navigate("/about");
4927
+ */
4492
4928
  navigate(path) {
4493
4929
  ctx.state.kernel?.processNav(path);
4494
4930
  },
4931
+ /**
4932
+ * Read the current resolved URL.
4933
+ *
4934
+ * @returns The current pathname + search.
4935
+ * @example
4936
+ * app.spa.current();
4937
+ */
4495
4938
  current() {
4496
4939
  return ctx.state.currentUrl;
4497
4940
  }
4498
4941
  };
4499
4942
  }
4500
-
4501
4943
  //#endregion
4502
4944
  //#region src/plugins/spa/events.ts
4503
4945
  /**
@@ -4517,7 +4959,6 @@ function spaEvents(register) {
4517
4959
  "spa:component-unmount": register("A component instance detached from an element.")
4518
4960
  };
4519
4961
  }
4520
-
4521
4962
  //#endregion
4522
4963
  //#region src/plugins/spa/types.ts
4523
4964
  var types_exports$7 = /* @__PURE__ */ __exportAll({ COMPONENT_HOOK_NAMES: () => COMPONENT_HOOK_NAMES });
@@ -4530,11 +4971,7 @@ const COMPONENT_HOOK_NAMES = [
4530
4971
  "onUnMount",
4531
4972
  "onDestroy"
4532
4973
  ];
4533
-
4534
- //#endregion
4535
- //#region src/plugins/spa/components.ts
4536
- /** The set of legal hook names, frozen for O(1) membership checks. */
4537
- const HOOK_NAME_SET = new Set(COMPONENT_HOOK_NAMES);
4974
+ new Set(COMPONENT_HOOK_NAMES);
4538
4975
  /**
4539
4976
  * Extracts the page data payload from the inline `script#__DATA__` element.
4540
4977
  * Returns an empty object when the script is absent, empty, or invalid JSON.
@@ -4702,7 +5139,6 @@ function notifyNavEnd(state) {
4702
5139
  const data = typeof document === "undefined" ? {} : extractPageData(document);
4703
5140
  for (const [element, instance] of state.instances) if (instance.persistent) runHook(instance, "onNavEnd", makeContext(element, data));
4704
5141
  }
4705
-
4706
5142
  //#endregion
4707
5143
  //#region src/plugins/spa/head.ts
4708
5144
  /** Single-element head selectors synced by replace/append/remove on navigation. */
@@ -4777,7 +5213,6 @@ function syncHead(_head, doc) {
4777
5213
  for (const selector of META_SELECTORS) syncElement(selector, doc);
4778
5214
  for (const selector of REPLACE_ALL_SELECTORS) replaceAllBySelector(selector, doc);
4779
5215
  }
4780
-
4781
5216
  //#endregion
4782
5217
  //#region src/plugins/spa/progress.ts
4783
5218
  /** Delay before the bar appears, so fast navigations show no indicator. */
@@ -4861,7 +5296,6 @@ function createProgressBar(enabled) {
4861
5296
  done
4862
5297
  };
4863
5298
  }
4864
-
4865
5299
  //#endregion
4866
5300
  //#region src/plugins/spa/router.ts
4867
5301
  /**
@@ -5096,7 +5530,6 @@ function attachRouter(handlers) {
5096
5530
  const navigation = getNavigation();
5097
5531
  return navigation ? attachNavigationApi(navigation, handlers) : attachHistoryFallback(handlers);
5098
5532
  }
5099
-
5100
5533
  //#endregion
5101
5534
  //#region src/plugins/spa/state.ts
5102
5535
  /** Error prefix for spa config-validation failures (spec/11 Part-3). */
@@ -5170,7 +5603,6 @@ function createState(_ctx) {
5170
5603
  kernel: null
5171
5604
  };
5172
5605
  }
5173
-
5174
5606
  //#endregion
5175
5607
  //#region src/plugins/spa/kernel.ts
5176
5608
  /**
@@ -5279,10 +5711,22 @@ function createSpaKernel(state, config, emit, deps) {
5279
5711
  onError: handleError
5280
5712
  };
5281
5713
  return {
5714
+ /**
5715
+ * Register config components and seed currentUrl from the document.
5716
+ *
5717
+ * @example
5718
+ * kernel.init();
5719
+ */
5282
5720
  init() {
5283
5721
  for (const component of resolved.components) registerComponent(state, component);
5284
5722
  state.currentUrl = currentLocationUrl();
5285
5723
  },
5724
+ /**
5725
+ * Boot navigation interception + initial scan (throws if already started).
5726
+ *
5727
+ * @example
5728
+ * kernel.boot();
5729
+ */
5286
5730
  boot() {
5287
5731
  if (typeof document === "undefined") return;
5288
5732
  if (state.started) throw new Error(`${ERROR_PREFIX} spa kernel already started.\n Call app.stop() before booting again (single boot per app).`);
@@ -5292,16 +5736,42 @@ function createSpaKernel(state, config, emit, deps) {
5292
5736
  scanAndMount(state, emit, resolved.swapSelector);
5293
5737
  state.started = true;
5294
5738
  },
5739
+ /**
5740
+ * Register a component definition (last-registered-wins).
5741
+ *
5742
+ * @param component - The component definition to register.
5743
+ * @example
5744
+ * kernel.register(counter);
5745
+ */
5295
5746
  register(component) {
5296
5747
  registerComponent(state, component);
5297
5748
  },
5749
+ /**
5750
+ * Process a navigation to `path` (fetch then swap; full reload on error).
5751
+ *
5752
+ * @param path - The target path to navigate to.
5753
+ * @example
5754
+ * kernel.processNav("/about");
5755
+ */
5298
5756
  processNav(path) {
5299
5757
  if (typeof document === "undefined") return;
5300
5758
  performNavigation(path, handlers).catch(() => {});
5301
5759
  },
5760
+ /**
5761
+ * Scan the swap region and mount components for matching elements.
5762
+ *
5763
+ * @example
5764
+ * kernel.scan();
5765
+ */
5302
5766
  scan() {
5303
5767
  scanAndMount(state, emit, resolved.swapSelector);
5304
5768
  },
5769
+ /**
5770
+ * Tear down router listeners, dispose all instances, reset boot state.
5771
+ *
5772
+ * @example
5773
+ * kernel.dispose();
5774
+ */
5305
5775
  dispose() {
5306
5776
  state.destroyRouter?.();
5307
5777
  state.destroyRouter = null;
@@ -5330,7 +5800,6 @@ function initSpa(ctx) {
5330
5800
  kernelRef.current = kernel;
5331
5801
  kernel.init();
5332
5802
  }
5333
-
5334
5803
  //#endregion
5335
5804
  //#region src/plugins/spa/lifecycle.ts
5336
5805
  /** Router/instance teardown captured during onStart (undefined when stopped). */
@@ -5378,7 +5847,6 @@ function disposeSpa() {
5378
5847
  logRef = void 0;
5379
5848
  }
5380
5849
  }
5381
-
5382
5850
  //#endregion
5383
5851
  //#region src/plugins/spa/index.ts
5384
5852
  /**
@@ -5402,42 +5870,28 @@ const spaPlugin = createPlugin$1("spa", {
5402
5870
  },
5403
5871
  onStop: disposeSpa
5404
5872
  });
5405
-
5406
5873
  //#endregion
5407
5874
  //#region src/plugins/build/types.ts
5408
5875
  var types_exports = /* @__PURE__ */ __exportAll({});
5409
-
5410
5876
  //#endregion
5411
5877
  //#region src/plugins/content/types.ts
5412
5878
  var types_exports$1 = /* @__PURE__ */ __exportAll({});
5413
-
5414
5879
  //#endregion
5415
5880
  //#region src/plugins/deploy/types.ts
5416
5881
  var types_exports$2 = /* @__PURE__ */ __exportAll({});
5417
-
5418
5882
  //#endregion
5419
5883
  //#region src/plugins/env/types.ts
5420
5884
  var types_exports$3 = /* @__PURE__ */ __exportAll({});
5421
-
5422
5885
  //#endregion
5423
5886
  //#region src/plugins/head/types.ts
5424
5887
  var types_exports$4 = /* @__PURE__ */ __exportAll({});
5425
-
5426
5888
  //#endregion
5427
5889
  //#region src/plugins/log/types.ts
5428
5890
  var types_exports$5 = /* @__PURE__ */ __exportAll({});
5429
-
5430
5891
  //#endregion
5431
5892
  //#region src/plugins/router/types.ts
5432
5893
  var types_exports$6 = /* @__PURE__ */ __exportAll({});
5433
-
5434
- //#endregion
5435
- //#region src/index.ts
5436
- /**
5437
- * @file `@moku-labs/web` — a Moku Layer-2 content static-site + SPA framework.
5438
- * @see README.md
5439
- */
5440
- const framework = createCore(coreConfig, {
5894
+ const { createApp, createPlugin } = createCore(coreConfig, {
5441
5895
  plugins: [
5442
5896
  sitePlugin,
5443
5897
  i18nPlugin,
@@ -5450,56 +5904,54 @@ const framework = createCore(coreConfig, {
5450
5904
  ],
5451
5905
  pluginConfigs: {}
5452
5906
  });
5453
- const { createApp, createPlugin } = framework;
5454
-
5455
5907
  //#endregion
5456
- Object.defineProperty(exports, 'Build', {
5457
- enumerable: true,
5458
- get: function () {
5459
- return types_exports;
5460
- }
5908
+ Object.defineProperty(exports, "Build", {
5909
+ enumerable: true,
5910
+ get: function() {
5911
+ return types_exports;
5912
+ }
5461
5913
  });
5462
- Object.defineProperty(exports, 'Content', {
5463
- enumerable: true,
5464
- get: function () {
5465
- return types_exports$1;
5466
- }
5914
+ Object.defineProperty(exports, "Content", {
5915
+ enumerable: true,
5916
+ get: function() {
5917
+ return types_exports$1;
5918
+ }
5467
5919
  });
5468
- Object.defineProperty(exports, 'Deploy', {
5469
- enumerable: true,
5470
- get: function () {
5471
- return types_exports$2;
5472
- }
5920
+ Object.defineProperty(exports, "Deploy", {
5921
+ enumerable: true,
5922
+ get: function() {
5923
+ return types_exports$2;
5924
+ }
5473
5925
  });
5474
- Object.defineProperty(exports, 'Env', {
5475
- enumerable: true,
5476
- get: function () {
5477
- return types_exports$3;
5478
- }
5926
+ Object.defineProperty(exports, "Env", {
5927
+ enumerable: true,
5928
+ get: function() {
5929
+ return types_exports$3;
5930
+ }
5479
5931
  });
5480
- Object.defineProperty(exports, 'Head', {
5481
- enumerable: true,
5482
- get: function () {
5483
- return types_exports$4;
5484
- }
5932
+ Object.defineProperty(exports, "Head", {
5933
+ enumerable: true,
5934
+ get: function() {
5935
+ return types_exports$4;
5936
+ }
5485
5937
  });
5486
- Object.defineProperty(exports, 'Log', {
5487
- enumerable: true,
5488
- get: function () {
5489
- return types_exports$5;
5490
- }
5938
+ Object.defineProperty(exports, "Log", {
5939
+ enumerable: true,
5940
+ get: function() {
5941
+ return types_exports$5;
5942
+ }
5491
5943
  });
5492
- Object.defineProperty(exports, 'Router', {
5493
- enumerable: true,
5494
- get: function () {
5495
- return types_exports$6;
5496
- }
5944
+ Object.defineProperty(exports, "Router", {
5945
+ enumerable: true,
5946
+ get: function() {
5947
+ return types_exports$6;
5948
+ }
5497
5949
  });
5498
- Object.defineProperty(exports, 'Spa', {
5499
- enumerable: true,
5500
- get: function () {
5501
- return types_exports$7;
5502
- }
5950
+ Object.defineProperty(exports, "Spa", {
5951
+ enumerable: true,
5952
+ get: function() {
5953
+ return types_exports$7;
5954
+ }
5503
5955
  });
5504
5956
  exports.buildArticleHead = buildArticleHead;
5505
5957
  exports.buildPlugin = buildPlugin;
@@ -5522,4 +5974,4 @@ exports.route = route;
5522
5974
  exports.routerPlugin = routerPlugin;
5523
5975
  exports.sitePlugin = sitePlugin;
5524
5976
  exports.spaPlugin = spaPlugin;
5525
- exports.twitter = twitter;
5977
+ exports.twitter = twitter;