flaglint 0.4.0 → 0.4.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 +11 -0
- package/README.md +14 -13
- package/dist/bin/flaglint.js +25 -10
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.4.1] - 2026-05-25
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Detail evaluation methods are classified as manual review and excluded from safe auto-transform counts.
|
|
15
|
+
- Generated LaunchDarkly OpenFeature provider setup now uses the SDK key constructor correctly.
|
|
16
|
+
- Evaluation-context guidance now states that either OpenFeature `targetingKey` or existing LaunchDarkly `key` is accepted.
|
|
17
|
+
- Default one-file flags no longer trigger staleness solely because they occur in one file; explicit `minFileCount: 1` remains available.
|
|
18
|
+
- Reporter output now says `Flags with review signals` instead of implying flags are safe to remove.
|
|
19
|
+
- Public early-preview messaging now states the Node.js server-side migration scope and review/testing requirement.
|
|
20
|
+
|
|
10
21
|
## [0.4.0] - 2026-05-24
|
|
11
22
|
|
|
12
23
|
### Added
|
package/README.md
CHANGED
|
@@ -21,9 +21,12 @@
|
|
|
21
21
|
</a>
|
|
22
22
|
</p>
|
|
23
23
|
|
|
24
|
-
>
|
|
25
|
-
>
|
|
26
|
-
>
|
|
24
|
+
> [!WARNING]
|
|
25
|
+
> FlagLint is currently an early preview.
|
|
26
|
+
>
|
|
27
|
+
> Automatic migration currently supports LaunchDarkly Node.js server-side SDK evaluation calls only. Generated changes must be reviewed and tested before merging.
|
|
28
|
+
>
|
|
29
|
+
> React hooks, higher-order components, browser/client-side SDK usage, bulk flag-state calls, detail evaluations, dynamic keys, and custom wrappers are not automatically migrated in this release.
|
|
27
30
|
|
|
28
31
|
# FlagLint
|
|
29
32
|
|
|
@@ -209,10 +212,8 @@ Run `flaglint migrate --dry-run` to review the migration plan.
|
|
|
209
212
|
| Dynamic flag key | ✗ manual | Key must be a static string literal |
|
|
210
213
|
| `ldClient.allFlags()` / `allFlagsState()` | ✗ manual | Bulk calls — no single-flag codemod |
|
|
211
214
|
| Unknown fallback type | ✗ manual | Fallback type must be determinable statically |
|
|
212
|
-
| React `useFlags()`, `useLDClient()` | detect only | Client-side — outside Node.js server SDK scope |
|
|
213
|
-
| React HOC / `<LDProvider>` | detect only | Client-side — outside Node.js server SDK scope |
|
|
214
215
|
|
|
215
|
-
`flaglint scan` and `flaglint migrate --dry-run` report
|
|
216
|
+
`flaglint scan` and `flaglint migrate --dry-run` report detected LaunchDarkly Node.js server-side SDK patterns, including manual-review cases.
|
|
216
217
|
`flaglint migrate --apply` rewrites only the ✓ rows above.
|
|
217
218
|
|
|
218
219
|
---
|
|
@@ -231,15 +232,15 @@ npm install @openfeature/server-sdk \
|
|
|
231
232
|
Bootstrap file (do not apply automatically — bootstrap is intentionally manual):
|
|
232
233
|
|
|
233
234
|
```typescript
|
|
234
|
-
import LaunchDarkly from "@launchdarkly/node-server-sdk";
|
|
235
235
|
import { LaunchDarklyProvider } from "@launchdarkly/openfeature-node-server";
|
|
236
236
|
import { OpenFeature } from "@openfeature/server-sdk";
|
|
237
237
|
|
|
238
|
-
const
|
|
239
|
-
await OpenFeature.setProviderAndWait(
|
|
238
|
+
const ldProvider = new LaunchDarklyProvider(process.env.LD_SDK_KEY!);
|
|
239
|
+
await OpenFeature.setProviderAndWait(ldProvider);
|
|
240
240
|
|
|
241
|
-
// Evaluation context
|
|
242
|
-
//
|
|
241
|
+
// Evaluation context needs a targeting key.
|
|
242
|
+
// Use OpenFeature `targetingKey` or keep an existing LaunchDarkly `key`:
|
|
243
|
+
// { targetingKey: user.id } or { key: user.id }
|
|
243
244
|
export const openFeatureClient = OpenFeature.getClient();
|
|
244
245
|
```
|
|
245
246
|
|
|
@@ -278,7 +279,7 @@ Create `.flaglintrc`, `.flaglintrc.json`, or `flaglint.config.json` in your proj
|
|
|
278
279
|
"include": ["**/*.{ts,tsx,js,jsx}"],
|
|
279
280
|
"exclude": ["**/node_modules/**", "**/dist/**"],
|
|
280
281
|
"provider": "launchdarkly",
|
|
281
|
-
"minFileCount":
|
|
282
|
+
"minFileCount": 0,
|
|
282
283
|
"reportTitle": "My Project Flag Report"
|
|
283
284
|
}
|
|
284
285
|
```
|
|
@@ -288,7 +289,7 @@ Create `.flaglintrc`, `.flaglintrc.json`, or `flaglint.config.json` in your proj
|
|
|
288
289
|
| `include` | `string[]` | `["**/*.{ts,tsx,js,jsx}"]` | Glob patterns to scan |
|
|
289
290
|
| `exclude` | `string[]` | `["**/node_modules/**", ...]` | Glob patterns to ignore |
|
|
290
291
|
| `provider` | `string` | `"launchdarkly"` | Feature flag provider |
|
|
291
|
-
| `minFileCount` | `number` | `
|
|
292
|
+
| `minFileCount` | `number` | `0` | Opt-in staleness heuristic. When set above 0, a flag is a staleness candidate if it appears in ≤ N files |
|
|
292
293
|
| `wrappers` | `string[]` | `[]` | Function names wrapping LD SDK calls. Example: `["flagPredicate", "useFlag"]` |
|
|
293
294
|
| `reportTitle` | `string` | — | Custom title for generated reports |
|
|
294
295
|
| `outputDir` | `string` | `"."` | Default output directory |
|
package/dist/bin/flaglint.js
CHANGED
|
@@ -53,6 +53,13 @@ var LD_FLAG_KEY_METHODS = /* @__PURE__ */ new Set([
|
|
|
53
53
|
"jsonVariationDetail"
|
|
54
54
|
]);
|
|
55
55
|
var LD_ALL_FLAGS_METHODS = /* @__PURE__ */ new Set(["allFlags", "allFlagsState"]);
|
|
56
|
+
var LD_DETAIL_METHODS = /* @__PURE__ */ new Set([
|
|
57
|
+
"variationDetail",
|
|
58
|
+
"boolVariationDetail",
|
|
59
|
+
"stringVariationDetail",
|
|
60
|
+
"numberVariationDetail",
|
|
61
|
+
"jsonVariationDetail"
|
|
62
|
+
]);
|
|
56
63
|
var LD_HOOKS = /* @__PURE__ */ new Set(["useFlags", "useLDClient"]);
|
|
57
64
|
var DEFAULT_EXCLUDE = ["**/node_modules/**", "**/dist/**", "**/build/**", "**/.next/**"];
|
|
58
65
|
function extractFlagKey(arg) {
|
|
@@ -120,7 +127,7 @@ function buildMigrationInventoryItem(code, filePath, loc, call, methodName, args
|
|
|
120
127
|
}
|
|
121
128
|
const fallback = args[2];
|
|
122
129
|
const valueType = inferValueType(methodName, fallback);
|
|
123
|
-
const manualReviewReason = isDynamic ? "dynamic-key" : valueType === "unknown" ? "unknown-fallback" : void 0;
|
|
130
|
+
const manualReviewReason = isDynamic ? "dynamic-key" : LD_DETAIL_METHODS.has(methodName) ? "detail-method" : valueType === "unknown" ? "unknown-fallback" : void 0;
|
|
124
131
|
return {
|
|
125
132
|
file: filePath,
|
|
126
133
|
line: loc.line,
|
|
@@ -528,7 +535,7 @@ function formatMarkdown(result, options) {
|
|
|
528
535
|
}
|
|
529
536
|
if (staleFlags.length > 0) {
|
|
530
537
|
lines.push("## \u26A0 Stale Flag Candidates");
|
|
531
|
-
lines.push("Flags
|
|
538
|
+
lines.push("Flags with review signals:");
|
|
532
539
|
lines.push("| Flag Key | Reason | Location |");
|
|
533
540
|
lines.push("|----------|--------|----------|");
|
|
534
541
|
for (const [key, data] of staleFlags) {
|
|
@@ -681,7 +688,7 @@ function formatHTML(result, options) {
|
|
|
681
688
|
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>`;
|
|
682
689
|
}).join("\n ");
|
|
683
690
|
const title = options.title ? esc(options.title) : "FlagLint Scan Report";
|
|
684
|
-
const version = true ? "0.4.
|
|
691
|
+
const version = true ? "0.4.1" : "0.1.0";
|
|
685
692
|
return `<!DOCTYPE html>
|
|
686
693
|
<html lang="en">
|
|
687
694
|
<head>
|
|
@@ -779,7 +786,7 @@ var FlagLintConfigSchema = z.object({
|
|
|
779
786
|
]),
|
|
780
787
|
provider: z.enum(["launchdarkly", "unleash", "growthbook", "custom"]).default("launchdarkly"),
|
|
781
788
|
// TODO v0.3: replace minFileCount with real date-based staleness via git log
|
|
782
|
-
minFileCount: z.number().int().min(0).default(
|
|
789
|
+
minFileCount: z.number().int().min(0).default(0),
|
|
783
790
|
wrappers: z.array(z.string()).default([]),
|
|
784
791
|
reportTitle: z.string().optional(),
|
|
785
792
|
outputDir: z.string().default(".")
|
|
@@ -1011,6 +1018,8 @@ function reasonLabel(reason) {
|
|
|
1011
1018
|
return "dynamic key";
|
|
1012
1019
|
case "unknown-fallback":
|
|
1013
1020
|
return "unsupported or unknown fallback";
|
|
1021
|
+
case "detail-method":
|
|
1022
|
+
return "detail method";
|
|
1014
1023
|
case "bulk-inventory-call":
|
|
1015
1024
|
return "bulk inventory call";
|
|
1016
1025
|
default:
|
|
@@ -1094,7 +1103,7 @@ function formatMigrationReport(analysis) {
|
|
|
1094
1103
|
unsupportedUnknownCount
|
|
1095
1104
|
} = analysis;
|
|
1096
1105
|
const date = (/* @__PURE__ */ new Date()).toLocaleDateString();
|
|
1097
|
-
const version = true ? "0.4.
|
|
1106
|
+
const version = true ? "0.4.1" : "0.1.0";
|
|
1098
1107
|
const lines = [];
|
|
1099
1108
|
lines.push("# OpenFeature Migration Inventory");
|
|
1100
1109
|
lines.push(`Generated by FlagLint v${version} on ${date}`);
|
|
@@ -1179,6 +1188,9 @@ function methodForType(valueType) {
|
|
|
1179
1188
|
function manualReason(item) {
|
|
1180
1189
|
if (item.manualReviewReason === "dynamic-key") return "dynamic key requires manual review";
|
|
1181
1190
|
if (item.manualReviewReason === "unknown-fallback") return "unknown fallback type requires manual review";
|
|
1191
|
+
if (item.manualReviewReason === "detail-method") {
|
|
1192
|
+
return "detail methods skipped: OpenFeature detail APIs exist, but LaunchDarkly/OpenFeature detail result parity requires manual review";
|
|
1193
|
+
}
|
|
1182
1194
|
if (item.manualReviewReason === "bulk-inventory-call") return "bulk inventory call has no single-flag codemod";
|
|
1183
1195
|
return "manual review required";
|
|
1184
1196
|
}
|
|
@@ -1268,7 +1280,7 @@ function formatProviderSetupSection() {
|
|
|
1268
1280
|
lines.push('import { OpenFeature } from "@openfeature/server-sdk";');
|
|
1269
1281
|
lines.push('import { LaunchDarklyProvider } from "@launchdarkly/openfeature-node-server";');
|
|
1270
1282
|
lines.push("");
|
|
1271
|
-
lines.push(
|
|
1283
|
+
lines.push("const ldProvider = new LaunchDarklyProvider(process.env.LD_SDK_KEY!);");
|
|
1272
1284
|
lines.push("await OpenFeature.setProviderAndWait(ldProvider);");
|
|
1273
1285
|
lines.push("");
|
|
1274
1286
|
lines.push("// Share this client across your application.");
|
|
@@ -1278,11 +1290,14 @@ function formatProviderSetupSection() {
|
|
|
1278
1290
|
lines.push("");
|
|
1279
1291
|
lines.push("### 3. Evaluation context \u2014 targeting key");
|
|
1280
1292
|
lines.push("");
|
|
1281
|
-
lines.push("LaunchDarkly requires a
|
|
1282
|
-
lines.push("
|
|
1293
|
+
lines.push("LaunchDarkly requires a targeting key in every evaluation context. The");
|
|
1294
|
+
lines.push("provider accepts either OpenFeature `targetingKey` or an existing");
|
|
1295
|
+
lines.push("LaunchDarkly `key`.");
|
|
1296
|
+
lines.push("Keep your existing LaunchDarkly `key` contexts, or use `targetingKey`");
|
|
1297
|
+
lines.push("for new OpenFeature-native contexts:");
|
|
1283
1298
|
lines.push("");
|
|
1284
1299
|
lines.push("```typescript");
|
|
1285
|
-
lines.push("{ targetingKey: user.key
|
|
1300
|
+
lines.push("{ targetingKey: user.id } // or { key: user.id }");
|
|
1286
1301
|
lines.push("```");
|
|
1287
1302
|
lines.push("");
|
|
1288
1303
|
return lines;
|
|
@@ -1881,7 +1896,7 @@ Examples:
|
|
|
1881
1896
|
// src/cli.ts
|
|
1882
1897
|
function createCLI() {
|
|
1883
1898
|
const program2 = new Command();
|
|
1884
|
-
program2.name("flaglint").description("LaunchDarkly Node.js server SDK -> OpenFeature migration.").version("0.4.
|
|
1899
|
+
program2.name("flaglint").description("LaunchDarkly Node.js server SDK -> OpenFeature migration.").version("0.4.1", "-v, --version", "output the current version").addHelpText(
|
|
1885
1900
|
"after",
|
|
1886
1901
|
`
|
|
1887
1902
|
Examples:
|