featuredrop 2.7.1 → 3.0.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.
- package/README.md +34 -1
- package/dist/astro.cjs +333 -0
- package/dist/astro.cjs.map +1 -0
- package/dist/astro.d.cts +242 -0
- package/dist/astro.d.ts +242 -0
- package/dist/astro.js +329 -0
- package/dist/astro.js.map +1 -0
- package/dist/engine.cjs +552 -0
- package/dist/engine.cjs.map +1 -0
- package/dist/engine.d.cts +422 -0
- package/dist/engine.d.ts +422 -0
- package/dist/engine.js +545 -0
- package/dist/engine.js.map +1 -0
- package/dist/featuredrop.cjs +208 -1
- package/dist/featuredrop.cjs.map +1 -1
- package/dist/next.cjs +336 -0
- package/dist/next.cjs.map +1 -0
- package/dist/next.d.cts +243 -0
- package/dist/next.d.ts +243 -0
- package/dist/next.js +332 -0
- package/dist/next.js.map +1 -0
- package/dist/nuxt.cjs +352 -0
- package/dist/nuxt.cjs.map +1 -0
- package/dist/nuxt.d.cts +282 -0
- package/dist/nuxt.d.ts +282 -0
- package/dist/nuxt.js +347 -0
- package/dist/nuxt.js.map +1 -0
- package/dist/preact.cjs +354 -0
- package/dist/preact.cjs.map +1 -1
- package/dist/preact.d.cts +170 -1
- package/dist/preact.d.ts +170 -1
- package/dist/preact.js +350 -1
- package/dist/preact.js.map +1 -1
- package/dist/react-hooks.cjs +82 -0
- package/dist/react-hooks.cjs.map +1 -1
- package/dist/react-hooks.d.cts +117 -1
- package/dist/react-hooks.d.ts +117 -1
- package/dist/react-hooks.js +80 -1
- package/dist/react-hooks.js.map +1 -1
- package/dist/react.cjs +354 -0
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +170 -1
- package/dist/react.d.ts +170 -1
- package/dist/react.js +350 -1
- package/dist/react.js.map +1 -1
- package/dist/remix.cjs +331 -0
- package/dist/remix.cjs.map +1 -0
- package/dist/remix.d.cts +305 -0
- package/dist/remix.d.ts +305 -0
- package/dist/remix.js +327 -0
- package/dist/remix.js.map +1 -0
- package/package.json +70 -2
package/README.md
CHANGED
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
<a href="https://bundlephobia.com/package/featuredrop"><img src="https://img.shields.io/bundlephobia/minzip/featuredrop?color=22c55e&label=gzipped" alt="bundle size" /></a>
|
|
18
18
|
<a href="https://github.com/GLINCKER/featuredrop/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/featuredrop?color=6366f1&label=license" alt="MIT license" /></a>
|
|
19
19
|
<a href="https://featuredrop.dev"><img src="https://img.shields.io/badge/docs-live-ea580c" alt="Live docs" /></a>
|
|
20
|
+
<a href="https://www.producthunt.com/products/featuredrop"><img src="https://img.shields.io/badge/Product%20Hunt-Featured-ff6154?logo=producthunt&logoColor=white" alt="Product Hunt" /></a>
|
|
21
|
+
<a href="https://context7.com/glincker/featuredrop"><img src="https://img.shields.io/badge/Context7-Indexed-0ea5e9?logo=data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMTIgMkM2LjQ4IDIgMiA2LjQ4IDIgMTJzNC40OCAxMCAxMCAxMCAxMC00LjQ4IDEwLTEwUzE3LjUyIDIgMTIgMnoiIGZpbGw9IndoaXRlIi8+PC9zdmc+" alt="Context7" /></a>
|
|
20
22
|
</p>
|
|
21
23
|
|
|
22
24
|
<p align="center">
|
|
@@ -267,6 +269,28 @@ function MyChangelog() {
|
|
|
267
269
|
|
|
268
270
|
> **When to use hooks vs components:** If your project uses shadcn/ui, Radix, or any custom design system, use hooks from `featuredrop/react/hooks`. If you want out-of-the-box UI, use components from `featuredrop/react`.
|
|
269
271
|
|
|
272
|
+
### shadcn/ui Components
|
|
273
|
+
|
|
274
|
+
Pre-built components that use shadcn primitives for UI + FeatureDrop hooks for logic. Install via the shadcn CLI:
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
npx shadcn@latest add https://featuredrop.dev/r/changelog-widget.json
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
| Component | Install |
|
|
281
|
+
|-----------|---------|
|
|
282
|
+
| New Badge | `npx shadcn@latest add https://featuredrop.dev/r/new-badge.json` |
|
|
283
|
+
| Changelog Widget | `npx shadcn@latest add https://featuredrop.dev/r/changelog-widget.json` |
|
|
284
|
+
| Tour | `npx shadcn@latest add https://featuredrop.dev/r/tour.json` |
|
|
285
|
+
| Checklist | `npx shadcn@latest add https://featuredrop.dev/r/checklist.json` |
|
|
286
|
+
| Feedback Widget | `npx shadcn@latest add https://featuredrop.dev/r/feedback-widget.json` |
|
|
287
|
+
|
|
288
|
+
Components land in `components/featuredrop/` — you own the code. [Full docs →](https://featuredrop.dev/docs/shadcn)
|
|
289
|
+
|
|
290
|
+
**Try it now:**
|
|
291
|
+
- [Next.js + shadcn example](https://stackblitz.com/github/GLINCKER/featuredrop/tree/main/examples/nextjs-shadcn) — Open in StackBlitz
|
|
292
|
+
- [Vanilla JS example](examples/vanilla/index.html) — Zero build step, CDN import
|
|
293
|
+
|
|
270
294
|
---
|
|
271
295
|
|
|
272
296
|
## AI-Native
|
|
@@ -282,6 +306,15 @@ FeatureDrop is built for the AI coding era. Your AI assistant already knows how
|
|
|
282
306
|
|
|
283
307
|
Then just ask: *"Add a changelog widget with auto-expiring badges to my app"* — Claude handles the rest.
|
|
284
308
|
|
|
309
|
+
### Context7 (works with any AI assistant)
|
|
310
|
+
|
|
311
|
+
```bash
|
|
312
|
+
# Search and install the FeatureDrop skill
|
|
313
|
+
npx ctx7 skills search FeatureDrop
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
Any AI coding assistant with Context7 support gets up-to-date FeatureDrop docs, API reference, and best practices injected into context automatically.
|
|
317
|
+
|
|
285
318
|
### Cursor / Copilot
|
|
286
319
|
|
|
287
320
|
```bash
|
|
@@ -406,7 +439,7 @@ npx featuredrop migrate --from beamer --input beamer-export.json --out features.
|
|
|
406
439
|
|
|
407
440
|
| | Beamer | Pendo | **FeatureDrop** |
|
|
408
441
|
|---|---|---|---|
|
|
409
|
-
| Price | $59–399/mo | $7k+/yr | **Free
|
|
442
|
+
| Price | $59–399/mo | $7k+/yr | **Free (MIT)** |
|
|
410
443
|
| Bundle impact | External script | ~300 kB agent | **< 3 kB core** |
|
|
411
444
|
| Vendor lock-in | Yes | Yes | **No** |
|
|
412
445
|
| Data ownership | Vendor-hosted | Vendor-hosted | **Your repo** |
|
package/dist/astro.cjs
ADDED
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/semver.ts
|
|
4
|
+
var SEMVER_REGEX = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?/;
|
|
5
|
+
function parseSemver(input) {
|
|
6
|
+
const match = input.trim().match(SEMVER_REGEX);
|
|
7
|
+
if (!match) return null;
|
|
8
|
+
return {
|
|
9
|
+
major: Number(match[1]),
|
|
10
|
+
minor: Number(match[2]),
|
|
11
|
+
patch: Number(match[3]),
|
|
12
|
+
prerelease: match[4] ? match[4].split(".") : []
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function compareSemver(a, b) {
|
|
16
|
+
const pa = parseSemver(a);
|
|
17
|
+
const pb = parseSemver(b);
|
|
18
|
+
if (!pa || !pb) return 0;
|
|
19
|
+
for (const key of ["major", "minor", "patch"]) {
|
|
20
|
+
if (pa[key] !== pb[key]) return pa[key] - pb[key];
|
|
21
|
+
}
|
|
22
|
+
const aPre = pa.prerelease;
|
|
23
|
+
const bPre = pb.prerelease;
|
|
24
|
+
if (aPre.length === 0 && bPre.length === 0) return 0;
|
|
25
|
+
if (aPre.length === 0) return 1;
|
|
26
|
+
if (bPre.length === 0) return -1;
|
|
27
|
+
const len = Math.max(aPre.length, bPre.length);
|
|
28
|
+
for (let i = 0; i < len; i++) {
|
|
29
|
+
const ai = aPre[i];
|
|
30
|
+
const bi = bPre[i];
|
|
31
|
+
if (ai === void 0) return -1;
|
|
32
|
+
if (bi === void 0) return 1;
|
|
33
|
+
const aNum = Number(ai);
|
|
34
|
+
const bNum = Number(bi);
|
|
35
|
+
const aIsNum = Number.isInteger(aNum);
|
|
36
|
+
const bIsNum = Number.isInteger(bNum);
|
|
37
|
+
if (aIsNum && bIsNum && aNum !== bNum) return aNum - bNum;
|
|
38
|
+
if (aIsNum !== bIsNum) return aIsNum ? -1 : 1;
|
|
39
|
+
if (ai !== bi) return ai < bi ? -1 : 1;
|
|
40
|
+
}
|
|
41
|
+
return 0;
|
|
42
|
+
}
|
|
43
|
+
function parseComparator(comp) {
|
|
44
|
+
const match = comp.trim().match(/^(>=|<=|>|<|=)?\\s*(.+)$/);
|
|
45
|
+
if (!match) return null;
|
|
46
|
+
const op = match[1] || ">=";
|
|
47
|
+
const version = match[2];
|
|
48
|
+
if (!parseSemver(version)) return null;
|
|
49
|
+
return { op, version };
|
|
50
|
+
}
|
|
51
|
+
function satisfiesComparator(version, comp) {
|
|
52
|
+
const diff = compareSemver(version, comp.version);
|
|
53
|
+
switch (comp.op) {
|
|
54
|
+
case ">":
|
|
55
|
+
return diff > 0;
|
|
56
|
+
case ">=":
|
|
57
|
+
return diff >= 0;
|
|
58
|
+
case "<":
|
|
59
|
+
return diff < 0;
|
|
60
|
+
case "<=":
|
|
61
|
+
return diff <= 0;
|
|
62
|
+
case "=":
|
|
63
|
+
return diff === 0;
|
|
64
|
+
default:
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function satisfiesRange(version, range) {
|
|
69
|
+
const parts = range.split(/\s+/).filter(Boolean);
|
|
70
|
+
if (parts.length === 0) return true;
|
|
71
|
+
for (const part of parts) {
|
|
72
|
+
const comp = parseComparator(part);
|
|
73
|
+
if (!comp) return false;
|
|
74
|
+
if (!satisfiesComparator(version, comp)) return false;
|
|
75
|
+
}
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// src/triggers.ts
|
|
80
|
+
function wildcardToRegExp(value) {
|
|
81
|
+
const escaped = value.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
|
|
82
|
+
const pattern = `^${escaped.replace(/\*/g, ".*")}$`;
|
|
83
|
+
return new RegExp(pattern);
|
|
84
|
+
}
|
|
85
|
+
function matchPath(path, pattern) {
|
|
86
|
+
if (pattern instanceof RegExp) return pattern.test(path);
|
|
87
|
+
if (!pattern) return false;
|
|
88
|
+
if (pattern.includes("*")) return wildcardToRegExp(pattern).test(path);
|
|
89
|
+
return path === pattern || path.startsWith(pattern);
|
|
90
|
+
}
|
|
91
|
+
function isTriggerMatch(trigger, context) {
|
|
92
|
+
if (!trigger) return true;
|
|
93
|
+
if (!context) return false;
|
|
94
|
+
if (trigger.type === "page") {
|
|
95
|
+
const path = context.path;
|
|
96
|
+
if (!path) return false;
|
|
97
|
+
return matchPath(path, trigger.match);
|
|
98
|
+
}
|
|
99
|
+
if (trigger.type === "usage") {
|
|
100
|
+
const usage = context.usage ?? {};
|
|
101
|
+
const count = usage[trigger.event] ?? 0;
|
|
102
|
+
return count >= (trigger.minActions ?? 1);
|
|
103
|
+
}
|
|
104
|
+
if (trigger.type === "time") {
|
|
105
|
+
const elapsedMs = context.elapsedMs ?? 0;
|
|
106
|
+
return elapsedMs >= trigger.minSeconds * 1e3;
|
|
107
|
+
}
|
|
108
|
+
if (trigger.type === "milestone") {
|
|
109
|
+
return context.milestones?.has(trigger.event) ?? false;
|
|
110
|
+
}
|
|
111
|
+
if (trigger.type === "frustration") {
|
|
112
|
+
const usage = context.usage ?? {};
|
|
113
|
+
const count = usage[trigger.pattern] ?? 0;
|
|
114
|
+
return count >= (trigger.threshold ?? 1);
|
|
115
|
+
}
|
|
116
|
+
if (trigger.type === "scroll") {
|
|
117
|
+
return (context.scrollPercent ?? 0) >= (trigger.minPercent ?? 50);
|
|
118
|
+
}
|
|
119
|
+
try {
|
|
120
|
+
return trigger.evaluate(context);
|
|
121
|
+
} catch {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// src/core.ts
|
|
127
|
+
function matchesAudience(audience, userContext) {
|
|
128
|
+
if (audience.plan && audience.plan.length > 0) {
|
|
129
|
+
if (!userContext.plan || !audience.plan.includes(userContext.plan)) {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (audience.role && audience.role.length > 0) {
|
|
134
|
+
if (!userContext.role || !audience.role.includes(userContext.role)) {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (audience.region && audience.region.length > 0) {
|
|
139
|
+
if (!userContext.region || !audience.region.includes(userContext.region)) {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
function isAudienceMatch(feature, userContext, matchFn) {
|
|
146
|
+
if (!feature.audience) return true;
|
|
147
|
+
const { plan, role, region, custom } = feature.audience;
|
|
148
|
+
const hasRules = plan && plan.length > 0 || role && role.length > 0 || region && region.length > 0 || custom && Object.keys(custom).length > 0;
|
|
149
|
+
if (!hasRules) return true;
|
|
150
|
+
if (!userContext) return false;
|
|
151
|
+
if (matchFn) return matchFn(feature.audience, userContext);
|
|
152
|
+
return matchesAudience(feature.audience, userContext);
|
|
153
|
+
}
|
|
154
|
+
function isVersionMatch(feature, appVersion) {
|
|
155
|
+
const v = feature.version;
|
|
156
|
+
if (!v || typeof v === "string") return true;
|
|
157
|
+
if (!appVersion) return false;
|
|
158
|
+
if (!v.introduced && !v.showNewUntil && !v.deprecatedAt && !v.showIn) return true;
|
|
159
|
+
if (v.showIn && !satisfiesRange(appVersion, v.showIn)) return false;
|
|
160
|
+
if (v.introduced && compareSemver(appVersion, v.introduced) < 0) return false;
|
|
161
|
+
if (v.deprecatedAt && compareSemver(appVersion, v.deprecatedAt) >= 0) return false;
|
|
162
|
+
if (v.showNewUntil && compareSemver(appVersion, v.showNewUntil) >= 0) return false;
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
function isFlagMatch(feature, flagBridge, userContext) {
|
|
166
|
+
if (!feature.flagKey) return true;
|
|
167
|
+
if (!flagBridge) return false;
|
|
168
|
+
try {
|
|
169
|
+
return flagBridge.isEnabled(feature.flagKey, userContext);
|
|
170
|
+
} catch {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function isProductMatch(feature, product) {
|
|
175
|
+
if (!feature.product || feature.product === "*") return true;
|
|
176
|
+
if (!product) return false;
|
|
177
|
+
return feature.product === product;
|
|
178
|
+
}
|
|
179
|
+
function isDependencyMatch(feature, dismissedIds, dependencyState) {
|
|
180
|
+
const dependsOn = feature.dependsOn;
|
|
181
|
+
if (!dependsOn) return true;
|
|
182
|
+
const seenIds = dependencyState?.seenIds;
|
|
183
|
+
const clickedIds = dependencyState?.clickedIds;
|
|
184
|
+
const dismissedDependencyIds = dependencyState?.dismissedIds ?? dismissedIds;
|
|
185
|
+
if (dependsOn.seen && dependsOn.seen.length > 0) {
|
|
186
|
+
for (const id of dependsOn.seen) {
|
|
187
|
+
const seen = seenIds?.has(id) ?? false;
|
|
188
|
+
if (!seen && !dismissedDependencyIds.has(id)) return false;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
if (dependsOn.clicked && dependsOn.clicked.length > 0) {
|
|
192
|
+
for (const id of dependsOn.clicked) {
|
|
193
|
+
if (!(clickedIds?.has(id) ?? false)) return false;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (dependsOn.dismissed && dependsOn.dismissed.length > 0) {
|
|
197
|
+
for (const id of dependsOn.dismissed) {
|
|
198
|
+
if (!dismissedDependencyIds.has(id)) return false;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
function isNew(feature, watermark, dismissedIds, now = /* @__PURE__ */ new Date(), userContext, matchAudience, appVersion, dependencyState, triggerContext, flagBridge, product) {
|
|
204
|
+
if (dismissedIds.has(feature.id)) return false;
|
|
205
|
+
if (!isAudienceMatch(feature, userContext, matchAudience)) return false;
|
|
206
|
+
if (!isDependencyMatch(feature, dismissedIds, dependencyState)) return false;
|
|
207
|
+
if (!isVersionMatch(feature, appVersion)) return false;
|
|
208
|
+
if (!isFlagMatch(feature, flagBridge, userContext)) return false;
|
|
209
|
+
if (!isProductMatch(feature, product)) return false;
|
|
210
|
+
if (!isTriggerMatch(feature.trigger, triggerContext)) return false;
|
|
211
|
+
const nowMs = now.getTime();
|
|
212
|
+
if (feature.publishAt) {
|
|
213
|
+
const publishMs = new Date(feature.publishAt).getTime();
|
|
214
|
+
if (nowMs < publishMs) return false;
|
|
215
|
+
}
|
|
216
|
+
const showUntilMs = new Date(feature.showNewUntil).getTime();
|
|
217
|
+
if (nowMs >= showUntilMs) return false;
|
|
218
|
+
if (watermark) {
|
|
219
|
+
const watermarkMs = new Date(watermark).getTime();
|
|
220
|
+
const releasedMs = new Date(feature.releasedAt).getTime();
|
|
221
|
+
if (releasedMs <= watermarkMs) return false;
|
|
222
|
+
}
|
|
223
|
+
return true;
|
|
224
|
+
}
|
|
225
|
+
function getNewFeatures(manifest, storage, now = /* @__PURE__ */ new Date(), userContext, matchAudience, appVersion, dependencyState, triggerContext, flagBridge, product) {
|
|
226
|
+
const watermark = storage.getWatermark();
|
|
227
|
+
const dismissedIds = storage.getDismissedIds();
|
|
228
|
+
return manifest.filter(
|
|
229
|
+
(f) => isNew(
|
|
230
|
+
f,
|
|
231
|
+
watermark,
|
|
232
|
+
dismissedIds,
|
|
233
|
+
now,
|
|
234
|
+
userContext,
|
|
235
|
+
matchAudience,
|
|
236
|
+
appVersion,
|
|
237
|
+
dependencyState,
|
|
238
|
+
triggerContext,
|
|
239
|
+
flagBridge,
|
|
240
|
+
product
|
|
241
|
+
)
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
function getNewFeatureCount(manifest, storage, now = /* @__PURE__ */ new Date(), userContext, matchAudience, appVersion, dependencyState, triggerContext, flagBridge, product) {
|
|
245
|
+
return getNewFeatures(
|
|
246
|
+
manifest,
|
|
247
|
+
storage,
|
|
248
|
+
now,
|
|
249
|
+
userContext,
|
|
250
|
+
matchAudience,
|
|
251
|
+
appVersion,
|
|
252
|
+
dependencyState,
|
|
253
|
+
triggerContext,
|
|
254
|
+
flagBridge,
|
|
255
|
+
product
|
|
256
|
+
).length;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// src/adapters/memory.ts
|
|
260
|
+
var MemoryAdapter = class {
|
|
261
|
+
watermark;
|
|
262
|
+
dismissed;
|
|
263
|
+
constructor(options = {}) {
|
|
264
|
+
this.watermark = options.watermark ?? null;
|
|
265
|
+
this.dismissed = /* @__PURE__ */ new Set();
|
|
266
|
+
}
|
|
267
|
+
getWatermark() {
|
|
268
|
+
return this.watermark;
|
|
269
|
+
}
|
|
270
|
+
getDismissedIds() {
|
|
271
|
+
return this.dismissed;
|
|
272
|
+
}
|
|
273
|
+
dismiss(id) {
|
|
274
|
+
this.dismissed.add(id);
|
|
275
|
+
}
|
|
276
|
+
async dismissAll(now) {
|
|
277
|
+
this.watermark = now.toISOString();
|
|
278
|
+
this.dismissed.clear();
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
// src/astro/index.ts
|
|
283
|
+
function getNewFeaturesServer(manifest, dismissedIds, options) {
|
|
284
|
+
const storage = new MemoryAdapter();
|
|
285
|
+
if (dismissedIds) {
|
|
286
|
+
for (const id of dismissedIds) {
|
|
287
|
+
storage.dismiss(id);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return getNewFeatures(
|
|
291
|
+
manifest,
|
|
292
|
+
storage,
|
|
293
|
+
options?.now,
|
|
294
|
+
options?.userContext,
|
|
295
|
+
options?.matchAudience,
|
|
296
|
+
options?.appVersion,
|
|
297
|
+
options?.dependencyState,
|
|
298
|
+
options?.triggerContext,
|
|
299
|
+
options?.flagBridge,
|
|
300
|
+
options?.product
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
function getNewCountServer(manifest, dismissedIds, options) {
|
|
304
|
+
const storage = new MemoryAdapter();
|
|
305
|
+
if (dismissedIds) {
|
|
306
|
+
for (const id of dismissedIds) {
|
|
307
|
+
storage.dismiss(id);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return getNewFeatureCount(
|
|
311
|
+
manifest,
|
|
312
|
+
storage,
|
|
313
|
+
options?.now,
|
|
314
|
+
options?.userContext,
|
|
315
|
+
options?.matchAudience,
|
|
316
|
+
options?.appVersion,
|
|
317
|
+
options?.dependencyState,
|
|
318
|
+
options?.triggerContext,
|
|
319
|
+
options?.flagBridge,
|
|
320
|
+
options?.product
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
function getManifestScript(manifest, dismissedIds) {
|
|
324
|
+
const data = JSON.stringify({ manifest, dismissedIds: dismissedIds ?? [] });
|
|
325
|
+
const safe = data.replace(/<\/script/gi, "<\\/script");
|
|
326
|
+
return `<script id="__FEATUREDROP_DATA__" type="application/json">${safe}</script>`;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
exports.getManifestScript = getManifestScript;
|
|
330
|
+
exports.getNewCountServer = getNewCountServer;
|
|
331
|
+
exports.getNewFeaturesServer = getNewFeaturesServer;
|
|
332
|
+
//# sourceMappingURL=astro.cjs.map
|
|
333
|
+
//# sourceMappingURL=astro.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/semver.ts","../src/triggers.ts","../src/core.ts","../src/adapters/memory.ts","../src/astro/index.ts"],"names":[],"mappings":"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","file":"astro.cjs","sourcesContent":["// Minimal semver comparison utilities (no build metadata sorting needed)\n\nexport type Comparator = \">=\" | \"<=\" | \">\" | \"<\" | \"=\";\n\nexport interface SemverParts {\n major: number;\n minor: number;\n patch: number;\n prerelease: string[];\n}\n\nconst SEMVER_REGEX = /^(\\d+)\\.(\\d+)\\.(\\d+)(?:-([0-9A-Za-z.-]+))?/;\n\nexport function parseSemver(input: string): SemverParts | null {\n const match = input.trim().match(SEMVER_REGEX);\n if (!match) return null;\n return {\n major: Number(match[1]),\n minor: Number(match[2]),\n patch: Number(match[3]),\n prerelease: match[4] ? match[4].split(\".\") : [],\n };\n}\n\nexport function compareSemver(a: string, b: string): number {\n const pa = parseSemver(a);\n const pb = parseSemver(b);\n if (!pa || !pb) return 0;\n\n for (const key of [\"major\", \"minor\", \"patch\"] as const) {\n if (pa[key] !== pb[key]) return pa[key] - pb[key];\n }\n\n // Handle prerelease: absence > presence, otherwise lexicographic\n const aPre = pa.prerelease;\n const bPre = pb.prerelease;\n if (aPre.length === 0 && bPre.length === 0) return 0;\n if (aPre.length === 0) return 1;\n if (bPre.length === 0) return -1;\n\n const len = Math.max(aPre.length, bPre.length);\n for (let i = 0; i < len; i++) {\n const ai = aPre[i];\n const bi = bPre[i];\n if (ai === undefined) return -1;\n if (bi === undefined) return 1;\n const aNum = Number(ai);\n const bNum = Number(bi);\n const aIsNum = Number.isInteger(aNum);\n const bIsNum = Number.isInteger(bNum);\n if (aIsNum && bIsNum && aNum !== bNum) return aNum - bNum;\n if (aIsNum !== bIsNum) return aIsNum ? -1 : 1;\n if (ai !== bi) return ai < bi ? -1 : 1;\n }\n return 0;\n}\n\nfunction parseComparator(comp: string): { op: Comparator; version: string } | null {\n const match = comp.trim().match(/^(>=|<=|>|<|=)?\\\\s*(.+)$/);\n if (!match) return null;\n const op = (match[1] as Comparator) || \">=\";\n const version = match[2];\n if (!parseSemver(version)) return null;\n return { op, version };\n}\n\nfunction satisfiesComparator(version: string, comp: { op: Comparator; version: string }): boolean {\n const diff = compareSemver(version, comp.version);\n switch (comp.op) {\n case \">\":\n return diff > 0;\n case \">=\":\n return diff >= 0;\n case \"<\":\n return diff < 0;\n case \"<=\":\n return diff <= 0;\n case \"=\":\n return diff === 0;\n default:\n return false;\n }\n}\n\n// Space-separated comparator list (AND semantics), e.g. \">=2.5.0 <3.0.0\"\nexport function satisfiesRange(version: string, range: string): boolean {\n const parts = range.split(/\\s+/).filter(Boolean);\n if (parts.length === 0) return true;\n for (const part of parts) {\n const comp = parseComparator(part);\n if (!comp) return false;\n if (!satisfiesComparator(version, comp)) return false;\n }\n return true;\n}\n","import type { FeatureEntry, FeatureTrigger, TriggerContext } from \"./types\";\n\nfunction wildcardToRegExp(value: string): RegExp {\n const escaped = value.replace(/[.+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const pattern = `^${escaped.replace(/\\*/g, \".*\")}$`;\n return new RegExp(pattern);\n}\n\nfunction matchPath(path: string, pattern: string | RegExp): boolean {\n if (pattern instanceof RegExp) return pattern.test(path);\n if (!pattern) return false;\n if (pattern.includes(\"*\")) return wildcardToRegExp(pattern).test(path);\n return path === pattern || path.startsWith(pattern);\n}\n\nexport function isTriggerMatch(trigger: FeatureTrigger | undefined, context?: TriggerContext): boolean {\n if (!trigger) return true;\n if (!context) return false;\n\n if (trigger.type === \"page\") {\n const path = context.path;\n if (!path) return false;\n return matchPath(path, trigger.match);\n }\n\n if (trigger.type === \"usage\") {\n const usage = context.usage ?? {};\n const count = usage[trigger.event] ?? 0;\n return count >= (trigger.minActions ?? 1);\n }\n\n if (trigger.type === \"time\") {\n const elapsedMs = context.elapsedMs ?? 0;\n return elapsedMs >= trigger.minSeconds * 1000;\n }\n\n if (trigger.type === \"milestone\") {\n return context.milestones?.has(trigger.event) ?? false;\n }\n\n if (trigger.type === \"frustration\") {\n const usage = context.usage ?? {};\n const count = usage[trigger.pattern] ?? 0;\n return count >= (trigger.threshold ?? 1);\n }\n\n if (trigger.type === \"scroll\") {\n return (context.scrollPercent ?? 0) >= (trigger.minPercent ?? 50);\n }\n\n try {\n return trigger.evaluate(context);\n } catch {\n return false;\n }\n}\n\nexport class TriggerEngine {\n private context: TriggerContext;\n\n constructor(initial?: TriggerContext) {\n this.context = {\n path: initial?.path,\n events: new Set(initial?.events ?? []),\n milestones: new Set(initial?.milestones ?? []),\n usage: { ...(initial?.usage ?? {}) },\n elapsedMs: initial?.elapsedMs ?? 0,\n scrollPercent: initial?.scrollPercent ?? 0,\n metadata: { ...(initial?.metadata ?? {}) },\n };\n }\n\n setPath(path: string): void {\n this.context.path = path;\n }\n\n trackEvent(event: string): void {\n const next = new Set(this.context.events ?? new Set<string>());\n next.add(event);\n this.context.events = next;\n }\n\n trackUsage(event: string, delta = 1): void {\n const usage = { ...(this.context.usage ?? {}) };\n usage[event] = (usage[event] ?? 0) + Math.max(1, delta);\n this.context.usage = usage;\n }\n\n trackMilestone(event: string): void {\n const next = new Set(this.context.milestones ?? new Set<string>());\n next.add(event);\n this.context.milestones = next;\n }\n\n setElapsedMs(elapsedMs: number): void {\n this.context.elapsedMs = Math.max(0, elapsedMs);\n }\n\n setScrollPercent(scrollPercent: number): void {\n const clamped = Math.max(0, Math.min(100, scrollPercent));\n this.context.scrollPercent = clamped;\n }\n\n setMetadata(next: Record<string, unknown>): void {\n this.context.metadata = { ...next };\n }\n\n getContext(): TriggerContext {\n return {\n path: this.context.path,\n events: new Set(this.context.events ?? []),\n milestones: new Set(this.context.milestones ?? []),\n usage: { ...(this.context.usage ?? {}) },\n elapsedMs: this.context.elapsedMs,\n scrollPercent: this.context.scrollPercent,\n metadata: { ...(this.context.metadata ?? {}) },\n };\n }\n\n evaluate(trigger: FeatureTrigger | undefined): boolean {\n return isTriggerMatch(trigger, this.context);\n }\n\n evaluateFeature(feature: Pick<FeatureEntry, \"trigger\">): boolean {\n return this.evaluate(feature.trigger);\n }\n}\n","import type {\n AudienceMatchFn,\n AudienceRule,\n FeatureEntry,\n FeatureManifest,\n StorageAdapter,\n UserContext,\n FeatureDependencyState,\n FeatureFlagBridge,\n TriggerContext,\n} from \"./types\";\nimport { compareSemver, satisfiesRange } from \"./semver\";\nimport { isTriggerMatch } from \"./triggers\";\n\n/**\n * Default audience matching logic.\n *\n * For each specified field (plan, role, region), checks if the user's\n * value is included in the allowed list. Fields use AND logic between them,\n * OR logic within each field's array. The `custom` field is ignored by\n * the default matcher — use a custom `AudienceMatchFn` for that.\n */\nexport function matchesAudience(\n audience: AudienceRule,\n userContext: UserContext,\n): boolean {\n if (audience.plan && audience.plan.length > 0) {\n if (!userContext.plan || !audience.plan.includes(userContext.plan)) {\n return false;\n }\n }\n if (audience.role && audience.role.length > 0) {\n if (!userContext.role || !audience.role.includes(userContext.role)) {\n return false;\n }\n }\n if (audience.region && audience.region.length > 0) {\n if (!userContext.region || !audience.region.includes(userContext.region)) {\n return false;\n }\n }\n return true;\n}\n\n/**\n * Check if a feature's audience allows the given user context.\n *\n * - No `audience` field → visible to all\n * - Empty `audience` ({}) → visible to all\n * - `audience` specified but no `userContext` → hidden (safe default)\n * - Otherwise, delegate to `matchFn` (or default `matchesAudience`)\n */\nfunction isAudienceMatch(\n feature: FeatureEntry,\n userContext?: UserContext,\n matchFn?: AudienceMatchFn,\n): boolean {\n // No audience restriction → show to everyone\n if (!feature.audience) return true;\n\n // Check if audience is empty (no fields with values)\n const { plan, role, region, custom } = feature.audience;\n const hasRules =\n (plan && plan.length > 0) ||\n (role && role.length > 0) ||\n (region && region.length > 0) ||\n (custom && Object.keys(custom).length > 0);\n if (!hasRules) return true;\n\n // Audience specified but no user context → hidden (safe default)\n if (!userContext) return false;\n\n // Use custom matcher if provided, otherwise default\n if (matchFn) return matchFn(feature.audience, userContext);\n return matchesAudience(feature.audience, userContext);\n}\n\nfunction isVersionMatch(feature: FeatureEntry, appVersion?: string): boolean {\n const v = feature.version;\n if (!v || typeof v === \"string\") return true; // string = display only\n if (!appVersion) return false; // Safe default when constraints exist\n if (!v.introduced && !v.showNewUntil && !v.deprecatedAt && !v.showIn) return true;\n\n // Range check\n if (v.showIn && !satisfiesRange(appVersion, v.showIn)) return false;\n\n if (v.introduced && compareSemver(appVersion, v.introduced) < 0) return false;\n if (v.deprecatedAt && compareSemver(appVersion, v.deprecatedAt) >= 0) return false;\n\n // showNewUntil gates \"new\" state only\n if (v.showNewUntil && compareSemver(appVersion, v.showNewUntil) >= 0) return false;\n\n return true;\n}\n\nfunction isFlagMatch(\n feature: FeatureEntry,\n flagBridge?: FeatureFlagBridge,\n userContext?: UserContext,\n): boolean {\n if (!feature.flagKey) return true;\n if (!flagBridge) return false;\n try {\n return flagBridge.isEnabled(feature.flagKey, userContext);\n } catch {\n return false;\n }\n}\n\nfunction isProductMatch(feature: FeatureEntry, product?: string): boolean {\n if (!feature.product || feature.product === \"*\") return true;\n if (!product) return false;\n return feature.product === product;\n}\n\nfunction isDependencyMatch(\n feature: FeatureEntry,\n dismissedIds: ReadonlySet<string>,\n dependencyState?: FeatureDependencyState,\n): boolean {\n const dependsOn = feature.dependsOn;\n if (!dependsOn) return true;\n\n const seenIds = dependencyState?.seenIds;\n const clickedIds = dependencyState?.clickedIds;\n const dismissedDependencyIds = dependencyState?.dismissedIds ?? dismissedIds;\n\n if (dependsOn.seen && dependsOn.seen.length > 0) {\n for (const id of dependsOn.seen) {\n const seen = seenIds?.has(id) ?? false;\n if (!seen && !dismissedDependencyIds.has(id)) return false;\n }\n }\n\n if (dependsOn.clicked && dependsOn.clicked.length > 0) {\n for (const id of dependsOn.clicked) {\n if (!(clickedIds?.has(id) ?? false)) return false;\n }\n }\n\n if (dependsOn.dismissed && dependsOn.dismissed.length > 0) {\n for (const id of dependsOn.dismissed) {\n if (!dismissedDependencyIds.has(id)) return false;\n }\n }\n\n return true;\n}\n\n/**\n * Check if a single feature should show as \"new\".\n *\n * A feature is \"new\" when ALL of these are true:\n * 1. Current time is before `showNewUntil`\n * 2. Feature was released after the watermark (or no watermark exists)\n * 3. Feature has not been individually dismissed\n * 4. If `publishAt` is set, current time must be after it (scheduled publishing)\n * 5. If `audience` is set, user must match the targeting rules\n * 6. If `flagKey` is set, the flag bridge must resolve it as enabled\n * 7. If `product` is set, it must match the current product scope\n */\nexport function isNew(\n feature: FeatureEntry,\n watermark: string | null,\n dismissedIds: ReadonlySet<string>,\n now: Date = new Date(),\n userContext?: UserContext,\n matchAudience?: AudienceMatchFn,\n appVersion?: string,\n dependencyState?: FeatureDependencyState,\n triggerContext?: TriggerContext,\n flagBridge?: FeatureFlagBridge,\n product?: string,\n): boolean {\n // Already dismissed by the user on this device\n if (dismissedIds.has(feature.id)) return false;\n\n // Audience targeting — check before time-based checks\n if (!isAudienceMatch(feature, userContext, matchAudience)) return false;\n\n // Dependency targeting — defer features until prerequisites are satisfied\n if (!isDependencyMatch(feature, dismissedIds, dependencyState)) return false;\n\n // Version targeting — requires appVersion when constraints exist\n if (!isVersionMatch(feature, appVersion)) return false;\n\n // Feature flag targeting — hide flagged entries unless enabled\n if (!isFlagMatch(feature, flagBridge, userContext)) return false;\n\n // Multi-product targeting — hide entries for other product scopes\n if (!isProductMatch(feature, product)) return false;\n\n // Contextual trigger rules — show only when trigger condition is satisfied.\n if (!isTriggerMatch(feature.trigger, triggerContext)) return false;\n\n const nowMs = now.getTime();\n\n // Scheduled publishing — hidden until publishAt\n if (feature.publishAt) {\n const publishMs = new Date(feature.publishAt).getTime();\n if (nowMs < publishMs) return false;\n }\n\n const showUntilMs = new Date(feature.showNewUntil).getTime();\n\n // Past the display window\n if (nowMs >= showUntilMs) return false;\n\n // If there's a watermark, feature must have been released after it\n if (watermark) {\n const watermarkMs = new Date(watermark).getTime();\n const releasedMs = new Date(feature.releasedAt).getTime();\n if (releasedMs <= watermarkMs) return false;\n }\n\n return true;\n}\n\n/**\n * Get all features that are currently \"new\" for this user.\n */\nexport function getNewFeatures(\n manifest: FeatureManifest,\n storage: StorageAdapter,\n now: Date = new Date(),\n userContext?: UserContext,\n matchAudience?: AudienceMatchFn,\n appVersion?: string,\n dependencyState?: FeatureDependencyState,\n triggerContext?: TriggerContext,\n flagBridge?: FeatureFlagBridge,\n product?: string,\n): FeatureEntry[] {\n const watermark = storage.getWatermark();\n const dismissedIds = storage.getDismissedIds();\n return manifest.filter((f) =>\n isNew(\n f,\n watermark,\n dismissedIds,\n now,\n userContext,\n matchAudience,\n appVersion,\n dependencyState,\n triggerContext,\n flagBridge,\n product,\n ),\n );\n}\n\n/**\n * Get the count of new features.\n */\nexport function getNewFeatureCount(\n manifest: FeatureManifest,\n storage: StorageAdapter,\n now: Date = new Date(),\n userContext?: UserContext,\n matchAudience?: AudienceMatchFn,\n appVersion?: string,\n dependencyState?: FeatureDependencyState,\n triggerContext?: TriggerContext,\n flagBridge?: FeatureFlagBridge,\n product?: string,\n): number {\n return getNewFeatures(\n manifest,\n storage,\n now,\n userContext,\n matchAudience,\n appVersion,\n dependencyState,\n triggerContext,\n flagBridge,\n product,\n ).length;\n}\n\n/**\n * Check if a specific sidebar key has a new feature.\n */\nexport function hasNewFeature(\n manifest: FeatureManifest,\n sidebarKey: string,\n storage: StorageAdapter,\n now: Date = new Date(),\n userContext?: UserContext,\n matchAudience?: AudienceMatchFn,\n appVersion?: string,\n dependencyState?: FeatureDependencyState,\n triggerContext?: TriggerContext,\n flagBridge?: FeatureFlagBridge,\n product?: string,\n): boolean {\n const watermark = storage.getWatermark();\n const dismissedIds = storage.getDismissedIds();\n return manifest.some(\n (f) =>\n f.sidebarKey === sidebarKey &&\n isNew(\n f,\n watermark,\n dismissedIds,\n now,\n userContext,\n matchAudience,\n appVersion,\n dependencyState,\n triggerContext,\n flagBridge,\n product,\n ),\n );\n}\n\n/**\n * Get all features sorted by priority (critical first) then by release date (newest first).\n */\nexport function getNewFeaturesSorted(\n manifest: FeatureManifest,\n storage: StorageAdapter,\n now: Date = new Date(),\n userContext?: UserContext,\n matchAudience?: AudienceMatchFn,\n appVersion?: string,\n dependencyState?: FeatureDependencyState,\n triggerContext?: TriggerContext,\n flagBridge?: FeatureFlagBridge,\n product?: string,\n): FeatureEntry[] {\n const priorityOrder = { critical: 0, normal: 1, low: 2 };\n return getNewFeatures(\n manifest,\n storage,\n now,\n userContext,\n matchAudience,\n appVersion,\n dependencyState,\n triggerContext,\n flagBridge,\n product,\n ).sort(\n (a, b) => {\n const pa = priorityOrder[a.priority ?? \"normal\"];\n const pb = priorityOrder[b.priority ?? \"normal\"];\n if (pa !== pb) return pa - pb;\n return (\n new Date(b.releasedAt).getTime() - new Date(a.releasedAt).getTime()\n );\n },\n );\n}\n","import type { StorageAdapter } from \"../types\";\n\n/**\n * In-memory storage adapter.\n *\n * Useful for:\n * - Testing (no side effects)\n * - Server-side rendering (no `window`/`localStorage`)\n * - Environments without persistent storage\n */\nexport class MemoryAdapter implements StorageAdapter {\n private watermark: string | null;\n private dismissed: Set<string>;\n\n constructor(options: { watermark?: string | null } = {}) {\n this.watermark = options.watermark ?? null;\n this.dismissed = new Set();\n }\n\n getWatermark(): string | null {\n return this.watermark;\n }\n\n getDismissedIds(): ReadonlySet<string> {\n return this.dismissed;\n }\n\n dismiss(id: string): void {\n this.dismissed.add(id);\n }\n\n async dismissAll(now: Date): Promise<void> {\n this.watermark = now.toISOString();\n this.dismissed.clear();\n }\n}\n","import { getNewFeatures, getNewFeatureCount } from \"../core\";\nimport { MemoryAdapter } from \"../adapters/memory\";\nimport type {\n FeatureEntry,\n FeatureManifest,\n UserContext,\n AudienceMatchFn,\n FeatureDependencyState,\n TriggerContext,\n FeatureFlagBridge,\n} from \"../types\";\n\nexport type { FeatureEntry, FeatureManifest };\n\n/** Options shared by both server helpers */\nexport interface ServerOptions {\n /** Current date override (defaults to `new Date()`) */\n now?: Date;\n /** User context for audience targeting */\n userContext?: UserContext;\n /** Custom audience matcher */\n matchAudience?: AudienceMatchFn;\n /** Current app semver string for version targeting */\n appVersion?: string;\n /** Dependency state for progressive disclosure */\n dependencyState?: FeatureDependencyState;\n /** Trigger context for contextual rules */\n triggerContext?: TriggerContext;\n /** Feature flag bridge for flag-gated entries */\n flagBridge?: FeatureFlagBridge;\n /** Product scope for multi-product manifests */\n product?: string;\n}\n\n/**\n * Server-side helper: get new features without browser storage.\n *\n * Creates a temporary MemoryAdapter pre-seeded with the provided dismissed IDs\n * and calls the core `getNewFeatures` function. Safe to use in Astro page\n * frontmatter, API routes, or any server context.\n *\n * @param manifest The feature manifest array.\n * @param dismissedIds IDs already dismissed by this user (from your session/DB).\n * @param options Optional targeting overrides.\n */\nexport function getNewFeaturesServer(\n manifest: FeatureManifest,\n dismissedIds?: string[],\n options?: ServerOptions,\n): FeatureEntry[] {\n const storage = new MemoryAdapter();\n if (dismissedIds) {\n for (const id of dismissedIds) {\n storage.dismiss(id);\n }\n }\n return getNewFeatures(\n manifest,\n storage,\n options?.now,\n options?.userContext,\n options?.matchAudience,\n options?.appVersion,\n options?.dependencyState,\n options?.triggerContext,\n options?.flagBridge,\n options?.product,\n );\n}\n\n/**\n * Server-side helper: get new feature count.\n *\n * Same as `getNewFeaturesServer` but returns the count only. Useful for\n * rendering badge numbers in Astro page frontmatter.\n *\n * @param manifest The feature manifest array.\n * @param dismissedIds IDs already dismissed by this user (from your session/DB).\n * @param options Optional targeting overrides.\n */\nexport function getNewCountServer(\n manifest: FeatureManifest,\n dismissedIds?: string[],\n options?: ServerOptions,\n): number {\n const storage = new MemoryAdapter();\n if (dismissedIds) {\n for (const id of dismissedIds) {\n storage.dismiss(id);\n }\n }\n return getNewFeatureCount(\n manifest,\n storage,\n options?.now,\n options?.userContext,\n options?.matchAudience,\n options?.appVersion,\n options?.dependencyState,\n options?.triggerContext,\n options?.flagBridge,\n options?.product,\n );\n}\n\n/**\n * Returns a raw HTML `<script>` tag string carrying manifest + dismissed IDs.\n *\n * Inject into your Astro layout via `set:html` so that client-side islands can\n * read the pre-computed data on first render without a flash-of-no-content.\n *\n * Usage in an Astro component:\n * ```astro\n * ---\n * import { getManifestScript } from \"featuredrop/astro\";\n * const script = getManifestScript(manifest, dismissedIds);\n * ---\n * <Fragment set:html={script} />\n * ```\n *\n * Client-side retrieval:\n * ```ts\n * const el = document.getElementById(\"__FEATUREDROP_DATA__\");\n * const { manifest, dismissedIds } = JSON.parse(el?.textContent ?? \"{}\");\n * ```\n *\n * @param manifest The feature manifest array.\n * @param dismissedIds IDs already dismissed by this user (from your session/DB).\n */\nexport function getManifestScript(\n manifest: FeatureEntry[],\n dismissedIds?: string[],\n): string {\n const data = JSON.stringify({ manifest, dismissedIds: dismissedIds ?? [] });\n // Escape </script> in JSON to prevent XSS / premature tag close\n const safe = data.replace(/<\\/script/gi, \"<\\\\/script\");\n return `<script id=\"__FEATUREDROP_DATA__\" type=\"application/json\">${safe}</script>`;\n}\n"]}
|