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 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
- plans, and prevents new vendor-coupled flag access from entering your codebase.
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
- **LaunchDarkly remains your provider. OpenFeature becomes the evaluation API your application code calls.**
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
- Markdown report excerpt:
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
- ### JSON output (`--format json`)
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
- npm install -g flaglint
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
- ## Provider setup (one-time manual step)
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
- **Do not remove any LaunchDarkly packages.** LaunchDarkly remains your feature flag provider;
265
- `@openfeature/server-sdk` becomes the evaluation interface your application code calls.
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
- ## Using an existing shared OpenFeature client
70
+ ## Before After (real output from enterprise demo)
270
71
 
271
- If your platform team already exports an OpenFeature client from a shared internal module
272
- (e.g. `platform/feature-flags.ts`), FlagLint can use that import as the proven binding for
273
- `--apply` — no local `OpenFeature.getClient()` call is required in every file.
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
- Declare the allowed import in `.flaglintrc`:
78
+ - return ldClient.stringVariation("payment-provider", ctx, "stripe");
79
+ + return openFeatureClient.getStringValue("payment-provider", "stripe", ctx);
276
80
 
277
- ```json
278
- {
279
- "openFeatureClientBindings": [
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
- ## CI Integration
91
+ ## Supported scope
379
92
 
380
- ### Enforce OpenFeature migration: block PRs with direct LD calls
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
- ```yaml
383
- - name: Validate no direct LaunchDarkly evaluations
384
- run: |
385
- npx flaglint validate --no-direct-launchdarkly \
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
- `scan` is for inventory and reporting. `migrate --dry-run` is for migration
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
- ## Precision
105
+ ## Provider setup (one-time, manual)
440
106
 
441
- Validated against 120 deterministic benchmark cases within the supported LaunchDarkly Node.js server-side SDK scope. 100% precision and recall are limited to those 120 tested cases and to the Node.js server-side SDK call patterns explicitly listed in the Supported API matrix above.
107
+ Before `--apply`, complete bootstrap setup once. Full instructions:
108
+ [OpenFeature Provider Setup](https://flaglint.dev/docs/integrations/openfeature-provider)
442
109
 
443
- Detection is AST-based, not regex: client binding patterns, named `init` import aliases,
444
- OpenFeature import aliases, and CJS require forms are resolved before matching.
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
- ## Enterprise demo
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
- For vulnerability reports, see [SECURITY.md](./SECURITY.md).
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
- ## Contributing
123
+ ## Local analysis
489
124
 
490
- See [CONTRIBUTING.md](./CONTRIBUTING.md).
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
- ## License
130
+ ## Links
495
131
 
496
- MIT see [LICENSE](./LICENSE).
132
+ [Security](./SECURITY.md) · [Contributing](./CONTRIBUTING.md) · [Changelog](./CHANGELOG.md) · [License](./LICENSE) · [Full docs](https://flaglint.dev/docs)
@@ -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 (decl.id.type !== "Identifier" || !decl.init) continue;
203
+ if (!decl.init) continue;
204
204
  const init = decl.init;
205
- if (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(
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.2" : "0.1.0";
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.2" : "0.1.0";
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.2", "-v, --version", "output the current version").addHelpText(
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.2",
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",