flaglint 0.2.1 → 0.2.2

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,6 +5,15 @@ 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
9
+
10
+ ### Fixed
11
+
12
+ - **Config mutation**: `--exclude-tests` no longer mutates the loaded config object in both `scan` and `migrate` commands — uses spread instead of `push()` so the original config is never modified.
13
+ - **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
+ - **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
+ - **ScanConfig boundary**: `scan()` now accepts `ScanConfig` (scan-relevant fields only) rather than the full `FlagLintConfig`, decoupling the scanner from CLI output concerns (`reportTitle`, `outputDir`).
16
+
8
17
  ## [0.2.1] - 2026-05-23
9
18
 
10
19
  ### Fixed
@@ -188,7 +188,7 @@ function detectUsages(ast, filePath, wrappers) {
188
188
  });
189
189
  return usages;
190
190
  }
191
- async function scan(source, config, onProgress) {
191
+ async function scan(source, config, onProgress, evaluator) {
192
192
  const start = Date.now();
193
193
  for (const pattern of config.include) {
194
194
  if (pattern.startsWith("/") || pattern.startsWith("..")) {
@@ -207,7 +207,7 @@ async function scan(source, config, onProgress) {
207
207
  code = await source.readFile(file);
208
208
  } catch (err) {
209
209
  const fsCode = err.code ?? "UNKNOWN";
210
- return { usages: [], warning: `warn: could not read ${file} (${fsCode})` };
210
+ return { usages: [], warning: { kind: "read-failure", file, fsCode } };
211
211
  }
212
212
  let ast;
213
213
  try {
@@ -220,7 +220,7 @@ async function scan(source, config, onProgress) {
220
220
  filePath: file
221
221
  });
222
222
  } catch {
223
- return { usages: [], warning: `warn: failed to parse ${file}` };
223
+ return { usages: [], warning: { kind: "parse-failure", file } };
224
224
  }
225
225
  return { usages: detectUsages(ast, file, config.wrappers), warning: null };
226
226
  }
@@ -261,6 +261,9 @@ async function scan(source, config, onProgress) {
261
261
  }
262
262
  }
263
263
  }
264
+ if (evaluator) {
265
+ await evaluator.evaluate(allUsages, config);
266
+ }
264
267
  const uniqueFlags = [
265
268
  ...new Set(
266
269
  allUsages.filter((u) => !u.isDynamic && u.flagKey !== "*").map((u) => u.flagKey)
@@ -404,7 +407,7 @@ function formatHTML(result, options) {
404
407
  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>`;
405
408
  }).join("\n ");
406
409
  const title = options.title ? esc(options.title) : "FlagLint Scan Report";
407
- const version = true ? "0.2.1" : "0.1.0";
410
+ const version = true ? "0.2.2" : "0.1.0";
408
411
  return `<!DOCTYPE html>
409
412
  <html lang="en">
410
413
  <head>
@@ -585,16 +588,15 @@ Examples:
585
588
  process.stderr.write(chalk.red(String(err instanceof Error ? err.message : err)) + "\n");
586
589
  process.exit(1);
587
590
  }
588
- if (options.excludeTests) {
589
- config.exclude.push(
590
- "**/*.test.ts",
591
- "**/*.test.tsx",
592
- "**/*.spec.ts",
593
- "**/*.spec.tsx",
594
- "**/__tests__/**",
595
- "**/tests/**"
596
- );
597
- }
591
+ const TEST_PATTERNS = [
592
+ "**/*.test.ts",
593
+ "**/*.test.tsx",
594
+ "**/*.spec.ts",
595
+ "**/*.spec.tsx",
596
+ "**/__tests__/**",
597
+ "**/tests/**"
598
+ ];
599
+ const scanConfig = options.excludeTests ? { ...config, exclude: [...config.exclude, ...TEST_PATTERNS] } : config;
598
600
  const format = options.format;
599
601
  const spinner = ora(`Scanning ${dir}...`).start();
600
602
  process.once("SIGINT", () => {
@@ -604,7 +606,7 @@ Examples:
604
606
  let lastSpinnerUpdate = 0;
605
607
  let result;
606
608
  try {
607
- result = await scan(new LocalFileSource(dir), config, (filesScanned) => {
609
+ result = await scan(new LocalFileSource(dir), scanConfig, (filesScanned) => {
608
610
  if (filesScanned - lastSpinnerUpdate >= 50) {
609
611
  spinner.text = `Scanning... (${filesScanned} files)`;
610
612
  lastSpinnerUpdate = filesScanned;
@@ -617,7 +619,8 @@ Examples:
617
619
  process.exit(1);
618
620
  }
619
621
  for (const w of result.warnings) {
620
- process.stderr.write(chalk.yellow(w + "\n"));
622
+ const msg = w.kind === "read-failure" ? `warn: could not read ${w.file} (${w.fsCode})` : `warn: failed to parse ${w.file}`;
623
+ process.stderr.write(chalk.yellow(msg + "\n"));
621
624
  }
622
625
  if (result.scannedFiles === 0) {
623
626
  process.stderr.write(
@@ -824,7 +827,7 @@ function analyze(result) {
824
827
  function formatMigrationReport(analysis) {
825
828
  const { readinessScore, requiredPackages, items, manualReviewCount, autoMigrateCount } = analysis;
826
829
  const date = (/* @__PURE__ */ new Date()).toLocaleDateString();
827
- const version = true ? "0.2.1" : "0.1.0";
830
+ const version = true ? "0.2.2" : "0.1.0";
828
831
  let scoreLabel;
829
832
  if (readinessScore >= 80) scoreLabel = "\u2713 Your codebase is ready for migration";
830
833
  else if (readinessScore >= 50) scoreLabel = "\u26A0 Some manual work required before migration";
@@ -937,16 +940,15 @@ Examples:
937
940
  process.stderr.write(chalk2.red(String(err instanceof Error ? err.message : err)) + "\n");
938
941
  process.exit(1);
939
942
  }
940
- if (options.excludeTests) {
941
- config.exclude.push(
942
- "**/*.test.ts",
943
- "**/*.test.tsx",
944
- "**/*.spec.ts",
945
- "**/*.spec.tsx",
946
- "**/__tests__/**",
947
- "**/tests/**"
948
- );
949
- }
943
+ const TEST_PATTERNS = [
944
+ "**/*.test.ts",
945
+ "**/*.test.tsx",
946
+ "**/*.spec.ts",
947
+ "**/*.spec.tsx",
948
+ "**/__tests__/**",
949
+ "**/tests/**"
950
+ ];
951
+ const scanConfig = options.excludeTests ? { ...config, exclude: [...config.exclude, ...TEST_PATTERNS] } : config;
950
952
  const spinner = ora2(`Scanning ${dir}...`).start();
951
953
  process.once("SIGINT", () => {
952
954
  spinner.stop();
@@ -954,7 +956,7 @@ Examples:
954
956
  });
955
957
  let scanResult;
956
958
  try {
957
- scanResult = await scan(new LocalFileSource(dir), config, (filesScanned) => {
959
+ scanResult = await scan(new LocalFileSource(dir), scanConfig, (filesScanned) => {
958
960
  spinner.text = `Scanning files... ${filesScanned}`;
959
961
  });
960
962
  spinner.text = "Analyzing migration readiness...";
@@ -983,7 +985,8 @@ Examples:
983
985
  const analysis = analyze(scanResult);
984
986
  spinner.stop();
985
987
  for (const w of scanResult.warnings) {
986
- process.stderr.write(chalk2.yellow(w + "\n"));
988
+ const msg = w.kind === "read-failure" ? `warn: could not read ${w.file} (${w.fsCode})` : `warn: failed to parse ${w.file}`;
989
+ process.stderr.write(chalk2.yellow(msg + "\n"));
987
990
  }
988
991
  const { readinessScore } = analysis;
989
992
  const scoreColor = readinessScore >= 80 ? chalk2.green : readinessScore >= 50 ? chalk2.yellow : chalk2.red;
@@ -1022,7 +1025,7 @@ Examples:
1022
1025
  // src/cli.ts
1023
1026
  function createCLI() {
1024
1027
  const program2 = new Command();
1025
- program2.name("flaglint").description("Find stale feature flags. Detect flag debt. Plan your OpenFeature migration.").version("0.2.1", "-v, --version", "output the current version").addHelpText(
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(
1026
1029
  "after",
1027
1030
  `
1028
1031
  Examples:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flaglint",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Find stale feature flags. Detect flag debt. Plan your OpenFeature migration.",
5
5
  "type": "module",
6
6
  "bin": {