@answerfox/sitemap 0.1.1
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/LICENSE +21 -0
- package/README.md +62 -0
- package/dist/build-sitemap.d.ts +42 -0
- package/dist/build-sitemap.d.ts.map +1 -0
- package/dist/build-sitemap.js +121 -0
- package/dist/build-sitemap.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/sitemap-index.d.ts +16 -0
- package/dist/sitemap-index.d.ts.map +1 -0
- package/dist/sitemap-index.js +29 -0
- package/dist/sitemap-index.js.map +1 -0
- package/package.json +51 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Anuj Ojha
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# @answerfox/sitemap
|
|
2
|
+
|
|
3
|
+
Sitemap builder for the [Answerfox](https://github.com/Anuj7411/answerfox) SEO toolkit. Takes a list of paths, applies smart defaults inferred from path patterns, and returns a Next.js-compatible `MetadataRoute.Sitemap`.
|
|
4
|
+
|
|
5
|
+
> **v0.1.0.** `buildSitemap()` and `sitemapIndex()` ship today with smart path-based defaults for priority and change frequency.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add @answerfox/sitemap
|
|
11
|
+
# requires next >= 14
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Usage
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
// app/sitemap.ts
|
|
18
|
+
import { buildSitemap } from '@answerfox/sitemap';
|
|
19
|
+
|
|
20
|
+
export default function sitemap() {
|
|
21
|
+
return buildSitemap(
|
|
22
|
+
[
|
|
23
|
+
{ path: '/' },
|
|
24
|
+
{ path: '/about' },
|
|
25
|
+
{ path: '/privacy' },
|
|
26
|
+
{ path: '/blog/launch' },
|
|
27
|
+
{ path: '/blog/post-1', lastModified: '2026-05-14' },
|
|
28
|
+
],
|
|
29
|
+
{ baseUrl: 'https://acme.com' },
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Smart defaults
|
|
35
|
+
|
|
36
|
+
Priority and `changeFrequency` are inferred from path patterns when you don't set them explicitly:
|
|
37
|
+
|
|
38
|
+
| Path pattern | priority | changeFrequency |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
| `/` (home) | 1.0 | daily |
|
|
41
|
+
| `/blog/*`, `/news/*`, `/posts/*` | 0.7 | weekly |
|
|
42
|
+
| `/docs/*` | 0.6 | weekly |
|
|
43
|
+
| `/products/*`, `/pricing` | 0.8 | weekly |
|
|
44
|
+
| `/about`, `/privacy`, `/terms`, `/contact`, `/faq` | 0.3 | yearly |
|
|
45
|
+
| Anything else | 0.5 | monthly |
|
|
46
|
+
|
|
47
|
+
Explicit `priority` or `changeFrequency` on a route always wins over inferred defaults.
|
|
48
|
+
|
|
49
|
+
## Large sites (>50k URLs)
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
import { sitemapIndex } from '@answerfox/sitemap';
|
|
53
|
+
|
|
54
|
+
const index = sitemapIndex([
|
|
55
|
+
{ url: 'https://acme.com/sitemap-1.xml' },
|
|
56
|
+
{ url: 'https://acme.com/sitemap-2.xml', lastModified: new Date() },
|
|
57
|
+
]);
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## License
|
|
61
|
+
|
|
62
|
+
[MIT](../../LICENSE) © 2026 Anuj Ojha
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { MetadataRoute } from 'next';
|
|
2
|
+
export type ChangeFrequency = 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never';
|
|
3
|
+
export interface SitemapRouteInput {
|
|
4
|
+
/**
|
|
5
|
+
* Path-only segment. Must start with `/`. Combined with `baseUrl`
|
|
6
|
+
* to produce the final `url` field. Don't pass a full URL.
|
|
7
|
+
*/
|
|
8
|
+
readonly path: string;
|
|
9
|
+
readonly lastModified?: Date | string | undefined;
|
|
10
|
+
readonly changeFrequency?: ChangeFrequency | undefined;
|
|
11
|
+
/** Float between 0.0 and 1.0 inclusive. */
|
|
12
|
+
readonly priority?: number | undefined;
|
|
13
|
+
/**
|
|
14
|
+
* Locale → absolute-URL map for hreflang alternates. Each URL is
|
|
15
|
+
* validated as an absolute http(s) URL.
|
|
16
|
+
*
|
|
17
|
+
* @example { 'en-US': 'https://acme.com/about', 'es-ES': 'https://acme.com/es/about' }
|
|
18
|
+
*/
|
|
19
|
+
readonly alternates?: Record<string, string> | undefined;
|
|
20
|
+
}
|
|
21
|
+
export interface BuildSitemapOptions {
|
|
22
|
+
/** Absolute http(s) base URL. Trailing slash is stripped. */
|
|
23
|
+
readonly baseUrl: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Internal helper used by both `buildSitemap()` and `sitemapIndex()`.
|
|
27
|
+
* Returns a validated value to assign, or pushes an issue and returns
|
|
28
|
+
* `undefined`.
|
|
29
|
+
*/
|
|
30
|
+
export declare function validateAndCoerceLastModified(input: Date | string, prefix: string, issues: string[]): Date | string | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* Build a Next.js-compatible sitemap from a list of paths + a base URL.
|
|
33
|
+
* Smart defaults for `priority` and `changeFrequency` are inferred from
|
|
34
|
+
* each path's pattern; explicit values on a route always win.
|
|
35
|
+
*
|
|
36
|
+
* @throws InvalidUrlError if `baseUrl` or any `alternates` URL is
|
|
37
|
+
* not a valid absolute http(s) URL.
|
|
38
|
+
* @throws SchemaValidationError batching every issue across paths,
|
|
39
|
+
* priorities, duplicates, and `lastModified` values.
|
|
40
|
+
*/
|
|
41
|
+
export declare function buildSitemap(routes: readonly SitemapRouteInput[], options: BuildSitemapOptions): MetadataRoute.Sitemap;
|
|
42
|
+
//# sourceMappingURL=build-sitemap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-sitemap.d.ts","sourceRoot":"","sources":["../src/build-sitemap.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,MAAM,CAAC;AAE1C,MAAM,MAAM,eAAe,GACvB,QAAQ,GACR,QAAQ,GACR,OAAO,GACP,QAAQ,GACR,SAAS,GACT,QAAQ,GACR,OAAO,CAAC;AAEZ,MAAM,WAAW,iBAAiB;IAChC;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,YAAY,CAAC,EAAE,IAAI,GAAG,MAAM,GAAG,SAAS,CAAC;IAClD,QAAQ,CAAC,eAAe,CAAC,EAAE,eAAe,GAAG,SAAS,CAAC;IACvD,2CAA2C;IAC3C,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACvC;;;;;OAKG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC;CAC1D;AAED,MAAM,WAAW,mBAAmB;IAClC,6DAA6D;IAC7D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAgDD;;;;GAIG;AACH,wBAAgB,6BAA6B,CAC3C,KAAK,EAAE,IAAI,GAAG,MAAM,EACpB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EAAE,GACf,IAAI,GAAG,MAAM,GAAG,SAAS,CAa3B;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,SAAS,iBAAiB,EAAE,EACpC,OAAO,EAAE,mBAAmB,GAC3B,aAAa,CAAC,OAAO,CAgDvB"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { SchemaValidationError, parseAbsoluteUrl } from '@answerfox/core';
|
|
2
|
+
/**
|
|
3
|
+
* Priority + changeFrequency defaults inferred from the path. Used
|
|
4
|
+
* whenever the corresponding field isn't supplied explicitly.
|
|
5
|
+
*/
|
|
6
|
+
function inferDefaults(path) {
|
|
7
|
+
if (path === '/')
|
|
8
|
+
return { priority: 1.0, changeFrequency: 'daily' };
|
|
9
|
+
if (/^\/(blog|news|posts)(\/|$)/.test(path)) {
|
|
10
|
+
return { priority: 0.7, changeFrequency: 'weekly' };
|
|
11
|
+
}
|
|
12
|
+
if (/^\/docs(\/|$)/.test(path)) {
|
|
13
|
+
return { priority: 0.6, changeFrequency: 'weekly' };
|
|
14
|
+
}
|
|
15
|
+
if (/^\/(products|pricing)(\/|$)/.test(path)) {
|
|
16
|
+
return { priority: 0.8, changeFrequency: 'weekly' };
|
|
17
|
+
}
|
|
18
|
+
if (/^\/(about|privacy|terms|contact|faq)(\/|$)/.test(path)) {
|
|
19
|
+
return { priority: 0.3, changeFrequency: 'yearly' };
|
|
20
|
+
}
|
|
21
|
+
return { priority: 0.5, changeFrequency: 'monthly' };
|
|
22
|
+
}
|
|
23
|
+
function composeUrl(baseUrl, path) {
|
|
24
|
+
const trimmed = baseUrl.replace(/\/$/, '');
|
|
25
|
+
return `${trimmed}${path}`;
|
|
26
|
+
}
|
|
27
|
+
/** Validate a string lastModified as ISO 8601 (date or date-time). */
|
|
28
|
+
const ISO_DATE_OR_DATETIME = /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2}(\.\d{1,9})?)?(Z|[+-]\d{2}:?\d{2})?)?$/;
|
|
29
|
+
function isValidIso8601Date(s) {
|
|
30
|
+
if (!ISO_DATE_OR_DATETIME.test(s))
|
|
31
|
+
return false;
|
|
32
|
+
if (Number.isNaN(Date.parse(s)))
|
|
33
|
+
return false;
|
|
34
|
+
// Catch silent rollovers like "2026-02-30" → March 2.
|
|
35
|
+
const parts = s.slice(0, 10).split('-');
|
|
36
|
+
const yearStr = parts[0];
|
|
37
|
+
const monthStr = parts[1];
|
|
38
|
+
const dayStr = parts[2];
|
|
39
|
+
if (yearStr === undefined || monthStr === undefined || dayStr === undefined)
|
|
40
|
+
return false;
|
|
41
|
+
const year = Number(yearStr);
|
|
42
|
+
const month = Number(monthStr);
|
|
43
|
+
const day = Number(dayStr);
|
|
44
|
+
const d = new Date(Date.UTC(year, month - 1, day));
|
|
45
|
+
return d.getUTCFullYear() === year && d.getUTCMonth() === month - 1 && d.getUTCDate() === day;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Internal helper used by both `buildSitemap()` and `sitemapIndex()`.
|
|
49
|
+
* Returns a validated value to assign, or pushes an issue and returns
|
|
50
|
+
* `undefined`.
|
|
51
|
+
*/
|
|
52
|
+
export function validateAndCoerceLastModified(input, prefix, issues) {
|
|
53
|
+
if (input instanceof Date) {
|
|
54
|
+
if (Number.isNaN(input.getTime())) {
|
|
55
|
+
issues.push(`${prefix}.lastModified is an Invalid Date`);
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
return input;
|
|
59
|
+
}
|
|
60
|
+
if (!isValidIso8601Date(input)) {
|
|
61
|
+
issues.push(`${prefix}.lastModified is not a valid ISO 8601 date (got "${input}")`);
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
return input;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Build a Next.js-compatible sitemap from a list of paths + a base URL.
|
|
68
|
+
* Smart defaults for `priority` and `changeFrequency` are inferred from
|
|
69
|
+
* each path's pattern; explicit values on a route always win.
|
|
70
|
+
*
|
|
71
|
+
* @throws InvalidUrlError if `baseUrl` or any `alternates` URL is
|
|
72
|
+
* not a valid absolute http(s) URL.
|
|
73
|
+
* @throws SchemaValidationError batching every issue across paths,
|
|
74
|
+
* priorities, duplicates, and `lastModified` values.
|
|
75
|
+
*/
|
|
76
|
+
export function buildSitemap(routes, options) {
|
|
77
|
+
const baseUrl = parseAbsoluteUrl(options.baseUrl);
|
|
78
|
+
const issues = [];
|
|
79
|
+
const seen = new Set();
|
|
80
|
+
routes.forEach((r, i) => {
|
|
81
|
+
if (!r.path.startsWith('/')) {
|
|
82
|
+
issues.push(`routes[${i}].path must start with "/" (got "${r.path}")`);
|
|
83
|
+
}
|
|
84
|
+
if (r.path.includes('://')) {
|
|
85
|
+
issues.push(`routes[${i}].path must be a path, not a full URL (got "${r.path}"). Use the \`baseUrl\` option for the origin.`);
|
|
86
|
+
}
|
|
87
|
+
if (r.priority !== undefined && (r.priority < 0 || r.priority > 1)) {
|
|
88
|
+
issues.push(`routes[${i}].priority must be between 0.0 and 1.0 (got ${r.priority})`);
|
|
89
|
+
}
|
|
90
|
+
if (seen.has(r.path)) {
|
|
91
|
+
issues.push(`routes[${i}].path is a duplicate of an earlier entry: "${r.path}"`);
|
|
92
|
+
}
|
|
93
|
+
seen.add(r.path);
|
|
94
|
+
if (r.lastModified !== undefined) {
|
|
95
|
+
validateAndCoerceLastModified(r.lastModified, `routes[${i}]`, issues);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
if (issues.length > 0) {
|
|
99
|
+
throw new SchemaValidationError(issues);
|
|
100
|
+
}
|
|
101
|
+
return routes.map((r) => {
|
|
102
|
+
const defaults = inferDefaults(r.path);
|
|
103
|
+
const entry = {
|
|
104
|
+
url: composeUrl(baseUrl, r.path),
|
|
105
|
+
changeFrequency: r.changeFrequency ?? defaults.changeFrequency,
|
|
106
|
+
priority: r.priority ?? defaults.priority,
|
|
107
|
+
};
|
|
108
|
+
if (r.lastModified !== undefined) {
|
|
109
|
+
entry.lastModified = r.lastModified;
|
|
110
|
+
}
|
|
111
|
+
if (r.alternates !== undefined) {
|
|
112
|
+
const languages = {};
|
|
113
|
+
for (const [locale, url] of Object.entries(r.alternates)) {
|
|
114
|
+
languages[locale] = parseAbsoluteUrl(url);
|
|
115
|
+
}
|
|
116
|
+
entry.alternates = { languages };
|
|
117
|
+
}
|
|
118
|
+
return entry;
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=build-sitemap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-sitemap.js","sourceRoot":"","sources":["../src/build-sitemap.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @answerfox/sitemap — sitemap builder with smart path-based defaults
|
|
3
|
+
* for the Answerfox SEO toolkit. Wraps Next.js's `MetadataRoute.Sitemap`
|
|
4
|
+
* type with priority/changeFrequency inference, URL composition,
|
|
5
|
+
* duplicate-path detection, and a sitemap-index helper for large sites.
|
|
6
|
+
*/
|
|
7
|
+
export declare const VERSION = "0.0.0";
|
|
8
|
+
export { buildSitemap, type BuildSitemapOptions, type ChangeFrequency, type SitemapRouteInput, } from './build-sitemap.js';
|
|
9
|
+
export { sitemapIndex, type SitemapIndexEntry } from './sitemap-index.js';
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,eAAO,MAAM,OAAO,UAAU,CAAC;AAE/B,OAAO,EACL,YAAY,EACZ,KAAK,mBAAmB,EACxB,KAAK,eAAe,EACpB,KAAK,iBAAiB,GACvB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,oBAAoB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @answerfox/sitemap — sitemap builder with smart path-based defaults
|
|
3
|
+
* for the Answerfox SEO toolkit. Wraps Next.js's `MetadataRoute.Sitemap`
|
|
4
|
+
* type with priority/changeFrequency inference, URL composition,
|
|
5
|
+
* duplicate-path detection, and a sitemap-index helper for large sites.
|
|
6
|
+
*/
|
|
7
|
+
export const VERSION = '0.0.0';
|
|
8
|
+
export { buildSitemap, } from './build-sitemap.js';
|
|
9
|
+
export { sitemapIndex } from './sitemap-index.js';
|
|
10
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC;AAE/B,OAAO,EACL,YAAY,GAIb,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,YAAY,EAA0B,MAAM,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface SitemapIndexEntry {
|
|
2
|
+
/** Absolute http(s) URL to a child sitemap file. */
|
|
3
|
+
readonly url: string;
|
|
4
|
+
readonly lastModified?: Date | string | undefined;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Build a sitemap-index structure for sites that exceed Google's 50,000-URL
|
|
8
|
+
* per-file limit. Each entry points at a child sitemap; the caller serializes
|
|
9
|
+
* the returned array to whatever format their host expects (Next.js's XML
|
|
10
|
+
* generator, custom route handler, etc.).
|
|
11
|
+
*
|
|
12
|
+
* @throws InvalidUrlError if any `url` is not a valid absolute http(s) URL.
|
|
13
|
+
* @throws SchemaValidationError batching every bad `lastModified` value.
|
|
14
|
+
*/
|
|
15
|
+
export declare function sitemapIndex(entries: readonly SitemapIndexEntry[]): SitemapIndexEntry[];
|
|
16
|
+
//# sourceMappingURL=sitemap-index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sitemap-index.d.ts","sourceRoot":"","sources":["../src/sitemap-index.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,iBAAiB;IAChC,oDAAoD;IACpD,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,YAAY,CAAC,EAAE,IAAI,GAAG,MAAM,GAAG,SAAS,CAAC;CACnD;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,SAAS,iBAAiB,EAAE,GAAG,iBAAiB,EAAE,CAiBvF"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { SchemaValidationError, parseAbsoluteUrl } from '@answerfox/core';
|
|
2
|
+
import { validateAndCoerceLastModified } from './build-sitemap.js';
|
|
3
|
+
/**
|
|
4
|
+
* Build a sitemap-index structure for sites that exceed Google's 50,000-URL
|
|
5
|
+
* per-file limit. Each entry points at a child sitemap; the caller serializes
|
|
6
|
+
* the returned array to whatever format their host expects (Next.js's XML
|
|
7
|
+
* generator, custom route handler, etc.).
|
|
8
|
+
*
|
|
9
|
+
* @throws InvalidUrlError if any `url` is not a valid absolute http(s) URL.
|
|
10
|
+
* @throws SchemaValidationError batching every bad `lastModified` value.
|
|
11
|
+
*/
|
|
12
|
+
export function sitemapIndex(entries) {
|
|
13
|
+
// First pass: batch lastModified issues so callers see every problem at once.
|
|
14
|
+
const issues = [];
|
|
15
|
+
entries.forEach((e, i) => {
|
|
16
|
+
if (e.lastModified !== undefined) {
|
|
17
|
+
validateAndCoerceLastModified(e.lastModified, `entries[${i}]`, issues);
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
if (issues.length > 0) {
|
|
21
|
+
throw new SchemaValidationError(issues);
|
|
22
|
+
}
|
|
23
|
+
// Second pass: URL validation throws InvalidUrlError on the first bad URL.
|
|
24
|
+
return entries.map((e) => {
|
|
25
|
+
const url = parseAbsoluteUrl(e.url);
|
|
26
|
+
return e.lastModified !== undefined ? { url, lastModified: e.lastModified } : { url };
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=sitemap-index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sitemap-index.js","sourceRoot":"","sources":["../src/sitemap-index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAC1E,OAAO,EAAE,6BAA6B,EAAE,MAAM,oBAAoB,CAAC;AAQnE;;;;;;;;GAQG;AACH,MAAM,UAAU,YAAY,CAAC,OAAqC;IAChE,8EAA8E;IAC9E,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACvB,IAAI,CAAC,CAAC,YAAY,KAAK,SAAS,EAAE,CAAC;YACjC,6BAA6B,CAAC,CAAC,CAAC,YAAY,EAAE,WAAW,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;QACzE,CAAC;IACH,CAAC,CAAC,CAAC;IACH,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,qBAAqB,CAAC,MAAM,CAAC,CAAC;IAC1C,CAAC;IAED,2EAA2E;IAC3E,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACvB,MAAM,GAAG,GAAG,gBAAgB,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QACpC,OAAO,CAAC,CAAC,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,YAAY,EAAE,CAAC,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC;IACxF,CAAC,CAAC,CAAC;AACL,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@answerfox/sitemap",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Sitemap builder with smart path-based defaults for the Answerfox SEO toolkit.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Anuj Ojha",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/Anuj7411/answerfox.git",
|
|
10
|
+
"directory": "packages/sitemap"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/Anuj7411/answerfox/tree/main/packages/sitemap#readme",
|
|
13
|
+
"bugs": "https://github.com/Anuj7411/answerfox/issues",
|
|
14
|
+
"type": "module",
|
|
15
|
+
"main": "./dist/index.js",
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"import": "./dist/index.js"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist",
|
|
25
|
+
"README.md"
|
|
26
|
+
],
|
|
27
|
+
"sideEffects": false,
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@answerfox/core": "0.1.1"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"next": ">=14.0.0"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"next": "^15.0.0",
|
|
39
|
+
"react": "^19.0.0",
|
|
40
|
+
"rimraf": "^6.0.1",
|
|
41
|
+
"typescript": "^5.7.3",
|
|
42
|
+
"vitest": "^3.0.5"
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"build": "tsc -p tsconfig.build.json",
|
|
46
|
+
"test": "vitest run",
|
|
47
|
+
"test:watch": "vitest",
|
|
48
|
+
"typecheck": "tsc --noEmit",
|
|
49
|
+
"clean": "rimraf dist .tsbuildinfo"
|
|
50
|
+
}
|
|
51
|
+
}
|