featuredrop 2.7.2 → 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 +32 -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/dist/featuredrop.cjs
CHANGED
|
@@ -8,6 +8,7 @@ var os = require('os');
|
|
|
8
8
|
var zod = require('zod');
|
|
9
9
|
var posthogNode = require('posthog-node');
|
|
10
10
|
var moduleApi = require('module');
|
|
11
|
+
var fs = require('fs');
|
|
11
12
|
|
|
12
13
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
13
14
|
function _interopNamespace(e) {
|
|
@@ -1234,6 +1235,184 @@ function generateRSS(manifest, options) {
|
|
|
1234
1235
|
"</rss>"
|
|
1235
1236
|
].join("");
|
|
1236
1237
|
}
|
|
1238
|
+
var CLAUDE_SKILL = `# FeatureDrop \u2014 Setup & Configuration Skill
|
|
1239
|
+
|
|
1240
|
+
## What it is
|
|
1241
|
+
Open-source product adoption toolkit. Changelogs, badges, tours, checklists, feedback.
|
|
1242
|
+
Zero dependencies. < 3 kB core. MIT licensed.
|
|
1243
|
+
|
|
1244
|
+
## Quick Setup
|
|
1245
|
+
1. \`npm install featuredrop\`
|
|
1246
|
+
2. Create features.json manifest
|
|
1247
|
+
3. Wrap root in \`<FeatureDropProvider manifest={features} storage={new LocalStorageAdapter()}>\`
|
|
1248
|
+
4. Drop components or use hooks
|
|
1249
|
+
|
|
1250
|
+
## Imports (ALWAYS use subpath imports)
|
|
1251
|
+
\`\`\`
|
|
1252
|
+
featuredrop \u2014 core functions (isNew, getNewFeatures, createManifest)
|
|
1253
|
+
featuredrop/react \u2014 components + hooks (NewBadge, ChangelogWidget, Tour, Checklist)
|
|
1254
|
+
featuredrop/react/hooks \u2014 headless hooks only (useChangelog, useTour, useChecklist, useNewFeature)
|
|
1255
|
+
featuredrop/adapters \u2014 storage (PostgresAdapter, RedisAdapter, IndexedDBAdapter, etc.)
|
|
1256
|
+
featuredrop/engine \u2014 AdoptionEngine (smart timing, format selection, adoption scoring)
|
|
1257
|
+
featuredrop/schema \u2014 Zod validation (featureEntrySchema, validateManifest)
|
|
1258
|
+
featuredrop/testing \u2014 test helpers (createMockManifest, createMockStorage, createTestProvider)
|
|
1259
|
+
featuredrop/tailwind \u2014 Tailwind plugin (featureDropPlugin)
|
|
1260
|
+
\`\`\`
|
|
1261
|
+
|
|
1262
|
+
## Hooks Reference (prefer these for custom UI)
|
|
1263
|
+
\`\`\`
|
|
1264
|
+
useNewFeature(sidebarKey) \u2192 { isNew, feature, dismiss }
|
|
1265
|
+
useNewCount() \u2192 number
|
|
1266
|
+
useChangelog() \u2192 { features, newFeatures, newCount, dismiss, dismissAll, markAllSeen, getByCategory }
|
|
1267
|
+
useTour(id) \u2192 { currentStep, stepIndex, totalSteps, isActive, start, next, prev, skip, complete }
|
|
1268
|
+
useChecklist(id) \u2192 { tasks, progress, isComplete, completeTask, resetChecklist }
|
|
1269
|
+
useSurvey(id) \u2192 { isOpen, show, hide, askLater, submitted, canShow }
|
|
1270
|
+
useSmartFeature(id) \u2192 { show, format, feature, dismiss, confidence, reason }
|
|
1271
|
+
useAdoptionScore() \u2192 { score, grade, breakdown, recommendations }
|
|
1272
|
+
useFeatureDrop() \u2192 full provider context (low-level)
|
|
1273
|
+
\`\`\`
|
|
1274
|
+
|
|
1275
|
+
## Components (ready-made UI with headless render prop mode)
|
|
1276
|
+
NewBadge, ChangelogWidget, ChangelogPage, Tour, Checklist, Spotlight, SpotlightChain,
|
|
1277
|
+
Hotspot, TooltipGroup, Banner, Toast, AnnouncementModal, Survey, FeedbackWidget,
|
|
1278
|
+
FeatureRequestButton, FeatureRequestForm, SmartAnnouncement
|
|
1279
|
+
|
|
1280
|
+
## Manifest Format
|
|
1281
|
+
\`\`\`json
|
|
1282
|
+
{
|
|
1283
|
+
"id": "dark-mode",
|
|
1284
|
+
"label": "Dark Mode",
|
|
1285
|
+
"description": "Toggle between light and dark themes.",
|
|
1286
|
+
"releasedAt": "2026-02-20",
|
|
1287
|
+
"showNewUntil": "2026-04-20",
|
|
1288
|
+
"category": "ui",
|
|
1289
|
+
"priority": "normal",
|
|
1290
|
+
"type": "feature"
|
|
1291
|
+
}
|
|
1292
|
+
\`\`\`
|
|
1293
|
+
|
|
1294
|
+
## Storage Adapters
|
|
1295
|
+
- LocalStorageAdapter (browser default, zero-config)
|
|
1296
|
+
- MemoryAdapter (testing, SSR)
|
|
1297
|
+
- IndexedDBAdapter (offline-first PWAs)
|
|
1298
|
+
- PostgresAdapter, RedisAdapter, MySQLAdapter, MongoAdapter, SQLiteAdapter (server)
|
|
1299
|
+
- SupabaseAdapter (Supabase with optional realtime)
|
|
1300
|
+
- RemoteAdapter (HTTP API with retry + circuit breaker)
|
|
1301
|
+
- HybridAdapter (local + remote with batched sync)
|
|
1302
|
+
|
|
1303
|
+
## Rules
|
|
1304
|
+
- Always use subpath imports (never bare 'featuredrop' for React code)
|
|
1305
|
+
- Prefer hooks over components when user has a custom design system
|
|
1306
|
+
- Features auto-expire via showNewUntil \u2014 don't build manual expiry logic
|
|
1307
|
+
- Zero production dependencies must be maintained
|
|
1308
|
+
- TypeScript strict \u2014 no any types
|
|
1309
|
+
`;
|
|
1310
|
+
var CURSORRULES = `When working with FeatureDrop:
|
|
1311
|
+
- FeatureDrop is a product adoption library (changelogs, badges, tours, checklists, feedback)
|
|
1312
|
+
- ALWAYS import from subpaths: 'featuredrop/react', 'featuredrop/react/hooks', 'featuredrop/adapters'
|
|
1313
|
+
- For custom UI: use hooks from 'featuredrop/react/hooks' (useChangelog, useTour, useChecklist, useSmartFeature)
|
|
1314
|
+
- For ready-made UI: use components from 'featuredrop/react' (NewBadge, ChangelogWidget, Tour, SmartAnnouncement)
|
|
1315
|
+
- Features are defined in JSON: { id, label, description, releasedAt, showNewUntil? }
|
|
1316
|
+
- Wrap app in <FeatureDropProvider manifest={features} storage={new LocalStorageAdapter()}>
|
|
1317
|
+
- Storage default: LocalStorageAdapter. Server: PostgresAdapter, RedisAdapter from 'featuredrop/adapters'
|
|
1318
|
+
- Engine: import { createAdoptionEngine } from 'featuredrop/engine' for smart timing + format selection
|
|
1319
|
+
- Core < 3 kB, React ~12 kB, zero production deps, fully tree-shakable
|
|
1320
|
+
- All components support headless mode via render props
|
|
1321
|
+
- For shadcn projects: use hooks from 'featuredrop/react/hooks' + shadcn primitives
|
|
1322
|
+
- TypeScript strict mode, no 'any' types
|
|
1323
|
+
- Zero production dependencies \u2014 do not add external deps
|
|
1324
|
+
`;
|
|
1325
|
+
var MCP_CONFIG = {
|
|
1326
|
+
featuredrop: {
|
|
1327
|
+
command: "npx",
|
|
1328
|
+
args: ["@featuredrop/mcp"]
|
|
1329
|
+
}
|
|
1330
|
+
};
|
|
1331
|
+
async function runAiSetup(cwd) {
|
|
1332
|
+
const root = cwd ?? process.cwd();
|
|
1333
|
+
const result = { detected: [], created: [], skipped: [] };
|
|
1334
|
+
const claudeDir = path.join(root, ".claude");
|
|
1335
|
+
const claudeSkillsDir = path.join(claudeDir, "skills");
|
|
1336
|
+
const claudeSkillPath = path.join(claudeSkillsDir, "featuredrop.md");
|
|
1337
|
+
if (fs.existsSync(claudeDir) || fs.existsSync(path.join(root, "CLAUDE.md"))) {
|
|
1338
|
+
result.detected.push("Claude Code");
|
|
1339
|
+
if (fs.existsSync(claudeSkillPath)) {
|
|
1340
|
+
result.skipped.push(claudeSkillPath);
|
|
1341
|
+
} else {
|
|
1342
|
+
await promises.mkdir(claudeSkillsDir, { recursive: true });
|
|
1343
|
+
await promises.writeFile(claudeSkillPath, CLAUDE_SKILL, "utf8");
|
|
1344
|
+
result.created.push(claudeSkillPath);
|
|
1345
|
+
}
|
|
1346
|
+
const claudeSettingsDir = claudeDir;
|
|
1347
|
+
const claudeMcpPath = path.join(claudeSettingsDir, "settings.json");
|
|
1348
|
+
if (!fs.existsSync(claudeMcpPath)) {
|
|
1349
|
+
const settings = { mcpServers: MCP_CONFIG };
|
|
1350
|
+
await promises.writeFile(claudeMcpPath, JSON.stringify(settings, null, 2) + "\n", "utf8");
|
|
1351
|
+
result.created.push(claudeMcpPath);
|
|
1352
|
+
} else {
|
|
1353
|
+
try {
|
|
1354
|
+
const existing = JSON.parse(await promises.readFile(claudeMcpPath, "utf8"));
|
|
1355
|
+
const mcpServers = existing.mcpServers ?? {};
|
|
1356
|
+
if (!mcpServers.featuredrop) {
|
|
1357
|
+
mcpServers.featuredrop = MCP_CONFIG.featuredrop;
|
|
1358
|
+
existing.mcpServers = mcpServers;
|
|
1359
|
+
await promises.writeFile(claudeMcpPath, JSON.stringify(existing, null, 2) + "\n", "utf8");
|
|
1360
|
+
result.created.push(`${claudeMcpPath} (merged)`);
|
|
1361
|
+
} else {
|
|
1362
|
+
result.skipped.push(claudeMcpPath);
|
|
1363
|
+
}
|
|
1364
|
+
} catch {
|
|
1365
|
+
result.skipped.push(`${claudeMcpPath} (parse error)`);
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
const cursorDir = path.join(root, ".cursor");
|
|
1370
|
+
const cursorRulesPath = path.join(root, ".cursorrules");
|
|
1371
|
+
const cursorMcpPath = path.join(cursorDir, "mcp.json");
|
|
1372
|
+
if (fs.existsSync(cursorDir) || fs.existsSync(cursorRulesPath)) {
|
|
1373
|
+
result.detected.push("Cursor");
|
|
1374
|
+
if (fs.existsSync(cursorRulesPath)) {
|
|
1375
|
+
const content = await promises.readFile(cursorRulesPath, "utf8");
|
|
1376
|
+
if (content.includes("FeatureDrop")) {
|
|
1377
|
+
result.skipped.push(cursorRulesPath);
|
|
1378
|
+
} else {
|
|
1379
|
+
await promises.writeFile(cursorRulesPath, content + "\n\n" + CURSORRULES, "utf8");
|
|
1380
|
+
result.created.push(`${cursorRulesPath} (appended)`);
|
|
1381
|
+
}
|
|
1382
|
+
} else {
|
|
1383
|
+
await promises.writeFile(cursorRulesPath, CURSORRULES, "utf8");
|
|
1384
|
+
result.created.push(cursorRulesPath);
|
|
1385
|
+
}
|
|
1386
|
+
if (fs.existsSync(cursorDir)) {
|
|
1387
|
+
if (!fs.existsSync(cursorMcpPath)) {
|
|
1388
|
+
const config = { servers: MCP_CONFIG };
|
|
1389
|
+
await promises.mkdir(cursorDir, { recursive: true });
|
|
1390
|
+
await promises.writeFile(cursorMcpPath, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
1391
|
+
result.created.push(cursorMcpPath);
|
|
1392
|
+
} else {
|
|
1393
|
+
result.skipped.push(cursorMcpPath);
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
const vscodeDir = path.join(root, ".vscode");
|
|
1398
|
+
const vscodeMcpPath = path.join(vscodeDir, "mcp.json");
|
|
1399
|
+
if (fs.existsSync(vscodeDir)) {
|
|
1400
|
+
result.detected.push("VS Code");
|
|
1401
|
+
if (!fs.existsSync(vscodeMcpPath)) {
|
|
1402
|
+
const config = { servers: MCP_CONFIG };
|
|
1403
|
+
await promises.writeFile(vscodeMcpPath, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
1404
|
+
result.created.push(vscodeMcpPath);
|
|
1405
|
+
} else {
|
|
1406
|
+
result.skipped.push(vscodeMcpPath);
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
if (result.detected.length === 0) {
|
|
1410
|
+
result.detected.push("(none detected \u2014 creating .cursorrules as default)");
|
|
1411
|
+
await promises.writeFile(cursorRulesPath, CURSORRULES, "utf8");
|
|
1412
|
+
result.created.push(cursorRulesPath);
|
|
1413
|
+
}
|
|
1414
|
+
return result;
|
|
1415
|
+
}
|
|
1237
1416
|
|
|
1238
1417
|
// src/cli.ts
|
|
1239
1418
|
function parseArgs(argv) {
|
|
@@ -1247,7 +1426,8 @@ function parseArgs(argv) {
|
|
|
1247
1426
|
"stats",
|
|
1248
1427
|
"doctor",
|
|
1249
1428
|
"generate-rss",
|
|
1250
|
-
"generate-changelog"
|
|
1429
|
+
"generate-changelog",
|
|
1430
|
+
"ai-setup"
|
|
1251
1431
|
]);
|
|
1252
1432
|
const command = allowed.has(commandRaw) ? commandRaw : "help";
|
|
1253
1433
|
const parsed = { command };
|
|
@@ -1287,6 +1467,7 @@ function printHelp() {
|
|
|
1287
1467
|
console.log(" featuredrop doctor [--pattern features/**/*.md] [--cwd .]");
|
|
1288
1468
|
console.log(" featuredrop generate-rss [--pattern features/**/*.md] [--out featuredrop.rss.xml] [--title ...] [--link ...] [--description ...] [--cwd .]");
|
|
1289
1469
|
console.log(" featuredrop generate-changelog [--pattern features/**/*.md] [--out CHANGELOG.generated.md] [--cwd .]");
|
|
1470
|
+
console.log(" featuredrop ai-setup [--cwd .] Detect AI tools and create context files");
|
|
1290
1471
|
}
|
|
1291
1472
|
async function promptForLabelIfNeeded(label) {
|
|
1292
1473
|
if (label?.trim()) return label.trim();
|
|
@@ -1333,6 +1514,32 @@ async function run() {
|
|
|
1333
1514
|
await shutdownPostHog();
|
|
1334
1515
|
return;
|
|
1335
1516
|
}
|
|
1517
|
+
if (args.command === "ai-setup") {
|
|
1518
|
+
const result = await runAiSetup(args.cwd);
|
|
1519
|
+
if (result.detected.length > 0) {
|
|
1520
|
+
console.log(`Detected: ${result.detected.join(", ")}`);
|
|
1521
|
+
}
|
|
1522
|
+
for (const path of result.created) {
|
|
1523
|
+
console.log(` Created: ${path}`);
|
|
1524
|
+
}
|
|
1525
|
+
for (const path of result.skipped) {
|
|
1526
|
+
console.log(` Skipped: ${path} (already exists)`);
|
|
1527
|
+
}
|
|
1528
|
+
if (result.created.length === 0 && result.skipped.length > 0) {
|
|
1529
|
+
console.log("All AI context files already up to date.");
|
|
1530
|
+
}
|
|
1531
|
+
posthog?.capture({
|
|
1532
|
+
distinctId,
|
|
1533
|
+
event: "cli_ai_setup",
|
|
1534
|
+
properties: {
|
|
1535
|
+
detected: result.detected,
|
|
1536
|
+
files_created: result.created.length,
|
|
1537
|
+
files_skipped: result.skipped.length
|
|
1538
|
+
}
|
|
1539
|
+
});
|
|
1540
|
+
await shutdownPostHog();
|
|
1541
|
+
return;
|
|
1542
|
+
}
|
|
1336
1543
|
if (args.command === "add") {
|
|
1337
1544
|
const label = await promptForLabelIfNeeded(args.label);
|
|
1338
1545
|
const result = await addFeatureEntry({
|