@megabudino/stack-utils 1.4.1 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -49,8 +49,7 @@ Proxy config:
49
49
  | --- | --- | --- | --- |
50
50
  | `originUrl` | `string` | required | Origin server URL |
51
51
  | `siteUrl` | `string` | required | Public site URL |
52
- | `sitemaps` | `string[]` | `[]` | SvelteKit sitemap URLs to inject into sitemap indexes |
53
- | `sitemapCacheSeconds` | `number` | `3600` | Cache max-age for sitemap responses |
52
+ | `sitemap` | `SitemapConfig` | `{}` | Sitemap index injection and origin URL filtering |
54
53
  | `proxyAssets` | `boolean` | `false` | Rewrite origin asset URLs to relative paths |
55
54
  | `additionalOrigins` | `string[]` | `[]` | Additional absolute origins to rewrite like `originUrl` |
56
55
  | `domActions` | `DomAction[]` | `[]` | Declarative DOM transformations for proxied HTML |
@@ -61,7 +60,7 @@ What it does:
61
60
  - lets SvelteKit routes resolve normally when a route exists
62
61
  - proxies unmatched requests to the configured origin
63
62
  - rewrites redirects back to the public domain
64
- - rewrites HTML, applies optional DOM actions, and injects extra sitemap entries when configured
63
+ - rewrites HTML, applies optional DOM actions, and rewrites sitemap XML when configured
65
64
 
66
65
  ### `images`
67
66
 
@@ -1,5 +1,5 @@
1
1
  import { applyDomActions } from './dom-actions.js';
2
- import { injectIntoSitemapIndex, rewriteHtml } from './rewrite.js';
2
+ import { injectIntoSitemapIndex, normalizeSitemapPath, removeExcludedUrlsFromSitemap, rewriteHtml } from './rewrite.js';
3
3
  /**
4
4
  * Create a SvelteKit `Handle` hook that reverse-proxies requests to an origin.
5
5
  *
@@ -7,9 +7,13 @@ import { injectIntoSitemapIndex, rewriteHtml } from './rewrite.js';
7
7
  * everything else is forwarded to the origin.
8
8
  */
9
9
  export function createProxyHandle(config) {
10
+ assertNoDeprecatedSitemapConfig(config);
10
11
  const { originUrl, siteUrl } = config;
11
- const sitemaps = config.sitemaps ?? [];
12
- const sitemapCache = `public, max-age=${config.sitemapCacheSeconds ?? 3600}`;
12
+ const sitemap = config.sitemap ?? {};
13
+ const sitemapIndexPath = sitemap.indexPath ? normalizeSitemapPath(sitemap.indexPath) : undefined;
14
+ const sitemapIncludeFiles = sitemap.includeFiles ?? [];
15
+ const sitemapExcludePaths = sitemap.excludePaths ?? [];
16
+ const sitemapCache = `public, max-age=${sitemap.cacheSeconds ?? 3600}`;
13
17
  const proxyAssets = config.proxyAssets ?? false;
14
18
  const additionalOrigins = config.additionalOrigins ?? [];
15
19
  const maxRedirects = config.maxInternalRedirects ?? 5;
@@ -72,8 +76,14 @@ export function createProxyHandle(config) {
72
76
  if (contentType.includes('text/xml') || contentType.includes('application/xml')) {
73
77
  let xml = await upstreamResponse.text();
74
78
  xml = xml.replace(/<\?xml-stylesheet[^?]*\?>\s*/g, '');
75
- if (sitemaps.length > 0 && xml.includes('</sitemapindex>')) {
76
- xml = injectIntoSitemapIndex(xml, siteUrl, sitemaps);
79
+ const isConfiguredSitemapIndex = sitemapIndexPath !== undefined && normalizeSitemapPath(pathname) === sitemapIndexPath;
80
+ if (isConfiguredSitemapIndex &&
81
+ sitemapIncludeFiles.length > 0 &&
82
+ xml.includes('</sitemapindex>')) {
83
+ xml = injectIntoSitemapIndex(xml, siteUrl, sitemapIncludeFiles);
84
+ }
85
+ else if (sitemapExcludePaths.length > 0 && xml.includes('</urlset>')) {
86
+ xml = removeExcludedUrlsFromSitemap(xml, sitemapExcludePaths);
77
87
  }
78
88
  cleanHeaders.set('cache-control', sitemapCache);
79
89
  return new Response(xml, {
@@ -103,3 +113,11 @@ export function createProxyHandle(config) {
103
113
  }
104
114
  };
105
115
  }
116
+ function assertNoDeprecatedSitemapConfig(config) {
117
+ if ('sitemaps' in config) {
118
+ throw new Error('`sitemaps` is deprecated and no longer supported. Use `sitemap.includeFiles` with `sitemap.indexPath` instead.');
119
+ }
120
+ if ('sitemapCacheSeconds' in config) {
121
+ throw new Error('`sitemapCacheSeconds` is deprecated and no longer supported. Use `sitemap.cacheSeconds` instead.');
122
+ }
123
+ }
@@ -1,2 +1,2 @@
1
1
  export { createProxyHandle } from './handle.js';
2
- export type { DomAction, ProxyConfig } from './types.js';
2
+ export type { DomAction, ProxyConfig, SitemapConfig } from './types.js';
@@ -9,5 +9,9 @@
9
9
  * - When `proxyAssets` is false, absolute origin URLs are left as-is.
10
10
  */
11
11
  export declare function rewriteHtml(html: string, originUrl: string, siteUrl: string, proxyAssets?: boolean, additionalOrigins?: string[]): string;
12
- /** Inject SvelteKit sitemap references into a `<sitemapindex>`. */
13
- export declare function injectIntoSitemapIndex(xml: string, siteUrl: string, sitemaps: string[]): string;
12
+ /** Normalize URL-like values to comparable sitemap pathnames. */
13
+ export declare function normalizeSitemapPath(value: string): string;
14
+ /** Inject local sitemap references into a configured `<sitemapindex>`. */
15
+ export declare function injectIntoSitemapIndex(xml: string, siteUrl: string, includeFiles: string[]): string;
16
+ /** Remove `<url>` entries whose `<loc>` pathname is listed in `excludePaths`. */
17
+ export declare function removeExcludedUrlsFromSitemap(xml: string, excludePaths: string[]): string;
@@ -41,10 +41,44 @@ export function rewriteHtml(html, originUrl, siteUrl, proxyAssets = false, addit
41
41
  }
42
42
  return html;
43
43
  }
44
- /** Inject SvelteKit sitemap references into a `<sitemapindex>`. */
45
- export function injectIntoSitemapIndex(xml, siteUrl, sitemaps) {
46
- const entries = sitemaps
44
+ /** Normalize URL-like values to comparable sitemap pathnames. */
45
+ export function normalizeSitemapPath(value) {
46
+ let pathname = value.trim();
47
+ try {
48
+ pathname = new URL(pathname, 'https://stack-utils.local').pathname;
49
+ }
50
+ catch {
51
+ // Fall back to the raw value below; invalid URL-like values should not crash rewriting.
52
+ }
53
+ if (!pathname.startsWith('/')) {
54
+ pathname = `/${pathname}`;
55
+ }
56
+ if (pathname === '/') {
57
+ return pathname;
58
+ }
59
+ return pathname.replace(/\/+$/g, '') || '/';
60
+ }
61
+ /** Inject local sitemap references into a configured `<sitemapindex>`. */
62
+ export function injectIntoSitemapIndex(xml, siteUrl, includeFiles) {
63
+ if (includeFiles.length === 0) {
64
+ return xml;
65
+ }
66
+ const entries = includeFiles
47
67
  .map((url) => `\t<sitemap>\n\t\t<loc>${siteUrl}${url}</loc>\n\t</sitemap>`)
48
68
  .join('\n');
49
69
  return xml.replace('</sitemapindex>', `${entries}\n</sitemapindex>`);
50
70
  }
71
+ /** Remove `<url>` entries whose `<loc>` pathname is listed in `excludePaths`. */
72
+ export function removeExcludedUrlsFromSitemap(xml, excludePaths) {
73
+ if (excludePaths.length === 0) {
74
+ return xml;
75
+ }
76
+ const excluded = new Set(excludePaths.map(normalizeSitemapPath));
77
+ return xml.replace(/<url\b[\s\S]*?<\/url>/g, (entry) => {
78
+ const loc = entry.match(/<loc\b[^>]*>\s*([\s\S]*?)\s*<\/loc>/i)?.[1];
79
+ if (!loc) {
80
+ return entry;
81
+ }
82
+ return excluded.has(normalizeSitemapPath(loc)) ? '' : entry;
83
+ });
84
+ }
@@ -34,22 +34,44 @@ export interface DomAction {
34
34
  */
35
35
  remove?: boolean;
36
36
  }
37
- /** Configuration for the reverse proxy. */
38
- export interface ProxyConfig {
39
- /** The origin URL (not publicly exposed). */
40
- originUrl: string;
41
- /** The public site URL (used in sitemaps and link rewriting). */
42
- siteUrl: string;
37
+ type DeprecatedProxyOption<Message extends string> = {
38
+ readonly __deprecatedProxyOption: Message;
39
+ };
40
+ /** Sitemap rewriting configuration for proxied XML responses. */
41
+ export interface SitemapConfig {
42
+ /**
43
+ * Origin sitemap index path that can receive `includeFiles`.
44
+ * Example: `/sitemap_index.xml`.
45
+ */
46
+ indexPath?: string;
43
47
  /**
44
- * SvelteKit sitemap URLs to inject into the origin's sitemap index.
45
- * Defaults to `[]`.
48
+ * Local sitemap files to add to the configured sitemap index.
49
+ * Example: [`/sveltekit-sitemap.xml`].
46
50
  */
47
- sitemaps?: string[];
51
+ includeFiles?: string[];
52
+ /**
53
+ * URL paths to remove from origin `<urlset>` sitemap responses.
54
+ * Matching compares normalized pathnames only.
55
+ */
56
+ excludePaths?: string[];
48
57
  /**
49
58
  * Cache-Control max-age (in seconds) for sitemap responses.
50
59
  * Defaults to 3600 (1 hour).
51
60
  */
52
- sitemapCacheSeconds?: number;
61
+ cacheSeconds?: number;
62
+ }
63
+ /** Configuration for the reverse proxy. */
64
+ export interface ProxyConfig {
65
+ /** The origin URL (not publicly exposed). */
66
+ originUrl: string;
67
+ /** The public site URL (used in sitemaps and link rewriting). */
68
+ siteUrl: string;
69
+ /** Explicit sitemap rewriting configuration for proxied XML responses. */
70
+ sitemap?: SitemapConfig;
71
+ /** @deprecated Use `sitemap.includeFiles` with `sitemap.indexPath` instead. */
72
+ sitemaps?: DeprecatedProxyOption<'`sitemaps` is deprecated. Use `sitemap.includeFiles` with `sitemap.indexPath` instead.'>;
73
+ /** @deprecated Use `sitemap.cacheSeconds` instead. */
74
+ sitemapCacheSeconds?: DeprecatedProxyOption<'`sitemapCacheSeconds` is deprecated. Use `sitemap.cacheSeconds` instead.'>;
53
75
  /**
54
76
  * If true, absolute origin URLs in HTML are rewritten to relative paths
55
77
  * so assets are served through the SvelteKit proxy and the origin is never exposed.
@@ -79,3 +101,4 @@ export interface ProxyConfig {
79
101
  */
80
102
  domActions?: DomAction[];
81
103
  }
104
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@megabudino/stack-utils",
3
- "version": "1.4.1",
3
+ "version": "1.5.0",
4
4
  "description": "Reusable utilities for a SvelteKit stack, including a reverse proxy and static image pipeline",
5
5
  "type": "module",
6
6
  "exports": {