@moku-labs/web 0.3.0 → 0.4.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/README.md +52 -12
- package/dist/convention-Dr8jxG70.cjs +81 -0
- package/dist/convention-X3zLTlJ8.mjs +33 -0
- package/dist/index.cjs +1503 -295
- package/dist/index.d.cts +1145 -632
- package/dist/index.d.mts +1144 -631
- package/dist/index.mjs +1478 -249
- package/dist/render-BL9Fv6G6.mjs +20 -0
- package/dist/render-BSTM0Akv.cjs +20 -0
- package/dist/writer-BcWqa_7I.mjs +90 -0
- package/dist/writer-DAF0pM25.cjs +92 -0
- package/package.json +3 -2
package/dist/index.d.mts
CHANGED
|
@@ -3,7 +3,7 @@ import { Pluggable, Processor } from "unified";
|
|
|
3
3
|
import { ComponentChildren, VNode } from "preact";
|
|
4
4
|
|
|
5
5
|
//#region src/plugins/log/types.d.ts
|
|
6
|
-
declare namespace types_d_exports$
|
|
6
|
+
declare namespace types_d_exports$6 {
|
|
7
7
|
export { ExpectChain, LogApi, LogConfig, LogEntry, LogLevel, LogSink, LogState };
|
|
8
8
|
}
|
|
9
9
|
/**
|
|
@@ -143,7 +143,7 @@ type LogApi = {
|
|
|
143
143
|
addSink(sink: LogSink): void; /** Clear all recorded entries while keeping registered sinks. */
|
|
144
144
|
reset(): void;
|
|
145
145
|
};
|
|
146
|
-
declare namespace types_d_exports$
|
|
146
|
+
declare namespace types_d_exports$4 {
|
|
147
147
|
export { EnvApi, EnvConfig, EnvProvider, EnvState, EnvVarSpec };
|
|
148
148
|
}
|
|
149
149
|
/**
|
|
@@ -302,6 +302,26 @@ type EnvApi = {
|
|
|
302
302
|
getPublicMap(): ReadonlyMap<string, string>;
|
|
303
303
|
};
|
|
304
304
|
//#endregion
|
|
305
|
+
//#region src/plugins/env/providers.browser.d.ts
|
|
306
|
+
/**
|
|
307
|
+
* A browser-safe {@link EnvProvider} that reads `import.meta.env` and an optional
|
|
308
|
+
* `globalThis[globalKey]` snapshot, merging them with the runtime global winning.
|
|
309
|
+
* Contains zero `node:*` imports, so it is safe to include in the client bundle.
|
|
310
|
+
* Never throws on missing sources — each absent source resolves to `{}`.
|
|
311
|
+
*
|
|
312
|
+
* @param options - Optional settings.
|
|
313
|
+
* @param options.globalKey - `globalThis` key to read a public-env snapshot from. Defaults to `"__ENV__"`.
|
|
314
|
+
* @returns An {@link EnvProvider} named `browser-env`.
|
|
315
|
+
* @example
|
|
316
|
+
* ```ts
|
|
317
|
+
* const provider = browserEnv();
|
|
318
|
+
* provider.load(); // { PUBLIC_API_URL: "/api", ... }
|
|
319
|
+
* ```
|
|
320
|
+
*/
|
|
321
|
+
declare function browserEnv(options?: {
|
|
322
|
+
globalKey?: string;
|
|
323
|
+
}): EnvProvider;
|
|
324
|
+
//#endregion
|
|
305
325
|
//#region src/plugins/env/index.d.ts
|
|
306
326
|
/**
|
|
307
327
|
* Core plugin that resolves, validates, and freezes the environment at `onInit`,
|
|
@@ -521,8 +541,8 @@ type Api$5 = {
|
|
|
521
541
|
*/
|
|
522
542
|
t(locale: string, key: string): string;
|
|
523
543
|
};
|
|
524
|
-
declare namespace types_d_exports$
|
|
525
|
-
export { Api$4 as Api, CompileInput, CompiledRoute, Config$4 as Config, ExtractRouteParams, ExtractSegmentParameter, HeadConfig$1 as HeadConfig, MatcherTable, Prettify, RouteBuilder, RouteContext, RouteDefinition, RouteHandlers, RouteMap, RouteState, RouterApi, RouterConfig, RouterState, State$4 as State, TypedRoute };
|
|
544
|
+
declare namespace types_d_exports$7 {
|
|
545
|
+
export { Api$4 as Api, ClientRoute, CompileInput, CompiledRoute, Config$4 as Config, ExtractRouteParams, ExtractSegmentParameter, HeadConfig$1 as HeadConfig, MatcherTable, Prettify, RouteBuilder, RouteContext, RouteDefinition, RouteHandlers, RouteMap, RouteState, RouterApi, RouterConfig, RouterState, State$4 as State, TypedRoute };
|
|
526
546
|
}
|
|
527
547
|
/**
|
|
528
548
|
* Param contribution of a single path segment. `{name:?}` / `:name?` → optional;
|
|
@@ -588,11 +608,23 @@ interface RouteBuilder<S extends RouteState> extends RouteDefinition {
|
|
|
588
608
|
layout(component: (children: ComponentChildren) => VNode): RouteBuilder<S>;
|
|
589
609
|
/** Attach the page render handler. */
|
|
590
610
|
render(handler: (ctx: RouteContext<S>) => VNode): RouteBuilder<S>;
|
|
611
|
+
/**
|
|
612
|
+
* Attach the client-side validation gate: parse the raw `unknown` fetched from
|
|
613
|
+
* the persisted data file back into this route's data type `S["data"]`. Runs at
|
|
614
|
+
* the trust boundary before `render` on the client (and MUST return `S["data"]`,
|
|
615
|
+
* so a mismatched schema is a compile error). Throw inside it to reject malformed
|
|
616
|
+
* data — `spa` then falls back to HTML-over-fetch. Use a hand guard or any
|
|
617
|
+
* Standard-Schema validator (zod/valibot/arktype).
|
|
618
|
+
*/
|
|
619
|
+
parse(handler: (raw: unknown) => S["data"]): RouteBuilder<S>;
|
|
591
620
|
/** Attach the head/SEO handler. */
|
|
592
621
|
head(handler: (ctx: RouteContext<S>) => HeadConfig$1): RouteBuilder<S>;
|
|
593
622
|
/** Attach a static-generation param producer. */
|
|
594
623
|
generate(handler: (locale: string) => S["params"][] | Promise<S["params"][]>): RouteBuilder<S>;
|
|
595
|
-
/**
|
|
624
|
+
/**
|
|
625
|
+
* Attach an arbitrary metadata bag. The bag MUST be JSON-serializable: it is
|
|
626
|
+
* projected verbatim into `clientManifest()` and shipped to the browser.
|
|
627
|
+
*/
|
|
596
628
|
meta(meta: Record<string, unknown>): RouteBuilder<S>;
|
|
597
629
|
/** Attach a JSON serializer for the route's data. */
|
|
598
630
|
toJson(handler: (ctx: RouteContext<S>) => unknown): RouteBuilder<S>;
|
|
@@ -607,6 +639,8 @@ interface RouteHandlers {
|
|
|
607
639
|
readonly layout?: (children: ComponentChildren) => VNode;
|
|
608
640
|
/** Page renderer. */
|
|
609
641
|
readonly render?: (ctx: RouteContext<RouteState>) => VNode;
|
|
642
|
+
/** Client-side validation gate: `unknown` (fetched JSON) → the route's data type, or throw. */
|
|
643
|
+
readonly parse?: (raw: unknown) => unknown;
|
|
610
644
|
/** Head/SEO producer. */
|
|
611
645
|
readonly head?: (ctx: RouteContext<RouteState>) => HeadConfig$1;
|
|
612
646
|
/** Static-generation param producer. */
|
|
@@ -711,6 +745,8 @@ interface MatcherTable {
|
|
|
711
745
|
interface RouterState {
|
|
712
746
|
/** Compiled matcher table; `null` until `onInit` assigns it. */
|
|
713
747
|
table: MatcherTable | null;
|
|
748
|
+
/** Resolved render mode (single source of truth; set in `onInit`). Defaults `"hybrid"`. */
|
|
749
|
+
mode: "ssg" | "spa" | "hybrid";
|
|
714
750
|
}
|
|
715
751
|
/** Plain-data input to `compileRoutes` — resolved DATA only, never the plugin ctx. */
|
|
716
752
|
interface CompileInput {
|
|
@@ -725,6 +761,24 @@ interface CompileInput {
|
|
|
725
761
|
/** Default locale used for bare-pattern fallback. */
|
|
726
762
|
readonly defaultLocale: string;
|
|
727
763
|
}
|
|
764
|
+
/**
|
|
765
|
+
* Serializable route entry for the client route-index — a projection of the
|
|
766
|
+
* compiled route table with NO `_handlers` closures, safe to ship to the browser
|
|
767
|
+
* (the SPA recompiles matchers lazily from `pattern`).
|
|
768
|
+
*
|
|
769
|
+
* @remarks
|
|
770
|
+
* `meta` MUST be JSON-serializable: `clientManifest()` is intended to survive a
|
|
771
|
+
* `JSON.stringify`/`JSON.parse` round-trip, so a route's `.meta()` bag must contain
|
|
772
|
+
* only JSON-safe values (no functions, symbols, or class instances).
|
|
773
|
+
*/
|
|
774
|
+
interface ClientRoute {
|
|
775
|
+
/** URL pattern string, e.g. `/{lang:?}/{slug}/`. */
|
|
776
|
+
readonly pattern: string;
|
|
777
|
+
/** Route name key from the route map. */
|
|
778
|
+
readonly name: string;
|
|
779
|
+
/** Route metadata bag from `.meta()`. MUST be JSON-serializable. */
|
|
780
|
+
readonly meta: Record<string, unknown>;
|
|
781
|
+
}
|
|
728
782
|
/** Public API exposed via `ctx.require(routerPlugin)`. */
|
|
729
783
|
type RouterApi = {
|
|
730
784
|
/**
|
|
@@ -767,197 +821,36 @@ type RouterApi = {
|
|
|
767
821
|
* for (const def of ctx.require(routerPlugin).manifest()) { def._handlers.load?.({}, "en"); }
|
|
768
822
|
*/
|
|
769
823
|
manifest(): readonly RouteDefinition[];
|
|
770
|
-
};
|
|
771
|
-
/** Re-export under the canonical `Config` name for the plugin-types barrel. */
|
|
772
|
-
type Config$4 = RouterConfig;
|
|
773
|
-
/** Re-export under the canonical `State` name for the plugin-types barrel. */
|
|
774
|
-
type State$4 = RouterState;
|
|
775
|
-
/** Re-export under the canonical `Api` name for the plugin-types barrel. */
|
|
776
|
-
type Api$4 = RouterApi;
|
|
777
|
-
declare namespace types_d_exports$1 {
|
|
778
|
-
export { Api$3 as Api, Article, ArticleCard, ComputedFields, Config$3 as Config, ContentApiContext, ContentEvents, Frontmatter, State$3 as State };
|
|
779
|
-
}
|
|
780
|
-
/**
|
|
781
|
-
* Configuration for the content plugin.
|
|
782
|
-
*
|
|
783
|
-
* @example
|
|
784
|
-
* ```ts
|
|
785
|
-
* { contentDir: "./src/content", trustedContent: false, shikiTheme: "github-dark" }
|
|
786
|
-
* ```
|
|
787
|
-
*/
|
|
788
|
-
type Config$3 = {
|
|
789
|
-
/** Absolute or project-relative path to the content directory. Validated in onInit. */contentDir: string;
|
|
790
|
-
/**
|
|
791
|
-
* SECURITY GATE. When false (the default), rehype-sanitize runs as the final
|
|
792
|
-
* pipeline step. Set true ONLY for fully author-controlled Markdown — true
|
|
793
|
-
* disables sanitize and trusts all raw HTML.
|
|
794
|
-
*/
|
|
795
|
-
trustedContent: boolean; /** Additional remark plugins, concatenated AFTER framework defaults. Defaults to []. */
|
|
796
|
-
extraRemarkPlugins?: readonly Pluggable[]; /** Additional rehype plugins, concatenated after custom transforms, before Shiki + sanitize. Defaults to []. */
|
|
797
|
-
extraRehypePlugins?: readonly Pluggable[]; /** Shiki theme name for syntax highlighting. Defaults to "github-dark". */
|
|
798
|
-
shikiTheme?: string; /** Author applied to articles whose frontmatter omits author. Defaults to undefined. */
|
|
799
|
-
defaultAuthor?: string;
|
|
800
|
-
};
|
|
801
|
-
/**
|
|
802
|
-
* Internal mutable state for the content plugin.
|
|
803
|
-
*
|
|
804
|
-
* @example
|
|
805
|
-
* ```ts
|
|
806
|
-
* { processor: null, articles: new Map(), slugs: null, dirtyPaths: new Set() }
|
|
807
|
-
* ```
|
|
808
|
-
*/
|
|
809
|
-
type State$3 = {
|
|
810
|
-
/** Lazily-created unified processor singleton. null until first render()/loadAll(). */processor: Processor | null; /** Article cache keyed locale -> (slug -> Article). Starts empty. */
|
|
811
|
-
articles: Map<string, Map<string, Article>>; /** Discovered, sorted slug list cached after first disk scan. null until first discovery. */
|
|
812
|
-
slugs: string[] | null; /** Paths marked stale by invalidate(); next loadAll() re-reads only these. Starts empty. */
|
|
813
|
-
dirtyPaths: Set<string>;
|
|
814
|
-
};
|
|
815
|
-
/**
|
|
816
|
-
* YAML frontmatter parsed from each article file.
|
|
817
|
-
*
|
|
818
|
-
* @example
|
|
819
|
-
* ```ts
|
|
820
|
-
* { title: "Hello", date: "2026-01-15", description: "Intro", tags: [], language: "en" }
|
|
821
|
-
* ```
|
|
822
|
-
*/
|
|
823
|
-
type Frontmatter = {
|
|
824
|
-
/** Article title. Required. */title: string; /** ISO 8601 date string, e.g. "2026-01-15". Required. */
|
|
825
|
-
date: string; /** Short summary used in cards, feeds, and meta description. Required. */
|
|
826
|
-
description: string; /** Topic tags. Required (may be empty array). */
|
|
827
|
-
tags: string[]; /** Source language code of this file. Required. */
|
|
828
|
-
language: string; /** Draft flag. Excluded from output in production mode. Defaults to false. */
|
|
829
|
-
draft?: boolean; /** Author name. Falls back to config.defaultAuthor when omitted. */
|
|
830
|
-
author?: string;
|
|
831
|
-
};
|
|
832
|
-
/**
|
|
833
|
-
* Fields computed by the pipeline (not authored in frontmatter).
|
|
834
|
-
*
|
|
835
|
-
* @example
|
|
836
|
-
* ```ts
|
|
837
|
-
* { slug: "hello", readingTime: 1, contentId: "hello", status: "published", wordCount: 42 }
|
|
838
|
-
* ```
|
|
839
|
-
*/
|
|
840
|
-
type ComputedFields = {
|
|
841
|
-
/** Article directory name. */slug: string; /** Reading time in minutes (ceiling, minimum 1). */
|
|
842
|
-
readingTime: number; /** Stable content identifier (slug by default). */
|
|
843
|
-
contentId: string; /** Derived publication status. */
|
|
844
|
-
status: "published" | "draft"; /** Word count from the source body. */
|
|
845
|
-
wordCount: number;
|
|
846
|
-
};
|
|
847
|
-
/**
|
|
848
|
-
* A fully processed, render-ready article.
|
|
849
|
-
*
|
|
850
|
-
* @example
|
|
851
|
-
* ```ts
|
|
852
|
-
* { frontmatter, computed, html: "<p>…</p>", locale: "en", isFallback: false, url: "/en/hello/" }
|
|
853
|
-
* ```
|
|
854
|
-
*/
|
|
855
|
-
type Article = {
|
|
856
|
-
/** Parsed frontmatter. */frontmatter: Frontmatter; /** Pipeline-computed metadata. */
|
|
857
|
-
computed: ComputedFields; /** Sanitized rendered HTML body. */
|
|
858
|
-
html: string; /** Locale this Article instance represents (the requested locale, even on fallback). */
|
|
859
|
-
locale: string; /** True when the default-locale file was used because the requested locale was missing. */
|
|
860
|
-
isFallback: boolean; /** Canonical URL for this article in this locale. */
|
|
861
|
-
url: string;
|
|
862
|
-
};
|
|
863
|
-
/**
|
|
864
|
-
* Lightweight projection of Article for cards/lists.
|
|
865
|
-
*
|
|
866
|
-
* @example
|
|
867
|
-
* ```ts
|
|
868
|
-
* { contentId: "hello", status: "published", title: "Hello", date: "2026-01-15", description: "Intro", tags: [], readingTime: 1, url: "/en/hello/" }
|
|
869
|
-
* ```
|
|
870
|
-
*/
|
|
871
|
-
type ArticleCard = {
|
|
872
|
-
/** Stable content identifier. */contentId: string; /** Derived publication status. */
|
|
873
|
-
status: "published" | "draft"; /** Article title. */
|
|
874
|
-
title: string; /** ISO 8601 date string. */
|
|
875
|
-
date: string; /** Short summary. */
|
|
876
|
-
description: string; /** Topic tags. */
|
|
877
|
-
tags: string[]; /** Reading time in minutes. */
|
|
878
|
-
readingTime: number; /** Canonical URL for this article in this locale. */
|
|
879
|
-
url: string;
|
|
880
|
-
};
|
|
881
|
-
/**
|
|
882
|
-
* Notification-only events emitted by the content plugin.
|
|
883
|
-
*
|
|
884
|
-
* @example
|
|
885
|
-
* ```ts
|
|
886
|
-
* emit("content:ready", { locales: ["en"], articleCount: 3 });
|
|
887
|
-
* ```
|
|
888
|
-
*/
|
|
889
|
-
type ContentEvents = {
|
|
890
|
-
/** All articles loaded across locales. */"content:ready": {
|
|
891
|
-
locales: readonly string[];
|
|
892
|
-
articleCount: number;
|
|
893
|
-
}; /** Article paths marked stale for dev rebuild. */
|
|
894
|
-
"content:invalidated": {
|
|
895
|
-
paths: readonly string[];
|
|
896
|
-
};
|
|
897
|
-
};
|
|
898
|
-
/**
|
|
899
|
-
* Kernel-free domain context handed to createContentApi by the wiring harness.
|
|
900
|
-
* Carries ctx.state (mutable escape hatch), config, global, emit, and the
|
|
901
|
-
* i18n-derived locale/url helpers — so api.ts stays free of createPlugin/ctx.
|
|
902
|
-
*
|
|
903
|
-
* @example
|
|
904
|
-
* ```ts
|
|
905
|
-
* const apiContext: ContentApiContext = { state, config, global, emit, locales, defaultLocale, articleToUrl };
|
|
906
|
-
* ```
|
|
907
|
-
*/
|
|
908
|
-
type ContentApiContext = {
|
|
909
|
-
/** Mutable plugin state (article cache + lazy processor). */state: State$3; /** Resolved plugin configuration. */
|
|
910
|
-
config: Config$3; /** Global framework configuration (mode, etc.). */
|
|
911
|
-
global: {
|
|
912
|
-
mode: "production" | "development";
|
|
913
|
-
}; /** Emit a registered content event. */
|
|
914
|
-
emit: <K extends keyof ContentEvents>(event: K, payload: ContentEvents[K]) => void; /** Active locale codes from i18n. */
|
|
915
|
-
locales: () => readonly string[]; /** Default locale code from i18n (fallback source). */
|
|
916
|
-
defaultLocale: () => string; /** Build a canonical article URL for a locale + slug. */
|
|
917
|
-
articleToUrl: (locale: string, slug: string) => string;
|
|
918
|
-
};
|
|
919
|
-
/**
|
|
920
|
-
* Public API for the content plugin.
|
|
921
|
-
*
|
|
922
|
-
* @example
|
|
923
|
-
* ```ts
|
|
924
|
-
* const map = await app.content.loadAll();
|
|
925
|
-
* ```
|
|
926
|
-
*/
|
|
927
|
-
type Api$3 = {
|
|
928
|
-
/**
|
|
929
|
-
* Load every article across every active locale, returning a locale-keyed
|
|
930
|
-
* map of date-descending Article arrays. Emits content:ready.
|
|
931
|
-
*/
|
|
932
|
-
loadAll(): Promise<Map<string, Article[]>>;
|
|
933
|
-
/**
|
|
934
|
-
* Resolve and render a single article for one locale, with locale fallback.
|
|
935
|
-
*
|
|
936
|
-
* @param slug - Article directory name.
|
|
937
|
-
* @param locale - Requested locale code.
|
|
938
|
-
*/
|
|
939
|
-
load(slug: string, locale: string): Promise<Article>;
|
|
940
|
-
/**
|
|
941
|
-
* Render a raw Markdown string to HTML through the full pipeline.
|
|
942
|
-
*
|
|
943
|
-
* @param md - Raw Markdown source.
|
|
944
|
-
*/
|
|
945
|
-
renderMarkdown(md: string): Promise<string>;
|
|
946
824
|
/**
|
|
947
|
-
*
|
|
825
|
+
* Serializable, specificity-sorted projection of the route table for client
|
|
826
|
+
* shipping. Maps the compiled table to `{ pattern, name, meta }` entries with NO
|
|
827
|
+
* `_handlers` closures, returned as a fresh frozen array. JSON-serializable so the
|
|
828
|
+
* SPA can embed it and recompile matchers lazily in the browser.
|
|
948
829
|
*
|
|
949
|
-
* @
|
|
830
|
+
* @returns A fresh, frozen, specificity-sorted read-only array of {@link ClientRoute}.
|
|
831
|
+
* @example
|
|
832
|
+
* const json = JSON.stringify(ctx.require(routerPlugin).clientManifest());
|
|
950
833
|
*/
|
|
951
|
-
|
|
834
|
+
clientManifest(): readonly ClientRoute[];
|
|
952
835
|
/**
|
|
953
|
-
*
|
|
836
|
+
* The resolved render mode — the single source of truth for static/hybrid/spa
|
|
837
|
+
* behavior. `build` reads it to decide whether to emit client data sidecars;
|
|
838
|
+
* `spa` reads it to decide whether to attempt client DATA navigation.
|
|
954
839
|
*
|
|
955
|
-
* @
|
|
840
|
+
* @returns `"ssg" | "spa" | "hybrid"`.
|
|
841
|
+
* @example
|
|
842
|
+
* if (ctx.require(routerPlugin).mode() !== "ssg") { ... }
|
|
956
843
|
*/
|
|
957
|
-
|
|
844
|
+
mode(): "ssg" | "spa" | "hybrid";
|
|
958
845
|
};
|
|
959
|
-
|
|
960
|
-
|
|
846
|
+
/** Re-export under the canonical `Config` name for the plugin-types barrel. */
|
|
847
|
+
type Config$4 = RouterConfig;
|
|
848
|
+
/** Re-export under the canonical `State` name for the plugin-types barrel. */
|
|
849
|
+
type State$4 = RouterState;
|
|
850
|
+
/** Re-export under the canonical `Api` name for the plugin-types barrel. */
|
|
851
|
+
type Api$4 = RouterApi;
|
|
852
|
+
declare namespace types_d_exports$5 {
|
|
853
|
+
export { Api$3 as Api, ArticleMeta, Config$3 as Config, HeadConfig, HeadDefaults, HeadElement, ResolvedRoute, State$3 as State };
|
|
961
854
|
}
|
|
962
855
|
/**
|
|
963
856
|
* @file head plugin — type definitions skeleton
|
|
@@ -973,7 +866,7 @@ declare namespace types_d_exports$4 {
|
|
|
973
866
|
* const config: Config = { titleTemplate: "%s — Moku" };
|
|
974
867
|
* ```
|
|
975
868
|
*/
|
|
976
|
-
type Config$
|
|
869
|
+
type Config$3 = {
|
|
977
870
|
/** Title template applied to per-route titles. `%s` is replaced by the route title. */titleTemplate?: string; /** Default Open Graph image URL used when a route does not supply one. */
|
|
978
871
|
defaultOgImage?: string; /** Default Twitter card type emitted when og/twitter content is present. */
|
|
979
872
|
twitterCard?: "summary" | "summary_large_image"; /** Default Twitter site handle (e.g. `"@moku_labs"`) emitted as `twitter:site`. */
|
|
@@ -991,7 +884,7 @@ type Config$2 = {
|
|
|
991
884
|
* const state: State = { defaults: null };
|
|
992
885
|
* ```
|
|
993
886
|
*/
|
|
994
|
-
type State$
|
|
887
|
+
type State$3 = {
|
|
995
888
|
/** Normalized head defaults, assigned once in `onInit` (initially `null`). */defaults: HeadDefaults | null;
|
|
996
889
|
};
|
|
997
890
|
/**
|
|
@@ -1085,7 +978,7 @@ type ResolvedRoute = {
|
|
|
1085
978
|
* const html: string = api.render(route, data);
|
|
1086
979
|
* ```
|
|
1087
980
|
*/
|
|
1088
|
-
type Api$
|
|
981
|
+
type Api$3 = {
|
|
1089
982
|
/**
|
|
1090
983
|
* Compose the final `<head>` inner HTML for a route. Pulled synchronously by `build`.
|
|
1091
984
|
*
|
|
@@ -1099,263 +992,41 @@ type Api$2 = {
|
|
|
1099
992
|
*/
|
|
1100
993
|
render(route: ResolvedRoute, data: unknown): string;
|
|
1101
994
|
};
|
|
1102
|
-
declare namespace types_d_exports {
|
|
1103
|
-
export {
|
|
995
|
+
declare namespace types_d_exports$8 {
|
|
996
|
+
export { COMPONENT_HOOK_NAMES, ComponentContext, ComponentDef, ComponentHooks, ComponentInstance, ExtractApi$1 as ExtractApi, PageData, ResolvedSpaConfig, SpaApi, SpaConfig, SpaContext, SpaDataReader, SpaEmitFunction, SpaEvents, SpaKernel, SpaKernelDeps, SpaRequire, SpaState };
|
|
1104
997
|
}
|
|
998
|
+
/** Payload map for the events `spa` emits, used to type the kernel's `emit` closure. */
|
|
999
|
+
type SpaEvents = {
|
|
1000
|
+
/** A navigation has been intercepted and is starting. */"spa:navigate": {
|
|
1001
|
+
from: string;
|
|
1002
|
+
to: string;
|
|
1003
|
+
}; /** The swap completed and the new URL is active. */
|
|
1004
|
+
"spa:navigated": {
|
|
1005
|
+
url: string;
|
|
1006
|
+
}; /** A component instance attached to an element. */
|
|
1007
|
+
"spa:component-mount": {
|
|
1008
|
+
name: string;
|
|
1009
|
+
el: Element;
|
|
1010
|
+
}; /** A component instance detached from an element. */
|
|
1011
|
+
"spa:component-unmount": {
|
|
1012
|
+
name: string;
|
|
1013
|
+
el: Element;
|
|
1014
|
+
};
|
|
1015
|
+
};
|
|
1016
|
+
/** Strictly-typed emit closure for the spa events (kernel overload form). */
|
|
1017
|
+
type SpaEmitFunction = EmitFn<SpaEvents>;
|
|
1105
1018
|
/**
|
|
1106
1019
|
* Structural extraction of a plugin instance's public API from its `_phantom`
|
|
1107
|
-
* carrier (mirrors the kernel's non-exported `ExtractPluginApi`)
|
|
1108
|
-
* framework's generic `require` is assignable to {@link PhaseContext.require}.
|
|
1020
|
+
* carrier (mirrors the kernel's non-exported `ExtractPluginApi`).
|
|
1109
1021
|
*
|
|
1110
1022
|
* @example
|
|
1111
|
-
*
|
|
1112
|
-
* type ContentApi = ExtractApi<typeof contentPlugin>;
|
|
1113
|
-
* ```
|
|
1023
|
+
* type RApi = ExtractApi<typeof routerPlugin>;
|
|
1114
1024
|
*/
|
|
1115
1025
|
type ExtractApi$1<PluginCandidate> = PluginCandidate extends {
|
|
1116
1026
|
readonly _phantom: {
|
|
1117
1027
|
readonly api: infer PluginApi;
|
|
1118
1028
|
};
|
|
1119
1029
|
} ? PluginApi : never;
|
|
1120
|
-
/**
|
|
1121
|
-
* Minimal logger slice used by the pipeline and phases (the core `log` API).
|
|
1122
|
-
*
|
|
1123
|
-
* @example
|
|
1124
|
-
* ```ts
|
|
1125
|
-
* const log: PhaseLog = { info: () => {}, debug: () => {}, warn: () => {}, error: () => {} };
|
|
1126
|
-
* ```
|
|
1127
|
-
*/
|
|
1128
|
-
type PhaseLog = {
|
|
1129
|
-
/** Record an informational event. */info(event: string, data?: unknown): void; /** Record a debug event. */
|
|
1130
|
-
debug(event: string, data?: unknown): void; /** Record a warning event. */
|
|
1131
|
-
warn(event: string, data?: unknown): void; /** Record an error event. */
|
|
1132
|
-
error(event: string, data?: unknown): void;
|
|
1133
|
-
};
|
|
1134
|
-
/**
|
|
1135
|
-
* Payload map for the events `build` emits, used to type the `emit` closure
|
|
1136
|
-
* handed to the pipeline driver and phases.
|
|
1137
|
-
*
|
|
1138
|
-
* @example
|
|
1139
|
-
* ```ts
|
|
1140
|
-
* const emit: PhaseEmit = (name, payload) => kernel.emit(name, payload);
|
|
1141
|
-
* ```
|
|
1142
|
-
*/
|
|
1143
|
-
type BuildEvents = {
|
|
1144
|
-
/** Phase boundary marker (start, then done with durationMs). */"build:phase": {
|
|
1145
|
-
phase: PhaseName;
|
|
1146
|
-
status: "start" | "done";
|
|
1147
|
-
durationMs?: number;
|
|
1148
|
-
}; /** One successful-run summary. */
|
|
1149
|
-
"build:complete": {
|
|
1150
|
-
outDir: string;
|
|
1151
|
-
pageCount: number;
|
|
1152
|
-
durationMs: number;
|
|
1153
|
-
};
|
|
1154
|
-
};
|
|
1155
|
-
/** Strictly-typed emit closure for the build events (kernel overload form). */
|
|
1156
|
-
type PhaseEmit = EmitFn<BuildEvents>;
|
|
1157
|
-
/** Generic `require` closure for pulling dependency plugin APIs at run time. */
|
|
1158
|
-
type PhaseRequire = <PluginCandidate extends {
|
|
1159
|
-
readonly name: string;
|
|
1160
|
-
readonly spec: unknown;
|
|
1161
|
-
readonly _phantom: {
|
|
1162
|
-
readonly config: unknown;
|
|
1163
|
-
readonly state: unknown;
|
|
1164
|
-
readonly api: unknown;
|
|
1165
|
-
readonly events: Record<string, unknown>;
|
|
1166
|
-
};
|
|
1167
|
-
}>(plugin: PluginCandidate) => ExtractApi$1<PluginCandidate>;
|
|
1168
|
-
/**
|
|
1169
|
-
* The plugin-context slice the pipeline driver and every phase consume: the
|
|
1170
|
-
* mutable `state`, the resolved `config`/`global`, plus `require`/`emit`/`log`.
|
|
1171
|
-
* Typed to match the kernel's generic context so the framework execution
|
|
1172
|
-
* context is structurally assignable.
|
|
1173
|
-
*
|
|
1174
|
-
* @example
|
|
1175
|
-
* ```ts
|
|
1176
|
-
* const ctx: PhaseContext = { state, config, global, require, emit, log };
|
|
1177
|
-
* ```
|
|
1178
|
-
*/
|
|
1179
|
-
type PhaseContext = {
|
|
1180
|
-
/** Mutable per-run build state (caches + runId). */state: State$1; /** Resolved, frozen build config. */
|
|
1181
|
-
readonly config: Readonly<Config$1>; /** Global framework config (mode, etc.). */
|
|
1182
|
-
readonly global: Readonly<{
|
|
1183
|
-
mode: "production" | "development";
|
|
1184
|
-
}>; /** Resolve a depended-upon plugin instance to its public API. */
|
|
1185
|
-
require: PhaseRequire; /** Emit a build event (notification-only). */
|
|
1186
|
-
emit: PhaseEmit; /** Structured logger (core `log` API). */
|
|
1187
|
-
readonly log: PhaseLog;
|
|
1188
|
-
};
|
|
1189
|
-
/**
|
|
1190
|
-
* Injectable PNG renderer for the og-images phase. Defaults to the real
|
|
1191
|
-
* Satori → resvg pipeline; unit tests inject a fake to assert hash-cache skip
|
|
1192
|
-
* and the `p-limit` bound without rasterizing real images.
|
|
1193
|
-
*
|
|
1194
|
-
* @example
|
|
1195
|
-
* ```ts
|
|
1196
|
-
* const render: OgPngRenderer = async () => new Uint8Array();
|
|
1197
|
-
* ```
|
|
1198
|
-
*/
|
|
1199
|
-
type OgPngRenderer = (input: {
|
|
1200
|
-
/** Article title rendered into the card. */title: string; /** Output width in pixels. */
|
|
1201
|
-
width: number; /** Output height in pixels. */
|
|
1202
|
-
height: number;
|
|
1203
|
-
}) => Promise<Uint8Array>;
|
|
1204
|
-
/**
|
|
1205
|
-
* Optional OG-image generation config. Omit the field (or set `false`) to disable.
|
|
1206
|
-
*
|
|
1207
|
-
* @example
|
|
1208
|
-
* ```ts
|
|
1209
|
-
* const og: OgImageConfig = { fontDir: "./fonts" };
|
|
1210
|
-
* ```
|
|
1211
|
-
*/
|
|
1212
|
-
interface OgImageConfig {
|
|
1213
|
-
/** Directory containing at least one .ttf/.otf/.woff font. Validated in onInit (void — config check only). */
|
|
1214
|
-
fontDir: string;
|
|
1215
|
-
/** Optional path to a custom OG template module. Falls back to the built-in template. */
|
|
1216
|
-
template?: string;
|
|
1217
|
-
/** Output dimensions. Defaults to 1200x630. */
|
|
1218
|
-
size?: {
|
|
1219
|
-
width: number;
|
|
1220
|
-
height: number;
|
|
1221
|
-
};
|
|
1222
|
-
}
|
|
1223
|
-
/**
|
|
1224
|
-
* Public configuration for the `build` plugin. Flags give opt-in granularity over
|
|
1225
|
-
* individual outputs without rewriting the pipeline.
|
|
1226
|
-
*
|
|
1227
|
-
* @example
|
|
1228
|
-
* ```ts
|
|
1229
|
-
* const config: Config = { outDir: "./dist", minify: true, feeds: true, sitemap: true, images: true, ogImage: false };
|
|
1230
|
-
* ```
|
|
1231
|
-
*/
|
|
1232
|
-
type Config$1 = {
|
|
1233
|
-
/** Output directory for the built site. */outDir: string; /** Minify bundled CSS/JS. */
|
|
1234
|
-
minify: boolean; /** Generate RSS/Atom/JSON feeds. */
|
|
1235
|
-
feeds: boolean; /** Generate sitemap.xml + robots.txt. */
|
|
1236
|
-
sitemap: boolean; /** Optimize + copy content images. */
|
|
1237
|
-
images: boolean; /** OG-image generation. `false` (or omitted) disables it; an object enables and configures it. */
|
|
1238
|
-
ogImage: OgImageConfig | false;
|
|
1239
|
-
};
|
|
1240
|
-
/**
|
|
1241
|
-
* Per-run closure state for the `build` plugin. Holds caches and config only —
|
|
1242
|
-
* no domain data is duplicated here (pulled fresh via `ctx.require` each run).
|
|
1243
|
-
*
|
|
1244
|
-
* @example
|
|
1245
|
-
* ```ts
|
|
1246
|
-
* const state: State = { config, manifest: null, buildCache: new Map(), runId: null, ogImageHashCache: new Map() };
|
|
1247
|
-
* ```
|
|
1248
|
-
*/
|
|
1249
|
-
interface State$1 {
|
|
1250
|
-
/** Resolved, frozen config snapshot. */
|
|
1251
|
-
config: Config$1;
|
|
1252
|
-
/** Cached route manifest for the current run (populated in Phase 3 from router). */
|
|
1253
|
-
manifest: RouteDefinition[] | null;
|
|
1254
|
-
/** Per-run build artifacts (e.g. hashed CSS/JS asset paths from Phase 1). */
|
|
1255
|
-
buildCache: Map<string, unknown>;
|
|
1256
|
-
/** Unique id for the current run (timestamp/uuid) — injected as build-id meta. */
|
|
1257
|
-
runId: string | null;
|
|
1258
|
-
/**
|
|
1259
|
-
* Content-hash cache for OG images: slug -> sha256(title + template + size).
|
|
1260
|
-
* Loaded from `<outDir>/.cache/og-images.json` at the OG phase and written back,
|
|
1261
|
-
* so unchanged articles are skipped on the next run.
|
|
1262
|
-
*/
|
|
1263
|
-
ogImageHashCache: Map<string, string>;
|
|
1264
|
-
}
|
|
1265
|
-
/**
|
|
1266
|
-
* Ordered names of the build pipeline phases, in execution order.
|
|
1267
|
-
*
|
|
1268
|
-
* @example
|
|
1269
|
-
* ```ts
|
|
1270
|
-
* const phase: PhaseName = "bundle";
|
|
1271
|
-
* ```
|
|
1272
|
-
*/
|
|
1273
|
-
type PhaseName = "bundle" | "content" | "images" | "pages" | "feeds" | "sitemap" | "og-images" | "root-index";
|
|
1274
|
-
/**
|
|
1275
|
-
* Result of a completed build run.
|
|
1276
|
-
*
|
|
1277
|
-
* @example
|
|
1278
|
-
* ```ts
|
|
1279
|
-
* const result: BuildResult = { outDir: "./dist", pageCount: 12, durationMs: 840 };
|
|
1280
|
-
* ```
|
|
1281
|
-
*/
|
|
1282
|
-
interface BuildResult {
|
|
1283
|
-
/** Resolved output directory the site was written to. */
|
|
1284
|
-
outDir: string;
|
|
1285
|
-
/** Number of route pages rendered. */
|
|
1286
|
-
pageCount: number;
|
|
1287
|
-
/** Total wall-clock duration of the run, in milliseconds. */
|
|
1288
|
-
durationMs: number;
|
|
1289
|
-
}
|
|
1290
|
-
/**
|
|
1291
|
-
* Public API surface mounted on `app.build`.
|
|
1292
|
-
*
|
|
1293
|
-
* @example
|
|
1294
|
-
* ```ts
|
|
1295
|
-
* const result = await app.build.run();
|
|
1296
|
-
* ```
|
|
1297
|
-
*/
|
|
1298
|
-
type Api$1 = {
|
|
1299
|
-
/**
|
|
1300
|
-
* Run the full SSG pipeline and write the site to disk.
|
|
1301
|
-
*
|
|
1302
|
-
* @param options - Optional run overrides.
|
|
1303
|
-
* @param options.outDir - Override the configured output directory for this run.
|
|
1304
|
-
* @returns The build result (outDir, pageCount, durationMs).
|
|
1305
|
-
* @example
|
|
1306
|
-
* ```ts
|
|
1307
|
-
* const result = await app.build.run({ outDir: "./preview" });
|
|
1308
|
-
* ```
|
|
1309
|
-
*/
|
|
1310
|
-
run(options?: {
|
|
1311
|
-
outDir?: string;
|
|
1312
|
-
}): Promise<BuildResult>;
|
|
1313
|
-
/**
|
|
1314
|
-
* List the phases in execution order (introspection / tooling).
|
|
1315
|
-
*
|
|
1316
|
-
* @returns The static ordered phase names.
|
|
1317
|
-
* @example
|
|
1318
|
-
* ```ts
|
|
1319
|
-
* const order = app.build.phases();
|
|
1320
|
-
* ```
|
|
1321
|
-
*/
|
|
1322
|
-
phases(): PhaseName[];
|
|
1323
|
-
};
|
|
1324
|
-
declare namespace types_d_exports$7 {
|
|
1325
|
-
export { COMPONENT_HOOK_NAMES, ComponentContext, ComponentDef, ComponentHooks, ComponentInstance, ExtractApi, PageData, ResolvedSpaConfig, SpaApi, SpaConfig, SpaContext, SpaEmitFunction, SpaEvents, SpaKernel, SpaKernelDeps, SpaRequire, SpaState };
|
|
1326
|
-
}
|
|
1327
|
-
/** Payload map for the events `spa` emits, used to type the kernel's `emit` closure. */
|
|
1328
|
-
type SpaEvents = {
|
|
1329
|
-
/** A navigation has been intercepted and is starting. */"spa:navigate": {
|
|
1330
|
-
from: string;
|
|
1331
|
-
to: string;
|
|
1332
|
-
}; /** The swap completed and the new URL is active. */
|
|
1333
|
-
"spa:navigated": {
|
|
1334
|
-
url: string;
|
|
1335
|
-
}; /** A component instance attached to an element. */
|
|
1336
|
-
"spa:component-mount": {
|
|
1337
|
-
name: string;
|
|
1338
|
-
el: Element;
|
|
1339
|
-
}; /** A component instance detached from an element. */
|
|
1340
|
-
"spa:component-unmount": {
|
|
1341
|
-
name: string;
|
|
1342
|
-
el: Element;
|
|
1343
|
-
};
|
|
1344
|
-
};
|
|
1345
|
-
/** Strictly-typed emit closure for the spa events (kernel overload form). */
|
|
1346
|
-
type SpaEmitFunction = EmitFn<SpaEvents>;
|
|
1347
|
-
/**
|
|
1348
|
-
* Structural extraction of a plugin instance's public API from its `_phantom`
|
|
1349
|
-
* carrier (mirrors the kernel's non-exported `ExtractPluginApi`).
|
|
1350
|
-
*
|
|
1351
|
-
* @example
|
|
1352
|
-
* type RApi = ExtractApi<typeof routerPlugin>;
|
|
1353
|
-
*/
|
|
1354
|
-
type ExtractApi<PluginCandidate> = PluginCandidate extends {
|
|
1355
|
-
readonly _phantom: {
|
|
1356
|
-
readonly api: infer PluginApi;
|
|
1357
|
-
};
|
|
1358
|
-
} ? PluginApi : never;
|
|
1359
1030
|
/** Generic `require` closure for pulling dependency plugin APIs at init time. */
|
|
1360
1031
|
type SpaRequire = <PluginCandidate extends {
|
|
1361
1032
|
readonly name: string;
|
|
@@ -1366,7 +1037,7 @@ type SpaRequire = <PluginCandidate extends {
|
|
|
1366
1037
|
readonly api: unknown;
|
|
1367
1038
|
readonly events: Record<string, unknown>;
|
|
1368
1039
|
};
|
|
1369
|
-
}>(plugin: PluginCandidate) => ExtractApi<PluginCandidate>;
|
|
1040
|
+
}>(plugin: PluginCandidate) => ExtractApi$1<PluginCandidate>;
|
|
1370
1041
|
/**
|
|
1371
1042
|
* The plugin-context slice the spa wiring consumes in `onInit`/`onStart`:
|
|
1372
1043
|
* mutable `state`, resolved `config`, `require`/`emit`/`log`. Structurally
|
|
@@ -1379,6 +1050,11 @@ interface SpaContext {
|
|
|
1379
1050
|
readonly config: Readonly<SpaConfig>;
|
|
1380
1051
|
/** Resolve a depended-upon plugin instance to its public API. */
|
|
1381
1052
|
require: SpaRequire;
|
|
1053
|
+
/**
|
|
1054
|
+
* Whether a plugin is registered (by name). Used to detect the OPTIONAL `data`
|
|
1055
|
+
* plugin at init — `spa` enables client DATA navigation only when `has("data")`.
|
|
1056
|
+
*/
|
|
1057
|
+
has: (name: string) => boolean;
|
|
1382
1058
|
/** Emit a spa lifecycle event (notification-only). */
|
|
1383
1059
|
emit: SpaEmitFunction;
|
|
1384
1060
|
/** Structured logger (core `log` API). */
|
|
@@ -1505,12 +1181,26 @@ interface ComponentInstance {
|
|
|
1505
1181
|
}
|
|
1506
1182
|
/** Page data payload parsed from the inline `script#__DATA__` element. */
|
|
1507
1183
|
type PageData = Record<string, unknown>;
|
|
1508
|
-
/**
|
|
1184
|
+
/**
|
|
1185
|
+
* The OPTIONAL `data` provider reader the kernel uses for client DATA navigation —
|
|
1186
|
+
* a structural slice of the `data` plugin's API (fetch the persisted JSON for a
|
|
1187
|
+
* page path). Captured at init via `ctx.has("data")`/`ctx.require` so `spa` never
|
|
1188
|
+
* imports the `data` plugin or its types.
|
|
1189
|
+
*/
|
|
1190
|
+
type SpaDataReader = (path: string) => Promise<unknown | null>;
|
|
1191
|
+
/** Resolved dependency APIs the kernel reuses (router match/mode, head compose, optional data). */
|
|
1509
1192
|
interface SpaKernelDeps {
|
|
1510
|
-
/** Router plugin API — used for client-side route
|
|
1193
|
+
/** Router plugin API — used for client-side route matching (`match`) + the resolved `mode`. */
|
|
1511
1194
|
router: RouterApi;
|
|
1512
1195
|
/** Head plugin API — its pure compose is reused for client head-sync. */
|
|
1513
|
-
head: Api$
|
|
1196
|
+
head: Api$3;
|
|
1197
|
+
/**
|
|
1198
|
+
* The OPTIONAL `data` reader. Present only when the `data` plugin is composed.
|
|
1199
|
+
* When present (and `router.mode() !== "ssg"`), navigation first tries the client
|
|
1200
|
+
* DATA path (match → `dataAt(path)` → `route.parse` → `route.render`); otherwise
|
|
1201
|
+
* it always uses HTML-over-fetch.
|
|
1202
|
+
*/
|
|
1203
|
+
dataAt?: SpaDataReader;
|
|
1514
1204
|
}
|
|
1515
1205
|
/** The single shared SPA kernel — pure factory over state/config/emit/deps. */
|
|
1516
1206
|
interface SpaKernel {
|
|
@@ -1540,76 +1230,774 @@ interface SpaKernel {
|
|
|
1540
1230
|
*/
|
|
1541
1231
|
register(component: ComponentDef): void;
|
|
1542
1232
|
/**
|
|
1543
|
-
* Process a navigation to `path`: fetch then swap then head-sync then emit.
|
|
1544
|
-
*
|
|
1545
|
-
* @param path - The target path to navigate to.
|
|
1546
|
-
* @returns void
|
|
1547
|
-
* @example
|
|
1548
|
-
* kernel.processNav("/about");
|
|
1233
|
+
* Process a navigation to `path`: fetch then swap then head-sync then emit.
|
|
1234
|
+
*
|
|
1235
|
+
* @param path - The target path to navigate to.
|
|
1236
|
+
* @returns void
|
|
1237
|
+
* @example
|
|
1238
|
+
* kernel.processNav("/about");
|
|
1239
|
+
*/
|
|
1240
|
+
processNav(path: string): void;
|
|
1241
|
+
/**
|
|
1242
|
+
* Query the swap region and mount components for matching elements.
|
|
1243
|
+
*
|
|
1244
|
+
* @returns void
|
|
1245
|
+
* @example
|
|
1246
|
+
* kernel.scan();
|
|
1247
|
+
*/
|
|
1248
|
+
scan(): void;
|
|
1249
|
+
/**
|
|
1250
|
+
* Tear down router listeners, run unmount/destroy, clear instances.
|
|
1251
|
+
*
|
|
1252
|
+
* @returns void
|
|
1253
|
+
* @example
|
|
1254
|
+
* kernel.dispose();
|
|
1255
|
+
*/
|
|
1256
|
+
dispose(): void;
|
|
1257
|
+
}
|
|
1258
|
+
/** Internal mutable state for the spa plugin (all kernel data lives here). */
|
|
1259
|
+
interface SpaState {
|
|
1260
|
+
/** Components registered by name (last-registered-wins). */
|
|
1261
|
+
registeredComponents: Map<string, ComponentDef>;
|
|
1262
|
+
/** Live component instances keyed by their bound element. */
|
|
1263
|
+
instances: Map<Element, ComponentInstance>;
|
|
1264
|
+
/** The current resolved URL (pathname + search). */
|
|
1265
|
+
currentUrl: string;
|
|
1266
|
+
/** Teardown handle for the attached router listeners (null when detached). */
|
|
1267
|
+
destroyRouter: (() => void) | null;
|
|
1268
|
+
/** Whether the browser runtime has been booted. */
|
|
1269
|
+
started: boolean;
|
|
1270
|
+
/** The single shared SPA kernel instance (null until onInit builds it). */
|
|
1271
|
+
kernel: SpaKernel | null;
|
|
1272
|
+
}
|
|
1273
|
+
/** Public API of the spa plugin (registration / control surface). */
|
|
1274
|
+
type SpaApi = {
|
|
1275
|
+
/**
|
|
1276
|
+
* Register a component definition for client mounting.
|
|
1277
|
+
*
|
|
1278
|
+
* @param component - The component definition created via `createComponent`.
|
|
1279
|
+
* @returns void
|
|
1280
|
+
* @example
|
|
1281
|
+
* app.spa.register(counter);
|
|
1282
|
+
*/
|
|
1283
|
+
register(component: ComponentDef): void;
|
|
1284
|
+
/**
|
|
1285
|
+
* Programmatically navigate to a path (client runtime; no-op without a DOM).
|
|
1286
|
+
*
|
|
1287
|
+
* @param path - Target path (pathname, optionally with search/hash).
|
|
1288
|
+
* @returns void
|
|
1289
|
+
* @example
|
|
1290
|
+
* app.spa.navigate("/about");
|
|
1291
|
+
*/
|
|
1292
|
+
navigate(path: string): void;
|
|
1293
|
+
/**
|
|
1294
|
+
* Read the current resolved URL.
|
|
1295
|
+
*
|
|
1296
|
+
* @returns The current pathname + search.
|
|
1297
|
+
* @example
|
|
1298
|
+
* const url = app.spa.current(); // "/about"
|
|
1299
|
+
*/
|
|
1300
|
+
current(): string;
|
|
1301
|
+
};
|
|
1302
|
+
declare namespace types_d_exports {
|
|
1303
|
+
export { Api$2 as Api, BuildCacheEntry, BuildEvents, BuildResult, Config$2 as Config, ExtractApi, OgFont, OgImageConfig, OgPngRenderer, PhaseContext, PhaseEmit, PhaseLog, PhaseName, PhaseRequire, RichOgInput, State$2 as State };
|
|
1304
|
+
}
|
|
1305
|
+
/**
|
|
1306
|
+
* Structural extraction of a plugin instance's public API from its `_phantom`
|
|
1307
|
+
* carrier (mirrors the kernel's non-exported `ExtractPluginApi`) so the
|
|
1308
|
+
* framework's generic `require` is assignable to {@link PhaseContext.require}.
|
|
1309
|
+
*
|
|
1310
|
+
* @example
|
|
1311
|
+
* ```ts
|
|
1312
|
+
* type ContentApi = ExtractApi<typeof contentPlugin>;
|
|
1313
|
+
* ```
|
|
1314
|
+
*/
|
|
1315
|
+
type ExtractApi<PluginCandidate> = PluginCandidate extends {
|
|
1316
|
+
readonly _phantom: {
|
|
1317
|
+
readonly api: infer PluginApi;
|
|
1318
|
+
};
|
|
1319
|
+
} ? PluginApi : never;
|
|
1320
|
+
/**
|
|
1321
|
+
* Minimal logger slice used by the pipeline and phases (the core `log` API).
|
|
1322
|
+
*
|
|
1323
|
+
* @example
|
|
1324
|
+
* ```ts
|
|
1325
|
+
* const log: PhaseLog = { info: () => {}, debug: () => {}, warn: () => {}, error: () => {} };
|
|
1326
|
+
* ```
|
|
1327
|
+
*/
|
|
1328
|
+
type PhaseLog = {
|
|
1329
|
+
/** Record an informational event. */info(event: string, data?: unknown): void; /** Record a debug event. */
|
|
1330
|
+
debug(event: string, data?: unknown): void; /** Record a warning event. */
|
|
1331
|
+
warn(event: string, data?: unknown): void; /** Record an error event. */
|
|
1332
|
+
error(event: string, data?: unknown): void;
|
|
1333
|
+
};
|
|
1334
|
+
/**
|
|
1335
|
+
* Payload map for the events `build` emits, used to type the `emit` closure
|
|
1336
|
+
* handed to the pipeline driver and phases.
|
|
1337
|
+
*
|
|
1338
|
+
* @example
|
|
1339
|
+
* ```ts
|
|
1340
|
+
* const emit: PhaseEmit = (name, payload) => kernel.emit(name, payload);
|
|
1341
|
+
* ```
|
|
1342
|
+
*/
|
|
1343
|
+
type BuildEvents = {
|
|
1344
|
+
/** Phase boundary marker (start, then done with durationMs). */"build:phase": {
|
|
1345
|
+
phase: PhaseName;
|
|
1346
|
+
status: "start" | "done";
|
|
1347
|
+
durationMs?: number;
|
|
1348
|
+
}; /** One successful-run summary. */
|
|
1349
|
+
"build:complete": {
|
|
1350
|
+
outDir: string;
|
|
1351
|
+
pageCount: number;
|
|
1352
|
+
durationMs: number;
|
|
1353
|
+
};
|
|
1354
|
+
};
|
|
1355
|
+
/** Strictly-typed emit closure for the build events (kernel overload form). */
|
|
1356
|
+
type PhaseEmit = EmitFn<BuildEvents>;
|
|
1357
|
+
/** Generic `require` closure for pulling dependency plugin APIs at run time. */
|
|
1358
|
+
type PhaseRequire = <PluginCandidate extends {
|
|
1359
|
+
readonly name: string;
|
|
1360
|
+
readonly spec: unknown;
|
|
1361
|
+
readonly _phantom: {
|
|
1362
|
+
readonly config: unknown;
|
|
1363
|
+
readonly state: unknown;
|
|
1364
|
+
readonly api: unknown;
|
|
1365
|
+
readonly events: Record<string, unknown>;
|
|
1366
|
+
};
|
|
1367
|
+
}>(plugin: PluginCandidate) => ExtractApi<PluginCandidate>;
|
|
1368
|
+
/**
|
|
1369
|
+
* The plugin-context slice the pipeline driver and every phase consume: the
|
|
1370
|
+
* mutable `state`, the resolved `config`/`global`, plus `require`/`emit`/`log`.
|
|
1371
|
+
* Typed to match the kernel's generic context so the framework execution
|
|
1372
|
+
* context is structurally assignable.
|
|
1373
|
+
*
|
|
1374
|
+
* @example
|
|
1375
|
+
* ```ts
|
|
1376
|
+
* const ctx: PhaseContext = { state, config, global, require, emit, log };
|
|
1377
|
+
* ```
|
|
1378
|
+
*/
|
|
1379
|
+
type PhaseContext = {
|
|
1380
|
+
/** Mutable per-run build state (caches + runId). */state: State$2; /** Resolved, frozen build config. */
|
|
1381
|
+
readonly config: Readonly<Config$2>; /** Global framework config (mode, etc.). */
|
|
1382
|
+
readonly global: Readonly<{
|
|
1383
|
+
mode: "production" | "development";
|
|
1384
|
+
}>; /** Resolve a depended-upon plugin instance to its public API. */
|
|
1385
|
+
require: PhaseRequire; /** Whether a plugin is registered (by name) — used to detect the OPTIONAL `data` plugin. */
|
|
1386
|
+
has: (name: string) => boolean; /** Emit a build event (notification-only). */
|
|
1387
|
+
emit: PhaseEmit; /** Structured logger (core `log` API). */
|
|
1388
|
+
readonly log: PhaseLog;
|
|
1389
|
+
};
|
|
1390
|
+
/**
|
|
1391
|
+
* Injectable PNG renderer for the og-images phase. Defaults to the real
|
|
1392
|
+
* Satori → resvg pipeline; unit tests inject a fake to assert hash-cache skip
|
|
1393
|
+
* and the `p-limit` bound without rasterizing real images.
|
|
1394
|
+
*
|
|
1395
|
+
* @example
|
|
1396
|
+
* ```ts
|
|
1397
|
+
* const render: OgPngRenderer = async () => new Uint8Array();
|
|
1398
|
+
* ```
|
|
1399
|
+
*/
|
|
1400
|
+
type OgPngRenderer = (input: RichOgInput) => Promise<Uint8Array>;
|
|
1401
|
+
/**
|
|
1402
|
+
* Rich input handed to a custom OG `render` hook for a single article card. Carries
|
|
1403
|
+
* the full article + site metadata so a consumer can compose any layout. Returned by
|
|
1404
|
+
* the framework, not authored by consumers directly.
|
|
1405
|
+
*
|
|
1406
|
+
* @example
|
|
1407
|
+
* ```ts
|
|
1408
|
+
* const input: RichOgInput = {
|
|
1409
|
+
* title: "Hello", description: "Intro", date: "2026-01-15", tags: ["a"],
|
|
1410
|
+
* locale: "en", siteName: "Blog", size: { width: 1200, height: 630 }
|
|
1411
|
+
* };
|
|
1412
|
+
* ```
|
|
1413
|
+
*/
|
|
1414
|
+
interface RichOgInput {
|
|
1415
|
+
/** Article title rendered into the card. */
|
|
1416
|
+
title: string;
|
|
1417
|
+
/** Article description / summary. */
|
|
1418
|
+
description: string;
|
|
1419
|
+
/** Publication date (ISO string from frontmatter). */
|
|
1420
|
+
date: string;
|
|
1421
|
+
/** Article tags. */
|
|
1422
|
+
tags: string[];
|
|
1423
|
+
/** Optional author name. */
|
|
1424
|
+
author?: string;
|
|
1425
|
+
/** Active locale for the card. */
|
|
1426
|
+
locale: string;
|
|
1427
|
+
/** Site name (from the site plugin / config). */
|
|
1428
|
+
siteName: string;
|
|
1429
|
+
/** Output dimensions for the card. */
|
|
1430
|
+
size: {
|
|
1431
|
+
width: number;
|
|
1432
|
+
height: number;
|
|
1433
|
+
};
|
|
1434
|
+
}
|
|
1435
|
+
/**
|
|
1436
|
+
* A single custom OG font entry. Each `path` is read to a Buffer ONCE per build and
|
|
1437
|
+
* handed to Satori. `weight`/`style` default to `400`/`"normal"` when omitted.
|
|
1438
|
+
*
|
|
1439
|
+
* @example
|
|
1440
|
+
* ```ts
|
|
1441
|
+
* const font: OgFont = { name: "Inter", path: "./fonts/Inter.ttf", weight: 600 };
|
|
1442
|
+
* ```
|
|
1443
|
+
*/
|
|
1444
|
+
interface OgFont {
|
|
1445
|
+
/** Font family name referenced by the rendered card. */
|
|
1446
|
+
name: string;
|
|
1447
|
+
/** Path to the .ttf/.otf/.woff file. */
|
|
1448
|
+
path: string;
|
|
1449
|
+
/** Numeric weight (defaults to 400). */
|
|
1450
|
+
weight?: number;
|
|
1451
|
+
/** Font style (defaults to "normal"). */
|
|
1452
|
+
style?: "normal" | "italic";
|
|
1453
|
+
}
|
|
1454
|
+
/**
|
|
1455
|
+
* Optional OG-image generation config. Omit the field (or set `false`) to disable.
|
|
1456
|
+
*
|
|
1457
|
+
* The optional `render` hook (`@jsxImportSource preact`) lets a consumer return a
|
|
1458
|
+
* Preact `VNode` for the card; the framework casts it to Satori's input at the single
|
|
1459
|
+
* render boundary. `fonts` supplies multiple named fonts loaded once per build.
|
|
1460
|
+
*
|
|
1461
|
+
* @example
|
|
1462
|
+
* ```ts
|
|
1463
|
+
* const og: OgImageConfig = { fontDir: "./fonts" };
|
|
1464
|
+
* ```
|
|
1465
|
+
*/
|
|
1466
|
+
interface OgImageConfig {
|
|
1467
|
+
/** Directory containing at least one .ttf/.otf/.woff font. Validated in onInit (void — config check only). */
|
|
1468
|
+
fontDir: string;
|
|
1469
|
+
/** Optional path to a custom OG template module. Falls back to the built-in template. */
|
|
1470
|
+
template?: string;
|
|
1471
|
+
/** Output dimensions. Defaults to 1200x630. */
|
|
1472
|
+
size?: {
|
|
1473
|
+
width: number;
|
|
1474
|
+
height: number;
|
|
1475
|
+
};
|
|
1476
|
+
/** Custom card renderer; returns a Preact `VNode` from the {@link RichOgInput}. */
|
|
1477
|
+
render?(input: RichOgInput): import("preact").VNode;
|
|
1478
|
+
/** Explicit named fonts loaded once per build (overrides the first-file scan). */
|
|
1479
|
+
fonts?: OgFont[];
|
|
1480
|
+
}
|
|
1481
|
+
/**
|
|
1482
|
+
* Public configuration for the `build` plugin. Flags give opt-in granularity over
|
|
1483
|
+
* individual outputs without rewriting the pipeline.
|
|
1484
|
+
*
|
|
1485
|
+
* @example
|
|
1486
|
+
* ```ts
|
|
1487
|
+
* const config: Config = { outDir: "./dist", minify: true, feeds: true, sitemap: true, images: true, ogImage: false };
|
|
1488
|
+
* ```
|
|
1489
|
+
*/
|
|
1490
|
+
type Config$2 = {
|
|
1491
|
+
/** Output directory for the built site. */outDir: string; /** Minify bundled CSS/JS. */
|
|
1492
|
+
minify: boolean; /** Generate RSS/Atom/JSON feeds. */
|
|
1493
|
+
feeds: boolean; /** Generate sitemap.xml + robots.txt. */
|
|
1494
|
+
sitemap: boolean; /** Optimize + copy content images. */
|
|
1495
|
+
images: boolean; /** OG-image generation. `false` (or omitted) disables it; an object enables and configures it. */
|
|
1496
|
+
ogImage: OgImageConfig | false; /** Auto-inject bundled `main.{css,js}` into rendered pages. Default `true`. */
|
|
1497
|
+
injectAssets?: boolean; /** Directory copied verbatim into `outDir` (skipped silently if absent). Default `"public"`. */
|
|
1498
|
+
publicDir?: string;
|
|
1499
|
+
/**
|
|
1500
|
+
* Emit `outDir/404.html`. `true` for the built-in default page, or
|
|
1501
|
+
* `{ route }` to supply the page's literal HTML body content (NOT a route
|
|
1502
|
+
* path/slug — the string is written into the 404 page verbatim). Default `false`.
|
|
1503
|
+
*/
|
|
1504
|
+
notFound?: boolean | {
|
|
1505
|
+
route?: string;
|
|
1506
|
+
}; /** Emit per-path i18n bare-path redirect HTML pages. Default `false`. */
|
|
1507
|
+
localeRedirects?: boolean; /** Authoritative client bundle entry path (overrides the conventional scan). */
|
|
1508
|
+
clientEntry?: string; /** HTML shell template with `<!--moku:head-->`/`<!--moku:body-->`/`<!--moku:assets-->` placeholders. */
|
|
1509
|
+
template?: string;
|
|
1510
|
+
};
|
|
1511
|
+
/**
|
|
1512
|
+
* A typed asset-manifest entry for one bundled asset kind (CSS or JS): a map of the
|
|
1513
|
+
* original entry basename to its hashed on-disk output path. Replaces the untyped
|
|
1514
|
+
* `Map<string, unknown>` reads when emitting `<link>`/`<script>` tags in pages.tsx.
|
|
1515
|
+
*
|
|
1516
|
+
* @example
|
|
1517
|
+
* ```ts
|
|
1518
|
+
* const entry: BuildCacheEntry = { "main.css": "assets/main-abc123.css" };
|
|
1519
|
+
* ```
|
|
1520
|
+
*/
|
|
1521
|
+
type BuildCacheEntry = Record<string, string>;
|
|
1522
|
+
/**
|
|
1523
|
+
* Per-run closure state for the `build` plugin. Holds caches and config only —
|
|
1524
|
+
* no domain data is duplicated here (pulled fresh via `ctx.require` each run).
|
|
1525
|
+
*
|
|
1526
|
+
* @example
|
|
1527
|
+
* ```ts
|
|
1528
|
+
* const state: State = { config, manifest: null, buildCache: new Map(), runId: null, ogImageHashCache: new Map() };
|
|
1529
|
+
* ```
|
|
1530
|
+
*/
|
|
1531
|
+
interface State$2 {
|
|
1532
|
+
/** Resolved, frozen config snapshot. */
|
|
1533
|
+
config: Config$2;
|
|
1534
|
+
/** Cached route manifest for the current run (populated in Phase 3 from router). */
|
|
1535
|
+
manifest: RouteDefinition[] | null;
|
|
1536
|
+
/** Per-run build artifacts (e.g. hashed CSS/JS asset paths from Phase 1). */
|
|
1537
|
+
buildCache: Map<string, unknown>;
|
|
1538
|
+
/** Unique id for the current run (timestamp/uuid) — injected as build-id meta. */
|
|
1539
|
+
runId: string | null;
|
|
1540
|
+
/**
|
|
1541
|
+
* Content-hash cache for OG images: slug -> sha256(title + template + size).
|
|
1542
|
+
* Loaded from `<outDir>/.cache/og-images.json` at the OG phase and written back,
|
|
1543
|
+
* so unchanged articles are skipped on the next run.
|
|
1544
|
+
*/
|
|
1545
|
+
ogImageHashCache: Map<string, string>;
|
|
1546
|
+
}
|
|
1547
|
+
/**
|
|
1548
|
+
* Ordered names of the build pipeline phases, in execution order.
|
|
1549
|
+
*
|
|
1550
|
+
* @example
|
|
1551
|
+
* ```ts
|
|
1552
|
+
* const phase: PhaseName = "bundle";
|
|
1553
|
+
* ```
|
|
1554
|
+
*/
|
|
1555
|
+
type PhaseName = "bundle" | "content" | "images" | "pages" | "feeds" | "sitemap" | "og-images" | "public" | "not-found" | "locale-redirects" | "root-index";
|
|
1556
|
+
/**
|
|
1557
|
+
* Result of a completed build run.
|
|
1558
|
+
*
|
|
1559
|
+
* @example
|
|
1560
|
+
* ```ts
|
|
1561
|
+
* const result: BuildResult = { outDir: "./dist", pageCount: 12, durationMs: 840 };
|
|
1562
|
+
* ```
|
|
1563
|
+
*/
|
|
1564
|
+
interface BuildResult {
|
|
1565
|
+
/** Resolved output directory the site was written to. */
|
|
1566
|
+
outDir: string;
|
|
1567
|
+
/** Number of route pages rendered. */
|
|
1568
|
+
pageCount: number;
|
|
1569
|
+
/** Total wall-clock duration of the run, in milliseconds. */
|
|
1570
|
+
durationMs: number;
|
|
1571
|
+
}
|
|
1572
|
+
/**
|
|
1573
|
+
* Public API surface mounted on `app.build`.
|
|
1574
|
+
*
|
|
1575
|
+
* @example
|
|
1576
|
+
* ```ts
|
|
1577
|
+
* const result = await app.build.run();
|
|
1578
|
+
* ```
|
|
1579
|
+
*/
|
|
1580
|
+
type Api$2 = {
|
|
1581
|
+
/**
|
|
1582
|
+
* Run the full SSG pipeline and write the site to disk.
|
|
1583
|
+
*
|
|
1584
|
+
* @param options - Optional run overrides.
|
|
1585
|
+
* @param options.outDir - Override the configured output directory for this run.
|
|
1586
|
+
* @returns The build result (outDir, pageCount, durationMs).
|
|
1587
|
+
* @example
|
|
1588
|
+
* ```ts
|
|
1589
|
+
* const result = await app.build.run({ outDir: "./preview" });
|
|
1590
|
+
* ```
|
|
1591
|
+
*/
|
|
1592
|
+
run(options?: {
|
|
1593
|
+
outDir?: string;
|
|
1594
|
+
}): Promise<BuildResult>;
|
|
1595
|
+
/**
|
|
1596
|
+
* List the phases in execution order (introspection / tooling).
|
|
1597
|
+
*
|
|
1598
|
+
* @returns The static ordered phase names.
|
|
1599
|
+
* @example
|
|
1600
|
+
* ```ts
|
|
1601
|
+
* const order = app.build.phases();
|
|
1602
|
+
* ```
|
|
1603
|
+
*/
|
|
1604
|
+
phases(): PhaseName[];
|
|
1605
|
+
};
|
|
1606
|
+
//#endregion
|
|
1607
|
+
//#region src/plugins/build/index.d.ts
|
|
1608
|
+
/**
|
|
1609
|
+
* Build plugin — the static-site-generation orchestrator. Renders every route to
|
|
1610
|
+
* `outDir`, and optionally emits feeds, a sitemap, optimized images, and OG
|
|
1611
|
+
* images. Depends on site, i18n, content, router, and head; emits `build:phase`.
|
|
1612
|
+
*
|
|
1613
|
+
* @example Configure the production build
|
|
1614
|
+
* ```ts
|
|
1615
|
+
* const app = createApp({
|
|
1616
|
+
* pluginConfigs: {
|
|
1617
|
+
* build: {
|
|
1618
|
+
* outDir: "dist",
|
|
1619
|
+
* minify: true,
|
|
1620
|
+
* feeds: true,
|
|
1621
|
+
* sitemap: true,
|
|
1622
|
+
* images: true,
|
|
1623
|
+
* ogImage: false // or an object to enable + configure OG-image generation
|
|
1624
|
+
* }
|
|
1625
|
+
* }
|
|
1626
|
+
* });
|
|
1627
|
+
* ```
|
|
1628
|
+
*/
|
|
1629
|
+
declare const buildPlugin: import("@moku-labs/core").PluginInstance<"build", Config$2, State$2, Api$2, {
|
|
1630
|
+
"build:phase": {
|
|
1631
|
+
phase: PhaseName;
|
|
1632
|
+
status: "start" | "done";
|
|
1633
|
+
durationMs?: number;
|
|
1634
|
+
};
|
|
1635
|
+
"build:complete": {
|
|
1636
|
+
outDir: string;
|
|
1637
|
+
pageCount: number;
|
|
1638
|
+
durationMs: number;
|
|
1639
|
+
};
|
|
1640
|
+
}> & Record<never, never>;
|
|
1641
|
+
declare namespace types_d_exports$1 {
|
|
1642
|
+
export { Api$1 as Api, Article, ArticleCard, ComputedFields, Config$1 as Config, ContentApiContext, ContentEvents, Frontmatter, State$1 as State };
|
|
1643
|
+
}
|
|
1644
|
+
/**
|
|
1645
|
+
* Configuration for the content plugin.
|
|
1646
|
+
*
|
|
1647
|
+
* @example
|
|
1648
|
+
* ```ts
|
|
1649
|
+
* { contentDir: "./src/content", trustedContent: false, shikiTheme: "github-dark" }
|
|
1650
|
+
* ```
|
|
1651
|
+
*/
|
|
1652
|
+
type Config$1 = {
|
|
1653
|
+
/** Absolute or project-relative path to the content directory. Validated in onInit. */contentDir: string;
|
|
1654
|
+
/**
|
|
1655
|
+
* SECURITY GATE. When false (the default), rehype-sanitize runs as the final
|
|
1656
|
+
* pipeline step. Set true ONLY for fully author-controlled Markdown — true
|
|
1657
|
+
* disables sanitize and trusts all raw HTML.
|
|
1658
|
+
*/
|
|
1659
|
+
trustedContent: boolean; /** Additional remark plugins, concatenated AFTER framework defaults. Defaults to []. */
|
|
1660
|
+
extraRemarkPlugins?: readonly Pluggable[]; /** Additional rehype plugins, concatenated after custom transforms, before Shiki + sanitize. Defaults to []. */
|
|
1661
|
+
extraRehypePlugins?: readonly Pluggable[]; /** Shiki theme name for syntax highlighting. Defaults to "github-dark". */
|
|
1662
|
+
shikiTheme?: string; /** Author applied to articles whose frontmatter omits author. Defaults to undefined. */
|
|
1663
|
+
defaultAuthor?: string;
|
|
1664
|
+
};
|
|
1665
|
+
/**
|
|
1666
|
+
* Internal mutable state for the content plugin.
|
|
1667
|
+
*
|
|
1668
|
+
* @example
|
|
1669
|
+
* ```ts
|
|
1670
|
+
* { processor: null, articles: new Map(), slugs: null, dirtyPaths: new Set() }
|
|
1671
|
+
* ```
|
|
1672
|
+
*/
|
|
1673
|
+
type State$1 = {
|
|
1674
|
+
/** Lazily-created unified processor singleton. null until first render()/loadAll(). */processor: Processor | null; /** Article cache keyed locale -> (slug -> Article). Starts empty. */
|
|
1675
|
+
articles: Map<string, Map<string, Article>>; /** Discovered, sorted slug list cached after first disk scan. null until first discovery. */
|
|
1676
|
+
slugs: string[] | null; /** Paths marked stale by invalidate(); next loadAll() re-reads only these. Starts empty. */
|
|
1677
|
+
dirtyPaths: Set<string>;
|
|
1678
|
+
};
|
|
1679
|
+
/**
|
|
1680
|
+
* YAML frontmatter parsed from each article file.
|
|
1681
|
+
*
|
|
1682
|
+
* @example
|
|
1683
|
+
* ```ts
|
|
1684
|
+
* { title: "Hello", date: "2026-01-15", description: "Intro", tags: [], language: "en" }
|
|
1685
|
+
* ```
|
|
1686
|
+
*/
|
|
1687
|
+
type Frontmatter = {
|
|
1688
|
+
/** Article title. Required. */title: string; /** ISO 8601 date string, e.g. "2026-01-15". Required. */
|
|
1689
|
+
date: string; /** Short summary used in cards, feeds, and meta description. Required. */
|
|
1690
|
+
description: string; /** Topic tags. Required (may be empty array). */
|
|
1691
|
+
tags: string[]; /** Source language code of this file. Required. */
|
|
1692
|
+
language: string; /** Draft flag. Excluded from output in production mode. Defaults to false. */
|
|
1693
|
+
draft?: boolean; /** Author name. Falls back to config.defaultAuthor when omitted. */
|
|
1694
|
+
author?: string;
|
|
1695
|
+
};
|
|
1696
|
+
/**
|
|
1697
|
+
* Fields computed by the pipeline (not authored in frontmatter).
|
|
1698
|
+
*
|
|
1699
|
+
* @example
|
|
1700
|
+
* ```ts
|
|
1701
|
+
* { slug: "hello", readingTime: 1, contentId: "hello", status: "published", wordCount: 42 }
|
|
1702
|
+
* ```
|
|
1703
|
+
*/
|
|
1704
|
+
type ComputedFields = {
|
|
1705
|
+
/** Article directory name. */slug: string; /** Reading time in minutes (ceiling, minimum 1). */
|
|
1706
|
+
readingTime: number; /** Stable content identifier (slug by default). */
|
|
1707
|
+
contentId: string; /** Derived publication status. */
|
|
1708
|
+
status: "published" | "draft"; /** Word count from the source body. */
|
|
1709
|
+
wordCount: number;
|
|
1710
|
+
};
|
|
1711
|
+
/**
|
|
1712
|
+
* A fully processed, render-ready article.
|
|
1713
|
+
*
|
|
1714
|
+
* @example
|
|
1715
|
+
* ```ts
|
|
1716
|
+
* { frontmatter, computed, html: "<p>…</p>", locale: "en", isFallback: false, url: "/en/hello/" }
|
|
1717
|
+
* ```
|
|
1718
|
+
*/
|
|
1719
|
+
type Article = {
|
|
1720
|
+
/** Parsed frontmatter. */frontmatter: Frontmatter; /** Pipeline-computed metadata. */
|
|
1721
|
+
computed: ComputedFields; /** Sanitized rendered HTML body. */
|
|
1722
|
+
html: string; /** Locale this Article instance represents (the requested locale, even on fallback). */
|
|
1723
|
+
locale: string; /** True when the default-locale file was used because the requested locale was missing. */
|
|
1724
|
+
isFallback: boolean; /** Canonical URL for this article in this locale. */
|
|
1725
|
+
url: string;
|
|
1726
|
+
};
|
|
1727
|
+
/**
|
|
1728
|
+
* Lightweight projection of Article for cards/lists.
|
|
1729
|
+
*
|
|
1730
|
+
* @example
|
|
1731
|
+
* ```ts
|
|
1732
|
+
* { contentId: "hello", status: "published", title: "Hello", date: "2026-01-15", description: "Intro", tags: [], readingTime: 1, url: "/en/hello/" }
|
|
1733
|
+
* ```
|
|
1734
|
+
*/
|
|
1735
|
+
type ArticleCard = {
|
|
1736
|
+
/** Stable content identifier. */contentId: string; /** Derived publication status. */
|
|
1737
|
+
status: "published" | "draft"; /** Article title. */
|
|
1738
|
+
title: string; /** ISO 8601 date string. */
|
|
1739
|
+
date: string; /** Short summary. */
|
|
1740
|
+
description: string; /** Topic tags. */
|
|
1741
|
+
tags: string[]; /** Reading time in minutes. */
|
|
1742
|
+
readingTime: number; /** Canonical URL for this article in this locale. */
|
|
1743
|
+
url: string;
|
|
1744
|
+
};
|
|
1745
|
+
/**
|
|
1746
|
+
* Notification-only events emitted by the content plugin.
|
|
1747
|
+
*
|
|
1748
|
+
* @example
|
|
1749
|
+
* ```ts
|
|
1750
|
+
* emit("content:ready", { locales: ["en"], articleCount: 3 });
|
|
1751
|
+
* ```
|
|
1752
|
+
*/
|
|
1753
|
+
type ContentEvents = {
|
|
1754
|
+
/** All articles loaded across locales. */"content:ready": {
|
|
1755
|
+
locales: readonly string[];
|
|
1756
|
+
articleCount: number;
|
|
1757
|
+
}; /** Article paths marked stale for dev rebuild. */
|
|
1758
|
+
"content:invalidated": {
|
|
1759
|
+
paths: readonly string[];
|
|
1760
|
+
};
|
|
1761
|
+
};
|
|
1762
|
+
/**
|
|
1763
|
+
* Kernel-free domain context handed to createContentApi by the wiring harness.
|
|
1764
|
+
* Carries ctx.state (mutable escape hatch), config, global, emit, and the
|
|
1765
|
+
* i18n-derived locale/url helpers — so api.ts stays free of createPlugin/ctx.
|
|
1766
|
+
*
|
|
1767
|
+
* @example
|
|
1768
|
+
* ```ts
|
|
1769
|
+
* const apiContext: ContentApiContext = { state, config, global, emit, locales, defaultLocale, articleToUrl };
|
|
1770
|
+
* ```
|
|
1771
|
+
*/
|
|
1772
|
+
type ContentApiContext = {
|
|
1773
|
+
/** Mutable plugin state (article cache + lazy processor). */state: State$1; /** Resolved plugin configuration. */
|
|
1774
|
+
config: Config$1; /** Global framework configuration (mode, etc.). */
|
|
1775
|
+
global: {
|
|
1776
|
+
mode: "production" | "development";
|
|
1777
|
+
}; /** Emit a registered content event. */
|
|
1778
|
+
emit: <K extends keyof ContentEvents>(event: K, payload: ContentEvents[K]) => void; /** Active locale codes from i18n. */
|
|
1779
|
+
locales: () => readonly string[]; /** Default locale code from i18n (fallback source). */
|
|
1780
|
+
defaultLocale: () => string; /** Build a canonical article URL for a locale + slug. */
|
|
1781
|
+
articleToUrl: (locale: string, slug: string) => string;
|
|
1782
|
+
};
|
|
1783
|
+
/**
|
|
1784
|
+
* Public API for the content plugin.
|
|
1785
|
+
*
|
|
1786
|
+
* @example
|
|
1787
|
+
* ```ts
|
|
1788
|
+
* const map = await app.content.loadAll();
|
|
1789
|
+
* ```
|
|
1790
|
+
*/
|
|
1791
|
+
type Api$1 = {
|
|
1792
|
+
/**
|
|
1793
|
+
* Load every article across every active locale, returning a locale-keyed
|
|
1794
|
+
* map of date-descending Article arrays. Emits content:ready.
|
|
1795
|
+
*/
|
|
1796
|
+
loadAll(): Promise<Map<string, Article[]>>;
|
|
1797
|
+
/**
|
|
1798
|
+
* Resolve and render a single article for one locale, with locale fallback.
|
|
1799
|
+
*
|
|
1800
|
+
* @param slug - Article directory name.
|
|
1801
|
+
* @param locale - Requested locale code.
|
|
1802
|
+
*/
|
|
1803
|
+
load(slug: string, locale: string): Promise<Article>;
|
|
1804
|
+
/**
|
|
1805
|
+
* Render a raw Markdown string to HTML through the full pipeline.
|
|
1806
|
+
*
|
|
1807
|
+
* @param md - Raw Markdown source.
|
|
1808
|
+
*/
|
|
1809
|
+
renderMarkdown(md: string): Promise<string>;
|
|
1810
|
+
/**
|
|
1811
|
+
* Mark file paths stale for incremental dev rebuilds. Emits content:invalidated.
|
|
1812
|
+
*
|
|
1813
|
+
* @param paths - File paths to invalidate.
|
|
1814
|
+
*/
|
|
1815
|
+
invalidate(paths: readonly string[]): void;
|
|
1816
|
+
/**
|
|
1817
|
+
* Project a full Article to a lightweight ArticleCard for list/grid rendering.
|
|
1818
|
+
*
|
|
1819
|
+
* @param article - The source article.
|
|
1820
|
+
*/
|
|
1821
|
+
articleToCard(article: Article): ArticleCard;
|
|
1822
|
+
};
|
|
1823
|
+
//#endregion
|
|
1824
|
+
//#region src/plugins/content/index.d.ts
|
|
1825
|
+
/**
|
|
1826
|
+
* Content plugin — Markdown pipeline: discovers files, parses frontmatter, renders
|
|
1827
|
+
* to sanitized HTML (rehype-sanitize unless `trustedContent`), and exposes a
|
|
1828
|
+
* locale-keyed Article model. Depends on i18n; emits `content:ready` and
|
|
1829
|
+
* `content:invalidated`.
|
|
1830
|
+
*
|
|
1831
|
+
* @example Point at a content directory and pick a Shiki theme
|
|
1832
|
+
* ```ts
|
|
1833
|
+
* const app = createApp({
|
|
1834
|
+
* pluginConfigs: {
|
|
1835
|
+
* content: {
|
|
1836
|
+
* contentDir: "./content",
|
|
1837
|
+
* shikiTheme: "github-dark",
|
|
1838
|
+
* defaultAuthor: "Ada Lovelace"
|
|
1839
|
+
* // trustedContent: true // ONLY for fully author-controlled Markdown — disables sanitize
|
|
1840
|
+
* }
|
|
1841
|
+
* }
|
|
1842
|
+
* });
|
|
1843
|
+
* ```
|
|
1844
|
+
*/
|
|
1845
|
+
declare const contentPlugin: import("@moku-labs/core").PluginInstance<"content", Config$1, State$1, Api$1, {
|
|
1846
|
+
"content:ready": {
|
|
1847
|
+
locales: readonly string[];
|
|
1848
|
+
articleCount: number;
|
|
1849
|
+
};
|
|
1850
|
+
"content:invalidated": {
|
|
1851
|
+
paths: readonly string[];
|
|
1852
|
+
};
|
|
1853
|
+
}> & Record<never, never>;
|
|
1854
|
+
declare namespace types_d_exports$2 {
|
|
1855
|
+
export { DataConfig, DataEntry, DataProvider, DataState, DataWriteSummary };
|
|
1856
|
+
}
|
|
1857
|
+
/**
|
|
1858
|
+
* @file data plugin — type definitions (Standard tier).
|
|
1859
|
+
*
|
|
1860
|
+
* The `data` plugin is the **agnostic data provider** for the SSG→DATA→SPA pattern.
|
|
1861
|
+
* It owns ONE thing: the contract `page path → persisted JSON file`. It knows
|
|
1862
|
+
* NOTHING about what the data *is* — no domain types appear here. A route decides
|
|
1863
|
+
* its own data shape (`load`'s return) and its own validation (`route.parse`).
|
|
1864
|
+
*
|
|
1865
|
+
* - **Node (build):** `write(entries)` persists one JSON file per page, keyed by
|
|
1866
|
+
* the page's URL via {@link DataProvider.fileFor}. `build` supplies the entries
|
|
1867
|
+
* (it already expanded the routes), so there is no duplicate expansion here.
|
|
1868
|
+
* - **Browser (runtime):** `at(path)` fetches + caches that file as `unknown`; the
|
|
1869
|
+
* route's `parse` validates it into the route's data type before `render`.
|
|
1870
|
+
*
|
|
1871
|
+
* The Node-only file-writing code (`node:fs`) is isolated behind a lazy `import()`
|
|
1872
|
+
* inside `write()`, so composing `data` in a browser app keeps the bundle free of
|
|
1873
|
+
* `node:*`.
|
|
1874
|
+
*/
|
|
1875
|
+
/**
|
|
1876
|
+
* Configuration for {@link dataPlugin}. All fields have defaults (see `./config`).
|
|
1877
|
+
*
|
|
1878
|
+
* @example
|
|
1879
|
+
* ```ts
|
|
1880
|
+
* const cfg: DataConfig = { outputDir: "_data", baseUrl: "/_data/" };
|
|
1881
|
+
* ```
|
|
1882
|
+
*/
|
|
1883
|
+
type DataConfig = {
|
|
1884
|
+
/**
|
|
1885
|
+
* WRITE side (Node): output subdir relative to the build `outDir`, a filesystem
|
|
1886
|
+
* path where `write()` persists the per-page JSON. Default `"_data"`.
|
|
1549
1887
|
*/
|
|
1550
|
-
|
|
1888
|
+
outputDir: string;
|
|
1551
1889
|
/**
|
|
1552
|
-
*
|
|
1553
|
-
*
|
|
1554
|
-
*
|
|
1555
|
-
* @example
|
|
1556
|
-
* kernel.scan();
|
|
1890
|
+
* READ side (browser): site-root-relative URL the client fetches the per-page
|
|
1891
|
+
* JSON from. A different domain from {@link DataConfig.outputDir} (a filesystem
|
|
1892
|
+
* path); keep consistent (`"/" + trim(outputDir) + "/"`). Default `"/_data/"`.
|
|
1557
1893
|
*/
|
|
1558
|
-
|
|
1894
|
+
baseUrl: string;
|
|
1895
|
+
};
|
|
1896
|
+
/** One page's data to persist — `build` produces these from its route expansion. */
|
|
1897
|
+
interface DataEntry {
|
|
1898
|
+
/** The page's URL path (e.g. `/en/hello/`); maps to a file via {@link DataProvider.fileFor}. */
|
|
1899
|
+
path: string;
|
|
1900
|
+
/** The serializable data for this page (the route's `load`/projection output). */
|
|
1901
|
+
data: unknown;
|
|
1902
|
+
}
|
|
1903
|
+
/** Summary returned by {@link DataProvider.write} and cached in state. */
|
|
1904
|
+
interface DataWriteSummary {
|
|
1905
|
+
/** Number of per-page JSON files written. */
|
|
1906
|
+
fileCount: number;
|
|
1907
|
+
/** Total bytes written across all files. */
|
|
1908
|
+
bytes: number;
|
|
1909
|
+
/** The written file paths, relative to the build `outDir`. */
|
|
1910
|
+
files: string[];
|
|
1911
|
+
}
|
|
1912
|
+
/**
|
|
1913
|
+
* Internal data state. `lastWrite` records the most recent `write()` (Node);
|
|
1914
|
+
* `cache` memoizes fetched per-path data (browser, lazy). Both empty until their
|
|
1915
|
+
* respective side first runs.
|
|
1916
|
+
*/
|
|
1917
|
+
interface DataState {
|
|
1918
|
+
/** Result of the last `write()`, or `null` if it has not run yet (Node). */
|
|
1919
|
+
lastWrite: DataWriteSummary | null;
|
|
1920
|
+
/** Per-path fetched data, cached after the first `at(path)` (browser). */
|
|
1921
|
+
cache: Map<string, unknown>;
|
|
1922
|
+
}
|
|
1923
|
+
/**
|
|
1924
|
+
* Public API mounted at `app.data` — the agnostic data provider. `write()` is the
|
|
1925
|
+
* Node persist side; `at()` is the browser read side; `urlFor`/`fileFor` are the
|
|
1926
|
+
* pure URL convention shared by both so the written file and fetched URL can never
|
|
1927
|
+
* drift.
|
|
1928
|
+
*
|
|
1929
|
+
* @example
|
|
1930
|
+
* ```ts
|
|
1931
|
+
* // Node build (build supplies the entries it already expanded):
|
|
1932
|
+
* await app.data.write([{ path: "/en/hello/", data: article }]);
|
|
1933
|
+
*
|
|
1934
|
+
* // Browser (inside spa nav): fetch the page's data, then route.parse validates it:
|
|
1935
|
+
* const raw = await app.data.at("/en/hello/"); // unknown | null
|
|
1936
|
+
* ```
|
|
1937
|
+
*/
|
|
1938
|
+
type DataProvider = {
|
|
1559
1939
|
/**
|
|
1560
|
-
*
|
|
1940
|
+
* READ (browser) — fetch (and cache) the persisted data for a page path from
|
|
1941
|
+
* `config.baseUrl`. Returns the raw parsed JSON as `unknown` (the caller's
|
|
1942
|
+
* `route.parse` validates it), or `null` if the fetch/parse fails.
|
|
1561
1943
|
*
|
|
1562
|
-
* @
|
|
1563
|
-
* @
|
|
1564
|
-
* kernel.dispose();
|
|
1944
|
+
* @param path - The page URL path (e.g. `/en/hello/`).
|
|
1945
|
+
* @returns The page's raw data, or `null` on failure.
|
|
1565
1946
|
*/
|
|
1566
|
-
|
|
1567
|
-
}
|
|
1568
|
-
/** Internal mutable state for the spa plugin (all kernel data lives here). */
|
|
1569
|
-
interface SpaState {
|
|
1570
|
-
/** Components registered by name (last-registered-wins). */
|
|
1571
|
-
registeredComponents: Map<string, ComponentDef>;
|
|
1572
|
-
/** Live component instances keyed by their bound element. */
|
|
1573
|
-
instances: Map<Element, ComponentInstance>;
|
|
1574
|
-
/** The current resolved URL (pathname + search). */
|
|
1575
|
-
currentUrl: string;
|
|
1576
|
-
/** Teardown handle for the attached router listeners (null when detached). */
|
|
1577
|
-
destroyRouter: (() => void) | null;
|
|
1578
|
-
/** Whether the browser runtime has been booted. */
|
|
1579
|
-
started: boolean;
|
|
1580
|
-
/** The single shared SPA kernel instance (null until onInit builds it). */
|
|
1581
|
-
kernel: SpaKernel | null;
|
|
1582
|
-
}
|
|
1583
|
-
/** Public API of the spa plugin (registration / control surface). */
|
|
1584
|
-
type SpaApi = {
|
|
1947
|
+
at(path: string): Promise<unknown | null>;
|
|
1585
1948
|
/**
|
|
1586
|
-
*
|
|
1949
|
+
* WRITE (Node) — persist one JSON file per entry, keyed by page path via
|
|
1950
|
+
* {@link DataProvider.fileFor}. Called by `build` after it expands routes (no
|
|
1951
|
+
* duplicate expansion). Lazily loads its `node:fs` writer, so it never
|
|
1952
|
+
* contaminates a browser bundle.
|
|
1587
1953
|
*
|
|
1588
|
-
* @param
|
|
1589
|
-
* @
|
|
1590
|
-
* @
|
|
1591
|
-
*
|
|
1954
|
+
* @param entries - The per-page data to persist.
|
|
1955
|
+
* @param options - Optional overrides.
|
|
1956
|
+
* @param options.outDir - Build output directory to write under (default `./dist`).
|
|
1957
|
+
* @returns A summary of the written files.
|
|
1592
1958
|
*/
|
|
1593
|
-
|
|
1959
|
+
write(entries: readonly DataEntry[], options?: {
|
|
1960
|
+
outDir?: string;
|
|
1961
|
+
}): Promise<DataWriteSummary>;
|
|
1594
1962
|
/**
|
|
1595
|
-
*
|
|
1963
|
+
* PURE — the browser fetch URL for a page path (e.g. `/en/hello/` →
|
|
1964
|
+
* `/_data/en/hello/index.json`). Shared with {@link DataProvider.fileFor}.
|
|
1596
1965
|
*
|
|
1597
|
-
* @param path -
|
|
1598
|
-
* @returns
|
|
1599
|
-
* @example
|
|
1600
|
-
* app.spa.navigate("/about");
|
|
1966
|
+
* @param path - The page URL path.
|
|
1967
|
+
* @returns The site-root-relative data URL.
|
|
1601
1968
|
*/
|
|
1602
|
-
|
|
1969
|
+
urlFor(path: string): string;
|
|
1603
1970
|
/**
|
|
1604
|
-
*
|
|
1971
|
+
* PURE — the `outDir`-relative file path for a page path (e.g. `/en/hello/` →
|
|
1972
|
+
* `_data/en/hello/index.json`). Shared with {@link DataProvider.urlFor}.
|
|
1605
1973
|
*
|
|
1606
|
-
* @
|
|
1607
|
-
* @
|
|
1608
|
-
* const url = app.spa.current(); // "/about"
|
|
1974
|
+
* @param path - The page URL path.
|
|
1975
|
+
* @returns The output-relative file path.
|
|
1609
1976
|
*/
|
|
1610
|
-
|
|
1977
|
+
fileFor(path: string): string;
|
|
1611
1978
|
};
|
|
1612
|
-
|
|
1979
|
+
//#endregion
|
|
1980
|
+
//#region src/plugins/data/index.d.ts
|
|
1981
|
+
/**
|
|
1982
|
+
* Data plugin — the agnostic data provider. Mounts `write(entries)` (Node persist),
|
|
1983
|
+
* `at(path)` (browser read), and the pure `urlFor`/`fileFor` convention at `app.data`.
|
|
1984
|
+
*
|
|
1985
|
+
* @example
|
|
1986
|
+
* ```ts
|
|
1987
|
+
* // Node build: `build` calls app.data.write(...) during its pages phase when
|
|
1988
|
+
* // router.mode !== "ssg". Just compose the plugin:
|
|
1989
|
+
* const app = createApp({
|
|
1990
|
+
* plugins: [dataPlugin, contentPlugin, buildPlugin],
|
|
1991
|
+
* pluginConfigs: { content: { contentDir: "./content" }, router: { routes, mode: "hybrid" } }
|
|
1992
|
+
* });
|
|
1993
|
+
* await app.start();
|
|
1994
|
+
* await app.build.run(); // writes HTML + per-page data sidecars
|
|
1995
|
+
*
|
|
1996
|
+
* // Browser app: compose `dataPlugin` too; spa fetches via app.data.at(path) on nav.
|
|
1997
|
+
* ```
|
|
1998
|
+
*/
|
|
1999
|
+
declare const dataPlugin: import("@moku-labs/core").PluginInstance<"data", DataConfig, DataState, DataProvider, {}> & Record<never, never>;
|
|
2000
|
+
declare namespace types_d_exports$3 {
|
|
1613
2001
|
export { Api, Config, DeployErrorCode, DeployInitOptions, DeployResult, DeployRunOptions, InitResult, SpawnFunction, SpawnOptions, SpawnedProcess, State, WranglerErrorKind };
|
|
1614
2002
|
}
|
|
1615
2003
|
/**
|
|
@@ -1797,6 +2185,24 @@ type Api = {
|
|
|
1797
2185
|
* Depends: site. Emits: deploy:complete.
|
|
1798
2186
|
* @see README.md
|
|
1799
2187
|
*/
|
|
2188
|
+
/**
|
|
2189
|
+
* Deploy plugin — ships the built `outDir` to Cloudflare Pages via the injectable
|
|
2190
|
+
* wrangler subprocess, with entropy-gated secret scrubbing of logged output.
|
|
2191
|
+
* Depends on site; emits `deploy:complete`.
|
|
2192
|
+
*
|
|
2193
|
+
* @example Configure the deploy target
|
|
2194
|
+
* ```ts
|
|
2195
|
+
* const app = createApp({
|
|
2196
|
+
* pluginConfigs: {
|
|
2197
|
+
* deploy: {
|
|
2198
|
+
* target: "cloudflare-pages",
|
|
2199
|
+
* outDir: "dist",
|
|
2200
|
+
* productionBranch: "main"
|
|
2201
|
+
* }
|
|
2202
|
+
* }
|
|
2203
|
+
* });
|
|
2204
|
+
* ```
|
|
2205
|
+
*/
|
|
1800
2206
|
declare const deployPlugin: import("@moku-labs/core").PluginInstance<"deploy", Config, State, Api, {
|
|
1801
2207
|
"deploy:complete": {
|
|
1802
2208
|
url: string;
|
|
@@ -1806,31 +2212,6 @@ declare const deployPlugin: import("@moku-labs/core").PluginInstance<"deploy", C
|
|
|
1806
2212
|
};
|
|
1807
2213
|
}> & Record<never, never>;
|
|
1808
2214
|
//#endregion
|
|
1809
|
-
//#region src/plugins/build/index.d.ts
|
|
1810
|
-
declare const buildPlugin: import("@moku-labs/core").PluginInstance<"build", Config$1, State$1, Api$1, {
|
|
1811
|
-
"build:phase": {
|
|
1812
|
-
phase: PhaseName;
|
|
1813
|
-
status: "start" | "done";
|
|
1814
|
-
durationMs?: number;
|
|
1815
|
-
};
|
|
1816
|
-
"build:complete": {
|
|
1817
|
-
outDir: string;
|
|
1818
|
-
pageCount: number;
|
|
1819
|
-
durationMs: number;
|
|
1820
|
-
};
|
|
1821
|
-
}> & Record<never, never>;
|
|
1822
|
-
//#endregion
|
|
1823
|
-
//#region src/plugins/content/index.d.ts
|
|
1824
|
-
declare const contentPlugin: import("@moku-labs/core").PluginInstance<"content", Config$3, State$3, Api$3, {
|
|
1825
|
-
"content:ready": {
|
|
1826
|
-
locales: readonly string[];
|
|
1827
|
-
articleCount: number;
|
|
1828
|
-
};
|
|
1829
|
-
"content:invalidated": {
|
|
1830
|
-
paths: readonly string[];
|
|
1831
|
-
};
|
|
1832
|
-
}> & Record<never, never>;
|
|
1833
|
-
//#endregion
|
|
1834
2215
|
//#region src/plugins/head/primitives.d.ts
|
|
1835
2216
|
/**
|
|
1836
2217
|
* Build a `<meta name=… content=…>` descriptor.
|
|
@@ -1910,7 +2291,26 @@ declare function feedLink(title: string, url: string, type?: string): HeadElemen
|
|
|
1910
2291
|
declare function buildArticleHead(articleMeta: ArticleMeta, canonicalUrl: string): HeadElement[];
|
|
1911
2292
|
//#endregion
|
|
1912
2293
|
//#region src/plugins/head/index.d.ts
|
|
1913
|
-
|
|
2294
|
+
/**
|
|
2295
|
+
* Head plugin — composes per-route `<head>` metadata (title template, Open Graph,
|
|
2296
|
+
* Twitter cards, canonical, hreflang). Use the re-exported SEO primitives
|
|
2297
|
+
* ({@link meta}, {@link og}, {@link twitter}, …) inside a route's `.head()`.
|
|
2298
|
+
* Depends on site, i18n, and router.
|
|
2299
|
+
*
|
|
2300
|
+
* @example Set global head defaults
|
|
2301
|
+
* ```ts
|
|
2302
|
+
* const app = createApp({
|
|
2303
|
+
* pluginConfigs: {
|
|
2304
|
+
* head: {
|
|
2305
|
+
* titleTemplate: "%s — My Blog",
|
|
2306
|
+
* twitterCard: "summary_large_image",
|
|
2307
|
+
* twitterHandle: "@moku_labs"
|
|
2308
|
+
* }
|
|
2309
|
+
* }
|
|
2310
|
+
* });
|
|
2311
|
+
* ```
|
|
2312
|
+
*/
|
|
2313
|
+
declare const headPlugin: import("@moku-labs/core").PluginInstance<"head", Config$3, State$3, Api$3, {}> & {
|
|
1914
2314
|
meta: typeof meta;
|
|
1915
2315
|
og: typeof og;
|
|
1916
2316
|
twitter: typeof twitter;
|
|
@@ -1922,6 +2322,25 @@ declare const headPlugin: import("@moku-labs/core").PluginInstance<"head", Confi
|
|
|
1922
2322
|
};
|
|
1923
2323
|
//#endregion
|
|
1924
2324
|
//#region src/plugins/i18n/index.d.ts
|
|
2325
|
+
/**
|
|
2326
|
+
* Internationalization plugin — locale registry plus a flat translation helper
|
|
2327
|
+
* with default-locale fallback. Pure config-as-data (no state or events);
|
|
2328
|
+
* consumed read-only by content, router, head, and build.
|
|
2329
|
+
*
|
|
2330
|
+
* @example Register locales and translations
|
|
2331
|
+
* ```ts
|
|
2332
|
+
* const app = createApp({
|
|
2333
|
+
* pluginConfigs: {
|
|
2334
|
+
* i18n: {
|
|
2335
|
+
* locales: ["en", "uk"],
|
|
2336
|
+
* defaultLocale: "en",
|
|
2337
|
+
* localeNames: { en: "English", uk: "Українська" },
|
|
2338
|
+
* translations: { uk: { "nav.home": "Головна" } }
|
|
2339
|
+
* }
|
|
2340
|
+
* }
|
|
2341
|
+
* });
|
|
2342
|
+
* ```
|
|
2343
|
+
*/
|
|
1925
2344
|
declare const i18nPlugin: import("@moku-labs/core").PluginInstance<"i18n", Config$5, Record<string, never>, Api$5, {}> & Record<never, never>;
|
|
1926
2345
|
//#endregion
|
|
1927
2346
|
//#region src/plugins/log/index.d.ts
|
|
@@ -1967,15 +2386,74 @@ declare function route<P extends string>(pattern: P): RouteBuilder<RouteState<P>
|
|
|
1967
2386
|
declare function defineRoutes<T extends RouteMap>(routes: T): T;
|
|
1968
2387
|
//#endregion
|
|
1969
2388
|
//#region src/plugins/router/index.d.ts
|
|
2389
|
+
/**
|
|
2390
|
+
* Router plugin — typed, named route definitions with locale-aware URL generation
|
|
2391
|
+
* and matching. Author routes with {@link route} + {@link defineRoutes}. Depends
|
|
2392
|
+
* on site (base URL) and i18n (locales).
|
|
2393
|
+
*
|
|
2394
|
+
* @example Define routes and choose a render mode
|
|
2395
|
+
* ```ts
|
|
2396
|
+
* const app = createApp({
|
|
2397
|
+
* pluginConfigs: {
|
|
2398
|
+
* router: {
|
|
2399
|
+
* routes: defineRoutes({
|
|
2400
|
+
* home: route("/"),
|
|
2401
|
+
* article: route("/blog/{slug}/")
|
|
2402
|
+
* }),
|
|
2403
|
+
* mode: "hybrid" // "ssg" | "spa" | "hybrid" (default)
|
|
2404
|
+
* }
|
|
2405
|
+
* }
|
|
2406
|
+
* });
|
|
2407
|
+
* ```
|
|
2408
|
+
*/
|
|
1970
2409
|
declare const routerPlugin: import("@moku-labs/core").PluginInstance<"router", RouterConfig, RouterState, RouterApi, {}> & {
|
|
1971
2410
|
route: typeof route;
|
|
1972
2411
|
defineRoutes: typeof defineRoutes;
|
|
1973
2412
|
};
|
|
1974
2413
|
//#endregion
|
|
1975
2414
|
//#region src/plugins/site/index.d.ts
|
|
2415
|
+
/**
|
|
2416
|
+
* Site plugin — holds global, frozen site metadata (name, url, author,
|
|
2417
|
+
* description) and builds canonical URLs. Consumed by router, head, and build.
|
|
2418
|
+
* `name` and `url` must be non-empty (validated at `onInit`).
|
|
2419
|
+
*
|
|
2420
|
+
* @example Set your site identity
|
|
2421
|
+
* ```ts
|
|
2422
|
+
* const app = createApp({
|
|
2423
|
+
* pluginConfigs: {
|
|
2424
|
+
* site: {
|
|
2425
|
+
* name: "My Blog",
|
|
2426
|
+
* url: "https://blog.dev",
|
|
2427
|
+
* author: "Ada Lovelace",
|
|
2428
|
+
* description: "Notes on computing"
|
|
2429
|
+
* }
|
|
2430
|
+
* }
|
|
2431
|
+
* });
|
|
2432
|
+
* ```
|
|
2433
|
+
*/
|
|
1976
2434
|
declare const sitePlugin: import("@moku-labs/core").PluginInstance<"site", Config$6, Record<string, never>, Api$6, {}> & Record<never, never>;
|
|
1977
2435
|
//#endregion
|
|
1978
2436
|
//#region src/plugins/spa/index.d.ts
|
|
2437
|
+
/**
|
|
2438
|
+
* SPA plugin — progressive client-side navigation layered over the static site:
|
|
2439
|
+
* swaps a page region on navigation, with an optional progress bar and View
|
|
2440
|
+
* Transitions. Register interactive islands with {@link createComponent}. Depends
|
|
2441
|
+
* on router and head; emits `spa:navigate`, `spa:navigated`, `spa:component-mount`,
|
|
2442
|
+
* and `spa:component-unmount`.
|
|
2443
|
+
*
|
|
2444
|
+
* @example Enable view transitions and a custom swap region
|
|
2445
|
+
* ```ts
|
|
2446
|
+
* const app = createApp({
|
|
2447
|
+
* pluginConfigs: {
|
|
2448
|
+
* spa: {
|
|
2449
|
+
* swapSelector: "main > section",
|
|
2450
|
+
* viewTransitions: true,
|
|
2451
|
+
* progressBar: true
|
|
2452
|
+
* }
|
|
2453
|
+
* }
|
|
2454
|
+
* });
|
|
2455
|
+
* ```
|
|
2456
|
+
*/
|
|
1979
2457
|
declare const spaPlugin: import("@moku-labs/core").PluginInstance<"spa", SpaConfig, SpaState, SpaApi, {
|
|
1980
2458
|
"spa:navigate": {
|
|
1981
2459
|
from: string;
|
|
@@ -1994,115 +2472,150 @@ declare const spaPlugin: import("@moku-labs/core").PluginInstance<"spa", SpaConf
|
|
|
1994
2472
|
};
|
|
1995
2473
|
}> & Record<never, never>;
|
|
1996
2474
|
//#endregion
|
|
2475
|
+
//#region src/plugins/env/providers.d.ts
|
|
2476
|
+
/**
|
|
2477
|
+
* A zero-dependency `.env`-style provider that re-reads and re-parses the file
|
|
2478
|
+
* from disk on every `load()`. Missing file resolves to `{}` (optional
|
|
2479
|
+
* overrides). Strips a single outer quote pair; does not strip trailing inline
|
|
2480
|
+
* comments on unquoted values.
|
|
2481
|
+
*
|
|
2482
|
+
* @param path - Path to the dotenv file. Defaults to `.env.local`.
|
|
2483
|
+
* @returns An {@link EnvProvider} named `dotenv:<path>` that reads fresh per call.
|
|
2484
|
+
* @example
|
|
2485
|
+
* ```ts
|
|
2486
|
+
* const provider = dotenv(".env.local");
|
|
2487
|
+
* provider.load(); // { PUBLIC_API_URL: "/api", ... }
|
|
2488
|
+
* ```
|
|
2489
|
+
*/
|
|
2490
|
+
declare function dotenv(path?: string): EnvProvider;
|
|
2491
|
+
/**
|
|
2492
|
+
* A provider that returns a shallow copy of `process.env` at `load()` time.
|
|
2493
|
+
*
|
|
2494
|
+
* @returns An {@link EnvProvider} named `process-env`.
|
|
2495
|
+
* @example
|
|
2496
|
+
* ```ts
|
|
2497
|
+
* const provider = processEnv();
|
|
2498
|
+
* provider.load().HOME; // current process value
|
|
2499
|
+
* ```
|
|
2500
|
+
*/
|
|
2501
|
+
declare function processEnv(): EnvProvider;
|
|
2502
|
+
/**
|
|
2503
|
+
* A provider that reads live, per-request Cloudflare bindings from
|
|
2504
|
+
* `globalThis.__CLOUDFLARE_ENV__` at `load()` time (`?? {}` when absent). Never
|
|
2505
|
+
* caches the binding object; the consumer owns the global's request lifecycle.
|
|
2506
|
+
*
|
|
2507
|
+
* @returns An {@link EnvProvider} named `cloudflare`.
|
|
2508
|
+
* @example
|
|
2509
|
+
* ```ts
|
|
2510
|
+
* globalThis.__CLOUDFLARE_ENV__ = env; // set by the request handler
|
|
2511
|
+
* const provider = cloudflareBindings();
|
|
2512
|
+
* provider.load(); // reads the current request's bindings
|
|
2513
|
+
* ```
|
|
2514
|
+
*/
|
|
2515
|
+
declare function cloudflareBindings(): EnvProvider;
|
|
2516
|
+
//#endregion
|
|
1997
2517
|
//#region src/index.d.ts
|
|
2518
|
+
/**
|
|
2519
|
+
* Create and initialize a `@moku-labs/web` application — the Layer-3 entry point.
|
|
2520
|
+
* Your overrides are merged over the framework defaults through the 4-level config
|
|
2521
|
+
* cascade, every plugin's lifecycle runs, and a fully-typed, frozen app is returned.
|
|
2522
|
+
*
|
|
2523
|
+
* The defaults are the isomorphic plugin set (`site`, `i18n`, `router`, `head`,
|
|
2524
|
+
* `spa` + `log`/`env` core). Add the Node-only plugins for an SSG build:
|
|
2525
|
+
* `createApp({ plugins: [contentPlugin, buildPlugin, deployPlugin] })`.
|
|
2526
|
+
*
|
|
2527
|
+
* @param options - Optional configuration:
|
|
2528
|
+
* - `pluginConfigs` — per-plugin overrides, keyed by plugin name.
|
|
2529
|
+
* - `config` — global framework config (e.g. `{ mode: "development" }`).
|
|
2530
|
+
* - `plugins` — extra plugins (Node-only built-ins or your own) merged into the app and its type.
|
|
2531
|
+
* - `onReady` / `onError` / `onStart` / `onStop` — lifecycle callbacks.
|
|
2532
|
+
* @returns The initialized app: `start()`, `stop()`, every plugin's API, and `log`.
|
|
2533
|
+
* @example
|
|
2534
|
+
* ```ts
|
|
2535
|
+
* // Node SSG build — add the node-only plugins:
|
|
2536
|
+
* const app = createApp({
|
|
2537
|
+
* plugins: [contentPlugin, buildPlugin, deployPlugin],
|
|
2538
|
+
* pluginConfigs: {
|
|
2539
|
+
* site: { name: "My Blog", url: "https://blog.dev", author: "Ada", description: "Notes" },
|
|
2540
|
+
* router: { routes: defineRoutes({ home: route("/"), post: route("/blog/{slug}/") }) }
|
|
2541
|
+
* }
|
|
2542
|
+
* });
|
|
2543
|
+
* await app.start();
|
|
2544
|
+
* await app.build.run();
|
|
2545
|
+
* ```
|
|
2546
|
+
*/
|
|
1998
2547
|
declare const createApp: <const ExtraPlugins extends readonly import("@moku-labs/core").AnyPluginInstance[] = readonly []>(options?: import("@moku-labs/core").CreateAppOptions<Config$7, Events, (import("@moku-labs/core").PluginInstance<"site", Config$6, Record<string, never>, Api$6, {}> & Record<never, never>) | (import("@moku-labs/core").PluginInstance<"i18n", Config$5, Record<string, never>, Api$5, {}> & Record<never, never>) | (import("@moku-labs/core").PluginInstance<"router", RouterConfig, RouterState, RouterApi, {}> & {
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
}
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
buildArticleHead: typeof buildArticleHead;
|
|
2072
|
-
}) | (import("@moku-labs/core").PluginInstance<"build", Config$1, State$1, Api$1, {
|
|
2073
|
-
"build:phase": {
|
|
2074
|
-
phase: PhaseName;
|
|
2075
|
-
status: "start" | "done";
|
|
2076
|
-
durationMs?: number;
|
|
2077
|
-
};
|
|
2078
|
-
"build:complete": {
|
|
2079
|
-
outDir: string;
|
|
2080
|
-
pageCount: number;
|
|
2081
|
-
durationMs: number;
|
|
2082
|
-
};
|
|
2083
|
-
}> & Record<never, never>) | (import("@moku-labs/core").PluginInstance<"spa", SpaConfig, SpaState, SpaApi, {
|
|
2084
|
-
"spa:navigate": {
|
|
2085
|
-
from: string;
|
|
2086
|
-
to: string;
|
|
2087
|
-
};
|
|
2088
|
-
"spa:navigated": {
|
|
2089
|
-
url: string;
|
|
2090
|
-
};
|
|
2091
|
-
"spa:component-mount": {
|
|
2092
|
-
name: string;
|
|
2093
|
-
el: Element;
|
|
2094
|
-
};
|
|
2095
|
-
"spa:component-unmount": {
|
|
2096
|
-
name: string;
|
|
2097
|
-
el: Element;
|
|
2098
|
-
};
|
|
2099
|
-
}> & Record<never, never>) | (import("@moku-labs/core").PluginInstance<"deploy", Config, State, Api, {
|
|
2100
|
-
"deploy:complete": {
|
|
2101
|
-
url: string;
|
|
2102
|
-
deploymentId: string;
|
|
2103
|
-
branch: string;
|
|
2104
|
-
durationMs: number;
|
|
2105
|
-
};
|
|
2106
|
-
}> & Record<never, never>) | ExtraPlugins[number], import("@moku-labs/core").CoreApisFromTuple<[import("@moku-labs/core").CorePluginInstance<"log", LogConfig, LogState, LogApi>, import("@moku-labs/core").CorePluginInstance<"env", EnvConfig, EnvState, EnvApi>]>>, createPlugin: import("@moku-labs/core").BoundCreatePluginFunction<Config$7, Events, import("@moku-labs/core").CoreApisFromTuple<[import("@moku-labs/core").CorePluginInstance<"log", LogConfig, LogState, LogApi>, import("@moku-labs/core").CorePluginInstance<"env", EnvConfig, EnvState, EnvApi>]>>;
|
|
2548
|
+
route: typeof route;
|
|
2549
|
+
defineRoutes: typeof defineRoutes;
|
|
2550
|
+
}) | (import("@moku-labs/core").PluginInstance<"head", Config$3, State$3, Api$3, {}> & {
|
|
2551
|
+
meta: typeof meta;
|
|
2552
|
+
og: typeof og;
|
|
2553
|
+
twitter: typeof twitter;
|
|
2554
|
+
jsonLd: typeof jsonLd;
|
|
2555
|
+
canonical: typeof canonical;
|
|
2556
|
+
hreflang: typeof hreflang;
|
|
2557
|
+
feedLink: typeof feedLink;
|
|
2558
|
+
buildArticleHead: typeof buildArticleHead;
|
|
2559
|
+
}) | (import("@moku-labs/core").PluginInstance<"spa", SpaConfig, SpaState, SpaApi, {
|
|
2560
|
+
"spa:navigate": {
|
|
2561
|
+
from: string;
|
|
2562
|
+
to: string;
|
|
2563
|
+
};
|
|
2564
|
+
"spa:navigated": {
|
|
2565
|
+
url: string;
|
|
2566
|
+
};
|
|
2567
|
+
"spa:component-mount": {
|
|
2568
|
+
name: string;
|
|
2569
|
+
el: Element;
|
|
2570
|
+
};
|
|
2571
|
+
"spa:component-unmount": {
|
|
2572
|
+
name: string;
|
|
2573
|
+
el: Element;
|
|
2574
|
+
};
|
|
2575
|
+
}> & Record<never, never>) | ExtraPlugins[number], [...ExtraPlugins], import("@moku-labs/core").CoreApisFromTuple<[import("@moku-labs/core").CorePluginInstance<"log", LogConfig, LogState, LogApi>, import("@moku-labs/core").CorePluginInstance<"env", EnvConfig, EnvState, EnvApi>]>> | undefined) => import("@moku-labs/core").App<Config$7, Events, (import("@moku-labs/core").PluginInstance<"site", Config$6, Record<string, never>, Api$6, {}> & Record<never, never>) | (import("@moku-labs/core").PluginInstance<"i18n", Config$5, Record<string, never>, Api$5, {}> & Record<never, never>) | (import("@moku-labs/core").PluginInstance<"router", RouterConfig, RouterState, RouterApi, {}> & {
|
|
2576
|
+
route: typeof route;
|
|
2577
|
+
defineRoutes: typeof defineRoutes;
|
|
2578
|
+
}) | (import("@moku-labs/core").PluginInstance<"head", Config$3, State$3, Api$3, {}> & {
|
|
2579
|
+
meta: typeof meta;
|
|
2580
|
+
og: typeof og;
|
|
2581
|
+
twitter: typeof twitter;
|
|
2582
|
+
jsonLd: typeof jsonLd;
|
|
2583
|
+
canonical: typeof canonical;
|
|
2584
|
+
hreflang: typeof hreflang;
|
|
2585
|
+
feedLink: typeof feedLink;
|
|
2586
|
+
buildArticleHead: typeof buildArticleHead;
|
|
2587
|
+
}) | (import("@moku-labs/core").PluginInstance<"spa", SpaConfig, SpaState, SpaApi, {
|
|
2588
|
+
"spa:navigate": {
|
|
2589
|
+
from: string;
|
|
2590
|
+
to: string;
|
|
2591
|
+
};
|
|
2592
|
+
"spa:navigated": {
|
|
2593
|
+
url: string;
|
|
2594
|
+
};
|
|
2595
|
+
"spa:component-mount": {
|
|
2596
|
+
name: string;
|
|
2597
|
+
el: Element;
|
|
2598
|
+
};
|
|
2599
|
+
"spa:component-unmount": {
|
|
2600
|
+
name: string;
|
|
2601
|
+
el: Element;
|
|
2602
|
+
};
|
|
2603
|
+
}> & Record<never, never>) | ExtraPlugins[number], import("@moku-labs/core").CoreApisFromTuple<[import("@moku-labs/core").CorePluginInstance<"log", LogConfig, LogState, LogApi>, import("@moku-labs/core").CorePluginInstance<"env", EnvConfig, EnvState, EnvApi>]>>;
|
|
2604
|
+
/**
|
|
2605
|
+
* Create a custom plugin bound to this framework's `Config`/`Events` and core
|
|
2606
|
+
* APIs. Plugin types are inferred from the spec object — never written explicitly.
|
|
2607
|
+
* Pass the result to {@link createApp} via `plugins`.
|
|
2608
|
+
*
|
|
2609
|
+
* @example
|
|
2610
|
+
* ```ts
|
|
2611
|
+
* const analytics = createPlugin("analytics", {
|
|
2612
|
+
* config: { writeKey: "" },
|
|
2613
|
+
* api: (ctx) => ({ track: (event: string) => ctx.log.info("analytics:track", { event }) })
|
|
2614
|
+
* });
|
|
2615
|
+
*
|
|
2616
|
+
* const app = createApp({ plugins: [analytics] });
|
|
2617
|
+
* ```
|
|
2618
|
+
*/
|
|
2619
|
+
declare const createPlugin: import("@moku-labs/core").BoundCreatePluginFunction<Config$7, Events, import("@moku-labs/core").CoreApisFromTuple<[import("@moku-labs/core").CorePluginInstance<"log", LogConfig, LogState, LogApi>, import("@moku-labs/core").CorePluginInstance<"env", EnvConfig, EnvState, EnvApi>]>>;
|
|
2107
2620
|
//#endregion
|
|
2108
|
-
export { types_d_exports as Build, types_d_exports$1 as Content, types_d_exports$2 as
|
|
2621
|
+
export { types_d_exports as Build, types_d_exports$1 as Content, types_d_exports$2 as Data, types_d_exports$3 as Deploy, types_d_exports$4 as Env, types_d_exports$5 as Head, types_d_exports$6 as Log, types_d_exports$7 as Router, types_d_exports$8 as Spa, browserEnv, buildArticleHead, buildPlugin, canonical, cloudflareBindings, contentPlugin, createApp, createPlugin, dataPlugin, defineRoutes, deployPlugin, dotenv, envPlugin, feedLink, headPlugin, hreflang, i18nPlugin, jsonLd, logPlugin, meta, og, processEnv, route, routerPlugin, sitePlugin, spaPlugin, twitter };
|