@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/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 __toESM = (mod, isNodeMode, target) => {
8
- target = mod != null ? __create(__getProtoOf(mod)) : {};
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
- get: all[name],
36
- enumerable: true,
37
- configurable: true,
38
- set: (newValue) => all[name] = () => newValue
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 __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
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 exports_DiskSitemapStorage = {};
45
- __export(exports_DiskSitemapStorage, {
34
+ var DiskSitemapStorage_exports = {};
35
+ __export(DiskSitemapStorage_exports, {
46
36
  DiskSitemapStorage: () => DiskSitemapStorage
47
37
  });
48
-
49
- class DiskSitemapStorage {
50
- outDir;
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
- async write(filename, content) {
57
- await import_promises.default.mkdir(this.outDir, { recursive: true });
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
- async read(filename) {
61
- try {
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
- async exists(filename) {
68
- try {
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
- import_promises = __toESM(require("node:fs/promises"));
84
- import_node_path = __toESM(require("node:path"));
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 exports_src = {};
89
- __export(exports_src, {
90
- routeScanner: () => routeScanner,
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
- DiffCalculator: () => DiffCalculator
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(exports_src);
124
+ module.exports = __toCommonJS(index_exports);
116
125
 
117
126
  // src/core/ChangeTracker.ts
118
- class MemoryChangeTracker {
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
- class DiffCalculator {
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
- class ShadowProcessor {
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
- class SitemapIndex {
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
415
452
  }
416
- }
453
+ };
417
454
 
418
455
  // src/core/SitemapStream.ts
419
- class SitemapStream {
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 !== undefined) {
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} ${entry.redirect.to} (${entry.redirect.type}) -->${nl}`;
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
609
645
  }
610
- }
646
+ };
611
647
 
612
648
  // src/core/SitemapGenerator.ts
613
- class SitemapGenerator {
649
+ var SitemapGenerator = class {
614
650
  options;
615
651
  shadowProcessor = null;
616
652
  constructor(options) {
617
653
  this.options = {
618
- maxEntriesPerFile: 50000,
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
- class IncrementalGenerator {
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
- class ProgressTracker {
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 || 1000;
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(path, locales, baseUrl = "", options = {}) {
852
- const cleanPath = path.startsWith("/") ? path : `/${path}`;
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
- class GenerateSitemapJob extends import_stream.Job {
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 = 50000,
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("node:crypto");
1040
+ var import_node_crypto = require("crypto");
950
1041
 
951
1042
  // src/redirect/RedirectHandler.ts
952
- class RedirectHandler {
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
- class MemorySitemapStorage {
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
- class OrbitSitemap {
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 OrbitSitemap("dynamic", {
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 OrbitSitemap("static", {
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("/")) : undefined;
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 filename = reqPath.split("/").pop() || indexFilename;
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(), exports_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(), exports_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(), exports_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) : undefined
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
- class RedirectDetector {
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((url) => this.detect(url).then((rule) => {
1452
- results.set(url, rule);
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("node:fs/promises");
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 || 5000;
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) * 1000;
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
- class MemoryRedirectManager {
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
- class GCPSitemapStorage {
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(`Failed to load Google Cloud Storage. Please install @google-cloud/storage: ${error instanceof Error ? error.message : String(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
- class MemoryProgressStorage {
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
- class RedisProgressStorage {
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
- class S3SitemapStorage {
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(`Failed to load AWS SDK. Please install @aws-sdk/client-s3: ${error instanceof Error ? error.message : String(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(new s3.PutObjectCommand({
1987
- Bucket: this.bucket,
1988
- Key: key,
1989
- Body: content,
1990
- ContentType: "application/xml"
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(new s3.GetObjectCommand({
1998
- Bucket: this.bucket,
1999
- Key: key
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(new s3.HeadObjectCommand({
2022
- Bucket: this.bucket,
2023
- Key: key
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(new s3.PutObjectCommand({
2046
- Bucket: this.bucket,
2047
- Key: shadowKey,
2048
- Body: content,
2049
- ContentType: "application/xml"
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(new s3.ListObjectsV2Command({
2059
- Bucket: this.bucket,
2060
- Prefix: prefix
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((obj) => obj.Key?.includes(`.shadow.${shadowId}`));
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(new s3.CopyObjectCommand({
2074
- Bucket: this.bucket,
2075
- CopySource: `${this.bucket}/${shadowFile.Key}`,
2076
- Key: originalKey,
2077
- ContentType: "application/xml"
2078
- }));
2079
- await s3.client.send(new s3.DeleteObjectCommand({
2080
- Bucket: this.bucket,
2081
- Key: shadowFile.Key
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(new s3.CopyObjectCommand({
2087
- Bucket: this.bucket,
2088
- CopySource: `${this.bucket}/${shadowFile.Key}`,
2089
- Key: versionedKey,
2090
- ContentType: "application/xml"
2091
- }));
2092
- await s3.client.send(new s3.CopyObjectCommand({
2093
- Bucket: this.bucket,
2094
- CopySource: `${this.bucket}/${shadowFile.Key}`,
2095
- Key: originalKey,
2096
- ContentType: "application/xml"
2097
- }));
2098
- await s3.client.send(new s3.DeleteObjectCommand({
2099
- Bucket: this.bucket,
2100
- Key: shadowFile.Key
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(new s3.ListObjectsV2Command({
2114
- Bucket: this.bucket,
2115
- Prefix: prefix
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(new s3.CopyObjectCommand({
2147
- Bucket: this.bucket,
2148
- CopySource: `${this.bucket}/${versionedKey}`,
2149
- Key: key,
2150
- ContentType: "application/xml"
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
+ });