@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
package/dist/dev.mjs ADDED
@@ -0,0 +1,82 @@
1
+ import { loadConfigEffect } from "./config.mjs";
2
+ import { Effect } from "effect";
3
+ import { NodeContext } from "@effect/platform-node";
4
+ import { existsSync, mkdirSync, readdirSync, watch, writeFileSync } from "node:fs";
5
+ import { basename, dirname, join } from "node:path";
6
+ //#region src/dev.ts
7
+ const run = (effect) => Effect.runPromise(Effect.provide(effect, NodeContext.layer));
8
+ const SKIP_DIRS = new Set([
9
+ "node_modules",
10
+ ".next",
11
+ ".git",
12
+ ".turbo",
13
+ "dist",
14
+ "out"
15
+ ]);
16
+ const VERSION_FILE = "content-version.ts";
17
+ function writeVersion(file, value) {
18
+ mkdirSync(dirname(file), { recursive: true });
19
+ writeFileSync(file, `export const CONTENT_VERSION = ${value};\n`);
20
+ }
21
+ function findVersionModules(root, depth = 6) {
22
+ const found = [];
23
+ const walk = (dir, left) => {
24
+ let entries;
25
+ try {
26
+ entries = readdirSync(dir, { withFileTypes: true });
27
+ } catch {
28
+ return;
29
+ }
30
+ for (const entry of entries) if (entry.isDirectory()) {
31
+ if (SKIP_DIRS.has(entry.name) || entry.name.startsWith(".")) continue;
32
+ if (left > 0) walk(join(dir, entry.name), left - 1);
33
+ } else if (entry.name === VERSION_FILE && basename(dir) === "_voxx") found.push(join(dir, entry.name));
34
+ };
35
+ walk(root, depth);
36
+ return found;
37
+ }
38
+ let registered = false;
39
+ /**
40
+ * Watches content directories and `voxx.json` for changes, then bumps a
41
+ * `CONTENT_VERSION` timestamp in each discovered `_voxx/content-version.ts`
42
+ * file to trigger Next.js hot reload.
43
+ *
44
+ * No-ops outside `NODE_ENV=development` and in non-Node runtimes (e.g. Edge).
45
+ * Safe to call multiple times — only the first call registers watchers.
46
+ *
47
+ * @param opts - Optional cwd, explicit version module paths, and debounce delay.
48
+ */
49
+ async function registerContentWatcher(opts = {}) {
50
+ if (process.env["NEXT_RUNTIME"] && process.env["NEXT_RUNTIME"] !== "nodejs") return;
51
+ if (process.env.NODE_ENV !== "development") return;
52
+ if (registered) return;
53
+ registered = true;
54
+ const cwd = opts.cwd ?? process.cwd();
55
+ const versionModules = opts.versionModules && opts.versionModules.length > 0 ? opts.versionModules : findVersionModules(cwd);
56
+ if (versionModules.length === 0) return;
57
+ const dirs = /* @__PURE__ */ new Set();
58
+ try {
59
+ const config = await run(loadConfigEffect({ cwd }));
60
+ for (const collection of config.collections) dirs.add(collection.dir);
61
+ } catch {}
62
+ const paths = new Set([join(cwd, "voxx.json"), ...dirs]);
63
+ let timer;
64
+ const bump = () => {
65
+ clearTimeout(timer);
66
+ timer = setTimeout(() => {
67
+ const value = Date.now();
68
+ for (const file of versionModules) writeVersion(file, value);
69
+ }, opts.debounceMs ?? 80);
70
+ };
71
+ const watchers = [];
72
+ for (const path of paths) {
73
+ if (!existsSync(path)) continue;
74
+ try {
75
+ watchers.push(watch(path, { recursive: true }, bump));
76
+ } catch {}
77
+ }
78
+ }
79
+ //#endregion
80
+ export { registerContentWatcher };
81
+
82
+ //# sourceMappingURL=dev.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dev.mjs","names":[],"sources":["../src/dev.ts"],"sourcesContent":["import {\n existsSync,\n mkdirSync,\n readdirSync,\n watch,\n writeFileSync,\n type Dirent,\n type FSWatcher,\n} from \"node:fs\";\nimport { basename, dirname, join } from \"node:path\";\nimport { Effect } from \"effect\";\nimport { FileSystem, Path } from \"@effect/platform\";\nimport { NodeContext } from \"@effect/platform-node\";\nimport { loadConfigEffect } from \"./config\";\n\ntype Services = FileSystem.FileSystem | Path.Path;\n\nconst run = <A, E>(effect: Effect.Effect<A, E, Services>): Promise<A> =>\n Effect.runPromise(Effect.provide(effect, NodeContext.layer));\n\n/** Options for `registerContentWatcher`. */\nexport interface ContentWatcherOptions {\n /** Working directory used to locate `voxx.json` and content dirs. Defaults to `process.cwd()`. */\n cwd?: string;\n /** Explicit paths to `content-version.ts` files to bump on change. Auto-discovered when omitted. */\n versionModules?: string[];\n /** Milliseconds to wait before writing after the last change event. Defaults to `80`. */\n debounceMs?: number;\n}\n\nconst SKIP_DIRS = new Set([\n \"node_modules\",\n \".next\",\n \".git\",\n \".turbo\",\n \"dist\",\n \"out\",\n]);\nconst VERSION_FILE = \"content-version.ts\";\n\nfunction writeVersion(file: string, value: number): void {\n mkdirSync(dirname(file), { recursive: true });\n writeFileSync(file, `export const CONTENT_VERSION = ${value};\\n`);\n}\n\nfunction findVersionModules(root: string, depth = 6): string[] {\n const found: string[] = [];\n const walk = (dir: string, left: number): void => {\n let entries: Dirent<string>[];\n try {\n entries = readdirSync(dir, { withFileTypes: true });\n } catch {\n return;\n }\n for (const entry of entries) {\n if (entry.isDirectory()) {\n if (SKIP_DIRS.has(entry.name) || entry.name.startsWith(\".\")) continue;\n if (left > 0) walk(join(dir, entry.name), left - 1);\n } else if (entry.name === VERSION_FILE && basename(dir) === \"_voxx\") {\n found.push(join(dir, entry.name));\n }\n }\n };\n walk(root, depth);\n return found;\n}\n\nlet registered = false;\n\n/**\n * Watches content directories and `voxx.json` for changes, then bumps a\n * `CONTENT_VERSION` timestamp in each discovered `_voxx/content-version.ts`\n * file to trigger Next.js hot reload.\n *\n * No-ops outside `NODE_ENV=development` and in non-Node runtimes (e.g. Edge).\n * Safe to call multiple times — only the first call registers watchers.\n *\n * @param opts - Optional cwd, explicit version module paths, and debounce delay.\n */\nexport async function registerContentWatcher(\n opts: ContentWatcherOptions = {},\n): Promise<void> {\n if (process.env[\"NEXT_RUNTIME\"] && process.env[\"NEXT_RUNTIME\"] !== \"nodejs\") {\n return;\n }\n if (process.env.NODE_ENV !== \"development\") return;\n if (registered) return;\n registered = true;\n\n const cwd = opts.cwd ?? process.cwd();\n\n const versionModules =\n opts.versionModules && opts.versionModules.length > 0\n ? opts.versionModules\n : findVersionModules(cwd);\n if (versionModules.length === 0) return;\n\n const dirs = new Set<string>();\n try {\n const config = await run(loadConfigEffect({ cwd }));\n for (const collection of config.collections) dirs.add(collection.dir);\n } catch {}\n\n const paths = new Set<string>([join(cwd, \"voxx.json\"), ...dirs]);\n\n let timer: ReturnType<typeof setTimeout> | undefined;\n const bump = () => {\n clearTimeout(timer);\n timer = setTimeout(() => {\n const value = Date.now();\n for (const file of versionModules) writeVersion(file, value);\n }, opts.debounceMs ?? 80);\n };\n\n const watchers: FSWatcher[] = [];\n for (const path of paths) {\n if (!existsSync(path)) continue;\n try {\n watchers.push(watch(path, { recursive: true }, bump));\n } catch {}\n }\n}\n"],"mappings":";;;;;;AAiBA,MAAM,OAAa,WACjB,OAAO,WAAW,OAAO,QAAQ,QAAQ,YAAY,KAAK,CAAC;AAY7D,MAAM,YAAY,IAAI,IAAI;CACxB;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AACD,MAAM,eAAe;AAErB,SAAS,aAAa,MAAc,OAAqB;CACvD,UAAU,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;CAC5C,cAAc,MAAM,kCAAkC,MAAM,IAAI;AAClE;AAEA,SAAS,mBAAmB,MAAc,QAAQ,GAAa;CAC7D,MAAM,QAAkB,CAAC;CACzB,MAAM,QAAQ,KAAa,SAAuB;EAChD,IAAI;EACJ,IAAI;GACF,UAAU,YAAY,KAAK,EAAE,eAAe,KAAK,CAAC;EACpD,QAAQ;GACN;EACF;EACA,KAAK,MAAM,SAAS,SAClB,IAAI,MAAM,YAAY,GAAG;GACvB,IAAI,UAAU,IAAI,MAAM,IAAI,KAAK,MAAM,KAAK,WAAW,GAAG,GAAG;GAC7D,IAAI,OAAO,GAAG,KAAK,KAAK,KAAK,MAAM,IAAI,GAAG,OAAO,CAAC;EACpD,OAAO,IAAI,MAAM,SAAS,gBAAgB,SAAS,GAAG,MAAM,SAC1D,MAAM,KAAK,KAAK,KAAK,MAAM,IAAI,CAAC;CAGtC;CACA,KAAK,MAAM,KAAK;CAChB,OAAO;AACT;AAEA,IAAI,aAAa;;;;;;;;;;;AAYjB,eAAsB,uBACpB,OAA8B,CAAC,GAChB;CACf,IAAI,QAAQ,IAAI,mBAAmB,QAAQ,IAAI,oBAAoB,UACjE;CAEF,IAAI,QAAQ,IAAI,aAAa,eAAe;CAC5C,IAAI,YAAY;CAChB,aAAa;CAEb,MAAM,MAAM,KAAK,OAAO,QAAQ,IAAI;CAEpC,MAAM,iBACJ,KAAK,kBAAkB,KAAK,eAAe,SAAS,IAChD,KAAK,iBACL,mBAAmB,GAAG;CAC5B,IAAI,eAAe,WAAW,GAAG;CAEjC,MAAM,uBAAO,IAAI,IAAY;CAC7B,IAAI;EACF,MAAM,SAAS,MAAM,IAAI,iBAAiB,EAAE,IAAI,CAAC,CAAC;EAClD,KAAK,MAAM,cAAc,OAAO,aAAa,KAAK,IAAI,WAAW,GAAG;CACtE,QAAQ,CAAC;CAET,MAAM,QAAQ,IAAI,IAAY,CAAC,KAAK,KAAK,WAAW,GAAG,GAAG,IAAI,CAAC;CAE/D,IAAI;CACJ,MAAM,aAAa;EACjB,aAAa,KAAK;EAClB,QAAQ,iBAAiB;GACvB,MAAM,QAAQ,KAAK,IAAI;GACvB,KAAK,MAAM,QAAQ,gBAAgB,aAAa,MAAM,KAAK;EAC7D,GAAG,KAAK,cAAc,EAAE;CAC1B;CAEA,MAAM,WAAwB,CAAC;CAC/B,KAAK,MAAM,QAAQ,OAAO;EACxB,IAAI,CAAC,WAAW,IAAI,GAAG;EACvB,IAAI;GACF,SAAS,KAAK,MAAM,MAAM,EAAE,WAAW,KAAK,GAAG,IAAI,CAAC;EACtD,QAAQ,CAAC;CACX;AACF"}
@@ -0,0 +1,23 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_schema = require("./schema.cjs");
3
+ const require_errors = require("./errors.cjs");
4
+ const require_types = require("./types.cjs");
5
+ const require_config = require("./config.cjs");
6
+ const require_frontmatter = require("./frontmatter.cjs");
7
+ const require_render = require("./render.cjs");
8
+ const require_content = require("./content.cjs");
9
+ exports.ConfigError = require_errors.ConfigError;
10
+ exports.ConfigInput = require_schema.ConfigInput;
11
+ exports.ContentDirMissing = require_errors.ContentDirMissing;
12
+ exports.DEFAULT_CONFIG = require_types.DEFAULT_CONFIG;
13
+ exports.Frontmatter = require_schema.Frontmatter;
14
+ exports.InvalidFrontmatter = require_errors.InvalidFrontmatter;
15
+ exports.PostNotFound = require_errors.PostNotFound;
16
+ exports.RenderError = require_errors.RenderError;
17
+ exports.findPost = require_content.findPost;
18
+ exports.getPostEffect = require_content.getPostEffect;
19
+ exports.getPostsEffect = require_content.getPostsEffect;
20
+ exports.loadConfigEffect = require_config.loadConfigEffect;
21
+ exports.parseFrontmatter = require_frontmatter.parseFrontmatter;
22
+ exports.renderMarkdownEffect = require_render.renderMarkdownEffect;
23
+ exports.resolveConfig = require_config.resolveConfig;
@@ -0,0 +1,8 @@
1
+ import { ConfigError, ContentDirMissing, InvalidFrontmatter, PostNotFound, RenderError, VoxxError } from "./errors.cjs";
2
+ import { ConfigInput, Frontmatter, FrontmatterData, VoxxConfigInput } from "./schema.cjs";
3
+ import { CollectionConfig, ContentType, DEFAULT_CONFIG, NavNode, Post, SeoData, TocItem, VoxxAuthor, VoxxConfig } from "./types.cjs";
4
+ import { LoadConfigOptions, loadConfigEffect, resolveConfig } from "./config.cjs";
5
+ import { GetPostsEffectOptions, findPost, getPostEffect, getPostsEffect } from "./content.cjs";
6
+ import { RenderResult, renderMarkdownEffect } from "./render.cjs";
7
+ import { ParsedFile, parseFrontmatter } from "./frontmatter.cjs";
8
+ export { type CollectionConfig, ConfigError, ConfigInput, ContentDirMissing, type ContentType, DEFAULT_CONFIG, Frontmatter, type FrontmatterData, type GetPostsEffectOptions, InvalidFrontmatter, type LoadConfigOptions, type NavNode, type ParsedFile, type Post, PostNotFound, RenderError, type RenderResult, type SeoData, type TocItem, type VoxxAuthor, type VoxxConfig, type VoxxConfigInput, VoxxError, findPost, getPostEffect, getPostsEffect, loadConfigEffect, parseFrontmatter, renderMarkdownEffect, resolveConfig };
@@ -0,0 +1,8 @@
1
+ import { ConfigError, ContentDirMissing, InvalidFrontmatter, PostNotFound, RenderError, VoxxError } from "./errors.mjs";
2
+ import { ConfigInput, Frontmatter, FrontmatterData, VoxxConfigInput } from "./schema.mjs";
3
+ import { CollectionConfig, ContentType, DEFAULT_CONFIG, NavNode, Post, SeoData, TocItem, VoxxAuthor, VoxxConfig } from "./types.mjs";
4
+ import { LoadConfigOptions, loadConfigEffect, resolveConfig } from "./config.mjs";
5
+ import { GetPostsEffectOptions, findPost, getPostEffect, getPostsEffect } from "./content.mjs";
6
+ import { RenderResult, renderMarkdownEffect } from "./render.mjs";
7
+ import { ParsedFile, parseFrontmatter } from "./frontmatter.mjs";
8
+ export { type CollectionConfig, ConfigError, ConfigInput, ContentDirMissing, type ContentType, DEFAULT_CONFIG, Frontmatter, type FrontmatterData, type GetPostsEffectOptions, InvalidFrontmatter, type LoadConfigOptions, type NavNode, type ParsedFile, type Post, PostNotFound, RenderError, type RenderResult, type SeoData, type TocItem, type VoxxAuthor, type VoxxConfig, type VoxxConfigInput, VoxxError, findPost, getPostEffect, getPostsEffect, loadConfigEffect, parseFrontmatter, renderMarkdownEffect, resolveConfig };
@@ -0,0 +1,8 @@
1
+ import { ConfigInput, Frontmatter } from "./schema.mjs";
2
+ import { ConfigError, ContentDirMissing, InvalidFrontmatter, PostNotFound, RenderError } from "./errors.mjs";
3
+ import { DEFAULT_CONFIG } from "./types.mjs";
4
+ import { loadConfigEffect, resolveConfig } from "./config.mjs";
5
+ import { parseFrontmatter } from "./frontmatter.mjs";
6
+ import { renderMarkdownEffect } from "./render.mjs";
7
+ import { findPost, getPostEffect, getPostsEffect } from "./content.mjs";
8
+ export { ConfigError, ConfigInput, ContentDirMissing, DEFAULT_CONFIG, Frontmatter, InvalidFrontmatter, PostNotFound, RenderError, findPost, getPostEffect, getPostsEffect, loadConfigEffect, parseFrontmatter, renderMarkdownEffect, resolveConfig };
@@ -0,0 +1,20 @@
1
+ let effect = require("effect");
2
+ //#region src/errors.ts
3
+ /** Thrown when `voxx.json` is missing, unreadable, or fails schema validation. */
4
+ var ConfigError = class extends effect.Data.TaggedError("ConfigError") {};
5
+ /** Thrown when a Markdown file's frontmatter cannot be parsed or is invalid. */
6
+ var InvalidFrontmatter = class extends effect.Data.TaggedError("InvalidFrontmatter") {};
7
+ /** Thrown when a requested slug does not match any loaded post. */
8
+ var PostNotFound = class extends effect.Data.TaggedError("PostNotFound") {};
9
+ /** Thrown when the configured content directory does not exist. */
10
+ var ContentDirMissing = class extends effect.Data.TaggedError("ContentDirMissing") {};
11
+ /** Thrown when the Markdown-to-HTML pipeline fails. */
12
+ var RenderError = class extends effect.Data.TaggedError("RenderError") {};
13
+ //#endregion
14
+ exports.ConfigError = ConfigError;
15
+ exports.ContentDirMissing = ContentDirMissing;
16
+ exports.InvalidFrontmatter = InvalidFrontmatter;
17
+ exports.PostNotFound = PostNotFound;
18
+ exports.RenderError = RenderError;
19
+
20
+ //# sourceMappingURL=errors.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.cjs","names":["Data"],"sources":["../src/errors.ts"],"sourcesContent":["import { Data } from \"effect\";\n\n/** Thrown when `voxx.json` is missing, unreadable, or fails schema validation. */\nexport class ConfigError extends Data.TaggedError(\"ConfigError\")<{\n readonly message: string;\n readonly cause?: unknown;\n}> {}\n\n/** Thrown when a Markdown file's frontmatter cannot be parsed or is invalid. */\nexport class InvalidFrontmatter extends Data.TaggedError(\"InvalidFrontmatter\")<{\n readonly file: string;\n readonly message: string;\n readonly cause?: unknown;\n}> {}\n\n/** Thrown when a requested slug does not match any loaded post. */\nexport class PostNotFound extends Data.TaggedError(\"PostNotFound\")<{\n readonly slug: string;\n}> {}\n\n/** Thrown when the configured content directory does not exist. */\nexport class ContentDirMissing extends Data.TaggedError(\"ContentDirMissing\")<{\n readonly dir: string;\n}> {}\n\n/** Thrown when the Markdown-to-HTML pipeline fails. */\nexport class RenderError extends Data.TaggedError(\"RenderError\")<{\n readonly message: string;\n readonly cause?: unknown;\n}> {}\n\n/** Union of all typed errors that Voxx can surface. */\nexport type VoxxError =\n | ConfigError\n | InvalidFrontmatter\n | PostNotFound\n | ContentDirMissing\n | RenderError;\n"],"mappings":";;;AAGA,IAAa,cAAb,cAAiCA,OAAAA,KAAK,YAAY,aAAa,CAAC,CAG7D,CAAC;;AAGJ,IAAa,qBAAb,cAAwCA,OAAAA,KAAK,YAAY,oBAAoB,CAAC,CAI3E,CAAC;;AAGJ,IAAa,eAAb,cAAkCA,OAAAA,KAAK,YAAY,cAAc,CAAC,CAE/D,CAAC;;AAGJ,IAAa,oBAAb,cAAuCA,OAAAA,KAAK,YAAY,mBAAmB,CAAC,CAEzE,CAAC;;AAGJ,IAAa,cAAb,cAAiCA,OAAAA,KAAK,YAAY,aAAa,CAAC,CAG7D,CAAC"}
@@ -0,0 +1,45 @@
1
+ //#region src/errors.d.ts
2
+ declare const ConfigError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }>) => import("effect/Cause").YieldableError & {
3
+ readonly _tag: "ConfigError";
4
+ } & Readonly<A>;
5
+ /** Thrown when `voxx.json` is missing, unreadable, or fails schema validation. */
6
+ declare class ConfigError extends ConfigError_base<{
7
+ readonly message: string;
8
+ readonly cause?: unknown;
9
+ }> {}
10
+ declare const InvalidFrontmatter_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }>) => import("effect/Cause").YieldableError & {
11
+ readonly _tag: "InvalidFrontmatter";
12
+ } & Readonly<A>;
13
+ /** Thrown when a Markdown file's frontmatter cannot be parsed or is invalid. */
14
+ declare class InvalidFrontmatter extends InvalidFrontmatter_base<{
15
+ readonly file: string;
16
+ readonly message: string;
17
+ readonly cause?: unknown;
18
+ }> {}
19
+ declare const PostNotFound_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }>) => import("effect/Cause").YieldableError & {
20
+ readonly _tag: "PostNotFound";
21
+ } & Readonly<A>;
22
+ /** Thrown when a requested slug does not match any loaded post. */
23
+ declare class PostNotFound extends PostNotFound_base<{
24
+ readonly slug: string;
25
+ }> {}
26
+ declare const ContentDirMissing_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }>) => import("effect/Cause").YieldableError & {
27
+ readonly _tag: "ContentDirMissing";
28
+ } & Readonly<A>;
29
+ /** Thrown when the configured content directory does not exist. */
30
+ declare class ContentDirMissing extends ContentDirMissing_base<{
31
+ readonly dir: string;
32
+ }> {}
33
+ declare const RenderError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }>) => import("effect/Cause").YieldableError & {
34
+ readonly _tag: "RenderError";
35
+ } & Readonly<A>;
36
+ /** Thrown when the Markdown-to-HTML pipeline fails. */
37
+ declare class RenderError extends RenderError_base<{
38
+ readonly message: string;
39
+ readonly cause?: unknown;
40
+ }> {}
41
+ /** Union of all typed errors that Voxx can surface. */
42
+ type VoxxError = ConfigError | InvalidFrontmatter | PostNotFound | ContentDirMissing | RenderError;
43
+ //#endregion
44
+ export { ConfigError, ContentDirMissing, InvalidFrontmatter, PostNotFound, RenderError, VoxxError };
45
+ //# sourceMappingURL=errors.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.cts","names":[],"sources":["../src/errors.ts"],"mappings":";;;;;cAGa,WAAA,SAAoB,gBAAA;EAAA,SACtB,OAAA;EAAA,SACA,KAAA;AAAA;AAAA,cACN,uBAAA;;;;cAGQ,kBAAA,SAA2B,uBAAA;EAAA,SAC7B,IAAA;EAAA,SACA,OAAA;EAAA,SACA,KAAA;AAAA;AAAA,cACN,iBAAA;;;;cAGQ,YAAA,SAAqB,iBAAA;EAAA,SACvB,IAAI;AAAA;AAAA,cACV,sBAAA;;;AAfL;AAAA,cAkBa,iBAAA,SAA0B,sBAAA;EAAA,SAC5B,GAAG;AAAA;AAAA,cACT,gBAAA;;;AAlBW;AAAA,cAqBH,WAAA,SAAoB,gBAAA;EAAA,SACtB,OAAA;EAAA,SACA,KAAA;AAAA;;KAIC,SAAA,GACR,WAAA,GACA,kBAAA,GACA,YAAA,GACA,iBAAA,GACA,WAAA"}
@@ -0,0 +1,45 @@
1
+ //#region src/errors.d.ts
2
+ declare const ConfigError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }>) => import("effect/Cause").YieldableError & {
3
+ readonly _tag: "ConfigError";
4
+ } & Readonly<A>;
5
+ /** Thrown when `voxx.json` is missing, unreadable, or fails schema validation. */
6
+ declare class ConfigError extends ConfigError_base<{
7
+ readonly message: string;
8
+ readonly cause?: unknown;
9
+ }> {}
10
+ declare const InvalidFrontmatter_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }>) => import("effect/Cause").YieldableError & {
11
+ readonly _tag: "InvalidFrontmatter";
12
+ } & Readonly<A>;
13
+ /** Thrown when a Markdown file's frontmatter cannot be parsed or is invalid. */
14
+ declare class InvalidFrontmatter extends InvalidFrontmatter_base<{
15
+ readonly file: string;
16
+ readonly message: string;
17
+ readonly cause?: unknown;
18
+ }> {}
19
+ declare const PostNotFound_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }>) => import("effect/Cause").YieldableError & {
20
+ readonly _tag: "PostNotFound";
21
+ } & Readonly<A>;
22
+ /** Thrown when a requested slug does not match any loaded post. */
23
+ declare class PostNotFound extends PostNotFound_base<{
24
+ readonly slug: string;
25
+ }> {}
26
+ declare const ContentDirMissing_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }>) => import("effect/Cause").YieldableError & {
27
+ readonly _tag: "ContentDirMissing";
28
+ } & Readonly<A>;
29
+ /** Thrown when the configured content directory does not exist. */
30
+ declare class ContentDirMissing extends ContentDirMissing_base<{
31
+ readonly dir: string;
32
+ }> {}
33
+ declare const RenderError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }>) => import("effect/Cause").YieldableError & {
34
+ readonly _tag: "RenderError";
35
+ } & Readonly<A>;
36
+ /** Thrown when the Markdown-to-HTML pipeline fails. */
37
+ declare class RenderError extends RenderError_base<{
38
+ readonly message: string;
39
+ readonly cause?: unknown;
40
+ }> {}
41
+ /** Union of all typed errors that Voxx can surface. */
42
+ type VoxxError = ConfigError | InvalidFrontmatter | PostNotFound | ContentDirMissing | RenderError;
43
+ //#endregion
44
+ export { ConfigError, ContentDirMissing, InvalidFrontmatter, PostNotFound, RenderError, VoxxError };
45
+ //# sourceMappingURL=errors.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.mts","names":[],"sources":["../src/errors.ts"],"mappings":";;;;;cAGa,WAAA,SAAoB,gBAAA;EAAA,SACtB,OAAA;EAAA,SACA,KAAA;AAAA;AAAA,cACN,uBAAA;;;;cAGQ,kBAAA,SAA2B,uBAAA;EAAA,SAC7B,IAAA;EAAA,SACA,OAAA;EAAA,SACA,KAAA;AAAA;AAAA,cACN,iBAAA;;;;cAGQ,YAAA,SAAqB,iBAAA;EAAA,SACvB,IAAI;AAAA;AAAA,cACV,sBAAA;;;AAfL;AAAA,cAkBa,iBAAA,SAA0B,sBAAA;EAAA,SAC5B,GAAG;AAAA;AAAA,cACT,gBAAA;;;AAlBW;AAAA,cAqBH,WAAA,SAAoB,gBAAA;EAAA,SACtB,OAAA;EAAA,SACA,KAAA;AAAA;;KAIC,SAAA,GACR,WAAA,GACA,kBAAA,GACA,YAAA,GACA,iBAAA,GACA,WAAA"}
@@ -0,0 +1,16 @@
1
+ import { Data } from "effect";
2
+ //#region src/errors.ts
3
+ /** Thrown when `voxx.json` is missing, unreadable, or fails schema validation. */
4
+ var ConfigError = class extends Data.TaggedError("ConfigError") {};
5
+ /** Thrown when a Markdown file's frontmatter cannot be parsed or is invalid. */
6
+ var InvalidFrontmatter = class extends Data.TaggedError("InvalidFrontmatter") {};
7
+ /** Thrown when a requested slug does not match any loaded post. */
8
+ var PostNotFound = class extends Data.TaggedError("PostNotFound") {};
9
+ /** Thrown when the configured content directory does not exist. */
10
+ var ContentDirMissing = class extends Data.TaggedError("ContentDirMissing") {};
11
+ /** Thrown when the Markdown-to-HTML pipeline fails. */
12
+ var RenderError = class extends Data.TaggedError("RenderError") {};
13
+ //#endregion
14
+ export { ConfigError, ContentDirMissing, InvalidFrontmatter, PostNotFound, RenderError };
15
+
16
+ //# sourceMappingURL=errors.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.mjs","names":[],"sources":["../src/errors.ts"],"sourcesContent":["import { Data } from \"effect\";\n\n/** Thrown when `voxx.json` is missing, unreadable, or fails schema validation. */\nexport class ConfigError extends Data.TaggedError(\"ConfigError\")<{\n readonly message: string;\n readonly cause?: unknown;\n}> {}\n\n/** Thrown when a Markdown file's frontmatter cannot be parsed or is invalid. */\nexport class InvalidFrontmatter extends Data.TaggedError(\"InvalidFrontmatter\")<{\n readonly file: string;\n readonly message: string;\n readonly cause?: unknown;\n}> {}\n\n/** Thrown when a requested slug does not match any loaded post. */\nexport class PostNotFound extends Data.TaggedError(\"PostNotFound\")<{\n readonly slug: string;\n}> {}\n\n/** Thrown when the configured content directory does not exist. */\nexport class ContentDirMissing extends Data.TaggedError(\"ContentDirMissing\")<{\n readonly dir: string;\n}> {}\n\n/** Thrown when the Markdown-to-HTML pipeline fails. */\nexport class RenderError extends Data.TaggedError(\"RenderError\")<{\n readonly message: string;\n readonly cause?: unknown;\n}> {}\n\n/** Union of all typed errors that Voxx can surface. */\nexport type VoxxError =\n | ConfigError\n | InvalidFrontmatter\n | PostNotFound\n | ContentDirMissing\n | RenderError;\n"],"mappings":";;;AAGA,IAAa,cAAb,cAAiC,KAAK,YAAY,aAAa,CAAC,CAG7D,CAAC;;AAGJ,IAAa,qBAAb,cAAwC,KAAK,YAAY,oBAAoB,CAAC,CAI3E,CAAC;;AAGJ,IAAa,eAAb,cAAkC,KAAK,YAAY,cAAc,CAAC,CAE/D,CAAC;;AAGJ,IAAa,oBAAb,cAAuC,KAAK,YAAY,mBAAmB,CAAC,CAEzE,CAAC;;AAGJ,IAAa,cAAb,cAAiC,KAAK,YAAY,aAAa,CAAC,CAG7D,CAAC"}
package/dist/feeds.cjs ADDED
@@ -0,0 +1,97 @@
1
+ const require_util = require("./util.cjs");
2
+ //#region src/feeds.ts
3
+ const cdata = (value) => `<![CDATA[${value.replace(/\]\]>/g, "]]]]><![CDATA[>")}]]>`;
4
+ /**
5
+ * Returns the default RSS feed path for the active content collection.
6
+ *
7
+ * @param config - Resolved Voxx config.
8
+ * @returns URL path string, e.g. `/blog/rss.xml`.
9
+ */
10
+ function rssPath(config) {
11
+ return require_util.joinPath(config.content.basePath, "rss.xml");
12
+ }
13
+ /**
14
+ * Renders a valid RSS 2.0 feed with `content:encoded` for all posts.
15
+ *
16
+ * @param posts - Ordered list of posts (most recent first).
17
+ * @param config - Resolved Voxx config.
18
+ * @param opts - Optional feed path override.
19
+ * @returns RSS XML string.
20
+ */
21
+ function renderRss(posts, config, opts = {}) {
22
+ const self = require_util.absoluteUrl(config.site.url, opts.path ?? rssPath(config));
23
+ const items = posts.map((p) => {
24
+ const link = require_util.absoluteUrl(config.site.url, p.url);
25
+ const categories = config.features.tags ? p.tags.map((t) => ` <category>${require_util.escapeXml(t)}</category>`).join("\n") : "";
26
+ return [
27
+ " <item>",
28
+ ` <title>${require_util.escapeXml(p.title)}</title>`,
29
+ ` <link>${require_util.escapeXml(link)}</link>`,
30
+ ` <guid isPermaLink="true">${require_util.escapeXml(link)}</guid>`,
31
+ ` <pubDate>${require_util.rfc822(p.date)}</pubDate>`,
32
+ ` <description>${require_util.escapeXml(p.description ?? p.excerpt)}</description>`,
33
+ ` <content:encoded>${cdata(p.html)}</content:encoded>`,
34
+ categories,
35
+ " </item>"
36
+ ].filter((l) => l !== "").join("\n");
37
+ }).join("\n");
38
+ return `<?xml version="1.0" encoding="UTF-8"?>
39
+ <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
40
+ <channel>
41
+ <title>${require_util.escapeXml(config.site.title)}</title>
42
+ <link>${require_util.escapeXml(config.site.url)}</link>
43
+ <description>${require_util.escapeXml(config.site.description)}</description>
44
+ <language>${require_util.escapeXml(config.site.locale)}</language>
45
+ <lastBuildDate>${require_util.rfc822(posts[0]?.date ?? (/* @__PURE__ */ new Date()).toISOString())}</lastBuildDate>
46
+ <atom:link href="${require_util.escapeXml(self)}" rel="self" type="application/rss+xml"/>
47
+ ${items}
48
+ </channel>
49
+ </rss>
50
+ `;
51
+ }
52
+ /**
53
+ * Renders an XML sitemap for all posts.
54
+ *
55
+ * @param posts - List of posts to include.
56
+ * @param config - Resolved Voxx config.
57
+ * @param opts - Optional extra index paths.
58
+ * @returns Sitemap XML string.
59
+ */
60
+ function renderSitemap(posts, config, opts = {}) {
61
+ const lastmod = require_util.isoDate(posts[0]?.updated ?? posts[0]?.date ?? (/* @__PURE__ */ new Date()).toISOString());
62
+ return `<?xml version="1.0" encoding="UTF-8"?>
63
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
64
+ ${[...(opts.indexPaths ?? [config.content.basePath]).map((p) => ({
65
+ loc: require_util.absoluteUrl(config.site.url, p),
66
+ lastmod
67
+ })), ...posts.map((p) => ({
68
+ loc: require_util.absoluteUrl(config.site.url, p.url),
69
+ lastmod: require_util.isoDate(p.updated ?? p.date)
70
+ }))].map((u) => [
71
+ " <url>",
72
+ ` <loc>${require_util.escapeXml(u.loc)}</loc>`,
73
+ ` <lastmod>${u.lastmod}</lastmod>`,
74
+ " </url>"
75
+ ].join("\n")).join("\n")}
76
+ </urlset>
77
+ `;
78
+ }
79
+ /**
80
+ * Renders a `robots.txt` that allows all crawlers and optionally
81
+ * references the sitemap when `features.sitemap` is enabled.
82
+ *
83
+ * @param config - Resolved Voxx config.
84
+ * @returns `robots.txt` content string.
85
+ */
86
+ function renderRobotsTxt(config) {
87
+ const lines = ["User-agent: *", "Allow: /"];
88
+ if (config.features.sitemap) lines.push("", `Sitemap: ${require_util.absoluteUrl(config.site.url, "/sitemap.xml")}`);
89
+ return `${lines.join("\n")}\n`;
90
+ }
91
+ //#endregion
92
+ exports.renderRobotsTxt = renderRobotsTxt;
93
+ exports.renderRss = renderRss;
94
+ exports.renderSitemap = renderSitemap;
95
+ exports.rssPath = rssPath;
96
+
97
+ //# sourceMappingURL=feeds.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"feeds.cjs","names":["joinPath","absoluteUrl","escapeXml","rfc822","isoDate"],"sources":["../src/feeds.ts"],"sourcesContent":["import { absoluteUrl, escapeXml, isoDate, joinPath, rfc822 } from \"./util\";\nimport type { Post, VoxxConfig } from \"./types\";\n\n/** Options for `renderRss`. */\nexport interface RenderRssOptions {\n /** Override the default RSS feed path (`<basePath>/rss.xml`). */\n path?: string;\n}\n\nconst cdata = (value: string) =>\n `<![CDATA[${value.replace(/\\]\\]>/g, \"]]]]><![CDATA[>\")}]]>`;\n\n/**\n * Returns the default RSS feed path for the active content collection.\n *\n * @param config - Resolved Voxx config.\n * @returns URL path string, e.g. `/blog/rss.xml`.\n */\nexport function rssPath(config: VoxxConfig): string {\n return joinPath(config.content.basePath, \"rss.xml\");\n}\n\n/**\n * Renders a valid RSS 2.0 feed with `content:encoded` for all posts.\n *\n * @param posts - Ordered list of posts (most recent first).\n * @param config - Resolved Voxx config.\n * @param opts - Optional feed path override.\n * @returns RSS XML string.\n */\nexport function renderRss(\n posts: Post[],\n config: VoxxConfig,\n opts: RenderRssOptions = {},\n): string {\n const self = absoluteUrl(config.site.url, opts.path ?? rssPath(config));\n const items = posts\n .map((p) => {\n const link = absoluteUrl(config.site.url, p.url);\n const categories = config.features.tags\n ? p.tags\n .map((t) => ` <category>${escapeXml(t)}</category>`)\n .join(\"\\n\")\n : \"\";\n return [\n \" <item>\",\n ` <title>${escapeXml(p.title)}</title>`,\n ` <link>${escapeXml(link)}</link>`,\n ` <guid isPermaLink=\"true\">${escapeXml(link)}</guid>`,\n ` <pubDate>${rfc822(p.date)}</pubDate>`,\n ` <description>${escapeXml(p.description ?? p.excerpt)}</description>`,\n ` <content:encoded>${cdata(p.html)}</content:encoded>`,\n categories,\n \" </item>\",\n ]\n .filter((l) => l !== \"\")\n .join(\"\\n\");\n })\n .join(\"\\n\");\n\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<rss version=\"2.0\" xmlns:atom=\"http://www.w3.org/2005/Atom\" xmlns:content=\"http://purl.org/rss/1.0/modules/content/\">\n <channel>\n <title>${escapeXml(config.site.title)}</title>\n <link>${escapeXml(config.site.url)}</link>\n <description>${escapeXml(config.site.description)}</description>\n <language>${escapeXml(config.site.locale)}</language>\n <lastBuildDate>${rfc822(posts[0]?.date ?? new Date().toISOString())}</lastBuildDate>\n <atom:link href=\"${escapeXml(self)}\" rel=\"self\" type=\"application/rss+xml\"/>\n${items}\n </channel>\n</rss>\n`;\n}\n\n/** Options for `renderSitemap`. */\nexport interface RenderSitemapOptions {\n /** Additional index paths to include before the post entries. */\n indexPaths?: string[];\n}\n\n/**\n * Renders an XML sitemap for all posts.\n *\n * @param posts - List of posts to include.\n * @param config - Resolved Voxx config.\n * @param opts - Optional extra index paths.\n * @returns Sitemap XML string.\n */\nexport function renderSitemap(\n posts: Post[],\n config: VoxxConfig,\n opts: RenderSitemapOptions = {},\n): string {\n const lastmod = isoDate(\n posts[0]?.updated ?? posts[0]?.date ?? new Date().toISOString(),\n );\n const entries = [\n ...(opts.indexPaths ?? [config.content.basePath]).map((p) => ({\n loc: absoluteUrl(config.site.url, p),\n lastmod,\n })),\n ...posts.map((p) => ({\n loc: absoluteUrl(config.site.url, p.url),\n lastmod: isoDate(p.updated ?? p.date),\n })),\n ];\n\n const body = entries\n .map((u) =>\n [\n \" <url>\",\n ` <loc>${escapeXml(u.loc)}</loc>`,\n ` <lastmod>${u.lastmod}</lastmod>`,\n \" </url>\",\n ].join(\"\\n\"),\n )\n .join(\"\\n\");\n\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n${body}\n</urlset>\n`;\n}\n\n/**\n * Renders a `robots.txt` that allows all crawlers and optionally\n * references the sitemap when `features.sitemap` is enabled.\n *\n * @param config - Resolved Voxx config.\n * @returns `robots.txt` content string.\n */\nexport function renderRobotsTxt(config: VoxxConfig): string {\n const lines = [\"User-agent: *\", \"Allow: /\"];\n if (config.features.sitemap) {\n lines.push(\"\", `Sitemap: ${absoluteUrl(config.site.url, \"/sitemap.xml\")}`);\n }\n return `${lines.join(\"\\n\")}\\n`;\n}\n"],"mappings":";;AASA,MAAM,SAAS,UACb,YAAY,MAAM,QAAQ,UAAU,iBAAiB,EAAE;;;;;;;AAQzD,SAAgB,QAAQ,QAA4B;CAClD,OAAOA,aAAAA,SAAS,OAAO,QAAQ,UAAU,SAAS;AACpD;;;;;;;;;AAUA,SAAgB,UACd,OACA,QACA,OAAyB,CAAC,GAClB;CACR,MAAM,OAAOC,aAAAA,YAAY,OAAO,KAAK,KAAK,KAAK,QAAQ,QAAQ,MAAM,CAAC;CACtE,MAAM,QAAQ,MACX,KAAK,MAAM;EACV,MAAM,OAAOA,aAAAA,YAAY,OAAO,KAAK,KAAK,EAAE,GAAG;EAC/C,MAAM,aAAa,OAAO,SAAS,OAC/B,EAAE,KACC,KAAK,MAAM,mBAAmBC,aAAAA,UAAU,CAAC,EAAE,YAAY,CAAC,CACxD,KAAK,IAAI,IACZ;EACJ,OAAO;GACL;GACA,gBAAgBA,aAAAA,UAAU,EAAE,KAAK,EAAE;GACnC,eAAeA,aAAAA,UAAU,IAAI,EAAE;GAC/B,kCAAkCA,aAAAA,UAAU,IAAI,EAAE;GAClD,kBAAkBC,aAAAA,OAAO,EAAE,IAAI,EAAE;GACjC,sBAAsBD,aAAAA,UAAU,EAAE,eAAe,EAAE,OAAO,EAAE;GAC5D,0BAA0B,MAAM,EAAE,IAAI,EAAE;GACxC;GACA;EACF,CAAC,CACE,QAAQ,MAAM,MAAM,EAAE,CAAC,CACvB,KAAK,IAAI;CACd,CAAC,CAAC,CACD,KAAK,IAAI;CAEZ,OAAO;;;aAGIA,aAAAA,UAAU,OAAO,KAAK,KAAK,EAAE;YAC9BA,aAAAA,UAAU,OAAO,KAAK,GAAG,EAAE;mBACpBA,aAAAA,UAAU,OAAO,KAAK,WAAW,EAAE;gBACtCA,aAAAA,UAAU,OAAO,KAAK,MAAM,EAAE;qBACzBC,aAAAA,OAAO,MAAM,EAAE,EAAE,yBAAQ,IAAI,KAAK,EAAA,CAAE,YAAY,CAAC,EAAE;uBACjDD,aAAAA,UAAU,IAAI,EAAE;EACrC,MAAM;;;;AAIR;;;;;;;;;AAgBA,SAAgB,cACd,OACA,QACA,OAA6B,CAAC,GACtB;CACR,MAAM,UAAUE,aAAAA,QACd,MAAM,EAAE,EAAE,WAAW,MAAM,EAAE,EAAE,yBAAQ,IAAI,KAAK,EAAA,CAAE,YAAY,CAChE;CAuBA,OAAO;;EAXM,CAVX,IAAI,KAAK,cAAc,CAAC,OAAO,QAAQ,QAAQ,EAAA,CAAG,KAAK,OAAO;EAC5D,KAAKH,aAAAA,YAAY,OAAO,KAAK,KAAK,CAAC;EACnC;CACF,EAAE,GACF,GAAG,MAAM,KAAK,OAAO;EACnB,KAAKA,aAAAA,YAAY,OAAO,KAAK,KAAK,EAAE,GAAG;EACvC,SAASG,aAAAA,QAAQ,EAAE,WAAW,EAAE,IAAI;CACtC,EAAE,CAGe,CAAC,CACjB,KAAK,MACJ;EACE;EACA,YAAYF,aAAAA,UAAU,EAAE,GAAG,EAAE;EAC7B,gBAAgB,EAAE,QAAQ;EAC1B;CACF,CAAC,CAAC,KAAK,IAAI,CACb,CAAC,CACA,KAAK,IAIL,EAAE;;;AAGP;;;;;;;;AASA,SAAgB,gBAAgB,QAA4B;CAC1D,MAAM,QAAQ,CAAC,iBAAiB,UAAU;CAC1C,IAAI,OAAO,SAAS,SAClB,MAAM,KAAK,IAAI,YAAYD,aAAAA,YAAY,OAAO,KAAK,KAAK,cAAc,GAAG;CAE3E,OAAO,GAAG,MAAM,KAAK,IAAI,EAAE;AAC7B"}
@@ -0,0 +1,49 @@
1
+ import { Post, VoxxConfig } from "./types.cjs";
2
+
3
+ //#region src/feeds.d.ts
4
+ /** Options for `renderRss`. */
5
+ interface RenderRssOptions {
6
+ /** Override the default RSS feed path (`<basePath>/rss.xml`). */
7
+ path?: string;
8
+ }
9
+ /**
10
+ * Returns the default RSS feed path for the active content collection.
11
+ *
12
+ * @param config - Resolved Voxx config.
13
+ * @returns URL path string, e.g. `/blog/rss.xml`.
14
+ */
15
+ declare function rssPath(config: VoxxConfig): string;
16
+ /**
17
+ * Renders a valid RSS 2.0 feed with `content:encoded` for all posts.
18
+ *
19
+ * @param posts - Ordered list of posts (most recent first).
20
+ * @param config - Resolved Voxx config.
21
+ * @param opts - Optional feed path override.
22
+ * @returns RSS XML string.
23
+ */
24
+ declare function renderRss(posts: Post[], config: VoxxConfig, opts?: RenderRssOptions): string;
25
+ /** Options for `renderSitemap`. */
26
+ interface RenderSitemapOptions {
27
+ /** Additional index paths to include before the post entries. */
28
+ indexPaths?: string[];
29
+ }
30
+ /**
31
+ * Renders an XML sitemap for all posts.
32
+ *
33
+ * @param posts - List of posts to include.
34
+ * @param config - Resolved Voxx config.
35
+ * @param opts - Optional extra index paths.
36
+ * @returns Sitemap XML string.
37
+ */
38
+ declare function renderSitemap(posts: Post[], config: VoxxConfig, opts?: RenderSitemapOptions): string;
39
+ /**
40
+ * Renders a `robots.txt` that allows all crawlers and optionally
41
+ * references the sitemap when `features.sitemap` is enabled.
42
+ *
43
+ * @param config - Resolved Voxx config.
44
+ * @returns `robots.txt` content string.
45
+ */
46
+ declare function renderRobotsTxt(config: VoxxConfig): string;
47
+ //#endregion
48
+ export { RenderRssOptions, RenderSitemapOptions, renderRobotsTxt, renderRss, renderSitemap, rssPath };
49
+ //# sourceMappingURL=feeds.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"feeds.d.cts","names":[],"sources":["../src/feeds.ts"],"mappings":";;;;UAIiB,gBAAA;EAAA;EAEf,IAAI;AAAA;;AAAA;AAYN;;;;iBAAgB,OAAA,CAAQ,MAAkB,EAAV,UAAU;AAY1C;;;;;;;;AAAA,iBAAgB,SAAA,CACd,KAAA,EAAO,IAAA,IACP,MAAA,EAAQ,UAAA,EACR,IAAA,GAAM,gBAAA;;UA2CS,oBAAA;EA5Cf;EA8CA,UAAU;AAAA;;AA7CiB;AA2C7B;;;;AAEY;AAWZ;iBAAgB,aAAA,CACd,KAAA,EAAO,IAAA,IACP,MAAA,EAAQ,UAAA,EACR,IAAA,GAAM,oBAAA;;;;;;;;iBAyCQ,eAAA,CAAgB,MAAkB,EAAV,UAAU"}
@@ -0,0 +1,49 @@
1
+ import { Post, VoxxConfig } from "./types.mjs";
2
+
3
+ //#region src/feeds.d.ts
4
+ /** Options for `renderRss`. */
5
+ interface RenderRssOptions {
6
+ /** Override the default RSS feed path (`<basePath>/rss.xml`). */
7
+ path?: string;
8
+ }
9
+ /**
10
+ * Returns the default RSS feed path for the active content collection.
11
+ *
12
+ * @param config - Resolved Voxx config.
13
+ * @returns URL path string, e.g. `/blog/rss.xml`.
14
+ */
15
+ declare function rssPath(config: VoxxConfig): string;
16
+ /**
17
+ * Renders a valid RSS 2.0 feed with `content:encoded` for all posts.
18
+ *
19
+ * @param posts - Ordered list of posts (most recent first).
20
+ * @param config - Resolved Voxx config.
21
+ * @param opts - Optional feed path override.
22
+ * @returns RSS XML string.
23
+ */
24
+ declare function renderRss(posts: Post[], config: VoxxConfig, opts?: RenderRssOptions): string;
25
+ /** Options for `renderSitemap`. */
26
+ interface RenderSitemapOptions {
27
+ /** Additional index paths to include before the post entries. */
28
+ indexPaths?: string[];
29
+ }
30
+ /**
31
+ * Renders an XML sitemap for all posts.
32
+ *
33
+ * @param posts - List of posts to include.
34
+ * @param config - Resolved Voxx config.
35
+ * @param opts - Optional extra index paths.
36
+ * @returns Sitemap XML string.
37
+ */
38
+ declare function renderSitemap(posts: Post[], config: VoxxConfig, opts?: RenderSitemapOptions): string;
39
+ /**
40
+ * Renders a `robots.txt` that allows all crawlers and optionally
41
+ * references the sitemap when `features.sitemap` is enabled.
42
+ *
43
+ * @param config - Resolved Voxx config.
44
+ * @returns `robots.txt` content string.
45
+ */
46
+ declare function renderRobotsTxt(config: VoxxConfig): string;
47
+ //#endregion
48
+ export { RenderRssOptions, RenderSitemapOptions, renderRobotsTxt, renderRss, renderSitemap, rssPath };
49
+ //# sourceMappingURL=feeds.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"feeds.d.mts","names":[],"sources":["../src/feeds.ts"],"mappings":";;;;UAIiB,gBAAA;EAAA;EAEf,IAAI;AAAA;;AAAA;AAYN;;;;iBAAgB,OAAA,CAAQ,MAAkB,EAAV,UAAU;AAY1C;;;;;;;;AAAA,iBAAgB,SAAA,CACd,KAAA,EAAO,IAAA,IACP,MAAA,EAAQ,UAAA,EACR,IAAA,GAAM,gBAAA;;UA2CS,oBAAA;EA5Cf;EA8CA,UAAU;AAAA;;AA7CiB;AA2C7B;;;;AAEY;AAWZ;iBAAgB,aAAA,CACd,KAAA,EAAO,IAAA,IACP,MAAA,EAAQ,UAAA,EACR,IAAA,GAAM,oBAAA;;;;;;;;iBAyCQ,eAAA,CAAgB,MAAkB,EAAV,UAAU"}
package/dist/feeds.mjs ADDED
@@ -0,0 +1,94 @@
1
+ import { absoluteUrl, escapeXml, isoDate, joinPath, rfc822 } from "./util.mjs";
2
+ //#region src/feeds.ts
3
+ const cdata = (value) => `<![CDATA[${value.replace(/\]\]>/g, "]]]]><![CDATA[>")}]]>`;
4
+ /**
5
+ * Returns the default RSS feed path for the active content collection.
6
+ *
7
+ * @param config - Resolved Voxx config.
8
+ * @returns URL path string, e.g. `/blog/rss.xml`.
9
+ */
10
+ function rssPath(config) {
11
+ return joinPath(config.content.basePath, "rss.xml");
12
+ }
13
+ /**
14
+ * Renders a valid RSS 2.0 feed with `content:encoded` for all posts.
15
+ *
16
+ * @param posts - Ordered list of posts (most recent first).
17
+ * @param config - Resolved Voxx config.
18
+ * @param opts - Optional feed path override.
19
+ * @returns RSS XML string.
20
+ */
21
+ function renderRss(posts, config, opts = {}) {
22
+ const self = absoluteUrl(config.site.url, opts.path ?? rssPath(config));
23
+ const items = posts.map((p) => {
24
+ const link = absoluteUrl(config.site.url, p.url);
25
+ const categories = config.features.tags ? p.tags.map((t) => ` <category>${escapeXml(t)}</category>`).join("\n") : "";
26
+ return [
27
+ " <item>",
28
+ ` <title>${escapeXml(p.title)}</title>`,
29
+ ` <link>${escapeXml(link)}</link>`,
30
+ ` <guid isPermaLink="true">${escapeXml(link)}</guid>`,
31
+ ` <pubDate>${rfc822(p.date)}</pubDate>`,
32
+ ` <description>${escapeXml(p.description ?? p.excerpt)}</description>`,
33
+ ` <content:encoded>${cdata(p.html)}</content:encoded>`,
34
+ categories,
35
+ " </item>"
36
+ ].filter((l) => l !== "").join("\n");
37
+ }).join("\n");
38
+ return `<?xml version="1.0" encoding="UTF-8"?>
39
+ <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
40
+ <channel>
41
+ <title>${escapeXml(config.site.title)}</title>
42
+ <link>${escapeXml(config.site.url)}</link>
43
+ <description>${escapeXml(config.site.description)}</description>
44
+ <language>${escapeXml(config.site.locale)}</language>
45
+ <lastBuildDate>${rfc822(posts[0]?.date ?? (/* @__PURE__ */ new Date()).toISOString())}</lastBuildDate>
46
+ <atom:link href="${escapeXml(self)}" rel="self" type="application/rss+xml"/>
47
+ ${items}
48
+ </channel>
49
+ </rss>
50
+ `;
51
+ }
52
+ /**
53
+ * Renders an XML sitemap for all posts.
54
+ *
55
+ * @param posts - List of posts to include.
56
+ * @param config - Resolved Voxx config.
57
+ * @param opts - Optional extra index paths.
58
+ * @returns Sitemap XML string.
59
+ */
60
+ function renderSitemap(posts, config, opts = {}) {
61
+ const lastmod = isoDate(posts[0]?.updated ?? posts[0]?.date ?? (/* @__PURE__ */ new Date()).toISOString());
62
+ return `<?xml version="1.0" encoding="UTF-8"?>
63
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
64
+ ${[...(opts.indexPaths ?? [config.content.basePath]).map((p) => ({
65
+ loc: absoluteUrl(config.site.url, p),
66
+ lastmod
67
+ })), ...posts.map((p) => ({
68
+ loc: absoluteUrl(config.site.url, p.url),
69
+ lastmod: isoDate(p.updated ?? p.date)
70
+ }))].map((u) => [
71
+ " <url>",
72
+ ` <loc>${escapeXml(u.loc)}</loc>`,
73
+ ` <lastmod>${u.lastmod}</lastmod>`,
74
+ " </url>"
75
+ ].join("\n")).join("\n")}
76
+ </urlset>
77
+ `;
78
+ }
79
+ /**
80
+ * Renders a `robots.txt` that allows all crawlers and optionally
81
+ * references the sitemap when `features.sitemap` is enabled.
82
+ *
83
+ * @param config - Resolved Voxx config.
84
+ * @returns `robots.txt` content string.
85
+ */
86
+ function renderRobotsTxt(config) {
87
+ const lines = ["User-agent: *", "Allow: /"];
88
+ if (config.features.sitemap) lines.push("", `Sitemap: ${absoluteUrl(config.site.url, "/sitemap.xml")}`);
89
+ return `${lines.join("\n")}\n`;
90
+ }
91
+ //#endregion
92
+ export { renderRobotsTxt, renderRss, renderSitemap, rssPath };
93
+
94
+ //# sourceMappingURL=feeds.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"feeds.mjs","names":[],"sources":["../src/feeds.ts"],"sourcesContent":["import { absoluteUrl, escapeXml, isoDate, joinPath, rfc822 } from \"./util\";\nimport type { Post, VoxxConfig } from \"./types\";\n\n/** Options for `renderRss`. */\nexport interface RenderRssOptions {\n /** Override the default RSS feed path (`<basePath>/rss.xml`). */\n path?: string;\n}\n\nconst cdata = (value: string) =>\n `<![CDATA[${value.replace(/\\]\\]>/g, \"]]]]><![CDATA[>\")}]]>`;\n\n/**\n * Returns the default RSS feed path for the active content collection.\n *\n * @param config - Resolved Voxx config.\n * @returns URL path string, e.g. `/blog/rss.xml`.\n */\nexport function rssPath(config: VoxxConfig): string {\n return joinPath(config.content.basePath, \"rss.xml\");\n}\n\n/**\n * Renders a valid RSS 2.0 feed with `content:encoded` for all posts.\n *\n * @param posts - Ordered list of posts (most recent first).\n * @param config - Resolved Voxx config.\n * @param opts - Optional feed path override.\n * @returns RSS XML string.\n */\nexport function renderRss(\n posts: Post[],\n config: VoxxConfig,\n opts: RenderRssOptions = {},\n): string {\n const self = absoluteUrl(config.site.url, opts.path ?? rssPath(config));\n const items = posts\n .map((p) => {\n const link = absoluteUrl(config.site.url, p.url);\n const categories = config.features.tags\n ? p.tags\n .map((t) => ` <category>${escapeXml(t)}</category>`)\n .join(\"\\n\")\n : \"\";\n return [\n \" <item>\",\n ` <title>${escapeXml(p.title)}</title>`,\n ` <link>${escapeXml(link)}</link>`,\n ` <guid isPermaLink=\"true\">${escapeXml(link)}</guid>`,\n ` <pubDate>${rfc822(p.date)}</pubDate>`,\n ` <description>${escapeXml(p.description ?? p.excerpt)}</description>`,\n ` <content:encoded>${cdata(p.html)}</content:encoded>`,\n categories,\n \" </item>\",\n ]\n .filter((l) => l !== \"\")\n .join(\"\\n\");\n })\n .join(\"\\n\");\n\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<rss version=\"2.0\" xmlns:atom=\"http://www.w3.org/2005/Atom\" xmlns:content=\"http://purl.org/rss/1.0/modules/content/\">\n <channel>\n <title>${escapeXml(config.site.title)}</title>\n <link>${escapeXml(config.site.url)}</link>\n <description>${escapeXml(config.site.description)}</description>\n <language>${escapeXml(config.site.locale)}</language>\n <lastBuildDate>${rfc822(posts[0]?.date ?? new Date().toISOString())}</lastBuildDate>\n <atom:link href=\"${escapeXml(self)}\" rel=\"self\" type=\"application/rss+xml\"/>\n${items}\n </channel>\n</rss>\n`;\n}\n\n/** Options for `renderSitemap`. */\nexport interface RenderSitemapOptions {\n /** Additional index paths to include before the post entries. */\n indexPaths?: string[];\n}\n\n/**\n * Renders an XML sitemap for all posts.\n *\n * @param posts - List of posts to include.\n * @param config - Resolved Voxx config.\n * @param opts - Optional extra index paths.\n * @returns Sitemap XML string.\n */\nexport function renderSitemap(\n posts: Post[],\n config: VoxxConfig,\n opts: RenderSitemapOptions = {},\n): string {\n const lastmod = isoDate(\n posts[0]?.updated ?? posts[0]?.date ?? new Date().toISOString(),\n );\n const entries = [\n ...(opts.indexPaths ?? [config.content.basePath]).map((p) => ({\n loc: absoluteUrl(config.site.url, p),\n lastmod,\n })),\n ...posts.map((p) => ({\n loc: absoluteUrl(config.site.url, p.url),\n lastmod: isoDate(p.updated ?? p.date),\n })),\n ];\n\n const body = entries\n .map((u) =>\n [\n \" <url>\",\n ` <loc>${escapeXml(u.loc)}</loc>`,\n ` <lastmod>${u.lastmod}</lastmod>`,\n \" </url>\",\n ].join(\"\\n\"),\n )\n .join(\"\\n\");\n\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n${body}\n</urlset>\n`;\n}\n\n/**\n * Renders a `robots.txt` that allows all crawlers and optionally\n * references the sitemap when `features.sitemap` is enabled.\n *\n * @param config - Resolved Voxx config.\n * @returns `robots.txt` content string.\n */\nexport function renderRobotsTxt(config: VoxxConfig): string {\n const lines = [\"User-agent: *\", \"Allow: /\"];\n if (config.features.sitemap) {\n lines.push(\"\", `Sitemap: ${absoluteUrl(config.site.url, \"/sitemap.xml\")}`);\n }\n return `${lines.join(\"\\n\")}\\n`;\n}\n"],"mappings":";;AASA,MAAM,SAAS,UACb,YAAY,MAAM,QAAQ,UAAU,iBAAiB,EAAE;;;;;;;AAQzD,SAAgB,QAAQ,QAA4B;CAClD,OAAO,SAAS,OAAO,QAAQ,UAAU,SAAS;AACpD;;;;;;;;;AAUA,SAAgB,UACd,OACA,QACA,OAAyB,CAAC,GAClB;CACR,MAAM,OAAO,YAAY,OAAO,KAAK,KAAK,KAAK,QAAQ,QAAQ,MAAM,CAAC;CACtE,MAAM,QAAQ,MACX,KAAK,MAAM;EACV,MAAM,OAAO,YAAY,OAAO,KAAK,KAAK,EAAE,GAAG;EAC/C,MAAM,aAAa,OAAO,SAAS,OAC/B,EAAE,KACC,KAAK,MAAM,mBAAmB,UAAU,CAAC,EAAE,YAAY,CAAC,CACxD,KAAK,IAAI,IACZ;EACJ,OAAO;GACL;GACA,gBAAgB,UAAU,EAAE,KAAK,EAAE;GACnC,eAAe,UAAU,IAAI,EAAE;GAC/B,kCAAkC,UAAU,IAAI,EAAE;GAClD,kBAAkB,OAAO,EAAE,IAAI,EAAE;GACjC,sBAAsB,UAAU,EAAE,eAAe,EAAE,OAAO,EAAE;GAC5D,0BAA0B,MAAM,EAAE,IAAI,EAAE;GACxC;GACA;EACF,CAAC,CACE,QAAQ,MAAM,MAAM,EAAE,CAAC,CACvB,KAAK,IAAI;CACd,CAAC,CAAC,CACD,KAAK,IAAI;CAEZ,OAAO;;;aAGI,UAAU,OAAO,KAAK,KAAK,EAAE;YAC9B,UAAU,OAAO,KAAK,GAAG,EAAE;mBACpB,UAAU,OAAO,KAAK,WAAW,EAAE;gBACtC,UAAU,OAAO,KAAK,MAAM,EAAE;qBACzB,OAAO,MAAM,EAAE,EAAE,yBAAQ,IAAI,KAAK,EAAA,CAAE,YAAY,CAAC,EAAE;uBACjD,UAAU,IAAI,EAAE;EACrC,MAAM;;;;AAIR;;;;;;;;;AAgBA,SAAgB,cACd,OACA,QACA,OAA6B,CAAC,GACtB;CACR,MAAM,UAAU,QACd,MAAM,EAAE,EAAE,WAAW,MAAM,EAAE,EAAE,yBAAQ,IAAI,KAAK,EAAA,CAAE,YAAY,CAChE;CAuBA,OAAO;;EAXM,CAVX,IAAI,KAAK,cAAc,CAAC,OAAO,QAAQ,QAAQ,EAAA,CAAG,KAAK,OAAO;EAC5D,KAAK,YAAY,OAAO,KAAK,KAAK,CAAC;EACnC;CACF,EAAE,GACF,GAAG,MAAM,KAAK,OAAO;EACnB,KAAK,YAAY,OAAO,KAAK,KAAK,EAAE,GAAG;EACvC,SAAS,QAAQ,EAAE,WAAW,EAAE,IAAI;CACtC,EAAE,CAGe,CAAC,CACjB,KAAK,MACJ;EACE;EACA,YAAY,UAAU,EAAE,GAAG,EAAE;EAC7B,gBAAgB,EAAE,QAAQ;EAC1B;CACF,CAAC,CAAC,KAAK,IAAI,CACb,CAAC,CACA,KAAK,IAIL,EAAE;;;AAGP;;;;;;;;AASA,SAAgB,gBAAgB,QAA4B;CAC1D,MAAM,QAAQ,CAAC,iBAAiB,UAAU;CAC1C,IAAI,OAAO,SAAS,SAClB,MAAM,KAAK,IAAI,YAAY,YAAY,OAAO,KAAK,KAAK,cAAc,GAAG;CAE3E,OAAO,GAAG,MAAM,KAAK,IAAI,EAAE;AAC7B"}