Skip to content

Feature/migrate command#1

Merged
AsierDev merged 5 commits intomainfrom
feature/migrate-command
Dec 17, 2025
Merged

Feature/migrate command#1
AsierDev merged 5 commits intomainfrom
feature/migrate-command

Conversation

@AsierDev
Copy link
Owner

@AsierDev AsierDev commented Dec 16, 2025

migrate command — Migrate existing projects from ESLint/Prettier to Biome

  • Automatic ESLint config migration (uses Biome's official biome migrate eslint)
  • Automatic Prettier config migration (biome migrate prettier)
  • Preserves Prettier settings (quotes, semicolons, trailing commas, line width)
  • Auto-detects and excludes generated folders (gen/, dist/, build/)
  • Reads ESLint ignorePatterns and applies them to Biome
  • Jest/Vitest globals automatically configured
  • Optional auto-format to apply Biome formatting in one commit
  • Smart dependency cleanup (detects all ESLint/Prettier plugins with regex)
  • Git safety commit before migration
  • Interactive prompts for relaxed rules, script updates, and cleanup
  • --dry-run mode for previewing changes
  • Project detection for ESLint, Prettier, and existing Biome configs
  • Biome version validation (requires ≥1.7)

Summary by CodeRabbit

  • New Features

    • Added a migrate command to convert ESLint/Prettier projects to Biome — supports dry-run, interactive prompts, optional auto-format, safety git commit, dependency/script cleanup, and Biome version validation.
  • Documentation

    • Expanded README and changelog with create/migrate workflows, command examples, options, and migration outcomes.
  • Tests

    • Added fixtures and unit tests covering project detection and cleanup/migration utilities.

✏️ Tip: You can customize this high-level summary in your review settings.

Includes detection logic, cleanup helpers, and Biome configuration inference based on existing ESLint/Prettier settings.
Adds the migrate command to the CLI, orchestrating the migration process including detection, installation, config migration, and cleanup.
Updates README and CHANGELOG with details about the new migrate command features and usage.
Add robust error handling for missing package.json files and improve async file
operations throughout the migration utilities. Handle cancelled prompts gracefully
and expand CI testing to Node.js 18, 20, and 22.

Key improvements:
- Safe package.json reading with fallback handling
- Async file existence checks in project detection
- Proper cancellation handling in user prompts
- Merge globals instead of overwriting existing ones
- Move semver to production dependencies
- Add proper test cleanup in unit tests
@coderabbitai
Copy link

coderabbitai bot commented Dec 16, 2025

Walkthrough

This PR adds a full ESLint/Prettier → Biome migration flow: a new migrate CLI command, orchestration logic, generators for ESLint/Prettier migration, cleanup utilities, project-detection, git-safety and validation helpers, types, tests/fixtures, and documentation; also adds semver and its types.

Changes

Cohort / File(s) Summary
CLI Integration
src/cli.ts
Added new migrate subcommand with options (--skip-install, --skip-cleanup, --skip-git, --dry-run) and action that calls runMigrate.
Core Migration Orchestration
src/commands/migrate.ts
New runMigrate(options: MigrateOptions): Promise<void> implementing detection, dry-run reporting, confirmation, optional safety commit, Biome install/validation, ESLint/Prettier migration, dependency cleanup, script updates, optional formatting, and summary.
Configuration Migration Generators
src/generators/migrate-eslint.ts, src/generators/migrate-prettier.ts
New utilities that invoke Biome migrate subcommands (eslint, prettier) via npx/execa, with spinner feedback and structured results.
Dependency Cleanup
src/generators/cleanup.ts
New module to detect/remove ESLint & Prettier deps, uninstall via package manager, remove config files, and update package.json scripts; exposes helper functions (findDepsToRemove, uninstallDeps, removeConfigFiles, updatePackageScripts, cleanupOldDeps).
Project Detection Utility
src/utils/detect-project.ts
New detection of ESLint/Prettier/Biome presence and package manager; exports detectProject, getESLintConfigs, getPrettierConfigs.
Biome Configuration Utility
src/utils/biome-config.ts
New helpers to read Prettier/ESLint configs, map Prettier settings to Biome formatter settings, prompt for trailing-commas/relaxed rules, detect generated folders, and apply Prettier-compatible settings to biome.json.
Git Safety Utility
src/utils/git-safety.ts
New functions isGitRepo, hasUncommittedChanges, createSafetyCommit to check repo state and optionally create a safety commit.
Migration Validation Utility
src/utils/validate-migration.ts
New validators: getBiomeVersion, validateBiomeVersion, and validateMigration to check Biome installation, version, and post-migration readiness.
Type Definitions
src/types/index.ts
Added interfaces: MigrateOptions, ProjectInfo, and MigrationResult.
Dependencies
package.json
Added semver dependency and @types/semver devDependency.
Documentation
CHANGELOG.md, README.md
Updated changelog and expanded README with "Create" and "Migrate" command sections and migration details.
Test Fixtures
tests/fixtures/eslint-project/*, tests/fixtures/prettier-project/*
Added fixture projects: ESLint (.eslintrc.json, package.json) and Prettier (.prettierrc, package.json`) setups.
Unit Tests
tests/unit/cleanup.test.ts, tests/unit/detect-project.test.ts
Added tests for dependency removal detection and project configuration detection.

Sequence Diagram

sequenceDiagram
    actor User
    participant CLI as CLI (src/cli.ts)
    participant Migrate as Migrate (src/commands/migrate.ts)
    participant Detect as Detect (src/utils/detect-project.ts)
    participant Config as BiomeConfig (src/utils/biome-config.ts)
    participant Generators as Generators (src/generators/*)
    participant Cleanup as Cleanup (src/generators/cleanup.ts)
    participant Validate as Validate (src/utils/validate-migration.ts)
    participant Git as GitUtils (src/utils/git-safety.ts)

    User->>CLI: run `biome migrate`
    CLI->>Migrate: runMigrate(options)
    Migrate->>Detect: detectProject()
    Detect-->>Migrate: ProjectInfo
    Migrate->>User: show findings / dry-run summary
    alt user confirms
        Migrate->>Git: isGitRepo(), hasUncommittedChanges()
        Git-->>Migrate: status
        Migrate->>Git: createSafetyCommit() (if enabled)
        Migrate->>Config: readPrettierConfig(), prompt relaxed rules
        Config-->>Migrate: formatter settings
        Migrate->>Generators: migrateESLint(), migratePrettier() (respect dry-run)
        Generators-->>Migrate: migration results
        Migrate->>Cleanup: cleanupOldDeps() (if enabled)
        Cleanup-->>Migrate: deps/files removed
        Migrate->>Validate: validateBiomeVersion(), validateMigration()
        Validate-->>Migrate: validation results
        Migrate->>User: final summary & next steps
    else user cancels
        Migrate->>User: abort message
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Focus review on:
    • src/commands/migrate.ts (orchestration, branching, error paths)
    • src/utils/biome-config.ts (config detection/mapping and prompts)
    • src/generators/cleanup.ts (dependency pattern correctness and package-manager commands)
    • Integration between spinner/UX code and error handling

Poem

🐰 I hopped through configs, lint, and style,
Turned scattered rules into Biome's smile,
With safety commits and tidy cleanup art,
One gentle migrate — a brand-new start. ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Feature/migrate command' clearly describes the main change: adding a new migrate command to the CLI. It is concise, specific, and directly reflects the primary feature being introduced.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/migrate-command

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (17)
src/generators/migrate-eslint.ts (1)

5-36: Consider enhancing error information.

The error handling currently only captures the error message, but the execa error object contains additional useful information like stderr, exitCode, and command that could help with debugging migration failures.

Apply this diff to capture more error details:

   } catch (error) {
-    const errorMessage = error instanceof Error ? error.message : "Unknown error";
+    const errorMessage = 
+      error instanceof Error 
+        ? error.message 
+        : "Unknown error";
+    const stderr = (error as any)?.stderr;
     s.stop(pc.red("✗ Failed to migrate ESLint"));
 
     return {
       success: false,
-      message: `ESLint migration failed: ${errorMessage}`,
+      message: `ESLint migration failed: ${errorMessage}${stderr ? `\n${stderr}` : ""}`,
     };
   }
src/utils/validate-migration.ts (2)

67-86: Consider caching package.json read.

The function reads package.json twice (lines 68-76 and 79-86). Consider reading it once and reusing the parsed object to improve efficiency.

Apply this diff:

   // 2. Verify that @biomejs/biome is installed
   const pkgPath = path.join(cwd, "package.json");
+  let pkg: any = null;
+  
   if (await fs.pathExists(pkgPath)) {
-    const pkg = await fs.readJson(pkgPath);
+    pkg = await fs.readJson(pkgPath);
     const hasBiomeDep =
       pkg.devDependencies?.["@biomejs/biome"] || pkg.dependencies?.["@biomejs/biome"];
 
     if (!hasBiomeDep) {
       issues.push("@biomejs/biome not found in dependencies");
     }
   }
 
   // 3. Check if scripts are updated
-  if (await fs.pathExists(pkgPath)) {
-    const pkg = await fs.readJson(pkgPath);
+  if (pkg) {
     const lintScript = pkg.scripts?.lint;
 
     if (!lintScript?.includes("biome")) {
       issues.push('package.json "lint" script does not use Biome');
     }
   }

89-94: Clarify the biome check behavior.

The comment says "We only care that it runs" but the try-catch silently swallows all errors. Consider logging or adding a more specific comment about what types of failures are acceptable (lint errors) vs. what might indicate a real problem (command not found, syntax errors).

Consider adding more context:

   // 4. Run biome check to verify it works
   try {
     await execa("npx", ["@biomejs/biome", "check", ".", "--max-diagnostics=0"], { cwd });
   } catch {
-    // Biome check might have lint errors, but that's OK
-    // We only care that it runs
+    // Biome check might report lint/format errors, which is expected and OK
+    // We only verify that the command executes (config is valid, Biome is installed)
+    // Command-not-found or syntax errors in biome.json would be caught by earlier checks
   }
src/utils/biome-config.ts (2)

218-337: Consider refactoring the large applyPrettierCompatibility function.

This function is 120 lines long and handles multiple responsibilities: reading configs, detecting folders, transforming patterns, prompting users, and writing config. Consider breaking it into smaller, focused functions for better maintainability and testability.

For example, extract:

  • buildExclusionPatterns(cwd, eslintIgnorePatterns, generatedFolders)
  • applyFormatterSettings(config, prettierConfig, trailingCommasOverride)
  • applyRelaxedRules(config)
  • addTestGlobals(config)

This would make the main function more readable and each piece easier to test in isolation.


311-327: LGTM with a suggestion.

The logic to add Jest/Vitest globals is sound. The use of Set ensures no duplicates. Consider adding a comment explaining that these globals are always added regardless of whether the project uses Jest/Vitest, which might be intentional for compatibility but could cause confusion.

Consider adding a clarifying comment:

   // Add Jest/Vitest globals for test files (merge with existing)
+  // Note: These are added proactively even if tests aren't detected,
+  // to avoid "undefined variable" errors when tests are added later
   config.javascript = config.javascript || {};
tests/unit/cleanup.test.ts (1)

86-97: Consider adding edge case for empty/undefined package object.

The test for no matching deps is good, but consider adding a test case for when pkg is empty {} or when dependencies/devDependencies are explicitly undefined to ensure robustness.

it("handles empty package object", () => {
  const pkg = {};
  const result = findDepsToRemove(pkg);
  expect(result).toHaveLength(0);
});
src/utils/git-safety.ts (1)

33-36: Handle isCancel from the confirm prompt.

If the user cancels the prompt (e.g., Ctrl+C), shouldCommit will be a symbol, not a boolean. The current check shouldCommit === true handles this safely by returning false, but explicitly handling cancellation would be more intentional.

+import { confirm, isCancel } from "@clack/prompts";

   const shouldCommit = await confirm({
     message: "You have uncommitted changes. Create safety commit before migrating?",
     initialValue: true,
   });

+  if (isCancel(shouldCommit)) {
+    return false;
+  }
+
   if (shouldCommit === true) {
src/generators/cleanup.ts (4)

38-47: Missing error handling for uninstall command failure.

If the package manager command fails (e.g., network issues, permission errors), the error will propagate unhandled. Consider catching and re-throwing with a more descriptive message, or letting the caller handle it explicitly.

 export async function uninstallDeps(
   deps: string[],
   pm: PackageManager,
   cwd: string,
 ): Promise<void> {
   if (deps.length === 0) return;

   const uninstallCmd = pm === "npm" ? "uninstall" : "remove";
-  await execa(pm, [uninstallCmd, ...deps], { cwd });
+  try {
+    await execa(pm, [uninstallCmd, ...deps], { cwd });
+  } catch (error) {
+    const errorMessage = error instanceof Error ? error.message : "Unknown error";
+    throw new Error(`Failed to uninstall packages: ${errorMessage}`);
+  }
 }

49-84: Unused parameter _info should be removed or utilized.

The _info parameter is prefixed with underscore indicating it's unused. If ProjectInfo context isn't needed here, remove the parameter entirely to avoid confusion.

-export async function removeConfigFiles(_info: ProjectInfo, cwd: string): Promise<string[]> {
+export async function removeConfigFiles(cwd: string): Promise<string[]> {

Note: This would require updating the call site in cleanupOldDeps at line 162.


105-114: Handle user cancellation from confirm prompt.

The confirm function from @clack/prompts can return a symbol when the user cancels (Ctrl+C). Currently only checking === true, but not handling the cancel case gracefully.

+import { confirm, spinner, isCancel } from "@clack/prompts";
 ...
   const shouldUpdate = await confirm({
     message: "Update package.json scripts to use Biome?",
     initialValue: true,
   });

+  if (isCancel(shouldUpdate)) {
+    return { updated: false, scripts: newScripts };
+  }
+
   if (shouldUpdate === true) {

146-159: Handle user cancellation from confirm prompt.

Similar to updatePackageScripts, the confirm call here doesn't handle the cancel case explicitly. When user presses Ctrl+C, shouldRemove will be a symbol, not true or false.

     const shouldRemove = await confirm({
       message: `Remove ${depsToRemove.length} ESLint/Prettier packages?`,
       initialValue: true,
     });

+    if (isCancel(shouldRemove)) {
+      return { depsRemoved: [], filesRemoved: [] };
+    }
+
     if (shouldRemove === true) {
src/utils/detect-project.ts (3)

18-29: Missing .prettierrc.toml configuration file.

Prettier supports TOML format for configuration files, which is not included in the list.

 const PRETTIER_CONFIGS = [
   ".prettierrc",
   ".prettierrc.json",
   ".prettierrc.yaml",
   ".prettierrc.yml",
   ".prettierrc.js",
   ".prettierrc.cjs",
   ".prettierrc.mjs",
+  ".prettierrc.toml",
   "prettier.config.js",
   "prettier.config.cjs",
   "prettier.config.mjs",
 ];

64-65: Consider detecting biome.jsonc in addition to biome.json.

Biome supports JSONC format (JSON with comments), and some projects may use biome.jsonc instead.

   // Detect existing Biome config (async)
-  const hasBiome = await fs.pathExists(path.join(cwd, "biome.json"));
+  const hasBiome =
+    (await fs.pathExists(path.join(cwd, "biome.json"))) ||
+    (await fs.pathExists(path.join(cwd, "biome.jsonc")));

6-16: Add eslint.config.ts, eslint.config.mts, and eslint.config.cts to the ESLint config detection list.

ESLint v9.18.0+ officially supports TypeScript configuration files. Including these extensions ensures the utility detects ESLint configs in projects using TypeScript configuration files (requires jiti v2.0+ for Node.js).

src/commands/migrate.ts (3)

128-129: Hardcoded npx may not work correctly with all package managers.

The code uses the detected packageManager for installation but switches to npx for running biome init. This could cause issues with pnpm or yarn workspaces.

+  const runCmd = projectInfo.packageManager === "npm" ? "npx" :
+                 projectInfo.packageManager === "pnpm" ? "pnpm" :
+                 projectInfo.packageManager === "yarn" ? "yarn" : "bunx";
+
   if (!projectInfo.hasBiome) {
     const s3 = spinner();
     s3.start("Initializing Biome configuration...");
     try {
-      await execa("npx", ["@biomejs/biome", "init"], { cwd });
+      await execa(runCmd, ["@biomejs/biome", "init"], { cwd });
       s3.stop(pc.green("✓ Created biome.json"));

Apply the same pattern at line 204 for biome check --write.


192-210: Missing cancellation handling for the formatting confirmation.

If the user cancels with Ctrl+C during this prompt, applyFormatting will be a symbol, and the code will silently skip formatting. Consider handling cancellation explicitly.

   const applyFormatting = await confirm({
     message:
       "Biome may format code differently than Prettier.\n" +
       "Apply Biome formatting now? (recommended, creates one reformatting commit)",
     initialValue: true,
   });

+  if (isCancel(applyFormatting)) {
+    cancel("Migration cancelled");
+    return;
+  }
+
   if (applyFormatting === true) {

100-114: Check package.json for existing @biomejs/biome dependency before installation.

The hasBiome check only verifies the existence of biome.json, but ignores whether @biomejs/biome is already listed in package.json dependencies. If the package is already declared but biome.json doesn't exist, the installation will still run unnecessarily. Consider checking both package.json devDependencies/dependencies (similar to the logic in validate-migration.ts lines 70-71) alongside the biome.json check to avoid redundant installation attempts.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3e2b0fd and 68f78eb.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (19)
  • CHANGELOG.md (1 hunks)
  • README.md (2 hunks)
  • package.json (1 hunks)
  • src/cli.ts (2 hunks)
  • src/commands/migrate.ts (1 hunks)
  • src/generators/cleanup.ts (1 hunks)
  • src/generators/migrate-eslint.ts (1 hunks)
  • src/generators/migrate-prettier.ts (1 hunks)
  • src/types/index.ts (1 hunks)
  • src/utils/biome-config.ts (1 hunks)
  • src/utils/detect-project.ts (1 hunks)
  • src/utils/git-safety.ts (1 hunks)
  • src/utils/validate-migration.ts (1 hunks)
  • tests/fixtures/eslint-project/.eslintrc.json (1 hunks)
  • tests/fixtures/eslint-project/package.json (1 hunks)
  • tests/fixtures/prettier-project/.prettierrc (1 hunks)
  • tests/fixtures/prettier-project/package.json (1 hunks)
  • tests/unit/cleanup.test.ts (1 hunks)
  • tests/unit/detect-project.test.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
tests/unit/detect-project.test.ts (1)
src/utils/detect-project.ts (3)
  • detectProject (31-76)
  • getESLintConfigs (78-80)
  • getPrettierConfigs (82-84)
tests/unit/cleanup.test.ts (1)
src/generators/cleanup.ts (1)
  • findDepsToRemove (25-36)
src/generators/cleanup.ts (2)
src/types/index.ts (2)
  • PackageManager (1-1)
  • ProjectInfo (27-35)
src/utils/detect-project.ts (2)
  • getESLintConfigs (78-80)
  • getPrettierConfigs (82-84)
src/cli.ts (1)
src/commands/migrate.ts (1)
  • runMigrate (17-233)
src/utils/detect-project.ts (2)
src/types/index.ts (1)
  • ProjectInfo (27-35)
src/utils/detect-pm.ts (1)
  • detectPackageManager (17-36)
🔇 Additional comments (26)
tests/fixtures/prettier-project/.prettierrc (1)

1-7: LGTM!

This test fixture provides a representative Prettier configuration for testing the migration workflow. The settings are standard and appropriate.

README.md (1)

7-73: LGTM!

The documentation clearly explains both the create and migrate workflows with comprehensive command options and expected outcomes. The structure is user-friendly and informative.

CHANGELOG.md (1)

10-26: LGTM!

The changelog comprehensively documents the new migration features. The format follows Keep a Changelog conventions and provides clear, detailed information about the new functionality.

src/types/index.ts (1)

19-43: LGTM!

The new type definitions are well-structured and provide clear contracts for the migration workflow. The field names are descriptive and types are appropriate.

src/utils/validate-migration.ts (2)

10-19: LGTM!

The version extraction logic correctly handles different output formats from the Biome CLI. The regex pattern is appropriate and the error handling gracefully returns null if extraction fails.


21-49: LGTM!

The version validation logic correctly uses semver.gte() to ensure the installed Biome version meets the minimum requirement. The error messages are clear and actionable.

src/utils/biome-config.ts (4)

6-28: LGTM!

The type definitions accurately represent Prettier and Biome configuration options. The interfaces are well-structured and cover the necessary properties for format migration.


43-85: LGTM!

The Prettier config reading logic appropriately handles multiple config file locations and formats, with proper fallback to package.json. The error handling gracefully continues searching if a config file is invalid.


91-138: LGTM!

The ESLint ignore patterns reader correctly handles multiple sources (.eslintrc, .eslintrc.json, package.json, .eslintignore) with appropriate error handling. The .eslintignore file parsing correctly filters comments and empty lines.


260-284: All formatter options being set are supported by Biome 1.7.0. Biome supports indentStyle, indentWidth, and lineWidth as standard formatter options, and bracketSpacing, lineEnding, and trailingCommas are documented configuration options. The JavaScript formatter options quoteStyle, arrowParentheses, trailingCommas, bracketSpacing, and semicolons are all supported. No compatibility concerns exist with the target version.

package.json (1)

30-31: Package versions are secure and appropriate for the migration.

The specified versions semver@^7.7.3 and picocolors@^1.1.1 are secure. The semver version is well above the fix threshold for known vulnerabilities (CVE-2022-25883 was fixed in 7.5.2), and no current security advisories apply to these versions. The caret operator allows compatible updates within the major version, ensuring minor and patch improvements are accessible without breaking changes.

tests/fixtures/eslint-project/package.json (1)

1-13: LGTM!

Well-structured test fixture for ESLint detection. The devDependencies cover the main ESLint ecosystem packages that the migration tool needs to detect and clean up.

tests/fixtures/prettier-project/package.json (1)

1-11: LGTM!

Good test fixture for Prettier detection. Including prettier-plugin-tailwindcss ensures the cleanup pattern matching for prettier-* packages is also tested.

tests/fixtures/eslint-project/.eslintrc.json (1)

1-25: LGTM!

Realistic ESLint configuration that exercises various detection and migration scenarios: extends, plugins, custom rules, TypeScript parser, and React support. Good coverage for testing the migration workflow.

tests/unit/cleanup.test.ts (1)

1-114: LGTM!

Comprehensive test coverage for the findDepsToRemove function. Tests cover all major ESLint/Prettier package patterns and verify that unrelated packages like typescript are correctly excluded.

src/cli.ts (1)

38-52: LGTM!

Clean integration of the migrate command with well-named options that are consistent with the existing create command patterns. The options mapping to runMigrate is straightforward and correct.

tests/unit/detect-project.test.ts (1)

1-132: Good test coverage for project detection utilities.

The tests cover all major configuration file formats for ESLint and Prettier, plus edge cases like package.json embedded configs and lockfile-based package manager detection. The temp directory isolation pattern is well-implemented.

src/generators/migrate-prettier.ts (1)

5-36: LGTM!

Clean implementation that follows the same pattern as migrateESLint. The spinner feedback, error handling, and return structure are consistent with the codebase conventions. The dryRun parameter is appropriately handled since the migrate command exits early during dry-run mode before calling this function.

src/utils/git-safety.ts (1)

4-20: LGTM!

The isGitRepo and hasUncommittedChanges utilities are well-implemented with appropriate error handling. Returning false on errors is a safe default for these checks.

src/generators/cleanup.ts (2)

10-19: LGTM! Regex patterns are well-designed for package detection.

The patterns correctly match ESLint and Prettier related packages using appropriate anchors and prefixes.


21-36: LGTM! Clean implementation.

The matchesPatterns helper and findDepsToRemove logic are clear and correctly aggregate dependencies from both dependencies and devDependencies.

src/utils/detect-project.ts (2)

31-76: LGTM! Well-structured detection logic.

The function correctly:

  • Handles missing/invalid package.json gracefully
  • Detects both file-based and package.json-embedded configs
  • Returns a comprehensive ProjectInfo object
  • Uses async operations appropriately

78-84: LGTM! Simple accessor functions.

These helpers expose the config lists for use in other modules like cleanup.ts.

src/commands/migrate.ts (3)

17-32: LGTM! Good initial validation.

The function correctly detects project setup and exits early with a clear message if there's nothing to migrate.


53-70: LGTM! Dry-run mode provides useful preview.

The dry-run implementation clearly shows what changes would be made without modifying files.


227-232: LGTM! Clear and helpful completion message.

The outro provides actionable next steps with the lint command.

Comment on lines +157 to +161
// 9. Read Prettier config BEFORE cleanup (so we can preserve settings)
const prettierConfig = await readPrettierConfig(cwd);
if (prettierConfig) {
console.log(pc.dim(" Read Prettier settings to preserve formatting preferences"));
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Prettier config is read after migration, but migration may have already removed the file.

If migratePrettier removes or modifies the Prettier config file as part of migration, readPrettierConfig at line 158 might not find the original settings. The comment says "BEFORE cleanup" but Prettier migration happens at line 150.

Consider reading the Prettier config earlier, before any migration steps:

+  // Read Prettier config BEFORE migration (so we can preserve settings)
+  const prettierConfig = projectInfo.hasPrettier ? await readPrettierConfig(cwd) : null;
+  if (prettierConfig) {
+    console.log(pc.dim("  Read Prettier settings to preserve formatting preferences"));
+  }
+
   // 7. Migrate ESLint
   if (projectInfo.hasESLint) {
     ...
   }
   
   // 8. Migrate Prettier
   if (projectInfo.hasPrettier) {
     ...
   }

-  // 9. Read Prettier config BEFORE cleanup (so we can preserve settings)
-  const prettierConfig = await readPrettierConfig(cwd);
-  if (prettierConfig) {
-    console.log(pc.dim("  Read Prettier settings to preserve formatting preferences"));
-  }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/commands/migrate.ts around lines 157 to 161, readPrettierConfig is
executed after migration/cleanup steps (contrary to the comment) which may
remove or change the Prettier config; move the call to readPrettierConfig so it
runs before any migratePrettier or cleanup operations (i.e., before line ~150),
store the result for later use, and ensure any errors are handled or ignored
gracefully so logging about preserved settings remains valid.

Comment on lines +144 to +169
export function mapPrettierToBiome(
prettier: PrettierConfig,
trailingCommasOverride?: "none" | "all",
): BiomeFormatterSettings {
// If es5, require override; otherwise map directly
let trailingCommas: "none" | "all" = "all";
if (trailingCommasOverride) {
trailingCommas = trailingCommasOverride;
} else if (prettier.trailingComma === "none") {
trailingCommas = "none";
} else if (prettier.trailingComma === "all") {
trailingCommas = "all";
}

return {
quoteStyle: prettier.singleQuote ? "single" : "double",
trailingCommas,
lineWidth: prettier.printWidth || 80,
indentWidth: prettier.tabWidth || 2,
indentStyle: prettier.useTabs ? "tab" : "space",
semicolons: prettier.semi === false ? "asNeeded" : "always",
bracketSpacing: prettier.bracketSpacing !== false,
arrowParentheses: prettier.arrowParens === "avoid" ? "asNeeded" : "always",
lineEnding: prettier.endOfLine === "crlf" ? "crlf" : prettier.endOfLine === "cr" ? "cr" : "lf",
};
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Verify default trailingCommas behavior.

When prettier.trailingComma is undefined and no override is provided, the function defaults to "all" (line 149). However, Prettier's default is "es5". This could lead to unexpected behavior when migrating projects without an explicit trailingComma setting.

Consider this adjustment:

 ): BiomeFormatterSettings {
   // If es5, require override; otherwise map directly
-  let trailingCommas: "none" | "all" = "all";
+  let trailingCommas: "none" | "all" = "all"; // Default to "all" to match common preference
   if (trailingCommasOverride) {
     trailingCommas = trailingCommasOverride;
   } else if (prettier.trailingComma === "none") {
     trailingCommas = "none";
   } else if (prettier.trailingComma === "all") {
     trailingCommas = "all";
+  } else if (prettier.trailingComma === undefined) {
+    // Prettier defaults to "es5", which Biome doesn't support
+    // Default to "all" as it's closer to "es5" behavior
+    trailingCommas = "all";
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function mapPrettierToBiome(
prettier: PrettierConfig,
trailingCommasOverride?: "none" | "all",
): BiomeFormatterSettings {
// If es5, require override; otherwise map directly
let trailingCommas: "none" | "all" = "all";
if (trailingCommasOverride) {
trailingCommas = trailingCommasOverride;
} else if (prettier.trailingComma === "none") {
trailingCommas = "none";
} else if (prettier.trailingComma === "all") {
trailingCommas = "all";
}
return {
quoteStyle: prettier.singleQuote ? "single" : "double",
trailingCommas,
lineWidth: prettier.printWidth || 80,
indentWidth: prettier.tabWidth || 2,
indentStyle: prettier.useTabs ? "tab" : "space",
semicolons: prettier.semi === false ? "asNeeded" : "always",
bracketSpacing: prettier.bracketSpacing !== false,
arrowParentheses: prettier.arrowParens === "avoid" ? "asNeeded" : "always",
lineEnding: prettier.endOfLine === "crlf" ? "crlf" : prettier.endOfLine === "cr" ? "cr" : "lf",
};
}
export function mapPrettierToBiome(
prettier: PrettierConfig,
trailingCommasOverride?: "none" | "all",
): BiomeFormatterSettings {
// If es5, require override; otherwise map directly
let trailingCommas: "none" | "all" = "all"; // Default to "all" to match common preference
if (trailingCommasOverride) {
trailingCommas = trailingCommasOverride;
} else if (prettier.trailingComma === "none") {
trailingCommas = "none";
} else if (prettier.trailingComma === "all") {
trailingCommas = "all";
} else if (prettier.trailingComma === undefined) {
// Prettier defaults to "es5", which Biome doesn't support
// Default to "all" as it's closer to "es5" behavior
trailingCommas = "all";
}
return {
quoteStyle: prettier.singleQuote ? "single" : "double",
trailingCommas,
lineWidth: prettier.printWidth || 80,
indentWidth: prettier.tabWidth || 2,
indentStyle: prettier.useTabs ? "tab" : "space",
semicolons: prettier.semi === false ? "asNeeded" : "always",
bracketSpacing: prettier.bracketSpacing !== false,
arrowParentheses: prettier.arrowParens === "avoid" ? "asNeeded" : "always",
lineEnding: prettier.endOfLine === "crlf" ? "crlf" : prettier.endOfLine === "cr" ? "cr" : "lf",
};
}

…ng, refine Biome exclusion patterns, and adjust git staging.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
src/commands/migrate.ts (2)

157-161: Prettier config read after migration may fail.

The comment at line 157 states "BEFORE cleanup," but readPrettierConfig is called after migratePrettier (line 150), which may have already removed or modified the Prettier configuration file. This could result in reading stale or missing config data.

Move the readPrettierConfig call before the migration steps to ensure the original Prettier settings are captured:

+  // Read Prettier config BEFORE migration (so we can preserve settings)
+  const prettierConfig = projectInfo.hasPrettier ? await readPrettierConfig(cwd) : null;
+  if (prettierConfig) {
+    console.log(pc.dim("  Read Prettier settings to preserve formatting preferences"));
+  }
+
   // 7. Migrate ESLint
   if (projectInfo.hasESLint) {
     const eslintResult = await migrateESLint(cwd);
     result.eslintMigrated = eslintResult.success;
     if (!eslintResult.success) {
       note(eslintResult.message, "ESLint migration warning");
     }
   }

   // 8. Migrate Prettier
   if (projectInfo.hasPrettier) {
     const prettierResult = await migratePrettier(cwd);
     result.prettierMigrated = prettierResult.success;
     if (!prettierResult.success) {
       note(prettierResult.message, "Prettier migration warning");
     }
   }

-  // 9. Read Prettier config BEFORE cleanup (so we can preserve settings)
-  const prettierConfig = await readPrettierConfig(cwd);
-  if (prettierConfig) {
-    console.log(pc.dim("  Read Prettier settings to preserve formatting preferences"));
-  }

169-171: Validation result not used.

The validation result from validateMigration is not captured or used. This means cleanup proceeds regardless of validation outcome, which could remove dependencies even if the migration didn't complete successfully.

Consider either:

  1. Capture the result and skip cleanup if validation fails:
   // 12. Validate migration before cleanup
-  await validateMigration(cwd);
+  const validation = await validateMigration(cwd);

   // 13. Cleanup old dependencies (only if migration was successful)
-  if (!options.skipCleanup) {
+  if (!options.skipCleanup && validation.success) {
     const cleanup = await cleanupOldDeps(projectInfo, cwd);
     result.depsRemoved = cleanup.depsRemoved;
     result.filesRemoved = cleanup.filesRemoved;
   }
  1. Or remove the variable assignment entirely if validation is purely informational:
   // 12. Validate migration (informational only)
   await validateMigration(cwd);
🧹 Nitpick comments (1)
src/utils/biome-config.ts (1)

144-169: Consider documenting the default trailingCommas behavior.

The function defaults to "all" for trailing commas when prettier.trailingComma is undefined (line 149). While functionally correct, this differs from Prettier's default of "es5" (which Biome doesn't support). Adding a comment would clarify this intentional choice.

 ): BiomeFormatterSettings {
   // If es5, require override; otherwise map directly
-  let trailingCommas: "none" | "all" = "all";
+  // Default to "all" since Biome doesn't support Prettier's default "es5"
+  // "all" is closer to "es5" behavior than "none"
+  let trailingCommas: "none" | "all" = "all";
   if (trailingCommasOverride) {
     trailingCommas = trailingCommasOverride;
   } else if (prettier.trailingComma === "none") {
     trailingCommas = "none";
   } else if (prettier.trailingComma === "all") {
     trailingCommas = "all";
+  } else if (prettier.trailingComma === undefined) {
+    // Explicitly handle undefined: keep default "all"
+    trailingCommas = "all";
   }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 68f78eb and 5c6b156.

📒 Files selected for processing (5)
  • src/commands/migrate.ts (1 hunks)
  • src/generators/cleanup.ts (1 hunks)
  • src/utils/biome-config.ts (1 hunks)
  • src/utils/git-safety.ts (1 hunks)
  • tests/unit/detect-project.test.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/generators/cleanup.ts
  • tests/unit/detect-project.test.ts
🧰 Additional context used
🧬 Code graph analysis (1)
src/commands/migrate.ts (8)
src/types/index.ts (2)
  • MigrateOptions (20-25)
  • MigrationResult (37-43)
src/utils/detect-project.ts (1)
  • detectProject (31-76)
src/utils/git-safety.ts (1)
  • createSafetyCommit (22-51)
src/utils/validate-migration.ts (2)
  • validateBiomeVersion (21-49)
  • validateMigration (51-107)
src/generators/migrate-eslint.ts (1)
  • migrateESLint (5-36)
src/generators/migrate-prettier.ts (1)
  • migratePrettier (5-36)
src/utils/biome-config.ts (3)
  • readPrettierConfig (43-85)
  • promptRelaxedRules (342-349)
  • applyPrettierCompatibility (218-337)
src/generators/cleanup.ts (2)
  • cleanupOldDeps (119-174)
  • updatePackageScripts (86-117)
🔇 Additional comments (13)
src/utils/git-safety.ts (3)

4-11: LGTM!

The function correctly detects Git repositories and handles errors gracefully by returning false.


13-20: LGTM!

The function correctly detects uncommitted changes and handles errors safely.


22-51: Previous concern addressed!

The function now correctly uses git add -u at line 40, which only stages modified and deleted tracked files. This addresses the previous review concern about staging untracked files unintentionally. The logic properly checks for a Git repository, verifies uncommitted changes, prompts the user, and creates a safety commit.

src/commands/migrate.ts (5)

17-52: LGTM!

The project detection and display logic correctly identifies ESLint/Prettier configurations and provides clear feedback to the user about what was detected.


54-89: LGTM!

The dry-run mode and safety commit flow are well implemented. The user is properly informed of changes before they're applied, and the git safety commit provides a rollback point.


91-137: LGTM!

The Biome installation and initialization flow correctly handles conditional installation, version validation, and configuration initialization with proper error handling.


173-190: LGTM!

The cleanup and script update logic correctly handles optional cleanup and provides helpful feedback when scripts aren't automatically updated.


192-233: LGTM!

The optional formatting step and migration summary provide excellent user experience. The clear warning about formatting differences and the comprehensive summary help users understand what changed.

src/utils/biome-config.ts (5)

43-85: LGTM!

The function correctly reads Prettier configuration from various file formats and safely handles errors. The decision to skip JavaScript config files is reasonable since they cannot be statically parsed.


91-138: LGTM!

The function correctly reads ESLint ignore patterns from multiple sources (config files, package.json, .eslintignore file) and properly filters out comments and empty lines.


174-191: LGTM!

The function provides a clear user prompt with helpful hints to resolve the "es5" trailing comma incompatibility, and safely handles cancellation.


196-213: LGTM!

The function correctly detects common generated folders and formats them as Biome exclusion patterns.


218-337: LGTM!

The function comprehensively applies Prettier compatibility settings to the Biome configuration. The ESLint ignore pattern normalization (lines 240-246) correctly filters existing negations and creates new exclusion patterns. The function properly:

  • Merges exclusion patterns from multiple sources
  • Maps Prettier settings to Biome equivalents
  • Applies optional relaxed lint rules
  • Configures test globals
  • Disables organize imports during migration

@AsierDev AsierDev merged commit 58cbb66 into main Dec 17, 2025
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant