@warlock.js/sitemap 5.15.0 → 5.16.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 (57) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +179 -137
  3. package/cjs/index.cjs +677 -201
  4. package/cjs/index.cjs.map +1 -1
  5. package/esm/atomic-publish.mjs +85 -0
  6. package/esm/atomic-publish.mjs.map +1 -0
  7. package/esm/atomic-write-file.mjs +50 -0
  8. package/esm/atomic-write-file.mjs.map +1 -0
  9. package/esm/duplicate-path-tracker.mjs +36 -0
  10. package/esm/duplicate-path-tracker.mjs.map +1 -0
  11. package/esm/errors.d.mts +39 -0
  12. package/esm/errors.mjs +52 -0
  13. package/esm/errors.mjs.map +1 -0
  14. package/esm/index.d.mts +8 -8
  15. package/esm/index.mjs +6 -6
  16. package/esm/lastmod.mjs +23 -0
  17. package/esm/lastmod.mjs.map +1 -0
  18. package/esm/normalize-entry.mjs +59 -0
  19. package/esm/normalize-entry.mjs.map +1 -0
  20. package/esm/route-counter.mjs +19 -0
  21. package/esm/route-counter.mjs.map +1 -0
  22. package/esm/shard-name.mjs +28 -0
  23. package/esm/shard-name.mjs.map +1 -0
  24. package/esm/sitemap-index-options.mjs +31 -0
  25. package/esm/sitemap-index-options.mjs.map +1 -0
  26. package/esm/sitemap-index-types.d.mts +39 -0
  27. package/esm/sitemap-index-xml.mjs +20 -0
  28. package/esm/sitemap-index-xml.mjs.map +1 -0
  29. package/esm/sitemap-index.d.mts +31 -0
  30. package/esm/sitemap-index.mjs +96 -0
  31. package/esm/sitemap-index.mjs.map +1 -0
  32. package/esm/sitemap-shard-writer.mjs +89 -0
  33. package/esm/sitemap-shard-writer.mjs.map +1 -0
  34. package/esm/sitemap.d.mts +63 -0
  35. package/esm/sitemap.mjs +129 -0
  36. package/esm/sitemap.mjs.map +1 -0
  37. package/esm/types.d.mts +51 -20
  38. package/esm/url.d.mts +1 -20
  39. package/esm/url.mjs +26 -19
  40. package/esm/url.mjs.map +1 -1
  41. package/esm/xml.d.mts +15 -4
  42. package/esm/xml.mjs +30 -8
  43. package/esm/xml.mjs.map +1 -1
  44. package/llms-full.txt +139 -159
  45. package/llms.txt +2 -2
  46. package/package.json +2 -14
  47. package/skills/sitemap-overview/SKILL.md +139 -159
  48. package/esm/collect-entries.d.mts +0 -44
  49. package/esm/collect-entries.mjs +0 -73
  50. package/esm/collect-entries.mjs.map +0 -1
  51. package/esm/diagnostic.d.mts +0 -13
  52. package/esm/diagnostic.mjs +0 -19
  53. package/esm/diagnostic.mjs.map +0 -1
  54. package/esm/routable-page.d.mts +0 -27
  55. package/esm/sitemap-connector.d.mts +0 -60
  56. package/esm/sitemap-connector.mjs +0 -117
  57. package/esm/sitemap-connector.mjs.map +0 -1
@@ -1,117 +0,0 @@
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
@@ -1 +0,0 @@
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"}