@gravito/constellation 1.0.0-alpha.6 → 1.0.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 +1 -1
- package/README.zh-TW.md +1 -1
- package/dist/DiskSitemapStorage-7ZZMGC4K.js +6 -0
- package/dist/chunk-7WHLC3OJ.js +56 -0
- package/dist/index.cjs +515 -254
- package/dist/index.d.cts +821 -0
- package/dist/index.d.ts +821 -0
- package/dist/{index.mjs → index.js} +413 -258
- package/package.json +21 -11
package/dist/index.cjs
CHANGED
|
@@ -1,121 +1,130 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
var __create = Object.create;
|
|
2
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
3
3
|
var __defProp = Object.defineProperty;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
-
var
|
|
8
|
-
|
|
9
|
-
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
10
|
-
for (let key of __getOwnPropNames(mod))
|
|
11
|
-
if (!__hasOwnProp.call(to, key))
|
|
12
|
-
__defProp(to, key, {
|
|
13
|
-
get: () => mod[key],
|
|
14
|
-
enumerable: true
|
|
15
|
-
});
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __moduleCache = /* @__PURE__ */ new WeakMap;
|
|
19
|
-
var __toCommonJS = (from) => {
|
|
20
|
-
var entry = __moduleCache.get(from), desc;
|
|
21
|
-
if (entry)
|
|
22
|
-
return entry;
|
|
23
|
-
entry = __defProp({}, "__esModule", { value: true });
|
|
24
|
-
if (from && typeof from === "object" || typeof from === "function")
|
|
25
|
-
__getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
|
|
26
|
-
get: () => from[key],
|
|
27
|
-
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
28
|
-
}));
|
|
29
|
-
__moduleCache.set(from, entry);
|
|
30
|
-
return entry;
|
|
8
|
+
var __esm = (fn, res) => function __init() {
|
|
9
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
31
10
|
};
|
|
32
11
|
var __export = (target, all) => {
|
|
33
12
|
for (var name in all)
|
|
34
|
-
__defProp(target, name, {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
13
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
14
|
+
};
|
|
15
|
+
var __copyProps = (to, from, except, desc) => {
|
|
16
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
17
|
+
for (let key of __getOwnPropNames(from))
|
|
18
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
19
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
20
|
+
}
|
|
21
|
+
return to;
|
|
40
22
|
};
|
|
41
|
-
var
|
|
23
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
24
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
25
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
26
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
27
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
28
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
29
|
+
mod
|
|
30
|
+
));
|
|
31
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
42
32
|
|
|
43
33
|
// src/storage/DiskSitemapStorage.ts
|
|
44
|
-
var
|
|
45
|
-
__export(
|
|
34
|
+
var DiskSitemapStorage_exports = {};
|
|
35
|
+
__export(DiskSitemapStorage_exports, {
|
|
46
36
|
DiskSitemapStorage: () => DiskSitemapStorage
|
|
47
37
|
});
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
baseUrl;
|
|
52
|
-
constructor(outDir, baseUrl) {
|
|
53
|
-
this.outDir = outDir;
|
|
54
|
-
this.baseUrl = baseUrl;
|
|
38
|
+
function sanitizeFilename(filename) {
|
|
39
|
+
if (!filename) {
|
|
40
|
+
throw new Error("Invalid sitemap filename.");
|
|
55
41
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
await import_promises.default.writeFile(import_node_path.default.join(this.outDir, filename), content);
|
|
42
|
+
if (filename.includes("\0")) {
|
|
43
|
+
throw new Error("Invalid sitemap filename.");
|
|
59
44
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
return await import_promises.default.readFile(import_node_path.default.join(this.outDir, filename), "utf-8");
|
|
63
|
-
} catch {
|
|
64
|
-
return null;
|
|
65
|
-
}
|
|
45
|
+
if (filename.includes("/") || filename.includes("\\")) {
|
|
46
|
+
throw new Error("Invalid sitemap filename.");
|
|
66
47
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
await import_promises.default.access(import_node_path.default.join(this.outDir, filename));
|
|
70
|
-
return true;
|
|
71
|
-
} catch {
|
|
72
|
-
return false;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
getUrl(filename) {
|
|
76
|
-
const base = this.baseUrl.endsWith("/") ? this.baseUrl.slice(0, -1) : this.baseUrl;
|
|
77
|
-
const file = filename.startsWith("/") ? filename.slice(1) : filename;
|
|
78
|
-
return `${base}/${file}`;
|
|
48
|
+
if (filename.includes("..")) {
|
|
49
|
+
throw new Error("Invalid sitemap filename.");
|
|
79
50
|
}
|
|
51
|
+
return filename;
|
|
80
52
|
}
|
|
81
|
-
var import_promises, import_node_path;
|
|
82
|
-
var init_DiskSitemapStorage = __esm(
|
|
83
|
-
|
|
84
|
-
|
|
53
|
+
var import_promises, import_node_path, DiskSitemapStorage;
|
|
54
|
+
var init_DiskSitemapStorage = __esm({
|
|
55
|
+
"src/storage/DiskSitemapStorage.ts"() {
|
|
56
|
+
"use strict";
|
|
57
|
+
import_promises = __toESM(require("fs/promises"), 1);
|
|
58
|
+
import_node_path = __toESM(require("path"), 1);
|
|
59
|
+
DiskSitemapStorage = class {
|
|
60
|
+
constructor(outDir, baseUrl) {
|
|
61
|
+
this.outDir = outDir;
|
|
62
|
+
this.baseUrl = baseUrl;
|
|
63
|
+
}
|
|
64
|
+
async write(filename, content) {
|
|
65
|
+
const safeName = sanitizeFilename(filename);
|
|
66
|
+
await import_promises.default.mkdir(this.outDir, { recursive: true });
|
|
67
|
+
await import_promises.default.writeFile(import_node_path.default.join(this.outDir, safeName), content);
|
|
68
|
+
}
|
|
69
|
+
async read(filename) {
|
|
70
|
+
try {
|
|
71
|
+
const safeName = sanitizeFilename(filename);
|
|
72
|
+
return await import_promises.default.readFile(import_node_path.default.join(this.outDir, safeName), "utf-8");
|
|
73
|
+
} catch {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
async exists(filename) {
|
|
78
|
+
try {
|
|
79
|
+
const safeName = sanitizeFilename(filename);
|
|
80
|
+
await import_promises.default.access(import_node_path.default.join(this.outDir, safeName));
|
|
81
|
+
return true;
|
|
82
|
+
} catch {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
getUrl(filename) {
|
|
87
|
+
const safeName = sanitizeFilename(filename);
|
|
88
|
+
const base = this.baseUrl.endsWith("/") ? this.baseUrl.slice(0, -1) : this.baseUrl;
|
|
89
|
+
const file = safeName.startsWith("/") ? safeName.slice(1) : safeName;
|
|
90
|
+
return `${base}/${file}`;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
85
94
|
});
|
|
86
95
|
|
|
87
96
|
// src/index.ts
|
|
88
|
-
var
|
|
89
|
-
__export(
|
|
90
|
-
|
|
91
|
-
generateI18nEntries: () => generateI18nEntries,
|
|
92
|
-
SitemapStream: () => SitemapStream,
|
|
93
|
-
SitemapIndex: () => SitemapIndex,
|
|
94
|
-
SitemapGenerator: () => SitemapGenerator,
|
|
95
|
-
ShadowProcessor: () => ShadowProcessor,
|
|
96
|
-
S3SitemapStorage: () => S3SitemapStorage,
|
|
97
|
-
RouteScanner: () => RouteScanner,
|
|
98
|
-
RedisRedirectManager: () => RedisRedirectManager,
|
|
99
|
-
RedisProgressStorage: () => RedisProgressStorage,
|
|
100
|
-
RedisChangeTracker: () => RedisChangeTracker,
|
|
101
|
-
RedirectHandler: () => RedirectHandler,
|
|
102
|
-
RedirectDetector: () => RedirectDetector,
|
|
103
|
-
ProgressTracker: () => ProgressTracker,
|
|
104
|
-
OrbitSitemap: () => OrbitSitemap,
|
|
105
|
-
MemorySitemapStorage: () => MemorySitemapStorage,
|
|
106
|
-
MemoryRedirectManager: () => MemoryRedirectManager,
|
|
107
|
-
MemoryProgressStorage: () => MemoryProgressStorage,
|
|
108
|
-
MemoryChangeTracker: () => MemoryChangeTracker,
|
|
109
|
-
IncrementalGenerator: () => IncrementalGenerator,
|
|
110
|
-
GenerateSitemapJob: () => GenerateSitemapJob,
|
|
111
|
-
GCPSitemapStorage: () => GCPSitemapStorage,
|
|
97
|
+
var index_exports = {};
|
|
98
|
+
__export(index_exports, {
|
|
99
|
+
DiffCalculator: () => DiffCalculator,
|
|
112
100
|
DiskSitemapStorage: () => DiskSitemapStorage,
|
|
113
|
-
|
|
101
|
+
GCPSitemapStorage: () => GCPSitemapStorage,
|
|
102
|
+
GenerateSitemapJob: () => GenerateSitemapJob,
|
|
103
|
+
IncrementalGenerator: () => IncrementalGenerator,
|
|
104
|
+
MemoryChangeTracker: () => MemoryChangeTracker,
|
|
105
|
+
MemoryProgressStorage: () => MemoryProgressStorage,
|
|
106
|
+
MemoryRedirectManager: () => MemoryRedirectManager,
|
|
107
|
+
MemorySitemapStorage: () => MemorySitemapStorage,
|
|
108
|
+
OrbitSitemap: () => OrbitSitemap,
|
|
109
|
+
ProgressTracker: () => ProgressTracker,
|
|
110
|
+
RedirectDetector: () => RedirectDetector,
|
|
111
|
+
RedirectHandler: () => RedirectHandler,
|
|
112
|
+
RedisChangeTracker: () => RedisChangeTracker,
|
|
113
|
+
RedisProgressStorage: () => RedisProgressStorage,
|
|
114
|
+
RedisRedirectManager: () => RedisRedirectManager,
|
|
115
|
+
RouteScanner: () => RouteScanner,
|
|
116
|
+
S3SitemapStorage: () => S3SitemapStorage,
|
|
117
|
+
ShadowProcessor: () => ShadowProcessor,
|
|
118
|
+
SitemapGenerator: () => SitemapGenerator,
|
|
119
|
+
SitemapIndex: () => SitemapIndex,
|
|
120
|
+
SitemapStream: () => SitemapStream,
|
|
121
|
+
generateI18nEntries: () => generateI18nEntries,
|
|
122
|
+
routeScanner: () => routeScanner
|
|
114
123
|
});
|
|
115
|
-
module.exports = __toCommonJS(
|
|
124
|
+
module.exports = __toCommonJS(index_exports);
|
|
116
125
|
|
|
117
126
|
// src/core/ChangeTracker.ts
|
|
118
|
-
|
|
127
|
+
var MemoryChangeTracker = class {
|
|
119
128
|
changes = [];
|
|
120
129
|
maxChanges;
|
|
121
130
|
constructor(options = {}) {
|
|
@@ -143,9 +152,8 @@ class MemoryChangeTracker {
|
|
|
143
152
|
}
|
|
144
153
|
this.changes = this.changes.filter((change) => change.timestamp < since);
|
|
145
154
|
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
class RedisChangeTracker {
|
|
155
|
+
};
|
|
156
|
+
var RedisChangeTracker = class {
|
|
149
157
|
client;
|
|
150
158
|
keyPrefix;
|
|
151
159
|
ttl;
|
|
@@ -220,18 +228,23 @@ class RedisChangeTracker {
|
|
|
220
228
|
await this.client.zrem(listKey, url);
|
|
221
229
|
}
|
|
222
230
|
}
|
|
223
|
-
} catch {
|
|
231
|
+
} catch {
|
|
232
|
+
}
|
|
224
233
|
}
|
|
225
|
-
}
|
|
234
|
+
};
|
|
235
|
+
|
|
226
236
|
// src/core/DiffCalculator.ts
|
|
227
|
-
|
|
237
|
+
var DiffCalculator = class {
|
|
228
238
|
batchSize;
|
|
229
239
|
constructor(options = {}) {
|
|
230
240
|
this.batchSize = options.batchSize || 1e4;
|
|
231
241
|
}
|
|
242
|
+
/**
|
|
243
|
+
* 計算兩個 sitemap 狀態的差異
|
|
244
|
+
*/
|
|
232
245
|
calculate(oldEntries, newEntries) {
|
|
233
|
-
const oldMap = new Map;
|
|
234
|
-
const newMap = new Map;
|
|
246
|
+
const oldMap = /* @__PURE__ */ new Map();
|
|
247
|
+
const newMap = /* @__PURE__ */ new Map();
|
|
235
248
|
for (const entry of oldEntries) {
|
|
236
249
|
oldMap.set(entry.url, entry);
|
|
237
250
|
}
|
|
@@ -256,9 +269,12 @@ class DiffCalculator {
|
|
|
256
269
|
}
|
|
257
270
|
return { added, updated, removed };
|
|
258
271
|
}
|
|
272
|
+
/**
|
|
273
|
+
* 批次計算差異(用於大量 URL)
|
|
274
|
+
*/
|
|
259
275
|
async calculateBatch(oldEntries, newEntries) {
|
|
260
|
-
const oldMap = new Map;
|
|
261
|
-
const newMap = new Map;
|
|
276
|
+
const oldMap = /* @__PURE__ */ new Map();
|
|
277
|
+
const newMap = /* @__PURE__ */ new Map();
|
|
262
278
|
for await (const entry of oldEntries) {
|
|
263
279
|
oldMap.set(entry.url, entry);
|
|
264
280
|
}
|
|
@@ -267,8 +283,11 @@ class DiffCalculator {
|
|
|
267
283
|
}
|
|
268
284
|
return this.calculate(Array.from(oldMap.values()), Array.from(newMap.values()));
|
|
269
285
|
}
|
|
286
|
+
/**
|
|
287
|
+
* 從變更記錄計算差異
|
|
288
|
+
*/
|
|
270
289
|
calculateFromChanges(baseEntries, changes) {
|
|
271
|
-
const entryMap = new Map;
|
|
290
|
+
const entryMap = /* @__PURE__ */ new Map();
|
|
272
291
|
for (const entry of baseEntries) {
|
|
273
292
|
entryMap.set(entry.url, entry);
|
|
274
293
|
}
|
|
@@ -284,6 +303,9 @@ class DiffCalculator {
|
|
|
284
303
|
const newEntries = Array.from(entryMap.values());
|
|
285
304
|
return this.calculate(baseEntries, newEntries);
|
|
286
305
|
}
|
|
306
|
+
/**
|
|
307
|
+
* 檢查 entry 是否有變更
|
|
308
|
+
*/
|
|
287
309
|
hasChanged(oldEntry, newEntry) {
|
|
288
310
|
if (oldEntry.lastmod !== newEntry.lastmod) {
|
|
289
311
|
return true;
|
|
@@ -301,9 +323,10 @@ class DiffCalculator {
|
|
|
301
323
|
}
|
|
302
324
|
return false;
|
|
303
325
|
}
|
|
304
|
-
}
|
|
326
|
+
};
|
|
327
|
+
|
|
305
328
|
// src/core/ShadowProcessor.ts
|
|
306
|
-
|
|
329
|
+
var ShadowProcessor = class {
|
|
307
330
|
options;
|
|
308
331
|
shadowId;
|
|
309
332
|
operations = [];
|
|
@@ -311,6 +334,9 @@ class ShadowProcessor {
|
|
|
311
334
|
this.options = options;
|
|
312
335
|
this.shadowId = `shadow-${Date.now()}-${Math.random().toString(36).substring(7)}`;
|
|
313
336
|
}
|
|
337
|
+
/**
|
|
338
|
+
* 添加一個影子操作
|
|
339
|
+
*/
|
|
314
340
|
async addOperation(operation) {
|
|
315
341
|
if (!this.options.enabled) {
|
|
316
342
|
await this.options.storage.write(operation.filename, operation.content);
|
|
@@ -326,6 +352,9 @@ class ShadowProcessor {
|
|
|
326
352
|
await this.options.storage.write(operation.filename, operation.content);
|
|
327
353
|
}
|
|
328
354
|
}
|
|
355
|
+
/**
|
|
356
|
+
* 提交所有影子操作
|
|
357
|
+
*/
|
|
329
358
|
async commit() {
|
|
330
359
|
if (!this.options.enabled) {
|
|
331
360
|
return;
|
|
@@ -343,22 +372,31 @@ class ShadowProcessor {
|
|
|
343
372
|
}
|
|
344
373
|
this.operations = [];
|
|
345
374
|
}
|
|
375
|
+
/**
|
|
376
|
+
* 取消所有影子操作
|
|
377
|
+
*/
|
|
346
378
|
async rollback() {
|
|
347
379
|
if (!this.options.enabled) {
|
|
348
380
|
return;
|
|
349
381
|
}
|
|
350
382
|
this.operations = [];
|
|
351
383
|
}
|
|
384
|
+
/**
|
|
385
|
+
* 獲取當前影子 ID
|
|
386
|
+
*/
|
|
352
387
|
getShadowId() {
|
|
353
388
|
return this.shadowId;
|
|
354
389
|
}
|
|
390
|
+
/**
|
|
391
|
+
* 獲取所有操作
|
|
392
|
+
*/
|
|
355
393
|
getOperations() {
|
|
356
394
|
return [...this.operations];
|
|
357
395
|
}
|
|
358
|
-
}
|
|
396
|
+
};
|
|
359
397
|
|
|
360
398
|
// src/core/SitemapIndex.ts
|
|
361
|
-
|
|
399
|
+
var SitemapIndex = class {
|
|
362
400
|
options;
|
|
363
401
|
entries = [];
|
|
364
402
|
constructor(options) {
|
|
@@ -389,8 +427,7 @@ class SitemapIndex {
|
|
|
389
427
|
`;
|
|
390
428
|
const indent = pretty ? " " : "";
|
|
391
429
|
const subIndent = pretty ? " " : "";
|
|
392
|
-
const nl = pretty ?
|
|
393
|
-
` : "";
|
|
430
|
+
const nl = pretty ? "\n" : "";
|
|
394
431
|
for (const entry of this.entries) {
|
|
395
432
|
let loc = entry.url;
|
|
396
433
|
if (!loc.startsWith("http")) {
|
|
@@ -413,10 +450,10 @@ class SitemapIndex {
|
|
|
413
450
|
escape(str) {
|
|
414
451
|
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
415
452
|
}
|
|
416
|
-
}
|
|
453
|
+
};
|
|
417
454
|
|
|
418
455
|
// src/core/SitemapStream.ts
|
|
419
|
-
|
|
456
|
+
var SitemapStream = class {
|
|
420
457
|
options;
|
|
421
458
|
entries = [];
|
|
422
459
|
constructor(options) {
|
|
@@ -467,8 +504,7 @@ class SitemapStream {
|
|
|
467
504
|
renderUrl(entry, baseUrl, pretty) {
|
|
468
505
|
const indent = pretty ? " " : "";
|
|
469
506
|
const subIndent = pretty ? " " : "";
|
|
470
|
-
const nl = pretty ?
|
|
471
|
-
` : "";
|
|
507
|
+
const nl = pretty ? "\n" : "";
|
|
472
508
|
let loc = entry.url;
|
|
473
509
|
if (!loc.startsWith("http")) {
|
|
474
510
|
if (!loc.startsWith("/")) {
|
|
@@ -485,7 +521,7 @@ class SitemapStream {
|
|
|
485
521
|
if (entry.changefreq) {
|
|
486
522
|
item += `${subIndent}<changefreq>${entry.changefreq}</changefreq>${nl}`;
|
|
487
523
|
}
|
|
488
|
-
if (entry.priority !==
|
|
524
|
+
if (entry.priority !== void 0) {
|
|
489
525
|
item += `${subIndent}<priority>${entry.priority.toFixed(1)}</priority>${nl}`;
|
|
490
526
|
}
|
|
491
527
|
if (entry.alternates) {
|
|
@@ -511,7 +547,7 @@ class SitemapStream {
|
|
|
511
547
|
item += `${subIndent}<xhtml:link rel="canonical" href="${this.escape(canonicalUrl)}"/>${nl}`;
|
|
512
548
|
}
|
|
513
549
|
if (entry.redirect && !entry.redirect.canonical) {
|
|
514
|
-
item += `${subIndent}<!-- Redirect: ${entry.redirect.from}
|
|
550
|
+
item += `${subIndent}<!-- Redirect: ${entry.redirect.from} \u2192 ${entry.redirect.to} (${entry.redirect.type}) -->${nl}`;
|
|
515
551
|
}
|
|
516
552
|
if (entry.images) {
|
|
517
553
|
for (const img of entry.images) {
|
|
@@ -607,15 +643,15 @@ class SitemapStream {
|
|
|
607
643
|
escape(str) {
|
|
608
644
|
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
609
645
|
}
|
|
610
|
-
}
|
|
646
|
+
};
|
|
611
647
|
|
|
612
648
|
// src/core/SitemapGenerator.ts
|
|
613
|
-
|
|
649
|
+
var SitemapGenerator = class {
|
|
614
650
|
options;
|
|
615
651
|
shadowProcessor = null;
|
|
616
652
|
constructor(options) {
|
|
617
653
|
this.options = {
|
|
618
|
-
maxEntriesPerFile:
|
|
654
|
+
maxEntriesPerFile: 5e4,
|
|
619
655
|
filename: "sitemap.xml",
|
|
620
656
|
...options
|
|
621
657
|
};
|
|
@@ -653,7 +689,7 @@ class SitemapGenerator {
|
|
|
653
689
|
const url = this.options.storage.getUrl(filename);
|
|
654
690
|
index.add({
|
|
655
691
|
url,
|
|
656
|
-
lastmod: new Date
|
|
692
|
+
lastmod: /* @__PURE__ */ new Date()
|
|
657
693
|
});
|
|
658
694
|
shardIndex++;
|
|
659
695
|
currentCount = 0;
|
|
@@ -694,13 +730,16 @@ class SitemapGenerator {
|
|
|
694
730
|
await this.options.storage.write(this.options.filename, indexXml);
|
|
695
731
|
}
|
|
696
732
|
}
|
|
733
|
+
/**
|
|
734
|
+
* 獲取影子處理器(如果啟用)
|
|
735
|
+
*/
|
|
697
736
|
getShadowProcessor() {
|
|
698
737
|
return this.shadowProcessor;
|
|
699
738
|
}
|
|
700
|
-
}
|
|
739
|
+
};
|
|
701
740
|
|
|
702
741
|
// src/core/IncrementalGenerator.ts
|
|
703
|
-
|
|
742
|
+
var IncrementalGenerator = class {
|
|
704
743
|
options;
|
|
705
744
|
changeTracker;
|
|
706
745
|
diffCalculator;
|
|
@@ -708,9 +747,12 @@ class IncrementalGenerator {
|
|
|
708
747
|
constructor(options) {
|
|
709
748
|
this.options = options;
|
|
710
749
|
this.changeTracker = options.changeTracker;
|
|
711
|
-
this.diffCalculator = options.diffCalculator || new DiffCalculator;
|
|
750
|
+
this.diffCalculator = options.diffCalculator || new DiffCalculator();
|
|
712
751
|
this.generator = new SitemapGenerator(options);
|
|
713
752
|
}
|
|
753
|
+
/**
|
|
754
|
+
* 生成完整的 sitemap(首次生成)
|
|
755
|
+
*/
|
|
714
756
|
async generateFull() {
|
|
715
757
|
await this.generator.run();
|
|
716
758
|
if (this.options.autoTrack) {
|
|
@@ -723,12 +765,15 @@ class IncrementalGenerator {
|
|
|
723
765
|
type: "add",
|
|
724
766
|
url: entry.url,
|
|
725
767
|
entry,
|
|
726
|
-
timestamp: new Date
|
|
768
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
727
769
|
});
|
|
728
770
|
}
|
|
729
771
|
}
|
|
730
772
|
}
|
|
731
773
|
}
|
|
774
|
+
/**
|
|
775
|
+
* 增量生成(只更新變更的部分)
|
|
776
|
+
*/
|
|
732
777
|
async generateIncremental(since) {
|
|
733
778
|
const changes = await this.changeTracker.getChanges(since);
|
|
734
779
|
if (changes.length === 0) {
|
|
@@ -738,12 +783,21 @@ class IncrementalGenerator {
|
|
|
738
783
|
const diff = this.diffCalculator.calculateFromChanges(baseEntries, changes);
|
|
739
784
|
await this.generateDiff(diff);
|
|
740
785
|
}
|
|
786
|
+
/**
|
|
787
|
+
* 手動追蹤變更
|
|
788
|
+
*/
|
|
741
789
|
async trackChange(change) {
|
|
742
790
|
await this.changeTracker.track(change);
|
|
743
791
|
}
|
|
792
|
+
/**
|
|
793
|
+
* 獲取變更記錄
|
|
794
|
+
*/
|
|
744
795
|
async getChanges(since) {
|
|
745
796
|
return this.changeTracker.getChanges(since);
|
|
746
797
|
}
|
|
798
|
+
/**
|
|
799
|
+
* 載入基礎 entries(從現有 sitemap)
|
|
800
|
+
*/
|
|
747
801
|
async loadBaseEntries() {
|
|
748
802
|
const entries = [];
|
|
749
803
|
const { providers } = this.options;
|
|
@@ -754,9 +808,15 @@ class IncrementalGenerator {
|
|
|
754
808
|
}
|
|
755
809
|
return entries;
|
|
756
810
|
}
|
|
811
|
+
/**
|
|
812
|
+
* 生成差異部分
|
|
813
|
+
*/
|
|
757
814
|
async generateDiff(_diff) {
|
|
758
815
|
await this.generator.run();
|
|
759
816
|
}
|
|
817
|
+
/**
|
|
818
|
+
* 將 AsyncIterable 轉換為陣列
|
|
819
|
+
*/
|
|
760
820
|
async toArray(iterable) {
|
|
761
821
|
const array = [];
|
|
762
822
|
for await (const item of iterable) {
|
|
@@ -764,17 +824,21 @@ class IncrementalGenerator {
|
|
|
764
824
|
}
|
|
765
825
|
return array;
|
|
766
826
|
}
|
|
767
|
-
}
|
|
827
|
+
};
|
|
828
|
+
|
|
768
829
|
// src/core/ProgressTracker.ts
|
|
769
|
-
|
|
830
|
+
var ProgressTracker = class {
|
|
770
831
|
storage;
|
|
771
832
|
updateInterval;
|
|
772
833
|
currentProgress = null;
|
|
773
834
|
updateTimer = null;
|
|
774
835
|
constructor(options) {
|
|
775
836
|
this.storage = options.storage;
|
|
776
|
-
this.updateInterval = options.updateInterval ||
|
|
837
|
+
this.updateInterval = options.updateInterval || 1e3;
|
|
777
838
|
}
|
|
839
|
+
/**
|
|
840
|
+
* 初始化進度追蹤
|
|
841
|
+
*/
|
|
778
842
|
async init(jobId, total) {
|
|
779
843
|
this.currentProgress = {
|
|
780
844
|
jobId,
|
|
@@ -782,10 +846,13 @@ class ProgressTracker {
|
|
|
782
846
|
total,
|
|
783
847
|
processed: 0,
|
|
784
848
|
percentage: 0,
|
|
785
|
-
startTime: new Date
|
|
849
|
+
startTime: /* @__PURE__ */ new Date()
|
|
786
850
|
};
|
|
787
851
|
await this.storage.set(jobId, this.currentProgress);
|
|
788
852
|
}
|
|
853
|
+
/**
|
|
854
|
+
* 更新進度
|
|
855
|
+
*/
|
|
789
856
|
async update(processed, status) {
|
|
790
857
|
if (!this.currentProgress) {
|
|
791
858
|
return;
|
|
@@ -805,26 +872,35 @@ class ProgressTracker {
|
|
|
805
872
|
}, this.updateInterval);
|
|
806
873
|
}
|
|
807
874
|
}
|
|
875
|
+
/**
|
|
876
|
+
* 完成進度追蹤
|
|
877
|
+
*/
|
|
808
878
|
async complete() {
|
|
809
879
|
if (!this.currentProgress) {
|
|
810
880
|
return;
|
|
811
881
|
}
|
|
812
882
|
this.currentProgress.status = "completed";
|
|
813
|
-
this.currentProgress.endTime = new Date;
|
|
883
|
+
this.currentProgress.endTime = /* @__PURE__ */ new Date();
|
|
814
884
|
this.currentProgress.percentage = 100;
|
|
815
885
|
await this.flush();
|
|
816
886
|
this.stop();
|
|
817
887
|
}
|
|
888
|
+
/**
|
|
889
|
+
* 標記為失敗
|
|
890
|
+
*/
|
|
818
891
|
async fail(error) {
|
|
819
892
|
if (!this.currentProgress) {
|
|
820
893
|
return;
|
|
821
894
|
}
|
|
822
895
|
this.currentProgress.status = "failed";
|
|
823
|
-
this.currentProgress.endTime = new Date;
|
|
896
|
+
this.currentProgress.endTime = /* @__PURE__ */ new Date();
|
|
824
897
|
this.currentProgress.error = error;
|
|
825
898
|
await this.flush();
|
|
826
899
|
this.stop();
|
|
827
900
|
}
|
|
901
|
+
/**
|
|
902
|
+
* 刷新進度到儲存
|
|
903
|
+
*/
|
|
828
904
|
async flush() {
|
|
829
905
|
if (!this.currentProgress) {
|
|
830
906
|
return;
|
|
@@ -837,19 +913,26 @@ class ProgressTracker {
|
|
|
837
913
|
error: this.currentProgress.error
|
|
838
914
|
});
|
|
839
915
|
}
|
|
916
|
+
/**
|
|
917
|
+
* 停止更新計時器
|
|
918
|
+
*/
|
|
840
919
|
stop() {
|
|
841
920
|
if (this.updateTimer) {
|
|
842
921
|
clearInterval(this.updateTimer);
|
|
843
922
|
this.updateTimer = null;
|
|
844
923
|
}
|
|
845
924
|
}
|
|
925
|
+
/**
|
|
926
|
+
* 獲取當前進度
|
|
927
|
+
*/
|
|
846
928
|
getCurrentProgress() {
|
|
847
929
|
return this.currentProgress ? { ...this.currentProgress } : null;
|
|
848
930
|
}
|
|
849
|
-
}
|
|
931
|
+
};
|
|
932
|
+
|
|
850
933
|
// src/helpers/I18nSitemap.ts
|
|
851
|
-
function generateI18nEntries(
|
|
852
|
-
const cleanPath =
|
|
934
|
+
function generateI18nEntries(path2, locales, baseUrl = "", options = {}) {
|
|
935
|
+
const cleanPath = path2.startsWith("/") ? path2 : `/${path2}`;
|
|
853
936
|
const cleanBaseUrl = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
|
|
854
937
|
const alternates = locales.map((locale) => {
|
|
855
938
|
return {
|
|
@@ -865,9 +948,10 @@ function generateI18nEntries(path, locales, baseUrl = "", options = {}) {
|
|
|
865
948
|
};
|
|
866
949
|
});
|
|
867
950
|
}
|
|
951
|
+
|
|
868
952
|
// src/jobs/GenerateSitemapJob.ts
|
|
869
953
|
var import_stream = require("@gravito/stream");
|
|
870
|
-
|
|
954
|
+
var GenerateSitemapJob = class extends import_stream.Job {
|
|
871
955
|
options;
|
|
872
956
|
generator;
|
|
873
957
|
totalEntries = 0;
|
|
@@ -906,6 +990,9 @@ class GenerateSitemapJob extends import_stream.Job {
|
|
|
906
990
|
throw err;
|
|
907
991
|
}
|
|
908
992
|
}
|
|
993
|
+
/**
|
|
994
|
+
* 計算總 URL 數
|
|
995
|
+
*/
|
|
909
996
|
async calculateTotal() {
|
|
910
997
|
let total = 0;
|
|
911
998
|
const { providers } = this.options.generatorOptions;
|
|
@@ -921,11 +1008,14 @@ class GenerateSitemapJob extends import_stream.Job {
|
|
|
921
1008
|
}
|
|
922
1009
|
return total;
|
|
923
1010
|
}
|
|
1011
|
+
/**
|
|
1012
|
+
* 帶進度追蹤的生成
|
|
1013
|
+
*/
|
|
924
1014
|
async generateWithProgress() {
|
|
925
1015
|
const { progressTracker, shadowProcessor, onProgress } = this.options;
|
|
926
1016
|
const {
|
|
927
1017
|
providers,
|
|
928
|
-
maxEntriesPerFile =
|
|
1018
|
+
maxEntriesPerFile = 5e4,
|
|
929
1019
|
storage,
|
|
930
1020
|
baseUrl,
|
|
931
1021
|
pretty,
|
|
@@ -944,20 +1034,24 @@ class GenerateSitemapJob extends import_stream.Job {
|
|
|
944
1034
|
});
|
|
945
1035
|
}
|
|
946
1036
|
}
|
|
947
|
-
}
|
|
1037
|
+
};
|
|
1038
|
+
|
|
948
1039
|
// src/OrbitSitemap.ts
|
|
949
|
-
var import_node_crypto = require("
|
|
1040
|
+
var import_node_crypto = require("crypto");
|
|
950
1041
|
|
|
951
1042
|
// src/redirect/RedirectHandler.ts
|
|
952
|
-
|
|
1043
|
+
var RedirectHandler = class {
|
|
953
1044
|
options;
|
|
954
1045
|
constructor(options) {
|
|
955
1046
|
this.options = options;
|
|
956
1047
|
}
|
|
1048
|
+
/**
|
|
1049
|
+
* 處理 entries 中的轉址
|
|
1050
|
+
*/
|
|
957
1051
|
async processEntries(entries) {
|
|
958
1052
|
const { manager, strategy, followChains, maxChainLength } = this.options;
|
|
959
1053
|
const _processedEntries = [];
|
|
960
|
-
const redirectMap = new Map;
|
|
1054
|
+
const redirectMap = /* @__PURE__ */ new Map();
|
|
961
1055
|
for (const entry of entries) {
|
|
962
1056
|
const redirectTarget = await manager.resolve(entry.url, followChains, maxChainLength);
|
|
963
1057
|
if (redirectTarget && entry.url !== redirectTarget) {
|
|
@@ -965,6 +1059,7 @@ class RedirectHandler {
|
|
|
965
1059
|
from: entry.url,
|
|
966
1060
|
to: redirectTarget,
|
|
967
1061
|
type: 301
|
|
1062
|
+
// Default to 301 for resolved chains
|
|
968
1063
|
});
|
|
969
1064
|
}
|
|
970
1065
|
}
|
|
@@ -981,9 +1076,12 @@ class RedirectHandler {
|
|
|
981
1076
|
return entries;
|
|
982
1077
|
}
|
|
983
1078
|
}
|
|
1079
|
+
/**
|
|
1080
|
+
* 策略一:移除舊 URL,加入新 URL
|
|
1081
|
+
*/
|
|
984
1082
|
handleRemoveOldAddNew(entries, redirectMap) {
|
|
985
1083
|
const processed = [];
|
|
986
|
-
const redirectedUrls = new Set;
|
|
1084
|
+
const redirectedUrls = /* @__PURE__ */ new Set();
|
|
987
1085
|
for (const entry of entries) {
|
|
988
1086
|
const redirect = redirectMap.get(entry.url);
|
|
989
1087
|
if (redirect) {
|
|
@@ -1003,6 +1101,9 @@ class RedirectHandler {
|
|
|
1003
1101
|
}
|
|
1004
1102
|
return processed;
|
|
1005
1103
|
}
|
|
1104
|
+
/**
|
|
1105
|
+
* 策略二:保留關聯,使用 canonical link
|
|
1106
|
+
*/
|
|
1006
1107
|
handleKeepRelation(entries, redirectMap) {
|
|
1007
1108
|
const processed = [];
|
|
1008
1109
|
for (const entry of entries) {
|
|
@@ -1023,6 +1124,9 @@ class RedirectHandler {
|
|
|
1023
1124
|
}
|
|
1024
1125
|
return processed;
|
|
1025
1126
|
}
|
|
1127
|
+
/**
|
|
1128
|
+
* 策略三:僅更新 URL
|
|
1129
|
+
*/
|
|
1026
1130
|
handleUpdateUrl(entries, redirectMap) {
|
|
1027
1131
|
return entries.map((entry) => {
|
|
1028
1132
|
const redirect = redirectMap.get(entry.url);
|
|
@@ -1040,9 +1144,12 @@ class RedirectHandler {
|
|
|
1040
1144
|
return entry;
|
|
1041
1145
|
});
|
|
1042
1146
|
}
|
|
1147
|
+
/**
|
|
1148
|
+
* 策略四:雙重標記
|
|
1149
|
+
*/
|
|
1043
1150
|
handleDualMark(entries, redirectMap) {
|
|
1044
1151
|
const processed = [];
|
|
1045
|
-
const addedUrls = new Set;
|
|
1152
|
+
const addedUrls = /* @__PURE__ */ new Set();
|
|
1046
1153
|
for (const entry of entries) {
|
|
1047
1154
|
const redirect = redirectMap.get(entry.url);
|
|
1048
1155
|
if (redirect) {
|
|
@@ -1072,15 +1179,14 @@ class RedirectHandler {
|
|
|
1072
1179
|
}
|
|
1073
1180
|
return processed;
|
|
1074
1181
|
}
|
|
1075
|
-
}
|
|
1182
|
+
};
|
|
1076
1183
|
|
|
1077
1184
|
// src/storage/MemorySitemapStorage.ts
|
|
1078
|
-
|
|
1079
|
-
baseUrl;
|
|
1080
|
-
files = new Map;
|
|
1185
|
+
var MemorySitemapStorage = class {
|
|
1081
1186
|
constructor(baseUrl) {
|
|
1082
1187
|
this.baseUrl = baseUrl;
|
|
1083
1188
|
}
|
|
1189
|
+
files = /* @__PURE__ */ new Map();
|
|
1084
1190
|
async write(filename, content) {
|
|
1085
1191
|
this.files.set(filename, content);
|
|
1086
1192
|
}
|
|
@@ -1095,28 +1201,60 @@ class MemorySitemapStorage {
|
|
|
1095
1201
|
const file = filename.startsWith("/") ? filename.slice(1) : filename;
|
|
1096
1202
|
return `${base}/${file}`;
|
|
1097
1203
|
}
|
|
1098
|
-
}
|
|
1204
|
+
};
|
|
1099
1205
|
|
|
1100
1206
|
// src/OrbitSitemap.ts
|
|
1101
|
-
|
|
1207
|
+
function sanitizeFilename2(value) {
|
|
1208
|
+
if (!value) {
|
|
1209
|
+
return null;
|
|
1210
|
+
}
|
|
1211
|
+
if (value.includes("\0")) {
|
|
1212
|
+
return null;
|
|
1213
|
+
}
|
|
1214
|
+
if (value.includes("/") || value.includes("\\")) {
|
|
1215
|
+
return null;
|
|
1216
|
+
}
|
|
1217
|
+
if (value.includes("..")) {
|
|
1218
|
+
return null;
|
|
1219
|
+
}
|
|
1220
|
+
return value;
|
|
1221
|
+
}
|
|
1222
|
+
var OrbitSitemap = class _OrbitSitemap {
|
|
1102
1223
|
options;
|
|
1103
1224
|
mode;
|
|
1104
1225
|
constructor(mode, options) {
|
|
1105
1226
|
this.mode = mode;
|
|
1106
1227
|
this.options = options;
|
|
1107
1228
|
}
|
|
1229
|
+
/**
|
|
1230
|
+
* Create a dynamic sitemap configuration.
|
|
1231
|
+
*
|
|
1232
|
+
* @param options - The dynamic sitemap options.
|
|
1233
|
+
* @returns An OrbitSitemap instance configured for dynamic generation.
|
|
1234
|
+
*/
|
|
1108
1235
|
static dynamic(options) {
|
|
1109
|
-
return new
|
|
1236
|
+
return new _OrbitSitemap("dynamic", {
|
|
1110
1237
|
path: "/sitemap.xml",
|
|
1111
1238
|
...options
|
|
1112
1239
|
});
|
|
1113
1240
|
}
|
|
1241
|
+
/**
|
|
1242
|
+
* Create a static sitemap configuration.
|
|
1243
|
+
*
|
|
1244
|
+
* @param options - The static sitemap options.
|
|
1245
|
+
* @returns An OrbitSitemap instance configured for static generation.
|
|
1246
|
+
*/
|
|
1114
1247
|
static static(options) {
|
|
1115
|
-
return new
|
|
1248
|
+
return new _OrbitSitemap("static", {
|
|
1116
1249
|
filename: "sitemap.xml",
|
|
1117
1250
|
...options
|
|
1118
1251
|
});
|
|
1119
1252
|
}
|
|
1253
|
+
/**
|
|
1254
|
+
* Install the sitemap module into PlanetCore.
|
|
1255
|
+
*
|
|
1256
|
+
* @param core - The PlanetCore instance.
|
|
1257
|
+
*/
|
|
1120
1258
|
install(core) {
|
|
1121
1259
|
if (this.mode === "dynamic") {
|
|
1122
1260
|
this.installDynamic(core);
|
|
@@ -1128,10 +1266,14 @@ class OrbitSitemap {
|
|
|
1128
1266
|
const opts = this.options;
|
|
1129
1267
|
const storage = opts.storage ?? new MemorySitemapStorage(opts.baseUrl);
|
|
1130
1268
|
const indexFilename = opts.path?.split("/").pop() ?? "sitemap.xml";
|
|
1131
|
-
const baseDir = opts.path ? opts.path.substring(0, opts.path.lastIndexOf("/")) :
|
|
1269
|
+
const baseDir = opts.path ? opts.path.substring(0, opts.path.lastIndexOf("/")) : void 0;
|
|
1132
1270
|
const handler = async (ctx) => {
|
|
1133
1271
|
const reqPath = ctx.req.path;
|
|
1134
|
-
const
|
|
1272
|
+
const rawName = reqPath.split("/").pop() || indexFilename;
|
|
1273
|
+
const filename = sanitizeFilename2(rawName);
|
|
1274
|
+
if (!filename) {
|
|
1275
|
+
return ctx.text("Not Found", 404);
|
|
1276
|
+
}
|
|
1135
1277
|
const isIndex = filename === indexFilename;
|
|
1136
1278
|
let content = await storage.read(filename);
|
|
1137
1279
|
if (!content && isIndex) {
|
|
@@ -1176,6 +1318,12 @@ class OrbitSitemap {
|
|
|
1176
1318
|
const shardRoute = `${baseDir}/${basename}-:shard.xml`;
|
|
1177
1319
|
core.router.get(shardRoute, handler);
|
|
1178
1320
|
}
|
|
1321
|
+
/**
|
|
1322
|
+
* Generate the sitemap (static mode only).
|
|
1323
|
+
*
|
|
1324
|
+
* @returns A promise that resolves when generation is complete.
|
|
1325
|
+
* @throws {Error} If called in dynamic mode.
|
|
1326
|
+
*/
|
|
1179
1327
|
async generate() {
|
|
1180
1328
|
if (this.mode !== "static") {
|
|
1181
1329
|
throw new Error("generate() can only be called in static mode");
|
|
@@ -1183,7 +1331,7 @@ class OrbitSitemap {
|
|
|
1183
1331
|
const opts = this.options;
|
|
1184
1332
|
let storage = opts.storage;
|
|
1185
1333
|
if (!storage) {
|
|
1186
|
-
const { DiskSitemapStorage: DiskSitemapStorage2 } = await Promise.resolve().then(() => (init_DiskSitemapStorage(),
|
|
1334
|
+
const { DiskSitemapStorage: DiskSitemapStorage2 } = await Promise.resolve().then(() => (init_DiskSitemapStorage(), DiskSitemapStorage_exports));
|
|
1187
1335
|
storage = new DiskSitemapStorage2(opts.outDir, opts.baseUrl);
|
|
1188
1336
|
}
|
|
1189
1337
|
let providers = opts.providers;
|
|
@@ -1212,6 +1360,13 @@ class OrbitSitemap {
|
|
|
1212
1360
|
await generator.run();
|
|
1213
1361
|
console.log(`[OrbitSitemap] Generated sitemap in ${opts.outDir}`);
|
|
1214
1362
|
}
|
|
1363
|
+
/**
|
|
1364
|
+
* Generate incremental sitemap updates (static mode only).
|
|
1365
|
+
*
|
|
1366
|
+
* @param since - Only include items modified since this date.
|
|
1367
|
+
* @returns A promise that resolves when incremental generation is complete.
|
|
1368
|
+
* @throws {Error} If called in dynamic mode, or if incremental generation is not enabled/configured.
|
|
1369
|
+
*/
|
|
1215
1370
|
async generateIncremental(since) {
|
|
1216
1371
|
if (this.mode !== "static") {
|
|
1217
1372
|
throw new Error("generateIncremental() can only be called in static mode");
|
|
@@ -1222,7 +1377,7 @@ class OrbitSitemap {
|
|
|
1222
1377
|
}
|
|
1223
1378
|
let storage = opts.storage;
|
|
1224
1379
|
if (!storage) {
|
|
1225
|
-
const { DiskSitemapStorage: DiskSitemapStorage2 } = await Promise.resolve().then(() => (init_DiskSitemapStorage(),
|
|
1380
|
+
const { DiskSitemapStorage: DiskSitemapStorage2 } = await Promise.resolve().then(() => (init_DiskSitemapStorage(), DiskSitemapStorage_exports));
|
|
1226
1381
|
storage = new DiskSitemapStorage2(opts.outDir, opts.baseUrl);
|
|
1227
1382
|
}
|
|
1228
1383
|
const incrementalGenerator = new IncrementalGenerator({
|
|
@@ -1236,15 +1391,22 @@ class OrbitSitemap {
|
|
|
1236
1391
|
await incrementalGenerator.generateIncremental(since);
|
|
1237
1392
|
console.log(`[OrbitSitemap] Generated incremental sitemap in ${opts.outDir}`);
|
|
1238
1393
|
}
|
|
1394
|
+
/**
|
|
1395
|
+
* Generate sitemap asynchronously in the background (static mode only).
|
|
1396
|
+
*
|
|
1397
|
+
* @param options - Options for the async generation job.
|
|
1398
|
+
* @returns A promise resolving to the job ID.
|
|
1399
|
+
* @throws {Error} If called in dynamic mode.
|
|
1400
|
+
*/
|
|
1239
1401
|
async generateAsync(options) {
|
|
1240
1402
|
if (this.mode !== "static") {
|
|
1241
1403
|
throw new Error("generateAsync() can only be called in static mode");
|
|
1242
1404
|
}
|
|
1243
1405
|
const opts = this.options;
|
|
1244
|
-
const jobId = import_node_crypto.randomUUID();
|
|
1406
|
+
const jobId = (0, import_node_crypto.randomUUID)();
|
|
1245
1407
|
let storage = opts.storage;
|
|
1246
1408
|
if (!storage) {
|
|
1247
|
-
const { DiskSitemapStorage: DiskSitemapStorage2 } = await Promise.resolve().then(() => (init_DiskSitemapStorage(),
|
|
1409
|
+
const { DiskSitemapStorage: DiskSitemapStorage2 } = await Promise.resolve().then(() => (init_DiskSitemapStorage(), DiskSitemapStorage_exports));
|
|
1248
1410
|
storage = new DiskSitemapStorage2(opts.outDir, opts.baseUrl);
|
|
1249
1411
|
}
|
|
1250
1412
|
let providers = opts.providers;
|
|
@@ -1290,6 +1452,12 @@ class OrbitSitemap {
|
|
|
1290
1452
|
});
|
|
1291
1453
|
return jobId;
|
|
1292
1454
|
}
|
|
1455
|
+
/**
|
|
1456
|
+
* Install API endpoints for triggering and monitoring sitemap generation.
|
|
1457
|
+
*
|
|
1458
|
+
* @param core - The PlanetCore instance.
|
|
1459
|
+
* @param basePath - The base path for the API endpoints (default: '/admin/sitemap').
|
|
1460
|
+
*/
|
|
1293
1461
|
installApiEndpoints(core, basePath = "/admin/sitemap") {
|
|
1294
1462
|
const opts = this.options;
|
|
1295
1463
|
core.router.post(`${basePath}/generate`, async (ctx) => {
|
|
@@ -1297,7 +1465,7 @@ class OrbitSitemap {
|
|
|
1297
1465
|
const body = await ctx.req.json().catch(() => ({}));
|
|
1298
1466
|
const jobId = await this.generateAsync({
|
|
1299
1467
|
incremental: body.incremental,
|
|
1300
|
-
since: body.since ? new Date(body.since) :
|
|
1468
|
+
since: body.since ? new Date(body.since) : void 0
|
|
1301
1469
|
});
|
|
1302
1470
|
return ctx.json({ jobId, status: "started" });
|
|
1303
1471
|
} catch (error) {
|
|
@@ -1327,6 +1495,9 @@ class OrbitSitemap {
|
|
|
1327
1495
|
return ctx.json(history);
|
|
1328
1496
|
});
|
|
1329
1497
|
}
|
|
1498
|
+
/**
|
|
1499
|
+
* Convert an AsyncIterable to an array.
|
|
1500
|
+
*/
|
|
1330
1501
|
async toArray(iterable) {
|
|
1331
1502
|
const array = [];
|
|
1332
1503
|
for await (const item of iterable) {
|
|
@@ -1334,16 +1505,17 @@ class OrbitSitemap {
|
|
|
1334
1505
|
}
|
|
1335
1506
|
return array;
|
|
1336
1507
|
}
|
|
1337
|
-
}
|
|
1508
|
+
};
|
|
1509
|
+
|
|
1338
1510
|
// src/providers/RouteScanner.ts
|
|
1339
1511
|
function matchGlob(str, pattern) {
|
|
1340
1512
|
const regexPattern = pattern.replace(/\./g, "\\.").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
1341
1513
|
const regex = new RegExp(`^${regexPattern}$`);
|
|
1342
1514
|
return regex.test(str);
|
|
1343
1515
|
}
|
|
1344
|
-
|
|
1345
|
-
class RouteScanner {
|
|
1516
|
+
var RouteScanner = class {
|
|
1346
1517
|
router;
|
|
1518
|
+
// Using any to key access internal routes
|
|
1347
1519
|
options;
|
|
1348
1520
|
constructor(router, options = {}) {
|
|
1349
1521
|
this.router = router;
|
|
@@ -1400,17 +1572,21 @@ class RouteScanner {
|
|
|
1400
1572
|
}
|
|
1401
1573
|
return true;
|
|
1402
1574
|
}
|
|
1403
|
-
}
|
|
1575
|
+
};
|
|
1404
1576
|
function routeScanner(router, options) {
|
|
1405
1577
|
return new RouteScanner(router, options);
|
|
1406
1578
|
}
|
|
1579
|
+
|
|
1407
1580
|
// src/redirect/RedirectDetector.ts
|
|
1408
|
-
|
|
1581
|
+
var RedirectDetector = class {
|
|
1409
1582
|
options;
|
|
1410
|
-
cache = new Map;
|
|
1583
|
+
cache = /* @__PURE__ */ new Map();
|
|
1411
1584
|
constructor(options) {
|
|
1412
1585
|
this.options = options;
|
|
1413
1586
|
}
|
|
1587
|
+
/**
|
|
1588
|
+
* 偵測單一 URL 的轉址
|
|
1589
|
+
*/
|
|
1414
1590
|
async detect(url) {
|
|
1415
1591
|
if (this.options.autoDetect?.cache) {
|
|
1416
1592
|
const cached = this.cache.get(url);
|
|
@@ -1440,21 +1616,29 @@ class RedirectDetector {
|
|
|
1440
1616
|
}
|
|
1441
1617
|
return null;
|
|
1442
1618
|
}
|
|
1619
|
+
/**
|
|
1620
|
+
* 批次偵測轉址
|
|
1621
|
+
*/
|
|
1443
1622
|
async detectBatch(urls) {
|
|
1444
|
-
const results = new Map;
|
|
1623
|
+
const results = /* @__PURE__ */ new Map();
|
|
1445
1624
|
const maxConcurrent = this.options.autoDetect?.maxConcurrent || 10;
|
|
1446
1625
|
const batches = [];
|
|
1447
|
-
for (let i = 0;i < urls.length; i += maxConcurrent) {
|
|
1626
|
+
for (let i = 0; i < urls.length; i += maxConcurrent) {
|
|
1448
1627
|
batches.push(urls.slice(i, i + maxConcurrent));
|
|
1449
1628
|
}
|
|
1450
1629
|
for (const batch of batches) {
|
|
1451
|
-
const promises = batch.map(
|
|
1452
|
-
|
|
1453
|
-
|
|
1630
|
+
const promises = batch.map(
|
|
1631
|
+
(url) => this.detect(url).then((rule) => {
|
|
1632
|
+
results.set(url, rule);
|
|
1633
|
+
})
|
|
1634
|
+
);
|
|
1454
1635
|
await Promise.all(promises);
|
|
1455
1636
|
}
|
|
1456
1637
|
return results;
|
|
1457
1638
|
}
|
|
1639
|
+
/**
|
|
1640
|
+
* 從資料庫偵測
|
|
1641
|
+
*/
|
|
1458
1642
|
async detectFromDatabase(url) {
|
|
1459
1643
|
const { database } = this.options;
|
|
1460
1644
|
if (!database?.enabled) {
|
|
@@ -1477,13 +1661,16 @@ class RedirectDetector {
|
|
|
1477
1661
|
return null;
|
|
1478
1662
|
}
|
|
1479
1663
|
}
|
|
1664
|
+
/**
|
|
1665
|
+
* 從設定檔偵測
|
|
1666
|
+
*/
|
|
1480
1667
|
async detectFromConfig(url) {
|
|
1481
1668
|
const { config } = this.options;
|
|
1482
1669
|
if (!config?.enabled) {
|
|
1483
1670
|
return null;
|
|
1484
1671
|
}
|
|
1485
1672
|
try {
|
|
1486
|
-
const fs2 = await import("
|
|
1673
|
+
const fs2 = await import("fs/promises");
|
|
1487
1674
|
const data = await fs2.readFile(config.path, "utf-8");
|
|
1488
1675
|
const redirects = JSON.parse(data);
|
|
1489
1676
|
const rule = redirects.find((r) => r.from === url);
|
|
@@ -1492,6 +1679,9 @@ class RedirectDetector {
|
|
|
1492
1679
|
return null;
|
|
1493
1680
|
}
|
|
1494
1681
|
}
|
|
1682
|
+
/**
|
|
1683
|
+
* 自動偵測(透過 HTTP 請求)
|
|
1684
|
+
*/
|
|
1495
1685
|
async detectAuto(url) {
|
|
1496
1686
|
const { autoDetect, baseUrl } = this.options;
|
|
1497
1687
|
if (!autoDetect?.enabled) {
|
|
@@ -1499,14 +1689,15 @@ class RedirectDetector {
|
|
|
1499
1689
|
}
|
|
1500
1690
|
try {
|
|
1501
1691
|
const fullUrl = url.startsWith("http") ? url : `${baseUrl}${url}`;
|
|
1502
|
-
const timeout = autoDetect.timeout ||
|
|
1503
|
-
const controller = new AbortController;
|
|
1692
|
+
const timeout = autoDetect.timeout || 5e3;
|
|
1693
|
+
const controller = new AbortController();
|
|
1504
1694
|
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
1505
1695
|
try {
|
|
1506
1696
|
const response = await fetch(fullUrl, {
|
|
1507
1697
|
method: "HEAD",
|
|
1508
1698
|
signal: controller.signal,
|
|
1509
1699
|
redirect: "manual"
|
|
1700
|
+
// 手動處理轉址
|
|
1510
1701
|
});
|
|
1511
1702
|
clearTimeout(timeoutId);
|
|
1512
1703
|
if (response.status === 301 || response.status === 302) {
|
|
@@ -1525,23 +1716,28 @@ class RedirectDetector {
|
|
|
1525
1716
|
throw error;
|
|
1526
1717
|
}
|
|
1527
1718
|
}
|
|
1528
|
-
} catch {
|
|
1719
|
+
} catch {
|
|
1720
|
+
}
|
|
1529
1721
|
return null;
|
|
1530
1722
|
}
|
|
1723
|
+
/**
|
|
1724
|
+
* 快取結果
|
|
1725
|
+
*/
|
|
1531
1726
|
cacheResult(url, rule) {
|
|
1532
1727
|
if (!this.options.autoDetect?.cache) {
|
|
1533
1728
|
return;
|
|
1534
1729
|
}
|
|
1535
|
-
const ttl = (this.options.autoDetect.cacheTtl || 3600) *
|
|
1730
|
+
const ttl = (this.options.autoDetect.cacheTtl || 3600) * 1e3;
|
|
1536
1731
|
this.cache.set(url, {
|
|
1537
1732
|
rule,
|
|
1538
1733
|
expires: Date.now() + ttl
|
|
1539
1734
|
});
|
|
1540
1735
|
}
|
|
1541
|
-
}
|
|
1736
|
+
};
|
|
1737
|
+
|
|
1542
1738
|
// src/redirect/RedirectManager.ts
|
|
1543
|
-
|
|
1544
|
-
rules = new Map;
|
|
1739
|
+
var MemoryRedirectManager = class {
|
|
1740
|
+
rules = /* @__PURE__ */ new Map();
|
|
1545
1741
|
maxRules;
|
|
1546
1742
|
constructor(options = {}) {
|
|
1547
1743
|
this.maxRules = options.maxRules || 1e5;
|
|
@@ -1582,9 +1778,8 @@ class MemoryRedirectManager {
|
|
|
1582
1778
|
}
|
|
1583
1779
|
return current;
|
|
1584
1780
|
}
|
|
1585
|
-
}
|
|
1586
|
-
|
|
1587
|
-
class RedisRedirectManager {
|
|
1781
|
+
};
|
|
1782
|
+
var RedisRedirectManager = class {
|
|
1588
1783
|
client;
|
|
1589
1784
|
keyPrefix;
|
|
1590
1785
|
ttl;
|
|
@@ -1663,19 +1858,20 @@ class RedisRedirectManager {
|
|
|
1663
1858
|
}
|
|
1664
1859
|
return current;
|
|
1665
1860
|
}
|
|
1666
|
-
}
|
|
1861
|
+
};
|
|
1667
1862
|
|
|
1668
1863
|
// src/index.ts
|
|
1669
1864
|
init_DiskSitemapStorage();
|
|
1670
1865
|
|
|
1671
1866
|
// src/storage/GCPSitemapStorage.ts
|
|
1672
|
-
|
|
1867
|
+
var GCPSitemapStorage = class {
|
|
1673
1868
|
bucket;
|
|
1674
1869
|
prefix;
|
|
1675
1870
|
baseUrl;
|
|
1676
1871
|
shadowEnabled;
|
|
1677
1872
|
shadowMode;
|
|
1678
1873
|
storageClient;
|
|
1874
|
+
// 動態載入 @google-cloud/storage
|
|
1679
1875
|
bucketInstance;
|
|
1680
1876
|
constructor(options) {
|
|
1681
1877
|
this.bucket = options.bucket;
|
|
@@ -1691,12 +1887,15 @@ class GCPSitemapStorage {
|
|
|
1691
1887
|
try {
|
|
1692
1888
|
const { Storage } = await import("@google-cloud/storage");
|
|
1693
1889
|
const clientOptions = {};
|
|
1694
|
-
if (this.constructor.name === "GCPSitemapStorage") {
|
|
1890
|
+
if (this.constructor.name === "GCPSitemapStorage") {
|
|
1891
|
+
}
|
|
1695
1892
|
this.storageClient = new Storage(clientOptions);
|
|
1696
1893
|
this.bucketInstance = this.storageClient.bucket(this.bucket);
|
|
1697
1894
|
return { client: this.storageClient, bucket: this.bucketInstance };
|
|
1698
1895
|
} catch (error) {
|
|
1699
|
-
throw new Error(
|
|
1896
|
+
throw new Error(
|
|
1897
|
+
`Failed to load Google Cloud Storage. Please install @google-cloud/storage: ${error instanceof Error ? error.message : String(error)}`
|
|
1898
|
+
);
|
|
1700
1899
|
}
|
|
1701
1900
|
}
|
|
1702
1901
|
getKey(filename) {
|
|
@@ -1748,6 +1947,7 @@ class GCPSitemapStorage {
|
|
|
1748
1947
|
const base = this.baseUrl.endsWith("/") ? this.baseUrl.slice(0, -1) : this.baseUrl;
|
|
1749
1948
|
return `${base}/${key}`;
|
|
1750
1949
|
}
|
|
1950
|
+
// 影子處理方法
|
|
1751
1951
|
async writeShadow(filename, content, shadowId) {
|
|
1752
1952
|
if (!this.shadowEnabled) {
|
|
1753
1953
|
return this.write(filename, content);
|
|
@@ -1824,10 +2024,11 @@ class GCPSitemapStorage {
|
|
|
1824
2024
|
}
|
|
1825
2025
|
await versionedFile.copy(bucket.file(key));
|
|
1826
2026
|
}
|
|
1827
|
-
}
|
|
2027
|
+
};
|
|
2028
|
+
|
|
1828
2029
|
// src/storage/MemoryProgressStorage.ts
|
|
1829
|
-
|
|
1830
|
-
storage = new Map;
|
|
2030
|
+
var MemoryProgressStorage = class {
|
|
2031
|
+
storage = /* @__PURE__ */ new Map();
|
|
1831
2032
|
async get(jobId) {
|
|
1832
2033
|
const progress = this.storage.get(jobId);
|
|
1833
2034
|
return progress ? { ...progress } : null;
|
|
@@ -1853,9 +2054,10 @@ class MemoryProgressStorage {
|
|
|
1853
2054
|
});
|
|
1854
2055
|
return limit ? sorted.slice(0, limit) : sorted;
|
|
1855
2056
|
}
|
|
1856
|
-
}
|
|
2057
|
+
};
|
|
2058
|
+
|
|
1857
2059
|
// src/storage/RedisProgressStorage.ts
|
|
1858
|
-
|
|
2060
|
+
var RedisProgressStorage = class {
|
|
1859
2061
|
client;
|
|
1860
2062
|
keyPrefix;
|
|
1861
2063
|
ttl;
|
|
@@ -1925,9 +2127,10 @@ class RedisProgressStorage {
|
|
|
1925
2127
|
return [];
|
|
1926
2128
|
}
|
|
1927
2129
|
}
|
|
1928
|
-
}
|
|
2130
|
+
};
|
|
2131
|
+
|
|
1929
2132
|
// src/storage/S3SitemapStorage.ts
|
|
1930
|
-
|
|
2133
|
+
var S3SitemapStorage = class {
|
|
1931
2134
|
bucket;
|
|
1932
2135
|
region;
|
|
1933
2136
|
prefix;
|
|
@@ -1935,6 +2138,7 @@ class S3SitemapStorage {
|
|
|
1935
2138
|
shadowEnabled;
|
|
1936
2139
|
shadowMode;
|
|
1937
2140
|
s3Client;
|
|
2141
|
+
// 動態載入 @aws-sdk/client-s3
|
|
1938
2142
|
constructor(options) {
|
|
1939
2143
|
this.bucket = options.bucket;
|
|
1940
2144
|
this.region = options.region || "us-east-1";
|
|
@@ -1960,7 +2164,8 @@ class S3SitemapStorage {
|
|
|
1960
2164
|
const clientOptions = {
|
|
1961
2165
|
region: this.region
|
|
1962
2166
|
};
|
|
1963
|
-
if (this.constructor.name === "S3SitemapStorage") {
|
|
2167
|
+
if (this.constructor.name === "S3SitemapStorage") {
|
|
2168
|
+
}
|
|
1964
2169
|
this.s3Client = {
|
|
1965
2170
|
S3Client,
|
|
1966
2171
|
PutObjectCommand,
|
|
@@ -1973,7 +2178,9 @@ class S3SitemapStorage {
|
|
|
1973
2178
|
};
|
|
1974
2179
|
return this.s3Client;
|
|
1975
2180
|
} catch (error) {
|
|
1976
|
-
throw new Error(
|
|
2181
|
+
throw new Error(
|
|
2182
|
+
`Failed to load AWS SDK. Please install @aws-sdk/client-s3: ${error instanceof Error ? error.message : String(error)}`
|
|
2183
|
+
);
|
|
1977
2184
|
}
|
|
1978
2185
|
}
|
|
1979
2186
|
getKey(filename) {
|
|
@@ -1983,21 +2190,25 @@ class S3SitemapStorage {
|
|
|
1983
2190
|
async write(filename, content) {
|
|
1984
2191
|
const s3 = await this.getS3Client();
|
|
1985
2192
|
const key = this.getKey(filename);
|
|
1986
|
-
await s3.client.send(
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
2193
|
+
await s3.client.send(
|
|
2194
|
+
new s3.PutObjectCommand({
|
|
2195
|
+
Bucket: this.bucket,
|
|
2196
|
+
Key: key,
|
|
2197
|
+
Body: content,
|
|
2198
|
+
ContentType: "application/xml"
|
|
2199
|
+
})
|
|
2200
|
+
);
|
|
1992
2201
|
}
|
|
1993
2202
|
async read(filename) {
|
|
1994
2203
|
try {
|
|
1995
2204
|
const s3 = await this.getS3Client();
|
|
1996
2205
|
const key = this.getKey(filename);
|
|
1997
|
-
const response = await s3.client.send(
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2206
|
+
const response = await s3.client.send(
|
|
2207
|
+
new s3.GetObjectCommand({
|
|
2208
|
+
Bucket: this.bucket,
|
|
2209
|
+
Key: key
|
|
2210
|
+
})
|
|
2211
|
+
);
|
|
2001
2212
|
if (!response.Body) {
|
|
2002
2213
|
return null;
|
|
2003
2214
|
}
|
|
@@ -2018,10 +2229,12 @@ class S3SitemapStorage {
|
|
|
2018
2229
|
try {
|
|
2019
2230
|
const s3 = await this.getS3Client();
|
|
2020
2231
|
const key = this.getKey(filename);
|
|
2021
|
-
await s3.client.send(
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2232
|
+
await s3.client.send(
|
|
2233
|
+
new s3.HeadObjectCommand({
|
|
2234
|
+
Bucket: this.bucket,
|
|
2235
|
+
Key: key
|
|
2236
|
+
})
|
|
2237
|
+
);
|
|
2025
2238
|
return true;
|
|
2026
2239
|
} catch (error) {
|
|
2027
2240
|
if (error.name === "NotFound" || error.$metadata?.httpStatusCode === 404) {
|
|
@@ -2035,6 +2248,7 @@ class S3SitemapStorage {
|
|
|
2035
2248
|
const base = this.baseUrl.endsWith("/") ? this.baseUrl.slice(0, -1) : this.baseUrl;
|
|
2036
2249
|
return `${base}/${key}`;
|
|
2037
2250
|
}
|
|
2251
|
+
// 影子處理方法
|
|
2038
2252
|
async writeShadow(filename, content, shadowId) {
|
|
2039
2253
|
if (!this.shadowEnabled) {
|
|
2040
2254
|
return this.write(filename, content);
|
|
@@ -2042,12 +2256,14 @@ class S3SitemapStorage {
|
|
|
2042
2256
|
const s3 = await this.getS3Client();
|
|
2043
2257
|
const id = shadowId || `shadow-${Date.now()}-${Math.random().toString(36).substring(7)}`;
|
|
2044
2258
|
const shadowKey = this.getKey(`${filename}.shadow.${id}`);
|
|
2045
|
-
await s3.client.send(
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2259
|
+
await s3.client.send(
|
|
2260
|
+
new s3.PutObjectCommand({
|
|
2261
|
+
Bucket: this.bucket,
|
|
2262
|
+
Key: shadowKey,
|
|
2263
|
+
Body: content,
|
|
2264
|
+
ContentType: "application/xml"
|
|
2265
|
+
})
|
|
2266
|
+
);
|
|
2051
2267
|
}
|
|
2052
2268
|
async commitShadow(shadowId) {
|
|
2053
2269
|
if (!this.shadowEnabled) {
|
|
@@ -2055,14 +2271,18 @@ class S3SitemapStorage {
|
|
|
2055
2271
|
}
|
|
2056
2272
|
const s3 = await this.getS3Client();
|
|
2057
2273
|
const prefix = this.prefix ? `${this.prefix}/` : "";
|
|
2058
|
-
const listResponse = await s3.client.send(
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2274
|
+
const listResponse = await s3.client.send(
|
|
2275
|
+
new s3.ListObjectsV2Command({
|
|
2276
|
+
Bucket: this.bucket,
|
|
2277
|
+
Prefix: prefix
|
|
2278
|
+
})
|
|
2279
|
+
);
|
|
2062
2280
|
if (!listResponse.Contents) {
|
|
2063
2281
|
return;
|
|
2064
2282
|
}
|
|
2065
|
-
const shadowFiles = listResponse.Contents.filter(
|
|
2283
|
+
const shadowFiles = listResponse.Contents.filter(
|
|
2284
|
+
(obj) => obj.Key?.includes(`.shadow.${shadowId}`)
|
|
2285
|
+
);
|
|
2066
2286
|
for (const shadowFile of shadowFiles) {
|
|
2067
2287
|
if (!shadowFile.Key) {
|
|
2068
2288
|
continue;
|
|
@@ -2070,35 +2290,45 @@ class S3SitemapStorage {
|
|
|
2070
2290
|
const originalKey = shadowFile.Key.replace(/\.shadow\.[^/]+$/, "");
|
|
2071
2291
|
const _originalFilename = originalKey.replace(prefix, "");
|
|
2072
2292
|
if (this.shadowMode === "atomic") {
|
|
2073
|
-
await s3.client.send(
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2293
|
+
await s3.client.send(
|
|
2294
|
+
new s3.CopyObjectCommand({
|
|
2295
|
+
Bucket: this.bucket,
|
|
2296
|
+
CopySource: `${this.bucket}/${shadowFile.Key}`,
|
|
2297
|
+
Key: originalKey,
|
|
2298
|
+
ContentType: "application/xml"
|
|
2299
|
+
})
|
|
2300
|
+
);
|
|
2301
|
+
await s3.client.send(
|
|
2302
|
+
new s3.DeleteObjectCommand({
|
|
2303
|
+
Bucket: this.bucket,
|
|
2304
|
+
Key: shadowFile.Key
|
|
2305
|
+
})
|
|
2306
|
+
);
|
|
2083
2307
|
} else {
|
|
2084
2308
|
const version = shadowId;
|
|
2085
2309
|
const versionedKey = `${originalKey}.v${version}`;
|
|
2086
|
-
await s3.client.send(
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2310
|
+
await s3.client.send(
|
|
2311
|
+
new s3.CopyObjectCommand({
|
|
2312
|
+
Bucket: this.bucket,
|
|
2313
|
+
CopySource: `${this.bucket}/${shadowFile.Key}`,
|
|
2314
|
+
Key: versionedKey,
|
|
2315
|
+
ContentType: "application/xml"
|
|
2316
|
+
})
|
|
2317
|
+
);
|
|
2318
|
+
await s3.client.send(
|
|
2319
|
+
new s3.CopyObjectCommand({
|
|
2320
|
+
Bucket: this.bucket,
|
|
2321
|
+
CopySource: `${this.bucket}/${shadowFile.Key}`,
|
|
2322
|
+
Key: originalKey,
|
|
2323
|
+
ContentType: "application/xml"
|
|
2324
|
+
})
|
|
2325
|
+
);
|
|
2326
|
+
await s3.client.send(
|
|
2327
|
+
new s3.DeleteObjectCommand({
|
|
2328
|
+
Bucket: this.bucket,
|
|
2329
|
+
Key: shadowFile.Key
|
|
2330
|
+
})
|
|
2331
|
+
);
|
|
2102
2332
|
}
|
|
2103
2333
|
}
|
|
2104
2334
|
}
|
|
@@ -2110,10 +2340,12 @@ class S3SitemapStorage {
|
|
|
2110
2340
|
const s3 = await this.getS3Client();
|
|
2111
2341
|
const key = this.getKey(filename);
|
|
2112
2342
|
const prefix = key.replace(/\.xml$/, "");
|
|
2113
|
-
const listResponse = await s3.client.send(
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2343
|
+
const listResponse = await s3.client.send(
|
|
2344
|
+
new s3.ListObjectsV2Command({
|
|
2345
|
+
Bucket: this.bucket,
|
|
2346
|
+
Prefix: prefix
|
|
2347
|
+
})
|
|
2348
|
+
);
|
|
2117
2349
|
if (!listResponse.Contents) {
|
|
2118
2350
|
return [];
|
|
2119
2351
|
}
|
|
@@ -2143,11 +2375,40 @@ class S3SitemapStorage {
|
|
|
2143
2375
|
if (!exists) {
|
|
2144
2376
|
throw new Error(`Version ${version} not found for ${filename}`);
|
|
2145
2377
|
}
|
|
2146
|
-
await s3.client.send(
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2378
|
+
await s3.client.send(
|
|
2379
|
+
new s3.CopyObjectCommand({
|
|
2380
|
+
Bucket: this.bucket,
|
|
2381
|
+
CopySource: `${this.bucket}/${versionedKey}`,
|
|
2382
|
+
Key: key,
|
|
2383
|
+
ContentType: "application/xml"
|
|
2384
|
+
})
|
|
2385
|
+
);
|
|
2152
2386
|
}
|
|
2153
|
-
}
|
|
2387
|
+
};
|
|
2388
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
2389
|
+
0 && (module.exports = {
|
|
2390
|
+
DiffCalculator,
|
|
2391
|
+
DiskSitemapStorage,
|
|
2392
|
+
GCPSitemapStorage,
|
|
2393
|
+
GenerateSitemapJob,
|
|
2394
|
+
IncrementalGenerator,
|
|
2395
|
+
MemoryChangeTracker,
|
|
2396
|
+
MemoryProgressStorage,
|
|
2397
|
+
MemoryRedirectManager,
|
|
2398
|
+
MemorySitemapStorage,
|
|
2399
|
+
OrbitSitemap,
|
|
2400
|
+
ProgressTracker,
|
|
2401
|
+
RedirectDetector,
|
|
2402
|
+
RedirectHandler,
|
|
2403
|
+
RedisChangeTracker,
|
|
2404
|
+
RedisProgressStorage,
|
|
2405
|
+
RedisRedirectManager,
|
|
2406
|
+
RouteScanner,
|
|
2407
|
+
S3SitemapStorage,
|
|
2408
|
+
ShadowProcessor,
|
|
2409
|
+
SitemapGenerator,
|
|
2410
|
+
SitemapIndex,
|
|
2411
|
+
SitemapStream,
|
|
2412
|
+
generateI18nEntries,
|
|
2413
|
+
routeScanner
|
|
2414
|
+
});
|