@prudentbird/voxx-core 1.0.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.
Files changed (123) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +42 -0
  3. package/dist/_virtual/_rolldown/runtime.cjs +23 -0
  4. package/dist/config.cjs +151 -0
  5. package/dist/config.cjs.map +1 -0
  6. package/dist/config.d.cts +122 -0
  7. package/dist/config.d.cts.map +1 -0
  8. package/dist/config.d.mts +122 -0
  9. package/dist/config.d.mts.map +1 -0
  10. package/dist/config.mjs +149 -0
  11. package/dist/config.mjs.map +1 -0
  12. package/dist/content.cjs +147 -0
  13. package/dist/content.cjs.map +1 -0
  14. package/dist/content.d.cts +41 -0
  15. package/dist/content.d.cts.map +1 -0
  16. package/dist/content.d.mts +41 -0
  17. package/dist/content.d.mts.map +1 -0
  18. package/dist/content.mjs +145 -0
  19. package/dist/content.mjs.map +1 -0
  20. package/dist/dev.cjs +82 -0
  21. package/dist/dev.cjs.map +1 -0
  22. package/dist/dev.d.cts +24 -0
  23. package/dist/dev.d.cts.map +1 -0
  24. package/dist/dev.d.mts +24 -0
  25. package/dist/dev.d.mts.map +1 -0
  26. package/dist/dev.mjs +82 -0
  27. package/dist/dev.mjs.map +1 -0
  28. package/dist/effect.cjs +23 -0
  29. package/dist/effect.d.cts +8 -0
  30. package/dist/effect.d.mts +8 -0
  31. package/dist/effect.mjs +8 -0
  32. package/dist/errors.cjs +20 -0
  33. package/dist/errors.cjs.map +1 -0
  34. package/dist/errors.d.cts +45 -0
  35. package/dist/errors.d.cts.map +1 -0
  36. package/dist/errors.d.mts +45 -0
  37. package/dist/errors.d.mts.map +1 -0
  38. package/dist/errors.mjs +16 -0
  39. package/dist/errors.mjs.map +1 -0
  40. package/dist/feeds.cjs +97 -0
  41. package/dist/feeds.cjs.map +1 -0
  42. package/dist/feeds.d.cts +49 -0
  43. package/dist/feeds.d.cts.map +1 -0
  44. package/dist/feeds.d.mts +49 -0
  45. package/dist/feeds.d.mts.map +1 -0
  46. package/dist/feeds.mjs +94 -0
  47. package/dist/feeds.mjs.map +1 -0
  48. package/dist/frontmatter.cjs +22 -0
  49. package/dist/frontmatter.cjs.map +1 -0
  50. package/dist/frontmatter.d.cts +30 -0
  51. package/dist/frontmatter.d.cts.map +1 -0
  52. package/dist/frontmatter.d.mts +30 -0
  53. package/dist/frontmatter.d.mts.map +1 -0
  54. package/dist/frontmatter.mjs +20 -0
  55. package/dist/frontmatter.mjs.map +1 -0
  56. package/dist/index.cjs +90 -0
  57. package/dist/index.cjs.map +1 -0
  58. package/dist/index.d.cts +50 -0
  59. package/dist/index.d.cts.map +1 -0
  60. package/dist/index.d.mts +50 -0
  61. package/dist/index.d.mts.map +1 -0
  62. package/dist/index.mjs +59 -0
  63. package/dist/index.mjs.map +1 -0
  64. package/dist/llms.cjs +81 -0
  65. package/dist/llms.cjs.map +1 -0
  66. package/dist/llms.d.cts +43 -0
  67. package/dist/llms.d.cts.map +1 -0
  68. package/dist/llms.d.mts +43 -0
  69. package/dist/llms.d.mts.map +1 -0
  70. package/dist/llms.mjs +78 -0
  71. package/dist/llms.mjs.map +1 -0
  72. package/dist/nav.cjs +50 -0
  73. package/dist/nav.cjs.map +1 -0
  74. package/dist/nav.d.cts +16 -0
  75. package/dist/nav.d.cts.map +1 -0
  76. package/dist/nav.d.mts +16 -0
  77. package/dist/nav.d.mts.map +1 -0
  78. package/dist/nav.mjs +50 -0
  79. package/dist/nav.mjs.map +1 -0
  80. package/dist/render.cjs +152 -0
  81. package/dist/render.cjs.map +1 -0
  82. package/dist/render.d.cts +29 -0
  83. package/dist/render.d.cts.map +1 -0
  84. package/dist/render.d.mts +29 -0
  85. package/dist/render.d.mts.map +1 -0
  86. package/dist/render.mjs +143 -0
  87. package/dist/render.mjs.map +1 -0
  88. package/dist/schema.cjs +78 -0
  89. package/dist/schema.cjs.map +1 -0
  90. package/dist/schema.d.cts +93 -0
  91. package/dist/schema.d.cts.map +1 -0
  92. package/dist/schema.d.mts +93 -0
  93. package/dist/schema.d.mts.map +1 -0
  94. package/dist/schema.mjs +77 -0
  95. package/dist/schema.mjs.map +1 -0
  96. package/dist/seo.cjs +77 -0
  97. package/dist/seo.cjs.map +1 -0
  98. package/dist/seo.d.cts +15 -0
  99. package/dist/seo.d.cts.map +1 -0
  100. package/dist/seo.d.mts +15 -0
  101. package/dist/seo.d.mts.map +1 -0
  102. package/dist/seo.mjs +77 -0
  103. package/dist/seo.mjs.map +1 -0
  104. package/dist/types.cjs +45 -0
  105. package/dist/types.cjs.map +1 -0
  106. package/dist/types.d.cts +138 -0
  107. package/dist/types.d.cts.map +1 -0
  108. package/dist/types.d.mts +138 -0
  109. package/dist/types.d.mts.map +1 -0
  110. package/dist/types.mjs +45 -0
  111. package/dist/types.mjs.map +1 -0
  112. package/dist/util.cjs +185 -0
  113. package/dist/util.cjs.map +1 -0
  114. package/dist/util.d.cts +98 -0
  115. package/dist/util.d.cts.map +1 -0
  116. package/dist/util.d.mts +98 -0
  117. package/dist/util.d.mts.map +1 -0
  118. package/dist/util.mjs +171 -0
  119. package/dist/util.mjs.map +1 -0
  120. package/package.json +106 -0
  121. package/theme/demo-globals.css +61 -0
  122. package/theme/voxx.css +915 -0
  123. package/voxx.schema.json +186 -0
@@ -0,0 +1,138 @@
1
+ //#region src/types.d.ts
2
+ /** Author metadata attached to a site or post. */
3
+ interface VoxxAuthor {
4
+ name: string;
5
+ url?: string;
6
+ }
7
+ /** The rendering mode for a content collection. */
8
+ type ContentType = "blog" | "docs" | "changelog";
9
+ /** A resolved content collection with all defaults applied. */
10
+ interface CollectionConfig {
11
+ name: string;
12
+ type: ContentType;
13
+ /** Filesystem path to the content directory. */
14
+ dir: string;
15
+ /** URL prefix for all posts in this collection. */
16
+ basePath: string;
17
+ /** Whether draft posts are included by default. */
18
+ drafts: boolean;
19
+ }
20
+ /** Resolved Voxx configuration merged from `voxx.json` and defaults. */
21
+ interface VoxxConfig {
22
+ site: {
23
+ title: string;
24
+ description: string; /** Canonical origin, e.g. `https://example.com`. */
25
+ url: string;
26
+ author?: VoxxAuthor; /** BCP 47 locale tag, e.g. `en-US`. */
27
+ locale: string;
28
+ };
29
+ /** Primary content collection (mirrors `collections[0]`). */
30
+ content: {
31
+ type: ContentType;
32
+ dir: string;
33
+ basePath: string;
34
+ drafts: boolean;
35
+ };
36
+ collections: CollectionConfig[];
37
+ theme: {
38
+ preset: "shadcn"; /** Path to a custom CSS file, or `null` to use the preset default. */
39
+ css: string | null; /** One or two Shiki theme names separated by a space (light dark). */
40
+ codeTheme: string;
41
+ };
42
+ features: {
43
+ toc: boolean;
44
+ rss: boolean;
45
+ sitemap: boolean;
46
+ llmsTxt: boolean;
47
+ tags: boolean;
48
+ readingTime: boolean;
49
+ };
50
+ seo: {
51
+ openGraph: boolean; /** Twitter/X handle including `@`, or `null` to omit. */
52
+ twitter: string | null;
53
+ jsonLd: boolean; /** Fallback OG image path relative to the site root. */
54
+ defaultImage: string | null;
55
+ };
56
+ }
57
+ /** A single entry in a page's table of contents. */
58
+ interface TocItem {
59
+ id: string;
60
+ text: string;
61
+ /** Heading level — `2` for `h2`, `3` for `h3`. */
62
+ depth: number;
63
+ }
64
+ /** A fully processed content post ready to render. */
65
+ interface Post {
66
+ /** Final path segment(s), e.g. `["getting-started", "install"]`. */
67
+ slug: string;
68
+ path: string[];
69
+ /** Absolute URL path, e.g. `/blog/my-post`. */
70
+ url: string;
71
+ title: string;
72
+ description?: string;
73
+ /** ISO 8601 publish date. */
74
+ date: string;
75
+ /** ISO 8601 last-modified date. */
76
+ updated?: string;
77
+ tags: string[];
78
+ category?: string;
79
+ /** Sort order within the parent directory (docs only). */
80
+ order?: number;
81
+ /** Semver string extracted from the filename or frontmatter (changelog only). */
82
+ version?: string;
83
+ draft: boolean;
84
+ image?: string;
85
+ author?: string;
86
+ /** Plain-text excerpt derived from the first 180 characters of content. */
87
+ excerpt: string;
88
+ readingTimeMinutes: number;
89
+ /** Rendered HTML string. */
90
+ html: string;
91
+ toc: TocItem[];
92
+ /** Raw Markdown source. */
93
+ content: string;
94
+ }
95
+ /** A node in the sidebar navigation tree. */
96
+ interface NavNode {
97
+ title: string;
98
+ /** Present on leaf nodes; absent on category nodes. */
99
+ url?: string;
100
+ children: NavNode[];
101
+ }
102
+ /** Open Graph article metadata. */
103
+ interface OpenGraphData {
104
+ title: string;
105
+ description: string;
106
+ url: string;
107
+ type: "article";
108
+ siteName: string;
109
+ locale: string;
110
+ images: string[];
111
+ publishedTime?: string;
112
+ modifiedTime?: string;
113
+ authors: string[];
114
+ tags: string[];
115
+ }
116
+ /** Twitter card metadata. */
117
+ interface TwitterData {
118
+ card: "summary_large_image";
119
+ site?: string;
120
+ creator?: string;
121
+ title: string;
122
+ description: string;
123
+ images: string[];
124
+ }
125
+ /** Aggregated SEO metadata for a single page. */
126
+ interface SeoData {
127
+ title: string;
128
+ description: string;
129
+ canonical: string;
130
+ openGraph?: OpenGraphData;
131
+ twitter?: TwitterData;
132
+ /** JSON-LD structured data object — serialize with `serializeJsonLd`. */
133
+ jsonLd?: Record<string, unknown>;
134
+ }
135
+ declare const DEFAULT_CONFIG: VoxxConfig;
136
+ //#endregion
137
+ export { CollectionConfig, ContentType, DEFAULT_CONFIG, NavNode, OpenGraphData, Post, SeoData, TocItem, TwitterData, VoxxAuthor, VoxxConfig };
138
+ //# sourceMappingURL=types.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.cts","names":[],"sources":["../src/types.ts"],"mappings":";;UACiB,UAAA;EACf,IAAA;EACA,GAAG;AAAA;AAAA;AAAA,KAIO,WAAA;;UAGK,gBAAA;EACf,IAAA;EACA,IAAA,EAAM,WAAW;EAFF;EAIf,GAAA;;EAEA,QAAA;EALA;EAOA,MAAA;AAAA;;UAIe,UAAA;EACf,IAAA;IACE,KAAA;IACA,WAAA,UAHa;IAKb,GAAA;IACA,MAAA,GAAS,UAAA,EAAA;IAET,MAAA;EAAA;EAS2B;EAN7B,OAAA;IACE,IAAA,EAAM,WAAA;IACN,GAAA;IACA,QAAA;IACA,MAAA;EAAA;EAEF,WAAA,EAAa,gBAAA;EACb,KAAA;IACE,MAAA,YAPA;IASA,GAAA,iBARA;IAUA,SAAA;EAAA;EAEF,QAAA;IACE,GAAA;IACA,GAAA;IACA,OAAA;IACA,OAAA;IACA,IAAA;IACA,WAAA;EAAA;EAEF,GAAA;IACE,SAAA,WALA;IAOA,OAAA;IACA,MAAA,WAJF;IAME,YAAA;EAAA;AAAA;;UAKa,OAAA;EACf,EAAA;EACA,IAAA;EAFsB;EAItB,KAAA;AAAA;;UAIe,IAAA;EAJf;EAMA,IAAA;EACA,IAAA;EAHe;EAKf,GAAA;EACA,KAAA;EACA,WAAA;EALA;EAOA,IAAA;EAJA;EAMA,OAAA;EACA,IAAA;EACA,QAAA;EAFA;EAIA,KAAA;EAFA;EAIA,OAAA;EACA,KAAA;EACA,KAAA;EACA,MAAA;EAAA;EAEA,OAAA;EACA,kBAAA;EAEA;EAAA,IAAA;EACA,GAAA,EAAK,OAAO;EAEZ;EAAA,OAAA;AAAA;AAIF;AAAA,UAAiB,OAAA;EACf,KAAA;EAGiB;EADjB,GAAA;EACA,QAAA,EAAU,OAAO;AAAA;;UAIF,aAAA;EACf,KAAA;EACA,WAAA;EACA,GAAA;EACA,IAAA;EACA,QAAA;EACA,MAAA;EACA,MAAA;EACA,aAAA;EACA,YAAA;EACA,OAAA;EACA,IAAA;AAAA;;UAIe,WAAA;EACf,IAAA;EACA,IAAA;EACA,OAAA;EACA,KAAA;EACA,WAAA;EACA,MAAA;AAAA;;UAIe,OAAA;EACf,KAAA;EACA,WAAA;EACA,SAAA;EACA,SAAA,GAAY,aAAA;EACZ,OAAA,GAAU,WAAA;EATJ;EAWN,MAAA,GAAS,MAAA;AAAA;AAAA,cAGE,cAAA,EAAgB,UAyC5B"}
@@ -0,0 +1,138 @@
1
+ //#region src/types.d.ts
2
+ /** Author metadata attached to a site or post. */
3
+ interface VoxxAuthor {
4
+ name: string;
5
+ url?: string;
6
+ }
7
+ /** The rendering mode for a content collection. */
8
+ type ContentType = "blog" | "docs" | "changelog";
9
+ /** A resolved content collection with all defaults applied. */
10
+ interface CollectionConfig {
11
+ name: string;
12
+ type: ContentType;
13
+ /** Filesystem path to the content directory. */
14
+ dir: string;
15
+ /** URL prefix for all posts in this collection. */
16
+ basePath: string;
17
+ /** Whether draft posts are included by default. */
18
+ drafts: boolean;
19
+ }
20
+ /** Resolved Voxx configuration merged from `voxx.json` and defaults. */
21
+ interface VoxxConfig {
22
+ site: {
23
+ title: string;
24
+ description: string; /** Canonical origin, e.g. `https://example.com`. */
25
+ url: string;
26
+ author?: VoxxAuthor; /** BCP 47 locale tag, e.g. `en-US`. */
27
+ locale: string;
28
+ };
29
+ /** Primary content collection (mirrors `collections[0]`). */
30
+ content: {
31
+ type: ContentType;
32
+ dir: string;
33
+ basePath: string;
34
+ drafts: boolean;
35
+ };
36
+ collections: CollectionConfig[];
37
+ theme: {
38
+ preset: "shadcn"; /** Path to a custom CSS file, or `null` to use the preset default. */
39
+ css: string | null; /** One or two Shiki theme names separated by a space (light dark). */
40
+ codeTheme: string;
41
+ };
42
+ features: {
43
+ toc: boolean;
44
+ rss: boolean;
45
+ sitemap: boolean;
46
+ llmsTxt: boolean;
47
+ tags: boolean;
48
+ readingTime: boolean;
49
+ };
50
+ seo: {
51
+ openGraph: boolean; /** Twitter/X handle including `@`, or `null` to omit. */
52
+ twitter: string | null;
53
+ jsonLd: boolean; /** Fallback OG image path relative to the site root. */
54
+ defaultImage: string | null;
55
+ };
56
+ }
57
+ /** A single entry in a page's table of contents. */
58
+ interface TocItem {
59
+ id: string;
60
+ text: string;
61
+ /** Heading level — `2` for `h2`, `3` for `h3`. */
62
+ depth: number;
63
+ }
64
+ /** A fully processed content post ready to render. */
65
+ interface Post {
66
+ /** Final path segment(s), e.g. `["getting-started", "install"]`. */
67
+ slug: string;
68
+ path: string[];
69
+ /** Absolute URL path, e.g. `/blog/my-post`. */
70
+ url: string;
71
+ title: string;
72
+ description?: string;
73
+ /** ISO 8601 publish date. */
74
+ date: string;
75
+ /** ISO 8601 last-modified date. */
76
+ updated?: string;
77
+ tags: string[];
78
+ category?: string;
79
+ /** Sort order within the parent directory (docs only). */
80
+ order?: number;
81
+ /** Semver string extracted from the filename or frontmatter (changelog only). */
82
+ version?: string;
83
+ draft: boolean;
84
+ image?: string;
85
+ author?: string;
86
+ /** Plain-text excerpt derived from the first 180 characters of content. */
87
+ excerpt: string;
88
+ readingTimeMinutes: number;
89
+ /** Rendered HTML string. */
90
+ html: string;
91
+ toc: TocItem[];
92
+ /** Raw Markdown source. */
93
+ content: string;
94
+ }
95
+ /** A node in the sidebar navigation tree. */
96
+ interface NavNode {
97
+ title: string;
98
+ /** Present on leaf nodes; absent on category nodes. */
99
+ url?: string;
100
+ children: NavNode[];
101
+ }
102
+ /** Open Graph article metadata. */
103
+ interface OpenGraphData {
104
+ title: string;
105
+ description: string;
106
+ url: string;
107
+ type: "article";
108
+ siteName: string;
109
+ locale: string;
110
+ images: string[];
111
+ publishedTime?: string;
112
+ modifiedTime?: string;
113
+ authors: string[];
114
+ tags: string[];
115
+ }
116
+ /** Twitter card metadata. */
117
+ interface TwitterData {
118
+ card: "summary_large_image";
119
+ site?: string;
120
+ creator?: string;
121
+ title: string;
122
+ description: string;
123
+ images: string[];
124
+ }
125
+ /** Aggregated SEO metadata for a single page. */
126
+ interface SeoData {
127
+ title: string;
128
+ description: string;
129
+ canonical: string;
130
+ openGraph?: OpenGraphData;
131
+ twitter?: TwitterData;
132
+ /** JSON-LD structured data object — serialize with `serializeJsonLd`. */
133
+ jsonLd?: Record<string, unknown>;
134
+ }
135
+ declare const DEFAULT_CONFIG: VoxxConfig;
136
+ //#endregion
137
+ export { CollectionConfig, ContentType, DEFAULT_CONFIG, NavNode, OpenGraphData, Post, SeoData, TocItem, TwitterData, VoxxAuthor, VoxxConfig };
138
+ //# sourceMappingURL=types.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.mts","names":[],"sources":["../src/types.ts"],"mappings":";;UACiB,UAAA;EACf,IAAA;EACA,GAAG;AAAA;AAAA;AAAA,KAIO,WAAA;;UAGK,gBAAA;EACf,IAAA;EACA,IAAA,EAAM,WAAW;EAFF;EAIf,GAAA;;EAEA,QAAA;EALA;EAOA,MAAA;AAAA;;UAIe,UAAA;EACf,IAAA;IACE,KAAA;IACA,WAAA,UAHa;IAKb,GAAA;IACA,MAAA,GAAS,UAAA,EAAA;IAET,MAAA;EAAA;EAS2B;EAN7B,OAAA;IACE,IAAA,EAAM,WAAA;IACN,GAAA;IACA,QAAA;IACA,MAAA;EAAA;EAEF,WAAA,EAAa,gBAAA;EACb,KAAA;IACE,MAAA,YAPA;IASA,GAAA,iBARA;IAUA,SAAA;EAAA;EAEF,QAAA;IACE,GAAA;IACA,GAAA;IACA,OAAA;IACA,OAAA;IACA,IAAA;IACA,WAAA;EAAA;EAEF,GAAA;IACE,SAAA,WALA;IAOA,OAAA;IACA,MAAA,WAJF;IAME,YAAA;EAAA;AAAA;;UAKa,OAAA;EACf,EAAA;EACA,IAAA;EAFsB;EAItB,KAAA;AAAA;;UAIe,IAAA;EAJf;EAMA,IAAA;EACA,IAAA;EAHe;EAKf,GAAA;EACA,KAAA;EACA,WAAA;EALA;EAOA,IAAA;EAJA;EAMA,OAAA;EACA,IAAA;EACA,QAAA;EAFA;EAIA,KAAA;EAFA;EAIA,OAAA;EACA,KAAA;EACA,KAAA;EACA,MAAA;EAAA;EAEA,OAAA;EACA,kBAAA;EAEA;EAAA,IAAA;EACA,GAAA,EAAK,OAAO;EAEZ;EAAA,OAAA;AAAA;AAIF;AAAA,UAAiB,OAAA;EACf,KAAA;EAGiB;EADjB,GAAA;EACA,QAAA,EAAU,OAAO;AAAA;;UAIF,aAAA;EACf,KAAA;EACA,WAAA;EACA,GAAA;EACA,IAAA;EACA,QAAA;EACA,MAAA;EACA,MAAA;EACA,aAAA;EACA,YAAA;EACA,OAAA;EACA,IAAA;AAAA;;UAIe,WAAA;EACf,IAAA;EACA,IAAA;EACA,OAAA;EACA,KAAA;EACA,WAAA;EACA,MAAA;AAAA;;UAIe,OAAA;EACf,KAAA;EACA,WAAA;EACA,SAAA;EACA,SAAA,GAAY,aAAA;EACZ,OAAA,GAAU,WAAA;EATJ;EAWN,MAAA,GAAS,MAAA;AAAA;AAAA,cAGE,cAAA,EAAgB,UAyC5B"}
package/dist/types.mjs ADDED
@@ -0,0 +1,45 @@
1
+ //#region src/types.ts
2
+ const DEFAULT_CONFIG = {
3
+ site: {
4
+ title: "Blog",
5
+ description: "",
6
+ url: "",
7
+ locale: "en-US"
8
+ },
9
+ content: {
10
+ type: "blog",
11
+ dir: "content/blog",
12
+ basePath: "/blog",
13
+ drafts: false
14
+ },
15
+ collections: [{
16
+ name: "blog",
17
+ type: "blog",
18
+ dir: "content/blog",
19
+ basePath: "/blog",
20
+ drafts: false
21
+ }],
22
+ theme: {
23
+ preset: "shadcn",
24
+ css: null,
25
+ codeTheme: "github-light github-dark"
26
+ },
27
+ features: {
28
+ toc: true,
29
+ rss: true,
30
+ sitemap: true,
31
+ llmsTxt: true,
32
+ tags: true,
33
+ readingTime: true
34
+ },
35
+ seo: {
36
+ openGraph: true,
37
+ twitter: null,
38
+ jsonLd: true,
39
+ defaultImage: null
40
+ }
41
+ };
42
+ //#endregion
43
+ export { DEFAULT_CONFIG };
44
+
45
+ //# sourceMappingURL=types.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.mjs","names":[],"sources":["../src/types.ts"],"sourcesContent":["/** Author metadata attached to a site or post. */\nexport interface VoxxAuthor {\n name: string;\n url?: string;\n}\n\n/** The rendering mode for a content collection. */\nexport type ContentType = \"blog\" | \"docs\" | \"changelog\";\n\n/** A resolved content collection with all defaults applied. */\nexport interface CollectionConfig {\n name: string;\n type: ContentType;\n /** Filesystem path to the content directory. */\n dir: string;\n /** URL prefix for all posts in this collection. */\n basePath: string;\n /** Whether draft posts are included by default. */\n drafts: boolean;\n}\n\n/** Resolved Voxx configuration merged from `voxx.json` and defaults. */\nexport interface VoxxConfig {\n site: {\n title: string;\n description: string;\n /** Canonical origin, e.g. `https://example.com`. */\n url: string;\n author?: VoxxAuthor;\n /** BCP 47 locale tag, e.g. `en-US`. */\n locale: string;\n };\n /** Primary content collection (mirrors `collections[0]`). */\n content: {\n type: ContentType;\n dir: string;\n basePath: string;\n drafts: boolean;\n };\n collections: CollectionConfig[];\n theme: {\n preset: \"shadcn\";\n /** Path to a custom CSS file, or `null` to use the preset default. */\n css: string | null;\n /** One or two Shiki theme names separated by a space (light dark). */\n codeTheme: string;\n };\n features: {\n toc: boolean;\n rss: boolean;\n sitemap: boolean;\n llmsTxt: boolean;\n tags: boolean;\n readingTime: boolean;\n };\n seo: {\n openGraph: boolean;\n /** Twitter/X handle including `@`, or `null` to omit. */\n twitter: string | null;\n jsonLd: boolean;\n /** Fallback OG image path relative to the site root. */\n defaultImage: string | null;\n };\n}\n\n/** A single entry in a page's table of contents. */\nexport interface TocItem {\n id: string;\n text: string;\n /** Heading level — `2` for `h2`, `3` for `h3`. */\n depth: number;\n}\n\n/** A fully processed content post ready to render. */\nexport interface Post {\n /** Final path segment(s), e.g. `[\"getting-started\", \"install\"]`. */\n slug: string;\n path: string[];\n /** Absolute URL path, e.g. `/blog/my-post`. */\n url: string;\n title: string;\n description?: string;\n /** ISO 8601 publish date. */\n date: string;\n /** ISO 8601 last-modified date. */\n updated?: string;\n tags: string[];\n category?: string;\n /** Sort order within the parent directory (docs only). */\n order?: number;\n /** Semver string extracted from the filename or frontmatter (changelog only). */\n version?: string;\n draft: boolean;\n image?: string;\n author?: string;\n /** Plain-text excerpt derived from the first 180 characters of content. */\n excerpt: string;\n readingTimeMinutes: number;\n /** Rendered HTML string. */\n html: string;\n toc: TocItem[];\n /** Raw Markdown source. */\n content: string;\n}\n\n/** A node in the sidebar navigation tree. */\nexport interface NavNode {\n title: string;\n /** Present on leaf nodes; absent on category nodes. */\n url?: string;\n children: NavNode[];\n}\n\n/** Open Graph article metadata. */\nexport interface OpenGraphData {\n title: string;\n description: string;\n url: string;\n type: \"article\";\n siteName: string;\n locale: string;\n images: string[];\n publishedTime?: string;\n modifiedTime?: string;\n authors: string[];\n tags: string[];\n}\n\n/** Twitter card metadata. */\nexport interface TwitterData {\n card: \"summary_large_image\";\n site?: string;\n creator?: string;\n title: string;\n description: string;\n images: string[];\n}\n\n/** Aggregated SEO metadata for a single page. */\nexport interface SeoData {\n title: string;\n description: string;\n canonical: string;\n openGraph?: OpenGraphData;\n twitter?: TwitterData;\n /** JSON-LD structured data object — serialize with `serializeJsonLd`. */\n jsonLd?: Record<string, unknown>;\n}\n\nexport const DEFAULT_CONFIG: VoxxConfig = {\n site: {\n title: \"Blog\",\n description: \"\",\n url: \"\",\n locale: \"en-US\",\n },\n content: {\n type: \"blog\",\n dir: \"content/blog\",\n basePath: \"/blog\",\n drafts: false,\n },\n collections: [\n {\n name: \"blog\",\n type: \"blog\",\n dir: \"content/blog\",\n basePath: \"/blog\",\n drafts: false,\n },\n ],\n theme: {\n preset: \"shadcn\",\n css: null,\n codeTheme: \"github-light github-dark\",\n },\n features: {\n toc: true,\n rss: true,\n sitemap: true,\n llmsTxt: true,\n tags: true,\n readingTime: true,\n },\n seo: {\n openGraph: true,\n twitter: null,\n jsonLd: true,\n defaultImage: null,\n },\n};\n"],"mappings":";AAqJA,MAAa,iBAA6B;CACxC,MAAM;EACJ,OAAO;EACP,aAAa;EACb,KAAK;EACL,QAAQ;CACV;CACA,SAAS;EACP,MAAM;EACN,KAAK;EACL,UAAU;EACV,QAAQ;CACV;CACA,aAAa,CACX;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,UAAU;EACV,QAAQ;CACV,CACF;CACA,OAAO;EACL,QAAQ;EACR,KAAK;EACL,WAAW;CACb;CACA,UAAU;EACR,KAAK;EACL,KAAK;EACL,SAAS;EACT,SAAS;EACT,MAAM;EACN,aAAa;CACf;CACA,KAAK;EACH,WAAW;EACX,SAAS;EACT,QAAQ;EACR,cAAc;CAChB;AACF"}
package/dist/util.cjs ADDED
@@ -0,0 +1,185 @@
1
+ //#region src/util.ts
2
+ /**
3
+ * Converts a string to a URL-safe slug.
4
+ *
5
+ * Normalizes unicode, lowercases, trims, and replaces non-alphanumeric
6
+ * characters with hyphens.
7
+ */
8
+ function slugify(input) {
9
+ return input.normalize("NFKD").replace(/[̀-ͯ]/g, "").toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
10
+ }
11
+ const DATE_PREFIX_RE = /^(\d{4})-(\d{2})-(\d{2})[-_.](.+)$/;
12
+ /**
13
+ * Strips a `YYYY-MM-DD` prefix from a filename stem.
14
+ *
15
+ * @param name - Filename stem without extension.
16
+ * @returns The extracted ISO date and the remaining name, or just `rest` if no prefix found.
17
+ */
18
+ function splitDatePrefix(name) {
19
+ const m = DATE_PREFIX_RE.exec(name);
20
+ if (!m) return { rest: name };
21
+ return {
22
+ date: `${m[1]}-${m[2]}-${m[3]}`,
23
+ rest: m[4]
24
+ };
25
+ }
26
+ const ORDER_PREFIX_RE = /^(\d{1,4})[-_.](.+)$/;
27
+ /**
28
+ * Strips a numeric order prefix (up to 4 digits) from a filename or directory stem.
29
+ *
30
+ * @param name - Filename stem without extension, e.g. `"01-getting-started"`.
31
+ * @returns The extracted order number and the remaining name.
32
+ */
33
+ function splitOrderPrefix(name) {
34
+ const m = ORDER_PREFIX_RE.exec(name);
35
+ if (!m) return { rest: name };
36
+ return {
37
+ order: Number(m[1]),
38
+ rest: m[2]
39
+ };
40
+ }
41
+ /**
42
+ * Converts a slug or filename stem to a human-readable title.
43
+ *
44
+ * @param segment - Slug segment, e.g. `"getting-started"`.
45
+ * @returns Title-cased string, e.g. `"Getting Started"`.
46
+ */
47
+ function humanize(segment) {
48
+ return segment.replace(/[-_]+/g, " ").replace(/\b\w/g, (ch) => ch.toUpperCase()).trim();
49
+ }
50
+ /**
51
+ * Joins a base path and a slug into a clean URL path.
52
+ *
53
+ * Ensures exactly one leading slash and removes duplicate slashes.
54
+ */
55
+ function joinPath(base, slug) {
56
+ const left = base.replace(/\/+$/, "");
57
+ const right = slug.replace(/^\/+/, "");
58
+ return `${left.startsWith("/") ? left : `/${left}`}/${right}`.replace(/([^:])\/{2,}/g, "$1/");
59
+ }
60
+ /**
61
+ * Resolves a site-relative path to a full absolute URL.
62
+ *
63
+ * Returns the path unchanged if it already starts with `http(s)://`.
64
+ *
65
+ * @param siteUrl - Canonical site origin, e.g. `https://example.com`.
66
+ * @param path - Absolute or relative URL path.
67
+ */
68
+ function absoluteUrl(siteUrl, path) {
69
+ if (/^https?:\/\//i.test(path)) return path;
70
+ return `${siteUrl.replace(/\/+$/, "")}${path.startsWith("/") ? path : `/${path}`}`;
71
+ }
72
+ /**
73
+ * Estimates reading time in minutes, assuming 200 words per minute.
74
+ * Always returns at least 1.
75
+ */
76
+ function readingTimeMinutes(markdown) {
77
+ const words = markdown.trim().split(/\s+/).filter(Boolean).length;
78
+ return Math.max(1, Math.round(words / 200));
79
+ }
80
+ /**
81
+ * Extracts a plain-text excerpt from Markdown, stripping code blocks,
82
+ * headings, and inline markup.
83
+ *
84
+ * @param markdown - Raw Markdown source.
85
+ * @param max - Maximum character length before truncation. Defaults to `180`.
86
+ */
87
+ function deriveExcerpt(markdown, max = 180) {
88
+ const text = markdown.replace(/```[\s\S]*?```/g, " ").replace(/`[^`]*`/g, " ").replace(/^#{1,6}\s.*$/gm, " ").replace(/!\[[^\]]*\]\([^)]*\)/g, " ").replace(/\[([^\]]*)\]\([^)]*\)/g, "$1").replace(/[*_>#~]/g, " ").replace(/\s+/g, " ").replace(/\s+([.,;:!?])/g, "$1").trim();
89
+ if (text.length <= max) return text;
90
+ return `${text.slice(0, max).replace(/\s+\S*$/, "")}…`;
91
+ }
92
+ /**
93
+ * Formats an ISO 8601 date string for display.
94
+ *
95
+ * @param iso - ISO date string, e.g. `"2024-01-15"`.
96
+ * @param locale - BCP 47 locale. Defaults to `"en-US"`.
97
+ * @returns Formatted string like `"January 15, 2024"`, or the original string if invalid.
98
+ */
99
+ function formatDate(iso, locale = "en-US") {
100
+ const d = new Date(iso);
101
+ if (Number.isNaN(d.getTime())) return iso;
102
+ return d.toLocaleDateString(locale, {
103
+ year: "numeric",
104
+ month: "long",
105
+ day: "numeric"
106
+ });
107
+ }
108
+ const VERSION_RE = /^v?(\d+(?:\.\d+)*(?:[-.][\w.]+)?)$/i;
109
+ /**
110
+ * Parses a semver string from a filename stem, stripping any leading `v`.
111
+ *
112
+ * @param name - Filename stem, e.g. `"v1.2.3"` or `"1.0.0-rc.1"`.
113
+ * @returns Normalized version string, or `undefined` if not a valid version.
114
+ */
115
+ function parseVersion(name) {
116
+ const m = VERSION_RE.exec(name);
117
+ return m ? m[1] : void 0;
118
+ }
119
+ /**
120
+ * Compares two semver-like version strings numerically.
121
+ *
122
+ * @returns Negative if `a < b`, positive if `a > b`, zero if equal.
123
+ */
124
+ function compareVersions(a, b) {
125
+ const partsA = a.split(/[-.]/);
126
+ const partsB = b.split(/[-.]/);
127
+ const len = Math.max(partsA.length, partsB.length);
128
+ for (let i = 0; i < len; i++) {
129
+ const ai = partsA[i] ?? "";
130
+ const bi = partsB[i] ?? "";
131
+ const na = Number(ai);
132
+ const nb = Number(bi);
133
+ if (!Number.isNaN(na) && !Number.isNaN(nb)) {
134
+ if (na !== nb) return na - nb;
135
+ } else if (ai !== bi) {
136
+ if (ai === "") return 1;
137
+ if (bi === "") return -1;
138
+ return ai < bi ? -1 : 1;
139
+ }
140
+ }
141
+ return 0;
142
+ }
143
+ /**
144
+ * Escapes a string for safe inclusion in XML/HTML attribute values and text nodes.
145
+ */
146
+ function escapeXml(value) {
147
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
148
+ }
149
+ /**
150
+ * Serializes a JSON-LD object to a string safe for inline `<script>` tags.
151
+ *
152
+ * Escapes `<`, `>`, `&`, and Unicode line/paragraph separators to prevent
153
+ * XSS and parser errors.
154
+ */
155
+ function serializeJsonLd(data) {
156
+ return JSON.stringify(data).replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
157
+ }
158
+ /** Formats an ISO 8601 date string as RFC 822 (used in RSS feeds). */
159
+ function rfc822(date) {
160
+ const d = new Date(date);
161
+ return (isNaN(d.getTime()) ? /* @__PURE__ */ new Date() : d).toUTCString();
162
+ }
163
+ /** Formats an ISO 8601 date string as `YYYY-MM-DD` (used in sitemaps). */
164
+ function isoDate(date) {
165
+ const d = new Date(date);
166
+ return (isNaN(d.getTime()) ? /* @__PURE__ */ new Date() : d).toISOString().slice(0, 10);
167
+ }
168
+ //#endregion
169
+ exports.absoluteUrl = absoluteUrl;
170
+ exports.compareVersions = compareVersions;
171
+ exports.deriveExcerpt = deriveExcerpt;
172
+ exports.escapeXml = escapeXml;
173
+ exports.formatDate = formatDate;
174
+ exports.humanize = humanize;
175
+ exports.isoDate = isoDate;
176
+ exports.joinPath = joinPath;
177
+ exports.parseVersion = parseVersion;
178
+ exports.readingTimeMinutes = readingTimeMinutes;
179
+ exports.rfc822 = rfc822;
180
+ exports.serializeJsonLd = serializeJsonLd;
181
+ exports.slugify = slugify;
182
+ exports.splitDatePrefix = splitDatePrefix;
183
+ exports.splitOrderPrefix = splitOrderPrefix;
184
+
185
+ //# sourceMappingURL=util.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"util.cjs","names":[],"sources":["../src/util.ts"],"sourcesContent":["/**\n * Converts a string to a URL-safe slug.\n *\n * Normalizes unicode, lowercases, trims, and replaces non-alphanumeric\n * characters with hyphens.\n */\nexport function slugify(input: string): string {\n return input\n .normalize(\"NFKD\")\n .replace(/[̀-ͯ]/g, \"\")\n .toLowerCase()\n .trim()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n}\n\nconst DATE_PREFIX_RE = /^(\\d{4})-(\\d{2})-(\\d{2})[-_.](.+)$/;\n\n/**\n * Strips a `YYYY-MM-DD` prefix from a filename stem.\n *\n * @param name - Filename stem without extension.\n * @returns The extracted ISO date and the remaining name, or just `rest` if no prefix found.\n */\nexport function splitDatePrefix(name: string): { date?: string; rest: string } {\n const m = DATE_PREFIX_RE.exec(name);\n if (!m) return { rest: name };\n return { date: `${m[1]}-${m[2]}-${m[3]}`, rest: m[4]! };\n}\n\nconst ORDER_PREFIX_RE = /^(\\d{1,4})[-_.](.+)$/;\n\n/**\n * Strips a numeric order prefix (up to 4 digits) from a filename or directory stem.\n *\n * @param name - Filename stem without extension, e.g. `\"01-getting-started\"`.\n * @returns The extracted order number and the remaining name.\n */\nexport function splitOrderPrefix(name: string): {\n order?: number;\n rest: string;\n} {\n const m = ORDER_PREFIX_RE.exec(name);\n if (!m) return { rest: name };\n return { order: Number(m[1]), rest: m[2]! };\n}\n\n/**\n * Converts a slug or filename stem to a human-readable title.\n *\n * @param segment - Slug segment, e.g. `\"getting-started\"`.\n * @returns Title-cased string, e.g. `\"Getting Started\"`.\n */\nexport function humanize(segment: string): string {\n return segment\n .replace(/[-_]+/g, \" \")\n .replace(/\\b\\w/g, (ch) => ch.toUpperCase())\n .trim();\n}\n\n/**\n * Joins a base path and a slug into a clean URL path.\n *\n * Ensures exactly one leading slash and removes duplicate slashes.\n */\nexport function joinPath(base: string, slug: string): string {\n const left = base.replace(/\\/+$/, \"\");\n const right = slug.replace(/^\\/+/, \"\");\n const prefix = left.startsWith(\"/\") ? left : `/${left}`;\n return `${prefix}/${right}`.replace(/([^:])\\/{2,}/g, \"$1/\");\n}\n\n/**\n * Resolves a site-relative path to a full absolute URL.\n *\n * Returns the path unchanged if it already starts with `http(s)://`.\n *\n * @param siteUrl - Canonical site origin, e.g. `https://example.com`.\n * @param path - Absolute or relative URL path.\n */\nexport function absoluteUrl(siteUrl: string, path: string): string {\n if (/^https?:\\/\\//i.test(path)) return path;\n const origin = siteUrl.replace(/\\/+$/, \"\");\n const rel = path.startsWith(\"/\") ? path : `/${path}`;\n return `${origin}${rel}`;\n}\n\n/**\n * Estimates reading time in minutes, assuming 200 words per minute.\n * Always returns at least 1.\n */\nexport function readingTimeMinutes(markdown: string): number {\n const words = markdown.trim().split(/\\s+/).filter(Boolean).length;\n return Math.max(1, Math.round(words / 200));\n}\n\n/**\n * Extracts a plain-text excerpt from Markdown, stripping code blocks,\n * headings, and inline markup.\n *\n * @param markdown - Raw Markdown source.\n * @param max - Maximum character length before truncation. Defaults to `180`.\n */\nexport function deriveExcerpt(markdown: string, max = 180): string {\n const text = markdown\n .replace(/```[\\s\\S]*?```/g, \" \")\n .replace(/`[^`]*`/g, \" \")\n .replace(/^#{1,6}\\s.*$/gm, \" \")\n .replace(/!\\[[^\\]]*\\]\\([^)]*\\)/g, \" \")\n .replace(/\\[([^\\]]*)\\]\\([^)]*\\)/g, \"$1\")\n .replace(/[*_>#~]/g, \" \")\n .replace(/\\s+/g, \" \")\n .replace(/\\s+([.,;:!?])/g, \"$1\")\n .trim();\n if (text.length <= max) return text;\n return `${text.slice(0, max).replace(/\\s+\\S*$/, \"\")}…`;\n}\n\n/**\n * Formats an ISO 8601 date string for display.\n *\n * @param iso - ISO date string, e.g. `\"2024-01-15\"`.\n * @param locale - BCP 47 locale. Defaults to `\"en-US\"`.\n * @returns Formatted string like `\"January 15, 2024\"`, or the original string if invalid.\n */\nexport function formatDate(iso: string, locale = \"en-US\"): string {\n const d = new Date(iso);\n if (Number.isNaN(d.getTime())) return iso;\n return d.toLocaleDateString(locale, {\n year: \"numeric\",\n month: \"long\",\n day: \"numeric\",\n });\n}\n\nconst VERSION_RE = /^v?(\\d+(?:\\.\\d+)*(?:[-.][\\w.]+)?)$/i;\n\n/**\n * Parses a semver string from a filename stem, stripping any leading `v`.\n *\n * @param name - Filename stem, e.g. `\"v1.2.3\"` or `\"1.0.0-rc.1\"`.\n * @returns Normalized version string, or `undefined` if not a valid version.\n */\nexport function parseVersion(name: string): string | undefined {\n const m = VERSION_RE.exec(name);\n return m ? m[1] : undefined;\n}\n\n/**\n * Compares two semver-like version strings numerically.\n *\n * @returns Negative if `a < b`, positive if `a > b`, zero if equal.\n */\nexport function compareVersions(a: string, b: string): number {\n const partsA = a.split(/[-.]/);\n const partsB = b.split(/[-.]/);\n const len = Math.max(partsA.length, partsB.length);\n for (let i = 0; i < len; i++) {\n const ai = partsA[i] ?? \"\";\n const bi = partsB[i] ?? \"\";\n const na = Number(ai);\n const nb = Number(bi);\n const bothNumeric = !Number.isNaN(na) && !Number.isNaN(nb);\n if (bothNumeric) {\n if (na !== nb) return na - nb;\n } else if (ai !== bi) {\n if (ai === \"\") return 1;\n if (bi === \"\") return -1;\n return ai < bi ? -1 : 1;\n }\n }\n return 0;\n}\n\n/**\n * Escapes a string for safe inclusion in XML/HTML attribute values and text nodes.\n */\nexport function escapeXml(value: string): string {\n return value\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/'/g, \"&apos;\");\n}\n\n/**\n * Serializes a JSON-LD object to a string safe for inline `<script>` tags.\n *\n * Escapes `<`, `>`, `&`, and Unicode line/paragraph separators to prevent\n * XSS and parser errors.\n */\nexport function serializeJsonLd(data: unknown): string {\n return JSON.stringify(data)\n .replace(/</g, \"\\\\u003c\")\n .replace(/>/g, \"\\\\u003e\")\n .replace(/&/g, \"\\\\u0026\")\n .replace(/\\u2028/g, \"\\\\u2028\")\n .replace(/\\u2029/g, \"\\\\u2029\");\n}\n\n/** Formats an ISO 8601 date string as RFC 822 (used in RSS feeds). */\nexport function rfc822(date: string): string {\n const d = new Date(date);\n return (isNaN(d.getTime()) ? new Date() : d).toUTCString();\n}\n\n/** Formats an ISO 8601 date string as `YYYY-MM-DD` (used in sitemaps). */\nexport function isoDate(date: string): string {\n const d = new Date(date);\n return (isNaN(d.getTime()) ? new Date() : d).toISOString().slice(0, 10);\n}\n"],"mappings":"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"}
@@ -0,0 +1,98 @@
1
+ //#region src/util.d.ts
2
+ /**
3
+ * Converts a string to a URL-safe slug.
4
+ *
5
+ * Normalizes unicode, lowercases, trims, and replaces non-alphanumeric
6
+ * characters with hyphens.
7
+ */
8
+ declare function slugify(input: string): string;
9
+ /**
10
+ * Strips a `YYYY-MM-DD` prefix from a filename stem.
11
+ *
12
+ * @param name - Filename stem without extension.
13
+ * @returns The extracted ISO date and the remaining name, or just `rest` if no prefix found.
14
+ */
15
+ declare function splitDatePrefix(name: string): {
16
+ date?: string;
17
+ rest: string;
18
+ };
19
+ /**
20
+ * Strips a numeric order prefix (up to 4 digits) from a filename or directory stem.
21
+ *
22
+ * @param name - Filename stem without extension, e.g. `"01-getting-started"`.
23
+ * @returns The extracted order number and the remaining name.
24
+ */
25
+ declare function splitOrderPrefix(name: string): {
26
+ order?: number;
27
+ rest: string;
28
+ };
29
+ /**
30
+ * Converts a slug or filename stem to a human-readable title.
31
+ *
32
+ * @param segment - Slug segment, e.g. `"getting-started"`.
33
+ * @returns Title-cased string, e.g. `"Getting Started"`.
34
+ */
35
+ declare function humanize(segment: string): string;
36
+ /**
37
+ * Joins a base path and a slug into a clean URL path.
38
+ *
39
+ * Ensures exactly one leading slash and removes duplicate slashes.
40
+ */
41
+ declare function joinPath(base: string, slug: string): string;
42
+ /**
43
+ * Resolves a site-relative path to a full absolute URL.
44
+ *
45
+ * Returns the path unchanged if it already starts with `http(s)://`.
46
+ *
47
+ * @param siteUrl - Canonical site origin, e.g. `https://example.com`.
48
+ * @param path - Absolute or relative URL path.
49
+ */
50
+ declare function absoluteUrl(siteUrl: string, path: string): string;
51
+ /**
52
+ * Estimates reading time in minutes, assuming 200 words per minute.
53
+ * Always returns at least 1.
54
+ */
55
+ declare function readingTimeMinutes(markdown: string): number;
56
+ /**
57
+ * Extracts a plain-text excerpt from Markdown, stripping code blocks,
58
+ * headings, and inline markup.
59
+ *
60
+ * @param markdown - Raw Markdown source.
61
+ * @param max - Maximum character length before truncation. Defaults to `180`.
62
+ */
63
+ declare function deriveExcerpt(markdown: string, max?: number): string;
64
+ /**
65
+ * Formats an ISO 8601 date string for display.
66
+ *
67
+ * @param iso - ISO date string, e.g. `"2024-01-15"`.
68
+ * @param locale - BCP 47 locale. Defaults to `"en-US"`.
69
+ * @returns Formatted string like `"January 15, 2024"`, or the original string if invalid.
70
+ */
71
+ declare function formatDate(iso: string, locale?: string): string;
72
+ /**
73
+ * Parses a semver string from a filename stem, stripping any leading `v`.
74
+ *
75
+ * @param name - Filename stem, e.g. `"v1.2.3"` or `"1.0.0-rc.1"`.
76
+ * @returns Normalized version string, or `undefined` if not a valid version.
77
+ */
78
+ declare function parseVersion(name: string): string | undefined;
79
+ /**
80
+ * Compares two semver-like version strings numerically.
81
+ *
82
+ * @returns Negative if `a < b`, positive if `a > b`, zero if equal.
83
+ */
84
+ declare function compareVersions(a: string, b: string): number;
85
+ /**
86
+ * Escapes a string for safe inclusion in XML/HTML attribute values and text nodes.
87
+ */
88
+ declare function escapeXml(value: string): string;
89
+ /**
90
+ * Serializes a JSON-LD object to a string safe for inline `<script>` tags.
91
+ *
92
+ * Escapes `<`, `>`, `&`, and Unicode line/paragraph separators to prevent
93
+ * XSS and parser errors.
94
+ */
95
+ declare function serializeJsonLd(data: unknown): string;
96
+ //#endregion
97
+ export { absoluteUrl, compareVersions, deriveExcerpt, escapeXml, formatDate, humanize, joinPath, parseVersion, readingTimeMinutes, serializeJsonLd, slugify, splitDatePrefix, splitOrderPrefix };
98
+ //# sourceMappingURL=util.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"util.d.cts","names":[],"sources":["../src/util.ts"],"mappings":";;AAMA;;;;AAAqC;iBAArB,OAAA,CAAQ,KAAa;;;;;;;iBAkBrB,eAAA,CAAgB,IAAA;EAAiB,IAAA;EAAe,IAAA;AAAA;;;;;;;iBAchD,gBAAA,CAAiB,IAAA;EAC/B,KAAA;EACA,IAAA;AAAA;;AAasC;AAYxC;;;;iBAZgB,QAAA,CAAS,OAAe;AA2BxC;;;;AAAyD;AAAzD,iBAfgB,QAAA,CAAS,IAAA,UAAc,IAAY;;;;AA0BA;AAYnD;;;;iBAvBgB,WAAA,CAAY,OAAA,UAAiB,IAAY;AA6CzD;;;;AAAA,iBAlCgB,kBAAA,CAAmB,QAAgB;AAoDnD;;;;AAAyC;AAUzC;;AAVA,iBAxCgB,aAAA,CAAc,QAAA,UAAkB,GAAS;;AAkDL;AAwBpD;;;;AAAuC;iBApDvB,UAAA,CAAW,GAAA,UAAa,MAAgB;;;;AAmEX;;;iBAjD7B,YAAA,CAAa,IAAY;;;;;;iBAUzB,eAAA,CAAgB,CAAA,UAAW,CAAS;;;;iBAwBpC,SAAA,CAAU,KAAa;;;;;;;iBAevB,eAAA,CAAgB,IAAa"}