@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.
Files changed (3) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/package.json +1 -1
  3. 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cocreate/sitemap",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "A simple sitemap component in vanilla javascript. Easily configured using HTML5 data-attributes and/or JavaScript API.",
5
5
  "keywords": [
6
6
  "sitemap",
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(render) {
27
- this.render = render;
24
+ constructor(crud) {
25
+ this.crud = crud;
28
26
  }
29
27
 
30
- async updateUrlInSitemap(urlToFind) {
31
- try {
32
- // Path to your sitemap file
33
- const sitemapPath = '/path/to/your/sitemap.xml';
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
- // Read and parse the sitemap XML
36
- let sitemapXml = fs.readFileSync(sitemapPath, 'utf8');
33
+ // Ensure the file is public
34
+ if (!file.public || file.public === "false")
35
+ return;
37
36
 
38
- // Regex pattern to find the entire <url>...</url> block containing the URL
39
- const regexPattern = `<url>\\s*<loc>${urlToFind}</loc>[\\s\\S]*?</url>`;
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
- // Perform regex search
42
- const match = sitemapXml.match(regexPattern);
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
- if (match) {
45
- const position = match.index; // Start position of the <url> block
46
- const endPosition = match.index + match[0].length; // End position of the <url> block
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
- // Replace the matched <url> block with a modified version (example)
50
- const modifiedUrlBlock = `<url>
51
- <loc>${urlToFind}</loc>
52
- <lastmod>${new Date().toISOString()}</lastmod>
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
- // Replace the original <url> block with the modified one
57
- sitemapXml = sitemapXml.slice(0, position) + modifiedUrlBlock + sitemapXml.slice(endPosition);
56
+ let { mainSitemap, sitemap } = await this.getSitemap(file, host);
58
57
 
59
- // Write back the modified sitemap XML to the file (optional)
60
- fs.writeFileSync(sitemapPath, sitemapXml);
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
- console.log('Sitemap updated successfully.');
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
- console.log(`URL ${urlToFind} not found in sitemap.`);
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;