flaglint 0.4.1 → 0.5.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 CHANGED
@@ -5,7 +5,105 @@ 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
- ## [Unreleased]
8
+ ## Unreleased
9
+
10
+ ## [0.5.1] - 2026-05-27
11
+
12
+ ### Fixed
13
+
14
+ - Corrected `migrate --dry-run` messaging when all previewed diffs use proven
15
+ OpenFeature client bindings. Dry-run output no longer claims placeholder
16
+ provider/client setup is required when configured imported bindings, aliases,
17
+ or local `OpenFeature.getClient()` bindings are already present.
18
+ - Clarified README and docs scope wording for both supported LaunchDarkly Node.js
19
+ server SDK package names: current `@launchdarkly/node-server-sdk` and legacy
20
+ `launchdarkly-node-server-sdk`.
21
+ - Corrected OpenTelemetry feature-flag semantic-convention guidance to use the
22
+ current `feature_flag.evaluation` event model and current attribute names.
23
+ - Corrected homepage release-state and lower CTA messaging now that `flaglint@0.5.0`
24
+ is published.
25
+ - Narrowed broad flag-debt wording where it could imply comprehensive unused-flag
26
+ lifecycle analysis rather than direct SDK coupling and migration review work.
27
+
28
+ ## [0.5.0] - 2026-05-26
29
+
30
+ ### Added
31
+
32
+ - **Configured imported OpenFeature client bindings** (`openFeatureClientBindings` in `.flaglintrc`):
33
+ Declare shared OpenFeature client exports by import name and glob module pattern.
34
+ `migrate --apply` recognises these imports as proven bindings without requiring every
35
+ service file to call `OpenFeature.getClient()` locally.
36
+ ```json
37
+ {
38
+ "openFeatureClientBindings": [
39
+ { "importName": "openFeatureClient", "modulePatterns": ["**/platform/feature-flags"] }
40
+ ]
41
+ }
42
+ ```
43
+
44
+ - **TypeScript ESM `.js` import compatibility**: `modulePatterns` globs now match TypeScript
45
+ source imports that carry a `.js` extension at the specifier level
46
+ (`import { openFeatureClient } from "../platform/feature-flags.js"`) even when the
47
+ configured pattern omits the extension (`**/platform/feature-flags`).
48
+
49
+ - **`validate --format sarif`**: `flaglint validate --no-direct-launchdarkly --format sarif
50
+ --output flaglint.sarif` emits SARIF 2.1.0 with rule id `flaglint.direct-launchdarkly`
51
+ and level `error`. Designed for GitHub Code Scanning upload — each direct LaunchDarkly
52
+ evaluation call produces a PR annotation. Zero violations produces a valid SARIF document
53
+ that GitHub Code Scanning interprets as "all clear".
54
+
55
+ - **Enterprise HTML audit report**: `flaglint scan --format html` now includes an Executive
56
+ Summary (total call-sites, unique flags, auto-migratable vs. manual-review breakdown),
57
+ Findings by Directory table, Recommended Next Steps workflow, and a Copy Markdown Summary
58
+ clipboard button.
59
+
60
+ - **Enterprise OpenFeature migration demo** (`examples/enterprise-checkout-service/`): end-to-end
61
+ walkthrough across five Node.js services (checkout, pricing, analytics, product, flags-wrapper).
62
+ Includes `before/`/`after/` snapshots, `after-complete/` (fully migrated, passes hard gate),
63
+ generated reports, `.flaglintrc` config, and a sample GitHub Actions CI workflow.
64
+
65
+ - **Docs site** (`www/docs/`): nine documentation pages covering getting started, all three
66
+ commands, supported scope, OpenFeature provider setup, CI/GitHub Actions integration,
67
+ OpenTelemetry observability guidance, safety model, and the enterprise demo.
68
+
69
+ - **Enterprise trust documentation**: `SECURITY.md`, `CONTRIBUTING.md`, and `CODE_OF_CONDUCT.md`
70
+ with SARIF rule-ID reference for the `flaglint.direct-launchdarkly` policy rule.
71
+
72
+ - **OpenFeature + OpenTelemetry observability guidance** (`www/docs/opentelemetry.html`):
73
+ documents how to instrument OpenFeature flag evaluations with OpenTelemetry using the
74
+ OpenFeature hooks API. FlagLint does not emit runtime telemetry; this page explains the
75
+ complementary integration pattern.
76
+
77
+ ### Fixed
78
+
79
+ - **Deterministic test execution from clean checkout**: `vitest` configuration no longer
80
+ relies on `process.env.INIT_CWD` for test file discovery, ensuring the test suite
81
+ is reproducible on first-time `npm ci && npm test` runs.
82
+
83
+ ### Changed
84
+
85
+ - Reposition README and homepage messaging around standardizing LaunchDarkly usage on
86
+ OpenFeature while keeping LaunchDarkly as the provider.
87
+ - Document the focused automation scope: LaunchDarkly Node.js server-side evaluation calls
88
+ in TypeScript and JavaScript, with dynamic keys, detail evaluations, bulk calls, browser
89
+ SDKs, React usage, and ambiguous patterns reported for manual review.
90
+ - Align supported runtime documentation and package metadata to Node.js `>=20`.
91
+ Resolves the Node.js engine metadata mismatch in `flaglint@0.4.1` (published as `>=22`).
92
+
93
+ ### Security
94
+
95
+ - Add Node.js 20/22 CI coverage, CodeQL analysis, Dependabot configuration, and vulnerability
96
+ reporting instructions.
97
+ - Update npm release workflow for Trusted Publishing/OIDC without long-lived npm publish tokens.
98
+
99
+ ### Scope boundaries (non-claims)
100
+
101
+ The following are explicitly out of scope for this release:
102
+ - LaunchDarkly replacement — LaunchDarkly remains the feature flag provider throughout.
103
+ - Automatic provider/bootstrap setup — `migrate --apply` never generates bootstrap files.
104
+ - Flag deletion or billing reduction — FlagLint does not evaluate live flag values.
105
+ - Built-in runtime OpenTelemetry instrumentation — see `www/docs/opentelemetry.html` for
106
+ the complementary integration pattern using OpenFeature hooks.
9
107
 
10
108
  ## [0.4.1] - 2026-05-25
11
109
 
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  </p>
4
4
 
5
5
  <p align="center">
6
- <strong>LaunchDarkly Node.js server SDK -> OpenFeature migration</strong>
6
+ <strong>Standardize LaunchDarkly usage on OpenFeature.</strong>
7
7
  </p>
8
8
 
9
9
  <p align="center">
@@ -21,21 +21,19 @@
21
21
  </a>
22
22
  </p>
23
23
 
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.
30
-
31
24
  # FlagLint
32
25
 
33
- FlagLint inventories direct LaunchDarkly Node.js server SDK calls in your TypeScript/JavaScript
34
- codebase, generates reviewable OpenFeature migration diffs, applies only guarded transformations,
35
- and enforces migration state in CI.
26
+ Standardize LaunchDarkly usage on OpenFeature.
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.
36
30
 
37
31
  **LaunchDarkly remains your provider. OpenFeature becomes the evaluation API your application code calls.**
38
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
+
39
37
  ---
40
38
 
41
39
  ## Workflow
@@ -43,7 +41,7 @@ and enforces migration state in CI.
43
41
  | Step | Command | Purpose |
44
42
  |------|---------|---------|
45
43
  | 1 | `flaglint scan` | AST inventory of every direct LD Node server SDK call |
46
- | 2 | `flaglint migrate --dry-run` | Reviewable before/after diffs with provider setup guidance |
44
+ | 2 | `flaglint migrate --dry-run` | Reviewable before/after diffs; provider setup guidance appears when needed |
47
45
  | 3 | `flaglint migrate --apply` | Apply only guarded, provably automatable transformations |
48
46
  | 4 | `flaglint validate --no-direct-launchdarkly` | CI gate: exit 1 if direct LD calls remain |
49
47
 
@@ -98,6 +96,8 @@ npm install -g flaglint
98
96
  npx flaglint
99
97
  ```
100
98
 
99
+ Requires Node.js 20 or newer. CI validates FlagLint on Node.js 20 and 22.
100
+
101
101
  ---
102
102
 
103
103
  ## Commands
@@ -141,7 +141,7 @@ flaglint migrate --exclude-tests # skip test and spec files
141
141
  | Option | Default | Description |
142
142
  |--------|---------|-------------|
143
143
  | `--output` | `MIGRATION.md` | Write migration plan to file |
144
- | `--dry-run` | — | Print reviewable diffs to stdout; includes provider setup guidance |
144
+ | `--dry-run` | — | Print reviewable diffs to stdout; includes provider setup guidance when a diff needs it |
145
145
  | `--apply` | — | Apply automatable transformations in-place (requires clean git tree) |
146
146
  | `--allow-dirty` | — | Override dirty-tree guard for `--apply` |
147
147
  | `--config` | auto-detect | Path to a config file |
@@ -149,8 +149,13 @@ flaglint migrate --exclude-tests # skip test and spec files
149
149
 
150
150
  **`--apply` safety contracts:**
151
151
  - Refuses on a dirty git working tree unless `--allow-dirty`
152
- - Skips any file that does not already contain a proven `openFeatureClient` binding
153
- (`openFeatureClient = OpenFeature.getClient()` from `@openfeature/server-sdk`)
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
154
159
  - Never touches detail methods, dynamic keys, unknown fallbacks, or bulk calls
155
160
  - Preserves `await` and original call arguments exactly
156
161
  - Idempotent: re-running with the same analysis has no effect
@@ -198,9 +203,21 @@ Run `flaglint migrate --dry-run` to review the migration plan.
198
203
 
199
204
  ---
200
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
+
201
217
  ## Supported API matrix
202
218
 
203
- **Scope: LaunchDarkly Node.js server-side SDK** (`launchdarkly-node-server-sdk`).
219
+ **Scope: LaunchDarkly Node.js server-side SDK evaluation calls from
220
+ `@launchdarkly/node-server-sdk` and legacy `launchdarkly-node-server-sdk` imports.**
204
221
 
205
222
  | LaunchDarkly call | Automatable | OpenFeature equivalent |
206
223
  |---|---|---|
@@ -249,6 +266,62 @@ export const openFeatureClient = OpenFeature.getClient();
249
266
 
250
267
  ---
251
268
 
269
+ ## Using an existing shared OpenFeature client
270
+
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.
274
+
275
+ Declare the allowed import in `.flaglintrc`:
276
+
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
+
252
325
  ## Example transformation
253
326
 
254
327
  **Before — direct LaunchDarkly Node.js server SDK:**
@@ -260,13 +333,16 @@ const timeout = await ldClient.numberVariation("timeout-ms", { key: user.id },
260
333
 
261
334
  **After — OpenFeature via LaunchDarkly provider:**
262
335
  ```typescript
263
- const enabled = await openFeatureClient.getBooleanValue("checkout-v2", false, { targetingKey: user.id });
264
- const theme = await openFeatureClient.getStringValue("color-theme", "light", { targetingKey: user.id });
265
- const timeout = await openFeatureClient.getNumberValue("timeout-ms", 5000, { targetingKey: user.id });
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 });
266
339
  ```
267
340
 
268
341
  Flag key, fallback value, `await`, and evaluation context are preserved exactly.
269
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.
270
346
 
271
347
  ---
272
348
 
@@ -291,6 +367,7 @@ Create `.flaglintrc`, `.flaglintrc.json`, or `flaglint.config.json` in your proj
291
367
  | `provider` | `string` | `"launchdarkly"` | Feature flag provider |
292
368
  | `minFileCount` | `number` | `0` | Opt-in staleness heuristic. When set above 0, a flag is a staleness candidate if it appears in ≤ N files |
293
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). |
294
371
  | `reportTitle` | `string` | — | Custom title for generated reports |
295
372
  | `outputDir` | `string` | `"."` | Default output directory |
296
373
 
@@ -328,22 +405,34 @@ jobs:
328
405
  with:
329
406
  node-version: 20
330
407
 
331
- - name: Scan for LaunchDarkly SDK usage
332
- run: npx flaglint scan --format sarif --output flaglint.sarif
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
333
423
  continue-on-error: true
334
424
 
335
425
  - name: Upload to GitHub Code Scanning
336
426
  uses: github/codeql-action/upload-sarif@v3
337
427
  with:
338
- sarif_file: flaglint.sarif
339
-
340
- - name: Enforce OpenFeature migration
341
- run: |
342
- npx flaglint validate --no-direct-launchdarkly \
343
- --bootstrap-exclude "src/provider/setup.ts"
428
+ sarif_file: flaglint-validation.sarif
344
429
  ```
345
430
 
346
- Code Scanning alerts show the exact file and line of each direct LD call — reviewers see them in the PR without running anything locally.
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.
347
436
 
348
437
  ---
349
438
 
@@ -351,8 +440,48 @@ Code Scanning alerts show the exact file and line of each direct LD call — rev
351
440
 
352
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.
353
442
 
354
- Detection is AST-based, not regex: client binding patterns, import aliases, CJS require forms,
355
- and custom wrappers are all resolved before matching.
443
+ Detection is AST-based, not regex: client binding patterns, import aliases, and CJS require
444
+ forms are resolved before matching.
445
+
446
+ ---
447
+
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.
482
+
483
+ For vulnerability reports, see [SECURITY.md](./SECURITY.md).
484
+ For a full trust and provenance statement, see [docs/trust.md](./docs/trust.md).
356
485
 
357
486
  ---
358
487
 
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ ApplyError,
4
+ applyMigration,
5
+ getOpenFeatureClientBindingName,
6
+ hasOpenFeatureClientBinding
7
+ } from "./chunk-MJLXM6GZ.js";
8
+ export {
9
+ ApplyError,
10
+ applyMigration,
11
+ getOpenFeatureClientBindingName,
12
+ hasOpenFeatureClientBinding
13
+ };