flaglint 0.7.0 → 0.9.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/CHANGELOG.md +28 -2
- package/README.md +13 -11
- package/dist/bin/flaglint.js +43 -23
- package/package.json +15 -4
package/CHANGELOG.md
CHANGED
|
@@ -5,15 +5,41 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.8.0] - 2026-06-13
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- **`--cost-estimate` renamed to `--effort-estimate`** (breaking) — the flag outputs
|
|
13
|
+
hours, not dollars; "effort" is more accurate. Update any scripts or CI pipelines
|
|
14
|
+
that use `--cost-estimate`.
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- Homepage email-capture / Loops signup section removed.
|
|
19
|
+
- Version displayed on homepage now read from `package.json` at build time — can no
|
|
20
|
+
longer drift from the published npm version.
|
|
21
|
+
- `robots.txt` sitemap URL updated to `/sitemap-index.xml` (was `/sitemap.xml`, which 404'd).
|
|
22
|
+
- Audit sample numbers corrected to real fixture output: 13 unique flags across 19 call
|
|
23
|
+
sites, readiness 53/100, estimate 20.8h–40h.
|
|
24
|
+
|
|
25
|
+
### Infrastructure
|
|
26
|
+
|
|
27
|
+
- Vitest `testTimeout`/`hookTimeout` raised to 30 s to fix flaky Windows/Node 22 CI timeouts.
|
|
28
|
+
- `npm publish --provenance` re-enabled in release workflow.
|
|
29
|
+
- `scripts/metrics/collect.mjs` added: fetches npm downloads + GitHub stars/forks/issues
|
|
30
|
+
and appends a JSON line to `.agent-output/metrics/history.jsonl`.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
8
34
|
## [0.7.0] - 2026-06-07
|
|
9
35
|
|
|
10
36
|
### Added
|
|
11
37
|
|
|
12
|
-
- `flaglint audit --
|
|
38
|
+
- `flaglint audit --effort-estimate` — adds a directional migration-effort estimate to
|
|
13
39
|
audit output. Computes low/high hour ranges from automatable and manual-review call
|
|
14
40
|
counts using configurable planning heuristics.
|
|
15
41
|
- `flaglint audit --hourly-rate <rate>` — adds engineering cost projection
|
|
16
|
-
(`costLow`/`costHigh`) to the estimate. Requires `--
|
|
42
|
+
(`costLow`/`costHigh`) to the estimate. Requires `--effort-estimate`.
|
|
17
43
|
- Migration readiness score displayed in `flaglint audit` terminal output: 0–100 ratio
|
|
18
44
|
of safely automatable calls to total detected LaunchDarkly calls, with grade
|
|
19
45
|
(`ready` | `moderate` | `complex` | `not-applicable`) and progress bar.
|
package/README.md
CHANGED
|
@@ -30,27 +30,27 @@ npx flaglint audit ./src
|
|
|
30
30
|
```
|
|
31
31
|
|
|
32
32
|
```
|
|
33
|
-
✓ Audit complete: 13 flags — 3 high risk, 10 medium risk
|
|
33
|
+
✓ Audit complete: 13 unique flags across 19 call sites — 3 high risk, 10 medium risk
|
|
34
34
|
|
|
35
|
-
Migration readiness:
|
|
36
|
-
[█████████████░░░░░░░░░░░░]
|
|
37
|
-
10 safely automatable ·
|
|
35
|
+
Migration readiness: 53/100 · moderate
|
|
36
|
+
[█████████████░░░░░░░░░░░░] 53%
|
|
37
|
+
10 of 19 call sites safely automatable · 9 require manual review
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
Add `--
|
|
40
|
+
Add `--effort-estimate` to include a directional planning estimate:
|
|
41
41
|
|
|
42
42
|
```bash
|
|
43
|
-
npx flaglint audit ./src --
|
|
43
|
+
npx flaglint audit ./src --effort-estimate
|
|
44
44
|
```
|
|
45
45
|
|
|
46
46
|
```
|
|
47
|
-
✓ Audit complete: 13 flags — 3 high risk, 10 medium risk
|
|
47
|
+
✓ Audit complete: 13 unique flags across 19 call sites — 3 high risk, 10 medium risk
|
|
48
48
|
|
|
49
|
-
Migration readiness:
|
|
50
|
-
[█████████████░░░░░░░░░░░░]
|
|
51
|
-
10 safely automatable ·
|
|
49
|
+
Migration readiness: 53/100 · moderate
|
|
50
|
+
[█████████████░░░░░░░░░░░░] 53%
|
|
51
|
+
10 of 19 call sites safely automatable · 9 require manual review
|
|
52
52
|
|
|
53
|
-
Estimated migration effort:
|
|
53
|
+
Estimated migration effort: 20.8h – 40h
|
|
54
54
|
Estimates are directional. See the report for assumptions.
|
|
55
55
|
```
|
|
56
56
|
|
|
@@ -58,6 +58,8 @@ For the full report see `--format html`. [Documentation](https://flaglint.dev/do
|
|
|
58
58
|
|
|
59
59
|
No API key. No source upload. LaunchDarkly stays your provider — OpenFeature becomes the evaluation API your application calls.
|
|
60
60
|
|
|
61
|
+
Migrating an existing Node.js service? Read the [complete LaunchDarkly-to-OpenFeature migration guide](https://flaglint.dev/guides/launchdarkly-to-openfeature-nodejs/).
|
|
62
|
+
|
|
61
63
|
---
|
|
62
64
|
|
|
63
65
|
## The problem FlagLint solves
|
package/dist/bin/flaglint.js
CHANGED
|
@@ -789,7 +789,7 @@ function formatHTML(result, options) {
|
|
|
789
789
|
<div class="card"><div class="card-num orange">${manualCount}</div><div class="card-label">Manual Review (${manualPct}%)</div></div>
|
|
790
790
|
<div class="card"><div class="card-num blue">${detailBulkCount}</div><div class="card-label">Detail/Bulk Calls</div></div>` : "";
|
|
791
791
|
const title = options.title ? esc(options.title) : "FlagLint Scan Report";
|
|
792
|
-
const version = true ? "0.
|
|
792
|
+
const version = true ? "0.9.0" : "0.1.0";
|
|
793
793
|
return `<!DOCTYPE html>
|
|
794
794
|
<html lang="en">
|
|
795
795
|
<head>
|
|
@@ -1322,7 +1322,7 @@ function formatMigrationReport(analysis) {
|
|
|
1322
1322
|
unsupportedUnknownCount
|
|
1323
1323
|
} = analysis;
|
|
1324
1324
|
const date = (/* @__PURE__ */ new Date()).toLocaleDateString();
|
|
1325
|
-
const version = true ? "0.
|
|
1325
|
+
const version = true ? "0.9.0" : "0.1.0";
|
|
1326
1326
|
const lines = [];
|
|
1327
1327
|
lines.push("# OpenFeature Migration Inventory");
|
|
1328
1328
|
lines.push(`Generated by FlagLint v${version} on ${date}`);
|
|
@@ -2154,9 +2154,12 @@ function buildAuditReport(scanResult, inventoryItems) {
|
|
|
2154
2154
|
const { riskLevel, riskReasons } = scoreFlag(usages, items);
|
|
2155
2155
|
const files = [...new Set(usages.map((u) => u.file))];
|
|
2156
2156
|
const callTypes = [...new Set(usages.map((u) => u.callType))];
|
|
2157
|
+
const isAutomatableTier = riskLevel === "medium" && riskReasons.length === 1 && riskReasons[0] === "safely automatable";
|
|
2158
|
+
const displayTier = isAutomatableTier ? "automatable" : riskLevel;
|
|
2157
2159
|
flags.push({
|
|
2158
2160
|
flagKey,
|
|
2159
2161
|
riskLevel,
|
|
2162
|
+
displayTier,
|
|
2160
2163
|
riskReasons,
|
|
2161
2164
|
callTypes,
|
|
2162
2165
|
fileCount: files.length,
|
|
@@ -2176,23 +2179,28 @@ function buildAuditReport(scanResult, inventoryItems) {
|
|
|
2176
2179
|
const highRisk = flags.filter((f) => f.riskLevel === "high").length;
|
|
2177
2180
|
const mediumRisk = flags.filter((f) => f.riskLevel === "medium").length;
|
|
2178
2181
|
const lowRisk = flags.filter((f) => f.riskLevel === "low").length;
|
|
2182
|
+
const automatableFlags = flags.filter((f) => f.displayTier === "automatable").length;
|
|
2183
|
+
const staleSignals = scanResult.usages.filter((u) => u.stalenessSignals.length > 0).length;
|
|
2184
|
+
const stalenessNote = staleSignals === 0 ? "No staleness signals detected. Heuristics checked: keyword match (flag key contains old/deprecated/legacy/temp/tmp/test/demo), path pattern (test/spec/mock files, deprecated/old/legacy directories), and minFileCount threshold. Git-history-based staleness (last evaluation date) requires git metadata and is not available in a pure static scan." : void 0;
|
|
2179
2185
|
const summary = {
|
|
2180
2186
|
totalFlags: flags.length,
|
|
2181
2187
|
highRisk,
|
|
2182
2188
|
mediumRisk,
|
|
2183
2189
|
lowRisk,
|
|
2190
|
+
automatableFlags,
|
|
2184
2191
|
totalUsages: scanResult.totalUsages,
|
|
2185
2192
|
dynamicKeys: scanResult.usages.filter((u) => u.isDynamic).length,
|
|
2186
2193
|
detailEvaluations: inventoryItems.filter((i) => i.manualReviewReason === "detail-method").length,
|
|
2187
2194
|
bulkCalls: inventoryItems.filter((i) => i.manualReviewReason === "bulk-inventory-call").length,
|
|
2188
2195
|
wrapperUsages: scanResult.usages.filter((u) => u.callType === "variation").length,
|
|
2189
|
-
staleSignals
|
|
2196
|
+
staleSignals,
|
|
2190
2197
|
safelyAutomatable: inventoryItems.filter((i) => i.safelyAutomatable).length,
|
|
2191
2198
|
manualReview: inventoryItems.filter((i) => !i.safelyAutomatable).length,
|
|
2192
2199
|
scannedFiles: scanResult.scannedFiles,
|
|
2193
2200
|
scanDurationMs: scanResult.scanDurationMs,
|
|
2194
2201
|
scannedAt: scanResult.scannedAt,
|
|
2195
|
-
scanRoot: scanResult.scanRoot
|
|
2202
|
+
scanRoot: scanResult.scanRoot,
|
|
2203
|
+
...stalenessNote !== void 0 ? { stalenessNote } : {}
|
|
2196
2204
|
};
|
|
2197
2205
|
const readiness = computeReadiness(scanResult.usages, inventoryItems);
|
|
2198
2206
|
return { summary, flags, readiness };
|
|
@@ -2256,6 +2264,10 @@ function formatAuditMarkdown(report, options) {
|
|
|
2256
2264
|
`| ${summary.dynamicKeys} | ${summary.detailEvaluations} | ${summary.bulkCalls} | ${summary.staleSignals} | ${summary.safelyAutomatable} | ${summary.manualReview} |`
|
|
2257
2265
|
);
|
|
2258
2266
|
lines.push("");
|
|
2267
|
+
if (summary.stalenessNote !== void 0) {
|
|
2268
|
+
lines.push(`> **Staleness:** ${summary.stalenessNote}`);
|
|
2269
|
+
lines.push("");
|
|
2270
|
+
}
|
|
2259
2271
|
lines.push("## Migration Readiness");
|
|
2260
2272
|
lines.push("");
|
|
2261
2273
|
if (readiness.grade === "not-applicable") {
|
|
@@ -2300,16 +2312,17 @@ function formatAuditMarkdown(report, options) {
|
|
|
2300
2312
|
lines.push("");
|
|
2301
2313
|
lines.push("| Flag Key | Risk | Usages | Files | Call Types | Reasons |");
|
|
2302
2314
|
lines.push("|----------|------|--------|-------|------------|---------|");
|
|
2303
|
-
const
|
|
2315
|
+
const tierLabel = {
|
|
2304
2316
|
high: "\u{1F534} High",
|
|
2305
2317
|
medium: "\u{1F7E1} Medium",
|
|
2318
|
+
automatable: "\u{1F7E2} Automatable",
|
|
2306
2319
|
low: "\u{1F7E2} Low"
|
|
2307
2320
|
};
|
|
2308
2321
|
for (const flag of flags) {
|
|
2309
2322
|
const reasons = flag.riskReasons.join(", ") || "\u2014";
|
|
2310
2323
|
const flagKey = displayFlagKey(flag);
|
|
2311
2324
|
lines.push(
|
|
2312
|
-
`| \`${flagKey}\` | ${
|
|
2325
|
+
`| \`${flagKey}\` | ${tierLabel[flag.displayTier]} | ${flag.usageCount} | ${flag.fileCount} | ${flag.callTypes.join(", ")} | ${reasons} |`
|
|
2313
2326
|
);
|
|
2314
2327
|
}
|
|
2315
2328
|
lines.push("");
|
|
@@ -2329,12 +2342,14 @@ function formatAuditMarkdown(report, options) {
|
|
|
2329
2342
|
}
|
|
2330
2343
|
function formatAuditHtml(report, options) {
|
|
2331
2344
|
const { summary, flags, readiness } = report;
|
|
2332
|
-
const version = true ? "0.
|
|
2345
|
+
const version = true ? "0.9.0" : "0.1.0";
|
|
2333
2346
|
const date = new Date(summary.scannedAt).toLocaleString();
|
|
2334
|
-
const
|
|
2335
|
-
if (
|
|
2347
|
+
const tierBadge = (tier) => {
|
|
2348
|
+
if (tier === "high")
|
|
2336
2349
|
return '<span class="badge badge-high">High</span>';
|
|
2337
|
-
if (
|
|
2350
|
+
if (tier === "automatable")
|
|
2351
|
+
return '<span class="badge badge-automatable">Automatable</span>';
|
|
2352
|
+
if (tier === "medium")
|
|
2338
2353
|
return '<span class="badge badge-medium">Medium</span>';
|
|
2339
2354
|
return '<span class="badge badge-low">Low</span>';
|
|
2340
2355
|
};
|
|
@@ -2343,7 +2358,7 @@ function formatAuditHtml(report, options) {
|
|
|
2343
2358
|
const flagKey = displayFlagKey(f);
|
|
2344
2359
|
return `<tr>
|
|
2345
2360
|
<td><code>${esc2(flagKey)}</code></td>
|
|
2346
|
-
<td>${
|
|
2361
|
+
<td>${tierBadge(f.displayTier)}</td>
|
|
2347
2362
|
<td>${f.usageCount}</td>
|
|
2348
2363
|
<td>${f.fileCount}</td>
|
|
2349
2364
|
<td>${f.callTypes.map(esc2).join(", ")}</td>
|
|
@@ -2439,11 +2454,14 @@ function formatAuditHtml(report, options) {
|
|
|
2439
2454
|
.badge-high{background:#fee2e2;color:#991b1b}
|
|
2440
2455
|
.badge-medium{background:#fef3c7;color:#92400e}
|
|
2441
2456
|
.badge-low{background:#dcfce7;color:#166534}
|
|
2457
|
+
.badge-automatable{background:#d1fae5;color:#065f46}
|
|
2442
2458
|
@media(prefers-color-scheme:dark){
|
|
2443
2459
|
.badge-high{background:#7f1d1d;color:#fca5a5}
|
|
2444
2460
|
.badge-medium{background:#78350f;color:#fcd34d}
|
|
2445
2461
|
.badge-low{background:#14532d;color:#86efac}
|
|
2462
|
+
.badge-automatable{background:#064e3b;color:#6ee7b7}
|
|
2446
2463
|
}
|
|
2464
|
+
.staleness-note{margin-top:.75rem;padding:.75rem 1rem;background:var(--surface);border:1px solid var(--border);border-radius:6px;font-size:.8125rem;color:var(--muted)}
|
|
2447
2465
|
.estimate-block{background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:1.25rem;margin-bottom:1.5rem}
|
|
2448
2466
|
.estimate-total{font-size:1.5rem;font-weight:700;margin-bottom:.75rem}
|
|
2449
2467
|
.estimate-cost{font-size:1rem;font-weight:600;margin:.75rem 0;color:#3b82f6}
|
|
@@ -2463,11 +2481,13 @@ function formatAuditHtml(report, options) {
|
|
|
2463
2481
|
<div class="cards">
|
|
2464
2482
|
<div class="card"><div class="card-num">${summary.totalFlags}</div><div class="card-label">Total Flags</div></div>
|
|
2465
2483
|
<div class="card"><div class="card-num red">${summary.highRisk}</div><div class="card-label">High Risk</div></div>
|
|
2466
|
-
|
|
2484
|
+
${summary.automatableFlags > 0 ? `<div class="card"><div class="card-num green">${summary.automatableFlags}</div><div class="card-label">Automatable</div></div>` : ""}
|
|
2485
|
+
${summary.mediumRisk - summary.automatableFlags > 0 ? `<div class="card"><div class="card-num amber">${summary.mediumRisk - summary.automatableFlags}</div><div class="card-label">Medium Risk</div></div>` : ""}
|
|
2467
2486
|
${summary.lowRisk > 0 ? `<div class="card"><div class="card-num green">${summary.lowRisk}</div><div class="card-label">Low Risk</div></div>` : ""}
|
|
2468
2487
|
<div class="card"><div class="card-num purple">${summary.safelyAutomatable}</div><div class="card-label">Safely Automatable</div></div>
|
|
2469
2488
|
<div class="card"><div class="card-num orange">${summary.manualReview}</div><div class="card-label">Manual Review</div></div>
|
|
2470
2489
|
</div>
|
|
2490
|
+
${summary.stalenessNote !== void 0 ? `<div class="staleness-note"><strong>Staleness:</strong> ${esc2(summary.stalenessNote)}</div>` : ""}
|
|
2471
2491
|
|
|
2472
2492
|
<h2>Migration Readiness</h2>
|
|
2473
2493
|
<div class="readiness-block">
|
|
@@ -2593,15 +2613,15 @@ function computeEstimate(readiness, overrides, hourlyRate) {
|
|
|
2593
2613
|
// src/commands/audit.ts
|
|
2594
2614
|
var VALID_AUDIT_FORMATS = ["json", "markdown", "html"];
|
|
2595
2615
|
function registerAuditCommand(program2) {
|
|
2596
|
-
program2.command("audit").description("Generate a flag debt audit report").argument("[dir]", "directory to audit", process.cwd()).option("-f, --format <format>", "output format: json | markdown | html", "markdown").option("-o, --output <file>", "write report to file").option("-c, --config <path>", "path to .flaglintrc config file").option("--exclude-tests", "exclude test files (*.test.*, *.spec.*, __tests__/, tests/)").option("--
|
|
2616
|
+
program2.command("audit").description("Generate a flag debt audit report").argument("[dir]", "directory to audit", process.cwd()).option("-f, --format <format>", "output format: json | markdown | html", "markdown").option("-o, --output <file>", "write report to file").option("-c, --config <path>", "path to .flaglintrc config file").option("--exclude-tests", "exclude test files (*.test.*, *.spec.*, __tests__/, tests/)").option("--effort-estimate", "include a migration effort estimate in the report. The default assumptions are configurable planning heuristics, not observed industry benchmarks.").option("--hourly-rate <rate>", "hourly rate for cost projection (requires --effort-estimate)").addHelpText(
|
|
2597
2617
|
"after",
|
|
2598
2618
|
`
|
|
2599
2619
|
Examples:
|
|
2600
2620
|
$ flaglint audit generate flag debt audit report
|
|
2601
2621
|
$ flaglint audit --format html shareable HTML report
|
|
2602
2622
|
$ flaglint audit --output audit.md save to file
|
|
2603
|
-
$ flaglint audit --
|
|
2604
|
-
$ flaglint audit --
|
|
2623
|
+
$ flaglint audit --effort-estimate include migration effort estimate
|
|
2624
|
+
$ flaglint audit --effort-estimate --hourly-rate 125 include cost projection`
|
|
2605
2625
|
).action(
|
|
2606
2626
|
async (dir, options) => {
|
|
2607
2627
|
if (!VALID_AUDIT_FORMATS.includes(options.format)) {
|
|
@@ -2615,9 +2635,9 @@ Examples:
|
|
|
2615
2635
|
}
|
|
2616
2636
|
let hourlyRate;
|
|
2617
2637
|
if (options.hourlyRate !== void 0) {
|
|
2618
|
-
if (!options.
|
|
2638
|
+
if (!options.effortEstimate) {
|
|
2619
2639
|
process.stderr.write(
|
|
2620
|
-
chalk4.yellow("warn: --hourly-rate has no effect without --
|
|
2640
|
+
chalk4.yellow("warn: --hourly-rate has no effect without --effort-estimate\n")
|
|
2621
2641
|
);
|
|
2622
2642
|
} else {
|
|
2623
2643
|
const parsed = Number(options.hourlyRate);
|
|
@@ -2710,11 +2730,11 @@ Examples:
|
|
|
2710
2730
|
`
|
|
2711
2731
|
)
|
|
2712
2732
|
);
|
|
2713
|
-
if (format === "json" && options.
|
|
2733
|
+
if (format === "json" && options.effortEstimate) {
|
|
2714
2734
|
const emptyReport = buildAuditReport(scanResult, []);
|
|
2715
2735
|
process.stdout.write(formatAuditJson(emptyReport, { estimate: null }) + "\n");
|
|
2716
2736
|
process.stderr.write(chalk4.dim("Migration estimate: N/A\n"));
|
|
2717
|
-
} else if (options.
|
|
2737
|
+
} else if (options.effortEstimate) {
|
|
2718
2738
|
process.stderr.write(chalk4.dim("Migration estimate: N/A\n"));
|
|
2719
2739
|
}
|
|
2720
2740
|
process.exit(0);
|
|
@@ -2723,7 +2743,7 @@ Examples:
|
|
|
2723
2743
|
scanResult,
|
|
2724
2744
|
scanResult.migrationInventory ?? []
|
|
2725
2745
|
);
|
|
2726
|
-
const renderOptions = options.
|
|
2746
|
+
const renderOptions = options.effortEstimate ? { estimate: computeEstimate(auditReport.readiness, void 0, hourlyRate) } : void 0;
|
|
2727
2747
|
let output;
|
|
2728
2748
|
if (format === "json") {
|
|
2729
2749
|
output = formatAuditJson(auditReport, renderOptions);
|
|
@@ -2762,7 +2782,7 @@ Examples:
|
|
|
2762
2782
|
process.stderr.write("\n");
|
|
2763
2783
|
if (readiness.grade === "not-applicable") {
|
|
2764
2784
|
process.stderr.write(chalk4.dim("Migration readiness: N/A \u2014 no direct LaunchDarkly calls detected.\n"));
|
|
2765
|
-
if (options.
|
|
2785
|
+
if (options.effortEstimate) {
|
|
2766
2786
|
process.stderr.write(chalk4.dim("Migration estimate: N/A\n"));
|
|
2767
2787
|
}
|
|
2768
2788
|
} else {
|
|
@@ -2777,7 +2797,7 @@ Examples:
|
|
|
2777
2797
|
`
|
|
2778
2798
|
)
|
|
2779
2799
|
);
|
|
2780
|
-
if (options.
|
|
2800
|
+
if (options.effortEstimate && renderOptions?.estimate) {
|
|
2781
2801
|
const est = renderOptions.estimate;
|
|
2782
2802
|
process.stderr.write("\n");
|
|
2783
2803
|
process.stderr.write(
|
|
@@ -2800,7 +2820,7 @@ Examples:
|
|
|
2800
2820
|
// src/cli.ts
|
|
2801
2821
|
function createCLI() {
|
|
2802
2822
|
const program2 = new Command();
|
|
2803
|
-
program2.name("flaglint").description("
|
|
2823
|
+
program2.name("flaglint").description("Find every direct LaunchDarkly SDK call, detect flag debt, and generate safe OpenFeature migration plans. Static analysis \u2014 no API key required.").version("0.9.0", "-v, --version", "output the current version").addHelpText(
|
|
2804
2824
|
"after",
|
|
2805
2825
|
`
|
|
2806
2826
|
Examples:
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flaglint",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.9.0",
|
|
4
|
+
"description": "Find every direct LaunchDarkly SDK call, detect flag debt, and generate safe OpenFeature migration plans. Static analysis — no API key required.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"flaglint": "dist/bin/flaglint.js"
|
|
@@ -16,11 +16,20 @@
|
|
|
16
16
|
"node": ">=20"
|
|
17
17
|
},
|
|
18
18
|
"keywords": [
|
|
19
|
-
"feature-flags",
|
|
20
19
|
"launchdarkly",
|
|
21
20
|
"openfeature",
|
|
22
|
-
"
|
|
21
|
+
"feature-flags",
|
|
22
|
+
"feature-flag-migration",
|
|
23
|
+
"launchdarkly-migration",
|
|
24
|
+
"openfeature-migration",
|
|
23
25
|
"flag-debt",
|
|
26
|
+
"static-analysis",
|
|
27
|
+
"codemod",
|
|
28
|
+
"cli",
|
|
29
|
+
"typescript",
|
|
30
|
+
"nodejs",
|
|
31
|
+
"cncf",
|
|
32
|
+
"migration-tool",
|
|
24
33
|
"developer-tools"
|
|
25
34
|
],
|
|
26
35
|
"author": "flaglint",
|
|
@@ -51,6 +60,8 @@
|
|
|
51
60
|
"preview:docs": "ASTRO_TELEMETRY_DISABLED=1 astro preview --port 4321",
|
|
52
61
|
"test:package": "tsx scripts/test-package.ts",
|
|
53
62
|
"test:smoke": "playwright test",
|
|
63
|
+
"metrics": "node scripts/metrics/collect.mjs",
|
|
64
|
+
"metrics:report": "node scripts/metrics/weekly-report.mjs",
|
|
54
65
|
"new-branch": "tsx scripts/new-branch.ts",
|
|
55
66
|
"release:patch": "tsx scripts/release.ts patch",
|
|
56
67
|
"release:minor": "tsx scripts/release.ts minor",
|