@puzzle-ui/seo 0.0.0-stage → 0.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 +755 -2
- package/dist/LICENSE +21 -0
- package/dist/context/config.cjs +82 -0
- package/dist/context/config.cjs.map +1 -0
- package/dist/context/config.d.cts +6 -0
- package/dist/context/config.d.ts +6 -0
- package/dist/context/config.js +80 -0
- package/dist/context/config.js.map +1 -0
- package/dist/context/index.cjs +6 -0
- package/dist/context/index.d.cts +4 -0
- package/dist/context/index.d.ts +4 -0
- package/dist/context/index.js +3 -0
- package/dist/context/types.d.cts +46 -0
- package/dist/context/types.d.ts +46 -0
- package/dist/core/graph.cjs +464 -0
- package/dist/core/graph.cjs.map +1 -0
- package/dist/core/graph.d.cts +62 -0
- package/dist/core/graph.d.ts +62 -0
- package/dist/core/graph.js +456 -0
- package/dist/core/graph.js.map +1 -0
- package/dist/core/index.cjs +11 -0
- package/dist/core/index.d.cts +3 -0
- package/dist/core/index.d.ts +3 -0
- package/dist/core/index.js +1 -0
- package/dist/core/types.d.cts +92 -0
- package/dist/core/types.d.ts +92 -0
- package/dist/index.cjs +96 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +48 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/metadata/index.cjs +4 -0
- package/dist/metadata/index.d.cts +3 -0
- package/dist/metadata/index.d.ts +3 -0
- package/dist/metadata/index.js +1 -0
- package/dist/metadata/resolve.cjs +122 -0
- package/dist/metadata/resolve.cjs.map +1 -0
- package/dist/metadata/resolve.d.cts +4 -0
- package/dist/metadata/resolve.d.ts +4 -0
- package/dist/metadata/resolve.js +121 -0
- package/dist/metadata/resolve.js.map +1 -0
- package/dist/metadata/types.d.cts +82 -0
- package/dist/metadata/types.d.ts +82 -0
- package/dist/next/index.cjs +13 -0
- package/dist/next/index.cjs.map +1 -0
- package/dist/next/index.d.cts +15 -0
- package/dist/next/index.d.ts +14 -0
- package/dist/next/index.js +12 -0
- package/dist/next/index.js.map +1 -0
- package/dist/react/context.cjs +30 -0
- package/dist/react/context.cjs.map +1 -0
- package/dist/react/context.d.cts +14 -0
- package/dist/react/context.d.ts +14 -0
- package/dist/react/context.js +27 -0
- package/dist/react/context.js.map +1 -0
- package/dist/react/index.cjs +48 -0
- package/dist/react/index.d.cts +6 -0
- package/dist/react/index.d.ts +6 -0
- package/dist/react/index.js +5 -0
- package/dist/react/metadata.cjs +100 -0
- package/dist/react/metadata.cjs.map +1 -0
- package/dist/react/metadata.d.cts +18 -0
- package/dist/react/metadata.d.ts +18 -0
- package/dist/react/metadata.js +99 -0
- package/dist/react/metadata.js.map +1 -0
- package/dist/react/provider.cjs +42 -0
- package/dist/react/provider.cjs.map +1 -0
- package/dist/react/provider.d.cts +3 -0
- package/dist/react/provider.d.ts +3 -0
- package/dist/react/provider.js +42 -0
- package/dist/react/provider.js.map +1 -0
- package/dist/react/schema-components.cjs +294 -0
- package/dist/react/schema-components.cjs.map +1 -0
- package/dist/react/schema-components.d.cts +84 -0
- package/dist/react/schema-components.d.ts +80 -0
- package/dist/react/schema-components.js +254 -0
- package/dist/react/schema-components.js.map +1 -0
- package/dist/react/types.d.cts +81 -0
- package/dist/react/types.d.ts +80 -0
- package/dist/react-router/index.cjs +13 -0
- package/dist/react-router/index.cjs.map +1 -0
- package/dist/react-router/index.d.cts +21 -0
- package/dist/react-router/index.d.ts +20 -0
- package/dist/react-router/index.js +12 -0
- package/dist/react-router/index.js.map +1 -0
- package/dist/robots/generate.cjs +88 -0
- package/dist/robots/generate.cjs.map +1 -0
- package/dist/robots/generate.d.cts +4 -0
- package/dist/robots/generate.d.ts +4 -0
- package/dist/robots/generate.js +86 -0
- package/dist/robots/generate.js.map +1 -0
- package/dist/robots/index.cjs +5 -0
- package/dist/robots/index.d.cts +3 -0
- package/dist/robots/index.d.ts +3 -0
- package/dist/robots/index.js +1 -0
- package/dist/robots/types.d.cts +27 -0
- package/dist/robots/types.d.ts +27 -0
- package/dist/schema/create-registry.cjs +12 -0
- package/dist/schema/create-registry.cjs.map +1 -0
- package/dist/schema/create-registry.d.cts +2 -0
- package/dist/schema/create-registry.d.ts +2 -0
- package/dist/schema/create-registry.js +12 -0
- package/dist/schema/create-registry.js.map +1 -0
- package/dist/schema/generated/index.d.cts +2 -0
- package/dist/schema/generated/schemaorg.cjs +36386 -0
- package/dist/schema/generated/schemaorg.cjs.map +1 -0
- package/dist/schema/generated/schemaorg.d.cts +945 -0
- package/dist/schema/generated/schemaorg.d.ts +945 -0
- package/dist/schema/generated/schemaorg.js +36384 -0
- package/dist/schema/generated/schemaorg.js.map +1 -0
- package/dist/schema/index.cjs +11 -0
- package/dist/schema/index.d.cts +7 -0
- package/dist/schema/index.d.ts +6 -0
- package/dist/schema/index.js +5 -0
- package/dist/schema/registry.cjs +194 -0
- package/dist/schema/registry.cjs.map +1 -0
- package/dist/schema/registry.d.cts +2 -0
- package/dist/schema/registry.d.ts +2 -0
- package/dist/schema/registry.js +194 -0
- package/dist/schema/registry.js.map +1 -0
- package/dist/schema/types.d.cts +79 -0
- package/dist/schema/types.d.ts +79 -0
- package/dist/schema/validate.cjs +131 -0
- package/dist/schema/validate.cjs.map +1 -0
- package/dist/schema/validate.d.cts +16 -0
- package/dist/schema/validate.d.ts +16 -0
- package/dist/schema/validate.js +131 -0
- package/dist/schema/validate.js.map +1 -0
- package/dist/server/handlers.cjs +96 -0
- package/dist/server/handlers.cjs.map +1 -0
- package/dist/server/handlers.d.cts +3 -0
- package/dist/server/handlers.d.ts +3 -0
- package/dist/server/handlers.js +95 -0
- package/dist/server/handlers.js.map +1 -0
- package/dist/server/index.cjs +6 -0
- package/dist/server/index.d.cts +4 -0
- package/dist/server/index.d.ts +4 -0
- package/dist/server/index.js +3 -0
- package/dist/server/routes.cjs +17 -0
- package/dist/server/routes.cjs.map +1 -0
- package/dist/server/routes.d.cts +2 -0
- package/dist/server/routes.d.ts +2 -0
- package/dist/server/routes.js +17 -0
- package/dist/server/routes.js.map +1 -0
- package/dist/server/types.d.cts +61 -0
- package/dist/server/types.d.ts +61 -0
- package/dist/sitemap/generate.cjs +147 -0
- package/dist/sitemap/generate.cjs.map +1 -0
- package/dist/sitemap/generate.d.cts +3 -0
- package/dist/sitemap/generate.d.ts +3 -0
- package/dist/sitemap/generate.js +146 -0
- package/dist/sitemap/generate.js.map +1 -0
- package/dist/sitemap/index.cjs +11 -0
- package/dist/sitemap/index.d.cts +4 -0
- package/dist/sitemap/index.d.ts +4 -0
- package/dist/sitemap/index.js +3 -0
- package/dist/sitemap/types.d.cts +82 -0
- package/dist/sitemap/types.d.ts +82 -0
- package/dist/sitemap/xml.cjs +215 -0
- package/dist/sitemap/xml.cjs.map +1 -0
- package/dist/sitemap/xml.d.cts +16 -0
- package/dist/sitemap/xml.d.ts +16 -0
- package/dist/sitemap/xml.js +210 -0
- package/dist/sitemap/xml.js.map +1 -0
- package/dist/tanstack/index.cjs +13 -0
- package/dist/tanstack/index.cjs.map +1 -0
- package/dist/tanstack/index.d.cts +19 -0
- package/dist/tanstack/index.d.ts +18 -0
- package/dist/tanstack/index.js +12 -0
- package/dist/tanstack/index.js.map +1 -0
- package/dist/testing/diagnostics.cjs +27 -0
- package/dist/testing/diagnostics.cjs.map +1 -0
- package/dist/testing/diagnostics.d.cts +3 -0
- package/dist/testing/diagnostics.d.ts +3 -0
- package/dist/testing/diagnostics.js +27 -0
- package/dist/testing/diagnostics.js.map +1 -0
- package/dist/testing/fixture.cjs +80 -0
- package/dist/testing/fixture.cjs.map +1 -0
- package/dist/testing/fixture.d.cts +4 -0
- package/dist/testing/fixture.d.ts +4 -0
- package/dist/testing/fixture.js +78 -0
- package/dist/testing/fixture.js.map +1 -0
- package/dist/testing/index.cjs +9 -0
- package/dist/testing/index.d.cts +5 -0
- package/dist/testing/index.d.ts +5 -0
- package/dist/testing/index.js +4 -0
- package/dist/testing/inspect.cjs +63 -0
- package/dist/testing/inspect.cjs.map +1 -0
- package/dist/testing/inspect.d.cts +5 -0
- package/dist/testing/inspect.d.ts +5 -0
- package/dist/testing/inspect.js +63 -0
- package/dist/testing/inspect.js.map +1 -0
- package/dist/testing/types.d.cts +40 -0
- package/dist/testing/types.d.ts +40 -0
- package/dist/urls/index.cjs +8 -0
- package/dist/urls/index.d.cts +3 -0
- package/dist/urls/index.d.ts +3 -0
- package/dist/urls/index.js +1 -0
- package/dist/urls/normalize.cjs +65 -0
- package/dist/urls/normalize.cjs.map +1 -0
- package/dist/urls/normalize.d.cts +14 -0
- package/dist/urls/normalize.d.ts +14 -0
- package/dist/urls/normalize.js +60 -0
- package/dist/urls/normalize.js.map +1 -0
- package/dist/urls/types.d.cts +22 -0
- package/dist/urls/types.d.ts +22 -0
- package/dist/validation/index.cjs +5 -0
- package/dist/validation/index.d.cts +3 -0
- package/dist/validation/index.d.ts +3 -0
- package/dist/validation/index.js +1 -0
- package/dist/validation/types.d.cts +75 -0
- package/dist/validation/types.d.ts +75 -0
- package/dist/validation/validate.cjs +452 -0
- package/dist/validation/validate.cjs.map +1 -0
- package/dist/validation/validate.d.cts +7 -0
- package/dist/validation/validate.d.ts +6 -0
- package/dist/validation/validate.js +450 -0
- package/dist/validation/validate.js.map +1 -0
- package/package.json +203 -4
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { createRobotsHandler, createSitemapHandler } from "./handlers.js";
|
|
2
|
+
// src/server/routes.ts
|
|
3
|
+
const toResponse = (result) => new Response(result.body, {
|
|
4
|
+
status: result.status,
|
|
5
|
+
headers: result.headers
|
|
6
|
+
});
|
|
7
|
+
const createSeoRouteHandlers = (options) => {
|
|
8
|
+
const sitemapHandler = createSitemapHandler(options.sitemap);
|
|
9
|
+
const robotsHandler = createRobotsHandler(options.robots);
|
|
10
|
+
return {
|
|
11
|
+
sitemap: async (request) => toResponse(await sitemapHandler(request)),
|
|
12
|
+
robots: async (request) => toResponse(await robotsHandler(request))
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
export { createSeoRouteHandlers };
|
|
16
|
+
|
|
17
|
+
//# sourceMappingURL=routes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.js","names":[],"sources":["../../src/server/routes.ts"],"sourcesContent":["import { createRobotsHandler, createSitemapHandler } from \"./handlers\";\n\nimport type {\n SeoRouteHandlers,\n SeoRouteOptions,\n SeoTextResponse,\n} from \"./types\";\n\nconst toResponse = (result: SeoTextResponse): Response =>\n new Response(result.body, {\n status: result.status,\n headers: result.headers,\n });\n\nexport const createSeoRouteHandlers = (\n options: SeoRouteOptions,\n): SeoRouteHandlers => {\n const sitemapHandler = createSitemapHandler(options.sitemap);\n const robotsHandler = createRobotsHandler(options.robots);\n\n return {\n sitemap: async (request) => toResponse(await sitemapHandler(request)),\n\n robots: async (request) => toResponse(await robotsHandler(request)),\n };\n};\n"],"mappings":";;AAQA,MAAM,cAAc,WAClB,IAAI,SAAS,OAAO,MAAM;CACxB,QAAQ,OAAO;CACf,SAAS,OAAO;AAClB,CAAC;AAEH,MAAa,0BACX,YACqB;CACrB,MAAM,iBAAiB,qBAAqB,QAAQ,OAAO;CAC3D,MAAM,gBAAgB,oBAAoB,QAAQ,MAAM;CAExD,OAAO;EACL,SAAS,OAAO,YAAY,WAAW,MAAM,eAAe,OAAO,CAAC;EAEpE,QAAQ,OAAO,YAAY,WAAW,MAAM,cAAc,OAAO,CAAC;CACpE;AACF"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { SeoConfig } from "../context/types.cjs";
|
|
2
|
+
import { SeoGraph } from "../core/types.cjs";
|
|
3
|
+
import { SeoPageMetadata } from "../metadata/types.cjs";
|
|
4
|
+
import { RobotsOptions } from "../robots/types.cjs";
|
|
5
|
+
import { SitemapGenerationOptions } from "../sitemap/types.cjs";
|
|
6
|
+
export interface SeoRequestContext {
|
|
7
|
+
requestId?: string;
|
|
8
|
+
url: string;
|
|
9
|
+
locale?: string;
|
|
10
|
+
config: SeoConfig;
|
|
11
|
+
}
|
|
12
|
+
export interface SeoServerResult {
|
|
13
|
+
metadata?: SeoPageMetadata;
|
|
14
|
+
graph?: SeoGraph;
|
|
15
|
+
}
|
|
16
|
+
export interface SeoServerAdapter {
|
|
17
|
+
resolve(context: SeoRequestContext): Promise<SeoServerResult>;
|
|
18
|
+
}
|
|
19
|
+
export interface SeoTextResponse {
|
|
20
|
+
status: number;
|
|
21
|
+
headers: Record<string, string>;
|
|
22
|
+
body: string;
|
|
23
|
+
}
|
|
24
|
+
export interface SeoTextHandlerOptions {
|
|
25
|
+
contentType: string;
|
|
26
|
+
cacheControl?: string;
|
|
27
|
+
headers?: Record<string, string>;
|
|
28
|
+
status?: number;
|
|
29
|
+
}
|
|
30
|
+
export interface SeoTextHandler {
|
|
31
|
+
(request?: Request): SeoTextResponse | Promise<SeoTextResponse>;
|
|
32
|
+
}
|
|
33
|
+
export interface SeoSitemapHandlerOptions {
|
|
34
|
+
sitemap: SitemapGenerationOptions;
|
|
35
|
+
cacheControl?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Public URL of the sitemap index.
|
|
38
|
+
*
|
|
39
|
+
* Defaults to /{filename}.xml, where filename defaults to "sitemap".
|
|
40
|
+
*/
|
|
41
|
+
path?: string;
|
|
42
|
+
}
|
|
43
|
+
export interface SeoRobotsHandlerOptions {
|
|
44
|
+
robots: RobotsOptions;
|
|
45
|
+
cacheControl?: string;
|
|
46
|
+
/**
|
|
47
|
+
* Public URL of robots.txt.
|
|
48
|
+
*
|
|
49
|
+
* Defaults to /robots.txt.
|
|
50
|
+
*/
|
|
51
|
+
path?: string;
|
|
52
|
+
}
|
|
53
|
+
export interface SeoRouteOptions {
|
|
54
|
+
sitemap: SeoSitemapHandlerOptions;
|
|
55
|
+
robots: SeoRobotsHandlerOptions;
|
|
56
|
+
}
|
|
57
|
+
export type SeoHttpHandler = (request: Request) => Promise<Response>;
|
|
58
|
+
export interface SeoRouteHandlers {
|
|
59
|
+
sitemap: SeoHttpHandler;
|
|
60
|
+
robots: SeoHttpHandler;
|
|
61
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { SeoConfig } from "../context/types.js";
|
|
2
|
+
import { SeoGraph } from "../core/types.js";
|
|
3
|
+
import { SeoPageMetadata } from "../metadata/types.js";
|
|
4
|
+
import { RobotsOptions } from "../robots/types.js";
|
|
5
|
+
import { SitemapGenerationOptions } from "../sitemap/types.js";
|
|
6
|
+
export interface SeoRequestContext {
|
|
7
|
+
requestId?: string;
|
|
8
|
+
url: string;
|
|
9
|
+
locale?: string;
|
|
10
|
+
config: SeoConfig;
|
|
11
|
+
}
|
|
12
|
+
export interface SeoServerResult {
|
|
13
|
+
metadata?: SeoPageMetadata;
|
|
14
|
+
graph?: SeoGraph;
|
|
15
|
+
}
|
|
16
|
+
export interface SeoServerAdapter {
|
|
17
|
+
resolve(context: SeoRequestContext): Promise<SeoServerResult>;
|
|
18
|
+
}
|
|
19
|
+
export interface SeoTextResponse {
|
|
20
|
+
status: number;
|
|
21
|
+
headers: Record<string, string>;
|
|
22
|
+
body: string;
|
|
23
|
+
}
|
|
24
|
+
export interface SeoTextHandlerOptions {
|
|
25
|
+
contentType: string;
|
|
26
|
+
cacheControl?: string;
|
|
27
|
+
headers?: Record<string, string>;
|
|
28
|
+
status?: number;
|
|
29
|
+
}
|
|
30
|
+
export interface SeoTextHandler {
|
|
31
|
+
(request?: Request): SeoTextResponse | Promise<SeoTextResponse>;
|
|
32
|
+
}
|
|
33
|
+
export interface SeoSitemapHandlerOptions {
|
|
34
|
+
sitemap: SitemapGenerationOptions;
|
|
35
|
+
cacheControl?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Public URL of the sitemap index.
|
|
38
|
+
*
|
|
39
|
+
* Defaults to /{filename}.xml, where filename defaults to "sitemap".
|
|
40
|
+
*/
|
|
41
|
+
path?: string;
|
|
42
|
+
}
|
|
43
|
+
export interface SeoRobotsHandlerOptions {
|
|
44
|
+
robots: RobotsOptions;
|
|
45
|
+
cacheControl?: string;
|
|
46
|
+
/**
|
|
47
|
+
* Public URL of robots.txt.
|
|
48
|
+
*
|
|
49
|
+
* Defaults to /robots.txt.
|
|
50
|
+
*/
|
|
51
|
+
path?: string;
|
|
52
|
+
}
|
|
53
|
+
export interface SeoRouteOptions {
|
|
54
|
+
sitemap: SeoSitemapHandlerOptions;
|
|
55
|
+
robots: SeoRobotsHandlerOptions;
|
|
56
|
+
}
|
|
57
|
+
export type SeoHttpHandler = (request: Request) => Promise<Response>;
|
|
58
|
+
export interface SeoRouteHandlers {
|
|
59
|
+
sitemap: SeoHttpHandler;
|
|
60
|
+
robots: SeoHttpHandler;
|
|
61
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
const require_normalize = require("../urls/normalize.cjs");
|
|
2
|
+
const require_xml = require("./xml.cjs");
|
|
3
|
+
// src/sitemap/generate.ts
|
|
4
|
+
const DEFAULT_MAX_ENTRIES = 5e4;
|
|
5
|
+
const DEFAULT_MAX_BYTES = 52428800;
|
|
6
|
+
const normalizeEntries = (entries, baseUrl, allowExternalUrls) => {
|
|
7
|
+
const unique = /* @__PURE__ */ new Map();
|
|
8
|
+
for (const entry of entries) {
|
|
9
|
+
if (entry.indexable === false || entry.statusCode !== void 0 && entry.statusCode !== 200 || entry.redirectTo) continue;
|
|
10
|
+
try {
|
|
11
|
+
const url = require_normalize.normalizeSeoUrl(entry.url, {
|
|
12
|
+
baseUrl,
|
|
13
|
+
allowExternal: allowExternalUrls
|
|
14
|
+
});
|
|
15
|
+
if (unique.has(url)) continue;
|
|
16
|
+
const alternates = (entry.alternates ?? []).flatMap((alternate) => {
|
|
17
|
+
try {
|
|
18
|
+
return [{
|
|
19
|
+
...alternate,
|
|
20
|
+
url: require_normalize.normalizeSeoUrl(alternate.url, {
|
|
21
|
+
baseUrl,
|
|
22
|
+
allowExternal: allowExternalUrls
|
|
23
|
+
})
|
|
24
|
+
}];
|
|
25
|
+
} catch {
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
const images = (entry.images ?? []).flatMap((image) => {
|
|
30
|
+
try {
|
|
31
|
+
return [{
|
|
32
|
+
...image,
|
|
33
|
+
url: require_normalize.normalizeSeoUrl(image.url, {
|
|
34
|
+
baseUrl,
|
|
35
|
+
allowExternal: true
|
|
36
|
+
})
|
|
37
|
+
}];
|
|
38
|
+
} catch {
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
unique.set(url, {
|
|
43
|
+
...entry,
|
|
44
|
+
url,
|
|
45
|
+
alternates,
|
|
46
|
+
images
|
|
47
|
+
});
|
|
48
|
+
} catch {}
|
|
49
|
+
}
|
|
50
|
+
return [...unique.values()].sort((a, b) => String(a.url).localeCompare(String(b.url)));
|
|
51
|
+
};
|
|
52
|
+
const serializeSitemap = (options) => {
|
|
53
|
+
const baseUrl = require_normalize.normalizeSeoOrigin(options.baseUrl);
|
|
54
|
+
const issues = require_xml.validateSitemapEntries(options.entries, {
|
|
55
|
+
baseUrl,
|
|
56
|
+
allowExternalUrls: options.allowExternalUrls
|
|
57
|
+
});
|
|
58
|
+
const entries = normalizeEntries(options.entries, baseUrl, options.allowExternalUrls ?? false);
|
|
59
|
+
return {
|
|
60
|
+
xml: require_xml.createSitemapXml(entries, {
|
|
61
|
+
baseUrl,
|
|
62
|
+
includeLastModified: options.includeLastModified,
|
|
63
|
+
allowExternalUrls: options.allowExternalUrls
|
|
64
|
+
}),
|
|
65
|
+
entries,
|
|
66
|
+
issues
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
const createSitemapIndexXml = (files) => {
|
|
70
|
+
return [
|
|
71
|
+
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
|
|
72
|
+
"<sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">",
|
|
73
|
+
files.map((file) => `<sitemap><loc>${require_xml.escapeXml(file.url)}</loc></sitemap>`).join(""),
|
|
74
|
+
"</sitemapindex>"
|
|
75
|
+
].join("");
|
|
76
|
+
};
|
|
77
|
+
const generateSitemap = (options) => {
|
|
78
|
+
const baseUrl = require_normalize.normalizeSeoOrigin(options.baseUrl);
|
|
79
|
+
const maxEntries = options.maxEntries ?? DEFAULT_MAX_ENTRIES;
|
|
80
|
+
const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
81
|
+
if (!Number.isInteger(maxEntries) || maxEntries < 1 || maxEntries > DEFAULT_MAX_ENTRIES) throw new Error("Sitemap maxEntries must be between 1 and 50,000.");
|
|
82
|
+
if (!Number.isInteger(maxBytes) || maxBytes < 1024 || maxBytes > DEFAULT_MAX_BYTES) throw new Error("Sitemap maxBytes must be between 1 KB and 50 MB.");
|
|
83
|
+
const issues = require_xml.validateSitemapEntries(options.entries, {
|
|
84
|
+
baseUrl,
|
|
85
|
+
allowExternalUrls: options.allowExternalUrls,
|
|
86
|
+
validateAlternates: options.validateAlternates
|
|
87
|
+
});
|
|
88
|
+
const entries = normalizeEntries(options.entries, baseUrl, options.allowExternalUrls ?? false);
|
|
89
|
+
const files = [];
|
|
90
|
+
let current = [];
|
|
91
|
+
const createXml = (items) => require_xml.createSitemapXml(items, {
|
|
92
|
+
baseUrl,
|
|
93
|
+
includeLastModified: options.includeLastModified,
|
|
94
|
+
allowExternalUrls: options.allowExternalUrls
|
|
95
|
+
});
|
|
96
|
+
const emptyDocumentBytes = require_xml.sitemapByteLength(createXml([]));
|
|
97
|
+
let currentBytes = emptyDocumentBytes;
|
|
98
|
+
const flush = () => {
|
|
99
|
+
if (current.length === 0) return;
|
|
100
|
+
const number = files.length + 1;
|
|
101
|
+
const filename = `${options.filename ?? "sitemap"}-${number}.xml`;
|
|
102
|
+
const url = require_normalize.normalizeSeoUrl(`/${filename}`, {
|
|
103
|
+
baseUrl,
|
|
104
|
+
allowExternal: false
|
|
105
|
+
});
|
|
106
|
+
files.push({
|
|
107
|
+
filename,
|
|
108
|
+
url,
|
|
109
|
+
xml: createXml(current),
|
|
110
|
+
entries: current
|
|
111
|
+
});
|
|
112
|
+
current = [];
|
|
113
|
+
currentBytes = emptyDocumentBytes;
|
|
114
|
+
};
|
|
115
|
+
for (const entry of entries) {
|
|
116
|
+
const singleXml = createXml([entry]);
|
|
117
|
+
const singleBytes = require_xml.sitemapByteLength(singleXml);
|
|
118
|
+
if (singleBytes > maxBytes) {
|
|
119
|
+
issues.push({
|
|
120
|
+
code: "ENTRY_TOO_LARGE",
|
|
121
|
+
message: `Sitemap entry exceeds the configured byte limit: ${entry.url}.`,
|
|
122
|
+
url: String(entry.url)
|
|
123
|
+
});
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const entryBytes = singleBytes - emptyDocumentBytes;
|
|
127
|
+
const exceedsCount = current.length + 1 > maxEntries;
|
|
128
|
+
const exceedsBytes = currentBytes + entryBytes > maxBytes;
|
|
129
|
+
if (current.length > 0 && (exceedsCount || exceedsBytes)) flush();
|
|
130
|
+
current.push(entry);
|
|
131
|
+
currentBytes += entryBytes;
|
|
132
|
+
}
|
|
133
|
+
flush();
|
|
134
|
+
if (files.length > DEFAULT_MAX_ENTRIES) throw new Error("Sitemap index exceeds the maximum of 50,000 sitemap references.");
|
|
135
|
+
const indexXml = files.length > 1 ? createSitemapIndexXml(files) : void 0;
|
|
136
|
+
return {
|
|
137
|
+
files,
|
|
138
|
+
indexXml,
|
|
139
|
+
indexUrl: indexXml ? require_normalize.normalizeSeoUrl(`/${options.filename ?? "sitemap"}.xml`, { baseUrl }) : void 0,
|
|
140
|
+
totalEntries: files.reduce((total, file) => total + file.entries.length, 0),
|
|
141
|
+
issues
|
|
142
|
+
};
|
|
143
|
+
};
|
|
144
|
+
exports.generateSitemap = generateSitemap;
|
|
145
|
+
exports.serializeSitemap = serializeSitemap;
|
|
146
|
+
|
|
147
|
+
//# sourceMappingURL=generate.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate.cjs","names":["normalizeSeoUrl","normalizeSeoOrigin","validateSitemapEntries","createSitemapXml","escapeXml","sitemapByteLength"],"sources":["../../src/sitemap/generate.ts"],"sourcesContent":["import type {\n SitemapEntry,\n SitemapOptions,\n SitemapGenerationOptions,\n SitemapResult,\n SitemapCollection,\n SitemapFile,\n} from \"./types\";\n\nimport { normalizeSeoUrl, normalizeSeoOrigin } from \"../urls\";\n\nimport {\n createSitemapXml,\n escapeXml,\n sitemapByteLength,\n validateSitemapEntries,\n} from \"./xml\";\n\nconst DEFAULT_MAX_ENTRIES = 50_000;\nconst DEFAULT_MAX_BYTES = 50 * 1024 * 1024;\n\nconst normalizeEntries = (\n entries: readonly SitemapEntry[],\n baseUrl: string,\n allowExternalUrls: boolean,\n): SitemapEntry[] => {\n const unique = new Map<string, SitemapEntry>();\n\n for (const entry of entries) {\n if (\n entry.indexable === false ||\n (entry.statusCode !== undefined && entry.statusCode !== 200) ||\n entry.redirectTo\n ) {\n continue;\n }\n\n try {\n const url = normalizeSeoUrl(entry.url, {\n baseUrl,\n allowExternal: allowExternalUrls,\n });\n\n if (unique.has(url)) {\n continue;\n }\n\n const alternates = (entry.alternates ?? []).flatMap((alternate) => {\n try {\n return [\n {\n ...alternate,\n url: normalizeSeoUrl(alternate.url, {\n baseUrl,\n allowExternal: allowExternalUrls,\n }),\n },\n ];\n } catch {\n return [];\n }\n });\n\n const images = (entry.images ?? []).flatMap((image) => {\n try {\n return [\n {\n ...image,\n url: normalizeSeoUrl(image.url, {\n baseUrl,\n allowExternal: true,\n }),\n },\n ];\n } catch {\n return [];\n }\n });\n\n unique.set(url, {\n ...entry,\n url,\n alternates,\n images,\n });\n } catch {\n // Invalid URLs are reported by validateSitemapEntries.\n }\n }\n\n return [...unique.values()].sort((a, b) =>\n String(a.url).localeCompare(String(b.url)),\n );\n};\n\nexport const serializeSitemap = (options: SitemapOptions): SitemapResult => {\n const baseUrl = normalizeSeoOrigin(options.baseUrl);\n\n const issues = validateSitemapEntries(options.entries, {\n baseUrl,\n allowExternalUrls: options.allowExternalUrls,\n });\n\n const entries = normalizeEntries(\n options.entries,\n baseUrl,\n options.allowExternalUrls ?? false,\n );\n\n return {\n xml: createSitemapXml(entries, {\n baseUrl,\n includeLastModified: options.includeLastModified,\n allowExternalUrls: options.allowExternalUrls,\n }),\n entries,\n issues,\n };\n};\n\nconst createSitemapIndexXml = (files: readonly SitemapFile[]): string => {\n const content = files\n .map((file) => `<sitemap><loc>${escapeXml(file.url)}</loc></sitemap>`)\n .join(\"\");\n\n return [\n '<?xml version=\"1.0\" encoding=\"UTF-8\"?>',\n '<sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">',\n content,\n \"</sitemapindex>\",\n ].join(\"\");\n};\n\nexport const generateSitemap = (\n options: SitemapGenerationOptions,\n): SitemapCollection => {\n const baseUrl = normalizeSeoOrigin(options.baseUrl);\n\n const maxEntries = options.maxEntries ?? DEFAULT_MAX_ENTRIES;\n const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;\n\n if (\n !Number.isInteger(maxEntries) ||\n maxEntries < 1 ||\n maxEntries > DEFAULT_MAX_ENTRIES\n ) {\n throw new Error(\"Sitemap maxEntries must be between 1 and 50,000.\");\n }\n\n if (\n !Number.isInteger(maxBytes) ||\n maxBytes < 1024 ||\n maxBytes > DEFAULT_MAX_BYTES\n ) {\n throw new Error(\"Sitemap maxBytes must be between 1 KB and 50 MB.\");\n }\n\n const issues = validateSitemapEntries(options.entries, {\n baseUrl,\n allowExternalUrls: options.allowExternalUrls,\n validateAlternates: options.validateAlternates,\n });\n\n const entries = normalizeEntries(\n options.entries,\n baseUrl,\n options.allowExternalUrls ?? false,\n );\n\n const files: SitemapFile[] = [];\n\n let current: SitemapEntry[] = [];\n\n const createXml = (items: readonly SitemapEntry[]) =>\n createSitemapXml(items, {\n baseUrl,\n includeLastModified: options.includeLastModified,\n allowExternalUrls: options.allowExternalUrls,\n });\n\n // The empty document represents the fixed XML header and footer.\n const emptyDocumentBytes = sitemapByteLength(createXml([]));\n\n let currentBytes = emptyDocumentBytes;\n\n const flush = () => {\n if (current.length === 0) {\n return;\n }\n\n const number = files.length + 1;\n\n const filename = `${options.filename ?? \"sitemap\"}-${number}.xml`;\n\n const url = normalizeSeoUrl(`/${filename}`, {\n baseUrl,\n allowExternal: false,\n });\n\n files.push({\n filename,\n url,\n xml: createXml(current),\n entries: current,\n });\n\n current = [];\n currentBytes = emptyDocumentBytes;\n };\n\n for (const entry of entries) {\n const singleXml = createXml([entry]);\n const singleBytes = sitemapByteLength(singleXml);\n\n if (singleBytes > maxBytes) {\n issues.push({\n code: \"ENTRY_TOO_LARGE\",\n message: `Sitemap entry exceeds the configured byte limit: ${entry.url}.`,\n url: String(entry.url),\n });\n\n continue;\n }\n\n // This includes the entry fragment and its namespace overhead.\n // Counting namespace overhead per entry is intentionally conservative.\n const entryBytes = singleBytes - emptyDocumentBytes;\n\n const exceedsCount = current.length + 1 > maxEntries;\n const exceedsBytes = currentBytes + entryBytes > maxBytes;\n\n if (current.length > 0 && (exceedsCount || exceedsBytes)) {\n flush();\n }\n\n current.push(entry);\n currentBytes += entryBytes;\n }\n\n flush();\n\n if (files.length > DEFAULT_MAX_ENTRIES) {\n throw new Error(\n \"Sitemap index exceeds the maximum of 50,000 sitemap references.\",\n );\n }\n\n const indexXml = files.length > 1 ? createSitemapIndexXml(files) : undefined;\n\n const indexUrl = indexXml\n ? normalizeSeoUrl(`/${options.filename ?? \"sitemap\"}.xml`, { baseUrl })\n : undefined;\n\n return {\n files,\n indexXml,\n indexUrl,\n totalEntries: files.reduce((total, file) => total + file.entries.length, 0),\n issues,\n };\n};\n"],"mappings":";;;AAkBA,MAAM,sBAAsB;AAC5B,MAAM,oBAAoB;AAE1B,MAAM,oBACJ,SACA,SACA,sBACmB;CACnB,MAAM,yBAAS,IAAI,IAA0B;CAE7C,KAAK,MAAM,SAAS,SAAS;EAC3B,IACE,MAAM,cAAc,SACnB,MAAM,eAAe,KAAA,KAAa,MAAM,eAAe,OACxD,MAAM,YAEN;EAGF,IAAI;GACF,MAAM,MAAMA,kBAAAA,gBAAgB,MAAM,KAAK;IACrC;IACA,eAAe;GACjB,CAAC;GAED,IAAI,OAAO,IAAI,GAAG,GAChB;GAGF,MAAM,cAAc,MAAM,cAAc,CAAC,EAAA,CAAG,SAAS,cAAc;IACjE,IAAI;KACF,OAAO,CACL;MACE,GAAG;MACH,KAAKA,kBAAAA,gBAAgB,UAAU,KAAK;OAClC;OACA,eAAe;MACjB,CAAC;KACH,CACF;IACF,QAAQ;KACN,OAAO,CAAC;IACV;GACF,CAAC;GAED,MAAM,UAAU,MAAM,UAAU,CAAC,EAAA,CAAG,SAAS,UAAU;IACrD,IAAI;KACF,OAAO,CACL;MACE,GAAG;MACH,KAAKA,kBAAAA,gBAAgB,MAAM,KAAK;OAC9B;OACA,eAAe;MACjB,CAAC;KACH,CACF;IACF,QAAQ;KACN,OAAO,CAAC;IACV;GACF,CAAC;GAED,OAAO,IAAI,KAAK;IACd,GAAG;IACH;IACA;IACA;GACF,CAAC;EACH,QAAQ,CAER;CACF;CAEA,OAAO,CAAC,GAAG,OAAO,OAAO,CAAC,CAAC,CAAC,MAAM,GAAG,MACnC,OAAO,EAAE,GAAG,CAAC,CAAC,cAAc,OAAO,EAAE,GAAG,CAAC,CAC3C;AACF;AAEA,MAAa,oBAAoB,YAA2C;CAC1E,MAAM,UAAUC,kBAAAA,mBAAmB,QAAQ,OAAO;CAElD,MAAM,SAASC,YAAAA,uBAAuB,QAAQ,SAAS;EACrD;EACA,mBAAmB,QAAQ;CAC7B,CAAC;CAED,MAAM,UAAU,iBACd,QAAQ,SACR,SACA,QAAQ,qBAAqB,KAC/B;CAEA,OAAO;EACL,KAAKC,YAAAA,iBAAiB,SAAS;GAC7B;GACA,qBAAqB,QAAQ;GAC7B,mBAAmB,QAAQ;EAC7B,CAAC;EACD;EACA;CACF;AACF;AAEA,MAAM,yBAAyB,UAA0C;CAKvE,OAAO;EACL;EACA;EANc,MACb,KAAK,SAAS,iBAAiBC,YAAAA,UAAU,KAAK,GAAG,EAAE,iBAAiB,CAAC,CACrE,KAAK,EAKA;EACN;CACF,CAAC,CAAC,KAAK,EAAE;AACX;AAEA,MAAa,mBACX,YACsB;CACtB,MAAM,UAAUH,kBAAAA,mBAAmB,QAAQ,OAAO;CAElD,MAAM,aAAa,QAAQ,cAAc;CACzC,MAAM,WAAW,QAAQ,YAAY;CAErC,IACE,CAAC,OAAO,UAAU,UAAU,KAC5B,aAAa,KACb,aAAa,qBAEb,MAAM,IAAI,MAAM,kDAAkD;CAGpE,IACE,CAAC,OAAO,UAAU,QAAQ,KAC1B,WAAW,QACX,WAAW,mBAEX,MAAM,IAAI,MAAM,kDAAkD;CAGpE,MAAM,SAASC,YAAAA,uBAAuB,QAAQ,SAAS;EACrD;EACA,mBAAmB,QAAQ;EAC3B,oBAAoB,QAAQ;CAC9B,CAAC;CAED,MAAM,UAAU,iBACd,QAAQ,SACR,SACA,QAAQ,qBAAqB,KAC/B;CAEA,MAAM,QAAuB,CAAC;CAE9B,IAAI,UAA0B,CAAC;CAE/B,MAAM,aAAa,UACjBC,YAAAA,iBAAiB,OAAO;EACtB;EACA,qBAAqB,QAAQ;EAC7B,mBAAmB,QAAQ;CAC7B,CAAC;CAGH,MAAM,qBAAqBE,YAAAA,kBAAkB,UAAU,CAAC,CAAC,CAAC;CAE1D,IAAI,eAAe;CAEnB,MAAM,cAAc;EAClB,IAAI,QAAQ,WAAW,GACrB;EAGF,MAAM,SAAS,MAAM,SAAS;EAE9B,MAAM,WAAW,GAAG,QAAQ,YAAY,UAAU,GAAG,OAAO;EAE5D,MAAM,MAAML,kBAAAA,gBAAgB,IAAI,YAAY;GAC1C;GACA,eAAe;EACjB,CAAC;EAED,MAAM,KAAK;GACT;GACA;GACA,KAAK,UAAU,OAAO;GACtB,SAAS;EACX,CAAC;EAED,UAAU,CAAC;EACX,eAAe;CACjB;CAEA,KAAK,MAAM,SAAS,SAAS;EAC3B,MAAM,YAAY,UAAU,CAAC,KAAK,CAAC;EACnC,MAAM,cAAcK,YAAAA,kBAAkB,SAAS;EAE/C,IAAI,cAAc,UAAU;GAC1B,OAAO,KAAK;IACV,MAAM;IACN,SAAS,oDAAoD,MAAM,IAAI;IACvE,KAAK,OAAO,MAAM,GAAG;GACvB,CAAC;GAED;EACF;EAIA,MAAM,aAAa,cAAc;EAEjC,MAAM,eAAe,QAAQ,SAAS,IAAI;EAC1C,MAAM,eAAe,eAAe,aAAa;EAEjD,IAAI,QAAQ,SAAS,MAAM,gBAAgB,eACzC,MAAM;EAGR,QAAQ,KAAK,KAAK;EAClB,gBAAgB;CAClB;CAEA,MAAM;CAEN,IAAI,MAAM,SAAS,qBACjB,MAAM,IAAI,MACR,iEACF;CAGF,MAAM,WAAW,MAAM,SAAS,IAAI,sBAAsB,KAAK,IAAI,KAAA;CAMnE,OAAO;EACL;EACA;EACA,UAPe,WACbL,kBAAAA,gBAAgB,IAAI,QAAQ,YAAY,UAAU,OAAO,EAAE,QAAQ,CAAC,IACpE,KAAA;EAMF,cAAc,MAAM,QAAQ,OAAO,SAAS,QAAQ,KAAK,QAAQ,QAAQ,CAAC;EAC1E;CACF;AACF"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { SitemapCollection, SitemapGenerationOptions, SitemapOptions, SitemapResult } from "./types.cjs";
|
|
2
|
+
export declare const serializeSitemap: (options: SitemapOptions) => SitemapResult;
|
|
3
|
+
export declare const generateSitemap: (options: SitemapGenerationOptions) => SitemapCollection;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { SitemapCollection, SitemapGenerationOptions, SitemapOptions, SitemapResult } from "./types.js";
|
|
2
|
+
export declare const serializeSitemap: (options: SitemapOptions) => SitemapResult;
|
|
3
|
+
export declare const generateSitemap: (options: SitemapGenerationOptions) => SitemapCollection;
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { normalizeSeoOrigin, normalizeSeoUrl } from "../urls/normalize.js";
|
|
2
|
+
import { createSitemapXml, escapeXml, sitemapByteLength, validateSitemapEntries } from "./xml.js";
|
|
3
|
+
// src/sitemap/generate.ts
|
|
4
|
+
const DEFAULT_MAX_ENTRIES = 5e4;
|
|
5
|
+
const DEFAULT_MAX_BYTES = 52428800;
|
|
6
|
+
const normalizeEntries = (entries, baseUrl, allowExternalUrls) => {
|
|
7
|
+
const unique = /* @__PURE__ */ new Map();
|
|
8
|
+
for (const entry of entries) {
|
|
9
|
+
if (entry.indexable === false || entry.statusCode !== void 0 && entry.statusCode !== 200 || entry.redirectTo) continue;
|
|
10
|
+
try {
|
|
11
|
+
const url = normalizeSeoUrl(entry.url, {
|
|
12
|
+
baseUrl,
|
|
13
|
+
allowExternal: allowExternalUrls
|
|
14
|
+
});
|
|
15
|
+
if (unique.has(url)) continue;
|
|
16
|
+
const alternates = (entry.alternates ?? []).flatMap((alternate) => {
|
|
17
|
+
try {
|
|
18
|
+
return [{
|
|
19
|
+
...alternate,
|
|
20
|
+
url: normalizeSeoUrl(alternate.url, {
|
|
21
|
+
baseUrl,
|
|
22
|
+
allowExternal: allowExternalUrls
|
|
23
|
+
})
|
|
24
|
+
}];
|
|
25
|
+
} catch {
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
const images = (entry.images ?? []).flatMap((image) => {
|
|
30
|
+
try {
|
|
31
|
+
return [{
|
|
32
|
+
...image,
|
|
33
|
+
url: normalizeSeoUrl(image.url, {
|
|
34
|
+
baseUrl,
|
|
35
|
+
allowExternal: true
|
|
36
|
+
})
|
|
37
|
+
}];
|
|
38
|
+
} catch {
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
unique.set(url, {
|
|
43
|
+
...entry,
|
|
44
|
+
url,
|
|
45
|
+
alternates,
|
|
46
|
+
images
|
|
47
|
+
});
|
|
48
|
+
} catch {}
|
|
49
|
+
}
|
|
50
|
+
return [...unique.values()].sort((a, b) => String(a.url).localeCompare(String(b.url)));
|
|
51
|
+
};
|
|
52
|
+
const serializeSitemap = (options) => {
|
|
53
|
+
const baseUrl = normalizeSeoOrigin(options.baseUrl);
|
|
54
|
+
const issues = validateSitemapEntries(options.entries, {
|
|
55
|
+
baseUrl,
|
|
56
|
+
allowExternalUrls: options.allowExternalUrls
|
|
57
|
+
});
|
|
58
|
+
const entries = normalizeEntries(options.entries, baseUrl, options.allowExternalUrls ?? false);
|
|
59
|
+
return {
|
|
60
|
+
xml: createSitemapXml(entries, {
|
|
61
|
+
baseUrl,
|
|
62
|
+
includeLastModified: options.includeLastModified,
|
|
63
|
+
allowExternalUrls: options.allowExternalUrls
|
|
64
|
+
}),
|
|
65
|
+
entries,
|
|
66
|
+
issues
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
const createSitemapIndexXml = (files) => {
|
|
70
|
+
return [
|
|
71
|
+
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
|
|
72
|
+
"<sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">",
|
|
73
|
+
files.map((file) => `<sitemap><loc>${escapeXml(file.url)}</loc></sitemap>`).join(""),
|
|
74
|
+
"</sitemapindex>"
|
|
75
|
+
].join("");
|
|
76
|
+
};
|
|
77
|
+
const generateSitemap = (options) => {
|
|
78
|
+
const baseUrl = normalizeSeoOrigin(options.baseUrl);
|
|
79
|
+
const maxEntries = options.maxEntries ?? DEFAULT_MAX_ENTRIES;
|
|
80
|
+
const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
81
|
+
if (!Number.isInteger(maxEntries) || maxEntries < 1 || maxEntries > DEFAULT_MAX_ENTRIES) throw new Error("Sitemap maxEntries must be between 1 and 50,000.");
|
|
82
|
+
if (!Number.isInteger(maxBytes) || maxBytes < 1024 || maxBytes > DEFAULT_MAX_BYTES) throw new Error("Sitemap maxBytes must be between 1 KB and 50 MB.");
|
|
83
|
+
const issues = validateSitemapEntries(options.entries, {
|
|
84
|
+
baseUrl,
|
|
85
|
+
allowExternalUrls: options.allowExternalUrls,
|
|
86
|
+
validateAlternates: options.validateAlternates
|
|
87
|
+
});
|
|
88
|
+
const entries = normalizeEntries(options.entries, baseUrl, options.allowExternalUrls ?? false);
|
|
89
|
+
const files = [];
|
|
90
|
+
let current = [];
|
|
91
|
+
const createXml = (items) => createSitemapXml(items, {
|
|
92
|
+
baseUrl,
|
|
93
|
+
includeLastModified: options.includeLastModified,
|
|
94
|
+
allowExternalUrls: options.allowExternalUrls
|
|
95
|
+
});
|
|
96
|
+
const emptyDocumentBytes = sitemapByteLength(createXml([]));
|
|
97
|
+
let currentBytes = emptyDocumentBytes;
|
|
98
|
+
const flush = () => {
|
|
99
|
+
if (current.length === 0) return;
|
|
100
|
+
const number = files.length + 1;
|
|
101
|
+
const filename = `${options.filename ?? "sitemap"}-${number}.xml`;
|
|
102
|
+
const url = normalizeSeoUrl(`/${filename}`, {
|
|
103
|
+
baseUrl,
|
|
104
|
+
allowExternal: false
|
|
105
|
+
});
|
|
106
|
+
files.push({
|
|
107
|
+
filename,
|
|
108
|
+
url,
|
|
109
|
+
xml: createXml(current),
|
|
110
|
+
entries: current
|
|
111
|
+
});
|
|
112
|
+
current = [];
|
|
113
|
+
currentBytes = emptyDocumentBytes;
|
|
114
|
+
};
|
|
115
|
+
for (const entry of entries) {
|
|
116
|
+
const singleXml = createXml([entry]);
|
|
117
|
+
const singleBytes = sitemapByteLength(singleXml);
|
|
118
|
+
if (singleBytes > maxBytes) {
|
|
119
|
+
issues.push({
|
|
120
|
+
code: "ENTRY_TOO_LARGE",
|
|
121
|
+
message: `Sitemap entry exceeds the configured byte limit: ${entry.url}.`,
|
|
122
|
+
url: String(entry.url)
|
|
123
|
+
});
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const entryBytes = singleBytes - emptyDocumentBytes;
|
|
127
|
+
const exceedsCount = current.length + 1 > maxEntries;
|
|
128
|
+
const exceedsBytes = currentBytes + entryBytes > maxBytes;
|
|
129
|
+
if (current.length > 0 && (exceedsCount || exceedsBytes)) flush();
|
|
130
|
+
current.push(entry);
|
|
131
|
+
currentBytes += entryBytes;
|
|
132
|
+
}
|
|
133
|
+
flush();
|
|
134
|
+
if (files.length > DEFAULT_MAX_ENTRIES) throw new Error("Sitemap index exceeds the maximum of 50,000 sitemap references.");
|
|
135
|
+
const indexXml = files.length > 1 ? createSitemapIndexXml(files) : void 0;
|
|
136
|
+
return {
|
|
137
|
+
files,
|
|
138
|
+
indexXml,
|
|
139
|
+
indexUrl: indexXml ? normalizeSeoUrl(`/${options.filename ?? "sitemap"}.xml`, { baseUrl }) : void 0,
|
|
140
|
+
totalEntries: files.reduce((total, file) => total + file.entries.length, 0),
|
|
141
|
+
issues
|
|
142
|
+
};
|
|
143
|
+
};
|
|
144
|
+
export { generateSitemap, serializeSitemap };
|
|
145
|
+
|
|
146
|
+
//# sourceMappingURL=generate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate.js","names":[],"sources":["../../src/sitemap/generate.ts"],"sourcesContent":["import type {\n SitemapEntry,\n SitemapOptions,\n SitemapGenerationOptions,\n SitemapResult,\n SitemapCollection,\n SitemapFile,\n} from \"./types\";\n\nimport { normalizeSeoUrl, normalizeSeoOrigin } from \"../urls\";\n\nimport {\n createSitemapXml,\n escapeXml,\n sitemapByteLength,\n validateSitemapEntries,\n} from \"./xml\";\n\nconst DEFAULT_MAX_ENTRIES = 50_000;\nconst DEFAULT_MAX_BYTES = 50 * 1024 * 1024;\n\nconst normalizeEntries = (\n entries: readonly SitemapEntry[],\n baseUrl: string,\n allowExternalUrls: boolean,\n): SitemapEntry[] => {\n const unique = new Map<string, SitemapEntry>();\n\n for (const entry of entries) {\n if (\n entry.indexable === false ||\n (entry.statusCode !== undefined && entry.statusCode !== 200) ||\n entry.redirectTo\n ) {\n continue;\n }\n\n try {\n const url = normalizeSeoUrl(entry.url, {\n baseUrl,\n allowExternal: allowExternalUrls,\n });\n\n if (unique.has(url)) {\n continue;\n }\n\n const alternates = (entry.alternates ?? []).flatMap((alternate) => {\n try {\n return [\n {\n ...alternate,\n url: normalizeSeoUrl(alternate.url, {\n baseUrl,\n allowExternal: allowExternalUrls,\n }),\n },\n ];\n } catch {\n return [];\n }\n });\n\n const images = (entry.images ?? []).flatMap((image) => {\n try {\n return [\n {\n ...image,\n url: normalizeSeoUrl(image.url, {\n baseUrl,\n allowExternal: true,\n }),\n },\n ];\n } catch {\n return [];\n }\n });\n\n unique.set(url, {\n ...entry,\n url,\n alternates,\n images,\n });\n } catch {\n // Invalid URLs are reported by validateSitemapEntries.\n }\n }\n\n return [...unique.values()].sort((a, b) =>\n String(a.url).localeCompare(String(b.url)),\n );\n};\n\nexport const serializeSitemap = (options: SitemapOptions): SitemapResult => {\n const baseUrl = normalizeSeoOrigin(options.baseUrl);\n\n const issues = validateSitemapEntries(options.entries, {\n baseUrl,\n allowExternalUrls: options.allowExternalUrls,\n });\n\n const entries = normalizeEntries(\n options.entries,\n baseUrl,\n options.allowExternalUrls ?? false,\n );\n\n return {\n xml: createSitemapXml(entries, {\n baseUrl,\n includeLastModified: options.includeLastModified,\n allowExternalUrls: options.allowExternalUrls,\n }),\n entries,\n issues,\n };\n};\n\nconst createSitemapIndexXml = (files: readonly SitemapFile[]): string => {\n const content = files\n .map((file) => `<sitemap><loc>${escapeXml(file.url)}</loc></sitemap>`)\n .join(\"\");\n\n return [\n '<?xml version=\"1.0\" encoding=\"UTF-8\"?>',\n '<sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">',\n content,\n \"</sitemapindex>\",\n ].join(\"\");\n};\n\nexport const generateSitemap = (\n options: SitemapGenerationOptions,\n): SitemapCollection => {\n const baseUrl = normalizeSeoOrigin(options.baseUrl);\n\n const maxEntries = options.maxEntries ?? DEFAULT_MAX_ENTRIES;\n const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;\n\n if (\n !Number.isInteger(maxEntries) ||\n maxEntries < 1 ||\n maxEntries > DEFAULT_MAX_ENTRIES\n ) {\n throw new Error(\"Sitemap maxEntries must be between 1 and 50,000.\");\n }\n\n if (\n !Number.isInteger(maxBytes) ||\n maxBytes < 1024 ||\n maxBytes > DEFAULT_MAX_BYTES\n ) {\n throw new Error(\"Sitemap maxBytes must be between 1 KB and 50 MB.\");\n }\n\n const issues = validateSitemapEntries(options.entries, {\n baseUrl,\n allowExternalUrls: options.allowExternalUrls,\n validateAlternates: options.validateAlternates,\n });\n\n const entries = normalizeEntries(\n options.entries,\n baseUrl,\n options.allowExternalUrls ?? false,\n );\n\n const files: SitemapFile[] = [];\n\n let current: SitemapEntry[] = [];\n\n const createXml = (items: readonly SitemapEntry[]) =>\n createSitemapXml(items, {\n baseUrl,\n includeLastModified: options.includeLastModified,\n allowExternalUrls: options.allowExternalUrls,\n });\n\n // The empty document represents the fixed XML header and footer.\n const emptyDocumentBytes = sitemapByteLength(createXml([]));\n\n let currentBytes = emptyDocumentBytes;\n\n const flush = () => {\n if (current.length === 0) {\n return;\n }\n\n const number = files.length + 1;\n\n const filename = `${options.filename ?? \"sitemap\"}-${number}.xml`;\n\n const url = normalizeSeoUrl(`/${filename}`, {\n baseUrl,\n allowExternal: false,\n });\n\n files.push({\n filename,\n url,\n xml: createXml(current),\n entries: current,\n });\n\n current = [];\n currentBytes = emptyDocumentBytes;\n };\n\n for (const entry of entries) {\n const singleXml = createXml([entry]);\n const singleBytes = sitemapByteLength(singleXml);\n\n if (singleBytes > maxBytes) {\n issues.push({\n code: \"ENTRY_TOO_LARGE\",\n message: `Sitemap entry exceeds the configured byte limit: ${entry.url}.`,\n url: String(entry.url),\n });\n\n continue;\n }\n\n // This includes the entry fragment and its namespace overhead.\n // Counting namespace overhead per entry is intentionally conservative.\n const entryBytes = singleBytes - emptyDocumentBytes;\n\n const exceedsCount = current.length + 1 > maxEntries;\n const exceedsBytes = currentBytes + entryBytes > maxBytes;\n\n if (current.length > 0 && (exceedsCount || exceedsBytes)) {\n flush();\n }\n\n current.push(entry);\n currentBytes += entryBytes;\n }\n\n flush();\n\n if (files.length > DEFAULT_MAX_ENTRIES) {\n throw new Error(\n \"Sitemap index exceeds the maximum of 50,000 sitemap references.\",\n );\n }\n\n const indexXml = files.length > 1 ? createSitemapIndexXml(files) : undefined;\n\n const indexUrl = indexXml\n ? normalizeSeoUrl(`/${options.filename ?? \"sitemap\"}.xml`, { baseUrl })\n : undefined;\n\n return {\n files,\n indexXml,\n indexUrl,\n totalEntries: files.reduce((total, file) => total + file.entries.length, 0),\n issues,\n };\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_xml = require("./xml.cjs");
|
|
3
|
+
const require_generate = require("./generate.cjs");
|
|
4
|
+
exports.createSitemapXml = require_xml.createSitemapXml;
|
|
5
|
+
exports.escapeXml = require_xml.escapeXml;
|
|
6
|
+
exports.formatSitemapDate = require_xml.formatSitemapDate;
|
|
7
|
+
exports.generateSitemap = require_generate.generateSitemap;
|
|
8
|
+
exports.serializeSitemap = require_generate.serializeSitemap;
|
|
9
|
+
exports.serializeSitemapEntry = require_xml.serializeSitemapEntry;
|
|
10
|
+
exports.sitemapByteLength = require_xml.sitemapByteLength;
|
|
11
|
+
exports.validateSitemapEntries = require_xml.validateSitemapEntries;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { SitemapAlternate, SitemapCollection, SitemapEntry, SitemapFile, SitemapGenerationOptions, SitemapImage, SitemapOptions, SitemapResult, SitemapValidationIssue, SitemapVideo } from "./types.cjs";
|
|
2
|
+
import { createSitemapXml, escapeXml, formatSitemapDate, serializeSitemapEntry, sitemapByteLength, validateSitemapEntries } from "./xml.cjs";
|
|
3
|
+
import { generateSitemap, serializeSitemap } from "./generate.cjs";
|
|
4
|
+
export { type SitemapAlternate, type SitemapCollection, type SitemapEntry, type SitemapFile, type SitemapGenerationOptions, type SitemapImage, type SitemapOptions, type SitemapResult, type SitemapValidationIssue, type SitemapVideo, createSitemapXml, escapeXml, formatSitemapDate, generateSitemap, serializeSitemap, serializeSitemapEntry, sitemapByteLength, validateSitemapEntries };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { SitemapAlternate, SitemapCollection, SitemapEntry, SitemapFile, SitemapGenerationOptions, SitemapImage, SitemapOptions, SitemapResult, SitemapValidationIssue, SitemapVideo } from "./types.js";
|
|
2
|
+
import { createSitemapXml, escapeXml, formatSitemapDate, serializeSitemapEntry, sitemapByteLength, validateSitemapEntries } from "./xml.js";
|
|
3
|
+
import { generateSitemap, serializeSitemap } from "./generate.js";
|
|
4
|
+
export { type SitemapAlternate, type SitemapCollection, type SitemapEntry, type SitemapFile, type SitemapGenerationOptions, type SitemapImage, type SitemapOptions, type SitemapResult, type SitemapValidationIssue, type SitemapVideo, createSitemapXml, escapeXml, formatSitemapDate, generateSitemap, serializeSitemap, serializeSitemapEntry, sitemapByteLength, validateSitemapEntries };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { createSitemapXml, escapeXml, formatSitemapDate, serializeSitemapEntry, sitemapByteLength, validateSitemapEntries } from "./xml.js";
|
|
2
|
+
import { generateSitemap, serializeSitemap } from "./generate.js";
|
|
3
|
+
export { createSitemapXml, escapeXml, formatSitemapDate, generateSitemap, serializeSitemap, serializeSitemapEntry, sitemapByteLength, validateSitemapEntries };
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { SeoUrl } from "../core/types.cjs";
|
|
2
|
+
export interface SitemapImage {
|
|
3
|
+
url: SeoUrl | string;
|
|
4
|
+
title?: string;
|
|
5
|
+
caption?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface SitemapVideo {
|
|
8
|
+
thumbnailUrl: string;
|
|
9
|
+
title: string;
|
|
10
|
+
description: string;
|
|
11
|
+
contentUrl?: string;
|
|
12
|
+
playerUrl?: string;
|
|
13
|
+
duration?: number;
|
|
14
|
+
expirationDate?: Date | string;
|
|
15
|
+
rating?: number;
|
|
16
|
+
viewCount?: number;
|
|
17
|
+
publicationDate?: Date | string;
|
|
18
|
+
familyFriendly?: boolean;
|
|
19
|
+
restriction?: {
|
|
20
|
+
relationship: "allow" | "deny";
|
|
21
|
+
countries: string[];
|
|
22
|
+
};
|
|
23
|
+
requiresSubscription?: boolean;
|
|
24
|
+
live?: boolean;
|
|
25
|
+
tags?: string[];
|
|
26
|
+
category?: string;
|
|
27
|
+
}
|
|
28
|
+
export interface SitemapAlternate {
|
|
29
|
+
locale: string;
|
|
30
|
+
url: SeoUrl | string;
|
|
31
|
+
}
|
|
32
|
+
export interface SitemapEntry {
|
|
33
|
+
url: SeoUrl | string;
|
|
34
|
+
lastModified?: Date | string;
|
|
35
|
+
changeFrequency?: "always" | "hourly" | "daily" | "weekly" | "monthly" | "yearly" | "never";
|
|
36
|
+
priority?: number;
|
|
37
|
+
images?: SitemapImage[];
|
|
38
|
+
videos?: SitemapVideo[];
|
|
39
|
+
alternates?: SitemapAlternate[];
|
|
40
|
+
/** Excludes the entry when false. */
|
|
41
|
+
indexable?: boolean;
|
|
42
|
+
/** Only HTTP 200 entries are included. */
|
|
43
|
+
statusCode?: number;
|
|
44
|
+
/** Excludes redirect entries. */
|
|
45
|
+
redirectTo?: string;
|
|
46
|
+
}
|
|
47
|
+
export interface SitemapOptions {
|
|
48
|
+
baseUrl: SeoUrl | string;
|
|
49
|
+
entries: SitemapEntry[];
|
|
50
|
+
includeLastModified?: boolean;
|
|
51
|
+
allowExternalUrls?: boolean;
|
|
52
|
+
}
|
|
53
|
+
export interface SitemapGenerationOptions extends SitemapOptions {
|
|
54
|
+
maxEntries?: number;
|
|
55
|
+
maxBytes?: number;
|
|
56
|
+
filename?: string;
|
|
57
|
+
validateAlternates?: boolean;
|
|
58
|
+
}
|
|
59
|
+
export interface SitemapValidationIssue {
|
|
60
|
+
code: string;
|
|
61
|
+
message: string;
|
|
62
|
+
url?: string;
|
|
63
|
+
path?: string;
|
|
64
|
+
}
|
|
65
|
+
export interface SitemapResult {
|
|
66
|
+
xml: string;
|
|
67
|
+
entries: SitemapEntry[];
|
|
68
|
+
issues: SitemapValidationIssue[];
|
|
69
|
+
}
|
|
70
|
+
export interface SitemapFile {
|
|
71
|
+
filename: string;
|
|
72
|
+
url: SeoUrl;
|
|
73
|
+
xml: string;
|
|
74
|
+
entries: SitemapEntry[];
|
|
75
|
+
}
|
|
76
|
+
export interface SitemapCollection {
|
|
77
|
+
files: SitemapFile[];
|
|
78
|
+
indexXml?: string;
|
|
79
|
+
indexUrl?: SeoUrl;
|
|
80
|
+
totalEntries: number;
|
|
81
|
+
issues: SitemapValidationIssue[];
|
|
82
|
+
}
|