@gravito/constellation 3.1.1 → 3.1.3
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 +30 -7
- package/dist/{DiskSitemapStorage-VLN5I24C.js → DiskSitemapStorage-6VXPMKLB.js} +1 -1
- package/dist/{chunk-3IZTXYU7.js → chunk-O3SCFF44.js} +21 -29
- package/dist/index.cjs +84 -87
- package/dist/index.d.cts +8 -18
- package/dist/index.d.ts +8 -18
- package/dist/index.js +55 -47
- package/package.json +8 -5
package/README.md
CHANGED
|
@@ -13,19 +13,34 @@ Powerful, high-performance SEO and Sitemap orchestration module for **Gravito ap
|
|
|
13
13
|
## 🌟 Key Features
|
|
14
14
|
|
|
15
15
|
### 🚀 High Performance & Scalability
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
- **
|
|
16
|
+
- **🪐 Galaxy-Ready Indexing**: Native integration with PlanetCore for universal sitemap management across all Satellites.
|
|
17
|
+
- **✨ Atomic SEO Deployments**: Shadow-processing engine for "Blue-Green" sitemap swaps, ensuring zero downtime for search crawlers.
|
|
18
|
+
- **Streaming Generation**: Uses `SitemapStream` for memory-efficient XML building with 40%+ memory peak reduction.
|
|
19
19
|
- **Auto-Sharding**: Automatically splits large sitemaps into multiple files (50,000 URLs limit) and generates sitemap indexes.
|
|
20
|
-
- **
|
|
21
|
-
- **Distributed Locking**: Prevents "cache stampedes" in distributed environments (e.g., Kubernetes) using Redis locks.
|
|
20
|
+
- **Distributed Locking**: Plasma-backed Redis locks to prevent "cache stampedes" in multi-node clusters.
|
|
22
21
|
|
|
23
22
|
### 🏢 Enterprise SEO Orchestration
|
|
24
23
|
- **Incremental Generation**: Only update modified URLs instead of regenerating the entire sitemap.
|
|
25
|
-
- **
|
|
26
|
-
- **301/302 Redirect Handling**: Intelligent detection and removal/replacement of redirected URLs to ensure search engines only see canonical links.
|
|
24
|
+
- **301/302 Redirect Handling**: Intelligent detection and removal/replacement of redirected URLs.
|
|
27
25
|
- **Cloud Storage Integration**: Built-in support for AWS S3 and Google Cloud Storage (GCS).
|
|
28
26
|
|
|
27
|
+
## 🌌 Role in Galaxy Architecture
|
|
28
|
+
|
|
29
|
+
In the **Gravito Galaxy Architecture**, Constellation acts as the **Star Chart (Navigation Layer)**.
|
|
30
|
+
|
|
31
|
+
- **Galaxy Map**: Aggregates the public-facing "Coodinates" (URLs) of all isolated Satellites, creating a unified map for search engines to navigate the entire ecosystem.
|
|
32
|
+
- **Discovery Pulse**: Works with the `Photon` Sensing Layer to identify and index new content as soon as it is manifested by a Satellite.
|
|
33
|
+
- **SEO Orchestrator**: Coordinates the deployment of metadata across multiple domains and environments, ensuring consistent brand visibility.
|
|
34
|
+
|
|
35
|
+
```mermaid
|
|
36
|
+
graph TD
|
|
37
|
+
S1[Satellite: Blog] -- "URLs" --> Const{Constellation Chart}
|
|
38
|
+
S2[Satellite: Shop] -- "URLs" --> Const
|
|
39
|
+
Const -->|Stream| S3[(S3 Storage)]
|
|
40
|
+
Const -->|Index| Google([Search Engines])
|
|
41
|
+
Const -.->|Lock| Plasma[(Plasma Redis)]
|
|
42
|
+
```
|
|
43
|
+
|
|
29
44
|
### 🛠️ Advanced Capabilities
|
|
30
45
|
- **Rich Extensions**: Support for Images, Videos, News, and i18n alternate links (hreflang).
|
|
31
46
|
- **Background Jobs**: Non-blocking generation with persistent progress tracking.
|
|
@@ -95,6 +110,14 @@ await sitemap.generate()
|
|
|
95
110
|
|
|
96
111
|
---
|
|
97
112
|
|
|
113
|
+
## 📚 Documentation
|
|
114
|
+
|
|
115
|
+
Detailed guides and references for the Galaxy Architecture:
|
|
116
|
+
|
|
117
|
+
- [🏗️ **Architecture Overview**](./README.md) — SEO and Sitemap orchestration engine.
|
|
118
|
+
- [🛰️ **Galaxy SEO**](./doc/GALAXY_SEO.md) — **NEW**: Multi-satellite sitemaps and shadow deployment.
|
|
119
|
+
- [💎 **Advanced Usage**](#-advanced-usage) — Compression, AWS S3, and streaming.
|
|
120
|
+
|
|
98
121
|
## 🏗️ Architecture & Modules
|
|
99
122
|
|
|
100
123
|
Constellation is composed of several specialized sub-modules:
|
|
@@ -1,32 +1,26 @@
|
|
|
1
1
|
// src/storage/DiskSitemapStorage.ts
|
|
2
2
|
import { createReadStream, createWriteStream } from "fs";
|
|
3
|
-
import fs from "fs/promises";
|
|
4
3
|
import path from "path";
|
|
5
|
-
import { Readable
|
|
4
|
+
import { Readable } from "stream";
|
|
6
5
|
import { pipeline } from "stream/promises";
|
|
6
|
+
import { getRuntimeAdapter, runtimeMkdir, runtimeReadText } from "@gravito/core";
|
|
7
7
|
|
|
8
8
|
// src/utils/Compression.ts
|
|
9
|
-
import { Readable } from "stream";
|
|
10
9
|
import { createGzip } from "zlib";
|
|
10
|
+
import { getCompressionAdapter } from "@gravito/core";
|
|
11
11
|
async function compressToBuffer(source, config) {
|
|
12
12
|
const level = config?.level ?? 6;
|
|
13
13
|
if (level < 1 || level > 9) {
|
|
14
14
|
throw new Error(`Invalid compression level: ${level}. Must be between 1 and 9.`);
|
|
15
15
|
}
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
try {
|
|
20
|
-
readable.pipe(gzip);
|
|
21
|
-
for await (const chunk of gzip) {
|
|
22
|
-
chunks.push(chunk);
|
|
23
|
-
}
|
|
24
|
-
return Buffer.concat(chunks);
|
|
25
|
-
} catch (error) {
|
|
26
|
-
readable.destroy();
|
|
27
|
-
gzip.destroy();
|
|
28
|
-
throw error;
|
|
16
|
+
const parts = [];
|
|
17
|
+
for await (const chunk of source) {
|
|
18
|
+
parts.push(chunk);
|
|
29
19
|
}
|
|
20
|
+
const input = new TextEncoder().encode(parts.join(""));
|
|
21
|
+
const adapter = getCompressionAdapter();
|
|
22
|
+
const compressed = await adapter.gzip(input, { level });
|
|
23
|
+
return Buffer.from(compressed);
|
|
30
24
|
}
|
|
31
25
|
function createCompressionStream(config) {
|
|
32
26
|
const level = config?.level ?? 6;
|
|
@@ -63,6 +57,7 @@ var DiskSitemapStorage = class {
|
|
|
63
57
|
this.outDir = outDir;
|
|
64
58
|
this.baseUrl = baseUrl;
|
|
65
59
|
}
|
|
60
|
+
runtime = getRuntimeAdapter();
|
|
66
61
|
/**
|
|
67
62
|
* Writes sitemap content to a file on the local disk.
|
|
68
63
|
*
|
|
@@ -71,8 +66,8 @@ var DiskSitemapStorage = class {
|
|
|
71
66
|
*/
|
|
72
67
|
async write(filename, content) {
|
|
73
68
|
const safeName = sanitizeFilename(filename);
|
|
74
|
-
await
|
|
75
|
-
await
|
|
69
|
+
await runtimeMkdir(this.runtime, this.outDir, { recursive: true });
|
|
70
|
+
await this.runtime.writeFile(path.join(this.outDir, safeName), content);
|
|
76
71
|
}
|
|
77
72
|
/**
|
|
78
73
|
* 使用串流方式寫入 sitemap 檔案,可選擇性啟用 gzip 壓縮。
|
|
@@ -86,10 +81,10 @@ var DiskSitemapStorage = class {
|
|
|
86
81
|
*/
|
|
87
82
|
async writeStream(filename, stream, options) {
|
|
88
83
|
const safeName = sanitizeFilename(options?.compress ? toGzipFilename(filename) : filename);
|
|
89
|
-
await
|
|
84
|
+
await runtimeMkdir(this.runtime, this.outDir, { recursive: true });
|
|
90
85
|
const filePath = path.join(this.outDir, safeName);
|
|
91
86
|
const writeStream = createWriteStream(filePath);
|
|
92
|
-
const readable =
|
|
87
|
+
const readable = Readable.from(stream, { encoding: "utf-8" });
|
|
93
88
|
if (options?.compress) {
|
|
94
89
|
const gzip = createCompressionStream();
|
|
95
90
|
await pipeline(readable, gzip, writeStream);
|
|
@@ -106,7 +101,7 @@ var DiskSitemapStorage = class {
|
|
|
106
101
|
async read(filename) {
|
|
107
102
|
try {
|
|
108
103
|
const safeName = sanitizeFilename(filename);
|
|
109
|
-
return await
|
|
104
|
+
return await runtimeReadText(this.runtime, path.join(this.outDir, safeName));
|
|
110
105
|
} catch {
|
|
111
106
|
return null;
|
|
112
107
|
}
|
|
@@ -121,7 +116,9 @@ var DiskSitemapStorage = class {
|
|
|
121
116
|
try {
|
|
122
117
|
const safeName = sanitizeFilename(filename);
|
|
123
118
|
const fullPath = path.join(this.outDir, safeName);
|
|
124
|
-
await
|
|
119
|
+
if (!await this.runtime.exists(fullPath)) {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
125
122
|
const stream = createReadStream(fullPath, { encoding: "utf-8" });
|
|
126
123
|
return stream;
|
|
127
124
|
} catch {
|
|
@@ -135,13 +132,8 @@ var DiskSitemapStorage = class {
|
|
|
135
132
|
* @returns A promise resolving to true if the file exists, false otherwise.
|
|
136
133
|
*/
|
|
137
134
|
async exists(filename) {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
await fs.access(path.join(this.outDir, safeName));
|
|
141
|
-
return true;
|
|
142
|
-
} catch {
|
|
143
|
-
return false;
|
|
144
|
-
}
|
|
135
|
+
const safeName = sanitizeFilename(filename);
|
|
136
|
+
return this.runtime.exists(path.join(this.outDir, safeName));
|
|
145
137
|
}
|
|
146
138
|
/**
|
|
147
139
|
* Returns the full public URL for a sitemap file.
|
package/dist/index.cjs
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
var __create = Object.create;
|
|
3
2
|
var __defProp = Object.defineProperty;
|
|
4
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -36,20 +35,14 @@ async function compressToBuffer(source, config) {
|
|
|
36
35
|
if (level < 1 || level > 9) {
|
|
37
36
|
throw new Error(`Invalid compression level: ${level}. Must be between 1 and 9.`);
|
|
38
37
|
}
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
try {
|
|
43
|
-
readable.pipe(gzip);
|
|
44
|
-
for await (const chunk of gzip) {
|
|
45
|
-
chunks.push(chunk);
|
|
46
|
-
}
|
|
47
|
-
return Buffer.concat(chunks);
|
|
48
|
-
} catch (error) {
|
|
49
|
-
readable.destroy();
|
|
50
|
-
gzip.destroy();
|
|
51
|
-
throw error;
|
|
38
|
+
const parts = [];
|
|
39
|
+
for await (const chunk of source) {
|
|
40
|
+
parts.push(chunk);
|
|
52
41
|
}
|
|
42
|
+
const input = new TextEncoder().encode(parts.join(""));
|
|
43
|
+
const adapter = (0, import_core.getCompressionAdapter)();
|
|
44
|
+
const compressed = await adapter.gzip(input, { level });
|
|
45
|
+
return Buffer.from(compressed);
|
|
53
46
|
}
|
|
54
47
|
function createCompressionStream(config) {
|
|
55
48
|
const level = config?.level ?? 6;
|
|
@@ -64,12 +57,11 @@ function toGzipFilename(filename) {
|
|
|
64
57
|
function fromGzipFilename(filename) {
|
|
65
58
|
return filename.endsWith(".gz") ? filename.slice(0, -3) : filename;
|
|
66
59
|
}
|
|
67
|
-
var
|
|
60
|
+
var import_node_zlib, import_core;
|
|
68
61
|
var init_Compression = __esm({
|
|
69
62
|
"src/utils/Compression.ts"() {
|
|
70
|
-
"use strict";
|
|
71
|
-
import_node_stream = require("stream");
|
|
72
63
|
import_node_zlib = require("zlib");
|
|
64
|
+
import_core = require("@gravito/core");
|
|
73
65
|
}
|
|
74
66
|
});
|
|
75
67
|
|
|
@@ -93,21 +85,21 @@ function sanitizeFilename(filename) {
|
|
|
93
85
|
}
|
|
94
86
|
return filename;
|
|
95
87
|
}
|
|
96
|
-
var import_node_fs,
|
|
88
|
+
var import_node_fs, import_node_path, import_node_stream, import_promises, import_core4, DiskSitemapStorage;
|
|
97
89
|
var init_DiskSitemapStorage = __esm({
|
|
98
90
|
"src/storage/DiskSitemapStorage.ts"() {
|
|
99
|
-
"use strict";
|
|
100
91
|
import_node_fs = require("fs");
|
|
101
|
-
import_promises = __toESM(require("fs/promises"), 1);
|
|
102
92
|
import_node_path = __toESM(require("path"), 1);
|
|
103
|
-
|
|
104
|
-
|
|
93
|
+
import_node_stream = require("stream");
|
|
94
|
+
import_promises = require("stream/promises");
|
|
95
|
+
import_core4 = require("@gravito/core");
|
|
105
96
|
init_Compression();
|
|
106
97
|
DiskSitemapStorage = class {
|
|
107
98
|
constructor(outDir, baseUrl) {
|
|
108
99
|
this.outDir = outDir;
|
|
109
100
|
this.baseUrl = baseUrl;
|
|
110
101
|
}
|
|
102
|
+
runtime = (0, import_core4.getRuntimeAdapter)();
|
|
111
103
|
/**
|
|
112
104
|
* Writes sitemap content to a file on the local disk.
|
|
113
105
|
*
|
|
@@ -116,8 +108,8 @@ var init_DiskSitemapStorage = __esm({
|
|
|
116
108
|
*/
|
|
117
109
|
async write(filename, content) {
|
|
118
110
|
const safeName = sanitizeFilename(filename);
|
|
119
|
-
await
|
|
120
|
-
await
|
|
111
|
+
await (0, import_core4.runtimeMkdir)(this.runtime, this.outDir, { recursive: true });
|
|
112
|
+
await this.runtime.writeFile(import_node_path.default.join(this.outDir, safeName), content);
|
|
121
113
|
}
|
|
122
114
|
/**
|
|
123
115
|
* 使用串流方式寫入 sitemap 檔案,可選擇性啟用 gzip 壓縮。
|
|
@@ -131,15 +123,15 @@ var init_DiskSitemapStorage = __esm({
|
|
|
131
123
|
*/
|
|
132
124
|
async writeStream(filename, stream, options) {
|
|
133
125
|
const safeName = sanitizeFilename(options?.compress ? toGzipFilename(filename) : filename);
|
|
134
|
-
await
|
|
126
|
+
await (0, import_core4.runtimeMkdir)(this.runtime, this.outDir, { recursive: true });
|
|
135
127
|
const filePath = import_node_path.default.join(this.outDir, safeName);
|
|
136
128
|
const writeStream = (0, import_node_fs.createWriteStream)(filePath);
|
|
137
|
-
const readable =
|
|
129
|
+
const readable = import_node_stream.Readable.from(stream, { encoding: "utf-8" });
|
|
138
130
|
if (options?.compress) {
|
|
139
131
|
const gzip = createCompressionStream();
|
|
140
|
-
await (0,
|
|
132
|
+
await (0, import_promises.pipeline)(readable, gzip, writeStream);
|
|
141
133
|
} else {
|
|
142
|
-
await (0,
|
|
134
|
+
await (0, import_promises.pipeline)(readable, writeStream);
|
|
143
135
|
}
|
|
144
136
|
}
|
|
145
137
|
/**
|
|
@@ -151,7 +143,7 @@ var init_DiskSitemapStorage = __esm({
|
|
|
151
143
|
async read(filename) {
|
|
152
144
|
try {
|
|
153
145
|
const safeName = sanitizeFilename(filename);
|
|
154
|
-
return await
|
|
146
|
+
return await (0, import_core4.runtimeReadText)(this.runtime, import_node_path.default.join(this.outDir, safeName));
|
|
155
147
|
} catch {
|
|
156
148
|
return null;
|
|
157
149
|
}
|
|
@@ -166,7 +158,9 @@ var init_DiskSitemapStorage = __esm({
|
|
|
166
158
|
try {
|
|
167
159
|
const safeName = sanitizeFilename(filename);
|
|
168
160
|
const fullPath = import_node_path.default.join(this.outDir, safeName);
|
|
169
|
-
await
|
|
161
|
+
if (!await this.runtime.exists(fullPath)) {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
170
164
|
const stream = (0, import_node_fs.createReadStream)(fullPath, { encoding: "utf-8" });
|
|
171
165
|
return stream;
|
|
172
166
|
} catch {
|
|
@@ -180,13 +174,8 @@ var init_DiskSitemapStorage = __esm({
|
|
|
180
174
|
* @returns A promise resolving to true if the file exists, false otherwise.
|
|
181
175
|
*/
|
|
182
176
|
async exists(filename) {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
await import_promises.default.access(import_node_path.default.join(this.outDir, safeName));
|
|
186
|
-
return true;
|
|
187
|
-
} catch {
|
|
188
|
-
return false;
|
|
189
|
-
}
|
|
177
|
+
const safeName = sanitizeFilename(filename);
|
|
178
|
+
return this.runtime.exists(import_node_path.default.join(this.outDir, safeName));
|
|
190
179
|
}
|
|
191
180
|
/**
|
|
192
181
|
* Returns the full public URL for a sitemap file.
|
|
@@ -419,10 +408,6 @@ var RedisChangeTracker = class {
|
|
|
419
408
|
|
|
420
409
|
// src/core/DiffCalculator.ts
|
|
421
410
|
var DiffCalculator = class {
|
|
422
|
-
batchSize;
|
|
423
|
-
constructor(options = {}) {
|
|
424
|
-
this.batchSize = options.batchSize || 1e4;
|
|
425
|
-
}
|
|
426
411
|
/**
|
|
427
412
|
* Calculates the difference between two sets of sitemap entries.
|
|
428
413
|
*
|
|
@@ -633,9 +618,11 @@ var ShadowProcessor = class {
|
|
|
633
618
|
};
|
|
634
619
|
|
|
635
620
|
// src/core/SitemapIndex.ts
|
|
621
|
+
var import_core2 = require("@gravito/core");
|
|
636
622
|
var SitemapIndex = class {
|
|
637
623
|
options;
|
|
638
624
|
entries = [];
|
|
625
|
+
escapeHtml = (0, import_core2.getEscapeHtml)();
|
|
639
626
|
constructor(options) {
|
|
640
627
|
this.options = { ...options };
|
|
641
628
|
if (this.options.baseUrl.endsWith("/")) {
|
|
@@ -691,7 +678,7 @@ var SitemapIndex = class {
|
|
|
691
678
|
loc = baseUrl + loc;
|
|
692
679
|
}
|
|
693
680
|
xml += `${indent}<sitemap>${nl}`;
|
|
694
|
-
xml += `${subIndent}<loc>${this.
|
|
681
|
+
xml += `${subIndent}<loc>${this.escapeHtml(loc)}</loc>${nl}`;
|
|
695
682
|
if (entry.lastmod) {
|
|
696
683
|
const date = entry.lastmod instanceof Date ? entry.lastmod : new Date(entry.lastmod);
|
|
697
684
|
xml += `${subIndent}<lastmod>${date.toISOString().split("T")[0]}</lastmod>${nl}`;
|
|
@@ -704,12 +691,10 @@ var SitemapIndex = class {
|
|
|
704
691
|
/**
|
|
705
692
|
* Escapes special XML characters in a string.
|
|
706
693
|
*/
|
|
707
|
-
escape(str) {
|
|
708
|
-
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
709
|
-
}
|
|
710
694
|
};
|
|
711
695
|
|
|
712
696
|
// src/core/SitemapStream.ts
|
|
697
|
+
var import_core3 = require("@gravito/core");
|
|
713
698
|
var SitemapStream = class {
|
|
714
699
|
options;
|
|
715
700
|
entries = [];
|
|
@@ -719,6 +704,7 @@ var SitemapStream = class {
|
|
|
719
704
|
hasNews: false,
|
|
720
705
|
hasAlternates: false
|
|
721
706
|
};
|
|
707
|
+
escapeHtml = (0, import_core3.getEscapeHtml)();
|
|
722
708
|
constructor(options) {
|
|
723
709
|
this.options = { ...options };
|
|
724
710
|
if (this.options.baseUrl.endsWith("/")) {
|
|
@@ -798,8 +784,19 @@ var SitemapStream = class {
|
|
|
798
784
|
yield '<?xml version="1.0" encoding="UTF-8"?>\n';
|
|
799
785
|
yield this.buildUrlsetOpenTag();
|
|
800
786
|
const { baseUrl, pretty } = this.options;
|
|
787
|
+
let buffer = "";
|
|
788
|
+
let count = 0;
|
|
801
789
|
for (const entry of this.entries) {
|
|
802
|
-
|
|
790
|
+
buffer += this.renderUrl(entry, baseUrl, pretty);
|
|
791
|
+
count++;
|
|
792
|
+
if (count >= 5e3) {
|
|
793
|
+
yield buffer;
|
|
794
|
+
buffer = "";
|
|
795
|
+
count = 0;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
if (buffer) {
|
|
799
|
+
yield buffer;
|
|
803
800
|
}
|
|
804
801
|
yield "</urlset>";
|
|
805
802
|
}
|
|
@@ -852,7 +849,7 @@ var SitemapStream = class {
|
|
|
852
849
|
loc = baseUrl + loc;
|
|
853
850
|
}
|
|
854
851
|
parts.push(`${indent}<url>${nl}`);
|
|
855
|
-
parts.push(`${subIndent}<loc>${this.
|
|
852
|
+
parts.push(`${subIndent}<loc>${this.escapeHtml(loc)}</loc>${nl}`);
|
|
856
853
|
if (entry.lastmod) {
|
|
857
854
|
const date = entry.lastmod instanceof Date ? entry.lastmod : new Date(entry.lastmod);
|
|
858
855
|
parts.push(`${subIndent}<lastmod>${date.toISOString().split("T")[0]}</lastmod>${nl}`);
|
|
@@ -873,7 +870,7 @@ var SitemapStream = class {
|
|
|
873
870
|
altLoc = baseUrl + altLoc;
|
|
874
871
|
}
|
|
875
872
|
parts.push(
|
|
876
|
-
`${subIndent}<xhtml:link rel="alternate" hreflang="${alt.lang}" href="${this.
|
|
873
|
+
`${subIndent}<xhtml:link rel="alternate" hreflang="${alt.lang}" href="${this.escapeHtml(altLoc)}"/>${nl}`
|
|
877
874
|
);
|
|
878
875
|
}
|
|
879
876
|
}
|
|
@@ -886,7 +883,7 @@ var SitemapStream = class {
|
|
|
886
883
|
canonicalUrl = baseUrl + canonicalUrl;
|
|
887
884
|
}
|
|
888
885
|
parts.push(
|
|
889
|
-
`${subIndent}<xhtml:link rel="canonical" href="${this.
|
|
886
|
+
`${subIndent}<xhtml:link rel="canonical" href="${this.escapeHtml(canonicalUrl)}"/>${nl}`
|
|
890
887
|
);
|
|
891
888
|
}
|
|
892
889
|
if (entry.redirect && !entry.redirect.canonical) {
|
|
@@ -904,23 +901,23 @@ var SitemapStream = class {
|
|
|
904
901
|
loc2 = baseUrl + loc2;
|
|
905
902
|
}
|
|
906
903
|
parts.push(`${subIndent}<image:image>${nl}`);
|
|
907
|
-
parts.push(`${subIndent} <image:loc>${this.
|
|
904
|
+
parts.push(`${subIndent} <image:loc>${this.escapeHtml(loc2)}</image:loc>${nl}`);
|
|
908
905
|
if (img.title) {
|
|
909
|
-
parts.push(`${subIndent} <image:title>${this.
|
|
906
|
+
parts.push(`${subIndent} <image:title>${this.escapeHtml(img.title)}</image:title>${nl}`);
|
|
910
907
|
}
|
|
911
908
|
if (img.caption) {
|
|
912
909
|
parts.push(
|
|
913
|
-
`${subIndent} <image:caption>${this.
|
|
910
|
+
`${subIndent} <image:caption>${this.escapeHtml(img.caption)}</image:caption>${nl}`
|
|
914
911
|
);
|
|
915
912
|
}
|
|
916
913
|
if (img.geo_location) {
|
|
917
914
|
parts.push(
|
|
918
|
-
`${subIndent} <image:geo_location>${this.
|
|
915
|
+
`${subIndent} <image:geo_location>${this.escapeHtml(img.geo_location)}</image:geo_location>${nl}`
|
|
919
916
|
);
|
|
920
917
|
}
|
|
921
918
|
if (img.license) {
|
|
922
919
|
parts.push(
|
|
923
|
-
`${subIndent} <image:license>${this.
|
|
920
|
+
`${subIndent} <image:license>${this.escapeHtml(img.license)}</image:license>${nl}`
|
|
924
921
|
);
|
|
925
922
|
}
|
|
926
923
|
parts.push(`${subIndent}</image:image>${nl}`);
|
|
@@ -930,20 +927,20 @@ var SitemapStream = class {
|
|
|
930
927
|
for (const video of entry.videos) {
|
|
931
928
|
parts.push(`${subIndent}<video:video>${nl}`);
|
|
932
929
|
parts.push(
|
|
933
|
-
`${subIndent} <video:thumbnail_loc>${this.
|
|
930
|
+
`${subIndent} <video:thumbnail_loc>${this.escapeHtml(video.thumbnail_loc)}</video:thumbnail_loc>${nl}`
|
|
934
931
|
);
|
|
935
|
-
parts.push(`${subIndent} <video:title>${this.
|
|
932
|
+
parts.push(`${subIndent} <video:title>${this.escapeHtml(video.title)}</video:title>${nl}`);
|
|
936
933
|
parts.push(
|
|
937
|
-
`${subIndent} <video:description>${this.
|
|
934
|
+
`${subIndent} <video:description>${this.escapeHtml(video.description)}</video:description>${nl}`
|
|
938
935
|
);
|
|
939
936
|
if (video.content_loc) {
|
|
940
937
|
parts.push(
|
|
941
|
-
`${subIndent} <video:content_loc>${this.
|
|
938
|
+
`${subIndent} <video:content_loc>${this.escapeHtml(video.content_loc)}</video:content_loc>${nl}`
|
|
942
939
|
);
|
|
943
940
|
}
|
|
944
941
|
if (video.player_loc) {
|
|
945
942
|
parts.push(
|
|
946
|
-
`${subIndent} <video:player_loc>${this.
|
|
943
|
+
`${subIndent} <video:player_loc>${this.escapeHtml(video.player_loc)}</video:player_loc>${nl}`
|
|
947
944
|
);
|
|
948
945
|
}
|
|
949
946
|
if (video.duration) {
|
|
@@ -965,7 +962,7 @@ var SitemapStream = class {
|
|
|
965
962
|
}
|
|
966
963
|
if (video.tag) {
|
|
967
964
|
for (const tag of video.tag) {
|
|
968
|
-
parts.push(`${subIndent} <video:tag>${this.
|
|
965
|
+
parts.push(`${subIndent} <video:tag>${this.escapeHtml(tag)}</video:tag>${nl}`);
|
|
969
966
|
}
|
|
970
967
|
}
|
|
971
968
|
parts.push(`${subIndent}</video:video>${nl}`);
|
|
@@ -975,25 +972,25 @@ var SitemapStream = class {
|
|
|
975
972
|
parts.push(`${subIndent}<news:news>${nl}`);
|
|
976
973
|
parts.push(`${subIndent} <news:publication>${nl}`);
|
|
977
974
|
parts.push(
|
|
978
|
-
`${subIndent} <news:name>${this.
|
|
975
|
+
`${subIndent} <news:name>${this.escapeHtml(entry.news.publication.name)}</news:name>${nl}`
|
|
979
976
|
);
|
|
980
977
|
parts.push(
|
|
981
|
-
`${subIndent} <news:language>${this.
|
|
978
|
+
`${subIndent} <news:language>${this.escapeHtml(entry.news.publication.language)}</news:language>${nl}`
|
|
982
979
|
);
|
|
983
980
|
parts.push(`${subIndent} </news:publication>${nl}`);
|
|
984
981
|
const pubDate = entry.news.publication_date instanceof Date ? entry.news.publication_date : new Date(entry.news.publication_date);
|
|
985
982
|
parts.push(
|
|
986
983
|
`${subIndent} <news:publication_date>${pubDate.toISOString()}</news:publication_date>${nl}`
|
|
987
984
|
);
|
|
988
|
-
parts.push(`${subIndent} <news:title>${this.
|
|
985
|
+
parts.push(`${subIndent} <news:title>${this.escapeHtml(entry.news.title)}</news:title>${nl}`);
|
|
989
986
|
if (entry.news.genres) {
|
|
990
987
|
parts.push(
|
|
991
|
-
`${subIndent} <news:genres>${this.
|
|
988
|
+
`${subIndent} <news:genres>${this.escapeHtml(entry.news.genres)}</news:genres>${nl}`
|
|
992
989
|
);
|
|
993
990
|
}
|
|
994
991
|
if (entry.news.keywords) {
|
|
995
992
|
parts.push(
|
|
996
|
-
`${subIndent} <news:keywords>${entry.news.keywords.map((k) => this.
|
|
993
|
+
`${subIndent} <news:keywords>${entry.news.keywords.map((k) => this.escapeHtml(k)).join(", ")}</news:keywords>${nl}`
|
|
997
994
|
);
|
|
998
995
|
}
|
|
999
996
|
parts.push(`${subIndent}</news:news>${nl}`);
|
|
@@ -1004,9 +1001,6 @@ var SitemapStream = class {
|
|
|
1004
1001
|
/**
|
|
1005
1002
|
* Escapes special XML characters in a string.
|
|
1006
1003
|
*/
|
|
1007
|
-
escape(str) {
|
|
1008
|
-
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1009
|
-
}
|
|
1010
1004
|
/**
|
|
1011
1005
|
* Returns all entries currently in the stream.
|
|
1012
1006
|
*
|
|
@@ -1326,7 +1320,6 @@ var SitemapParser = class {
|
|
|
1326
1320
|
var IncrementalGenerator = class {
|
|
1327
1321
|
options;
|
|
1328
1322
|
changeTracker;
|
|
1329
|
-
diffCalculator;
|
|
1330
1323
|
generator;
|
|
1331
1324
|
mutex = new Mutex();
|
|
1332
1325
|
constructor(options) {
|
|
@@ -1336,7 +1329,6 @@ var IncrementalGenerator = class {
|
|
|
1336
1329
|
...options
|
|
1337
1330
|
};
|
|
1338
1331
|
this.changeTracker = this.options.changeTracker;
|
|
1339
|
-
this.diffCalculator = this.options.diffCalculator || new DiffCalculator();
|
|
1340
1332
|
this.generator = new SitemapGenerator(this.options);
|
|
1341
1333
|
}
|
|
1342
1334
|
/**
|
|
@@ -1584,6 +1576,7 @@ var ProgressTracker = class {
|
|
|
1584
1576
|
console.error("[ProgressTracker] Failed to flush progress:", err);
|
|
1585
1577
|
});
|
|
1586
1578
|
}, this.updateInterval);
|
|
1579
|
+
this.updateTimer.unref?.();
|
|
1587
1580
|
}
|
|
1588
1581
|
}
|
|
1589
1582
|
/**
|
|
@@ -2041,7 +2034,6 @@ var MemoryLock = class {
|
|
|
2041
2034
|
};
|
|
2042
2035
|
|
|
2043
2036
|
// src/locks/RedisLock.ts
|
|
2044
|
-
var import_node_crypto = require("crypto");
|
|
2045
2037
|
var RedisLock = class {
|
|
2046
2038
|
/**
|
|
2047
2039
|
* Constructs a new RedisLock instance with the specified configuration.
|
|
@@ -2074,7 +2066,7 @@ var RedisLock = class {
|
|
|
2074
2066
|
* UUIDs are sufficiently random to prevent lock hijacking across instances.
|
|
2075
2067
|
* However, they are stored in plain text in Redis (not encrypted).
|
|
2076
2068
|
*/
|
|
2077
|
-
lockId =
|
|
2069
|
+
lockId = crypto.randomUUID();
|
|
2078
2070
|
/**
|
|
2079
2071
|
* Redis key prefix for all locks acquired through this instance.
|
|
2080
2072
|
*
|
|
@@ -2304,9 +2296,6 @@ var RedisLock = class {
|
|
|
2304
2296
|
}
|
|
2305
2297
|
};
|
|
2306
2298
|
|
|
2307
|
-
// src/OrbitSitemap.ts
|
|
2308
|
-
var import_node_crypto2 = require("crypto");
|
|
2309
|
-
|
|
2310
2299
|
// src/redirect/RedirectHandler.ts
|
|
2311
2300
|
var RedirectHandler = class {
|
|
2312
2301
|
options;
|
|
@@ -2321,7 +2310,6 @@ var RedirectHandler = class {
|
|
|
2321
2310
|
*/
|
|
2322
2311
|
async processEntries(entries) {
|
|
2323
2312
|
const { manager, strategy, followChains, maxChainLength } = this.options;
|
|
2324
|
-
const _processedEntries = [];
|
|
2325
2313
|
const redirectMap = /* @__PURE__ */ new Map();
|
|
2326
2314
|
for (const entry of entries) {
|
|
2327
2315
|
const redirectTarget = await manager.resolve(entry.url, followChains, maxChainLength);
|
|
@@ -2745,7 +2733,7 @@ var OrbitSitemap = class _OrbitSitemap {
|
|
|
2745
2733
|
throw new Error("generateAsync() can only be called in static mode");
|
|
2746
2734
|
}
|
|
2747
2735
|
const opts = this.options;
|
|
2748
|
-
const jobId =
|
|
2736
|
+
const jobId = crypto.randomUUID();
|
|
2749
2737
|
let storage = opts.storage;
|
|
2750
2738
|
if (!storage) {
|
|
2751
2739
|
const { DiskSitemapStorage: DiskSitemapStorage2 } = await Promise.resolve().then(() => (init_DiskSitemapStorage(), DiskSitemapStorage_exports));
|
|
@@ -3002,7 +2990,11 @@ var RedirectDetector = class {
|
|
|
3002
2990
|
}
|
|
3003
2991
|
try {
|
|
3004
2992
|
const { connection, table, columns } = database;
|
|
3005
|
-
const
|
|
2993
|
+
const safeTable = this.assertSafeIdentifier(table);
|
|
2994
|
+
const safeFrom = this.assertSafeIdentifier(columns.from);
|
|
2995
|
+
const safeTo = this.assertSafeIdentifier(columns.to);
|
|
2996
|
+
const safeType = this.assertSafeIdentifier(columns.type);
|
|
2997
|
+
const query = `SELECT ${safeFrom}, ${safeTo}, ${safeType} FROM ${safeTable} WHERE ${safeFrom} = ? LIMIT 1`;
|
|
3006
2998
|
const results = await connection.query(query, [url]);
|
|
3007
2999
|
if (results.length === 0) {
|
|
3008
3000
|
return null;
|
|
@@ -3026,8 +3018,8 @@ var RedirectDetector = class {
|
|
|
3026
3018
|
return null;
|
|
3027
3019
|
}
|
|
3028
3020
|
try {
|
|
3029
|
-
const
|
|
3030
|
-
const data = await
|
|
3021
|
+
const fs = await import("fs/promises");
|
|
3022
|
+
const data = await fs.readFile(config.path, "utf-8");
|
|
3031
3023
|
const redirects = JSON.parse(data);
|
|
3032
3024
|
const rule = redirects.find((r) => r.from === url);
|
|
3033
3025
|
return rule || null;
|
|
@@ -3048,6 +3040,7 @@ var RedirectDetector = class {
|
|
|
3048
3040
|
const timeout = autoDetect.timeout || 5e3;
|
|
3049
3041
|
const controller = new AbortController();
|
|
3050
3042
|
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
3043
|
+
timeoutId.unref?.();
|
|
3051
3044
|
try {
|
|
3052
3045
|
const response = await fetch(fullUrl, {
|
|
3053
3046
|
method: "HEAD",
|
|
@@ -3089,6 +3082,12 @@ var RedirectDetector = class {
|
|
|
3089
3082
|
expires: Date.now() + ttl
|
|
3090
3083
|
});
|
|
3091
3084
|
}
|
|
3085
|
+
assertSafeIdentifier(identifier) {
|
|
3086
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(identifier)) {
|
|
3087
|
+
throw new Error(`Invalid database identifier: ${identifier}`);
|
|
3088
|
+
}
|
|
3089
|
+
return identifier;
|
|
3090
|
+
}
|
|
3092
3091
|
};
|
|
3093
3092
|
|
|
3094
3093
|
// src/redirect/RedirectManager.ts
|
|
@@ -3278,8 +3277,8 @@ var RedisRedirectManager = class {
|
|
|
3278
3277
|
init_DiskSitemapStorage();
|
|
3279
3278
|
|
|
3280
3279
|
// src/storage/GCPSitemapStorage.ts
|
|
3281
|
-
var
|
|
3282
|
-
var
|
|
3280
|
+
var import_node_stream2 = require("stream");
|
|
3281
|
+
var import_promises2 = require("stream/promises");
|
|
3283
3282
|
init_Compression();
|
|
3284
3283
|
var GCPSitemapStorage = class {
|
|
3285
3284
|
bucket;
|
|
@@ -3357,12 +3356,12 @@ var GCPSitemapStorage = class {
|
|
|
3357
3356
|
cacheControl: "public, max-age=3600"
|
|
3358
3357
|
}
|
|
3359
3358
|
});
|
|
3360
|
-
const readable =
|
|
3359
|
+
const readable = import_node_stream2.Readable.from(stream, { encoding: "utf-8" });
|
|
3361
3360
|
if (options?.compress) {
|
|
3362
3361
|
const gzip = createCompressionStream();
|
|
3363
|
-
await (0,
|
|
3362
|
+
await (0, import_promises2.pipeline)(readable, gzip, writeStream);
|
|
3364
3363
|
} else {
|
|
3365
|
-
await (0,
|
|
3364
|
+
await (0, import_promises2.pipeline)(readable, writeStream);
|
|
3366
3365
|
}
|
|
3367
3366
|
}
|
|
3368
3367
|
/**
|
|
@@ -3487,7 +3486,6 @@ var GCPSitemapStorage = class {
|
|
|
3487
3486
|
});
|
|
3488
3487
|
for (const shadowFile of shadowFiles) {
|
|
3489
3488
|
const originalKey = shadowFile.name.replace(/\.shadow\.[^/]+$/, "");
|
|
3490
|
-
const _originalFilename = originalKey.replace(prefix, "");
|
|
3491
3489
|
if (this.shadowMode === "atomic") {
|
|
3492
3490
|
await shadowFile.copy(bucket.file(originalKey));
|
|
3493
3491
|
await shadowFile.delete();
|
|
@@ -3975,7 +3973,6 @@ var S3SitemapStorage = class {
|
|
|
3975
3973
|
continue;
|
|
3976
3974
|
}
|
|
3977
3975
|
const originalKey = shadowFile.Key.replace(/\.shadow\.[^/]+$/, "");
|
|
3978
|
-
const _originalFilename = originalKey.replace(prefix, "");
|
|
3979
3976
|
if (this.shadowMode === "atomic") {
|
|
3980
3977
|
await s3.client.send(
|
|
3981
3978
|
new s3.CopyObjectCommand({
|
package/dist/index.d.cts
CHANGED
|
@@ -677,16 +677,6 @@ interface DiffResult {
|
|
|
677
677
|
/** URLs that were present in the old set but are missing from the new one. */
|
|
678
678
|
removed: string[];
|
|
679
679
|
}
|
|
680
|
-
/**
|
|
681
|
-
* Options for configuring the `DiffCalculator`.
|
|
682
|
-
*
|
|
683
|
-
* @public
|
|
684
|
-
* @since 3.0.0
|
|
685
|
-
*/
|
|
686
|
-
interface DiffCalculatorOptions {
|
|
687
|
-
/** Batch size for processing large datasets. @default 10000 */
|
|
688
|
-
batchSize?: number;
|
|
689
|
-
}
|
|
690
680
|
/**
|
|
691
681
|
* DiffCalculator compares two sets of sitemap entries to identify changes.
|
|
692
682
|
*
|
|
@@ -698,8 +688,6 @@ interface DiffCalculatorOptions {
|
|
|
698
688
|
* @since 3.0.0
|
|
699
689
|
*/
|
|
700
690
|
declare class DiffCalculator {
|
|
701
|
-
private batchSize;
|
|
702
|
-
constructor(options?: DiffCalculatorOptions);
|
|
703
691
|
/**
|
|
704
692
|
* Calculates the difference between two sets of sitemap entries.
|
|
705
693
|
*
|
|
@@ -922,7 +910,6 @@ interface IncrementalGeneratorOptions extends SitemapGeneratorOptions {
|
|
|
922
910
|
declare class IncrementalGenerator {
|
|
923
911
|
private options;
|
|
924
912
|
private changeTracker;
|
|
925
|
-
private diffCalculator;
|
|
926
913
|
private generator;
|
|
927
914
|
private mutex;
|
|
928
915
|
constructor(options: IncrementalGeneratorOptions);
|
|
@@ -1063,6 +1050,7 @@ declare class ProgressTracker {
|
|
|
1063
1050
|
declare class SitemapIndex {
|
|
1064
1051
|
private options;
|
|
1065
1052
|
private entries;
|
|
1053
|
+
private escapeHtml;
|
|
1066
1054
|
constructor(options: SitemapStreamOptions);
|
|
1067
1055
|
/**
|
|
1068
1056
|
* Adds a single entry to the sitemap index.
|
|
@@ -1084,10 +1072,6 @@ declare class SitemapIndex {
|
|
|
1084
1072
|
* @returns The complete XML string for the sitemap index.
|
|
1085
1073
|
*/
|
|
1086
1074
|
toXML(): string;
|
|
1087
|
-
/**
|
|
1088
|
-
* Escapes special XML characters in a string.
|
|
1089
|
-
*/
|
|
1090
|
-
private escape;
|
|
1091
1075
|
}
|
|
1092
1076
|
|
|
1093
1077
|
/**
|
|
@@ -1114,6 +1098,7 @@ declare class SitemapStream {
|
|
|
1114
1098
|
private options;
|
|
1115
1099
|
private entries;
|
|
1116
1100
|
private flags;
|
|
1101
|
+
private escapeHtml;
|
|
1117
1102
|
constructor(options: SitemapStreamOptions);
|
|
1118
1103
|
/**
|
|
1119
1104
|
* Adds a single entry to the sitemap stream.
|
|
@@ -1173,7 +1158,6 @@ declare class SitemapStream {
|
|
|
1173
1158
|
/**
|
|
1174
1159
|
* Escapes special XML characters in a string.
|
|
1175
1160
|
*/
|
|
1176
|
-
private escape;
|
|
1177
1161
|
/**
|
|
1178
1162
|
* Returns all entries currently in the stream.
|
|
1179
1163
|
*
|
|
@@ -2341,6 +2325,7 @@ declare class RedirectDetector {
|
|
|
2341
2325
|
* Caches the detection result for a URL.
|
|
2342
2326
|
*/
|
|
2343
2327
|
private cacheResult;
|
|
2328
|
+
private assertSafeIdentifier;
|
|
2344
2329
|
}
|
|
2345
2330
|
|
|
2346
2331
|
/**
|
|
@@ -2555,6 +2540,7 @@ declare class RedisRedirectManager implements RedirectManager {
|
|
|
2555
2540
|
declare class DiskSitemapStorage implements SitemapStorage {
|
|
2556
2541
|
private outDir;
|
|
2557
2542
|
private baseUrl;
|
|
2543
|
+
private runtime;
|
|
2558
2544
|
constructor(outDir: string, baseUrl: string);
|
|
2559
2545
|
/**
|
|
2560
2546
|
* Writes sitemap content to a file on the local disk.
|
|
@@ -3065,6 +3051,10 @@ interface CompressionConfig {
|
|
|
3065
3051
|
* 將 AsyncIterable<string> 壓縮為 Buffer。
|
|
3066
3052
|
* 適用於需要完整壓縮結果的場景(如 S3 Upload)。
|
|
3067
3053
|
*
|
|
3054
|
+
* 使用 RuntimeCompressionAdapter 自動選擇最佳壓縮實作:
|
|
3055
|
+
* - Bun: 原生 C++ 壓縮(2-5x 更快)
|
|
3056
|
+
* - Node.js: node:zlib fallback
|
|
3057
|
+
*
|
|
3068
3058
|
* @param source - 輸入的字串串流
|
|
3069
3059
|
* @param config - 壓縮設定
|
|
3070
3060
|
* @returns 壓縮後的 Buffer
|
package/dist/index.d.ts
CHANGED
|
@@ -677,16 +677,6 @@ interface DiffResult {
|
|
|
677
677
|
/** URLs that were present in the old set but are missing from the new one. */
|
|
678
678
|
removed: string[];
|
|
679
679
|
}
|
|
680
|
-
/**
|
|
681
|
-
* Options for configuring the `DiffCalculator`.
|
|
682
|
-
*
|
|
683
|
-
* @public
|
|
684
|
-
* @since 3.0.0
|
|
685
|
-
*/
|
|
686
|
-
interface DiffCalculatorOptions {
|
|
687
|
-
/** Batch size for processing large datasets. @default 10000 */
|
|
688
|
-
batchSize?: number;
|
|
689
|
-
}
|
|
690
680
|
/**
|
|
691
681
|
* DiffCalculator compares two sets of sitemap entries to identify changes.
|
|
692
682
|
*
|
|
@@ -698,8 +688,6 @@ interface DiffCalculatorOptions {
|
|
|
698
688
|
* @since 3.0.0
|
|
699
689
|
*/
|
|
700
690
|
declare class DiffCalculator {
|
|
701
|
-
private batchSize;
|
|
702
|
-
constructor(options?: DiffCalculatorOptions);
|
|
703
691
|
/**
|
|
704
692
|
* Calculates the difference between two sets of sitemap entries.
|
|
705
693
|
*
|
|
@@ -922,7 +910,6 @@ interface IncrementalGeneratorOptions extends SitemapGeneratorOptions {
|
|
|
922
910
|
declare class IncrementalGenerator {
|
|
923
911
|
private options;
|
|
924
912
|
private changeTracker;
|
|
925
|
-
private diffCalculator;
|
|
926
913
|
private generator;
|
|
927
914
|
private mutex;
|
|
928
915
|
constructor(options: IncrementalGeneratorOptions);
|
|
@@ -1063,6 +1050,7 @@ declare class ProgressTracker {
|
|
|
1063
1050
|
declare class SitemapIndex {
|
|
1064
1051
|
private options;
|
|
1065
1052
|
private entries;
|
|
1053
|
+
private escapeHtml;
|
|
1066
1054
|
constructor(options: SitemapStreamOptions);
|
|
1067
1055
|
/**
|
|
1068
1056
|
* Adds a single entry to the sitemap index.
|
|
@@ -1084,10 +1072,6 @@ declare class SitemapIndex {
|
|
|
1084
1072
|
* @returns The complete XML string for the sitemap index.
|
|
1085
1073
|
*/
|
|
1086
1074
|
toXML(): string;
|
|
1087
|
-
/**
|
|
1088
|
-
* Escapes special XML characters in a string.
|
|
1089
|
-
*/
|
|
1090
|
-
private escape;
|
|
1091
1075
|
}
|
|
1092
1076
|
|
|
1093
1077
|
/**
|
|
@@ -1114,6 +1098,7 @@ declare class SitemapStream {
|
|
|
1114
1098
|
private options;
|
|
1115
1099
|
private entries;
|
|
1116
1100
|
private flags;
|
|
1101
|
+
private escapeHtml;
|
|
1117
1102
|
constructor(options: SitemapStreamOptions);
|
|
1118
1103
|
/**
|
|
1119
1104
|
* Adds a single entry to the sitemap stream.
|
|
@@ -1173,7 +1158,6 @@ declare class SitemapStream {
|
|
|
1173
1158
|
/**
|
|
1174
1159
|
* Escapes special XML characters in a string.
|
|
1175
1160
|
*/
|
|
1176
|
-
private escape;
|
|
1177
1161
|
/**
|
|
1178
1162
|
* Returns all entries currently in the stream.
|
|
1179
1163
|
*
|
|
@@ -2341,6 +2325,7 @@ declare class RedirectDetector {
|
|
|
2341
2325
|
* Caches the detection result for a URL.
|
|
2342
2326
|
*/
|
|
2343
2327
|
private cacheResult;
|
|
2328
|
+
private assertSafeIdentifier;
|
|
2344
2329
|
}
|
|
2345
2330
|
|
|
2346
2331
|
/**
|
|
@@ -2555,6 +2540,7 @@ declare class RedisRedirectManager implements RedirectManager {
|
|
|
2555
2540
|
declare class DiskSitemapStorage implements SitemapStorage {
|
|
2556
2541
|
private outDir;
|
|
2557
2542
|
private baseUrl;
|
|
2543
|
+
private runtime;
|
|
2558
2544
|
constructor(outDir: string, baseUrl: string);
|
|
2559
2545
|
/**
|
|
2560
2546
|
* Writes sitemap content to a file on the local disk.
|
|
@@ -3065,6 +3051,10 @@ interface CompressionConfig {
|
|
|
3065
3051
|
* 將 AsyncIterable<string> 壓縮為 Buffer。
|
|
3066
3052
|
* 適用於需要完整壓縮結果的場景(如 S3 Upload)。
|
|
3067
3053
|
*
|
|
3054
|
+
* 使用 RuntimeCompressionAdapter 自動選擇最佳壓縮實作:
|
|
3055
|
+
* - Bun: 原生 C++ 壓縮(2-5x 更快)
|
|
3056
|
+
* - Node.js: node:zlib fallback
|
|
3057
|
+
*
|
|
3068
3058
|
* @param source - 輸入的字串串流
|
|
3069
3059
|
* @param config - 壓縮設定
|
|
3070
3060
|
* @returns 壓縮後的 Buffer
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
createCompressionStream,
|
|
5
5
|
fromGzipFilename,
|
|
6
6
|
toGzipFilename
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-O3SCFF44.js";
|
|
8
8
|
|
|
9
9
|
// src/core/ChangeTracker.ts
|
|
10
10
|
var MemoryChangeTracker = class {
|
|
@@ -185,10 +185,6 @@ var RedisChangeTracker = class {
|
|
|
185
185
|
|
|
186
186
|
// src/core/DiffCalculator.ts
|
|
187
187
|
var DiffCalculator = class {
|
|
188
|
-
batchSize;
|
|
189
|
-
constructor(options = {}) {
|
|
190
|
-
this.batchSize = options.batchSize || 1e4;
|
|
191
|
-
}
|
|
192
188
|
/**
|
|
193
189
|
* Calculates the difference between two sets of sitemap entries.
|
|
194
190
|
*
|
|
@@ -396,9 +392,11 @@ var ShadowProcessor = class {
|
|
|
396
392
|
};
|
|
397
393
|
|
|
398
394
|
// src/core/SitemapIndex.ts
|
|
395
|
+
import { getEscapeHtml } from "@gravito/core";
|
|
399
396
|
var SitemapIndex = class {
|
|
400
397
|
options;
|
|
401
398
|
entries = [];
|
|
399
|
+
escapeHtml = getEscapeHtml();
|
|
402
400
|
constructor(options) {
|
|
403
401
|
this.options = { ...options };
|
|
404
402
|
if (this.options.baseUrl.endsWith("/")) {
|
|
@@ -454,7 +452,7 @@ var SitemapIndex = class {
|
|
|
454
452
|
loc = baseUrl + loc;
|
|
455
453
|
}
|
|
456
454
|
xml += `${indent}<sitemap>${nl}`;
|
|
457
|
-
xml += `${subIndent}<loc>${this.
|
|
455
|
+
xml += `${subIndent}<loc>${this.escapeHtml(loc)}</loc>${nl}`;
|
|
458
456
|
if (entry.lastmod) {
|
|
459
457
|
const date = entry.lastmod instanceof Date ? entry.lastmod : new Date(entry.lastmod);
|
|
460
458
|
xml += `${subIndent}<lastmod>${date.toISOString().split("T")[0]}</lastmod>${nl}`;
|
|
@@ -467,12 +465,10 @@ var SitemapIndex = class {
|
|
|
467
465
|
/**
|
|
468
466
|
* Escapes special XML characters in a string.
|
|
469
467
|
*/
|
|
470
|
-
escape(str) {
|
|
471
|
-
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
472
|
-
}
|
|
473
468
|
};
|
|
474
469
|
|
|
475
470
|
// src/core/SitemapStream.ts
|
|
471
|
+
import { getEscapeHtml as getEscapeHtml2 } from "@gravito/core";
|
|
476
472
|
var SitemapStream = class {
|
|
477
473
|
options;
|
|
478
474
|
entries = [];
|
|
@@ -482,6 +478,7 @@ var SitemapStream = class {
|
|
|
482
478
|
hasNews: false,
|
|
483
479
|
hasAlternates: false
|
|
484
480
|
};
|
|
481
|
+
escapeHtml = getEscapeHtml2();
|
|
485
482
|
constructor(options) {
|
|
486
483
|
this.options = { ...options };
|
|
487
484
|
if (this.options.baseUrl.endsWith("/")) {
|
|
@@ -561,8 +558,19 @@ var SitemapStream = class {
|
|
|
561
558
|
yield '<?xml version="1.0" encoding="UTF-8"?>\n';
|
|
562
559
|
yield this.buildUrlsetOpenTag();
|
|
563
560
|
const { baseUrl, pretty } = this.options;
|
|
561
|
+
let buffer = "";
|
|
562
|
+
let count = 0;
|
|
564
563
|
for (const entry of this.entries) {
|
|
565
|
-
|
|
564
|
+
buffer += this.renderUrl(entry, baseUrl, pretty);
|
|
565
|
+
count++;
|
|
566
|
+
if (count >= 5e3) {
|
|
567
|
+
yield buffer;
|
|
568
|
+
buffer = "";
|
|
569
|
+
count = 0;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
if (buffer) {
|
|
573
|
+
yield buffer;
|
|
566
574
|
}
|
|
567
575
|
yield "</urlset>";
|
|
568
576
|
}
|
|
@@ -615,7 +623,7 @@ var SitemapStream = class {
|
|
|
615
623
|
loc = baseUrl + loc;
|
|
616
624
|
}
|
|
617
625
|
parts.push(`${indent}<url>${nl}`);
|
|
618
|
-
parts.push(`${subIndent}<loc>${this.
|
|
626
|
+
parts.push(`${subIndent}<loc>${this.escapeHtml(loc)}</loc>${nl}`);
|
|
619
627
|
if (entry.lastmod) {
|
|
620
628
|
const date = entry.lastmod instanceof Date ? entry.lastmod : new Date(entry.lastmod);
|
|
621
629
|
parts.push(`${subIndent}<lastmod>${date.toISOString().split("T")[0]}</lastmod>${nl}`);
|
|
@@ -636,7 +644,7 @@ var SitemapStream = class {
|
|
|
636
644
|
altLoc = baseUrl + altLoc;
|
|
637
645
|
}
|
|
638
646
|
parts.push(
|
|
639
|
-
`${subIndent}<xhtml:link rel="alternate" hreflang="${alt.lang}" href="${this.
|
|
647
|
+
`${subIndent}<xhtml:link rel="alternate" hreflang="${alt.lang}" href="${this.escapeHtml(altLoc)}"/>${nl}`
|
|
640
648
|
);
|
|
641
649
|
}
|
|
642
650
|
}
|
|
@@ -649,7 +657,7 @@ var SitemapStream = class {
|
|
|
649
657
|
canonicalUrl = baseUrl + canonicalUrl;
|
|
650
658
|
}
|
|
651
659
|
parts.push(
|
|
652
|
-
`${subIndent}<xhtml:link rel="canonical" href="${this.
|
|
660
|
+
`${subIndent}<xhtml:link rel="canonical" href="${this.escapeHtml(canonicalUrl)}"/>${nl}`
|
|
653
661
|
);
|
|
654
662
|
}
|
|
655
663
|
if (entry.redirect && !entry.redirect.canonical) {
|
|
@@ -667,23 +675,23 @@ var SitemapStream = class {
|
|
|
667
675
|
loc2 = baseUrl + loc2;
|
|
668
676
|
}
|
|
669
677
|
parts.push(`${subIndent}<image:image>${nl}`);
|
|
670
|
-
parts.push(`${subIndent} <image:loc>${this.
|
|
678
|
+
parts.push(`${subIndent} <image:loc>${this.escapeHtml(loc2)}</image:loc>${nl}`);
|
|
671
679
|
if (img.title) {
|
|
672
|
-
parts.push(`${subIndent} <image:title>${this.
|
|
680
|
+
parts.push(`${subIndent} <image:title>${this.escapeHtml(img.title)}</image:title>${nl}`);
|
|
673
681
|
}
|
|
674
682
|
if (img.caption) {
|
|
675
683
|
parts.push(
|
|
676
|
-
`${subIndent} <image:caption>${this.
|
|
684
|
+
`${subIndent} <image:caption>${this.escapeHtml(img.caption)}</image:caption>${nl}`
|
|
677
685
|
);
|
|
678
686
|
}
|
|
679
687
|
if (img.geo_location) {
|
|
680
688
|
parts.push(
|
|
681
|
-
`${subIndent} <image:geo_location>${this.
|
|
689
|
+
`${subIndent} <image:geo_location>${this.escapeHtml(img.geo_location)}</image:geo_location>${nl}`
|
|
682
690
|
);
|
|
683
691
|
}
|
|
684
692
|
if (img.license) {
|
|
685
693
|
parts.push(
|
|
686
|
-
`${subIndent} <image:license>${this.
|
|
694
|
+
`${subIndent} <image:license>${this.escapeHtml(img.license)}</image:license>${nl}`
|
|
687
695
|
);
|
|
688
696
|
}
|
|
689
697
|
parts.push(`${subIndent}</image:image>${nl}`);
|
|
@@ -693,20 +701,20 @@ var SitemapStream = class {
|
|
|
693
701
|
for (const video of entry.videos) {
|
|
694
702
|
parts.push(`${subIndent}<video:video>${nl}`);
|
|
695
703
|
parts.push(
|
|
696
|
-
`${subIndent} <video:thumbnail_loc>${this.
|
|
704
|
+
`${subIndent} <video:thumbnail_loc>${this.escapeHtml(video.thumbnail_loc)}</video:thumbnail_loc>${nl}`
|
|
697
705
|
);
|
|
698
|
-
parts.push(`${subIndent} <video:title>${this.
|
|
706
|
+
parts.push(`${subIndent} <video:title>${this.escapeHtml(video.title)}</video:title>${nl}`);
|
|
699
707
|
parts.push(
|
|
700
|
-
`${subIndent} <video:description>${this.
|
|
708
|
+
`${subIndent} <video:description>${this.escapeHtml(video.description)}</video:description>${nl}`
|
|
701
709
|
);
|
|
702
710
|
if (video.content_loc) {
|
|
703
711
|
parts.push(
|
|
704
|
-
`${subIndent} <video:content_loc>${this.
|
|
712
|
+
`${subIndent} <video:content_loc>${this.escapeHtml(video.content_loc)}</video:content_loc>${nl}`
|
|
705
713
|
);
|
|
706
714
|
}
|
|
707
715
|
if (video.player_loc) {
|
|
708
716
|
parts.push(
|
|
709
|
-
`${subIndent} <video:player_loc>${this.
|
|
717
|
+
`${subIndent} <video:player_loc>${this.escapeHtml(video.player_loc)}</video:player_loc>${nl}`
|
|
710
718
|
);
|
|
711
719
|
}
|
|
712
720
|
if (video.duration) {
|
|
@@ -728,7 +736,7 @@ var SitemapStream = class {
|
|
|
728
736
|
}
|
|
729
737
|
if (video.tag) {
|
|
730
738
|
for (const tag of video.tag) {
|
|
731
|
-
parts.push(`${subIndent} <video:tag>${this.
|
|
739
|
+
parts.push(`${subIndent} <video:tag>${this.escapeHtml(tag)}</video:tag>${nl}`);
|
|
732
740
|
}
|
|
733
741
|
}
|
|
734
742
|
parts.push(`${subIndent}</video:video>${nl}`);
|
|
@@ -738,25 +746,25 @@ var SitemapStream = class {
|
|
|
738
746
|
parts.push(`${subIndent}<news:news>${nl}`);
|
|
739
747
|
parts.push(`${subIndent} <news:publication>${nl}`);
|
|
740
748
|
parts.push(
|
|
741
|
-
`${subIndent} <news:name>${this.
|
|
749
|
+
`${subIndent} <news:name>${this.escapeHtml(entry.news.publication.name)}</news:name>${nl}`
|
|
742
750
|
);
|
|
743
751
|
parts.push(
|
|
744
|
-
`${subIndent} <news:language>${this.
|
|
752
|
+
`${subIndent} <news:language>${this.escapeHtml(entry.news.publication.language)}</news:language>${nl}`
|
|
745
753
|
);
|
|
746
754
|
parts.push(`${subIndent} </news:publication>${nl}`);
|
|
747
755
|
const pubDate = entry.news.publication_date instanceof Date ? entry.news.publication_date : new Date(entry.news.publication_date);
|
|
748
756
|
parts.push(
|
|
749
757
|
`${subIndent} <news:publication_date>${pubDate.toISOString()}</news:publication_date>${nl}`
|
|
750
758
|
);
|
|
751
|
-
parts.push(`${subIndent} <news:title>${this.
|
|
759
|
+
parts.push(`${subIndent} <news:title>${this.escapeHtml(entry.news.title)}</news:title>${nl}`);
|
|
752
760
|
if (entry.news.genres) {
|
|
753
761
|
parts.push(
|
|
754
|
-
`${subIndent} <news:genres>${this.
|
|
762
|
+
`${subIndent} <news:genres>${this.escapeHtml(entry.news.genres)}</news:genres>${nl}`
|
|
755
763
|
);
|
|
756
764
|
}
|
|
757
765
|
if (entry.news.keywords) {
|
|
758
766
|
parts.push(
|
|
759
|
-
`${subIndent} <news:keywords>${entry.news.keywords.map((k) => this.
|
|
767
|
+
`${subIndent} <news:keywords>${entry.news.keywords.map((k) => this.escapeHtml(k)).join(", ")}</news:keywords>${nl}`
|
|
760
768
|
);
|
|
761
769
|
}
|
|
762
770
|
parts.push(`${subIndent}</news:news>${nl}`);
|
|
@@ -767,9 +775,6 @@ var SitemapStream = class {
|
|
|
767
775
|
/**
|
|
768
776
|
* Escapes special XML characters in a string.
|
|
769
777
|
*/
|
|
770
|
-
escape(str) {
|
|
771
|
-
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
772
|
-
}
|
|
773
778
|
/**
|
|
774
779
|
* Returns all entries currently in the stream.
|
|
775
780
|
*
|
|
@@ -1089,7 +1094,6 @@ var SitemapParser = class {
|
|
|
1089
1094
|
var IncrementalGenerator = class {
|
|
1090
1095
|
options;
|
|
1091
1096
|
changeTracker;
|
|
1092
|
-
diffCalculator;
|
|
1093
1097
|
generator;
|
|
1094
1098
|
mutex = new Mutex();
|
|
1095
1099
|
constructor(options) {
|
|
@@ -1099,7 +1103,6 @@ var IncrementalGenerator = class {
|
|
|
1099
1103
|
...options
|
|
1100
1104
|
};
|
|
1101
1105
|
this.changeTracker = this.options.changeTracker;
|
|
1102
|
-
this.diffCalculator = this.options.diffCalculator || new DiffCalculator();
|
|
1103
1106
|
this.generator = new SitemapGenerator(this.options);
|
|
1104
1107
|
}
|
|
1105
1108
|
/**
|
|
@@ -1347,6 +1350,7 @@ var ProgressTracker = class {
|
|
|
1347
1350
|
console.error("[ProgressTracker] Failed to flush progress:", err);
|
|
1348
1351
|
});
|
|
1349
1352
|
}, this.updateInterval);
|
|
1353
|
+
this.updateTimer.unref?.();
|
|
1350
1354
|
}
|
|
1351
1355
|
}
|
|
1352
1356
|
/**
|
|
@@ -1804,7 +1808,6 @@ var MemoryLock = class {
|
|
|
1804
1808
|
};
|
|
1805
1809
|
|
|
1806
1810
|
// src/locks/RedisLock.ts
|
|
1807
|
-
import { randomUUID } from "crypto";
|
|
1808
1811
|
var RedisLock = class {
|
|
1809
1812
|
/**
|
|
1810
1813
|
* Constructs a new RedisLock instance with the specified configuration.
|
|
@@ -1837,7 +1840,7 @@ var RedisLock = class {
|
|
|
1837
1840
|
* UUIDs are sufficiently random to prevent lock hijacking across instances.
|
|
1838
1841
|
* However, they are stored in plain text in Redis (not encrypted).
|
|
1839
1842
|
*/
|
|
1840
|
-
lockId = randomUUID();
|
|
1843
|
+
lockId = crypto.randomUUID();
|
|
1841
1844
|
/**
|
|
1842
1845
|
* Redis key prefix for all locks acquired through this instance.
|
|
1843
1846
|
*
|
|
@@ -2067,9 +2070,6 @@ var RedisLock = class {
|
|
|
2067
2070
|
}
|
|
2068
2071
|
};
|
|
2069
2072
|
|
|
2070
|
-
// src/OrbitSitemap.ts
|
|
2071
|
-
import { randomUUID as randomUUID2 } from "crypto";
|
|
2072
|
-
|
|
2073
2073
|
// src/redirect/RedirectHandler.ts
|
|
2074
2074
|
var RedirectHandler = class {
|
|
2075
2075
|
options;
|
|
@@ -2084,7 +2084,6 @@ var RedirectHandler = class {
|
|
|
2084
2084
|
*/
|
|
2085
2085
|
async processEntries(entries) {
|
|
2086
2086
|
const { manager, strategy, followChains, maxChainLength } = this.options;
|
|
2087
|
-
const _processedEntries = [];
|
|
2088
2087
|
const redirectMap = /* @__PURE__ */ new Map();
|
|
2089
2088
|
for (const entry of entries) {
|
|
2090
2089
|
const redirectTarget = await manager.resolve(entry.url, followChains, maxChainLength);
|
|
@@ -2435,7 +2434,7 @@ var OrbitSitemap = class _OrbitSitemap {
|
|
|
2435
2434
|
const opts = this.options;
|
|
2436
2435
|
let storage = opts.storage;
|
|
2437
2436
|
if (!storage) {
|
|
2438
|
-
const { DiskSitemapStorage: DiskSitemapStorage2 } = await import("./DiskSitemapStorage-
|
|
2437
|
+
const { DiskSitemapStorage: DiskSitemapStorage2 } = await import("./DiskSitemapStorage-6VXPMKLB.js");
|
|
2439
2438
|
storage = new DiskSitemapStorage2(opts.outDir, opts.baseUrl);
|
|
2440
2439
|
}
|
|
2441
2440
|
let providers = opts.providers;
|
|
@@ -2481,7 +2480,7 @@ var OrbitSitemap = class _OrbitSitemap {
|
|
|
2481
2480
|
}
|
|
2482
2481
|
let storage = opts.storage;
|
|
2483
2482
|
if (!storage) {
|
|
2484
|
-
const { DiskSitemapStorage: DiskSitemapStorage2 } = await import("./DiskSitemapStorage-
|
|
2483
|
+
const { DiskSitemapStorage: DiskSitemapStorage2 } = await import("./DiskSitemapStorage-6VXPMKLB.js");
|
|
2485
2484
|
storage = new DiskSitemapStorage2(opts.outDir, opts.baseUrl);
|
|
2486
2485
|
}
|
|
2487
2486
|
const incrementalGenerator = new IncrementalGenerator({
|
|
@@ -2507,10 +2506,10 @@ var OrbitSitemap = class _OrbitSitemap {
|
|
|
2507
2506
|
throw new Error("generateAsync() can only be called in static mode");
|
|
2508
2507
|
}
|
|
2509
2508
|
const opts = this.options;
|
|
2510
|
-
const jobId =
|
|
2509
|
+
const jobId = crypto.randomUUID();
|
|
2511
2510
|
let storage = opts.storage;
|
|
2512
2511
|
if (!storage) {
|
|
2513
|
-
const { DiskSitemapStorage: DiskSitemapStorage2 } = await import("./DiskSitemapStorage-
|
|
2512
|
+
const { DiskSitemapStorage: DiskSitemapStorage2 } = await import("./DiskSitemapStorage-6VXPMKLB.js");
|
|
2514
2513
|
storage = new DiskSitemapStorage2(opts.outDir, opts.baseUrl);
|
|
2515
2514
|
}
|
|
2516
2515
|
let providers = opts.providers;
|
|
@@ -2764,7 +2763,11 @@ var RedirectDetector = class {
|
|
|
2764
2763
|
}
|
|
2765
2764
|
try {
|
|
2766
2765
|
const { connection, table, columns } = database;
|
|
2767
|
-
const
|
|
2766
|
+
const safeTable = this.assertSafeIdentifier(table);
|
|
2767
|
+
const safeFrom = this.assertSafeIdentifier(columns.from);
|
|
2768
|
+
const safeTo = this.assertSafeIdentifier(columns.to);
|
|
2769
|
+
const safeType = this.assertSafeIdentifier(columns.type);
|
|
2770
|
+
const query = `SELECT ${safeFrom}, ${safeTo}, ${safeType} FROM ${safeTable} WHERE ${safeFrom} = ? LIMIT 1`;
|
|
2768
2771
|
const results = await connection.query(query, [url]);
|
|
2769
2772
|
if (results.length === 0) {
|
|
2770
2773
|
return null;
|
|
@@ -2810,6 +2813,7 @@ var RedirectDetector = class {
|
|
|
2810
2813
|
const timeout = autoDetect.timeout || 5e3;
|
|
2811
2814
|
const controller = new AbortController();
|
|
2812
2815
|
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
2816
|
+
timeoutId.unref?.();
|
|
2813
2817
|
try {
|
|
2814
2818
|
const response = await fetch(fullUrl, {
|
|
2815
2819
|
method: "HEAD",
|
|
@@ -2851,6 +2855,12 @@ var RedirectDetector = class {
|
|
|
2851
2855
|
expires: Date.now() + ttl
|
|
2852
2856
|
});
|
|
2853
2857
|
}
|
|
2858
|
+
assertSafeIdentifier(identifier) {
|
|
2859
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(identifier)) {
|
|
2860
|
+
throw new Error(`Invalid database identifier: ${identifier}`);
|
|
2861
|
+
}
|
|
2862
|
+
return identifier;
|
|
2863
|
+
}
|
|
2854
2864
|
};
|
|
2855
2865
|
|
|
2856
2866
|
// src/redirect/RedirectManager.ts
|
|
@@ -3245,7 +3255,6 @@ var GCPSitemapStorage = class {
|
|
|
3245
3255
|
});
|
|
3246
3256
|
for (const shadowFile of shadowFiles) {
|
|
3247
3257
|
const originalKey = shadowFile.name.replace(/\.shadow\.[^/]+$/, "");
|
|
3248
|
-
const _originalFilename = originalKey.replace(prefix, "");
|
|
3249
3258
|
if (this.shadowMode === "atomic") {
|
|
3250
3259
|
await shadowFile.copy(bucket.file(originalKey));
|
|
3251
3260
|
await shadowFile.delete();
|
|
@@ -3732,7 +3741,6 @@ var S3SitemapStorage = class {
|
|
|
3732
3741
|
continue;
|
|
3733
3742
|
}
|
|
3734
3743
|
const originalKey = shadowFile.Key.replace(/\.shadow\.[^/]+$/, "");
|
|
3735
|
-
const _originalFilename = originalKey.replace(prefix, "");
|
|
3736
3744
|
if (this.shadowMode === "atomic") {
|
|
3737
3745
|
await s3.client.send(
|
|
3738
3746
|
new s3.CopyObjectCommand({
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gravito/constellation",
|
|
3
|
-
"
|
|
3
|
+
"sideEffects": false,
|
|
4
|
+
"version": "3.1.3",
|
|
4
5
|
"description": "Powerful sitemap generation for Gravito applications with dynamic/static support, sharding, and caching.",
|
|
5
6
|
"main": "./dist/index.cjs",
|
|
6
7
|
"module": "./dist/index.js",
|
|
@@ -8,6 +9,7 @@
|
|
|
8
9
|
"types": "./dist/index.d.ts",
|
|
9
10
|
"exports": {
|
|
10
11
|
".": {
|
|
12
|
+
"bun": "./dist/index.js",
|
|
11
13
|
"types": "./dist/index.d.ts",
|
|
12
14
|
"import": "./dist/index.js",
|
|
13
15
|
"require": "./dist/index.cjs"
|
|
@@ -20,6 +22,7 @@
|
|
|
20
22
|
],
|
|
21
23
|
"scripts": {
|
|
22
24
|
"build": "bun run build.ts",
|
|
25
|
+
"build:dts": "bun run build.ts --dts-only",
|
|
23
26
|
"typecheck": "bun tsc -p tsconfig.json --noEmit --skipLibCheck",
|
|
24
27
|
"test": "bun test --timeout=10000",
|
|
25
28
|
"test:coverage": "bun test --timeout=10000 --coverage --coverage-reporter=lcov --coverage-dir coverage && bun run --bun scripts/check-coverage.ts",
|
|
@@ -28,18 +31,18 @@
|
|
|
28
31
|
"test:integration": "test $(find tests -name '*.integration.test.ts' 2>/dev/null | wc -l) -gt 0 && find tests -name '*.integration.test.ts' -print0 | xargs -0 bun test --timeout=10000 || echo 'No integration tests found'"
|
|
29
32
|
},
|
|
30
33
|
"peerDependencies": {
|
|
31
|
-
"@gravito/core": "^
|
|
34
|
+
"@gravito/core": "^2.0.0"
|
|
32
35
|
},
|
|
33
36
|
"dependencies": {
|
|
34
37
|
"@aws-sdk/client-s3": "^3.956.0",
|
|
35
38
|
"@google-cloud/storage": "^7.18.0",
|
|
36
|
-
"@gravito/stream": "^2.
|
|
37
|
-
"@gravito/photon": "^1.
|
|
39
|
+
"@gravito/stream": "^2.1.1",
|
|
40
|
+
"@gravito/photon": "^1.1.3"
|
|
38
41
|
},
|
|
39
42
|
"devDependencies": {
|
|
40
43
|
"bun-plugin-dts": "^0.3.0",
|
|
41
44
|
"bun-types": "latest",
|
|
42
|
-
"@gravito/core": "^
|
|
45
|
+
"@gravito/core": "^2.0.6",
|
|
43
46
|
"tsup": "^8.5.1",
|
|
44
47
|
"typescript": "^5.9.3"
|
|
45
48
|
},
|