featuredrop 1.4.0 → 2.2.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/README.md +287 -760
- package/dist/adapters.cjs +1757 -0
- package/dist/adapters.cjs.map +1 -0
- package/dist/adapters.d.cts +744 -0
- package/dist/adapters.d.ts +744 -0
- package/dist/adapters.js +1745 -0
- package/dist/adapters.js.map +1 -0
- package/dist/admin.cjs +148 -32
- package/dist/admin.cjs.map +1 -1
- package/dist/admin.d.cts +14 -3
- package/dist/admin.d.ts +14 -3
- package/dist/admin.js +148 -32
- package/dist/admin.js.map +1 -1
- package/dist/bridges.cjs +111 -13
- package/dist/bridges.cjs.map +1 -1
- package/dist/bridges.d.cts +12 -5
- package/dist/bridges.d.ts +12 -5
- package/dist/bridges.js +111 -13
- package/dist/bridges.js.map +1 -1
- package/dist/ci.cjs +34 -0
- package/dist/ci.cjs.map +1 -1
- package/dist/ci.d.cts +5 -1
- package/dist/ci.d.ts +5 -1
- package/dist/ci.js +34 -1
- package/dist/ci.js.map +1 -1
- package/dist/cms.cjs +835 -0
- package/dist/cms.cjs.map +1 -0
- package/dist/cms.d.cts +236 -0
- package/dist/cms.d.ts +236 -0
- package/dist/cms.js +829 -0
- package/dist/cms.js.map +1 -0
- package/dist/flags.cjs +27 -7
- package/dist/flags.cjs.map +1 -1
- package/dist/flags.d.cts +14 -0
- package/dist/flags.d.ts +14 -0
- package/dist/flags.js +27 -7
- package/dist/flags.js.map +1 -1
- package/dist/index.cjs +52 -4481
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1340
- package/dist/index.d.ts +1 -1340
- package/dist/index.js +53 -4388
- package/dist/index.js.map +1 -1
- package/dist/markdown.cjs +257 -0
- package/dist/markdown.cjs.map +1 -0
- package/dist/markdown.d.cts +9 -0
- package/dist/markdown.d.ts +9 -0
- package/dist/markdown.js +234 -0
- package/dist/markdown.js.map +1 -0
- package/dist/renderer.cjs +503 -0
- package/dist/renderer.cjs.map +1 -0
- package/dist/renderer.d.cts +250 -0
- package/dist/renderer.d.ts +250 -0
- package/dist/renderer.js +501 -0
- package/dist/renderer.js.map +1 -0
- package/dist/rss.cjs +291 -0
- package/dist/rss.cjs.map +1 -0
- package/dist/rss.d.cts +158 -0
- package/dist/rss.d.ts +158 -0
- package/dist/rss.js +268 -0
- package/dist/rss.js.map +1 -0
- package/package.json +72 -6
package/dist/bridges.cjs
CHANGED
|
@@ -287,17 +287,115 @@ function generateRSS(manifest, options) {
|
|
|
287
287
|
}
|
|
288
288
|
|
|
289
289
|
// src/bridges.ts
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
290
|
+
var DEFAULT_RETRY_STATUSES = [408, 429, 500, 502, 503, 504];
|
|
291
|
+
var BridgeRequestError = class extends Error {
|
|
292
|
+
status;
|
|
293
|
+
retryable;
|
|
294
|
+
constructor(message, options = {}) {
|
|
295
|
+
super(message);
|
|
296
|
+
this.name = "BridgeRequestError";
|
|
297
|
+
this.status = options.status;
|
|
298
|
+
this.retryable = options.retryable ?? false;
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
function sleep(ms) {
|
|
302
|
+
if (ms <= 0) return Promise.resolve();
|
|
303
|
+
return new Promise((resolve) => {
|
|
304
|
+
setTimeout(resolve, ms);
|
|
298
305
|
});
|
|
299
|
-
|
|
300
|
-
|
|
306
|
+
}
|
|
307
|
+
function createAbortSignal(timeoutMs, sourceSignal) {
|
|
308
|
+
if (!timeoutMs && !sourceSignal) {
|
|
309
|
+
return {
|
|
310
|
+
signal: void 0,
|
|
311
|
+
cleanup: () => {
|
|
312
|
+
},
|
|
313
|
+
timedOut: () => false
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
const controller = new AbortController();
|
|
317
|
+
let timeoutId;
|
|
318
|
+
let timeoutTriggered = false;
|
|
319
|
+
let onAbort;
|
|
320
|
+
if (sourceSignal) {
|
|
321
|
+
if (sourceSignal.aborted) {
|
|
322
|
+
controller.abort(sourceSignal.reason);
|
|
323
|
+
} else {
|
|
324
|
+
onAbort = () => controller.abort(sourceSignal.reason);
|
|
325
|
+
sourceSignal.addEventListener("abort", onAbort, { once: true });
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
if (timeoutMs && timeoutMs > 0) {
|
|
329
|
+
timeoutId = setTimeout(() => {
|
|
330
|
+
timeoutTriggered = true;
|
|
331
|
+
controller.abort();
|
|
332
|
+
}, timeoutMs);
|
|
333
|
+
}
|
|
334
|
+
return {
|
|
335
|
+
signal: controller.signal,
|
|
336
|
+
cleanup: () => {
|
|
337
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
338
|
+
if (sourceSignal && onAbort) sourceSignal.removeEventListener("abort", onAbort);
|
|
339
|
+
},
|
|
340
|
+
timedOut: () => timeoutTriggered
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
async function readResponseText(response) {
|
|
344
|
+
const hasText = response && typeof response === "object" && "text" in response && typeof response.text === "function";
|
|
345
|
+
if (!hasText) return "";
|
|
346
|
+
try {
|
|
347
|
+
const text = await response.text();
|
|
348
|
+
return text.trim();
|
|
349
|
+
} catch {
|
|
350
|
+
return "";
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
async function postJson(url, payload, options = {}) {
|
|
354
|
+
const maxRetries = Math.max(0, options.maxRetries ?? 0);
|
|
355
|
+
const retryDelayMs = Math.max(0, options.retryDelayMs ?? 250);
|
|
356
|
+
const retryStatuses = new Set(options.retryOnStatuses ?? DEFAULT_RETRY_STATUSES);
|
|
357
|
+
let attempt = 0;
|
|
358
|
+
for (; ; ) {
|
|
359
|
+
const { signal, cleanup, timedOut } = createAbortSignal(options.timeoutMs, options.signal);
|
|
360
|
+
try {
|
|
361
|
+
const response = await fetch(url, {
|
|
362
|
+
method: "POST",
|
|
363
|
+
headers: {
|
|
364
|
+
"Content-Type": "application/json",
|
|
365
|
+
...options.headers ?? {}
|
|
366
|
+
},
|
|
367
|
+
body: JSON.stringify(payload),
|
|
368
|
+
signal
|
|
369
|
+
});
|
|
370
|
+
if (response.ok) return;
|
|
371
|
+
const retryableStatus = retryStatuses.has(response.status);
|
|
372
|
+
const responseText = await readResponseText(response);
|
|
373
|
+
const message = responseText ? `[featuredrop] Bridge request failed (${response.status}) for ${url}: ${responseText}` : `[featuredrop] Bridge request failed (${response.status}) for ${url}`;
|
|
374
|
+
if (!retryableStatus || attempt >= maxRetries) {
|
|
375
|
+
throw new BridgeRequestError(message, {
|
|
376
|
+
status: response.status,
|
|
377
|
+
retryable: retryableStatus
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
} catch (error) {
|
|
381
|
+
const isBridgeRequestError = error instanceof BridgeRequestError;
|
|
382
|
+
const isAbortError = error instanceof DOMException ? error.name === "AbortError" : error && typeof error === "object" && "name" in error && error.name === "AbortError";
|
|
383
|
+
if (isAbortError && timedOut()) {
|
|
384
|
+
throw new BridgeRequestError(
|
|
385
|
+
`[featuredrop] Bridge request timed out after ${options.timeoutMs}ms for ${url}`
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
const shouldRetry = isBridgeRequestError ? error.retryable && attempt < maxRetries : attempt < maxRetries;
|
|
389
|
+
if (!shouldRetry) {
|
|
390
|
+
if (error instanceof Error) throw error;
|
|
391
|
+
throw new Error(`[featuredrop] Bridge request failed for ${url}`);
|
|
392
|
+
}
|
|
393
|
+
} finally {
|
|
394
|
+
cleanup();
|
|
395
|
+
}
|
|
396
|
+
attempt += 1;
|
|
397
|
+
const waitMs = retryDelayMs * 2 ** (attempt - 1);
|
|
398
|
+
await sleep(waitMs);
|
|
301
399
|
}
|
|
302
400
|
}
|
|
303
401
|
function formatFeatureLine(feature) {
|
|
@@ -325,7 +423,7 @@ var SlackBridge = {
|
|
|
325
423
|
}
|
|
326
424
|
]
|
|
327
425
|
};
|
|
328
|
-
await postJson(options.webhookUrl, payload);
|
|
426
|
+
await postJson(options.webhookUrl, payload, options);
|
|
329
427
|
}
|
|
330
428
|
};
|
|
331
429
|
var DiscordBridge = {
|
|
@@ -345,7 +443,7 @@ var DiscordBridge = {
|
|
|
345
443
|
}
|
|
346
444
|
]
|
|
347
445
|
};
|
|
348
|
-
await postJson(options.webhookUrl, payload);
|
|
446
|
+
await postJson(options.webhookUrl, payload, options);
|
|
349
447
|
}
|
|
350
448
|
};
|
|
351
449
|
var WebhookBridge = {
|
|
@@ -356,7 +454,7 @@ var WebhookBridge = {
|
|
|
356
454
|
sentAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
357
455
|
...options.body ?? {}
|
|
358
456
|
};
|
|
359
|
-
await postJson(options.url, payload, options
|
|
457
|
+
await postJson(options.url, payload, options);
|
|
360
458
|
}
|
|
361
459
|
};
|
|
362
460
|
var EmailDigestGenerator = {
|
package/dist/bridges.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/markdown.ts","../src/rss.ts","../src/bridges.ts"],"names":["moduleApi","sanitized","decoded"],"mappings":"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","file":"bridges.cjs","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, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\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(/<(\\/?)([a-z0-9]+)([^>]*)>/gi, (match, slash, tag, rest) => {\n if (!allowTags.includes(tag.toLowerCase())) return match;\n const decodedRest = rest\n .replace(/"/g, '\"')\n .replace(/'/g, \"'\")\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/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: \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, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\");\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","import { generateRSS } from \"./rss\";\nimport type { FeatureEntry, FeatureManifest } from \"./types\";\n\nasync function postJson(url: string, payload: unknown, headers?: Record<string, string>): Promise<void> {\n const response = await fetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...(headers ?? {}),\n },\n body: JSON.stringify(payload),\n });\n\n if (!response.ok) {\n throw new Error(`[featuredrop] Bridge request failed (${response.status}) for ${url}`);\n }\n}\n\nfunction formatFeatureLine(feature: FeatureEntry): string {\n const released = new Date(feature.releasedAt).toLocaleDateString(\"en-US\", {\n year: \"numeric\",\n month: \"short\",\n day: \"numeric\",\n });\n return `${feature.label} (${released})`;\n}\n\nexport interface SlackBridgeOptions {\n webhookUrl: string;\n username?: string;\n iconEmoji?: string;\n channel?: string;\n formatter?: (feature: FeatureEntry) => Record<string, unknown>;\n}\n\nexport const SlackBridge = {\n async notify(feature: FeatureEntry, options: SlackBridgeOptions): Promise<void> {\n const payload = options.formatter\n ? options.formatter(feature)\n : {\n username: options.username,\n icon_emoji: options.iconEmoji,\n channel: options.channel,\n text: `New feature published: *${feature.label}*`,\n attachments: [\n {\n color: \"#2563eb\",\n title: feature.label,\n text: feature.description ?? \"No description provided.\",\n title_link: feature.url,\n footer: `featuredrop | ${feature.id}`,\n },\n ],\n };\n await postJson(options.webhookUrl, payload);\n },\n};\n\nexport interface DiscordBridgeOptions {\n webhookUrl: string;\n username?: string;\n avatarUrl?: string;\n formatter?: (feature: FeatureEntry) => Record<string, unknown>;\n}\n\nexport const DiscordBridge = {\n async notify(feature: FeatureEntry, options: DiscordBridgeOptions): Promise<void> {\n const payload = options.formatter\n ? options.formatter(feature)\n : {\n username: options.username ?? \"featuredrop\",\n avatar_url: options.avatarUrl,\n embeds: [\n {\n title: feature.label,\n description: feature.description ?? \"No description provided.\",\n url: feature.url,\n color: 0x2563eb,\n footer: {\n text: `featuredrop | ${feature.id}`,\n },\n },\n ],\n };\n await postJson(options.webhookUrl, payload);\n },\n};\n\nexport interface WebhookBridgeOptions {\n url: string;\n headers?: Record<string, string>;\n event?: string;\n body?: Record<string, unknown>;\n}\n\nexport const WebhookBridge = {\n async post(feature: FeatureEntry, options: WebhookBridgeOptions): Promise<void> {\n const payload = {\n event: options.event ?? \"feature.published\",\n feature,\n sentAt: new Date().toISOString(),\n ...(options.body ?? {}),\n };\n await postJson(options.url, payload, options.headers);\n },\n};\n\nexport interface EmailDigestGeneratorOptions {\n title?: string;\n intro?: string;\n template?: \"default\" | \"minimal\";\n productName?: string;\n}\n\nexport const EmailDigestGenerator = {\n generate(features: readonly FeatureEntry[], options: EmailDigestGeneratorOptions = {}): string {\n const title = options.title ?? \"Product Updates\";\n const intro = options.intro ?? \"Here are the latest updates:\";\n const productName = options.productName ?? \"Your Product\";\n const template = options.template ?? \"default\";\n\n const listItems = features\n .map((feature) => {\n const safeLabel = feature.label.replace(/</g, \"<\").replace(/>/g, \">\");\n const safeDescription = (feature.description ?? \"\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\");\n const link = feature.url\n ? `<a href=\"${feature.url}\" style=\"color:#2563eb;text-decoration:none;\">Read more</a>`\n : \"\";\n\n if (template === \"minimal\") {\n return `<li><strong>${safeLabel}</strong>${safeDescription ? ` - ${safeDescription}` : \"\"}</li>`;\n }\n\n return [\n \"<li style=\\\"margin:0 0 14px;\\\">\",\n `<p style=\"margin:0 0 4px;font-weight:600;color:#111827;\">${safeLabel}</p>`,\n safeDescription\n ? `<p style=\"margin:0 0 6px;color:#4b5563;line-height:1.45;\">${safeDescription}</p>`\n : \"\",\n link ? `<p style=\"margin:0;\">${link}</p>` : \"\",\n \"</li>\",\n ].join(\"\");\n })\n .join(\"\");\n\n if (template === \"minimal\") {\n return [\n \"<!doctype html>\",\n \"<html><body>\",\n `<h2>${title}</h2>`,\n `<p>${intro}</p>`,\n `<ul>${listItems}</ul>`,\n \"</body></html>\",\n ].join(\"\");\n }\n\n const summary = features.map((feature) => formatFeatureLine(feature)).join(\" | \");\n return [\n \"<!doctype html>\",\n \"<html>\",\n \"<body style=\\\"font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;background:#f8fafc;padding:20px;\\\">\",\n \"<div style=\\\"max-width:640px;margin:0 auto;background:#ffffff;border:1px solid #e5e7eb;border-radius:12px;padding:20px;\\\">\",\n `<p style=\"margin:0 0 12px;color:#6b7280;font-size:12px;letter-spacing:0.08em;text-transform:uppercase;\">${productName}</p>`,\n `<h1 style=\"margin:0 0 8px;font-size:22px;color:#111827;\">${title}</h1>`,\n `<p style=\"margin:0 0 14px;color:#374151;\">${intro}</p>`,\n `<p style=\"margin:0 0 18px;color:#6b7280;font-size:13px;\">${summary}</p>`,\n `<ul style=\"padding-left:18px;margin:0;\">${listItems}</ul>`,\n \"</div>\",\n \"</body>\",\n \"</html>\",\n ].join(\"\");\n },\n};\n\nexport interface RSSFeedGeneratorOptions {\n title?: string;\n link?: string;\n description?: string;\n}\n\nexport const RSSFeedGenerator = {\n generate(manifest: FeatureManifest, options?: RSSFeedGeneratorOptions): string {\n return generateRSS(manifest, options);\n },\n};\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/markdown.ts","../src/rss.ts","../src/bridges.ts"],"names":["moduleApi","sanitized","decoded"],"mappings":"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","file":"bridges.cjs","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, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\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(/<(\\/?)([a-z0-9]+)([^>]*)>/gi, (match, slash, tag, rest) => {\n if (!allowTags.includes(tag.toLowerCase())) return match;\n const decodedRest = rest\n .replace(/"/g, '\"')\n .replace(/'/g, \"'\")\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/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: \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, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\");\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","import { generateRSS } from \"./rss\";\nimport type { FeatureEntry, FeatureManifest } from \"./types\";\n\nexport interface BridgeRequestOptions {\n headers?: Record<string, string>;\n timeoutMs?: number;\n maxRetries?: number;\n retryDelayMs?: number;\n retryOnStatuses?: number[];\n signal?: AbortSignal;\n}\n\nconst DEFAULT_RETRY_STATUSES = [408, 429, 500, 502, 503, 504];\n\nclass BridgeRequestError extends Error {\n readonly status?: number;\n readonly retryable: boolean;\n\n constructor(message: string, options: { status?: number; retryable?: boolean } = {}) {\n super(message);\n this.name = \"BridgeRequestError\";\n this.status = options.status;\n this.retryable = options.retryable ?? false;\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n if (ms <= 0) return Promise.resolve();\n return new Promise((resolve) => {\n setTimeout(resolve, ms);\n });\n}\n\nfunction createAbortSignal(\n timeoutMs: number | undefined,\n sourceSignal: AbortSignal | undefined,\n): { signal: AbortSignal | undefined; cleanup: () => void; timedOut: () => boolean } {\n if (!timeoutMs && !sourceSignal) {\n return {\n signal: undefined,\n cleanup: () => {\n // noop\n },\n timedOut: () => false,\n };\n }\n\n const controller = new AbortController();\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n let timeoutTriggered = false;\n let onAbort: (() => void) | undefined;\n\n if (sourceSignal) {\n if (sourceSignal.aborted) {\n controller.abort(sourceSignal.reason);\n } else {\n onAbort = () => controller.abort(sourceSignal.reason);\n sourceSignal.addEventListener(\"abort\", onAbort, { once: true });\n }\n }\n\n if (timeoutMs && timeoutMs > 0) {\n timeoutId = setTimeout(() => {\n timeoutTriggered = true;\n controller.abort();\n }, timeoutMs);\n }\n\n return {\n signal: controller.signal,\n cleanup: () => {\n if (timeoutId) clearTimeout(timeoutId);\n if (sourceSignal && onAbort) sourceSignal.removeEventListener(\"abort\", onAbort);\n },\n timedOut: () => timeoutTriggered,\n };\n}\n\nasync function readResponseText(response: Response): Promise<string> {\n const hasText =\n response &&\n typeof response === \"object\" &&\n \"text\" in response &&\n typeof response.text === \"function\";\n if (!hasText) return \"\";\n try {\n const text = await response.text();\n return text.trim();\n } catch {\n return \"\";\n }\n}\n\nasync function postJson(\n url: string,\n payload: unknown,\n options: BridgeRequestOptions = {},\n): Promise<void> {\n const maxRetries = Math.max(0, options.maxRetries ?? 0);\n const retryDelayMs = Math.max(0, options.retryDelayMs ?? 250);\n const retryStatuses = new Set(options.retryOnStatuses ?? DEFAULT_RETRY_STATUSES);\n\n let attempt = 0;\n for (;;) {\n const { signal, cleanup, timedOut } = createAbortSignal(options.timeoutMs, options.signal);\n try {\n const response = await fetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...(options.headers ?? {}),\n },\n body: JSON.stringify(payload),\n signal,\n });\n\n if (response.ok) return;\n\n const retryableStatus = retryStatuses.has(response.status);\n const responseText = await readResponseText(response);\n const message = responseText\n ? `[featuredrop] Bridge request failed (${response.status}) for ${url}: ${responseText}`\n : `[featuredrop] Bridge request failed (${response.status}) for ${url}`;\n\n if (!retryableStatus || attempt >= maxRetries) {\n throw new BridgeRequestError(message, {\n status: response.status,\n retryable: retryableStatus,\n });\n }\n } catch (error: unknown) {\n const isBridgeRequestError = error instanceof BridgeRequestError;\n const isAbortError =\n error instanceof DOMException\n ? error.name === \"AbortError\"\n : error &&\n typeof error === \"object\" &&\n \"name\" in error &&\n (error as { name?: string }).name === \"AbortError\";\n\n if (isAbortError && timedOut()) {\n throw new BridgeRequestError(\n `[featuredrop] Bridge request timed out after ${options.timeoutMs}ms for ${url}`,\n );\n }\n\n const shouldRetry = isBridgeRequestError\n ? error.retryable && attempt < maxRetries\n : attempt < maxRetries;\n\n if (!shouldRetry) {\n if (error instanceof Error) throw error;\n throw new Error(`[featuredrop] Bridge request failed for ${url}`);\n }\n } finally {\n cleanup();\n }\n\n attempt += 1;\n const waitMs = retryDelayMs * 2 ** (attempt - 1);\n await sleep(waitMs);\n }\n}\n\nfunction formatFeatureLine(feature: FeatureEntry): string {\n const released = new Date(feature.releasedAt).toLocaleDateString(\"en-US\", {\n year: \"numeric\",\n month: \"short\",\n day: \"numeric\",\n });\n return `${feature.label} (${released})`;\n}\n\nexport interface SlackBridgeOptions extends BridgeRequestOptions {\n webhookUrl: string;\n username?: string;\n iconEmoji?: string;\n channel?: string;\n formatter?: (feature: FeatureEntry) => Record<string, unknown>;\n}\n\nexport const SlackBridge = {\n async notify(feature: FeatureEntry, options: SlackBridgeOptions): Promise<void> {\n const payload = options.formatter\n ? options.formatter(feature)\n : {\n username: options.username,\n icon_emoji: options.iconEmoji,\n channel: options.channel,\n text: `New feature published: *${feature.label}*`,\n attachments: [\n {\n color: \"#2563eb\",\n title: feature.label,\n text: feature.description ?? \"No description provided.\",\n title_link: feature.url,\n footer: `featuredrop | ${feature.id}`,\n },\n ],\n };\n await postJson(options.webhookUrl, payload, options);\n },\n};\n\nexport interface DiscordBridgeOptions extends BridgeRequestOptions {\n webhookUrl: string;\n username?: string;\n avatarUrl?: string;\n formatter?: (feature: FeatureEntry) => Record<string, unknown>;\n}\n\nexport const DiscordBridge = {\n async notify(feature: FeatureEntry, options: DiscordBridgeOptions): Promise<void> {\n const payload = options.formatter\n ? options.formatter(feature)\n : {\n username: options.username ?? \"featuredrop\",\n avatar_url: options.avatarUrl,\n embeds: [\n {\n title: feature.label,\n description: feature.description ?? \"No description provided.\",\n url: feature.url,\n color: 0x2563eb,\n footer: {\n text: `featuredrop | ${feature.id}`,\n },\n },\n ],\n };\n await postJson(options.webhookUrl, payload, options);\n },\n};\n\nexport interface WebhookBridgeOptions extends BridgeRequestOptions {\n url: string;\n event?: string;\n body?: Record<string, unknown>;\n}\n\nexport const WebhookBridge = {\n async post(feature: FeatureEntry, options: WebhookBridgeOptions): Promise<void> {\n const payload = {\n event: options.event ?? \"feature.published\",\n feature,\n sentAt: new Date().toISOString(),\n ...(options.body ?? {}),\n };\n await postJson(options.url, payload, options);\n },\n};\n\nexport interface EmailDigestGeneratorOptions {\n title?: string;\n intro?: string;\n template?: \"default\" | \"minimal\";\n productName?: string;\n}\n\nexport const EmailDigestGenerator = {\n generate(features: readonly FeatureEntry[], options: EmailDigestGeneratorOptions = {}): string {\n const title = options.title ?? \"Product Updates\";\n const intro = options.intro ?? \"Here are the latest updates:\";\n const productName = options.productName ?? \"Your Product\";\n const template = options.template ?? \"default\";\n\n const listItems = features\n .map((feature) => {\n const safeLabel = feature.label.replace(/</g, \"<\").replace(/>/g, \">\");\n const safeDescription = (feature.description ?? \"\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\");\n const link = feature.url\n ? `<a href=\"${feature.url}\" style=\"color:#2563eb;text-decoration:none;\">Read more</a>`\n : \"\";\n\n if (template === \"minimal\") {\n return `<li><strong>${safeLabel}</strong>${safeDescription ? ` - ${safeDescription}` : \"\"}</li>`;\n }\n\n return [\n \"<li style=\\\"margin:0 0 14px;\\\">\",\n `<p style=\"margin:0 0 4px;font-weight:600;color:#111827;\">${safeLabel}</p>`,\n safeDescription\n ? `<p style=\"margin:0 0 6px;color:#4b5563;line-height:1.45;\">${safeDescription}</p>`\n : \"\",\n link ? `<p style=\"margin:0;\">${link}</p>` : \"\",\n \"</li>\",\n ].join(\"\");\n })\n .join(\"\");\n\n if (template === \"minimal\") {\n return [\n \"<!doctype html>\",\n \"<html><body>\",\n `<h2>${title}</h2>`,\n `<p>${intro}</p>`,\n `<ul>${listItems}</ul>`,\n \"</body></html>\",\n ].join(\"\");\n }\n\n const summary = features.map((feature) => formatFeatureLine(feature)).join(\" | \");\n return [\n \"<!doctype html>\",\n \"<html>\",\n \"<body style=\\\"font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;background:#f8fafc;padding:20px;\\\">\",\n \"<div style=\\\"max-width:640px;margin:0 auto;background:#ffffff;border:1px solid #e5e7eb;border-radius:12px;padding:20px;\\\">\",\n `<p style=\"margin:0 0 12px;color:#6b7280;font-size:12px;letter-spacing:0.08em;text-transform:uppercase;\">${productName}</p>`,\n `<h1 style=\"margin:0 0 8px;font-size:22px;color:#111827;\">${title}</h1>`,\n `<p style=\"margin:0 0 14px;color:#374151;\">${intro}</p>`,\n `<p style=\"margin:0 0 18px;color:#6b7280;font-size:13px;\">${summary}</p>`,\n `<ul style=\"padding-left:18px;margin:0;\">${listItems}</ul>`,\n \"</div>\",\n \"</body>\",\n \"</html>\",\n ].join(\"\");\n },\n};\n\nexport interface RSSFeedGeneratorOptions {\n title?: string;\n link?: string;\n description?: string;\n}\n\nexport const RSSFeedGenerator = {\n generate(manifest: FeatureManifest, options?: RSSFeedGeneratorOptions): string {\n return generateRSS(manifest, options);\n },\n};\n"]}
|
package/dist/bridges.d.cts
CHANGED
|
@@ -145,7 +145,15 @@ interface FeatureEntry {
|
|
|
145
145
|
/** The full feature manifest — an array of feature entries */
|
|
146
146
|
type FeatureManifest = readonly FeatureEntry[];
|
|
147
147
|
|
|
148
|
-
interface
|
|
148
|
+
interface BridgeRequestOptions {
|
|
149
|
+
headers?: Record<string, string>;
|
|
150
|
+
timeoutMs?: number;
|
|
151
|
+
maxRetries?: number;
|
|
152
|
+
retryDelayMs?: number;
|
|
153
|
+
retryOnStatuses?: number[];
|
|
154
|
+
signal?: AbortSignal;
|
|
155
|
+
}
|
|
156
|
+
interface SlackBridgeOptions extends BridgeRequestOptions {
|
|
149
157
|
webhookUrl: string;
|
|
150
158
|
username?: string;
|
|
151
159
|
iconEmoji?: string;
|
|
@@ -155,7 +163,7 @@ interface SlackBridgeOptions {
|
|
|
155
163
|
declare const SlackBridge: {
|
|
156
164
|
notify(feature: FeatureEntry, options: SlackBridgeOptions): Promise<void>;
|
|
157
165
|
};
|
|
158
|
-
interface DiscordBridgeOptions {
|
|
166
|
+
interface DiscordBridgeOptions extends BridgeRequestOptions {
|
|
159
167
|
webhookUrl: string;
|
|
160
168
|
username?: string;
|
|
161
169
|
avatarUrl?: string;
|
|
@@ -164,9 +172,8 @@ interface DiscordBridgeOptions {
|
|
|
164
172
|
declare const DiscordBridge: {
|
|
165
173
|
notify(feature: FeatureEntry, options: DiscordBridgeOptions): Promise<void>;
|
|
166
174
|
};
|
|
167
|
-
interface WebhookBridgeOptions {
|
|
175
|
+
interface WebhookBridgeOptions extends BridgeRequestOptions {
|
|
168
176
|
url: string;
|
|
169
|
-
headers?: Record<string, string>;
|
|
170
177
|
event?: string;
|
|
171
178
|
body?: Record<string, unknown>;
|
|
172
179
|
}
|
|
@@ -191,4 +198,4 @@ declare const RSSFeedGenerator: {
|
|
|
191
198
|
generate(manifest: FeatureManifest, options?: RSSFeedGeneratorOptions): string;
|
|
192
199
|
};
|
|
193
200
|
|
|
194
|
-
export { DiscordBridge, type DiscordBridgeOptions, EmailDigestGenerator, type EmailDigestGeneratorOptions, RSSFeedGenerator, type RSSFeedGeneratorOptions, SlackBridge, type SlackBridgeOptions, WebhookBridge, type WebhookBridgeOptions };
|
|
201
|
+
export { type BridgeRequestOptions, DiscordBridge, type DiscordBridgeOptions, EmailDigestGenerator, type EmailDigestGeneratorOptions, RSSFeedGenerator, type RSSFeedGeneratorOptions, SlackBridge, type SlackBridgeOptions, WebhookBridge, type WebhookBridgeOptions };
|
package/dist/bridges.d.ts
CHANGED
|
@@ -145,7 +145,15 @@ interface FeatureEntry {
|
|
|
145
145
|
/** The full feature manifest — an array of feature entries */
|
|
146
146
|
type FeatureManifest = readonly FeatureEntry[];
|
|
147
147
|
|
|
148
|
-
interface
|
|
148
|
+
interface BridgeRequestOptions {
|
|
149
|
+
headers?: Record<string, string>;
|
|
150
|
+
timeoutMs?: number;
|
|
151
|
+
maxRetries?: number;
|
|
152
|
+
retryDelayMs?: number;
|
|
153
|
+
retryOnStatuses?: number[];
|
|
154
|
+
signal?: AbortSignal;
|
|
155
|
+
}
|
|
156
|
+
interface SlackBridgeOptions extends BridgeRequestOptions {
|
|
149
157
|
webhookUrl: string;
|
|
150
158
|
username?: string;
|
|
151
159
|
iconEmoji?: string;
|
|
@@ -155,7 +163,7 @@ interface SlackBridgeOptions {
|
|
|
155
163
|
declare const SlackBridge: {
|
|
156
164
|
notify(feature: FeatureEntry, options: SlackBridgeOptions): Promise<void>;
|
|
157
165
|
};
|
|
158
|
-
interface DiscordBridgeOptions {
|
|
166
|
+
interface DiscordBridgeOptions extends BridgeRequestOptions {
|
|
159
167
|
webhookUrl: string;
|
|
160
168
|
username?: string;
|
|
161
169
|
avatarUrl?: string;
|
|
@@ -164,9 +172,8 @@ interface DiscordBridgeOptions {
|
|
|
164
172
|
declare const DiscordBridge: {
|
|
165
173
|
notify(feature: FeatureEntry, options: DiscordBridgeOptions): Promise<void>;
|
|
166
174
|
};
|
|
167
|
-
interface WebhookBridgeOptions {
|
|
175
|
+
interface WebhookBridgeOptions extends BridgeRequestOptions {
|
|
168
176
|
url: string;
|
|
169
|
-
headers?: Record<string, string>;
|
|
170
177
|
event?: string;
|
|
171
178
|
body?: Record<string, unknown>;
|
|
172
179
|
}
|
|
@@ -191,4 +198,4 @@ declare const RSSFeedGenerator: {
|
|
|
191
198
|
generate(manifest: FeatureManifest, options?: RSSFeedGeneratorOptions): string;
|
|
192
199
|
};
|
|
193
200
|
|
|
194
|
-
export { DiscordBridge, type DiscordBridgeOptions, EmailDigestGenerator, type EmailDigestGeneratorOptions, RSSFeedGenerator, type RSSFeedGeneratorOptions, SlackBridge, type SlackBridgeOptions, WebhookBridge, type WebhookBridgeOptions };
|
|
201
|
+
export { type BridgeRequestOptions, DiscordBridge, type DiscordBridgeOptions, EmailDigestGenerator, type EmailDigestGeneratorOptions, RSSFeedGenerator, type RSSFeedGeneratorOptions, SlackBridge, type SlackBridgeOptions, WebhookBridge, type WebhookBridgeOptions };
|
package/dist/bridges.js
CHANGED
|
@@ -264,17 +264,115 @@ function generateRSS(manifest, options) {
|
|
|
264
264
|
}
|
|
265
265
|
|
|
266
266
|
// src/bridges.ts
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
267
|
+
var DEFAULT_RETRY_STATUSES = [408, 429, 500, 502, 503, 504];
|
|
268
|
+
var BridgeRequestError = class extends Error {
|
|
269
|
+
status;
|
|
270
|
+
retryable;
|
|
271
|
+
constructor(message, options = {}) {
|
|
272
|
+
super(message);
|
|
273
|
+
this.name = "BridgeRequestError";
|
|
274
|
+
this.status = options.status;
|
|
275
|
+
this.retryable = options.retryable ?? false;
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
function sleep(ms) {
|
|
279
|
+
if (ms <= 0) return Promise.resolve();
|
|
280
|
+
return new Promise((resolve) => {
|
|
281
|
+
setTimeout(resolve, ms);
|
|
275
282
|
});
|
|
276
|
-
|
|
277
|
-
|
|
283
|
+
}
|
|
284
|
+
function createAbortSignal(timeoutMs, sourceSignal) {
|
|
285
|
+
if (!timeoutMs && !sourceSignal) {
|
|
286
|
+
return {
|
|
287
|
+
signal: void 0,
|
|
288
|
+
cleanup: () => {
|
|
289
|
+
},
|
|
290
|
+
timedOut: () => false
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
const controller = new AbortController();
|
|
294
|
+
let timeoutId;
|
|
295
|
+
let timeoutTriggered = false;
|
|
296
|
+
let onAbort;
|
|
297
|
+
if (sourceSignal) {
|
|
298
|
+
if (sourceSignal.aborted) {
|
|
299
|
+
controller.abort(sourceSignal.reason);
|
|
300
|
+
} else {
|
|
301
|
+
onAbort = () => controller.abort(sourceSignal.reason);
|
|
302
|
+
sourceSignal.addEventListener("abort", onAbort, { once: true });
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
if (timeoutMs && timeoutMs > 0) {
|
|
306
|
+
timeoutId = setTimeout(() => {
|
|
307
|
+
timeoutTriggered = true;
|
|
308
|
+
controller.abort();
|
|
309
|
+
}, timeoutMs);
|
|
310
|
+
}
|
|
311
|
+
return {
|
|
312
|
+
signal: controller.signal,
|
|
313
|
+
cleanup: () => {
|
|
314
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
315
|
+
if (sourceSignal && onAbort) sourceSignal.removeEventListener("abort", onAbort);
|
|
316
|
+
},
|
|
317
|
+
timedOut: () => timeoutTriggered
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
async function readResponseText(response) {
|
|
321
|
+
const hasText = response && typeof response === "object" && "text" in response && typeof response.text === "function";
|
|
322
|
+
if (!hasText) return "";
|
|
323
|
+
try {
|
|
324
|
+
const text = await response.text();
|
|
325
|
+
return text.trim();
|
|
326
|
+
} catch {
|
|
327
|
+
return "";
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
async function postJson(url, payload, options = {}) {
|
|
331
|
+
const maxRetries = Math.max(0, options.maxRetries ?? 0);
|
|
332
|
+
const retryDelayMs = Math.max(0, options.retryDelayMs ?? 250);
|
|
333
|
+
const retryStatuses = new Set(options.retryOnStatuses ?? DEFAULT_RETRY_STATUSES);
|
|
334
|
+
let attempt = 0;
|
|
335
|
+
for (; ; ) {
|
|
336
|
+
const { signal, cleanup, timedOut } = createAbortSignal(options.timeoutMs, options.signal);
|
|
337
|
+
try {
|
|
338
|
+
const response = await fetch(url, {
|
|
339
|
+
method: "POST",
|
|
340
|
+
headers: {
|
|
341
|
+
"Content-Type": "application/json",
|
|
342
|
+
...options.headers ?? {}
|
|
343
|
+
},
|
|
344
|
+
body: JSON.stringify(payload),
|
|
345
|
+
signal
|
|
346
|
+
});
|
|
347
|
+
if (response.ok) return;
|
|
348
|
+
const retryableStatus = retryStatuses.has(response.status);
|
|
349
|
+
const responseText = await readResponseText(response);
|
|
350
|
+
const message = responseText ? `[featuredrop] Bridge request failed (${response.status}) for ${url}: ${responseText}` : `[featuredrop] Bridge request failed (${response.status}) for ${url}`;
|
|
351
|
+
if (!retryableStatus || attempt >= maxRetries) {
|
|
352
|
+
throw new BridgeRequestError(message, {
|
|
353
|
+
status: response.status,
|
|
354
|
+
retryable: retryableStatus
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
} catch (error) {
|
|
358
|
+
const isBridgeRequestError = error instanceof BridgeRequestError;
|
|
359
|
+
const isAbortError = error instanceof DOMException ? error.name === "AbortError" : error && typeof error === "object" && "name" in error && error.name === "AbortError";
|
|
360
|
+
if (isAbortError && timedOut()) {
|
|
361
|
+
throw new BridgeRequestError(
|
|
362
|
+
`[featuredrop] Bridge request timed out after ${options.timeoutMs}ms for ${url}`
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
const shouldRetry = isBridgeRequestError ? error.retryable && attempt < maxRetries : attempt < maxRetries;
|
|
366
|
+
if (!shouldRetry) {
|
|
367
|
+
if (error instanceof Error) throw error;
|
|
368
|
+
throw new Error(`[featuredrop] Bridge request failed for ${url}`);
|
|
369
|
+
}
|
|
370
|
+
} finally {
|
|
371
|
+
cleanup();
|
|
372
|
+
}
|
|
373
|
+
attempt += 1;
|
|
374
|
+
const waitMs = retryDelayMs * 2 ** (attempt - 1);
|
|
375
|
+
await sleep(waitMs);
|
|
278
376
|
}
|
|
279
377
|
}
|
|
280
378
|
function formatFeatureLine(feature) {
|
|
@@ -302,7 +400,7 @@ var SlackBridge = {
|
|
|
302
400
|
}
|
|
303
401
|
]
|
|
304
402
|
};
|
|
305
|
-
await postJson(options.webhookUrl, payload);
|
|
403
|
+
await postJson(options.webhookUrl, payload, options);
|
|
306
404
|
}
|
|
307
405
|
};
|
|
308
406
|
var DiscordBridge = {
|
|
@@ -322,7 +420,7 @@ var DiscordBridge = {
|
|
|
322
420
|
}
|
|
323
421
|
]
|
|
324
422
|
};
|
|
325
|
-
await postJson(options.webhookUrl, payload);
|
|
423
|
+
await postJson(options.webhookUrl, payload, options);
|
|
326
424
|
}
|
|
327
425
|
};
|
|
328
426
|
var WebhookBridge = {
|
|
@@ -333,7 +431,7 @@ var WebhookBridge = {
|
|
|
333
431
|
sentAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
334
432
|
...options.body ?? {}
|
|
335
433
|
};
|
|
336
|
-
await postJson(options.url, payload, options
|
|
434
|
+
await postJson(options.url, payload, options);
|
|
337
435
|
}
|
|
338
436
|
};
|
|
339
437
|
var EmailDigestGenerator = {
|