flaglint 0.2.2 → 0.3.0

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,12 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
- ## [0.2.2] - 2026-05-23
8
+ ## [0.3.0] - 2026-05-23
9
+
10
+ ### Added
11
+
12
+ - **SARIF output**: `flaglint scan --format sarif --output flaglint.sarif` now emits SARIF 2.1.0 for GitHub Code Scanning / PR annotations.
13
+ - **Persistent scan metadata**: `ScanResult` now includes `scannedAt` and `scanRoot`, giving JSON/SARIF/HTML reports a stable scan timestamp and source-root context.
9
14
 
10
15
  ### Fixed
11
16
 
@@ -13,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
13
18
  - **Typed scan warnings**: `ScanResult.warnings` is now a typed `ScanWarning` union (`read-failure` | `parse-failure`) instead of opaque strings, preserving structured data at the domain boundary.
14
19
  - **StalenessEvaluator wired**: The `StalenessEvaluator` interface now has a call site in `scan()` — pass an `evaluator` to inject API-based staleness signals without touching core scanner logic.
15
20
  - **ScanConfig boundary**: `scan()` now accepts `ScanConfig` (scan-relevant fields only) rather than the full `FlagLintConfig`, decoupling the scanner from CLI output concerns (`reportTitle`, `outputDir`).
21
+ - **Report count consistency**: Markdown and HTML stale candidate counts now exclude wildcard (`*`) usages, matching the CLI summary.
16
22
 
17
23
  ## [0.2.1] - 2026-05-23
18
24
 
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  </p>
4
4
 
5
5
  <p align="center">
6
- <strong>Find stale feature flags. Detect flag debt. Plan your OpenFeature migration.</strong>
6
+ <strong>Your LaunchDarkly codebase has flag debt. FlagLint tells you exactly what and where.</strong>
7
7
  </p>
8
8
 
9
9
  <p align="center">
@@ -24,11 +24,8 @@
24
24
 
25
25
  # FlagLint
26
26
 
27
- **Find stale feature flags. Detect flag debt. Plan your OpenFeature migration.**
28
-
29
- [![CI](https://github.com/flaglint/flaglint/actions/workflows/ci.yml/badge.svg)](https://github.com/flaglint/flaglint/actions/workflows/ci.yml)
30
- [![npm version](https://img.shields.io/npm/v/flaglint.svg)](https://www.npmjs.com/package/flaglint)
31
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
27
+ Find zombie flags. Eliminate flag debt. Generate your OpenFeature
28
+ migration plan.
32
29
 
33
30
  ---
34
31
 
@@ -36,6 +33,8 @@
36
33
 
37
34
  LaunchDarkly flags accumulate. Teams add them, forget to clean them up, and gradually build flag debt — dead code paths controlled by flags nobody manages. When you finally want to migrate to OpenFeature, you don't even know what you have.
38
35
 
36
+ Like Uber's Piranha — for any JS/TS codebase.
37
+
39
38
  **FlagLint fixes this.** It scans your codebase, maps every flag usage, identifies stale candidates, and generates a step-by-step OpenFeature migration plan.
40
39
 
41
40
  ---
@@ -46,6 +45,49 @@ LaunchDarkly flags accumulate. Teams add them, forget to clean them up, and grad
46
45
  npx flaglint scan
47
46
  ```
48
47
 
48
+ Example output:
49
+
50
+ ```text
51
+ ✓ 15 flag usages found across 6 unique flags (48ms)
52
+ ⚠ 5 potentially stale flag(s) — review recommended
53
+ ℹ 1 dynamic flag key(s) require manual review
54
+ ```
55
+
56
+ Markdown report excerpt:
57
+
58
+ ```markdown
59
+ ## Flag Inventory
60
+ | Flag Key | Usages | Files | Call Types | Status |
61
+ |----------|--------|-------|------------|--------|
62
+ | show-banner | 1 | 1 | variation | ✓ Active |
63
+ | old-checkout | 1 | 1 | variation | ⚠ Stale |
64
+ | temp-debug-mode | 1 | 1 | variation | ⚠ Stale |
65
+
66
+ ## ⚠ Stale Flag Candidates
67
+ | Flag Key | Reason | Location |
68
+ |----------|--------|----------|
69
+ | old-checkout | Contains "old" in key | ld-stale.ts:1 |
70
+ ```
71
+
72
+ ### JSON output (`--format json`)
73
+
74
+ Pipe-friendly. Every usage includes file, line, call type,
75
+ and structured staleness signals:
76
+
77
+ ```json
78
+ {
79
+ "flagKey": "old-checkout",
80
+ "isDynamic": false,
81
+ "file": "src/components/Checkout.tsx",
82
+ "line": 14,
83
+ "callType": "variation",
84
+ "stalenessSignals": [
85
+ { "source": "keyword", "keyword": "old" },
86
+ { "source": "minFileCount", "fileCount": 1, "threshold": 1 }
87
+ ]
88
+ }
89
+ ```
90
+
49
91
  ---
50
92
 
51
93
  ## Installation
@@ -68,13 +110,15 @@ Scans a directory for LaunchDarkly SDK usage.
68
110
  flaglint scan ./src
69
111
  flaglint scan --format json --output report.json
70
112
  flaglint scan --format html --output report.html
113
+ flaglint scan --format sarif --output flaglint.sarif
71
114
  ```
72
115
 
73
116
  | Option | Default | Description |
74
117
  |--------|---------|-------------|
75
- | `--format` | `markdown` | Output format: `json`, `markdown`, `html` |
118
+ | `--format` | `markdown` | Output format: `json`, `markdown`, `html`, `sarif` |
76
119
  | `--output` | stdout | Write report to file |
77
- | `--config` | auto-detect | Path to `.flaglintrc` |
120
+ | `--config` | auto-detect | Path to a config file |
121
+ | `--exclude-tests` | — | Exclude test files from scan results |
78
122
 
79
123
  Exit code `0` when no stale flags found, `1` when stale flags exist — enabling CI blocking.
80
124
 
@@ -94,13 +138,13 @@ flaglint migrate --output MIGRATION.md
94
138
  |--------|---------|-------------|
95
139
  | `--output` | `MIGRATION.md` | Write migration plan to file |
96
140
  | `--dry-run` | — | Print plan to stdout, do not write file |
97
- | `--config` | auto-detect | Path to `.flaglintrc` |
141
+ | `--config` | auto-detect | Path to a config file |
98
142
 
99
143
  ---
100
144
 
101
145
  ## Configuration
102
146
 
103
- Create `.flaglintrc` in your project root:
147
+ Create `.flaglintrc`, `.flaglintrc.json`, or `flaglint.config.json` in your project root:
104
148
 
105
149
  ```json
106
150
  {
@@ -122,18 +166,53 @@ Create `.flaglintrc` in your project root:
122
166
  | `reportTitle` | `string` | — | Custom title for generated reports |
123
167
  | `outputDir` | `string` | `"."` | Default output directory |
124
168
 
125
- FlagLint searches for config in this order: `--config` flag → `.flaglintrc` → `.flaglintrc.json` → `flaglint.config.json`.
169
+ FlagLint searches for config in this order: `--config` path → `.flaglintrc` → `.flaglintrc.json` → `flaglint.config.json`.
126
170
 
127
171
  ---
128
172
 
129
173
  ## CI Integration
130
174
 
175
+ ### Basic — block PRs on stale flags
176
+
131
177
  ```yaml
132
178
  - name: Check for stale flags
133
179
  run: npx flaglint scan --format json --output flaglint-report.json
134
180
  # exits 1 if stale flags found, blocking the PR
135
181
  ```
136
182
 
183
+ ### GitHub PR annotations via SARIF
184
+
185
+ Stale flags appear as warnings directly in the PR diff —
186
+ no dashboard, no separate tool.
187
+
188
+ ```yaml
189
+ name: FlagLint
190
+ on: [pull_request]
191
+
192
+ jobs:
193
+ flaglint:
194
+ runs-on: ubuntu-latest
195
+ permissions:
196
+ security-events: write
197
+ contents: read
198
+ steps:
199
+ - uses: actions/checkout@v4
200
+ - uses: actions/setup-node@v4
201
+ with:
202
+ node-version: 20
203
+ - name: Scan for flag debt
204
+ run: npx flaglint scan --format sarif --output flaglint.sarif
205
+ continue-on-error: true
206
+ - name: Upload to GitHub Code Scanning
207
+ uses: github/codeql-action/upload-sarif@v3
208
+ with:
209
+ sarif_file: flaglint.sarif
210
+ ```
211
+
212
+ Stale flags show up as Code Scanning alerts on the exact file
213
+ and line where the flag is used — reviewers see them in the PR
214
+ without running anything locally.
215
+
137
216
  ---
138
217
 
139
218
  ## What FlagLint detects
@@ -142,9 +221,10 @@ FlagLint searches for config in this order: `--config` flag → `.flaglintrc`
142
221
  - `ldClient.allFlags()`
143
222
  - `useFlags()`, `useLDClient()` React hooks
144
223
  - `<LDProvider>` and `withLDConsumer()` patterns
224
+ - Custom wrapper calls such as `flagPredicate("my-flag", false)` when configured with `wrappers`
145
225
  - Dynamic flag keys (runtime-determined, flagged for manual review)
146
226
 
147
- All detections include the **file path**, **line number**, **call type**, and a **stale heuristic** based on key names and file locations.
227
+ All detections include the **file path**, **line number**, **call type**, and staleness signals based on key names, file locations, and low file counts.
148
228
 
149
229
  ---
150
230
 
@@ -169,6 +249,14 @@ See [CONTRIBUTING.md](./CONTRIBUTING.md).
169
249
 
170
250
  ---
171
251
 
252
+ ## Free flag debt audit
253
+
254
+ Running this on a real codebase?
255
+ [Book a free 30-minute audit →](https://flaglint.dev#waitlist)
256
+ I'll run FlagLint on your repo and walk you through the results.
257
+
258
+ ---
259
+
172
260
  ## License
173
261
 
174
262
  MIT — see [LICENSE](./LICENSE).
@@ -6,7 +6,7 @@ import { Command } from "commander";
6
6
  // src/commands/scan.ts
7
7
  import { writeFile } from "fs/promises";
8
8
  import { stat } from "fs/promises";
9
- import { resolve as resolve2 } from "path";
9
+ import { resolve as resolve4 } from "path";
10
10
  import chalk from "chalk";
11
11
  import ora from "ora";
12
12
 
@@ -190,6 +190,7 @@ function detectUsages(ast, filePath, wrappers) {
190
190
  }
191
191
  async function scan(source, config, onProgress, evaluator) {
192
192
  const start = Date.now();
193
+ const scannedAt = (/* @__PURE__ */ new Date()).toISOString();
193
194
  for (const pattern of config.include) {
194
195
  if (pattern.startsWith("/") || pattern.startsWith("..")) {
195
196
  throw new Error(
@@ -270,6 +271,8 @@ async function scan(source, config, onProgress, evaluator) {
270
271
  )
271
272
  ];
272
273
  return {
274
+ scannedAt,
275
+ scanRoot: source.root ?? ".",
273
276
  scannedFiles,
274
277
  totalUsages: allUsages.length,
275
278
  uniqueFlags,
@@ -282,12 +285,14 @@ async function scan(source, config, onProgress, evaluator) {
282
285
  // src/scanner/local-source.ts
283
286
  import fg from "fast-glob";
284
287
  import { readFile } from "fs/promises";
285
- import { join, relative } from "path";
288
+ import { join, relative, resolve } from "path";
286
289
  var LocalFileSource = class {
287
290
  constructor(dir) {
288
291
  this.dir = dir;
292
+ this.root = resolve(dir);
289
293
  }
290
294
  dir;
295
+ root;
291
296
  async listFiles(include, exclude) {
292
297
  const files = await fg(include, {
293
298
  cwd: this.dir,
@@ -302,6 +307,10 @@ var LocalFileSource = class {
302
307
  }
303
308
  };
304
309
 
310
+ // src/reporter/index.ts
311
+ import { resolve as resolve2 } from "path";
312
+ import { pathToFileURL } from "url";
313
+
305
314
  // src/types.ts
306
315
  var isStale = (u) => u.stalenessSignals.length > 0;
307
316
 
@@ -331,11 +340,11 @@ function sortedFlagEntries(map) {
331
340
  }
332
341
  function formatMarkdown(result, options) {
333
342
  const { scannedFiles, totalUsages, uniqueFlags, usages, scanDurationMs } = result;
334
- const staleUsages = usages.filter(isStale);
343
+ const staleUsages = usages.filter((u) => isStale(u) && !u.isDynamic && u.flagKey !== "*");
335
344
  const dynamicUsages = usages.filter((u) => u.isDynamic);
336
345
  const flagMap = buildFlagMap(usages);
337
346
  const sorted = sortedFlagEntries(flagMap);
338
- const staleFlags = sorted.filter(([, d]) => d.isStale);
347
+ const staleFlags = sorted.filter(([key, d]) => key !== "*" && d.isStale);
339
348
  const lines = [];
340
349
  lines.push("# FlagLint Scan Report");
341
350
  if (options.title) lines.push("", options.title);
@@ -391,13 +400,128 @@ function formatMarkdown(result, options) {
391
400
  return lines.join("\n");
392
401
  }
393
402
  function formatJSON(result) {
394
- return JSON.stringify({ generatedAt: (/* @__PURE__ */ new Date()).toISOString(), ...result }, null, 2);
403
+ return JSON.stringify({ generatedAt: result.scannedAt, ...result }, null, 2);
404
+ }
405
+ function signalRuleId(usage) {
406
+ const signal = usage.stalenessSignals[0];
407
+ if (!signal) return "flaglint.stale";
408
+ return `flaglint.${signal.source}`;
409
+ }
410
+ function signalMessage(usage) {
411
+ const reasons = usage.stalenessSignals.map((signal) => {
412
+ switch (signal.source) {
413
+ case "keyword":
414
+ return `keyword "${signal.keyword}"`;
415
+ case "path":
416
+ return `path pattern "${signal.pattern}"`;
417
+ case "minFileCount":
418
+ return `file count ${signal.fileCount} <= ${signal.threshold}`;
419
+ }
420
+ });
421
+ return reasons.length > 0 ? reasons.join(", ") : "staleness signal";
422
+ }
423
+ function sarifUri(file) {
424
+ return file.split(/[\\/]/).join("/");
425
+ }
426
+ function sarifRootUri(scanRoot) {
427
+ const uri = pathToFileURL(resolve2(scanRoot)).href;
428
+ return uri.endsWith("/") ? uri : `${uri}/`;
429
+ }
430
+ function formatSARIF(result) {
431
+ const staleUsages = result.usages.filter((u) => isStale(u) && !u.isDynamic && u.flagKey !== "*");
432
+ const rules = [
433
+ {
434
+ id: "flaglint.keyword",
435
+ name: "Stale flag keyword",
436
+ shortDescription: { text: "Flag key contains a stale keyword" },
437
+ helpUri: "https://github.com/flaglint/flaglint#what-flaglint-detects"
438
+ },
439
+ {
440
+ id: "flaglint.path",
441
+ name: "Stale flag path",
442
+ shortDescription: { text: "Flag usage appears in a stale path" },
443
+ helpUri: "https://github.com/flaglint/flaglint#what-flaglint-detects"
444
+ },
445
+ {
446
+ id: "flaglint.minFileCount",
447
+ name: "Low file count",
448
+ shortDescription: { text: "Flag appears in too few files" },
449
+ helpUri: "https://github.com/flaglint/flaglint#configuration"
450
+ }
451
+ ];
452
+ return JSON.stringify(
453
+ {
454
+ $schema: "https://json.schemastore.org/sarif-2.1.0.json",
455
+ version: "2.1.0",
456
+ runs: [
457
+ {
458
+ tool: {
459
+ driver: {
460
+ name: "FlagLint",
461
+ informationUri: "https://github.com/flaglint/flaglint",
462
+ rules
463
+ }
464
+ },
465
+ invocations: [
466
+ {
467
+ executionSuccessful: true,
468
+ startTimeUtc: result.scannedAt,
469
+ properties: {
470
+ scannedFiles: result.scannedFiles,
471
+ totalUsages: result.totalUsages,
472
+ uniqueFlags: result.uniqueFlags.length,
473
+ scanDurationMs: result.scanDurationMs
474
+ }
475
+ }
476
+ ],
477
+ originalUriBaseIds: {
478
+ "%SRCROOT%": {
479
+ uri: sarifRootUri(result.scanRoot)
480
+ }
481
+ },
482
+ results: staleUsages.map((usage) => ({
483
+ ruleId: signalRuleId(usage),
484
+ level: "warning",
485
+ message: {
486
+ text: `Potentially stale feature flag "${usage.flagKey}" detected: ${signalMessage(usage)}.`
487
+ },
488
+ locations: [
489
+ {
490
+ physicalLocation: {
491
+ artifactLocation: {
492
+ uri: sarifUri(usage.file),
493
+ uriBaseId: "%SRCROOT%"
494
+ },
495
+ region: {
496
+ startLine: Math.max(usage.line, 1),
497
+ startColumn: Math.max(usage.column + 1, 1)
498
+ }
499
+ }
500
+ }
501
+ ],
502
+ partialFingerprints: {
503
+ "flagKey/v1": usage.flagKey
504
+ },
505
+ properties: {
506
+ flagKey: usage.flagKey,
507
+ callType: usage.callType,
508
+ stalenessSignals: usage.stalenessSignals
509
+ }
510
+ }))
511
+ }
512
+ ]
513
+ },
514
+ null,
515
+ 2
516
+ );
395
517
  }
396
518
  function formatHTML(result, options) {
397
519
  const { scannedFiles, totalUsages, uniqueFlags, usages, scanDurationMs } = result;
398
- const staleCount = new Set(usages.filter(isStale).map((u) => u.flagKey)).size;
520
+ const staleCount = new Set(
521
+ usages.filter((u) => isStale(u) && !u.isDynamic && u.flagKey !== "*").map((u) => u.flagKey)
522
+ ).size;
399
523
  const dynamicCount = usages.filter((u) => u.isDynamic).length;
400
- const date = (/* @__PURE__ */ new Date()).toLocaleString();
524
+ const date = new Date(result.scannedAt).toLocaleString();
401
525
  const flagMap = buildFlagMap(usages);
402
526
  const sorted = sortedFlagEntries(flagMap);
403
527
  const rows = sorted.map(([key, data]) => {
@@ -407,7 +531,7 @@ function formatHTML(result, options) {
407
531
  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>`;
408
532
  }).join("\n ");
409
533
  const title = options.title ? esc(options.title) : "FlagLint Scan Report";
410
- const version = true ? "0.2.2" : "0.1.0";
534
+ const version = true ? "0.3.0" : "0.1.0";
411
535
  return `<!DOCTYPE html>
412
536
  <html lang="en">
413
537
  <head>
@@ -482,14 +606,16 @@ function formatReport(result, options) {
482
606
  return formatJSON(result);
483
607
  case "html":
484
608
  return formatHTML(result, options);
485
- default:
609
+ case "markdown":
486
610
  return formatMarkdown(result, options);
611
+ case "sarif":
612
+ return formatSARIF(result);
487
613
  }
488
614
  }
489
615
 
490
616
  // src/config.ts
491
617
  import { readFile as readFile2, access } from "fs/promises";
492
- import { resolve } from "path";
618
+ import { resolve as resolve3 } from "path";
493
619
  import { z, ZodError } from "zod";
494
620
  var FlagLintConfigSchema = z.object({
495
621
  include: z.array(z.string()).default(["**/*.{ts,tsx,js,jsx}"]),
@@ -512,7 +638,7 @@ var SEARCH_PATHS = [".flaglintrc", ".flaglintrc.json", "flaglint.config.json"];
512
638
  async function loadConfig(configPath) {
513
639
  const candidates = configPath ? [configPath] : SEARCH_PATHS;
514
640
  for (const candidate of candidates) {
515
- const full = resolve(candidate);
641
+ const full = resolve3(candidate);
516
642
  try {
517
643
  await access(full);
518
644
  } catch {
@@ -538,15 +664,16 @@ async function loadConfig(configPath) {
538
664
  }
539
665
 
540
666
  // src/commands/scan.ts
541
- var VALID_FORMATS = ["json", "markdown", "html"];
667
+ var VALID_FORMATS = ["json", "markdown", "html", "sarif"];
542
668
  function registerScanCommand(program2) {
543
- program2.command("scan").description("Scan a directory for feature flag usages and detect stale flags").argument("[dir]", "directory to scan", process.cwd()).option("-f, --format <format>", "output format: json | markdown | html", "markdown").option("-o, --output <file>", "write report to file").option("-c, --config <path>", "path to .flaglintrc config file").option("--exclude-tests", "exclude test files (*.test.*, *.spec.*, __tests__/, tests/)").addHelpText(
669
+ program2.command("scan").description("Scan a directory for feature flag usages and detect stale flags").argument("[dir]", "directory to scan", process.cwd()).option("-f, --format <format>", "output format: json | markdown | html | sarif", "markdown").option("-o, --output <file>", "write report to file").option("-c, --config <path>", "path to .flaglintrc config file").option("--exclude-tests", "exclude test files (*.test.*, *.spec.*, __tests__/, tests/)").addHelpText(
544
670
  "after",
545
671
  `
546
672
  Examples:
547
673
  $ flaglint scan scan current directory
548
674
  $ flaglint scan ./src scan specific directory
549
675
  $ flaglint scan --format json output as JSON
676
+ $ flaglint scan --format sarif output as SARIF for GitHub Code Scanning
550
677
  $ flaglint scan --output report.md save to file
551
678
  $ flaglint scan --exclude-tests skip test and spec files`
552
679
  ).action(
@@ -561,7 +688,7 @@ Examples:
561
688
  process.exit(2);
562
689
  }
563
690
  try {
564
- const s = await stat(resolve2(dir));
691
+ const s = await stat(resolve4(dir));
565
692
  if (!s.isDirectory()) {
566
693
  process.stderr.write(chalk.red(`Error: Not a directory: ${dir}
567
694
  `));
@@ -661,7 +788,7 @@ Examples:
661
788
  }
662
789
  const report = formatReport(result, { format, title: config.reportTitle });
663
790
  if (options.output) {
664
- const outPath = resolve2(options.output);
791
+ const outPath = resolve4(options.output);
665
792
  try {
666
793
  await writeFile(outPath, report, "utf8");
667
794
  process.stderr.write(chalk.dim(` Report written to ${options.output}
@@ -686,7 +813,7 @@ Examples:
686
813
  // src/commands/migrate.ts
687
814
  import { writeFile as writeFile2 } from "fs/promises";
688
815
  import { stat as stat2 } from "fs/promises";
689
- import { resolve as resolve3 } from "path";
816
+ import { resolve as resolve5 } from "path";
690
817
  import chalk2 from "chalk";
691
818
  import ora2 from "ora";
692
819
 
@@ -827,7 +954,7 @@ function analyze(result) {
827
954
  function formatMigrationReport(analysis) {
828
955
  const { readinessScore, requiredPackages, items, manualReviewCount, autoMigrateCount } = analysis;
829
956
  const date = (/* @__PURE__ */ new Date()).toLocaleDateString();
830
- const version = true ? "0.2.2" : "0.1.0";
957
+ const version = true ? "0.3.0" : "0.1.0";
831
958
  let scoreLabel;
832
959
  if (readinessScore >= 80) scoreLabel = "\u2713 Your codebase is ready for migration";
833
960
  else if (readinessScore >= 50) scoreLabel = "\u26A0 Some manual work required before migration";
@@ -913,7 +1040,7 @@ Examples:
913
1040
  ).action(
914
1041
  async (dir, options) => {
915
1042
  try {
916
- const s = await stat2(resolve3(dir));
1043
+ const s = await stat2(resolve5(dir));
917
1044
  if (!s.isDirectory()) {
918
1045
  process.stderr.write(chalk2.red(`Error: Not a directory: ${dir}
919
1046
  `));
@@ -1003,7 +1130,7 @@ Examples:
1003
1130
  process.stdout.write(report + "\n");
1004
1131
  process.exit(0);
1005
1132
  }
1006
- const outPath = resolve3(options.output);
1133
+ const outPath = resolve5(options.output);
1007
1134
  try {
1008
1135
  await writeFile2(outPath, report, "utf8");
1009
1136
  process.stderr.write(chalk2.green(`Migration plan written to ${options.output}
@@ -1025,7 +1152,7 @@ Examples:
1025
1152
  // src/cli.ts
1026
1153
  function createCLI() {
1027
1154
  const program2 = new Command();
1028
- program2.name("flaglint").description("Find stale feature flags. Detect flag debt. Plan your OpenFeature migration.").version("0.2.2", "-v, --version", "output the current version").addHelpText(
1155
+ program2.name("flaglint").description("Find stale feature flags. Detect flag debt. Plan your OpenFeature migration.").version("0.3.0", "-v, --version", "output the current version").addHelpText(
1029
1156
  "after",
1030
1157
  `
1031
1158
  Examples:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flaglint",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "Find stale feature flags. Detect flag debt. Plan your OpenFeature migration.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -34,13 +34,15 @@
34
34
  "url": "https://github.com/flaglint/flaglint/issues"
35
35
  },
36
36
  "scripts": {
37
- "build": "tsup",
37
+ "sync:www": "tsx scripts/sync-www.ts",
38
+ "build": "npm run sync:www && tsup",
38
39
  "dev": "tsup --watch",
39
40
  "typecheck": "tsc --noEmit",
40
41
  "typecheck:agent": "tsc --project tsconfig.agent.json",
41
42
  "test": "vitest",
42
43
  "test:run": "vitest run",
43
44
  "test:coverage": "vitest run --coverage",
45
+ "new-branch": "tsx scripts/new-branch.ts",
44
46
  "release:patch": "tsx scripts/release.ts patch",
45
47
  "release:minor": "tsx scripts/release.ts minor",
46
48
  "release:major": "tsx scripts/release.ts major"