@cocreate/sitemap 1.0.0 → 1.1.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/CHANGELOG.md +14 -0
- package/package.json +1 -1
- package/src/index.js +256 -30
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
# [1.1.0](https://github.com/CoCreate-app/CoCreate-sitemap/compare/v1.0.0...v1.1.0) (2024-08-24)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* apply host for crud ([9a9fbf0](https://github.com/CoCreate-app/CoCreate-sitemap/commit/9a9fbf0c636d6aa50a607e9fdd2cf96c29f0e1bc))
|
|
7
|
+
* check logic to auto add html files to sitemap ([6ca692b](https://github.com/CoCreate-app/CoCreate-sitemap/commit/6ca692b486ad2f26c7140349756ebc7454ce2e00))
|
|
8
|
+
* handle mainSitmap and sitmap as a file object ([99b97c3](https://github.com/CoCreate-app/CoCreate-sitemap/commit/99b97c3aa3198ea090a2efe8d9d2af5875af73ed))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* create multiple sitemaps for the various supported types ([98af2dc](https://github.com/CoCreate-app/CoCreate-sitemap/commit/98af2dc972ae15a34caaf709358d543f67828e95))
|
|
14
|
+
|
|
1
15
|
# 1.0.0 (2024-06-27)
|
|
2
16
|
|
|
3
17
|
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -20,53 +20,279 @@
|
|
|
20
20
|
// you must obtain a commercial license from CoCreate LLC.
|
|
21
21
|
// For details, visit <https://cocreate.app/licenses/> or contact us at sales@cocreate.app.
|
|
22
22
|
|
|
23
|
-
const fs = require('fs');
|
|
24
|
-
|
|
25
23
|
class CoCreateSitemap {
|
|
26
|
-
constructor(
|
|
27
|
-
this.
|
|
24
|
+
constructor(crud) {
|
|
25
|
+
this.crud = crud;
|
|
28
26
|
}
|
|
29
27
|
|
|
30
|
-
async
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
28
|
+
async check(file, host) {
|
|
29
|
+
// Ensure the file is HTML and does not have a sitemap object yet
|
|
30
|
+
if (!file.sitemap && file['content-type'] !== 'text/html')
|
|
31
|
+
return;
|
|
34
32
|
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
// Ensure the file is public
|
|
34
|
+
if (!file.public || file.public === "false")
|
|
35
|
+
return;
|
|
37
36
|
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
// Check if the file is HTML and contains a noindex meta tag
|
|
38
|
+
if (file['content-type'] === 'text/html' && file.src.includes('<meta name="robots" content="noindex">'))
|
|
39
|
+
return;
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
// Compare the lastmod date in the sitemap with the modified.on date
|
|
42
|
+
if (file.sitemap && file.sitemap.lastmod && file.modified.on) {
|
|
43
|
+
if (new Date(file.sitemap.lastmod) >= new Date(file.modified.on))
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
43
46
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
console.log(`URL ${urlToFind} found in sitemap at position ${position}-${endPosition}.`);
|
|
47
|
+
// Logic to update the sitemap
|
|
48
|
+
this.updateSitemap(file, host);
|
|
49
|
+
}
|
|
48
50
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
<priority>1.0</priority>
|
|
54
|
-
</url>`;
|
|
51
|
+
async updateSitemap(file, host) {
|
|
52
|
+
try {
|
|
53
|
+
// TODO: need to get info such as host
|
|
54
|
+
const entry = this.createEntry(file);
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
sitemapXml = sitemapXml.slice(0, position) + modifiedUrlBlock + sitemapXml.slice(endPosition);
|
|
56
|
+
let { mainSitemap, sitemap } = await this.getSitemap(file, host);
|
|
58
57
|
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
if (file.sitemap.pathname) {
|
|
59
|
+
// Perform regex search starting at the pathname
|
|
60
|
+
const regexPattern = `<url>\\s*<loc>.*?${file.sitemap.pathname.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}.*?</loc>[\\s\\S]*?</url>`;
|
|
61
|
+
const match = sitemap.src.match(new RegExp(regexPattern));
|
|
61
62
|
|
|
62
|
-
|
|
63
|
+
if (match) {
|
|
64
|
+
const position = match.index; // Start position of the <url> block
|
|
65
|
+
const endPosition = match.index + match[0].length; // End position of the <url> block
|
|
66
|
+
|
|
67
|
+
// Replace the original <url> block with the modified one
|
|
68
|
+
sitemap.src = sitemap.src.slice(0, position) + entry + sitemap.src.slice(endPosition);
|
|
69
|
+
} else {
|
|
70
|
+
sitemap.src = sitemap.src.replace('</urlset>', `${entry}</urlset>`);
|
|
71
|
+
}
|
|
63
72
|
} else {
|
|
64
|
-
|
|
73
|
+
file.sitemap.pathname = sitemap.pathname
|
|
74
|
+
sitemap.src = sitemap.src.replace('</urlset>', `${entry}</urlset>`);
|
|
65
75
|
}
|
|
76
|
+
|
|
77
|
+
this.saveSitemap(mainSitemap, host);
|
|
78
|
+
this.saveSitemap(sitemap, host);
|
|
79
|
+
this.saveSitemap(file, host);
|
|
80
|
+
|
|
81
|
+
// console.log('Sitemap updated successfully.');
|
|
66
82
|
} catch (err) {
|
|
67
83
|
console.error('Error updating sitemap:', err);
|
|
68
84
|
}
|
|
69
85
|
}
|
|
86
|
+
|
|
87
|
+
createEntry(file) {
|
|
88
|
+
const depth = (file.pathname.match(/\//g) || []).length;
|
|
89
|
+
const priority = Math.max(0.1, 1.0 - (depth - 1) * 0.1).toFixed(1);
|
|
90
|
+
|
|
91
|
+
const defaultKeys = {
|
|
92
|
+
loc: file.pathname,
|
|
93
|
+
lastmod: file.modified.on,
|
|
94
|
+
changefreq: 'monthly', // Example default value
|
|
95
|
+
priority: priority,
|
|
96
|
+
};
|
|
97
|
+
// Merge default keys with file.sitemap, prioritizing file.sitemap values
|
|
98
|
+
file.sitemap = { ...defaultKeys, ...file.sitemap };
|
|
99
|
+
file.sitemap.lastmod = file.modified.on;
|
|
100
|
+
|
|
101
|
+
let entry = `\t<url>\n`;
|
|
102
|
+
|
|
103
|
+
for (const key of Object.keys(file.sitemap)) {
|
|
104
|
+
if (key === 'pathname')
|
|
105
|
+
continue
|
|
106
|
+
const value = file.sitemap[key];
|
|
107
|
+
|
|
108
|
+
if (typeof value === 'object' && value !== null) {
|
|
109
|
+
entry += `\t\t<${key}:${key}>\n`;
|
|
110
|
+
|
|
111
|
+
for (const nestedKey of Object.keys(value)) {
|
|
112
|
+
const nestedValue = value[nestedKey];
|
|
113
|
+
entry += `\t\t\t<${key}:${nestedKey}>${nestedValue}</${key}:${nestedKey}>\n`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
entry += `\t\t</${key}:${key}>\n`;
|
|
117
|
+
} else {
|
|
118
|
+
entry += `\t\t<${key}>${value}</${key}>\n`;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
entry += `\t</url>\n`;
|
|
123
|
+
|
|
124
|
+
return entry;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async getSitemap(file, host) {
|
|
128
|
+
let mainSitemap = {
|
|
129
|
+
host: file.host,
|
|
130
|
+
name: 'sitemap.xml',
|
|
131
|
+
path: '/',
|
|
132
|
+
pathname: '/sitemap.xml',
|
|
133
|
+
directory: '/',
|
|
134
|
+
'content-type': 'application/xml',
|
|
135
|
+
public: true,
|
|
136
|
+
organization_id: file.organization_id
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
mainSitemap = await this.readSitemap(mainSitemap, host);
|
|
140
|
+
if (!mainSitemap.src)
|
|
141
|
+
mainSitemap.src = this.createSitemap('main')
|
|
142
|
+
|
|
143
|
+
let sitemap = {
|
|
144
|
+
host: file.host,
|
|
145
|
+
path: '/',
|
|
146
|
+
pathname: file.sitemap.pathname,
|
|
147
|
+
directory: '/',
|
|
148
|
+
'content-type': 'application/xml',
|
|
149
|
+
public: true,
|
|
150
|
+
organization_id: file.organization_id
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Update loc using pathname
|
|
154
|
+
file.sitemap.loc = `${file.pathname}`
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
// Query the database for the correct sitemap based on the loc and type
|
|
158
|
+
if (file.sitemap.pathname) {
|
|
159
|
+
sitemap = await this.readSitemap(sitemap, host);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (!sitemap.src) {
|
|
163
|
+
let type = 'sitemap';
|
|
164
|
+
|
|
165
|
+
// Identify content type to determine sitemap type
|
|
166
|
+
if (file['content-type'].startsWith('image/')) {
|
|
167
|
+
type = 'image';
|
|
168
|
+
} else if (file['content-type'].startsWith('video/')) {
|
|
169
|
+
type = 'video';
|
|
170
|
+
} else if (file.sitemap.news) {
|
|
171
|
+
// type = 'news';
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
let name = `sitemap`
|
|
175
|
+
if (type === 'image' || type === 'video' || type === 'news')
|
|
176
|
+
name = `sitemap-${type}`
|
|
177
|
+
|
|
178
|
+
// If no existing sitemap found check last index sitemap
|
|
179
|
+
let index = await this.getLastSitemapIndex(mainSitemap, name);
|
|
180
|
+
if (index) {
|
|
181
|
+
sitemap.pathname = `/${name}${index}.xml`
|
|
182
|
+
sitemap = await this.readSitemap(sitemap, host);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Check if there's room in the last index sitemap
|
|
186
|
+
if (!this.checkSitemap(sitemap.src)) {
|
|
187
|
+
if (sitemap.src)
|
|
188
|
+
index += 1
|
|
189
|
+
sitemap.name = `${name}${index}.xml`
|
|
190
|
+
sitemap.pathname = `/${name}${index}.xml`
|
|
191
|
+
sitemap.src = this.createSitemap(type);
|
|
192
|
+
|
|
193
|
+
// Add the new sitemap entry
|
|
194
|
+
const indexEntry = `\n<sitemap>\n\t<loc>{{$host}}/${name}${index}.xml</loc>\n</sitemap>`;
|
|
195
|
+
mainSitemap.src = mainSitemap.src.replace('</sitemapindex>', `${indexEntry}\n</sitemapindex>`);
|
|
196
|
+
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return { mainSitemap, sitemap }
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async readSitemap(file, host) {
|
|
205
|
+
let data = {
|
|
206
|
+
method: 'object.read',
|
|
207
|
+
host: host,
|
|
208
|
+
array: 'files',
|
|
209
|
+
$filter: {
|
|
210
|
+
query: {
|
|
211
|
+
host: { $in: [host, '*'] },
|
|
212
|
+
pathname: file.pathname
|
|
213
|
+
},
|
|
214
|
+
limit: 1
|
|
215
|
+
},
|
|
216
|
+
organization_id: file.organization_id
|
|
217
|
+
}
|
|
218
|
+
data = await this.crud.send(data)
|
|
219
|
+
if (data.object && data.object.length)
|
|
220
|
+
return data.object[0]
|
|
221
|
+
else
|
|
222
|
+
return file
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
createSitemap(type) {
|
|
226
|
+
if (type === 'main')
|
|
227
|
+
return `<?xml version="1.0" encoding="UTF-8"?>\n<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n</sitemapindex>`;
|
|
228
|
+
else if (type === 'image' || type === 'video' || type === 'news')
|
|
229
|
+
return `<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:${type}="http://www.google.com/schemas/sitemap-${type}/1.1">\n</urlset>`;
|
|
230
|
+
else
|
|
231
|
+
return `<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n</urlset>`;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async saveSitemap(file, host) {
|
|
235
|
+
let data = {
|
|
236
|
+
method: 'object.update',
|
|
237
|
+
host: host,
|
|
238
|
+
array: 'files',
|
|
239
|
+
object: file,
|
|
240
|
+
upsert: true,
|
|
241
|
+
organization_id: file.organization_id
|
|
242
|
+
}
|
|
243
|
+
if (!file._id)
|
|
244
|
+
data.$filter = {
|
|
245
|
+
query: {
|
|
246
|
+
host: { $in: [host, '*'] },
|
|
247
|
+
pathname: file.pathname
|
|
248
|
+
},
|
|
249
|
+
limit: 1
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
data = await this.crud.send(data)
|
|
253
|
+
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async getLastSitemapIndex(mainSitemap, filename) {
|
|
257
|
+
try {
|
|
258
|
+
// Use regex to match all sitemap entries for the given type
|
|
259
|
+
const regex = new RegExp(`\\/${filename}(\\d*)\\.xml<\\/loc>`, 'g');
|
|
260
|
+
const matches = mainSitemap.src.match(regex);
|
|
261
|
+
|
|
262
|
+
return matches ? matches.length : 0;
|
|
263
|
+
} catch (err) {
|
|
264
|
+
console.error(`Error determining next sitemap index for ${filename}:`, err);
|
|
265
|
+
return null; // Or some default value or throw an error
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
checkSitemap(sitemap) {
|
|
270
|
+
try {
|
|
271
|
+
if (!sitemap)
|
|
272
|
+
return false
|
|
273
|
+
|
|
274
|
+
// Count the number of <url> entries
|
|
275
|
+
const urlCount = (sitemap.match(/<url>/g) || []).length;
|
|
276
|
+
if (urlCount >= 50000)
|
|
277
|
+
return false;
|
|
278
|
+
|
|
279
|
+
// Get the size of the sitemap string in bytes
|
|
280
|
+
const fileSizeInBytes = Buffer.byteLength(sitemap, 'utf8');
|
|
281
|
+
const fileSizeInMB = fileSizeInBytes / (1024 * 1024);
|
|
282
|
+
|
|
283
|
+
// console.log(`Sitemap has ${urlCount} entries and is ${fileSizeInMB.toFixed(2)} MB.`);
|
|
284
|
+
|
|
285
|
+
// Check if the file size exceeds either the 50MB limit or the MongoDB 16MB limit
|
|
286
|
+
if (fileSizeInMB >= 50 || fileSizeInMB >= 15)
|
|
287
|
+
return false;
|
|
288
|
+
|
|
289
|
+
return true;
|
|
290
|
+
} catch (err) {
|
|
291
|
+
console.error('Error checking sitemap file:', err);
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
70
296
|
}
|
|
71
297
|
|
|
72
298
|
module.exports = CoCreateSitemap;
|