strapi-sitemap-generator 0.1.1

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 (32) hide show
  1. package/README.md +27 -0
  2. package/dist/_chunks/App-Bvbv7t_S.mjs +25065 -0
  3. package/dist/_chunks/App-D449cQR3.js +25105 -0
  4. package/dist/_chunks/en-BptFMppp.mjs +7 -0
  5. package/dist/_chunks/en-CZAGT--R.js +7 -0
  6. package/dist/admin/index.js +67 -0
  7. package/dist/admin/index.mjs +68 -0
  8. package/dist/admin/src/components/Initializer.d.ts +5 -0
  9. package/dist/admin/src/components/PluginIcon.d.ts +2 -0
  10. package/dist/admin/src/index.d.ts +14 -0
  11. package/dist/admin/src/pages/App.d.ts +2 -0
  12. package/dist/admin/src/pages/HomePage.d.ts +2 -0
  13. package/dist/admin/src/pluginId.d.ts +1 -0
  14. package/dist/admin/src/utils/getTranslation.d.ts +2 -0
  15. package/dist/server/index.js +338 -0
  16. package/dist/server/index.mjs +339 -0
  17. package/dist/server/src/bootstrap.d.ts +5 -0
  18. package/dist/server/src/config/index.d.ts +5 -0
  19. package/dist/server/src/content-types/index.d.ts +2 -0
  20. package/dist/server/src/controllers/controller.d.ts +34 -0
  21. package/dist/server/src/controllers/index.d.ts +12 -0
  22. package/dist/server/src/destroy.d.ts +5 -0
  23. package/dist/server/src/index.d.ts +95 -0
  24. package/dist/server/src/middlewares/index.d.ts +2 -0
  25. package/dist/server/src/policies/index.d.ts +2 -0
  26. package/dist/server/src/register.d.ts +5 -0
  27. package/dist/server/src/routes/admin/index.d.ts +12 -0
  28. package/dist/server/src/routes/content-api/index.d.ts +13 -0
  29. package/dist/server/src/routes/index.d.ts +26 -0
  30. package/dist/server/src/services/index.d.ts +38 -0
  31. package/dist/server/src/services/service.d.ts +55 -0
  32. package/package.json +78 -0
@@ -0,0 +1,7 @@
1
+ const en = {
2
+ "plugin.name": "Sitemap Generator",
3
+ "plugin.description": "Generate XML sitemaps from your Strapi content"
4
+ };
5
+ export {
6
+ en as default
7
+ };
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const en = {
4
+ "plugin.name": "Sitemap Generator",
5
+ "plugin.description": "Generate XML sitemaps from your Strapi content"
6
+ };
7
+ exports.default = en;
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ const React = require("react");
3
+ const jsxRuntime = require("react/jsx-runtime");
4
+ const icons = require("@strapi/icons");
5
+ const __variableDynamicImportRuntimeHelper = (glob, path, segs) => {
6
+ const v = glob[path];
7
+ if (v) {
8
+ return typeof v === "function" ? v() : Promise.resolve(v);
9
+ }
10
+ return new Promise((_, reject) => {
11
+ (typeof queueMicrotask === "function" ? queueMicrotask : setTimeout)(
12
+ reject.bind(
13
+ null,
14
+ new Error(
15
+ "Unknown variable dynamic import: " + path + (path.split("/").length !== segs ? ". Note that variables only represent file names one level deep." : "")
16
+ )
17
+ )
18
+ );
19
+ });
20
+ };
21
+ const PLUGIN_ID = "strapi-sitemap-generator";
22
+ const getTranslation = (id) => `${PLUGIN_ID}.${id}`;
23
+ const Initializer = ({ setPlugin }) => {
24
+ const ref = React.useRef(setPlugin);
25
+ React.useEffect(() => {
26
+ ref.current(PLUGIN_ID);
27
+ }, []);
28
+ return null;
29
+ };
30
+ const PluginIcon = () => /* @__PURE__ */ jsxRuntime.jsx(icons.PuzzlePiece, {});
31
+ const index = {
32
+ register(app) {
33
+ app.addMenuLink({
34
+ to: `plugins/${PLUGIN_ID}`,
35
+ icon: PluginIcon,
36
+ intlLabel: {
37
+ id: `${PLUGIN_ID}.plugin.name`,
38
+ defaultMessage: "Sitemap Generator"
39
+ },
40
+ Component: async () => {
41
+ const { App } = await Promise.resolve().then(() => require("../_chunks/App-D449cQR3.js"));
42
+ return App;
43
+ }
44
+ });
45
+ app.registerPlugin({
46
+ id: PLUGIN_ID,
47
+ initializer: Initializer,
48
+ isReady: false,
49
+ name: PLUGIN_ID
50
+ });
51
+ },
52
+ async registerTrads({ locales }) {
53
+ return Promise.all(
54
+ locales.map(async (locale) => {
55
+ try {
56
+ const { default: data } = await __variableDynamicImportRuntimeHelper(/* @__PURE__ */ Object.assign({ "./translations/en.json": () => Promise.resolve().then(() => require("../_chunks/en-CZAGT--R.js")) }), `./translations/${locale}.json`, 3);
57
+ return { data: getTranslation(data), locale };
58
+ } catch {
59
+ return { data: {}, locale };
60
+ }
61
+ })
62
+ );
63
+ },
64
+ bootstrap() {
65
+ }
66
+ };
67
+ module.exports = index;
@@ -0,0 +1,68 @@
1
+ import { useRef, useEffect } from "react";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { PuzzlePiece } from "@strapi/icons";
4
+ const __variableDynamicImportRuntimeHelper = (glob, path, segs) => {
5
+ const v = glob[path];
6
+ if (v) {
7
+ return typeof v === "function" ? v() : Promise.resolve(v);
8
+ }
9
+ return new Promise((_, reject) => {
10
+ (typeof queueMicrotask === "function" ? queueMicrotask : setTimeout)(
11
+ reject.bind(
12
+ null,
13
+ new Error(
14
+ "Unknown variable dynamic import: " + path + (path.split("/").length !== segs ? ". Note that variables only represent file names one level deep." : "")
15
+ )
16
+ )
17
+ );
18
+ });
19
+ };
20
+ const PLUGIN_ID = "strapi-sitemap-generator";
21
+ const getTranslation = (id) => `${PLUGIN_ID}.${id}`;
22
+ const Initializer = ({ setPlugin }) => {
23
+ const ref = useRef(setPlugin);
24
+ useEffect(() => {
25
+ ref.current(PLUGIN_ID);
26
+ }, []);
27
+ return null;
28
+ };
29
+ const PluginIcon = () => /* @__PURE__ */ jsx(PuzzlePiece, {});
30
+ const index = {
31
+ register(app) {
32
+ app.addMenuLink({
33
+ to: `plugins/${PLUGIN_ID}`,
34
+ icon: PluginIcon,
35
+ intlLabel: {
36
+ id: `${PLUGIN_ID}.plugin.name`,
37
+ defaultMessage: "Sitemap Generator"
38
+ },
39
+ Component: async () => {
40
+ const { App } = await import("../_chunks/App-Bvbv7t_S.mjs");
41
+ return App;
42
+ }
43
+ });
44
+ app.registerPlugin({
45
+ id: PLUGIN_ID,
46
+ initializer: Initializer,
47
+ isReady: false,
48
+ name: PLUGIN_ID
49
+ });
50
+ },
51
+ async registerTrads({ locales }) {
52
+ return Promise.all(
53
+ locales.map(async (locale) => {
54
+ try {
55
+ const { default: data } = await __variableDynamicImportRuntimeHelper(/* @__PURE__ */ Object.assign({ "./translations/en.json": () => import("../_chunks/en-BptFMppp.mjs") }), `./translations/${locale}.json`, 3);
56
+ return { data: getTranslation(data), locale };
57
+ } catch {
58
+ return { data: {}, locale };
59
+ }
60
+ })
61
+ );
62
+ },
63
+ bootstrap() {
64
+ }
65
+ };
66
+ export {
67
+ index as default
68
+ };
@@ -0,0 +1,5 @@
1
+ type InitializerProps = {
2
+ setPlugin: (id: string) => void;
3
+ };
4
+ declare const Initializer: ({ setPlugin }: InitializerProps) => null;
5
+ export { Initializer };
@@ -0,0 +1,2 @@
1
+ declare const PluginIcon: () => import("react/jsx-runtime").JSX.Element;
2
+ export { PluginIcon };
@@ -0,0 +1,14 @@
1
+ declare const _default: {
2
+ register(app: any): void;
3
+ registerTrads({ locales }: {
4
+ locales: string[];
5
+ }): Promise<({
6
+ data: string;
7
+ locale: string;
8
+ } | {
9
+ data: {};
10
+ locale: string;
11
+ })[]>;
12
+ bootstrap(): void;
13
+ };
14
+ export default _default;
@@ -0,0 +1,2 @@
1
+ declare const App: () => import("react/jsx-runtime").JSX.Element;
2
+ export { App };
@@ -0,0 +1,2 @@
1
+ declare const HomePage: () => import("react/jsx-runtime").JSX.Element;
2
+ export { HomePage };
@@ -0,0 +1 @@
1
+ export declare const PLUGIN_ID = "strapi-sitemap-generator";
@@ -0,0 +1,2 @@
1
+ declare const getTranslation: (id: string) => string;
2
+ export { getTranslation };
@@ -0,0 +1,338 @@
1
+ "use strict";
2
+ const bootstrap = ({ strapi }) => {
3
+ };
4
+ const destroy = ({ strapi }) => {
5
+ };
6
+ const register = ({ strapi }) => {
7
+ };
8
+ const config = {
9
+ default: {},
10
+ validator() {
11
+ }
12
+ };
13
+ const contentTypes = {};
14
+ const controller = ({ strapi }) => ({
15
+ /**
16
+ * Generate sitemap XML
17
+ * Public endpoint for serving sitemap.xml
18
+ */
19
+ async generateXml(ctx) {
20
+ try {
21
+ const service2 = strapi.plugin("strapi-sitemap-generator").service("service");
22
+ const xml = await service2.generateXML();
23
+ ctx.type = "application/xml";
24
+ ctx.body = xml;
25
+ } catch (error) {
26
+ ctx.throw(500, `Failed to generate sitemap: ${error.message}`);
27
+ }
28
+ },
29
+ /**
30
+ * Get sitemap data as JSON
31
+ * Admin endpoint for preview
32
+ */
33
+ async getSitemapData(ctx) {
34
+ try {
35
+ const service2 = strapi.plugin("strapi-sitemap-generator").service("service");
36
+ const data = await service2.getSitemapData();
37
+ ctx.body = data;
38
+ } catch (error) {
39
+ ctx.throw(500, `Failed to get sitemap data: ${error.message}`);
40
+ }
41
+ },
42
+ /**
43
+ * (Removed) Download sitemap.xml - not needed, engines fetch XML via public URL
44
+ */
45
+ /**
46
+ * Get all available content types
47
+ * Admin endpoint for content type discovery
48
+ */
49
+ async getContentTypes(ctx) {
50
+ try {
51
+ const service2 = strapi.plugin("strapi-sitemap-generator").service("service");
52
+ const contentTypes2 = await service2.discoverContentTypes();
53
+ ctx.body = { data: contentTypes2 };
54
+ } catch (error) {
55
+ ctx.throw(500, `Failed to discover content types: ${error.message}`);
56
+ }
57
+ },
58
+ /**
59
+ * Get current plugin configuration
60
+ * Admin endpoint for loading settings
61
+ */
62
+ async getConfig(ctx) {
63
+ try {
64
+ const service2 = strapi.plugin("strapi-sitemap-generator").service("service");
65
+ const config2 = await service2.getConfig();
66
+ ctx.body = config2;
67
+ } catch (error) {
68
+ ctx.throw(500, `Failed to get config: ${error.message}`);
69
+ }
70
+ },
71
+ /**
72
+ * Update plugin configuration
73
+ * Admin endpoint for saving settings
74
+ */
75
+ async updateConfig(ctx) {
76
+ try {
77
+ const service2 = strapi.plugin("strapi-sitemap-generator").service("service");
78
+ const config2 = await service2.updateConfig(ctx.request.body);
79
+ ctx.body = config2;
80
+ } catch (error) {
81
+ ctx.throw(500, `Failed to update config: ${error.message}`);
82
+ }
83
+ }
84
+ });
85
+ const controllers = {
86
+ controller
87
+ };
88
+ const middlewares = {};
89
+ const policies = {};
90
+ const contentAPIRoutes = {
91
+ type: "content-api",
92
+ routes: [
93
+ {
94
+ method: "GET",
95
+ path: "/sitemap.xml",
96
+ handler: "controller.generateXml",
97
+ config: {
98
+ auth: false,
99
+ policies: []
100
+ }
101
+ }
102
+ ]
103
+ };
104
+ const adminAPIRoutes = {
105
+ type: "admin",
106
+ routes: [
107
+ {
108
+ method: "GET",
109
+ path: "/data",
110
+ handler: "controller.getSitemapData",
111
+ config: {
112
+ policies: []
113
+ }
114
+ },
115
+ {
116
+ method: "GET",
117
+ path: "/content-types",
118
+ handler: "controller.getContentTypes",
119
+ config: {
120
+ policies: []
121
+ }
122
+ },
123
+ {
124
+ method: "GET",
125
+ path: "/config",
126
+ handler: "controller.getConfig",
127
+ config: {
128
+ policies: []
129
+ }
130
+ },
131
+ {
132
+ method: "PUT",
133
+ path: "/config",
134
+ handler: "controller.updateConfig",
135
+ config: {
136
+ policies: []
137
+ }
138
+ }
139
+ ]
140
+ };
141
+ const routes = {
142
+ "content-api": contentAPIRoutes,
143
+ admin: adminAPIRoutes
144
+ };
145
+ const service = ({ strapi }) => ({
146
+ /**
147
+ * Discover ALL content types with slug fields
148
+ */
149
+ async discoverContentTypes() {
150
+ const contentTypes2 = [];
151
+ const allContentTypes = strapi.contentTypes;
152
+ for (const [uid, contentType] of Object.entries(allContentTypes)) {
153
+ if (!uid.startsWith("api::")) continue;
154
+ const schema = contentType;
155
+ const attributes = schema.attributes || {};
156
+ const hasSlug = "slug" in attributes;
157
+ if (hasSlug) {
158
+ contentTypes2.push({
159
+ uid,
160
+ singularName: schema.info?.singularName || uid.split(".").pop(),
161
+ pluralName: schema.info?.pluralName || uid.split(".").pop(),
162
+ displayName: schema.info?.displayName || uid,
163
+ hasPublishedAt: "publishedAt" in attributes
164
+ });
165
+ }
166
+ }
167
+ return contentTypes2;
168
+ },
169
+ /**
170
+ * Get content type info
171
+ */
172
+ async getContentTypeInfo(uid) {
173
+ const contentType = strapi.contentTypes[uid];
174
+ const attributes = contentType.attributes || {};
175
+ return {
176
+ singularName: contentType.info?.singularName || uid.split(".").pop(),
177
+ pluralName: contentType.info?.pluralName || uid.split(".").pop(),
178
+ displayName: contentType.info?.displayName || uid,
179
+ hasPublishedAt: "publishedAt" in attributes
180
+ };
181
+ },
182
+ /**
183
+ * Generate sitemap XML from user-selected content types
184
+ */
185
+ async generateXML() {
186
+ const config2 = await this.getConfig();
187
+ const {
188
+ baseUrl = "https://example.com",
189
+ selectedContentTypes = [],
190
+ customPaths = {},
191
+ customPriorities = {},
192
+ customChangefreq = {}
193
+ } = config2;
194
+ strapi.log.info(`[Sitemap] Generating sitemap with ${selectedContentTypes.length} selected content types`);
195
+ strapi.log.info(`[Sitemap] Selected types:`, selectedContentTypes);
196
+ let xml = '<?xml version="1.0" encoding="UTF-8"?>\n';
197
+ xml += '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n';
198
+ if (selectedContentTypes.length === 0) {
199
+ strapi.log.warn("[Sitemap] No content types selected for sitemap generation");
200
+ xml += "</urlset>";
201
+ return xml;
202
+ }
203
+ for (const uid of selectedContentTypes) {
204
+ try {
205
+ const contentType = await this.getContentTypeInfo(uid);
206
+ strapi.log.info(`[Sitemap] Processing content type: ${uid}`);
207
+ const entries = await strapi.entityService.findMany(uid);
208
+ strapi.log.info(`[Sitemap] Found ${entries?.length || 0} entries for ${uid}`);
209
+ if (entries && Array.isArray(entries) && entries.length > 0) {
210
+ const basePath = customPaths[uid] || `/${contentType.pluralName}`;
211
+ const priority = customPriorities[uid] || 0.7;
212
+ const changefreq = customChangefreq[uid] || "monthly";
213
+ entries.forEach((entry) => {
214
+ if (entry.slug) {
215
+ xml += this.generateUrlEntry({
216
+ loc: `${baseUrl}${basePath}/${entry.slug}`,
217
+ lastmod: entry.updatedAt ? new Date(entry.updatedAt).toISOString().split("T")[0] : (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
218
+ priority,
219
+ changefreq
220
+ });
221
+ }
222
+ });
223
+ }
224
+ } catch (error) {
225
+ strapi.log.error(`[Sitemap] Failed to fetch entries for ${uid}:`, error.message);
226
+ strapi.log.error(`[Sitemap] Error details:`, error);
227
+ }
228
+ }
229
+ xml += "</urlset>";
230
+ strapi.log.info(`[Sitemap] Sitemap generation complete`);
231
+ return xml;
232
+ },
233
+ generateUrlEntry({ loc, lastmod, priority, changefreq }) {
234
+ let entry = " <url>\n";
235
+ entry += ` <loc>${this.escapeXml(loc)}</loc>
236
+ `;
237
+ if (lastmod) entry += ` <lastmod>${lastmod}</lastmod>
238
+ `;
239
+ if (changefreq) entry += ` <changefreq>${changefreq}</changefreq>
240
+ `;
241
+ if (priority) entry += ` <priority>${priority}</priority>
242
+ `;
243
+ entry += " </url>\n";
244
+ return entry;
245
+ },
246
+ escapeXml(unsafe) {
247
+ return unsafe.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
248
+ },
249
+ /**
250
+ * Get structured sitemap data for frontend display
251
+ */
252
+ async getSitemapData() {
253
+ const config2 = await this.getConfig();
254
+ const {
255
+ baseUrl = "https://example.com",
256
+ selectedContentTypes = [],
257
+ customPaths = {}
258
+ } = config2;
259
+ strapi.log.info(`[Sitemap] Getting sitemap data for ${selectedContentTypes.length} content types`);
260
+ const data = {
261
+ entries: {},
262
+ meta: {
263
+ totalUrls: 0,
264
+ lastGenerated: (/* @__PURE__ */ new Date()).toISOString(),
265
+ contentTypes: []
266
+ }
267
+ };
268
+ if (selectedContentTypes.length === 0) {
269
+ strapi.log.warn("[Sitemap] No content types selected - returning empty sitemap data");
270
+ return data;
271
+ }
272
+ for (const uid of selectedContentTypes) {
273
+ try {
274
+ const contentType = await this.getContentTypeInfo(uid);
275
+ strapi.log.info(`[Sitemap] Fetching data for ${uid}`);
276
+ const entries = await strapi.entityService.findMany(uid);
277
+ strapi.log.info(`[Sitemap] Found ${entries?.length || 0} entries for ${uid}`);
278
+ if (entries && Array.isArray(entries) && entries.length > 0) {
279
+ const basePath = customPaths[uid] || `/${contentType.pluralName}`;
280
+ data.entries[uid] = entries.filter((entry) => entry.slug).map((entry) => ({
281
+ url: `${baseUrl}${basePath}/${entry.slug}`,
282
+ title: entry.title || entry.name || entry.slug,
283
+ lastmod: entry.updatedAt
284
+ }));
285
+ data.meta.contentTypes.push({
286
+ uid,
287
+ displayName: contentType.displayName,
288
+ count: data.entries[uid].length
289
+ });
290
+ data.meta.totalUrls += data.entries[uid].length;
291
+ }
292
+ } catch (error) {
293
+ strapi.log.error(`[Sitemap] Failed to fetch ${uid}:`, error.message);
294
+ strapi.log.error(`[Sitemap] Error details:`, error);
295
+ }
296
+ }
297
+ strapi.log.info(`[Sitemap] Total URLs in sitemap: ${data.meta.totalUrls}`);
298
+ return data;
299
+ },
300
+ /**
301
+ * Get plugin configuration
302
+ */
303
+ async getConfig() {
304
+ const pluginStore = strapi.store({ type: "plugin", name: "strapi-sitemap-generator" });
305
+ const config2 = await pluginStore.get({ key: "config" });
306
+ return config2 || {
307
+ baseUrl: "https://example.com",
308
+ selectedContentTypes: [],
309
+ customPaths: {},
310
+ customPriorities: {},
311
+ customChangefreq: {}
312
+ };
313
+ },
314
+ /**
315
+ * Update plugin configuration
316
+ */
317
+ async updateConfig(newConfig) {
318
+ const pluginStore = strapi.store({ type: "plugin", name: "strapi-sitemap-generator" });
319
+ await pluginStore.set({ key: "config", value: newConfig });
320
+ return newConfig;
321
+ }
322
+ });
323
+ const services = {
324
+ service
325
+ };
326
+ const index = {
327
+ register,
328
+ bootstrap,
329
+ destroy,
330
+ config,
331
+ controllers,
332
+ routes,
333
+ services,
334
+ contentTypes,
335
+ policies,
336
+ middlewares
337
+ };
338
+ module.exports = index;