featuredrop 1.4.0 → 2.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 (62) hide show
  1. package/README.md +287 -760
  2. package/dist/adapters.cjs +1757 -0
  3. package/dist/adapters.cjs.map +1 -0
  4. package/dist/adapters.d.cts +744 -0
  5. package/dist/adapters.d.ts +744 -0
  6. package/dist/adapters.js +1745 -0
  7. package/dist/adapters.js.map +1 -0
  8. package/dist/admin.cjs +148 -32
  9. package/dist/admin.cjs.map +1 -1
  10. package/dist/admin.d.cts +14 -3
  11. package/dist/admin.d.ts +14 -3
  12. package/dist/admin.js +148 -32
  13. package/dist/admin.js.map +1 -1
  14. package/dist/bridges.cjs +111 -13
  15. package/dist/bridges.cjs.map +1 -1
  16. package/dist/bridges.d.cts +12 -5
  17. package/dist/bridges.d.ts +12 -5
  18. package/dist/bridges.js +111 -13
  19. package/dist/bridges.js.map +1 -1
  20. package/dist/ci.cjs +34 -0
  21. package/dist/ci.cjs.map +1 -1
  22. package/dist/ci.d.cts +5 -1
  23. package/dist/ci.d.ts +5 -1
  24. package/dist/ci.js +34 -1
  25. package/dist/ci.js.map +1 -1
  26. package/dist/cms.cjs +835 -0
  27. package/dist/cms.cjs.map +1 -0
  28. package/dist/cms.d.cts +236 -0
  29. package/dist/cms.d.ts +236 -0
  30. package/dist/cms.js +829 -0
  31. package/dist/cms.js.map +1 -0
  32. package/dist/flags.cjs +27 -7
  33. package/dist/flags.cjs.map +1 -1
  34. package/dist/flags.d.cts +14 -0
  35. package/dist/flags.d.ts +14 -0
  36. package/dist/flags.js +27 -7
  37. package/dist/flags.js.map +1 -1
  38. package/dist/index.cjs +52 -4481
  39. package/dist/index.cjs.map +1 -1
  40. package/dist/index.d.cts +1 -1340
  41. package/dist/index.d.ts +1 -1340
  42. package/dist/index.js +53 -4388
  43. package/dist/index.js.map +1 -1
  44. package/dist/markdown.cjs +257 -0
  45. package/dist/markdown.cjs.map +1 -0
  46. package/dist/markdown.d.cts +9 -0
  47. package/dist/markdown.d.ts +9 -0
  48. package/dist/markdown.js +234 -0
  49. package/dist/markdown.js.map +1 -0
  50. package/dist/renderer.cjs +503 -0
  51. package/dist/renderer.cjs.map +1 -0
  52. package/dist/renderer.d.cts +250 -0
  53. package/dist/renderer.d.ts +250 -0
  54. package/dist/renderer.js +501 -0
  55. package/dist/renderer.js.map +1 -0
  56. package/dist/rss.cjs +291 -0
  57. package/dist/rss.cjs.map +1 -0
  58. package/dist/rss.d.cts +158 -0
  59. package/dist/rss.d.ts +158 -0
  60. package/dist/rss.js +268 -0
  61. package/dist/rss.js.map +1 -0
  62. package/package.json +72 -6
package/dist/rss.js ADDED
@@ -0,0 +1,268 @@
1
+ import * as moduleApi from 'module';
2
+
3
+ // src/markdown.ts
4
+ var dynamicRequire = typeof moduleApi.createRequire === "function" ? moduleApi.createRequire(import.meta.url) : null;
5
+ var cachedMarked = null;
6
+ var cachedShiki = null;
7
+ function optionalRequire(name) {
8
+ if (!dynamicRequire) return null;
9
+ try {
10
+ return dynamicRequire(name);
11
+ } catch (error) {
12
+ if (error && typeof error === "object" && "code" in error && error.code === "MODULE_NOT_FOUND") {
13
+ return null;
14
+ }
15
+ return null;
16
+ }
17
+ }
18
+ function getMarked() {
19
+ if (cachedMarked !== null) return cachedMarked || null;
20
+ cachedMarked = optionalRequire("marked") ?? false;
21
+ return cachedMarked || null;
22
+ }
23
+ function getShiki() {
24
+ if (cachedShiki !== null) return cachedShiki || null;
25
+ cachedShiki = optionalRequire("shiki") ?? false;
26
+ return cachedShiki || null;
27
+ }
28
+ function escapeHtml(value) {
29
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
30
+ }
31
+ function sanitizeUrl(url) {
32
+ if (!url) return null;
33
+ const trimmed = url.trim();
34
+ if (!trimmed) return null;
35
+ const lower = trimmed.toLowerCase();
36
+ if (lower.startsWith("javascript:")) return null;
37
+ if (lower.startsWith("data:")) return null;
38
+ if (lower.startsWith("vbscript:")) return null;
39
+ if (/['"<>\s]/.test(trimmed)) return null;
40
+ return trimmed;
41
+ }
42
+ function sanitizeHtml(html) {
43
+ return html.replace(/<script[\s\S]*?>[\s\S]*?<\/script>/gi, "").replace(/<style[\s\S]*?>[\s\S]*?<\/style>/gi, "").replace(/\s+on[a-z]+\s*=\s*("[^"]*"|'[^']*'|[^\s>]+)/gi, "").replace(/\s+(?:href|src|xlink:href)\s*=\s*("|')(?:javascript:|data:)[^"']*\1/gi, "");
44
+ }
45
+ function decodeAllowedEntities(html) {
46
+ const allowTags = [
47
+ "p",
48
+ "strong",
49
+ "em",
50
+ "a",
51
+ "code",
52
+ "pre",
53
+ "img",
54
+ "ul",
55
+ "ol",
56
+ "li",
57
+ "blockquote",
58
+ "h1",
59
+ "h2",
60
+ "h3",
61
+ "h4",
62
+ "h5",
63
+ "h6",
64
+ "br"
65
+ ];
66
+ return html.replace(/&lt;(\/?)([a-z0-9]+)([^>]*)&gt;/gi, (match, slash, tag, rest) => {
67
+ if (!allowTags.includes(tag.toLowerCase())) return match;
68
+ const decodedRest = rest.replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">");
69
+ return `<${slash}${tag}${decodedRest}>`;
70
+ });
71
+ }
72
+ function renderCodeBlock(code, language) {
73
+ const shiki = getShiki();
74
+ if (shiki?.codeToHtml) {
75
+ try {
76
+ const rendered = shiki.codeToHtml(code, { lang: language || "text", theme: "github-dark" });
77
+ if (typeof rendered === "string") return rendered;
78
+ } catch {
79
+ }
80
+ }
81
+ const langAttr = language ? ` class="language-${escapeHtml(language)}"` : "";
82
+ return `<pre><code${langAttr}>${escapeHtml(code)}</code></pre>`;
83
+ }
84
+ function inlineMarkdown(text) {
85
+ let result = escapeHtml(text);
86
+ const codeSpans = [];
87
+ result = result.replace(/`([^`]+)`/g, (_match, code) => {
88
+ const idx = codeSpans.length;
89
+ codeSpans.push(`<code>${escapeHtml(code)}</code>`);
90
+ return `\xA7\xA7CODE${idx}\xA7\xA7`;
91
+ });
92
+ result = result.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (_match, alt, url) => {
93
+ const safeUrl = sanitizeUrl(url);
94
+ const safeAlt = escapeHtml(alt ?? "");
95
+ if (!safeUrl) return safeAlt;
96
+ return `<img src="${escapeHtml(safeUrl)}" alt="${safeAlt}" />`;
97
+ });
98
+ result = result.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_match, label, url) => {
99
+ const safeUrl = sanitizeUrl(url);
100
+ const safeLabel = escapeHtml(label ?? "");
101
+ if (!safeUrl) return safeLabel;
102
+ return `<a href="${escapeHtml(safeUrl)}" target="_blank" rel="noopener noreferrer">${safeLabel}</a>`;
103
+ });
104
+ result = result.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>");
105
+ result = result.replace(/\*([^*]+)\*/g, "<em>$1</em>");
106
+ result = result.replace(/§§CODE(\d+)§§/g, (_m, idx) => codeSpans[Number(idx)] ?? "");
107
+ return result;
108
+ }
109
+ function fallbackParse(markdown) {
110
+ const lines = markdown.split(/\r?\n/);
111
+ const blocks = [];
112
+ let listBuffer = null;
113
+ let quoteBuffer = null;
114
+ let inCodeBlock = false;
115
+ let codeLang;
116
+ let codeLines = [];
117
+ const flushList = () => {
118
+ if (!listBuffer) return;
119
+ blocks.push(`<ul>${listBuffer.map((item) => `<li>${item}</li>`).join("")}</ul>`);
120
+ listBuffer = null;
121
+ };
122
+ const flushQuote = () => {
123
+ if (!quoteBuffer) return;
124
+ const content = quoteBuffer.map((line) => inlineMarkdown(line.trim())).join("<br>");
125
+ blocks.push(`<blockquote>${content}</blockquote>`);
126
+ quoteBuffer = null;
127
+ };
128
+ const flushCode = () => {
129
+ if (!inCodeBlock) return;
130
+ blocks.push(renderCodeBlock(codeLines.join("\n"), codeLang));
131
+ codeLines = [];
132
+ codeLang = void 0;
133
+ inCodeBlock = false;
134
+ };
135
+ for (const rawLine of lines) {
136
+ const line = rawLine.replace(/\s+$/, "");
137
+ const codeFence = line.match(/^```(.*)$/);
138
+ if (codeFence) {
139
+ if (inCodeBlock) {
140
+ flushCode();
141
+ } else {
142
+ flushList();
143
+ flushQuote();
144
+ inCodeBlock = true;
145
+ codeLang = codeFence[1]?.trim() || void 0;
146
+ codeLines = [];
147
+ }
148
+ continue;
149
+ }
150
+ if (inCodeBlock) {
151
+ codeLines.push(rawLine);
152
+ continue;
153
+ }
154
+ const listMatch = line.match(/^\s*[-*+]\s+(.*)$/);
155
+ if (listMatch) {
156
+ flushQuote();
157
+ listBuffer = listBuffer ?? [];
158
+ listBuffer.push(inlineMarkdown(listMatch[1].trim()));
159
+ continue;
160
+ }
161
+ if (listBuffer) flushList();
162
+ const headingMatch = line.match(/^(#{1,6})\s+(.*)$/);
163
+ if (headingMatch) {
164
+ flushQuote();
165
+ const level = headingMatch[1].length;
166
+ const content = inlineMarkdown(headingMatch[2].trim());
167
+ blocks.push(`<h${level}>${content}</h${level}>`);
168
+ continue;
169
+ }
170
+ const quoteMatch = line.match(/^>\s?(.*)$/);
171
+ if (quoteMatch) {
172
+ quoteBuffer = quoteBuffer ?? [];
173
+ quoteBuffer.push(quoteMatch[1]);
174
+ continue;
175
+ }
176
+ if (quoteBuffer) flushQuote();
177
+ if (!line.trim()) {
178
+ continue;
179
+ }
180
+ blocks.push(`<p>${inlineMarkdown(line.trim())}</p>`);
181
+ }
182
+ flushList();
183
+ flushQuote();
184
+ flushCode();
185
+ return blocks.join("\n");
186
+ }
187
+ function renderWithMarked(markdown, marked) {
188
+ if (!marked.parse) return null;
189
+ const renderer = marked.Renderer ? new marked.Renderer() : void 0;
190
+ if (renderer) {
191
+ renderer.link = (href, _title, text) => {
192
+ const safeUrl = sanitizeUrl(href);
193
+ if (!safeUrl) return escapeHtml(text);
194
+ return `<a href="${escapeHtml(safeUrl)}" target="_blank" rel="noopener noreferrer">${text}</a>`;
195
+ };
196
+ renderer.image = (href, _title, text) => {
197
+ const safeUrl = sanitizeUrl(href);
198
+ const safeAlt = escapeHtml(text ?? "");
199
+ if (!safeUrl) return safeAlt;
200
+ return `<img src="${escapeHtml(safeUrl)}" alt="${safeAlt}" />`;
201
+ };
202
+ }
203
+ const output = marked.parse(markdown, renderer ? { renderer } : void 0);
204
+ if (typeof output === "string") return output;
205
+ return output ? String(output) : null;
206
+ }
207
+ function parseDescription(markdown) {
208
+ if (!markdown) return "";
209
+ const marked = getMarked();
210
+ if (marked) {
211
+ try {
212
+ const rendered = renderWithMarked(markdown, marked);
213
+ if (rendered) {
214
+ const sanitized2 = sanitizeHtml(rendered);
215
+ const decoded2 = decodeAllowedEntities(sanitized2);
216
+ return sanitizeHtml(decoded2);
217
+ }
218
+ } catch {
219
+ }
220
+ }
221
+ if (/<[^>]+>/.test(markdown)) {
222
+ const sanitized2 = sanitizeHtml(markdown);
223
+ const decoded2 = decodeAllowedEntities(sanitized2);
224
+ return sanitizeHtml(decoded2);
225
+ }
226
+ const fallback = fallbackParse(markdown);
227
+ const sanitized = sanitizeHtml(fallback);
228
+ const decoded = decodeAllowedEntities(sanitized);
229
+ return sanitizeHtml(decoded);
230
+ }
231
+
232
+ // src/rss.ts
233
+ function escape(str) {
234
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
235
+ }
236
+ function generateRSS(manifest, options) {
237
+ const title = escape(options?.title ?? "Featuredrop Changelog");
238
+ const link = escape(options?.link ?? "");
239
+ const desc = escape(options?.description ?? "Product updates");
240
+ const items = manifest.slice().sort((a, b) => new Date(b.releasedAt).getTime() - new Date(a.releasedAt).getTime()).map((item) => {
241
+ const descriptionHtml = item.description ? parseDescription(item.description) : "";
242
+ const itemLink = item.url ? escape(item.url) : "";
243
+ return [
244
+ "<item>",
245
+ `<title>${escape(item.label)}</title>`,
246
+ itemLink ? `<link>${itemLink}</link>` : "",
247
+ `<guid isPermaLink="false">${escape(item.id)}</guid>`,
248
+ `<pubDate>${new Date(item.releasedAt).toUTCString()}</pubDate>`,
249
+ `<description><![CDATA[${descriptionHtml}]]></description>`,
250
+ "</item>"
251
+ ].join("");
252
+ }).join("");
253
+ return [
254
+ '<?xml version="1.0" encoding="UTF-8"?>',
255
+ '<rss version="2.0">',
256
+ "<channel>",
257
+ `<title>${title}</title>`,
258
+ link ? `<link>${link}</link>` : "",
259
+ `<description>${desc}</description>`,
260
+ items,
261
+ "</channel>",
262
+ "</rss>"
263
+ ].join("");
264
+ }
265
+
266
+ export { generateRSS };
267
+ //# sourceMappingURL=rss.js.map
268
+ //# sourceMappingURL=rss.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/markdown.ts","../src/rss.ts"],"names":["sanitized","decoded"],"mappings":"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","file":"rss.js","sourcesContent":["import * as moduleApi from \"module\";\n\n// Lightweight markdown parser with optional `marked` + `shiki` support.\n// The function is synchronous and always returns sanitized HTML.\n\ntype MarkedRenderer = {\n link?: (href: string | null, title: string | null, text: string) => string;\n image?: (href: string | null, title: string | null, text: string) => string;\n paragraph?: (text: string) => string;\n heading?: (text: string, level: number) => string;\n};\n\ntype MarkedModule = {\n Renderer?: new () => MarkedRenderer;\n parse?: (markdown: string, options?: { renderer?: MarkedRenderer }) => string | Promise<string>;\n};\n\ntype ShikiLike = {\n codeToHtml?: (code: string, options?: { lang?: string; theme?: string }) => string | Promise<string>;\n};\n\nconst dynamicRequire =\n typeof moduleApi.createRequire === \"function\" ? moduleApi.createRequire(import.meta.url) : null;\n\nlet cachedMarked: MarkedModule | null | false = null;\nlet cachedShiki: ShikiLike | null | false = null;\n\nfunction optionalRequire<T>(name: string): T | null {\n if (!dynamicRequire) return null;\n try {\n // Using dynamic require so missing optional peers don't break bundling/runtime.\n return dynamicRequire(name) as T;\n } catch (error: unknown) {\n if (error && typeof error === \"object\" && \"code\" in error && (error as { code?: string }).code === \"MODULE_NOT_FOUND\") {\n return null;\n }\n // Any other error should still be treated as a failure to keep parsing resilient.\n return null;\n }\n}\n\nfunction getMarked(): MarkedModule | null {\n if (cachedMarked !== null) return cachedMarked || null;\n cachedMarked = optionalRequire<MarkedModule>(\"marked\") ?? false;\n return cachedMarked || null;\n}\n\nfunction getShiki(): ShikiLike | null {\n if (cachedShiki !== null) return cachedShiki || null;\n cachedShiki = optionalRequire<ShikiLike>(\"shiki\") ?? false;\n return cachedShiki || null;\n}\n\nfunction escapeHtml(value: string): string {\n return value\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/'/g, \"&#39;\");\n}\n\nfunction sanitizeUrl(url: string | null | undefined): string | null {\n if (!url) return null;\n const trimmed = url.trim();\n if (!trimmed) return null;\n\n const lower = trimmed.toLowerCase();\n if (lower.startsWith(\"javascript:\")) return null;\n if (lower.startsWith(\"data:\")) return null;\n if (lower.startsWith(\"vbscript:\")) return null;\n\n // Disallow characters that can break attribute context\n if (/['\"<>\\s]/.test(trimmed)) return null;\n\n return trimmed;\n}\n\nfunction sanitizeHtml(html: string): string {\n return html\n // Remove script/style tags entirely\n .replace(/<script[\\s\\S]*?>[\\s\\S]*?<\\/script>/gi, \"\")\n .replace(/<style[\\s\\S]*?>[\\s\\S]*?<\\/style>/gi, \"\")\n // Remove inline event handlers (on*)\n .replace(/\\s+on[a-z]+\\s*=\\s*(\"[^\"]*\"|'[^']*'|[^\\s>]+)/gi, \"\")\n // Remove javascript: or data: URLs in href/src/xlink:href\n .replace(/\\s+(?:href|src|xlink:href)\\s*=\\s*(\"|')(?:javascript:|data:)[^\"']*\\1/gi, \"\");\n}\n\nfunction decodeAllowedEntities(html: string): string {\n const allowTags = [\n \"p\",\n \"strong\",\n \"em\",\n \"a\",\n \"code\",\n \"pre\",\n \"img\",\n \"ul\",\n \"ol\",\n \"li\",\n \"blockquote\",\n \"h1\",\n \"h2\",\n \"h3\",\n \"h4\",\n \"h5\",\n \"h6\",\n \"br\",\n ];\n\n // Decode common entities inside allowed tags only\n return html.replace(/&lt;(\\/?)([a-z0-9]+)([^>]*)&gt;/gi, (match, slash, tag, rest) => {\n if (!allowTags.includes(tag.toLowerCase())) return match;\n const decodedRest = rest\n .replace(/&quot;/g, '\"')\n .replace(/&#39;/g, \"'\")\n .replace(/&amp;/g, \"&\")\n .replace(/&lt;/g, \"<\")\n .replace(/&gt;/g, \">\");\n return `<${slash}${tag}${decodedRest}>`;\n });\n}\n\nfunction renderCodeBlock(code: string, language: string | undefined): string {\n const shiki = getShiki();\n if (shiki?.codeToHtml) {\n try {\n const rendered = shiki.codeToHtml(code, { lang: language || \"text\", theme: \"github-dark\" });\n if (typeof rendered === \"string\") return rendered;\n } catch {\n // Fall through to non-highlighted rendering\n }\n }\n\n const langAttr = language ? ` class=\"language-${escapeHtml(language)}\"` : \"\";\n return `<pre><code${langAttr}>${escapeHtml(code)}</code></pre>`;\n}\n\nfunction inlineMarkdown(text: string): string {\n // Escape user-provided HTML before applying markdown conversions.\n let result = escapeHtml(text);\n\n // Protect inline code spans so subsequent replacements don't mangle them.\n const codeSpans: string[] = [];\n result = result.replace(/`([^`]+)`/g, (_match, code) => {\n const idx = codeSpans.length;\n codeSpans.push(`<code>${escapeHtml(code)}</code>`);\n return `§§CODE${idx}§§`;\n });\n\n // Images: ![alt](url)\n result = result.replace(/!\\[([^\\]]*)\\]\\(([^)]+)\\)/g, (_match, alt, url) => {\n const safeUrl = sanitizeUrl(url);\n const safeAlt = escapeHtml(alt ?? \"\");\n if (!safeUrl) return safeAlt;\n return `<img src=\"${escapeHtml(safeUrl)}\" alt=\"${safeAlt}\" />`;\n });\n\n // Links: [text](url)\n result = result.replace(/\\[([^\\]]+)\\]\\(([^)]+)\\)/g, (_match, label, url) => {\n const safeUrl = sanitizeUrl(url);\n const safeLabel = escapeHtml(label ?? \"\");\n if (!safeUrl) return safeLabel;\n return `<a href=\"${escapeHtml(safeUrl)}\" target=\"_blank\" rel=\"noopener noreferrer\">${safeLabel}</a>`;\n });\n\n // Bold and italic (basic)\n result = result.replace(/\\*\\*([^*]+)\\*\\*/g, \"<strong>$1</strong>\");\n result = result.replace(/\\*([^*]+)\\*/g, \"<em>$1</em>\");\n\n // Restore code spans\n result = result.replace(/§§CODE(\\d+)§§/g, (_m, idx) => codeSpans[Number(idx)] ?? \"\");\n\n return result;\n}\n\nfunction fallbackParse(markdown: string): string {\n const lines = markdown.split(/\\r?\\n/);\n const blocks: string[] = [];\n let listBuffer: string[] | null = null;\n let quoteBuffer: string[] | null = null;\n let inCodeBlock = false;\n let codeLang: string | undefined;\n let codeLines: string[] = [];\n\n const flushList = () => {\n if (!listBuffer) return;\n blocks.push(`<ul>${listBuffer.map((item) => `<li>${item}</li>`).join(\"\")}</ul>`);\n listBuffer = null;\n };\n\n const flushQuote = () => {\n if (!quoteBuffer) return;\n const content = quoteBuffer.map((line) => inlineMarkdown(line.trim())).join(\"<br>\");\n blocks.push(`<blockquote>${content}</blockquote>`);\n quoteBuffer = null;\n };\n\n const flushCode = () => {\n if (!inCodeBlock) return;\n blocks.push(renderCodeBlock(codeLines.join(\"\\n\"), codeLang));\n codeLines = [];\n codeLang = undefined;\n inCodeBlock = false;\n };\n\n for (const rawLine of lines) {\n const line = rawLine.replace(/\\s+$/, \"\");\n\n const codeFence = line.match(/^```(.*)$/);\n if (codeFence) {\n if (inCodeBlock) {\n flushCode();\n } else {\n flushList();\n flushQuote();\n inCodeBlock = true;\n codeLang = codeFence[1]?.trim() || undefined;\n codeLines = [];\n }\n continue;\n }\n\n if (inCodeBlock) {\n codeLines.push(rawLine);\n continue;\n }\n\n const listMatch = line.match(/^\\s*[-*+]\\s+(.*)$/);\n if (listMatch) {\n flushQuote();\n listBuffer = listBuffer ?? [];\n listBuffer.push(inlineMarkdown(listMatch[1].trim()));\n continue;\n }\n\n if (listBuffer) flushList();\n\n const headingMatch = line.match(/^(#{1,6})\\s+(.*)$/);\n if (headingMatch) {\n flushQuote();\n const level = headingMatch[1].length;\n const content = inlineMarkdown(headingMatch[2].trim());\n blocks.push(`<h${level}>${content}</h${level}>`);\n continue;\n }\n\n const quoteMatch = line.match(/^>\\s?(.*)$/);\n if (quoteMatch) {\n quoteBuffer = quoteBuffer ?? [];\n quoteBuffer.push(quoteMatch[1]);\n continue;\n }\n\n if (quoteBuffer) flushQuote();\n\n if (!line.trim()) {\n continue;\n }\n\n blocks.push(`<p>${inlineMarkdown(line.trim())}</p>`);\n }\n\n flushList();\n flushQuote();\n flushCode();\n\n return blocks.join(\"\\n\");\n}\n\nfunction renderWithMarked(markdown: string, marked: MarkedModule): string | null {\n if (!marked.parse) return null;\n\n const renderer = marked.Renderer ? new marked.Renderer() : undefined;\n\n if (renderer) {\n renderer.link = (href, _title, text) => {\n const safeUrl = sanitizeUrl(href);\n if (!safeUrl) return escapeHtml(text);\n return `<a href=\"${escapeHtml(safeUrl)}\" target=\"_blank\" rel=\"noopener noreferrer\">${text}</a>`;\n };\n renderer.image = (href, _title, text) => {\n const safeUrl = sanitizeUrl(href);\n const safeAlt = escapeHtml(text ?? \"\");\n if (!safeUrl) return safeAlt;\n return `<img src=\"${escapeHtml(safeUrl)}\" alt=\"${safeAlt}\" />`;\n };\n }\n\n const output = marked.parse(markdown, renderer ? { renderer } : undefined);\n if (typeof output === \"string\") return output;\n return output ? String(output) : null;\n}\n\n/**\n * Parse a feature description from markdown into sanitized HTML.\n * - Uses `marked` when installed (optional peer dep)\n * - Falls back to a tiny built-in parser when `marked` is absent\n * - Strips script tags, event handlers, and javascript:/data: URLs\n */\nexport function parseDescription(markdown: string): string {\n if (!markdown) return \"\";\n\n const marked = getMarked();\n if (marked) {\n try {\n const rendered = renderWithMarked(markdown, marked);\n if (rendered) {\n const sanitized = sanitizeHtml(rendered);\n const decoded = decodeAllowedEntities(sanitized);\n return sanitizeHtml(decoded);\n }\n } catch {\n // If marked fails for any reason, fall back to the tiny parser.\n }\n }\n\n // Fast path: raw HTML provided without `marked` installed\n if (/<[^>]+>/.test(markdown)) {\n const sanitized = sanitizeHtml(markdown);\n const decoded = decodeAllowedEntities(sanitized);\n return sanitizeHtml(decoded);\n }\n\n const fallback = fallbackParse(markdown);\n const sanitized = sanitizeHtml(fallback);\n const decoded = decodeAllowedEntities(sanitized);\n return sanitizeHtml(decoded);\n}\n","import type { FeatureManifest } from \"./types\";\nimport { parseDescription } from \"./markdown\";\n\nfunction escape(str: string): string {\n return str\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\");\n}\n\n/**\n * Generate a simple RSS 2.0 feed from a feature manifest.\n * Titles and descriptions are sanitized via `parseDescription`.\n */\nexport function generateRSS(manifest: FeatureManifest, options?: { title?: string; link?: string; description?: string }): string {\n const title = escape(options?.title ?? \"Featuredrop Changelog\");\n const link = escape(options?.link ?? \"\");\n const desc = escape(options?.description ?? \"Product updates\");\n\n const items = manifest\n .slice()\n .sort((a, b) => new Date(b.releasedAt).getTime() - new Date(a.releasedAt).getTime())\n .map((item) => {\n const descriptionHtml = item.description ? parseDescription(item.description) : \"\";\n const itemLink = item.url ? escape(item.url) : \"\";\n return [\n \"<item>\",\n `<title>${escape(item.label)}</title>`,\n itemLink ? `<link>${itemLink}</link>` : \"\",\n `<guid isPermaLink=\\\"false\\\">${escape(item.id)}</guid>`,\n `<pubDate>${new Date(item.releasedAt).toUTCString()}</pubDate>`,\n `<description><![CDATA[${descriptionHtml}]]></description>`,\n \"</item>\",\n ].join(\"\");\n })\n .join(\"\");\n\n return [\n \"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?>\",\n \"<rss version=\\\"2.0\\\">\",\n \"<channel>\",\n `<title>${title}</title>`,\n link ? `<link>${link}</link>` : \"\",\n `<description>${desc}</description>`,\n items,\n \"</channel>\",\n \"</rss>\",\n ].join(\"\");\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "featuredrop",
3
- "version": "1.4.0",
3
+ "version": "2.1.0",
4
4
  "description": "Lightweight feature discovery system. Show 'New' badges that auto-expire.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -30,6 +30,36 @@
30
30
  "default": "./dist/react.cjs"
31
31
  }
32
32
  },
33
+ "./adapters": {
34
+ "import": {
35
+ "types": "./dist/adapters.d.ts",
36
+ "default": "./dist/adapters.js"
37
+ },
38
+ "require": {
39
+ "types": "./dist/adapters.d.cts",
40
+ "default": "./dist/adapters.cjs"
41
+ }
42
+ },
43
+ "./markdown": {
44
+ "import": {
45
+ "types": "./dist/markdown.d.ts",
46
+ "default": "./dist/markdown.js"
47
+ },
48
+ "require": {
49
+ "types": "./dist/markdown.d.cts",
50
+ "default": "./dist/markdown.cjs"
51
+ }
52
+ },
53
+ "./rss": {
54
+ "import": {
55
+ "types": "./dist/rss.d.ts",
56
+ "default": "./dist/rss.js"
57
+ },
58
+ "require": {
59
+ "types": "./dist/rss.d.cts",
60
+ "default": "./dist/rss.cjs"
61
+ }
62
+ },
33
63
  "./admin": {
34
64
  "import": {
35
65
  "types": "./dist/admin.d.ts",
@@ -116,6 +146,16 @@
116
146
  "default": "./dist/bridges.cjs"
117
147
  }
118
148
  },
149
+ "./cms": {
150
+ "import": {
151
+ "types": "./dist/cms.d.ts",
152
+ "default": "./dist/cms.js"
153
+ },
154
+ "require": {
155
+ "types": "./dist/cms.d.cts",
156
+ "default": "./dist/cms.cjs"
157
+ }
158
+ },
119
159
  "./ci": {
120
160
  "import": {
121
161
  "types": "./dist/ci.d.ts",
@@ -126,6 +166,16 @@
126
166
  "default": "./dist/ci.cjs"
127
167
  }
128
168
  },
169
+ "./renderer": {
170
+ "import": {
171
+ "types": "./dist/renderer.d.ts",
172
+ "default": "./dist/renderer.js"
173
+ },
174
+ "require": {
175
+ "types": "./dist/renderer.d.cts",
176
+ "default": "./dist/renderer.cjs"
177
+ }
178
+ },
129
179
  "./flags": {
130
180
  "import": {
131
181
  "types": "./dist/flags.d.ts",
@@ -152,6 +202,15 @@
152
202
  "react": [
153
203
  "./dist/react.d.ts"
154
204
  ],
205
+ "adapters": [
206
+ "./dist/adapters.d.ts"
207
+ ],
208
+ "markdown": [
209
+ "./dist/markdown.d.ts"
210
+ ],
211
+ "rss": [
212
+ "./dist/rss.d.ts"
213
+ ],
155
214
  "admin": [
156
215
  "./dist/admin.d.ts"
157
216
  ],
@@ -176,9 +235,15 @@
176
235
  "bridges": [
177
236
  "./dist/bridges.d.ts"
178
237
  ],
238
+ "cms": [
239
+ "./dist/cms.d.ts"
240
+ ],
179
241
  "ci": [
180
242
  "./dist/ci.d.ts"
181
243
  ],
244
+ "renderer": [
245
+ "./dist/renderer.d.ts"
246
+ ],
182
247
  "flags": [
183
248
  "./dist/flags.d.ts"
184
249
  ]
@@ -234,7 +299,7 @@
234
299
  "type": "git",
235
300
  "url": "https://github.com/GLINCKER/featuredrop"
236
301
  },
237
- "homepage": "https://github.com/GLINCKER/featuredrop#readme",
302
+ "homepage": "https://featuredrop.dev",
238
303
  "bugs": {
239
304
  "url": "https://github.com/GLINCKER/featuredrop/issues"
240
305
  },
@@ -246,7 +311,8 @@
246
311
  "react": ">=18",
247
312
  "solid-js": ">=1.8.0",
248
313
  "svelte": ">=5.0.0",
249
- "vue": ">=3.3"
314
+ "vue": ">=3.3",
315
+ "zod": ">=3.22.0"
250
316
  },
251
317
  "peerDependenciesMeta": {
252
318
  "preact": {
@@ -263,11 +329,11 @@
263
329
  },
264
330
  "svelte": {
265
331
  "optional": true
332
+ },
333
+ "zod": {
334
+ "optional": true
266
335
  }
267
336
  },
268
- "dependencies": {
269
- "zod": "^3.24.1"
270
- },
271
337
  "devDependencies": {
272
338
  "@commitlint/cli": "^19.0.0",
273
339
  "@commitlint/config-conventional": "^19.0.0",