flaglint 0.5.2 → 0.5.4
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 +8 -0
- package/README.md +53 -417
- package/dist/bin/flaglint.js +18 -6
- package/package.json +10 -5
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## Unreleased
|
|
9
9
|
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- Added truth-gate coverage and scanner support for destructured CommonJS
|
|
13
|
+
LaunchDarkly initializer aliases such as
|
|
14
|
+
`const { init: ldInit } = require("@launchdarkly/node-server-sdk")`. This
|
|
15
|
+
keeps the public alias/CJS provenance claim backed across scan, validate,
|
|
16
|
+
validation SARIF, dry-run, and guarded apply flows.
|
|
17
|
+
|
|
10
18
|
## [0.5.2] - 2026-05-27
|
|
11
19
|
|
|
12
20
|
### Fixed
|
package/README.md
CHANGED
|
@@ -23,27 +23,13 @@
|
|
|
23
23
|
|
|
24
24
|
# FlagLint
|
|
25
25
|
|
|
26
|
-
Standardize LaunchDarkly usage on OpenFeature
|
|
26
|
+
**Standardize LaunchDarkly usage on OpenFeature.**
|
|
27
27
|
|
|
28
|
-
FlagLint inventories direct LaunchDarkly Node.js SDK calls, generates reviewable migration
|
|
29
|
-
|
|
28
|
+
FlagLint inventories direct LaunchDarkly Node.js SDK calls, generates reviewable migration plans,
|
|
29
|
+
and prevents new vendor-coupled flag access from entering your codebase.
|
|
30
|
+
LaunchDarkly remains your provider. OpenFeature becomes the evaluation API your application code calls.
|
|
30
31
|
|
|
31
|
-
**
|
|
32
|
-
|
|
33
|
-
Docs: [Getting Started](https://flaglint.dev/docs/getting-started) · [Commands](https://flaglint.dev/docs/commands/scan) · [Supported Scope](https://flaglint.dev/docs/supported-scope) · [CI Integration](https://flaglint.dev/docs/ci-github-actions) · [Enterprise Demo](https://flaglint.dev/docs/demo)
|
|
34
|
-
|
|
35
|
-
[View enterprise demo source ->](./examples/enterprise-checkout-service)
|
|
36
|
-
|
|
37
|
-
---
|
|
38
|
-
|
|
39
|
-
## Workflow
|
|
40
|
-
|
|
41
|
-
| Step | Command | Purpose |
|
|
42
|
-
|------|---------|---------|
|
|
43
|
-
| 1 | `flaglint scan` | AST inventory of every direct LD Node server SDK call |
|
|
44
|
-
| 2 | `flaglint migrate --dry-run` | Reviewable before/after diffs; provider setup guidance appears when needed |
|
|
45
|
-
| 3 | `flaglint migrate --apply` | Apply only guarded, provably automatable transformations |
|
|
46
|
-
| 4 | `flaglint validate --no-direct-launchdarkly` | CI gate: exit 1 if direct LD calls remain |
|
|
32
|
+
**[Documentation](https://flaglint.dev/docs)** · **[Quickstart](https://flaglint.dev/docs/quickstart)** · **[Enterprise Demo](https://flaglint.dev/docs/enterprise-demo)** · **[npm](https://npmjs.com/package/flaglint)** · **[Issues](https://github.com/flaglint/flaglint/issues)**
|
|
47
33
|
|
|
48
34
|
---
|
|
49
35
|
|
|
@@ -53,444 +39,94 @@ Docs: [Getting Started](https://flaglint.dev/docs/getting-started) · [Commands]
|
|
|
53
39
|
npx flaglint scan ./src
|
|
54
40
|
```
|
|
55
41
|
|
|
56
|
-
Example output:
|
|
57
|
-
|
|
58
|
-
```text
|
|
59
|
-
✓ 15 flag usages found across 6 unique flags (48ms)
|
|
60
|
-
ℹ 1 dynamic flag key(s) require manual review
|
|
61
42
|
```
|
|
43
|
+
✔ Scanning 5 files...
|
|
44
|
+
✔ Found 20 direct LaunchDarkly Node SDK calls across 11 unique flags
|
|
45
|
+
⚡ 6 dynamic flag keys — manual review required
|
|
46
|
+
↳ 2 detail method calls — manual review required
|
|
62
47
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
```markdown
|
|
66
|
-
## Flag Inventory
|
|
67
|
-
| Flag Key | Usages | Files | Call Types |
|
|
68
|
-
|----------|--------|-------|------------|
|
|
69
|
-
| checkout-v2 | 3 | 2 | boolVariation |
|
|
70
|
-
| color-theme | 1 | 1 | stringVariation |
|
|
71
|
-
| timeout-ms | 1 | 1 | numberVariation |
|
|
48
|
+
→ Run flaglint migrate --dry-run for a reviewable migration diff
|
|
72
49
|
```
|
|
73
50
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
Pipe-friendly. Every usage includes file, line, call type, and staleness signals:
|
|
77
|
-
|
|
78
|
-
```json
|
|
79
|
-
{
|
|
80
|
-
"flagKey": "checkout-v2",
|
|
81
|
-
"isDynamic": false,
|
|
82
|
-
"file": "src/services/checkout.ts",
|
|
83
|
-
"line": 14,
|
|
84
|
-
"callType": "boolVariation",
|
|
85
|
-
"stalenessSignals": []
|
|
86
|
-
}
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
---
|
|
90
|
-
|
|
91
|
-
## Installation
|
|
51
|
+
Preview the migration before changing anything:
|
|
92
52
|
|
|
93
53
|
```bash
|
|
94
|
-
|
|
95
|
-
# or use without installing
|
|
96
|
-
npx flaglint
|
|
54
|
+
npx flaglint migrate ./src --dry-run
|
|
97
55
|
```
|
|
98
56
|
|
|
99
|
-
Requires Node.js 20 or newer. CI validates FlagLint on Node.js 20 and 22.
|
|
100
|
-
|
|
101
|
-
---
|
|
102
|
-
|
|
103
|
-
## Commands
|
|
104
|
-
|
|
105
|
-
### `flaglint scan [dir]`
|
|
106
|
-
|
|
107
|
-
AST-based inventory of direct LaunchDarkly Node.js server SDK calls.
|
|
108
|
-
|
|
109
|
-
```bash
|
|
110
|
-
flaglint scan ./src
|
|
111
|
-
flaglint scan --format json --output report.json
|
|
112
|
-
flaglint scan --format html --output report.html
|
|
113
|
-
flaglint scan --format sarif --output flaglint.sarif
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
| Option | Default | Description |
|
|
117
|
-
|--------|---------|-------------|
|
|
118
|
-
| `--format` | `markdown` | Output format: `json`, `markdown`, `html`, `sarif` |
|
|
119
|
-
| `--output` | stdout | Write report to file |
|
|
120
|
-
| `--config` | auto-detect | Path to a config file |
|
|
121
|
-
| `--exclude-tests` | — | Exclude test files from scan results |
|
|
122
|
-
|
|
123
|
-
Exit code `0` when no staleness signals detected, `1` when staleness signals are present —
|
|
124
|
-
enabling CI visibility into flag usage patterns.
|
|
125
|
-
|
|
126
|
-
---
|
|
127
|
-
|
|
128
|
-
### `flaglint migrate [dir]`
|
|
129
|
-
|
|
130
|
-
Analyzes migration readiness and generates an OpenFeature migration plan.
|
|
131
|
-
|
|
132
|
-
```bash
|
|
133
|
-
flaglint migrate ./src # write MIGRATION.md
|
|
134
|
-
flaglint migrate --dry-run # reviewable diffs to stdout
|
|
135
|
-
flaglint migrate --apply # guarded: apply only provably automatable transformations in-place
|
|
136
|
-
flaglint migrate --apply --allow-dirty # apply even on a dirty working tree
|
|
137
|
-
flaglint migrate --output plan.md # write to custom file
|
|
138
|
-
flaglint migrate --exclude-tests # skip test and spec files
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
| Option | Default | Description |
|
|
142
|
-
|--------|---------|-------------|
|
|
143
|
-
| `--output` | `MIGRATION.md` | Write migration plan to file |
|
|
144
|
-
| `--dry-run` | — | Print reviewable diffs to stdout; includes provider setup guidance when a diff needs it |
|
|
145
|
-
| `--apply` | — | Apply automatable transformations in-place (requires clean git tree) |
|
|
146
|
-
| `--allow-dirty` | — | Override dirty-tree guard for `--apply` |
|
|
147
|
-
| `--config` | auto-detect | Path to a config file |
|
|
148
|
-
| `--exclude-tests` | — | Skip `*.test.*`, `*.spec.*`, `__tests__/`, `tests/` |
|
|
149
|
-
|
|
150
|
-
**`--apply` safety contracts:**
|
|
151
|
-
- Refuses on a dirty git working tree unless `--allow-dirty`
|
|
152
|
-
- Skips any file that does not contain a proven OpenFeature client binding:
|
|
153
|
-
either a local `OpenFeature.getClient()` binding or a configured imported
|
|
154
|
-
shared client allowlisted with `openFeatureClientBindings`
|
|
155
|
-
- Imported client matching uses glob-safe `modulePatterns`; aliased imports
|
|
156
|
-
preserve the local identifier; TypeScript ESM `.js` runtime import specifiers
|
|
157
|
-
are recognized; ambiguous or unconfigured imports skip safely
|
|
158
|
-
- Provider/bootstrap setup is never inserted automatically
|
|
159
|
-
- Never touches detail methods, dynamic keys, unknown fallbacks, or bulk calls
|
|
160
|
-
- Preserves `await` and original call arguments exactly
|
|
161
|
-
- Idempotent: re-running with the same analysis has no effect
|
|
162
|
-
|
|
163
|
-
---
|
|
164
|
-
|
|
165
|
-
### `flaglint validate [dir]`
|
|
166
|
-
|
|
167
|
-
Validates that your codebase complies with feature flag policy rules.
|
|
168
|
-
Designed for CI enforcement after migration is complete.
|
|
169
|
-
|
|
170
|
-
```bash
|
|
171
|
-
flaglint validate # report usages, always exits 0
|
|
172
|
-
flaglint validate --no-direct-launchdarkly # exit 1 on any direct LD eval call
|
|
173
|
-
flaglint validate --no-direct-launchdarkly \
|
|
174
|
-
--bootstrap-exclude src/provider/setup.ts # allow specific bootstrap file
|
|
175
|
-
flaglint validate --no-direct-launchdarkly \
|
|
176
|
-
--bootstrap-exclude "src/provider/**" # allow all provider-directory files
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
| Option | Default | Description |
|
|
180
|
-
|--------|---------|-------------|
|
|
181
|
-
| `--no-direct-launchdarkly` | — | Exit 1 if any direct LD Node server evaluation calls found |
|
|
182
|
-
| `--bootstrap-exclude <glob>` | — | Repeatable glob; matching files excluded from violations |
|
|
183
|
-
| `--config` | auto-detect | Path to a config file |
|
|
184
|
-
|
|
185
|
-
Exit codes: `0` = passed, `1` = violations found, `130` = SIGINT.
|
|
186
|
-
|
|
187
|
-
**Example pass output:**
|
|
188
|
-
```
|
|
189
|
-
✓ validate --no-direct-launchdarkly: no direct LaunchDarkly evaluation calls found.
|
|
190
|
-
Scanned 42 file(s).
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
**Example fail output:**
|
|
194
|
-
```
|
|
195
|
-
✗ validate --no-direct-launchdarkly: 2 direct LaunchDarkly evaluation call(s) found.
|
|
196
|
-
|
|
197
|
-
src/services/checkout.ts:42:8 — boolVariation("checkout-v2")
|
|
198
|
-
src/services/pricing.ts:17:4 — boolVariation(dynamic key — manual review required)
|
|
199
|
-
|
|
200
|
-
These files must migrate to OpenFeature before this rule passes.
|
|
201
|
-
Run `flaglint migrate --dry-run` to review the migration plan.
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
---
|
|
205
|
-
|
|
206
|
-
## Focused scope for safe automation
|
|
207
|
-
|
|
208
|
-
Automatic migration currently supports LaunchDarkly Node.js server-side evaluation calls in
|
|
209
|
-
TypeScript and JavaScript. That narrow scope is intentional: FlagLint only rewrites call sites
|
|
210
|
-
where the value type, static flag key, fallback, evaluation context, and OpenFeature client
|
|
211
|
-
binding are explicit enough to preserve.
|
|
212
|
-
|
|
213
|
-
Dynamic keys, detail evaluations, bulk flag-state calls, browser SDKs, React usage, and ambiguous
|
|
214
|
-
patterns are reported for manual review. They are inventoried so teams can plan the migration,
|
|
215
|
-
but they are not automatically transformed.
|
|
216
|
-
|
|
217
|
-
## Supported API matrix
|
|
218
|
-
|
|
219
|
-
**Scope: LaunchDarkly Node.js server-side SDK evaluation calls from
|
|
220
|
-
`@launchdarkly/node-server-sdk` and legacy `launchdarkly-node-server-sdk` imports.**
|
|
221
|
-
|
|
222
|
-
| LaunchDarkly call | Automatable | OpenFeature equivalent |
|
|
223
|
-
|---|---|---|
|
|
224
|
-
| `ldClient.boolVariation(key, ctx, false)` | ✓ | `openFeatureClient.getBooleanValue(key, false, ctx)` |
|
|
225
|
-
| `ldClient.stringVariation(key, ctx, "")` | ✓ | `openFeatureClient.getStringValue(key, "", ctx)` |
|
|
226
|
-
| `ldClient.numberVariation(key, ctx, 0)` | ✓ | `openFeatureClient.getNumberValue(key, 0, ctx)` |
|
|
227
|
-
| `ldClient.jsonVariation(key, ctx, {})` | ✓ | `openFeatureClient.getObjectValue(key, {}, ctx)` |
|
|
228
|
-
| `ldClient.*VariationDetail(...)` | ✗ manual | Detail result shapes differ — requires manual review |
|
|
229
|
-
| Dynamic flag key | ✗ manual | Key must be a static string literal |
|
|
230
|
-
| `ldClient.allFlags()` / `allFlagsState()` | ✗ manual | Bulk calls — no single-flag codemod |
|
|
231
|
-
| Unknown fallback type | ✗ manual | Fallback type must be determinable statically |
|
|
232
|
-
|
|
233
|
-
`flaglint scan` and `flaglint migrate --dry-run` report detected LaunchDarkly Node.js server-side SDK patterns, including manual-review cases.
|
|
234
|
-
`flaglint migrate --apply` rewrites only the ✓ rows above.
|
|
235
|
-
|
|
236
57
|
---
|
|
237
58
|
|
|
238
|
-
##
|
|
239
|
-
|
|
240
|
-
`flaglint migrate --dry-run` includes this guidance inline. **Complete provider setup in
|
|
241
|
-
one dedicated file before running `--apply`.**
|
|
242
|
-
|
|
243
|
-
```bash
|
|
244
|
-
npm install @openfeature/server-sdk \
|
|
245
|
-
@launchdarkly/node-server-sdk \
|
|
246
|
-
@launchdarkly/openfeature-node-server
|
|
247
|
-
```
|
|
248
|
-
|
|
249
|
-
Bootstrap file (do not apply automatically — bootstrap is intentionally manual):
|
|
250
|
-
|
|
251
|
-
```typescript
|
|
252
|
-
import { LaunchDarklyProvider } from "@launchdarkly/openfeature-node-server";
|
|
253
|
-
import { OpenFeature } from "@openfeature/server-sdk";
|
|
254
|
-
|
|
255
|
-
const ldProvider = new LaunchDarklyProvider(process.env.LD_SDK_KEY!);
|
|
256
|
-
await OpenFeature.setProviderAndWait(ldProvider);
|
|
257
|
-
|
|
258
|
-
// Evaluation context needs a targeting key.
|
|
259
|
-
// Use OpenFeature `targetingKey` or keep an existing LaunchDarkly `key`:
|
|
260
|
-
// { targetingKey: user.id } or { key: user.id }
|
|
261
|
-
export const openFeatureClient = OpenFeature.getClient();
|
|
262
|
-
```
|
|
59
|
+
## Workflow
|
|
263
60
|
|
|
264
|
-
|
|
265
|
-
|
|
61
|
+
| Step | Command | What happens |
|
|
62
|
+
|------|---------|-------------|
|
|
63
|
+
| 1 | `flaglint scan ./src` | AST inventory of every direct LD Node server SDK call |
|
|
64
|
+
| 2 | `flaglint migrate --dry-run` | Reviewable before/after diffs; inline provider setup guidance |
|
|
65
|
+
| 3 | `flaglint migrate --apply` | Rewrites only guarded, provably automatable call-sites |
|
|
66
|
+
| 4 | `flaglint validate --no-direct-launchdarkly` | CI gate: exit 1 if direct LD evaluation calls remain |
|
|
266
67
|
|
|
267
68
|
---
|
|
268
69
|
|
|
269
|
-
##
|
|
70
|
+
## Before → After (real output from enterprise demo)
|
|
270
71
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
72
|
+
```diff
|
|
73
|
+
--- a/checkout.ts
|
|
74
|
+
+++ b/checkout.ts
|
|
75
|
+
- return ldClient.boolVariation("checkout-v2", ctx, false);
|
|
76
|
+
+ return openFeatureClient.getBooleanValue("checkout-v2", false, ctx);
|
|
274
77
|
|
|
275
|
-
|
|
78
|
+
- return ldClient.stringVariation("payment-provider", ctx, "stripe");
|
|
79
|
+
+ return openFeatureClient.getStringValue("payment-provider", "stripe", ctx);
|
|
276
80
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
"
|
|
280
|
-
|
|
281
|
-
"importName": "openFeatureClient",
|
|
282
|
-
"modulePatterns": ["**/platform/feature-flags"]
|
|
283
|
-
}
|
|
284
|
-
]
|
|
285
|
-
}
|
|
286
|
-
```
|
|
287
|
-
|
|
288
|
-
`modulePatterns` are **glob patterns** matched against the module import specifier
|
|
289
|
-
(leading `./` and `../` traversal is stripped before matching). A pattern of
|
|
290
|
-
`"**/platform/feature-flags"` matches `"../platform/feature-flags"` and
|
|
291
|
-
`"../../shared/platform/feature-flags"`, but **not** `"../platform/feature-flags-legacy"` or
|
|
292
|
-
`"../other/platform/feature-flags-backup"`.
|
|
293
|
-
For TypeScript ESM projects, configured module patterns without `.js` also recognize
|
|
294
|
-
the corresponding `.js` runtime import specifier.
|
|
295
|
-
|
|
296
|
-
**Before:**
|
|
297
|
-
```typescript
|
|
298
|
-
// services/checkout.ts
|
|
299
|
-
import { openFeatureClient } from "../platform/feature-flags";
|
|
300
|
-
import LaunchDarkly from "launchdarkly-node-server-sdk";
|
|
301
|
-
|
|
302
|
-
const enabled = ldClient.boolVariation("checkout-v2", { key: user.id }, false);
|
|
303
|
-
```
|
|
304
|
-
|
|
305
|
-
**After (`flaglint migrate --apply`):**
|
|
306
|
-
```typescript
|
|
307
|
-
// services/checkout.ts
|
|
308
|
-
import { openFeatureClient } from "../platform/feature-flags";
|
|
309
|
-
import LaunchDarkly from "launchdarkly-node-server-sdk";
|
|
310
|
-
|
|
311
|
-
const enabled = openFeatureClient.getBooleanValue("checkout-v2", false, { key: user.id });
|
|
312
|
-
```
|
|
313
|
-
|
|
314
|
-
If the import is aliased (e.g. `import { openFeatureClient as flags } from "..."`), FlagLint
|
|
315
|
-
previews and applies the transformation using the **local alias name** (`flags.getBooleanValue(...)`).
|
|
316
|
-
|
|
317
|
-
When two configured bindings both match a file, FlagLint considers the result ambiguous and
|
|
318
|
-
**skips that file** rather than guessing. Skipped files are reported in `--dry-run` output.
|
|
319
|
-
|
|
320
|
-
Provider initialization remains the platform team's responsibility. FlagLint never inserts or
|
|
321
|
-
modifies bootstrap/provider setup code.
|
|
322
|
-
|
|
323
|
-
---
|
|
324
|
-
|
|
325
|
-
## Example transformation
|
|
326
|
-
|
|
327
|
-
**Before — direct LaunchDarkly Node.js server SDK:**
|
|
328
|
-
```typescript
|
|
329
|
-
const enabled = await ldClient.boolVariation("checkout-v2", { key: user.id }, false);
|
|
330
|
-
const theme = await ldClient.stringVariation("color-theme", { key: user.id }, "light");
|
|
331
|
-
const timeout = await ldClient.numberVariation("timeout-ms", { key: user.id }, 5000);
|
|
332
|
-
```
|
|
333
|
-
|
|
334
|
-
**After — OpenFeature via LaunchDarkly provider:**
|
|
335
|
-
```typescript
|
|
336
|
-
const enabled = await openFeatureClient.getBooleanValue("checkout-v2", false, { key: user.id });
|
|
337
|
-
const theme = await openFeatureClient.getStringValue("color-theme", "light", { key: user.id });
|
|
338
|
-
const timeout = await openFeatureClient.getNumberValue("timeout-ms", 5000, { key: user.id });
|
|
81
|
+
--- a/pricing.ts
|
|
82
|
+
+++ b/pricing.ts
|
|
83
|
+
- return ldClient.numberVariation("discount-percentage", ctx, 0);
|
|
84
|
+
+ return openFeatureClient.getNumberValue("discount-percentage", 0, ctx);
|
|
339
85
|
```
|
|
340
86
|
|
|
341
87
|
Flag key, fallback value, `await`, and evaluation context are preserved exactly.
|
|
342
|
-
LaunchDarkly continues to serve the flags — only the call-site API changes.
|
|
343
|
-
When authoring new OpenFeature-native bootstrap or application code, you may use
|
|
344
|
-
OpenFeature `targetingKey`; FlagLint does not silently rewrite existing
|
|
345
|
-
LaunchDarkly `key` contexts.
|
|
346
|
-
|
|
347
|
-
---
|
|
348
|
-
|
|
349
|
-
## Configuration
|
|
350
|
-
|
|
351
|
-
Create `.flaglintrc`, `.flaglintrc.json`, or `flaglint.config.json` in your project root:
|
|
352
|
-
|
|
353
|
-
```json
|
|
354
|
-
{
|
|
355
|
-
"include": ["**/*.{ts,tsx,js,jsx}"],
|
|
356
|
-
"exclude": ["**/node_modules/**", "**/dist/**"],
|
|
357
|
-
"provider": "launchdarkly",
|
|
358
|
-
"minFileCount": 0,
|
|
359
|
-
"reportTitle": "My Project Flag Report"
|
|
360
|
-
}
|
|
361
|
-
```
|
|
362
|
-
|
|
363
|
-
| Field | Type | Default | Description |
|
|
364
|
-
|-------|------|---------|-------------|
|
|
365
|
-
| `include` | `string[]` | `["**/*.{ts,tsx,js,jsx}"]` | Glob patterns to scan |
|
|
366
|
-
| `exclude` | `string[]` | `["**/node_modules/**", ...]` | Glob patterns to ignore |
|
|
367
|
-
| `provider` | `string` | `"launchdarkly"` | Feature flag provider |
|
|
368
|
-
| `minFileCount` | `number` | `0` | Opt-in staleness heuristic. When set above 0, a flag is a staleness candidate if it appears in ≤ N files |
|
|
369
|
-
| `wrappers` | `string[]` | `[]` | Function names wrapping LD SDK calls. Example: `["flagPredicate", "useFlag"]` |
|
|
370
|
-
| `openFeatureClientBindings` | `{ importName: string; modulePatterns: string[] }[]` | `[]` | Allowlist shared imported OpenFeature client bindings for `--apply` eligibility. See [Using an existing shared OpenFeature client](#using-an-existing-shared-openfeature-client). |
|
|
371
|
-
| `reportTitle` | `string` | — | Custom title for generated reports |
|
|
372
|
-
| `outputDir` | `string` | `"."` | Default output directory |
|
|
373
|
-
|
|
374
|
-
FlagLint searches for config in this order: `--config` path → `.flaglintrc` → `.flaglintrc.json` → `flaglint.config.json`.
|
|
375
88
|
|
|
376
89
|
---
|
|
377
90
|
|
|
378
|
-
##
|
|
91
|
+
## Supported scope
|
|
379
92
|
|
|
380
|
-
|
|
93
|
+
LaunchDarkly Node.js server-side SDK calls from `launchdarkly-node-server-sdk` and
|
|
94
|
+
`@launchdarkly/node-server-sdk`. Both ESM import and CJS `require()` forms.
|
|
381
95
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
--bootstrap-exclude "src/provider/setup.ts"
|
|
387
|
-
# exits 1 if any direct LD evaluation calls remain outside the bootstrap file
|
|
388
|
-
```
|
|
389
|
-
|
|
390
|
-
### Full migration CI pipeline with SARIF annotations
|
|
391
|
-
|
|
392
|
-
```yaml
|
|
393
|
-
name: FlagLint
|
|
394
|
-
on: [pull_request]
|
|
395
|
-
|
|
396
|
-
jobs:
|
|
397
|
-
flaglint:
|
|
398
|
-
runs-on: ubuntu-latest
|
|
399
|
-
permissions:
|
|
400
|
-
security-events: write
|
|
401
|
-
contents: read
|
|
402
|
-
steps:
|
|
403
|
-
- uses: actions/checkout@v4
|
|
404
|
-
- uses: actions/setup-node@v4
|
|
405
|
-
with:
|
|
406
|
-
node-version: 20
|
|
407
|
-
|
|
408
|
-
- name: Inventory LaunchDarkly SDK usage
|
|
409
|
-
run: npx flaglint scan ./src --format html --output flaglint-inventory.html
|
|
410
|
-
continue-on-error: true
|
|
411
|
-
|
|
412
|
-
- name: Plan OpenFeature migration
|
|
413
|
-
run: npx flaglint migrate ./src --dry-run --output flaglint-migration.md
|
|
414
|
-
continue-on-error: true
|
|
415
|
-
|
|
416
|
-
- name: Validate direct LaunchDarkly policy
|
|
417
|
-
run: |
|
|
418
|
-
npx flaglint validate ./src \
|
|
419
|
-
--no-direct-launchdarkly \
|
|
420
|
-
--bootstrap-exclude "src/provider/setup.ts" \
|
|
421
|
-
--format sarif \
|
|
422
|
-
--output flaglint-validation.sarif
|
|
423
|
-
continue-on-error: true
|
|
424
|
-
|
|
425
|
-
- name: Upload to GitHub Code Scanning
|
|
426
|
-
uses: github/codeql-action/upload-sarif@v3
|
|
427
|
-
with:
|
|
428
|
-
sarif_file: flaglint-validation.sarif
|
|
429
|
-
```
|
|
96
|
+
`--apply` rewrites `boolVariation`, `stringVariation`, `numberVariation`, `jsonVariation`
|
|
97
|
+
where the flag key, fallback, and OpenFeature client binding are statically explicit.
|
|
98
|
+
Detail methods, dynamic keys, bulk calls, and unknown fallback types are reported for manual review.
|
|
99
|
+
Browser SDKs, React SDKs, and non-LaunchDarkly providers are outside current scope.
|
|
430
100
|
|
|
431
|
-
|
|
432
|
-
planning. `validate --no-direct-launchdarkly --format sarif` is for CI policy
|
|
433
|
-
annotations and enforcement. Code Scanning alerts show the exact file and line of
|
|
434
|
-
each direct LD call under the SARIF rule id `flaglint.direct-launchdarkly` —
|
|
435
|
-
reviewers see them in the PR without running anything locally.
|
|
101
|
+
Full coverage table: [Supported Scope](https://flaglint.dev/docs/reference/supported-scope)
|
|
436
102
|
|
|
437
103
|
---
|
|
438
104
|
|
|
439
|
-
##
|
|
105
|
+
## Provider setup (one-time, manual)
|
|
440
106
|
|
|
441
|
-
|
|
107
|
+
Before `--apply`, complete bootstrap setup once. Full instructions:
|
|
108
|
+
[OpenFeature Provider Setup](https://flaglint.dev/docs/integrations/openfeature-provider)
|
|
442
109
|
|
|
443
|
-
|
|
444
|
-
|
|
110
|
+
Key points:
|
|
111
|
+
- `new LaunchDarklyProvider(process.env.LD_SDK_KEY!)` — SDK key constructor
|
|
112
|
+
- Evaluation context accepts either `targetingKey` (OpenFeature-native) or an existing LaunchDarkly `key`
|
|
113
|
+
- **Do not remove LaunchDarkly packages** — the OpenFeature provider depends on them at runtime
|
|
445
114
|
|
|
446
115
|
---
|
|
447
116
|
|
|
448
|
-
##
|
|
449
|
-
|
|
450
|
-
See a realistic end-to-end migration walkthrough — multiple Node.js services,
|
|
451
|
-
mixed automatable and manual-review patterns, SARIF output, and CI enforcement:
|
|
452
|
-
|
|
453
|
-
**[View enterprise demo](./examples/enterprise-checkout-service/README.md)**
|
|
454
|
-
|
|
455
|
-
The demo shows scan inventory, exact dry-run preview, guarded apply,
|
|
456
|
-
migration-in-progress advisory findings, and a completed-state validation hard
|
|
457
|
-
gate.
|
|
458
|
-
|
|
459
|
-
---
|
|
460
|
-
|
|
461
|
-
## Security and trust
|
|
462
|
-
|
|
463
|
-
FlagLint runs entirely on your machine. No source code, flag keys, or file paths
|
|
464
|
-
are transmitted to any external service. The tool makes no outbound network
|
|
465
|
-
connections during a flag scan or migration. No LaunchDarkly SDK key or any
|
|
466
|
-
credentials are required.
|
|
467
|
-
|
|
468
|
-
`flaglint migrate --apply` refuses to write files on a dirty git working tree
|
|
469
|
-
(unless `--allow-dirty` is passed), requires a proven OpenFeature client binding
|
|
470
|
-
before touching a file, and verifies each source range against the original call
|
|
471
|
-
expression before rewriting.
|
|
472
|
-
|
|
473
|
-
The project release workflow is configured to publish through GitHub Actions
|
|
474
|
-
using npm Trusted Publishing/OIDC. The publish job uses Node 24 because npm
|
|
475
|
-
Trusted Publishing has stricter runtime requirements; FlagLint runtime support
|
|
476
|
-
remains Node.js >=20. npm-side Trusted Publisher configuration must be completed
|
|
477
|
-
before the first OIDC-based publication unless it has already been configured
|
|
478
|
-
and verified.
|
|
479
|
-
|
|
480
|
-
Core behavior is covered by automated tests executed in CI on supported Node
|
|
481
|
-
versions.
|
|
117
|
+
## Requirements
|
|
482
118
|
|
|
483
|
-
|
|
484
|
-
For a full trust and provenance statement, see [docs/trust.md](./docs/trust.md).
|
|
119
|
+
Node.js 20 or newer. No LaunchDarkly SDK key or credentials required for scan or migrate.
|
|
485
120
|
|
|
486
121
|
---
|
|
487
122
|
|
|
488
|
-
##
|
|
123
|
+
## Local analysis
|
|
489
124
|
|
|
490
|
-
|
|
125
|
+
FlagLint runs entirely on your machine. No source code, flag keys, or file paths are
|
|
126
|
+
transmitted to any external service. No outbound network connections during scan or migration.
|
|
491
127
|
|
|
492
128
|
---
|
|
493
129
|
|
|
494
|
-
##
|
|
130
|
+
## Links
|
|
495
131
|
|
|
496
|
-
|
|
132
|
+
[Security](./SECURITY.md) · [Contributing](./CONTRIBUTING.md) · [Changelog](./CHANGELOG.md) · [License](./LICENSE) · [Full docs](https://flaglint.dev/docs)
|
package/dist/bin/flaglint.js
CHANGED
|
@@ -200,12 +200,24 @@ function collectLDClients(ast) {
|
|
|
200
200
|
if (stmt.type === "VariableDeclaration") {
|
|
201
201
|
const varDecl = stmt;
|
|
202
202
|
for (const decl of varDecl.declarations) {
|
|
203
|
-
if (
|
|
203
|
+
if (!decl.init) continue;
|
|
204
204
|
const init = decl.init;
|
|
205
|
-
|
|
205
|
+
const isLDRequire = init.type === "CallExpression" && init.callee.type === "Identifier" && init.callee.name === "require" && init.arguments.length >= 1 && init.arguments[0].type === "Literal" && LD_NODE_SERVER_PACKAGES.has(
|
|
206
206
|
init.arguments[0].value
|
|
207
|
-
)
|
|
207
|
+
);
|
|
208
|
+
if (!isLDRequire) continue;
|
|
209
|
+
if (decl.id.type === "Identifier") {
|
|
208
210
|
ldNamespaces.add(decl.id.name);
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
if (decl.id.type === "ObjectPattern") {
|
|
214
|
+
for (const prop of decl.id.properties) {
|
|
215
|
+
if (prop.type !== "Property") continue;
|
|
216
|
+
const keyName = prop.key.type === "Identifier" ? prop.key.name : prop.key.type === "Literal" && typeof prop.key.value === "string" ? prop.key.value : void 0;
|
|
217
|
+
if (keyName === "init" && prop.value.type === "Identifier") {
|
|
218
|
+
ldInitFunctions.add(prop.value.name);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
209
221
|
}
|
|
210
222
|
}
|
|
211
223
|
}
|
|
@@ -751,7 +763,7 @@ function formatHTML(result, options) {
|
|
|
751
763
|
<div class="card"><div class="card-num orange">${manualCount}</div><div class="card-label">Manual Review (${manualPct}%)</div></div>
|
|
752
764
|
<div class="card"><div class="card-num blue">${detailBulkCount}</div><div class="card-label">Detail/Bulk Calls</div></div>` : "";
|
|
753
765
|
const title = options.title ? esc(options.title) : "FlagLint Scan Report";
|
|
754
|
-
const version = true ? "0.5.
|
|
766
|
+
const version = true ? "0.5.4" : "0.1.0";
|
|
755
767
|
return `<!DOCTYPE html>
|
|
756
768
|
<html lang="en">
|
|
757
769
|
<head>
|
|
@@ -1204,7 +1216,7 @@ function formatMigrationReport(analysis) {
|
|
|
1204
1216
|
unsupportedUnknownCount
|
|
1205
1217
|
} = analysis;
|
|
1206
1218
|
const date = (/* @__PURE__ */ new Date()).toLocaleDateString();
|
|
1207
|
-
const version = true ? "0.5.
|
|
1219
|
+
const version = true ? "0.5.4" : "0.1.0";
|
|
1208
1220
|
const lines = [];
|
|
1209
1221
|
lines.push("# OpenFeature Migration Inventory");
|
|
1210
1222
|
lines.push(`Generated by FlagLint v${version} on ${date}`);
|
|
@@ -1968,7 +1980,7 @@ Examples:
|
|
|
1968
1980
|
// src/cli.ts
|
|
1969
1981
|
function createCLI() {
|
|
1970
1982
|
const program2 = new Command();
|
|
1971
|
-
program2.name("flaglint").description("LaunchDarkly Node.js server SDK -> OpenFeature migration.").version("0.5.
|
|
1983
|
+
program2.name("flaglint").description("LaunchDarkly Node.js server SDK -> OpenFeature migration.").version("0.5.4", "-v, --version", "output the current version").addHelpText(
|
|
1972
1984
|
"after",
|
|
1973
1985
|
`
|
|
1974
1986
|
Examples:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flaglint",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.4",
|
|
4
4
|
"description": "LaunchDarkly Node.js server SDK -> OpenFeature migration.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -35,15 +35,18 @@
|
|
|
35
35
|
},
|
|
36
36
|
"scripts": {
|
|
37
37
|
"sync:www": "tsx scripts/sync-www.ts",
|
|
38
|
-
"build": "npm run sync:www && tsup",
|
|
38
|
+
"build:cli": "npm run sync:www && tsup",
|
|
39
|
+
"build:docs": "ASTRO_TELEMETRY_DISABLED=1 astro build",
|
|
40
|
+
"build": "npm run build:cli && npm run build:docs",
|
|
41
|
+
"dev:docs": "ASTRO_TELEMETRY_DISABLED=1 astro dev",
|
|
39
42
|
"dev": "tsup --watch",
|
|
40
43
|
"typecheck": "tsc --noEmit",
|
|
41
44
|
"typecheck:agent": "tsc --project tsconfig.agent.json",
|
|
42
|
-
"pretest": "npm run build",
|
|
45
|
+
"pretest": "npm run build:cli",
|
|
43
46
|
"test": "vitest",
|
|
44
|
-
"pretest:run": "npm run build",
|
|
47
|
+
"pretest:run": "npm run build:cli",
|
|
45
48
|
"test:run": "vitest run",
|
|
46
|
-
"pretest:coverage": "npm run build",
|
|
49
|
+
"pretest:coverage": "npm run build:cli",
|
|
47
50
|
"test:coverage": "vitest run --coverage",
|
|
48
51
|
"new-branch": "tsx scripts/new-branch.ts",
|
|
49
52
|
"release:patch": "tsx scripts/release.ts patch",
|
|
@@ -62,9 +65,11 @@
|
|
|
62
65
|
"zod": "^3.23.8"
|
|
63
66
|
},
|
|
64
67
|
"devDependencies": {
|
|
68
|
+
"@astrojs/starlight": "^0.39.2",
|
|
65
69
|
"@types/micromatch": "^4.0.10",
|
|
66
70
|
"@types/node": "^22.0.0",
|
|
67
71
|
"@vitest/coverage-v8": "^4.1.6",
|
|
72
|
+
"astro": "^6.3.8",
|
|
68
73
|
"clipboardy": "^4.0.0",
|
|
69
74
|
"tsup": "^8.2.4",
|
|
70
75
|
"tsx": "^4.19.0",
|