@warlock.js/sitemap 5.15.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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collect-entries.mjs","names":[],"sources":["../../../../../../sitemap/src/collect-entries.ts"],"sourcesContent":["import type { RoutablePage } from \"./routable-page\";\r\nimport type { SitemapDefaults, SitemapEntry } from \"./types\";\r\n\r\nconst DYNAMIC_SEGMENT = /(^|\\/):[A-Za-z_][A-Za-z0-9_]*/;\r\n\r\n/** A route path carries a dynamic segment (`[id]` -> `:id`) it cannot enumerate on its own. */\r\nexport function isDynamicRoutePath(routePath: string): boolean {\r\n return DYNAMIC_SEGMENT.test(routePath);\r\n}\r\n\r\nfunction isNoindex(robots: string | undefined): boolean {\r\n return robots !== undefined && /noindex/i.test(robots);\r\n}\r\n\r\n/** Applies the config's `defaults` to any entry that omits `changefreq`/`priority` — shared by page-derived and app-supplied entries alike. */\r\nexport function withDefaults(entry: SitemapEntry, defaults: SitemapDefaults | undefined): SitemapEntry {\r\n return {\r\n ...entry,\r\n changefreq: entry.changefreq ?? defaults?.changefreq,\r\n priority: entry.priority ?? defaults?.priority,\r\n };\r\n}\r\n\r\n/**\r\n * Combines page-derived entries with app-supplied ones (`SitemapConnectorOptions.entries`),\r\n * deduplicating by `path`. App-supplied entries are ADDED, not substituted — an\r\n * app with both a page graph and extra URLs (e.g. rows the page graph can't\r\n * see) wants both — but where the same path appears in both, the app-supplied\r\n * entry wins, since it was written for that exact path on purpose.\r\n */\r\nexport function mergeSitemapEntries(\r\n pageEntries: readonly SitemapEntry[],\r\n appEntries: readonly SitemapEntry[],\r\n): SitemapEntry[] {\r\n const byPath = new Map<string, SitemapEntry>();\r\n\r\n for (const entry of pageEntries) byPath.set(entry.path, entry);\r\n for (const entry of appEntries) byPath.set(entry.path, entry);\r\n\r\n return Array.from(byPath.values());\r\n}\r\n\r\nexport type CollectSitemapEntriesOptions = {\r\n defaults?: SitemapDefaults;\r\n};\r\n\r\nexport type CollectSitemapEntriesResult = {\r\n entries: SitemapEntry[];\r\n /** Route names of dynamic routes with no `sitemap` export — feed to `describeUnresolvedDynamicRoutes`. */\r\n unresolvedDynamicRoutes: string[];\r\n};\r\n\r\n/**\r\n * Walks the routable pages and produces the entries + the unresolved-dynamic\r\n * diagnostic input, applying every exclusion rule:\r\n *\r\n * - `metadata.robots` says `noindex` -> excluded.\r\n * - `sitemap: false` -> excluded.\r\n * - a `sitemap` export (any route) -> its returned entries, in place of the\r\n * page's own route path.\r\n * - a dynamic route with no `sitemap` export -> omitted, name collected.\r\n * - everything else (static routes) -> one entry at the page's own route path.\r\n *\r\n * Not-found and error pages are excluded by construction: the caller is\r\n * expected to hand this only `DiscoveredRoutablePage`-derived entries, and\r\n * the not-found route is never one of those (`@warlock.js/web`'s discovery\r\n * reports it as a routable page for the client matcher, but the runtime\r\n * wiring filters it out before calling here — see the sitemap README).\r\n */\r\nexport async function collectSitemapEntries(\r\n pages: readonly RoutablePage[],\r\n options: CollectSitemapEntriesOptions = {},\r\n): Promise<CollectSitemapEntriesResult> {\r\n const entries: SitemapEntry[] = [];\r\n const unresolvedDynamicRoutes: string[] = [];\r\n\r\n for (const page of pages) {\r\n if (isNoindex(page.robots)) continue;\r\n if (page.sitemap === false) continue;\r\n\r\n if (typeof page.sitemap === \"function\") {\r\n const produced = await page.sitemap();\r\n\r\n for (const entry of produced) entries.push(withDefaults(entry, options.defaults));\r\n\r\n continue;\r\n }\r\n\r\n if (isDynamicRoutePath(page.routePath)) {\r\n unresolvedDynamicRoutes.push(page.routeName);\r\n continue;\r\n }\r\n\r\n entries.push(withDefaults({ path: page.routePath }, options.defaults));\r\n }\r\n\r\n return { entries, unresolvedDynamicRoutes };\r\n}\r\n"],"mappings":";AAGA,MAAM,kBAAkB;;AAGxB,SAAgB,mBAAmB,WAA4B;CAC7D,OAAO,gBAAgB,KAAK,SAAS;AACvC;AAEA,SAAS,UAAU,QAAqC;CACtD,OAAO,WAAW,UAAa,WAAW,KAAK,MAAM;AACvD;;AAGA,SAAgB,aAAa,OAAqB,UAAqD;CACrG,OAAO;EACL,GAAG;EACH,YAAY,MAAM,cAAc,UAAU;EAC1C,UAAU,MAAM,YAAY,UAAU;CACxC;AACF;;;;;;;;AASA,SAAgB,oBACd,aACA,YACgB;CAChB,MAAM,yBAAS,IAAI,IAA0B;CAE7C,KAAK,MAAM,SAAS,aAAa,OAAO,IAAI,MAAM,MAAM,KAAK;CAC7D,KAAK,MAAM,SAAS,YAAY,OAAO,IAAI,MAAM,MAAM,KAAK;CAE5D,OAAO,MAAM,KAAK,OAAO,OAAO,CAAC;AACnC;;;;;;;;;;;;;;;;;;AA6BA,eAAsB,sBACpB,OACA,UAAwC,CAAC,GACH;CACtC,MAAM,UAA0B,CAAC;CACjC,MAAM,0BAAoC,CAAC;CAE3C,KAAK,MAAM,QAAQ,OAAO;EACxB,IAAI,UAAU,KAAK,MAAM,GAAG;EAC5B,IAAI,KAAK,YAAY,OAAO;EAE5B,IAAI,OAAO,KAAK,YAAY,YAAY;GACtC,MAAM,WAAW,MAAM,KAAK,QAAQ;GAEpC,KAAK,MAAM,SAAS,UAAU,QAAQ,KAAK,aAAa,OAAO,QAAQ,QAAQ,CAAC;GAEhF;EACF;EAEA,IAAI,mBAAmB,KAAK,SAAS,GAAG;GACtC,wBAAwB,KAAK,KAAK,SAAS;GAC3C;EACF;EAEA,QAAQ,KAAK,aAAa,EAAE,MAAM,KAAK,UAAU,GAAG,QAAQ,QAAQ,CAAC;CACvE;CAEA,OAAO;EAAE;EAAS;CAAwB;AAC5C"}
@@ -0,0 +1,13 @@
1
+ //#region ../sitemap/src/diagnostic.d.ts
2
+ /**
3
+ * The dev-mode diagnostic for dynamic routes {@link collectSitemapEntries}
4
+ * (`collect-entries.ts`) could not enumerate — the whole reason this package
5
+ * is written carefully. A dynamic route cannot be enumerated without
6
+ * application data; what the framework controls is whether the developer
7
+ * finds out. Returns `undefined` when there is nothing to report, so a caller
8
+ * can `if (message) console.warn(message)` without an extra length check.
9
+ */
10
+ declare function describeUnresolvedDynamicRoutes(routeNames: readonly string[]): string | undefined;
11
+ //#endregion
12
+ export { describeUnresolvedDynamicRoutes };
13
+ //# sourceMappingURL=diagnostic.d.mts.map
@@ -0,0 +1,19 @@
1
+ //#region ../sitemap/src/diagnostic.ts
2
+ /**
3
+ * The dev-mode diagnostic for dynamic routes {@link collectSitemapEntries}
4
+ * (`collect-entries.ts`) could not enumerate — the whole reason this package
5
+ * is written carefully. A dynamic route cannot be enumerated without
6
+ * application data; what the framework controls is whether the developer
7
+ * finds out. Returns `undefined` when there is nothing to report, so a caller
8
+ * can `if (message) console.warn(message)` without an extra length check.
9
+ */
10
+ function describeUnresolvedDynamicRoutes(routeNames) {
11
+ if (routeNames.length === 0) return void 0;
12
+ const plural = routeNames.length === 1 ? "" : "s";
13
+ const named = routeNames.map((name) => ` - ${name}`).join("\n");
14
+ return `[warlock:sitemap] ${routeNames.length} dynamic route${plural} ${routeNames.length === 1 ? "has" : "have"} no \`sitemap\` export and ${routeNames.length === 1 ? "is" : "are"} OMITTED from sitemap.xml:\n${named}\nA dynamic route cannot be enumerated without application data. Add \`export const sitemap: SitemapEntries = async () => [...]\` to each page above, or \`export const sitemap = false\` to keep it out of the sitemap deliberately.`;
15
+ }
16
+
17
+ //#endregion
18
+ export { describeUnresolvedDynamicRoutes };
19
+ //# sourceMappingURL=diagnostic.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diagnostic.mjs","names":[],"sources":["../../../../../../sitemap/src/diagnostic.ts"],"sourcesContent":["/**\n * The dev-mode diagnostic for dynamic routes {@link collectSitemapEntries}\n * (`collect-entries.ts`) could not enumerate — the whole reason this package\n * is written carefully. A dynamic route cannot be enumerated without\n * application data; what the framework controls is whether the developer\n * finds out. Returns `undefined` when there is nothing to report, so a caller\n * can `if (message) console.warn(message)` without an extra length check.\n */\nexport function describeUnresolvedDynamicRoutes(routeNames: readonly string[]): string | undefined {\n if (routeNames.length === 0) return undefined;\n\n const plural = routeNames.length === 1 ? \"\" : \"s\";\n const named = routeNames.map((name) => ` - ${name}`).join(\"\\n\");\n\n return (\n `[warlock:sitemap] ${routeNames.length} dynamic route${plural} ` +\n `${routeNames.length === 1 ? \"has\" : \"have\"} no \\`sitemap\\` export and ` +\n `${routeNames.length === 1 ? \"is\" : \"are\"} OMITTED from sitemap.xml:\\n${named}\\n` +\n \"A dynamic route cannot be enumerated without application data. Add \" +\n \"`export const sitemap: SitemapEntries = async () => [...]` to each page above, \" +\n \"or `export const sitemap = false` to keep it out of the sitemap deliberately.\"\n );\n}\n"],"mappings":";;;;;;;;;AAQA,SAAgB,gCAAgC,YAAmD;CACjG,IAAI,WAAW,WAAW,GAAG,OAAO;CAEpC,MAAM,SAAS,WAAW,WAAW,IAAI,KAAK;CAC9C,MAAM,QAAQ,WAAW,KAAK,SAAS,OAAO,MAAM,CAAC,CAAC,KAAK,IAAI;CAE/D,OACE,qBAAqB,WAAW,OAAO,gBAAgB,OAAO,GAC3D,WAAW,WAAW,IAAI,QAAQ,OAAO,6BACzC,WAAW,WAAW,IAAI,OAAO,MAAM,8BAA8B,MAAM;AAKlF"}
@@ -0,0 +1,8 @@
1
+ import { ChangeFreq, SitemapConfig, SitemapDefaults, SitemapEntries, SitemapEntry } from "./types.mjs";
2
+ import { RoutablePage, SitemapPageExport } from "./routable-page.mjs";
3
+ import { CollectSitemapEntriesOptions, CollectSitemapEntriesResult, collectSitemapEntries, isDynamicRoutePath, mergeSitemapEntries, withDefaults } from "./collect-entries.mjs";
4
+ import { describeUnresolvedDynamicRoutes } from "./diagnostic.mjs";
5
+ import { buildSitemapXml, escapeXml } from "./xml.mjs";
6
+ import { MissingPublicUrlError, ResolveOriginOptions, joinOrigin, resolveOrigin } from "./url.mjs";
7
+ import { DEFAULT_SITEMAP_PATH, NoPageRegistryError, SITEMAP_CONNECTOR_PRIORITY, SitemapConnectorOptions, sitemapConnector } from "./sitemap-connector.mjs";
8
+ export { type ChangeFreq, type CollectSitemapEntriesOptions, type CollectSitemapEntriesResult, DEFAULT_SITEMAP_PATH, MissingPublicUrlError, NoPageRegistryError, type ResolveOriginOptions, type RoutablePage, SITEMAP_CONNECTOR_PRIORITY, type SitemapConfig, type SitemapConnectorOptions, type SitemapDefaults, type SitemapEntries, type SitemapEntry, type SitemapPageExport, buildSitemapXml, collectSitemapEntries, describeUnresolvedDynamicRoutes, escapeXml, isDynamicRoutePath, joinOrigin, mergeSitemapEntries, resolveOrigin, sitemapConnector, withDefaults };
package/esm/index.mjs ADDED
@@ -0,0 +1,7 @@
1
+ import { collectSitemapEntries, isDynamicRoutePath, mergeSitemapEntries, withDefaults } from "./collect-entries.mjs";
2
+ import { describeUnresolvedDynamicRoutes } from "./diagnostic.mjs";
3
+ import { MissingPublicUrlError, joinOrigin, resolveOrigin } from "./url.mjs";
4
+ import { buildSitemapXml, escapeXml } from "./xml.mjs";
5
+ import { DEFAULT_SITEMAP_PATH, NoPageRegistryError, SITEMAP_CONNECTOR_PRIORITY, sitemapConnector } from "./sitemap-connector.mjs";
6
+
7
+ export { DEFAULT_SITEMAP_PATH, MissingPublicUrlError, NoPageRegistryError, SITEMAP_CONNECTOR_PRIORITY, buildSitemapXml, collectSitemapEntries, describeUnresolvedDynamicRoutes, escapeXml, isDynamicRoutePath, joinOrigin, mergeSitemapEntries, resolveOrigin, sitemapConnector, withDefaults };
@@ -0,0 +1,27 @@
1
+ import { SitemapEntries } from "./types.mjs";
2
+
3
+ //#region ../sitemap/src/routable-page.d.ts
4
+ /**
5
+ * A page's `sitemap` export: the entries function, an explicit opt-out
6
+ * (`export const sitemap = false`), or absent entirely.
7
+ */
8
+ type SitemapPageExport = SitemapEntries | false | undefined;
9
+ /**
10
+ * The minimal facts {@link collectSitemapEntries} (`collect-entries.ts`) needs
11
+ * about one routable page.
12
+ *
13
+ * Deliberately NOT `DiscoveredRoutablePage` from `@warlock.js/web` — that type
14
+ * carries filesystem paths, layout chains and middleware wiring this package
15
+ * has no business reading. The runtime wiring (a separate package, adapting
16
+ * `discoverPages()` and each page module's exports) builds this shape; this
17
+ * package only ever consumes it.
18
+ */
19
+ type RoutablePage = {
20
+ /** Unique route identity — named in the dynamic-route diagnostic when unresolved. */routeName: string; /** The effective route path, `:param` for a dynamic segment (e.g. `/posts/:id`). */
21
+ routePath: string; /** `metadata.robots`, when the page's `metadata` export is a static object. */
22
+ robots?: string;
23
+ sitemap?: SitemapPageExport;
24
+ };
25
+ //#endregion
26
+ export { RoutablePage, SitemapPageExport };
27
+ //# sourceMappingURL=routable-page.d.mts.map
@@ -0,0 +1,60 @@
1
+ import { SitemapConfig, SitemapEntries } from "./types.mjs";
2
+ import { Connector } from "@warlock.js/core";
3
+
4
+ //#region ../sitemap/src/sitemap-connector.d.ts
5
+ /** Default path when `src/config/sitemap.ts` does not set one. */
6
+ declare const DEFAULT_SITEMAP_PATH = "/sitemap.xml";
7
+ /**
8
+ * Boots after the HTTP connector (`ConnectorPriority.HTTP` is `5`) and after
9
+ * web (`5.5`, `web-connector-factory.ts`) — the route it registers has to
10
+ * land on the same router web's pages already share, and `listRoutablePages`
11
+ * only has a page graph to read once web has scanned it.
12
+ */
13
+ declare const SITEMAP_CONNECTOR_PRIORITY = 5.6;
14
+ type SitemapConnectorOptions = {
15
+ /** Supply the configuration directly instead of reading the `sitemap` config key (`src/config/sitemap.ts`). */config?: SitemapConfig;
16
+ /**
17
+ * App-supplied entries — the only source of entries in a Warlock **API-only**
18
+ * project, which has no `@warlock.js/web` page registry for `listRoutablePages()`
19
+ * to read. When `@warlock.js/web` IS installed, these are ADDED to the
20
+ * page-derived entries (see {@link mergeSitemapEntries}), not substituted, so an
21
+ * app with both pages and extra URLs (e.g. rows a database holds) gets both.
22
+ */
23
+ entries?: SitemapEntries;
24
+ };
25
+ /**
26
+ * Raised at `boot()` when the sitemap is enabled but has no way to produce
27
+ * entries: `@warlock.js/web` is not installed, so there is no page registry
28
+ * for `listRoutablePages()` to read, AND no `entries` option was supplied.
29
+ * Refuses to boot rather than registering a route that would silently serve
30
+ * an empty `<urlset>` — the same reasoning as {@link MissingPublicUrlError}:
31
+ * a sitemap that looks complete while producing nothing is worse than one
32
+ * that never started.
33
+ */
34
+ declare class NoPageRegistryError extends Error {
35
+ constructor();
36
+ }
37
+ /**
38
+ * Construct the sitemap connector.
39
+ *
40
+ * At `boot()`: reads the `sitemap` config (a no-op when `enabled` is not
41
+ * `true`), resolves the public origin ONCE — failing loud via
42
+ * {@link resolveOrigin}'s {@link MissingPublicUrlError} rather than falling
43
+ * back to a request-derived host — and registers `GET <config.path>`.
44
+ *
45
+ * The route itself re-reads the page graph on every request via
46
+ * `listRoutablePages()`, not once at boot: the registry can change under
47
+ * `warlock dev`, and a sitemap that only reflects the app's shape at the
48
+ * moment it booted is stale in exactly the way that made `2ede40cf`-class
49
+ * defects expensive.
50
+ *
51
+ * @example
52
+ * // warlock.config.ts
53
+ * import { sitemapConnector } from "@warlock.js/sitemap";
54
+ *
55
+ * export default defineConfig({ connectors: [sitemapConnector()] });
56
+ */
57
+ declare function sitemapConnector(options?: SitemapConnectorOptions): Connector;
58
+ //#endregion
59
+ export { DEFAULT_SITEMAP_PATH, NoPageRegistryError, SITEMAP_CONNECTOR_PRIORITY, SitemapConnectorOptions, sitemapConnector };
60
+ //# sourceMappingURL=sitemap-connector.d.mts.map
@@ -0,0 +1,117 @@
1
+ import { collectSitemapEntries, mergeSitemapEntries, withDefaults } from "./collect-entries.mjs";
2
+ import { describeUnresolvedDynamicRoutes } from "./diagnostic.mjs";
3
+ import { resolveOrigin } from "./url.mjs";
4
+ import { buildSitemapXml } from "./xml.mjs";
5
+
6
+ //#region ../sitemap/src/sitemap-connector.ts
7
+ /** Default path when `src/config/sitemap.ts` does not set one. */
8
+ const DEFAULT_SITEMAP_PATH = "/sitemap.xml";
9
+ /**
10
+ * Boots after the HTTP connector (`ConnectorPriority.HTTP` is `5`) and after
11
+ * web (`5.5`, `web-connector-factory.ts`) — the route it registers has to
12
+ * land on the same router web's pages already share, and `listRoutablePages`
13
+ * only has a page graph to read once web has scanned it.
14
+ */
15
+ const SITEMAP_CONNECTOR_PRIORITY = 5.6;
16
+ /**
17
+ * Raised at `boot()` when the sitemap is enabled but has no way to produce
18
+ * entries: `@warlock.js/web` is not installed, so there is no page registry
19
+ * for `listRoutablePages()` to read, AND no `entries` option was supplied.
20
+ * Refuses to boot rather than registering a route that would silently serve
21
+ * an empty `<urlset>` — the same reasoning as {@link MissingPublicUrlError}:
22
+ * a sitemap that looks complete while producing nothing is worse than one
23
+ * that never started.
24
+ */
25
+ var NoPageRegistryError = class extends Error {
26
+ constructor() {
27
+ super("Sitemap is enabled but has no source of entries: `@warlock.js/web` is not installed, so there is no page registry to read, and no `entries` option was supplied either. Fix this by installing `@warlock.js/web`, or by passing `sitemapConnector({ entries: async () => [...] })` with your own supplier.");
28
+ this.name = "NoPageRegistryError";
29
+ }
30
+ };
31
+ /** Adapts one `listRoutablePages()` result into the package's own minimal `RoutablePage` shape. */
32
+ function toRoutablePage(page) {
33
+ const metadata = page.metadata;
34
+ const robots = metadata !== null && typeof metadata === "object" && "robots" in metadata ? metadata.robots : void 0;
35
+ return {
36
+ routeName: page.routeName,
37
+ routePath: page.routePath,
38
+ robots: typeof robots === "string" ? robots : void 0,
39
+ sitemap: page.sitemap
40
+ };
41
+ }
42
+ /**
43
+ * Construct the sitemap connector.
44
+ *
45
+ * At `boot()`: reads the `sitemap` config (a no-op when `enabled` is not
46
+ * `true`), resolves the public origin ONCE — failing loud via
47
+ * {@link resolveOrigin}'s {@link MissingPublicUrlError} rather than falling
48
+ * back to a request-derived host — and registers `GET <config.path>`.
49
+ *
50
+ * The route itself re-reads the page graph on every request via
51
+ * `listRoutablePages()`, not once at boot: the registry can change under
52
+ * `warlock dev`, and a sitemap that only reflects the app's shape at the
53
+ * moment it booted is stale in exactly the way that made `2ede40cf`-class
54
+ * defects expensive.
55
+ *
56
+ * @example
57
+ * // warlock.config.ts
58
+ * import { sitemapConnector } from "@warlock.js/sitemap";
59
+ *
60
+ * export default defineConfig({ connectors: [sitemapConnector()] });
61
+ */
62
+ function sitemapConnector(options = {}) {
63
+ let active = false;
64
+ const connector = {
65
+ name: "sitemap",
66
+ priority: SITEMAP_CONNECTOR_PRIORITY,
67
+ lifecyclePhase: "late",
68
+ isActive: () => active,
69
+ async boot() {
70
+ const { config, router } = await import("@warlock.js/core");
71
+ const sitemapConfig = options.config ?? config.get("sitemap");
72
+ if (!sitemapConfig?.enabled) return;
73
+ const origin = resolveOrigin({
74
+ publicUrl: config.get("app")?.publicUrl,
75
+ env: process.env
76
+ });
77
+ const path = sitemapConfig.path || "/sitemap.xml";
78
+ let listRoutablePages;
79
+ try {
80
+ ({listRoutablePages} = await import("@warlock.js/web/build"));
81
+ } catch {
82
+ listRoutablePages = void 0;
83
+ }
84
+ if (!listRoutablePages && !options.entries) throw new NoPageRegistryError();
85
+ router.get(path, async ({ response }) => {
86
+ const { entries: pageEntries, unresolvedDynamicRoutes } = await collectSitemapEntries(listRoutablePages ? (await listRoutablePages({ appRoot: process.cwd() })).map(toRoutablePage) : [], { defaults: sitemapConfig.defaults });
87
+ const entries = mergeSitemapEntries(pageEntries, options.entries ? (await options.entries()).map((entry) => withDefaults(entry, sitemapConfig.defaults)) : []);
88
+ if (process.env.NODE_ENV !== "production") {
89
+ const diagnostic = describeUnresolvedDynamicRoutes(unresolvedDynamicRoutes);
90
+ if (diagnostic) console.warn(diagnostic);
91
+ }
92
+ const xml = buildSitemapXml(entries, origin);
93
+ return response.setContentType("application/xml").send(xml);
94
+ });
95
+ active = true;
96
+ },
97
+ async start() {},
98
+ async restart() {
99
+ await connector.shutdown();
100
+ await connector.boot();
101
+ },
102
+ async shutdown() {
103
+ active = false;
104
+ },
105
+ shouldRestart(changedFiles) {
106
+ return changedFiles.some((file) => {
107
+ const normalized = file.replace(/\\/g, "/");
108
+ return normalized === "src/config/sitemap.ts" || normalized.endsWith("/src/config/sitemap.ts");
109
+ });
110
+ }
111
+ };
112
+ return connector;
113
+ }
114
+
115
+ //#endregion
116
+ export { DEFAULT_SITEMAP_PATH, NoPageRegistryError, SITEMAP_CONNECTOR_PRIORITY, sitemapConnector };
117
+ //# sourceMappingURL=sitemap-connector.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sitemap-connector.mjs","names":[],"sources":["../../../../../../sitemap/src/sitemap-connector.ts"],"sourcesContent":["/**\n * `sitemapConnector()` — the ONE thing `warlock.config.ts` imports from\n * `@warlock.js/sitemap`, and the connector `warlock add sitemap` registers.\n *\n * Deliberately a plain object with TYPE-ONLY imports from core, same\n * reasoning as `queueConnector()`/`webConnector()`: the config file that\n * constructs it must not drag core's runtime graph in at config-load time.\n * `@warlock.js/core` and `@warlock.js/web` are imported lazily, inside\n * `boot()`, where the app has already loaded both.\n */\nimport type { Connector, ConnectorLifecyclePhase, HttpContext } from \"@warlock.js/core\";\nimport { collectSitemapEntries, mergeSitemapEntries, withDefaults } from \"./collect-entries\";\nimport { describeUnresolvedDynamicRoutes } from \"./diagnostic\";\nimport type { RoutablePage } from \"./routable-page\";\nimport type { SitemapConfig, SitemapEntries } from \"./types\";\nimport { resolveOrigin } from \"./url\";\nimport { buildSitemapXml } from \"./xml\";\n\n/** Default path when `src/config/sitemap.ts` does not set one. */\nexport const DEFAULT_SITEMAP_PATH = \"/sitemap.xml\";\n\n/**\n * Boots after the HTTP connector (`ConnectorPriority.HTTP` is `5`) and after\n * web (`5.5`, `web-connector-factory.ts`) — the route it registers has to\n * land on the same router web's pages already share, and `listRoutablePages`\n * only has a page graph to read once web has scanned it.\n */\nexport const SITEMAP_CONNECTOR_PRIORITY = 5.6;\n\nexport type SitemapConnectorOptions = {\n /** Supply the configuration directly instead of reading the `sitemap` config key (`src/config/sitemap.ts`). */\n config?: SitemapConfig;\n /**\n * App-supplied entries — the only source of entries in a Warlock **API-only**\n * project, which has no `@warlock.js/web` page registry for `listRoutablePages()`\n * to read. When `@warlock.js/web` IS installed, these are ADDED to the\n * page-derived entries (see {@link mergeSitemapEntries}), not substituted, so an\n * app with both pages and extra URLs (e.g. rows a database holds) gets both.\n */\n entries?: SitemapEntries;\n};\n\n/**\n * Raised at `boot()` when the sitemap is enabled but has no way to produce\n * entries: `@warlock.js/web` is not installed, so there is no page registry\n * for `listRoutablePages()` to read, AND no `entries` option was supplied.\n * Refuses to boot rather than registering a route that would silently serve\n * an empty `<urlset>` — the same reasoning as {@link MissingPublicUrlError}:\n * a sitemap that looks complete while producing nothing is worse than one\n * that never started.\n */\nexport class NoPageRegistryError extends Error {\n public constructor() {\n super(\n \"Sitemap is enabled but has no source of entries: `@warlock.js/web` is not installed, \" +\n \"so there is no page registry to read, and no `entries` option was supplied either. \" +\n \"Fix this by installing `@warlock.js/web`, or by passing \" +\n \"`sitemapConnector({ entries: async () => [...] })` with your own supplier.\",\n );\n this.name = \"NoPageRegistryError\";\n }\n}\n\n/** Adapts one `listRoutablePages()` result into the package's own minimal `RoutablePage` shape. */\nfunction toRoutablePage(page: {\n routeName: string;\n routePath: string;\n metadata?: unknown;\n sitemap?: unknown;\n}): RoutablePage {\n const metadata = page.metadata;\n const robots =\n metadata !== null && typeof metadata === \"object\" && \"robots\" in metadata\n ? (metadata as { robots?: unknown }).robots\n : undefined;\n\n return {\n routeName: page.routeName,\n routePath: page.routePath,\n robots: typeof robots === \"string\" ? robots : undefined,\n sitemap: page.sitemap as RoutablePage[\"sitemap\"],\n };\n}\n\n/**\n * Construct the sitemap connector.\n *\n * At `boot()`: reads the `sitemap` config (a no-op when `enabled` is not\n * `true`), resolves the public origin ONCE — failing loud via\n * {@link resolveOrigin}'s {@link MissingPublicUrlError} rather than falling\n * back to a request-derived host — and registers `GET <config.path>`.\n *\n * The route itself re-reads the page graph on every request via\n * `listRoutablePages()`, not once at boot: the registry can change under\n * `warlock dev`, and a sitemap that only reflects the app's shape at the\n * moment it booted is stale in exactly the way that made `2ede40cf`-class\n * defects expensive.\n *\n * @example\n * // warlock.config.ts\n * import { sitemapConnector } from \"@warlock.js/sitemap\";\n *\n * export default defineConfig({ connectors: [sitemapConnector()] });\n */\nexport function sitemapConnector(options: SitemapConnectorOptions = {}): Connector {\n let active = false;\n\n const connector: Connector = {\n name: \"sitemap\",\n priority: SITEMAP_CONNECTOR_PRIORITY,\n // Core's `ConnectorLifecyclePhase.Late`; spelled out so this module stays\n // free of a runtime import of core, same as `queueConnector()`.\n lifecyclePhase: \"late\" as ConnectorLifecyclePhase,\n isActive: () => active,\n async boot() {\n const { config, router } = await import(\"@warlock.js/core\");\n\n const sitemapConfig = options.config ?? config.get<SitemapConfig | undefined>(\"sitemap\");\n\n if (!sitemapConfig?.enabled) {\n return;\n }\n\n const appConfig = config.get<{ publicUrl?: string } | undefined>(\"app\");\n const origin = resolveOrigin({ publicUrl: appConfig?.publicUrl, env: process.env });\n const path = sitemapConfig.path || DEFAULT_SITEMAP_PATH;\n\n // Checked once, at boot: `@warlock.js/web`'s presence can't change per\n // request, and failing here — before the route is even registered —\n // surfaces a misconfigured app at startup instead of on its first hit.\n let listRoutablePages: typeof import(\"@warlock.js/web/build\").listRoutablePages | undefined;\n try {\n ({ listRoutablePages } = await import(\"@warlock.js/web/build\"));\n } catch {\n listRoutablePages = undefined;\n }\n\n if (!listRoutablePages && !options.entries) {\n throw new NoPageRegistryError();\n }\n\n router.get(path, async ({ response }: HttpContext) => {\n const pages = listRoutablePages\n ? (await listRoutablePages({ appRoot: process.cwd() })).map(toRoutablePage)\n : [];\n\n const { entries: pageEntries, unresolvedDynamicRoutes } = await collectSitemapEntries(pages, {\n defaults: sitemapConfig.defaults,\n });\n\n const appEntries = options.entries\n ? (await options.entries()).map((entry) => withDefaults(entry, sitemapConfig.defaults))\n : [];\n\n const entries = mergeSitemapEntries(pageEntries, appEntries);\n\n if (process.env.NODE_ENV !== \"production\") {\n const diagnostic = describeUnresolvedDynamicRoutes(unresolvedDynamicRoutes);\n if (diagnostic) console.warn(diagnostic);\n }\n\n const xml = buildSitemapXml(entries, origin);\n\n return response.setContentType(\"application/xml\").send(xml);\n });\n\n active = true;\n },\n async start() {\n // Nothing to start: the route is registered at boot, once the HTTP\n // connector has built its server but before it listens — the same\n // window `queueConnector()`'s dashboard mount uses.\n },\n async restart() {\n await connector.shutdown();\n await connector.boot();\n },\n async shutdown() {\n active = false;\n },\n shouldRestart(changedFiles: string[]) {\n return changedFiles.some((file) => {\n const normalized = file.replace(/\\\\/g, \"/\");\n\n return normalized === \"src/config/sitemap.ts\" || normalized.endsWith(\"/src/config/sitemap.ts\");\n });\n },\n };\n\n return connector;\n}\n"],"mappings":";;;;;;;AAmBA,MAAa,uBAAuB;;;;;;;AAQpC,MAAa,6BAA6B;;;;;;;;;;AAwB1C,IAAa,sBAAb,cAAyC,MAAM;CAC7C,AAAO,cAAc;EACnB,MACE,4SAIF;EACA,KAAK,OAAO;CACd;AACF;;AAGA,SAAS,eAAe,MAKP;CACf,MAAM,WAAW,KAAK;CACtB,MAAM,SACJ,aAAa,QAAQ,OAAO,aAAa,YAAY,YAAY,WAC5D,SAAkC,SACnC;CAEN,OAAO;EACL,WAAW,KAAK;EAChB,WAAW,KAAK;EAChB,QAAQ,OAAO,WAAW,WAAW,SAAS;EAC9C,SAAS,KAAK;CAChB;AACF;;;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,iBAAiB,UAAmC,CAAC,GAAc;CACjF,IAAI,SAAS;CAEb,MAAM,YAAuB;EAC3B,MAAM;EACN,UAAU;EAGV,gBAAgB;EAChB,gBAAgB;EAChB,MAAM,OAAO;GACX,MAAM,EAAE,QAAQ,WAAW,MAAM,OAAO;GAExC,MAAM,gBAAgB,QAAQ,UAAU,OAAO,IAA+B,SAAS;GAEvF,IAAI,CAAC,eAAe,SAClB;GAIF,MAAM,SAAS,cAAc;IAAE,WADb,OAAO,IAAwC,KACf,CAAC,EAAE;IAAW,KAAK,QAAQ;GAAI,CAAC;GAClF,MAAM,OAAO,cAAc;GAK3B,IAAI;GACJ,IAAI;IACF,CAAC,CAAE,qBAAsB,MAAM,OAAO;GACxC,QAAQ;IACN,oBAAoB;GACtB;GAEA,IAAI,CAAC,qBAAqB,CAAC,QAAQ,SACjC,MAAM,IAAI,oBAAoB;GAGhC,OAAO,IAAI,MAAM,OAAO,EAAE,eAA4B;IAKpD,MAAM,EAAE,SAAS,aAAa,4BAA4B,MAAM,sBAJlD,qBACT,MAAM,kBAAkB,EAAE,SAAS,QAAQ,IAAI,EAAE,CAAC,EAAC,CAAE,IAAI,cAAc,IACxE,CAAC,GAEwF,EAC3F,UAAU,cAAc,SAC1B,CAAC;IAMD,MAAM,UAAU,oBAAoB,aAJjB,QAAQ,WACtB,MAAM,QAAQ,QAAQ,EAAC,CAAE,KAAK,UAAU,aAAa,OAAO,cAAc,QAAQ,CAAC,IACpF,CAAC,CAEsD;IAE3D,IAAI,QAAQ,IAAI,aAAa,cAAc;KACzC,MAAM,aAAa,gCAAgC,uBAAuB;KAC1E,IAAI,YAAY,QAAQ,KAAK,UAAU;IACzC;IAEA,MAAM,MAAM,gBAAgB,SAAS,MAAM;IAE3C,OAAO,SAAS,eAAe,iBAAiB,CAAC,CAAC,KAAK,GAAG;GAC5D,CAAC;GAED,SAAS;EACX;EACA,MAAM,QAAQ,CAId;EACA,MAAM,UAAU;GACd,MAAM,UAAU,SAAS;GACzB,MAAM,UAAU,KAAK;EACvB;EACA,MAAM,WAAW;GACf,SAAS;EACX;EACA,cAAc,cAAwB;GACpC,OAAO,aAAa,MAAM,SAAS;IACjC,MAAM,aAAa,KAAK,QAAQ,OAAO,GAAG;IAE1C,OAAO,eAAe,2BAA2B,WAAW,SAAS,wBAAwB;GAC/F,CAAC;EACH;CACF;CAEA,OAAO;AACT"}
@@ -0,0 +1,33 @@
1
+ //#region ../sitemap/src/types.d.ts
2
+ /** The `<changefreq>` values the sitemap protocol defines. */
3
+ type ChangeFreq = "always" | "hourly" | "daily" | "weekly" | "monthly" | "yearly" | "never";
4
+ /**
5
+ * One `<url>` block. `path` is app-relative (`/posts/hello-world`) — this
6
+ * package joins it against the configured origin; see `url.ts`.
7
+ */
8
+ type SitemapEntry = {
9
+ path: string;
10
+ lastmod?: string;
11
+ changefreq?: ChangeFreq;
12
+ priority?: number;
13
+ };
14
+ /**
15
+ * A page's `sitemap` export, when it is a function: produces the entries a
16
+ * dynamic route stands for. Runs server-side, at sitemap-build time — not
17
+ * per-request.
18
+ */
19
+ type SitemapEntries = () => SitemapEntry[] | Promise<SitemapEntry[]>;
20
+ /** Fallback `changefreq`/`priority` applied to any entry that omits them. */
21
+ type SitemapDefaults = {
22
+ changefreq?: ChangeFreq;
23
+ priority?: number;
24
+ };
25
+ /** `src/config/sitemap.ts` — written by `warlock add sitemap`. */
26
+ type SitemapConfig = {
27
+ enabled: boolean;
28
+ path: string;
29
+ defaults?: SitemapDefaults;
30
+ };
31
+ //#endregion
32
+ export { ChangeFreq, SitemapConfig, SitemapDefaults, SitemapEntries, SitemapEntry };
33
+ //# sourceMappingURL=types.d.mts.map
package/esm/url.d.mts ADDED
@@ -0,0 +1,29 @@
1
+ //#region ../sitemap/src/url.d.ts
2
+ /**
3
+ * Joins a configured origin and an app-relative route path into one absolute
4
+ * URL, with exactly one slash at the seam regardless of whether either side
5
+ * already carries one.
6
+ */
7
+ declare function joinOrigin(origin: string, routePath: string): string;
8
+ /**
9
+ * Raised when the sitemap is enabled but no public origin is configured.
10
+ * Refuses to boot rather than falling back to a request-derived origin: a
11
+ * sitemap served with the wrong host is worse than one that refuses to
12
+ * start, because nothing downstream ever tells you it was wrong.
13
+ */
14
+ declare class MissingPublicUrlError extends Error {
15
+ constructor();
16
+ }
17
+ type ResolveOriginOptions = {
18
+ /** `app.publicUrl` from the app's config, when set. */publicUrl?: string; /** Defaults to `process.env`; overridable for tests. */
19
+ env?: Record<string, string | undefined>;
20
+ };
21
+ /**
22
+ * The origin the sitemap is served from: `app.publicUrl` first, then the
23
+ * `PUBLIC_APP_URL` env fallback. Throws {@link MissingPublicUrlError} when
24
+ * neither is set — this is the boot-time check, called once, not per-request.
25
+ */
26
+ declare function resolveOrigin(options?: ResolveOriginOptions): string;
27
+ //#endregion
28
+ export { MissingPublicUrlError, ResolveOriginOptions, joinOrigin, resolveOrigin };
29
+ //# sourceMappingURL=url.d.mts.map
package/esm/url.mjs ADDED
@@ -0,0 +1,35 @@
1
+ //#region ../sitemap/src/url.ts
2
+ /**
3
+ * Joins a configured origin and an app-relative route path into one absolute
4
+ * URL, with exactly one slash at the seam regardless of whether either side
5
+ * already carries one.
6
+ */
7
+ function joinOrigin(origin, routePath) {
8
+ return `${origin.endsWith("/") ? origin.slice(0, -1) : origin}${routePath.startsWith("/") ? routePath : `/${routePath}`}`;
9
+ }
10
+ /**
11
+ * Raised when the sitemap is enabled but no public origin is configured.
12
+ * Refuses to boot rather than falling back to a request-derived origin: a
13
+ * sitemap served with the wrong host is worse than one that refuses to
14
+ * start, because nothing downstream ever tells you it was wrong.
15
+ */
16
+ var MissingPublicUrlError = class extends Error {
17
+ constructor() {
18
+ super("Sitemap is enabled but no public origin is configured. Set `app.publicUrl` in warlock.config.ts, or the PUBLIC_APP_URL environment variable.");
19
+ this.name = "MissingPublicUrlError";
20
+ }
21
+ };
22
+ /**
23
+ * The origin the sitemap is served from: `app.publicUrl` first, then the
24
+ * `PUBLIC_APP_URL` env fallback. Throws {@link MissingPublicUrlError} when
25
+ * neither is set — this is the boot-time check, called once, not per-request.
26
+ */
27
+ function resolveOrigin(options = {}) {
28
+ const origin = options.publicUrl ?? options.env?.PUBLIC_APP_URL;
29
+ if (!origin) throw new MissingPublicUrlError();
30
+ return origin;
31
+ }
32
+
33
+ //#endregion
34
+ export { MissingPublicUrlError, joinOrigin, resolveOrigin };
35
+ //# sourceMappingURL=url.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"url.mjs","names":[],"sources":["../../../../../../sitemap/src/url.ts"],"sourcesContent":["/**\n * Joins a configured origin and an app-relative route path into one absolute\n * URL, with exactly one slash at the seam regardless of whether either side\n * already carries one.\n */\nexport function joinOrigin(origin: string, routePath: string): string {\n const trimmedOrigin = origin.endsWith(\"/\") ? origin.slice(0, -1) : origin;\n const normalizedPath = routePath.startsWith(\"/\") ? routePath : `/${routePath}`;\n\n return `${trimmedOrigin}${normalizedPath}`;\n}\n\n/**\n * Raised when the sitemap is enabled but no public origin is configured.\n * Refuses to boot rather than falling back to a request-derived origin: a\n * sitemap served with the wrong host is worse than one that refuses to\n * start, because nothing downstream ever tells you it was wrong.\n */\nexport class MissingPublicUrlError extends Error {\n public constructor() {\n super(\n \"Sitemap is enabled but no public origin is configured. Set `app.publicUrl` \" +\n \"in warlock.config.ts, or the PUBLIC_APP_URL environment variable.\",\n );\n this.name = \"MissingPublicUrlError\";\n }\n}\n\nexport type ResolveOriginOptions = {\n /** `app.publicUrl` from the app's config, when set. */\n publicUrl?: string;\n /** Defaults to `process.env`; overridable for tests. */\n env?: Record<string, string | undefined>;\n};\n\n/**\n * The origin the sitemap is served from: `app.publicUrl` first, then the\n * `PUBLIC_APP_URL` env fallback. Throws {@link MissingPublicUrlError} when\n * neither is set — this is the boot-time check, called once, not per-request.\n */\nexport function resolveOrigin(options: ResolveOriginOptions = {}): string {\n const origin = options.publicUrl ?? options.env?.PUBLIC_APP_URL;\n\n if (!origin) throw new MissingPublicUrlError();\n\n return origin;\n}\n"],"mappings":";;;;;;AAKA,SAAgB,WAAW,QAAgB,WAA2B;CAIpE,OAAO,GAHe,OAAO,SAAS,GAAG,IAAI,OAAO,MAAM,GAAG,EAAE,IAAI,SAC5C,UAAU,WAAW,GAAG,IAAI,YAAY,IAAI;AAGrE;;;;;;;AAQA,IAAa,wBAAb,cAA2C,MAAM;CAC/C,AAAO,cAAc;EACnB,MACE,8IAEF;EACA,KAAK,OAAO;CACd;AACF;;;;;;AAcA,SAAgB,cAAc,UAAgC,CAAC,GAAW;CACxE,MAAM,SAAS,QAAQ,aAAa,QAAQ,KAAK;CAEjD,IAAI,CAAC,QAAQ,MAAM,IAAI,sBAAsB;CAE7C,OAAO;AACT"}
package/esm/xml.d.mts ADDED
@@ -0,0 +1,14 @@
1
+ import { SitemapEntry } from "./types.mjs";
2
+
3
+ //#region ../sitemap/src/xml.d.ts
4
+ /** Escapes the five XML-significant characters. A URL's query string routinely contains `&`. */
5
+ declare function escapeXml(value: string): string;
6
+ /**
7
+ * Serialises entries into a `urlset` sitemap document — the sitemaps.org
8
+ * namespace, `<url>` per entry, element order `loc` / `lastmod` / `changefreq`
9
+ * / `priority` (schema order; a validator that checks order rejects any other).
10
+ */
11
+ declare function buildSitemapXml(entries: readonly SitemapEntry[], origin: string): string;
12
+ //#endregion
13
+ export { buildSitemapXml, escapeXml };
14
+ //# sourceMappingURL=xml.d.mts.map
package/esm/xml.mjs ADDED
@@ -0,0 +1,35 @@
1
+ import { joinOrigin } from "./url.mjs";
2
+
3
+ //#region ../sitemap/src/xml.ts
4
+ const XML_ESCAPES = {
5
+ "&": "&amp;",
6
+ "<": "&lt;",
7
+ ">": "&gt;",
8
+ "\"": "&quot;",
9
+ "'": "&apos;"
10
+ };
11
+ /** Escapes the five XML-significant characters. A URL's query string routinely contains `&`. */
12
+ function escapeXml(value) {
13
+ return value.replace(/[&<>"']/g, (char) => XML_ESCAPES[char] ?? char);
14
+ }
15
+ function entryXml(entry, origin) {
16
+ const lines = [` <url>`, ` <loc>${escapeXml(joinOrigin(origin, entry.path))}</loc>`];
17
+ if (entry.lastmod !== void 0) lines.push(` <lastmod>${escapeXml(entry.lastmod)}</lastmod>`);
18
+ if (entry.changefreq !== void 0) lines.push(` <changefreq>${entry.changefreq}</changefreq>`);
19
+ if (entry.priority !== void 0) lines.push(` <priority>${entry.priority}</priority>`);
20
+ lines.push(` </url>`);
21
+ return lines.join("\n");
22
+ }
23
+ /**
24
+ * Serialises entries into a `urlset` sitemap document — the sitemaps.org
25
+ * namespace, `<url>` per entry, element order `loc` / `lastmod` / `changefreq`
26
+ * / `priority` (schema order; a validator that checks order rejects any other).
27
+ */
28
+ function buildSitemapXml(entries, origin) {
29
+ const body = entries.map((entry) => entryXml(entry, origin)).join("\n");
30
+ return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n" + (body.length > 0 ? `${body}\n` : "") + `</urlset>\n`;
31
+ }
32
+
33
+ //#endregion
34
+ export { buildSitemapXml, escapeXml };
35
+ //# sourceMappingURL=xml.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"xml.mjs","names":[],"sources":["../../../../../../sitemap/src/xml.ts"],"sourcesContent":["import type { SitemapEntry } from \"./types\";\nimport { joinOrigin } from \"./url\";\n\nconst XML_ESCAPES: Record<string, string> = {\n \"&\": \"&amp;\",\n \"<\": \"&lt;\",\n \">\": \"&gt;\",\n '\"': \"&quot;\",\n \"'\": \"&apos;\",\n};\n\n/** Escapes the five XML-significant characters. A URL's query string routinely contains `&`. */\nexport function escapeXml(value: string): string {\n // The character class and the table are written together, so the lookup can\n // only miss if one is edited without the other; falling back to the original\n // character keeps that editing mistake from silently emitting `undefined`\n // into a URL.\n return value.replace(/[&<>\"']/g, (char) => XML_ESCAPES[char] ?? char);\n}\n\nfunction entryXml(entry: SitemapEntry, origin: string): string {\n const lines = [` <url>`, ` <loc>${escapeXml(joinOrigin(origin, entry.path))}</loc>`];\n\n if (entry.lastmod !== undefined) {\n lines.push(` <lastmod>${escapeXml(entry.lastmod)}</lastmod>`);\n }\n\n if (entry.changefreq !== undefined) {\n lines.push(` <changefreq>${entry.changefreq}</changefreq>`);\n }\n\n if (entry.priority !== undefined) {\n lines.push(` <priority>${entry.priority}</priority>`);\n }\n\n lines.push(` </url>`);\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Serialises entries into a `urlset` sitemap document — the sitemaps.org\n * namespace, `<url>` per entry, element order `loc` / `lastmod` / `changefreq`\n * / `priority` (schema order; a validator that checks order rejects any other).\n */\nexport function buildSitemapXml(entries: readonly SitemapEntry[], origin: string): string {\n const body = entries.map((entry) => entryXml(entry, origin)).join(\"\\n\");\n\n return (\n `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n` +\n `<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\\n` +\n (body.length > 0 ? `${body}\\n` : \"\") +\n `</urlset>\\n`\n );\n}\n"],"mappings":";;;AAGA,MAAM,cAAsC;CAC1C,KAAK;CACL,KAAK;CACL,KAAK;CACL,MAAK;CACL,KAAK;AACP;;AAGA,SAAgB,UAAU,OAAuB;CAK/C,OAAO,MAAM,QAAQ,aAAa,SAAS,YAAY,SAAS,IAAI;AACtE;AAEA,SAAS,SAAS,OAAqB,QAAwB;CAC7D,MAAM,QAAQ,CAAC,WAAW,YAAY,UAAU,WAAW,QAAQ,MAAM,IAAI,CAAC,EAAE,OAAO;CAEvF,IAAI,MAAM,YAAY,QACpB,MAAM,KAAK,gBAAgB,UAAU,MAAM,OAAO,EAAE,WAAW;CAGjE,IAAI,MAAM,eAAe,QACvB,MAAM,KAAK,mBAAmB,MAAM,WAAW,cAAc;CAG/D,IAAI,MAAM,aAAa,QACrB,MAAM,KAAK,iBAAiB,MAAM,SAAS,YAAY;CAGzD,MAAM,KAAK,UAAU;CAErB,OAAO,MAAM,KAAK,IAAI;AACxB;;;;;;AAOA,SAAgB,gBAAgB,SAAkC,QAAwB;CACxF,MAAM,OAAO,QAAQ,KAAK,UAAU,SAAS,OAAO,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI;CAEtE,OACE,kHAEC,KAAK,SAAS,IAAI,GAAG,KAAK,MAAM,MACjC;AAEJ"}