flaglint 0.2.0 → 0.2.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/CHANGELOG.md +14 -0
- package/README.md +1 -0
- package/dist/bin/flaglint.js +22 -6
- package/package.json +1 -5
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,20 @@ 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.2.1] - 2026-05-23
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **Parse failure on generic TypeScript arrow functions** — `flagPredicate = <T>(...)` and similar generic arrows in `.ts` files now parse correctly. Root cause: `@typescript-eslint/typescript-estree` wasn't receiving a `filePath`, so it couldn't apply TypeScript's extension-based JSX rules. Adding `filePath` tells the compiler to treat `.ts` files as non-JSX (generics parse cleanly) and `.tsx` as JSX (LDProvider detection still works). Validated against LaunchDarkly's own docs codebase.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- **`wrappers` config option** — detect custom wrapper functions as flag usages. Add your wrapper names to `.flaglintrc`:
|
|
17
|
+
```json
|
|
18
|
+
{ "wrappers": ["flagPredicate", "useFlag", "getFlag", "isEnabled"] }
|
|
19
|
+
```
|
|
20
|
+
FlagLint will treat calls to these functions as `variation`-equivalent. Supports static and dynamic flag keys. Default is `[]` — no behaviour change for existing users.
|
|
21
|
+
|
|
8
22
|
## [0.2.0] - 2026-05-22
|
|
9
23
|
|
|
10
24
|
### Breaking Changes
|
package/README.md
CHANGED
|
@@ -118,6 +118,7 @@ Create `.flaglintrc` in your project root:
|
|
|
118
118
|
| `exclude` | `string[]` | `["**/node_modules/**", ...]` | Glob patterns to ignore |
|
|
119
119
|
| `provider` | `string` | `"launchdarkly"` | Feature flag provider |
|
|
120
120
|
| `minFileCount` | `number` | `1` | A flag is stale if it appears in ≤ N files (default: 1) |
|
|
121
|
+
| `wrappers` | `string[]` | `[]` | Function names that wrap LD SDK calls. FlagLint will detect calls to these functions as flag usages. Example: `["flagPredicate", "useFlag", "getFlag", "isEnabled"]` |
|
|
121
122
|
| `reportTitle` | `string` | — | Custom title for generated reports |
|
|
122
123
|
| `outputDir` | `string` | `"."` | Default output directory |
|
|
123
124
|
|
package/dist/bin/flaglint.js
CHANGED
|
@@ -76,7 +76,7 @@ function walk(root, visit) {
|
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
|
-
function detectUsages(ast, filePath) {
|
|
79
|
+
function detectUsages(ast, filePath, wrappers) {
|
|
80
80
|
const usages = [];
|
|
81
81
|
walk(ast, (node) => {
|
|
82
82
|
if (node.type === "CallExpression") {
|
|
@@ -141,6 +141,20 @@ function detectUsages(ast, filePath) {
|
|
|
141
141
|
return;
|
|
142
142
|
}
|
|
143
143
|
}
|
|
144
|
+
if (wrappers.length > 0 && callee.type === "Identifier" && wrappers.includes(callee.name) && call.arguments.length >= 1) {
|
|
145
|
+
const { flagKey, isDynamic } = extractFlagKey(call.arguments[0]);
|
|
146
|
+
const sig = checkStale(flagKey, filePath);
|
|
147
|
+
usages.push({
|
|
148
|
+
flagKey,
|
|
149
|
+
isDynamic,
|
|
150
|
+
file: filePath,
|
|
151
|
+
line: loc.line,
|
|
152
|
+
column: loc.column,
|
|
153
|
+
callType: "variation",
|
|
154
|
+
stalenessSignals: sig ? [sig] : []
|
|
155
|
+
});
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
144
158
|
if (callee.type === "CallExpression" && callee.callee.type === "Identifier" && callee.callee.name === "withLDConsumer") {
|
|
145
159
|
const sig = checkStale("*", filePath);
|
|
146
160
|
usages.push({
|
|
@@ -202,12 +216,13 @@ async function scan(source, config, onProgress) {
|
|
|
202
216
|
loc: true,
|
|
203
217
|
range: false,
|
|
204
218
|
comment: false,
|
|
205
|
-
tokens: false
|
|
219
|
+
tokens: false,
|
|
220
|
+
filePath: file
|
|
206
221
|
});
|
|
207
222
|
} catch {
|
|
208
223
|
return { usages: [], warning: `warn: failed to parse ${file}` };
|
|
209
224
|
}
|
|
210
|
-
return { usages: detectUsages(ast, file), warning: null };
|
|
225
|
+
return { usages: detectUsages(ast, file, config.wrappers), warning: null };
|
|
211
226
|
}
|
|
212
227
|
const limit = pLimit(50);
|
|
213
228
|
const results = await Promise.all(
|
|
@@ -389,7 +404,7 @@ function formatHTML(result, options) {
|
|
|
389
404
|
return `<tr class="${cls}"><td><code>${esc(key)}</code></td><td>${data.usages.length}</td><td>${fileList}</td><td>${[...data.callTypes].map(esc).join(", ")}</td><td>${status}</td></tr>`;
|
|
390
405
|
}).join("\n ");
|
|
391
406
|
const title = options.title ? esc(options.title) : "FlagLint Scan Report";
|
|
392
|
-
const version = true ? "0.2.
|
|
407
|
+
const version = true ? "0.2.1" : "0.1.0";
|
|
393
408
|
return `<!DOCTYPE html>
|
|
394
409
|
<html lang="en">
|
|
395
410
|
<head>
|
|
@@ -486,6 +501,7 @@ var FlagLintConfigSchema = z.object({
|
|
|
486
501
|
provider: z.enum(["launchdarkly", "unleash", "growthbook", "custom"]).default("launchdarkly"),
|
|
487
502
|
// TODO v0.3: replace minFileCount with real date-based staleness via git log
|
|
488
503
|
minFileCount: z.number().int().min(0).default(1),
|
|
504
|
+
wrappers: z.array(z.string()).default([]),
|
|
489
505
|
reportTitle: z.string().optional(),
|
|
490
506
|
outputDir: z.string().default(".")
|
|
491
507
|
});
|
|
@@ -808,7 +824,7 @@ function analyze(result) {
|
|
|
808
824
|
function formatMigrationReport(analysis) {
|
|
809
825
|
const { readinessScore, requiredPackages, items, manualReviewCount, autoMigrateCount } = analysis;
|
|
810
826
|
const date = (/* @__PURE__ */ new Date()).toLocaleDateString();
|
|
811
|
-
const version = true ? "0.2.
|
|
827
|
+
const version = true ? "0.2.1" : "0.1.0";
|
|
812
828
|
let scoreLabel;
|
|
813
829
|
if (readinessScore >= 80) scoreLabel = "\u2713 Your codebase is ready for migration";
|
|
814
830
|
else if (readinessScore >= 50) scoreLabel = "\u26A0 Some manual work required before migration";
|
|
@@ -1006,7 +1022,7 @@ Examples:
|
|
|
1006
1022
|
// src/cli.ts
|
|
1007
1023
|
function createCLI() {
|
|
1008
1024
|
const program2 = new Command();
|
|
1009
|
-
program2.name("flaglint").description("Find stale feature flags. Detect flag debt. Plan your OpenFeature migration.").version("0.2.
|
|
1025
|
+
program2.name("flaglint").description("Find stale feature flags. Detect flag debt. Plan your OpenFeature migration.").version("0.2.1", "-v, --version", "output the current version").addHelpText(
|
|
1010
1026
|
"after",
|
|
1011
1027
|
`
|
|
1012
1028
|
Examples:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flaglint",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Find stale feature flags. Detect flag debt. Plan your OpenFeature migration.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -41,10 +41,6 @@
|
|
|
41
41
|
"test": "vitest",
|
|
42
42
|
"test:run": "vitest run",
|
|
43
43
|
"test:coverage": "vitest run --coverage",
|
|
44
|
-
"agent": "tsx scripts/agent/agent.ts",
|
|
45
|
-
"agent:launch": "tsx scripts/agent/agent.ts launch",
|
|
46
|
-
"agent:parallel": "tsx scripts/agent/agent.ts parallel",
|
|
47
|
-
"agent:sync": "tsx scripts/agent/agent.ts sync-docs",
|
|
48
44
|
"release:patch": "tsx scripts/release.ts patch",
|
|
49
45
|
"release:minor": "tsx scripts/release.ts minor",
|
|
50
46
|
"release:major": "tsx scripts/release.ts major"
|