@onruntime/next-sitemap 0.6.2 → 0.8.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 +12 -0
- package/dist/app/index.cjs +200 -213
- package/dist/app/index.d.cts +11 -11
- package/dist/app/index.d.ts +11 -11
- package/dist/app/index.js +201 -210
- package/dist/index.cjs +214 -4
- package/dist/index.d.cts +65 -2
- package/dist/index.d.ts +65 -2
- package/dist/index.js +186 -5
- package/dist/loader.js +38 -0
- package/dist/pages/index.cjs +206 -205
- package/dist/pages/index.d.cts +24 -30
- package/dist/pages/index.d.ts +24 -30
- package/dist/pages/index.js +207 -202
- package/dist/worker.cjs +267 -0
- package/package.json +2 -2
package/dist/loader.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// src/shared.ts
|
|
2
|
+
import { existsSync } from "fs";
|
|
3
|
+
import { dirname, join, delimiter } from "path";
|
|
4
|
+
import * as childProcess from "child_process";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
var JS_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
7
|
+
".js",
|
|
8
|
+
".cjs",
|
|
9
|
+
".mjs",
|
|
10
|
+
".jsx",
|
|
11
|
+
".ts",
|
|
12
|
+
".cts",
|
|
13
|
+
".mts",
|
|
14
|
+
".tsx",
|
|
15
|
+
".json",
|
|
16
|
+
".node"
|
|
17
|
+
]);
|
|
18
|
+
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
19
|
+
var isDev = process.env.NODE_ENV === "development";
|
|
20
|
+
|
|
21
|
+
// src/loader.ts
|
|
22
|
+
function resolve(specifier, context, nextResolve) {
|
|
23
|
+
const ext = specifier.match(/\.[a-z0-9]+$/i)?.[0]?.toLowerCase();
|
|
24
|
+
if (ext && !JS_EXTENSIONS.has(ext)) {
|
|
25
|
+
return { shortCircuit: true, url: `mock:${specifier}` };
|
|
26
|
+
}
|
|
27
|
+
return nextResolve(specifier, context);
|
|
28
|
+
}
|
|
29
|
+
function load(url, context, nextLoad) {
|
|
30
|
+
if (url.startsWith("mock:")) {
|
|
31
|
+
return { shortCircuit: true, format: "module", source: "export default {};" };
|
|
32
|
+
}
|
|
33
|
+
return nextLoad(url, context);
|
|
34
|
+
}
|
|
35
|
+
export {
|
|
36
|
+
load,
|
|
37
|
+
resolve
|
|
38
|
+
};
|
package/dist/pages/index.cjs
CHANGED
|
@@ -2,12 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
var fs = require('fs');
|
|
4
4
|
var path = require('path');
|
|
5
|
-
var
|
|
6
|
-
var
|
|
5
|
+
var childProcess = require('child_process');
|
|
6
|
+
var url = require('url');
|
|
7
7
|
|
|
8
8
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
9
|
-
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
10
|
-
|
|
11
9
|
function _interopNamespace(e) {
|
|
12
10
|
if (e && e.__esModule) return e;
|
|
13
11
|
var n = Object.create(null);
|
|
@@ -26,11 +24,168 @@ function _interopNamespace(e) {
|
|
|
26
24
|
return Object.freeze(n);
|
|
27
25
|
}
|
|
28
26
|
|
|
29
|
-
var
|
|
30
|
-
var path__namespace = /*#__PURE__*/_interopNamespace(path);
|
|
31
|
-
var stripJsonComments__default = /*#__PURE__*/_interopDefault(stripJsonComments);
|
|
27
|
+
var childProcess__namespace = /*#__PURE__*/_interopNamespace(childProcess);
|
|
32
28
|
|
|
33
29
|
// src/pages/index.ts
|
|
30
|
+
var NO_STATIC_PARAMS = "NO_STATIC_PARAMS";
|
|
31
|
+
var spawnProcess = childProcess__namespace.spawn;
|
|
32
|
+
var __dirname$1 = path.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))));
|
|
33
|
+
var paramsCache = /* @__PURE__ */ new Map();
|
|
34
|
+
var isDev = process.env.NODE_ENV === "development";
|
|
35
|
+
async function executeWorker(directory, fileKey, debug) {
|
|
36
|
+
const absolutePath = path.join(directory, fileKey.replace("./", ""));
|
|
37
|
+
const projectRoot = process.cwd();
|
|
38
|
+
const distRoot = path.join(__dirname$1, "..");
|
|
39
|
+
const workerPath = path.join(distRoot, "worker.cjs");
|
|
40
|
+
const loaderPath = path.join(distRoot, "loader.js");
|
|
41
|
+
if (debug) {
|
|
42
|
+
console.log(`[next-sitemap] Worker path: ${workerPath}`);
|
|
43
|
+
console.log(`[next-sitemap] Worker exists: ${fs.existsSync(workerPath)}`);
|
|
44
|
+
console.log(`[next-sitemap] Loader path: ${loaderPath}`);
|
|
45
|
+
console.log(`[next-sitemap] Loader exists: ${fs.existsSync(loaderPath)}`);
|
|
46
|
+
}
|
|
47
|
+
return new Promise((resolve) => {
|
|
48
|
+
const nodePath = [
|
|
49
|
+
path.join(projectRoot, "node_modules"),
|
|
50
|
+
path.join(__dirname$1, "..", "node_modules")
|
|
51
|
+
].join(path.delimiter);
|
|
52
|
+
const importFlag = ["--import", loaderPath];
|
|
53
|
+
const args = [...importFlag, workerPath, absolutePath, projectRoot];
|
|
54
|
+
const child = spawnProcess("node", args, {
|
|
55
|
+
cwd: projectRoot,
|
|
56
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
57
|
+
env: { ...process.env, NODE_PATH: nodePath }
|
|
58
|
+
});
|
|
59
|
+
let stdout = "";
|
|
60
|
+
let stderr = "";
|
|
61
|
+
child.stdout.on("data", (data) => {
|
|
62
|
+
stdout += data.toString();
|
|
63
|
+
});
|
|
64
|
+
child.stderr.on("data", (data) => {
|
|
65
|
+
stderr += data.toString();
|
|
66
|
+
});
|
|
67
|
+
child.on("close", (code) => {
|
|
68
|
+
if (debug && stderr) {
|
|
69
|
+
console.warn(`[next-sitemap] Worker stderr: ${stderr}`);
|
|
70
|
+
}
|
|
71
|
+
if (code !== 0 && code !== null) {
|
|
72
|
+
if (debug) console.warn(`[next-sitemap] Worker exited with code ${code}`);
|
|
73
|
+
resolve(null);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
const lines = stdout.trim().split("\n");
|
|
78
|
+
const result = JSON.parse(lines[lines.length - 1]);
|
|
79
|
+
if (result.success) {
|
|
80
|
+
resolve(result.params);
|
|
81
|
+
} else {
|
|
82
|
+
if (result.error !== NO_STATIC_PARAMS && debug) {
|
|
83
|
+
console.warn(`[next-sitemap] Worker error: ${result.error}`);
|
|
84
|
+
}
|
|
85
|
+
resolve(null);
|
|
86
|
+
}
|
|
87
|
+
} catch {
|
|
88
|
+
if (debug) console.warn(`[next-sitemap] Failed to parse worker output: ${stdout}`);
|
|
89
|
+
resolve(null);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
child.on("error", (err) => {
|
|
93
|
+
if (debug) console.warn(`[next-sitemap] Failed to spawn worker: ${err.message}`);
|
|
94
|
+
resolve(null);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
async function getRouteParams(route, directory, debug) {
|
|
99
|
+
const cacheKey = route.fileKey;
|
|
100
|
+
if (paramsCache.has(cacheKey)) {
|
|
101
|
+
return paramsCache.get(cacheKey);
|
|
102
|
+
}
|
|
103
|
+
if (debug) {
|
|
104
|
+
console.log(`[next-sitemap] ${route.pathname}: executing static params via worker`);
|
|
105
|
+
}
|
|
106
|
+
const params = await executeWorker(directory, route.fileKey, debug);
|
|
107
|
+
paramsCache.set(cacheKey, params);
|
|
108
|
+
if (debug && params) {
|
|
109
|
+
console.log(`[next-sitemap] ${route.pathname}: got ${params.length} params`);
|
|
110
|
+
}
|
|
111
|
+
return params;
|
|
112
|
+
}
|
|
113
|
+
async function generateAllPaths(routes, directory, debug) {
|
|
114
|
+
const staticPaths = ["/"];
|
|
115
|
+
const dynamicRoutes = [];
|
|
116
|
+
for (const route of routes) {
|
|
117
|
+
if (route.dynamicSegments.length === 0) {
|
|
118
|
+
if (route.pathname !== "/") {
|
|
119
|
+
staticPaths.push(route.pathname);
|
|
120
|
+
}
|
|
121
|
+
} else {
|
|
122
|
+
dynamicRoutes.push(route);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const startTime = isDev ? Date.now() : 0;
|
|
126
|
+
if (isDev && dynamicRoutes.length > 0) {
|
|
127
|
+
console.log(`[next-sitemap] Generating sitemap for ${dynamicRoutes.length} dynamic routes (dev only, instant in production)...`);
|
|
128
|
+
}
|
|
129
|
+
const dynamicResults = await Promise.all(
|
|
130
|
+
dynamicRoutes.map(async (route) => {
|
|
131
|
+
const params = await getRouteParams(route, directory, debug);
|
|
132
|
+
if (!params || params.length === 0) {
|
|
133
|
+
if (debug) {
|
|
134
|
+
console.warn(`[next-sitemap] Skipping dynamic route ${route.pathname}: no static params or empty result.`);
|
|
135
|
+
}
|
|
136
|
+
return [];
|
|
137
|
+
}
|
|
138
|
+
const paths = [];
|
|
139
|
+
for (const param of params) {
|
|
140
|
+
let path = route.pathname;
|
|
141
|
+
let valid = true;
|
|
142
|
+
for (const segment of route.dynamicSegments) {
|
|
143
|
+
const value = param[segment];
|
|
144
|
+
if (value === void 0) {
|
|
145
|
+
if (debug) {
|
|
146
|
+
console.warn(`[next-sitemap] ${route.pathname}: missing param "${segment}" in`, param);
|
|
147
|
+
}
|
|
148
|
+
valid = false;
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
path = path.replace(`[${segment}]`, value);
|
|
152
|
+
}
|
|
153
|
+
if (valid) paths.push(path);
|
|
154
|
+
}
|
|
155
|
+
return paths;
|
|
156
|
+
})
|
|
157
|
+
);
|
|
158
|
+
const allPaths = new Set(staticPaths);
|
|
159
|
+
for (const paths of dynamicResults) {
|
|
160
|
+
for (const path of paths) {
|
|
161
|
+
allPaths.add(path);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (isDev && dynamicRoutes.length > 0) {
|
|
165
|
+
const elapsed = Date.now() - startTime;
|
|
166
|
+
console.log(`[next-sitemap] Done! Found ${allPaths.size} total URLs in ${elapsed}ms.`);
|
|
167
|
+
}
|
|
168
|
+
return Array.from(allPaths);
|
|
169
|
+
}
|
|
170
|
+
function pathsToEntries(paths, config) {
|
|
171
|
+
const { baseUrl, locales = [], defaultLocale, exclude, priority, changeFreq } = config;
|
|
172
|
+
return paths.filter((pathname) => !shouldExclude(pathname, exclude)).map((pathname) => {
|
|
173
|
+
const entry = {
|
|
174
|
+
url: buildUrl(baseUrl, pathname, defaultLocale, defaultLocale),
|
|
175
|
+
lastModified: /* @__PURE__ */ new Date(),
|
|
176
|
+
changeFrequency: getChangeFreq(pathname, changeFreq),
|
|
177
|
+
priority: getPriority(pathname, priority)
|
|
178
|
+
};
|
|
179
|
+
if (locales.length > 0) {
|
|
180
|
+
entry.alternates = {
|
|
181
|
+
languages: Object.fromEntries(
|
|
182
|
+
locales.map((locale) => [locale, buildUrl(baseUrl, pathname, locale, defaultLocale)])
|
|
183
|
+
)
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
return entry;
|
|
187
|
+
});
|
|
188
|
+
}
|
|
34
189
|
|
|
35
190
|
// src/index.ts
|
|
36
191
|
function calculateDepthPriority(pathname) {
|
|
@@ -79,7 +234,8 @@ function buildUrl(baseUrl, pathname, locale, defaultLocale) {
|
|
|
79
234
|
}
|
|
80
235
|
return `${baseUrl}/${locale}${normalizedPath}`;
|
|
81
236
|
}
|
|
82
|
-
function generateSitemapXml(entries) {
|
|
237
|
+
function generateSitemapXml(entries, options) {
|
|
238
|
+
const { poweredBy = true } = options || {};
|
|
83
239
|
const hasAlternates = entries.some((e) => e.alternates?.languages);
|
|
84
240
|
const xmlns = hasAlternates ? 'xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml"' : 'xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"';
|
|
85
241
|
const urlEntries = entries.map((entry) => {
|
|
@@ -103,13 +259,14 @@ function generateSitemapXml(entries) {
|
|
|
103
259
|
${parts.join("\n")}
|
|
104
260
|
</url>`;
|
|
105
261
|
}).join("\n");
|
|
106
|
-
|
|
262
|
+
const comment = poweredBy ? "\n<!-- Powered by @onruntime/next-sitemap -->" : "";
|
|
263
|
+
return `<?xml version="1.0" encoding="UTF-8"?>${comment}
|
|
107
264
|
<urlset ${xmlns}>
|
|
108
265
|
${urlEntries}
|
|
109
266
|
</urlset>`;
|
|
110
267
|
}
|
|
111
268
|
function generateSitemapIndexXml(baseUrl, sitemapCount, options) {
|
|
112
|
-
const { sitemapPattern = "/sitemap-{id}.xml", additionalSitemaps = [] } = options || {};
|
|
269
|
+
const { sitemapPattern = "/sitemap-{id}.xml", additionalSitemaps = [], poweredBy = true } = options || {};
|
|
113
270
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
114
271
|
const paginatedEntries = Array.from({ length: sitemapCount }, (_, i) => {
|
|
115
272
|
const loc = `${baseUrl}${sitemapPattern.replace("{id}", String(i))}`;
|
|
@@ -126,7 +283,8 @@ function generateSitemapIndexXml(baseUrl, sitemapCount, options) {
|
|
|
126
283
|
</sitemap>`;
|
|
127
284
|
});
|
|
128
285
|
const allEntries = [...paginatedEntries, ...additionalEntries].join("\n");
|
|
129
|
-
|
|
286
|
+
const comment = poweredBy ? "\n<!-- Powered by @onruntime/next-sitemap -->" : "";
|
|
287
|
+
return `<?xml version="1.0" encoding="UTF-8"?>${comment}
|
|
130
288
|
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
131
289
|
${allEntries}
|
|
132
290
|
</sitemapindex>`;
|
|
@@ -134,7 +292,12 @@ ${allEntries}
|
|
|
134
292
|
|
|
135
293
|
// src/pages/robots.ts
|
|
136
294
|
function generateRobotsTxt(config) {
|
|
295
|
+
const { poweredBy = true } = config;
|
|
137
296
|
const lines = [];
|
|
297
|
+
if (poweredBy) {
|
|
298
|
+
lines.push("# Powered by @onruntime/next-sitemap");
|
|
299
|
+
lines.push("");
|
|
300
|
+
}
|
|
138
301
|
const rules = Array.isArray(config.rules) ? config.rules : [config.rules];
|
|
139
302
|
for (const rule of rules) {
|
|
140
303
|
const userAgents = Array.isArray(rule.userAgent) ? rule.userAgent : [rule.userAgent];
|
|
@@ -171,18 +334,17 @@ function generateRobotsTxt(config) {
|
|
|
171
334
|
}
|
|
172
335
|
|
|
173
336
|
// src/pages/index.ts
|
|
174
|
-
var joinPath = (...segments) => path__namespace.join(...segments);
|
|
175
337
|
function findPageFiles(dir, baseDir = dir) {
|
|
176
338
|
const files = [];
|
|
177
339
|
try {
|
|
178
|
-
const entries =
|
|
340
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
179
341
|
for (const entry of entries) {
|
|
180
|
-
const fullPath =
|
|
342
|
+
const fullPath = path.join(dir, entry.name);
|
|
181
343
|
if (entry.isDirectory()) {
|
|
182
344
|
if (entry.name === "api") continue;
|
|
183
345
|
files.push(...findPageFiles(fullPath, baseDir));
|
|
184
346
|
} else if (/\.(tsx?|jsx?)$/.test(entry.name) && !entry.name.startsWith("_")) {
|
|
185
|
-
const relativePath = "./" +
|
|
347
|
+
const relativePath = "./" + path.relative(baseDir, fullPath).replace(/\\/g, "/");
|
|
186
348
|
files.push(relativePath);
|
|
187
349
|
}
|
|
188
350
|
}
|
|
@@ -190,67 +352,12 @@ function findPageFiles(dir, baseDir = dir) {
|
|
|
190
352
|
}
|
|
191
353
|
return files;
|
|
192
354
|
}
|
|
193
|
-
function
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
return srcPages;
|
|
355
|
+
function resolvePagesDirectory(pagesDirectory) {
|
|
356
|
+
if (pagesDirectory) {
|
|
357
|
+
return path.isAbsolute(pagesDirectory) ? pagesDirectory : path.join(process.cwd(), pagesDirectory);
|
|
197
358
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
function resolvePagesDirectory(options) {
|
|
201
|
-
if (options.pagesDirectory) {
|
|
202
|
-
return path__namespace.isAbsolute(options.pagesDirectory) ? options.pagesDirectory : path__namespace.join(process.cwd(), options.pagesDirectory);
|
|
203
|
-
}
|
|
204
|
-
return detectPagesDirectory();
|
|
205
|
-
}
|
|
206
|
-
function getPageKeys(options) {
|
|
207
|
-
return findPageFiles(resolvePagesDirectory(options));
|
|
208
|
-
}
|
|
209
|
-
var jitiCache = /* @__PURE__ */ new Map();
|
|
210
|
-
function getTsconfigPaths(projectRoot) {
|
|
211
|
-
const alias = {};
|
|
212
|
-
try {
|
|
213
|
-
const tsconfigPath = path__namespace.join(projectRoot, "tsconfig.json");
|
|
214
|
-
if (fs__namespace.existsSync(tsconfigPath)) {
|
|
215
|
-
const content = fs__namespace.readFileSync(tsconfigPath, "utf-8");
|
|
216
|
-
const withoutComments = stripJsonComments__default.default(content);
|
|
217
|
-
const cleaned = withoutComments.replace(/,(\s*[}\]])/g, "$1");
|
|
218
|
-
const tsconfig = JSON.parse(cleaned);
|
|
219
|
-
const baseUrl = tsconfig.compilerOptions?.baseUrl || ".";
|
|
220
|
-
const paths = tsconfig.compilerOptions?.paths || {};
|
|
221
|
-
for (const [key, values] of Object.entries(paths)) {
|
|
222
|
-
if (values.length > 0) {
|
|
223
|
-
const aliasKey = key.replace(/\*$/, "");
|
|
224
|
-
const aliasValue = joinPath(projectRoot, baseUrl, values[0].replace(/\*$/, ""));
|
|
225
|
-
alias[aliasKey] = aliasValue;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
} catch {
|
|
230
|
-
}
|
|
231
|
-
return alias;
|
|
232
|
-
}
|
|
233
|
-
function getJiti(projectRoot) {
|
|
234
|
-
if (jitiCache.has(projectRoot)) {
|
|
235
|
-
return jitiCache.get(projectRoot);
|
|
236
|
-
}
|
|
237
|
-
const alias = getTsconfigPaths(projectRoot);
|
|
238
|
-
const jiti$1 = jiti.createJiti((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)), {
|
|
239
|
-
moduleCache: false,
|
|
240
|
-
interopDefault: true,
|
|
241
|
-
jsx: true,
|
|
242
|
-
alias
|
|
243
|
-
});
|
|
244
|
-
jitiCache.set(projectRoot, jiti$1);
|
|
245
|
-
return jiti$1;
|
|
246
|
-
}
|
|
247
|
-
async function importPage(pagesDirectory, key) {
|
|
248
|
-
const relativePath = key.replace("./", "");
|
|
249
|
-
const absolutePath = path__namespace.join(pagesDirectory, relativePath);
|
|
250
|
-
const projectRoot = process.cwd();
|
|
251
|
-
const jiti = getJiti(projectRoot);
|
|
252
|
-
const module = await jiti.import(absolutePath);
|
|
253
|
-
return module.default || module;
|
|
359
|
+
const srcPages = path.join(process.cwd(), "src/pages");
|
|
360
|
+
return fs.existsSync(srcPages) ? srcPages : path.join(process.cwd(), "pages");
|
|
254
361
|
}
|
|
255
362
|
function extractRoutes(pageKeys, localeSegment) {
|
|
256
363
|
const routes = [];
|
|
@@ -259,10 +366,8 @@ function extractRoutes(pageKeys, localeSegment) {
|
|
|
259
366
|
let pathname = key.replace(/^\.\//, "/").replace(/\.(tsx?|jsx?)$/, "").replace(/\/index$/, "/");
|
|
260
367
|
if (pathname === "") pathname = "/";
|
|
261
368
|
if (localeSegment) {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
""
|
|
265
|
-
);
|
|
369
|
+
const escapedSegment = localeSegment.replace(/[[\]]/g, "\\$&");
|
|
370
|
+
pathname = pathname.replace(new RegExp(`^/${escapedSegment}`), "");
|
|
266
371
|
}
|
|
267
372
|
if (/(?:^|\/)(src|pages)(?:\/|$)/.test(pathname)) continue;
|
|
268
373
|
if (!pathname || pathname === "") {
|
|
@@ -270,160 +375,57 @@ function extractRoutes(pageKeys, localeSegment) {
|
|
|
270
375
|
} else if (!pathname.startsWith("/")) {
|
|
271
376
|
pathname = "/" + pathname;
|
|
272
377
|
}
|
|
273
|
-
const dynamicSegments = pathname.match(/\[([^\]]+)\]/g)?.map((s) => s.slice(1, -1))
|
|
274
|
-
routes.push({
|
|
275
|
-
pathname,
|
|
276
|
-
dynamicSegments,
|
|
277
|
-
key
|
|
278
|
-
});
|
|
378
|
+
const dynamicSegments = pathname.match(/\[([^\]]+)\]/g)?.map((s) => s.slice(1, -1)) ?? [];
|
|
379
|
+
routes.push({ pathname, dynamicSegments, fileKey: key });
|
|
279
380
|
}
|
|
280
381
|
return routes;
|
|
281
382
|
}
|
|
282
|
-
async function getAllPaths(routes, pagesDirectory, debug = false) {
|
|
283
|
-
const allPaths = ["/"];
|
|
284
|
-
const seenPaths = /* @__PURE__ */ new Set(["/"]);
|
|
285
|
-
for (const route of routes) {
|
|
286
|
-
if (route.dynamicSegments.length === 0) {
|
|
287
|
-
if (route.pathname !== "/" && !seenPaths.has(route.pathname)) {
|
|
288
|
-
allPaths.push(route.pathname);
|
|
289
|
-
seenPaths.add(route.pathname);
|
|
290
|
-
}
|
|
291
|
-
} else {
|
|
292
|
-
let getParams = null;
|
|
293
|
-
try {
|
|
294
|
-
if (debug) {
|
|
295
|
-
console.log(`[next-sitemap] ${route.pathname}: importing ${route.key}`);
|
|
296
|
-
}
|
|
297
|
-
const module = await importPage(pagesDirectory, route.key);
|
|
298
|
-
const getStaticPaths = module.getStaticPaths;
|
|
299
|
-
if (getStaticPaths) {
|
|
300
|
-
getParams = async () => {
|
|
301
|
-
const result = await getStaticPaths();
|
|
302
|
-
return result.paths.map((p) => p.params);
|
|
303
|
-
};
|
|
304
|
-
} else if (module.generateStaticParams) {
|
|
305
|
-
getParams = module.generateStaticParams;
|
|
306
|
-
}
|
|
307
|
-
} catch (error) {
|
|
308
|
-
if (debug) {
|
|
309
|
-
console.warn(`[next-sitemap] ${route.pathname}: import failed:`, error);
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
if (getParams) {
|
|
313
|
-
try {
|
|
314
|
-
const params = await getParams();
|
|
315
|
-
if (debug) {
|
|
316
|
-
console.log(`[next-sitemap] ${route.pathname}: getStaticPaths returned ${params.length} params`);
|
|
317
|
-
}
|
|
318
|
-
for (const param of params) {
|
|
319
|
-
let dynamicPath = route.pathname;
|
|
320
|
-
for (const segment of route.dynamicSegments) {
|
|
321
|
-
const value = param[segment];
|
|
322
|
-
if (value === void 0) {
|
|
323
|
-
if (debug) {
|
|
324
|
-
console.warn(`[next-sitemap] ${route.pathname}: missing param "${segment}" in`, param);
|
|
325
|
-
}
|
|
326
|
-
continue;
|
|
327
|
-
}
|
|
328
|
-
dynamicPath = dynamicPath.replace(`[${segment}]`, value);
|
|
329
|
-
}
|
|
330
|
-
if (!seenPaths.has(dynamicPath)) {
|
|
331
|
-
allPaths.push(dynamicPath);
|
|
332
|
-
seenPaths.add(dynamicPath);
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
} catch (error) {
|
|
336
|
-
console.error(`[next-sitemap] Error calling getStaticPaths for ${route.pathname}:`, error);
|
|
337
|
-
}
|
|
338
|
-
} else {
|
|
339
|
-
if (debug) {
|
|
340
|
-
console.warn(
|
|
341
|
-
`[next-sitemap] Skipping dynamic route ${route.pathname}: no getStaticPaths exported. Use additionalSitemaps for routes that fetch data at runtime.`
|
|
342
|
-
);
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
return allPaths;
|
|
348
|
-
}
|
|
349
|
-
function pathsToEntries(paths, config) {
|
|
350
|
-
const { baseUrl, locales = [], defaultLocale, exclude, priority, changeFreq } = config;
|
|
351
|
-
const filteredPaths = paths.filter((pathname) => !shouldExclude(pathname, exclude));
|
|
352
|
-
return filteredPaths.map((pathname) => {
|
|
353
|
-
const entry = {
|
|
354
|
-
url: buildUrl(baseUrl, pathname, defaultLocale, defaultLocale),
|
|
355
|
-
lastModified: /* @__PURE__ */ new Date(),
|
|
356
|
-
changeFrequency: getChangeFreq(pathname, changeFreq),
|
|
357
|
-
priority: getPriority(pathname, priority)
|
|
358
|
-
};
|
|
359
|
-
if (locales.length > 0) {
|
|
360
|
-
entry.alternates = {
|
|
361
|
-
languages: Object.fromEntries(
|
|
362
|
-
locales.map((locale) => [
|
|
363
|
-
locale,
|
|
364
|
-
buildUrl(baseUrl, pathname, locale, defaultLocale)
|
|
365
|
-
])
|
|
366
|
-
)
|
|
367
|
-
};
|
|
368
|
-
}
|
|
369
|
-
return entry;
|
|
370
|
-
});
|
|
371
|
-
}
|
|
372
383
|
function createSitemapIndexApiHandler(options) {
|
|
373
384
|
const { urlsPerSitemap = 5e3, additionalSitemaps, exclude, debug = false } = options;
|
|
374
385
|
const localeSegment = options.localeSegment ?? "";
|
|
375
|
-
const pagesDir = resolvePagesDirectory(options);
|
|
376
|
-
const pageKeys =
|
|
386
|
+
const pagesDir = resolvePagesDirectory(options.pagesDirectory);
|
|
387
|
+
const pageKeys = findPageFiles(pagesDir);
|
|
377
388
|
const routes = extractRoutes(pageKeys, localeSegment);
|
|
378
|
-
if (debug) {
|
|
379
|
-
console.log(`[next-sitemap] Found ${routes.length} routes:`);
|
|
380
|
-
routes.forEach((r) => {
|
|
381
|
-
const isDynamic = r.dynamicSegments.length > 0;
|
|
382
|
-
const segments = isDynamic ? ` [${r.dynamicSegments.join(", ")}]` : "";
|
|
383
|
-
console.log(` ${r.pathname}${segments}${isDynamic ? " (dynamic)" : ""}`);
|
|
384
|
-
});
|
|
385
|
-
}
|
|
386
389
|
return async function handler(_req, res) {
|
|
387
|
-
|
|
388
|
-
|
|
390
|
+
if (debug) {
|
|
391
|
+
console.log(`[next-sitemap] Found ${routes.length} routes`);
|
|
392
|
+
}
|
|
393
|
+
const allPaths = await generateAllPaths(routes, pagesDir, debug);
|
|
394
|
+
const filteredPaths = allPaths.filter((p) => !shouldExclude(p, exclude));
|
|
389
395
|
const sitemapCount = Math.max(1, Math.ceil(filteredPaths.length / urlsPerSitemap));
|
|
390
|
-
const xml = generateSitemapIndexXml(options.baseUrl, sitemapCount, {
|
|
391
|
-
additionalSitemaps
|
|
392
|
-
});
|
|
393
396
|
res.setHeader("Content-Type", "application/xml");
|
|
394
|
-
res.status(200).send(
|
|
397
|
+
res.status(200).send(
|
|
398
|
+
generateSitemapIndexXml(options.baseUrl, sitemapCount, { additionalSitemaps, poweredBy: options.poweredBy })
|
|
399
|
+
);
|
|
395
400
|
};
|
|
396
401
|
}
|
|
397
402
|
function createSitemapApiHandler(options) {
|
|
398
403
|
const { urlsPerSitemap = 5e3, exclude, debug = false } = options;
|
|
399
404
|
const localeSegment = options.localeSegment ?? "";
|
|
400
|
-
const pagesDir = resolvePagesDirectory(options);
|
|
401
|
-
const pageKeys =
|
|
405
|
+
const pagesDir = resolvePagesDirectory(options.pagesDirectory);
|
|
406
|
+
const pageKeys = findPageFiles(pagesDir);
|
|
402
407
|
const routes = extractRoutes(pageKeys, localeSegment);
|
|
403
408
|
const getFilteredPaths = async () => {
|
|
404
|
-
const allPaths = await
|
|
405
|
-
return allPaths.filter((
|
|
409
|
+
const allPaths = await generateAllPaths(routes, pagesDir, debug);
|
|
410
|
+
return allPaths.filter((p) => !shouldExclude(p, exclude));
|
|
406
411
|
};
|
|
407
412
|
return async function handler(req, res) {
|
|
408
413
|
const { id } = req.query;
|
|
409
414
|
const sitemapId = parseInt(Array.isArray(id) ? id[0] : id || "0", 10);
|
|
410
415
|
const filteredPaths = await getFilteredPaths();
|
|
411
|
-
const
|
|
412
|
-
const end = start + urlsPerSitemap;
|
|
413
|
-
const paths = filteredPaths.slice(start, end);
|
|
416
|
+
const paths = filteredPaths.slice(sitemapId * urlsPerSitemap, (sitemapId + 1) * urlsPerSitemap);
|
|
414
417
|
const entries = pathsToEntries(paths, { ...options, exclude: void 0 });
|
|
415
|
-
const xml = generateSitemapXml(entries);
|
|
416
418
|
res.setHeader("Content-Type", "application/xml");
|
|
417
|
-
res.status(200).send(
|
|
419
|
+
res.status(200).send(generateSitemapXml(entries, { poweredBy: options.poweredBy }));
|
|
418
420
|
};
|
|
419
421
|
}
|
|
420
422
|
async function getSitemapStaticPaths(options) {
|
|
421
423
|
const { urlsPerSitemap = 5e3, exclude, debug = false } = options;
|
|
422
424
|
const localeSegment = options.localeSegment ?? "";
|
|
423
|
-
const pagesDir = resolvePagesDirectory(options);
|
|
424
|
-
const pageKeys =
|
|
425
|
+
const pagesDir = resolvePagesDirectory(options.pagesDirectory);
|
|
426
|
+
const pageKeys = findPageFiles(pagesDir);
|
|
425
427
|
const routes = extractRoutes(pageKeys, localeSegment);
|
|
426
|
-
const allPaths = await
|
|
428
|
+
const allPaths = await generateAllPaths(routes, pagesDir, debug);
|
|
427
429
|
const filteredPaths = allPaths.filter((pathname) => !shouldExclude(pathname, exclude));
|
|
428
430
|
const sitemapCount = Math.max(1, Math.ceil(filteredPaths.length / urlsPerSitemap));
|
|
429
431
|
return {
|
|
@@ -435,9 +437,8 @@ async function getSitemapStaticPaths(options) {
|
|
|
435
437
|
}
|
|
436
438
|
function createRobotsApiHandler(config) {
|
|
437
439
|
return function handler(_req, res) {
|
|
438
|
-
const robotsTxt = generateRobotsTxt(config);
|
|
439
440
|
res.setHeader("Content-Type", "text/plain");
|
|
440
|
-
res.status(200).send(
|
|
441
|
+
res.status(200).send(generateRobotsTxt(config));
|
|
441
442
|
};
|
|
442
443
|
}
|
|
443
444
|
|
package/dist/pages/index.d.cts
CHANGED
|
@@ -2,6 +2,7 @@ import { MetadataRoute, NextApiRequest, NextApiResponse } from 'next';
|
|
|
2
2
|
export { MetadataRoute } from 'next';
|
|
3
3
|
|
|
4
4
|
type ChangeFrequency = "always" | "hourly" | "daily" | "weekly" | "monthly" | "yearly" | "never";
|
|
5
|
+
|
|
5
6
|
interface SitemapConfig {
|
|
6
7
|
/**
|
|
7
8
|
* Base URL of the site (e.g., "https://example.com")
|
|
@@ -54,6 +55,16 @@ interface SitemapConfig {
|
|
|
54
55
|
* @example additionalSitemaps: ["/products-sitemap.xml", "/blog-sitemap.xml"]
|
|
55
56
|
*/
|
|
56
57
|
additionalSitemaps?: string[];
|
|
58
|
+
/**
|
|
59
|
+
* Enable debug logging to diagnose issues with route discovery
|
|
60
|
+
* @default false
|
|
61
|
+
*/
|
|
62
|
+
debug?: boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Include "Powered by @onruntime/next-sitemap" comment in generated XML
|
|
65
|
+
* @default true
|
|
66
|
+
*/
|
|
67
|
+
poweredBy?: boolean;
|
|
57
68
|
}
|
|
58
69
|
interface SitemapEntry {
|
|
59
70
|
url: string;
|
|
@@ -65,39 +76,35 @@ interface SitemapEntry {
|
|
|
65
76
|
};
|
|
66
77
|
}
|
|
67
78
|
|
|
79
|
+
interface RobotsConfig extends MetadataRoute.Robots {
|
|
80
|
+
/**
|
|
81
|
+
* Include "Powered by @onruntime/next-sitemap" comment
|
|
82
|
+
* @default true
|
|
83
|
+
*/
|
|
84
|
+
poweredBy?: boolean;
|
|
85
|
+
}
|
|
68
86
|
/**
|
|
69
87
|
* Generate robots.txt content from configuration
|
|
70
88
|
* Compatible with Next.js MetadataRoute.Robots
|
|
71
89
|
*/
|
|
72
|
-
declare function generateRobotsTxt(config:
|
|
90
|
+
declare function generateRobotsTxt(config: RobotsConfig): string;
|
|
73
91
|
|
|
74
92
|
interface CreateSitemapApiHandlerOptions extends SitemapConfig {
|
|
75
93
|
/**
|
|
76
94
|
* Path to the pages directory to scan for page files.
|
|
77
95
|
* Can be absolute or relative to process.cwd().
|
|
78
96
|
* If not provided, auto-detects src/pages or pages.
|
|
79
|
-
*
|
|
80
|
-
* Example:
|
|
81
|
-
* ```ts
|
|
82
|
-
* pagesDirectory: 'src/pages'
|
|
83
|
-
* ```
|
|
84
97
|
*/
|
|
85
98
|
pagesDirectory?: string;
|
|
86
|
-
/**
|
|
87
|
-
* Enable debug logging to diagnose issues with route discovery
|
|
88
|
-
* Logs info about getStaticPaths calls and skipped routes
|
|
89
|
-
* @default false
|
|
90
|
-
*/
|
|
91
|
-
debug?: boolean;
|
|
92
99
|
}
|
|
93
100
|
/**
|
|
94
101
|
* Create API handler for sitemap index route
|
|
95
|
-
* Use in: pages/api/sitemap.xml.ts
|
|
102
|
+
* Use in: pages/api/sitemap.xml.ts
|
|
96
103
|
*/
|
|
97
104
|
declare function createSitemapIndexApiHandler(options: CreateSitemapApiHandlerOptions): (_req: NextApiRequest, res: NextApiResponse) => Promise<void>;
|
|
98
105
|
/**
|
|
99
106
|
* Create API handler for individual sitemap routes
|
|
100
|
-
* Use in: pages/api/sitemap/[id].ts
|
|
107
|
+
* Use in: pages/api/sitemap/[id].ts
|
|
101
108
|
*/
|
|
102
109
|
declare function createSitemapApiHandler(options: CreateSitemapApiHandlerOptions): (req: NextApiRequest, res: NextApiResponse) => Promise<void>;
|
|
103
110
|
/**
|
|
@@ -114,21 +121,8 @@ declare function getSitemapStaticPaths(options: CreateSitemapApiHandlerOptions):
|
|
|
114
121
|
}>;
|
|
115
122
|
/**
|
|
116
123
|
* Create API handler for robots.txt
|
|
117
|
-
* Use in: pages/api/robots.txt.ts
|
|
118
|
-
*
|
|
119
|
-
* @example
|
|
120
|
-
* // pages/api/robots.txt.ts
|
|
121
|
-
* import { createRobotsApiHandler } from "@onruntime/next-sitemap/pages";
|
|
122
|
-
*
|
|
123
|
-
* export default createRobotsApiHandler({
|
|
124
|
-
* rules: {
|
|
125
|
-
* userAgent: "*",
|
|
126
|
-
* allow: "/",
|
|
127
|
-
* disallow: ["/admin", "/private"],
|
|
128
|
-
* },
|
|
129
|
-
* sitemap: "https://example.com/sitemap.xml",
|
|
130
|
-
* });
|
|
124
|
+
* Use in: pages/api/robots.txt.ts
|
|
131
125
|
*/
|
|
132
|
-
declare function createRobotsApiHandler(config:
|
|
126
|
+
declare function createRobotsApiHandler(config: RobotsConfig): (_req: NextApiRequest, res: NextApiResponse) => void;
|
|
133
127
|
|
|
134
|
-
export { type ChangeFrequency, type CreateSitemapApiHandlerOptions, type SitemapConfig, type SitemapEntry, createRobotsApiHandler, createSitemapApiHandler, createSitemapIndexApiHandler, generateRobotsTxt, getSitemapStaticPaths };
|
|
128
|
+
export { type ChangeFrequency, type CreateSitemapApiHandlerOptions, type RobotsConfig, type SitemapConfig, type SitemapEntry, createRobotsApiHandler, createSitemapApiHandler, createSitemapIndexApiHandler, generateRobotsTxt, getSitemapStaticPaths };
|