biome-to-oxc 1.4.0 → 2.0.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/README.md CHANGED
@@ -28,17 +28,27 @@ Current capabilities:
28
28
  ✅ **Preserves Representable Setup**
29
29
 
30
30
  - Ignore patterns, override file globs, and override exclusions
31
- - `.biomeignore` patterns migrated into Oxlint `ignorePatterns` (compatibility alias)
31
+ - Negated `includes` exceptions (`!pattern`), translated into `ignorePatterns` for both tools
32
+ - `.biomeignore` patterns migrated into the Oxlint **and** Oxfmt `ignorePatterns`. Biome 2.x does
33
+ not read `.biomeignore` itself, so this deliberately narrows scope; the migration says so
32
34
  - Linter configuration overrides
33
35
  - Formatter configuration overrides when they can be represented as Oxfmt file-glob overrides
34
36
  - JavaScript globals
35
37
  - Rule severities (`error`/`warn`/`off`; `info` and `on` are accepted with explicit lossy-mapping warnings)
36
38
  - Explicit `printWidth` handling (no silent changes)
37
- - Custom output-directory glob rebasing so generated configs keep source-relative paths
39
+ - The set of formatted files: Oxfmt-only formats (YAML, TOML, Markdown) are excluded, and
40
+ `sortPackageJson` is pinned off, so migrating never reformats files Biome left alone
41
+
42
+ ✅ **Refuses to Lose Behaviour Silently**
43
+
44
+ Anything the generated configs provably do not reproduce is recorded as a **semantic loss** and
45
+ listed under `losses` in the report. Losses make the migration report `success: false` and block
46
+ both `--delete` and removal of the `@biomejs/biome` dependency, so a lossy conversion can never
47
+ leave a project without a working linter or formatter. Resolve the listed losses and re-run.
38
48
 
39
49
  Positive-only Biome `files`/`linter`/`formatter` include selection has no direct Oxc config
40
- equivalent. The migration reports this explicitly; pass equivalent paths to the Oxc CLI or review
41
- the generated ignore configuration before removing Biome.
50
+ equivalent and is reported as a loss; pass equivalent paths to the Oxc CLI or review the generated
51
+ ignore configuration before removing Biome.
42
52
 
43
53
  ### Advanced Features
44
54
 
@@ -207,11 +217,14 @@ npx biome-to-oxc --report migration-report.json
207
217
  # Print the migration report to stdout as JSON
208
218
  npx biome-to-oxc --json
209
219
 
210
- # Migrate with custom output directory
211
- npx biome-to-oxc --output-dir ./config
220
+ # Migrate with a custom output directory.
221
+ # Oxlint and Oxfmt resolve config globs inside the config file's own directory and reject `..`,
222
+ # so the output directory must be the Biome project root or an ancestor of it. A directory below
223
+ # the project root is rejected whenever the migration would emit any glob.
224
+ npx biome-to-oxc --output-dir ../shared-config
212
225
 
213
226
  # Rewritten scripts explicitly load configs placed outside the package root
214
- npx biome-to-oxc --output-dir ./config --update-scripts
227
+ npx biome-to-oxc --output-dir ../shared-config --update-scripts
215
228
 
216
229
  # Migrate without creating backups
217
230
  npx biome-to-oxc --no-backup
@@ -341,16 +354,24 @@ npx oxlint --type-aware --type-check
341
354
 
342
355
  ## Known Limitations
343
356
 
344
- - Not all Biome rules have direct Oxlint equivalents (warnings will be shown)
357
+ - Not all Biome rules have direct Oxlint equivalents (reported as semantic losses)
345
358
  - Some advanced Biome features may not be supported
346
359
  - Biome recommended/all presets and group-level severities are approximated with Oxlint categories;
347
- preset membership is not identical between the tools
360
+ preset membership is not identical between the tools. This approximation is reported as a warning
361
+ rather than a loss, because it applies to essentially every migration
348
362
  - Positive-only Biome include selection cannot be encoded directly in Oxlint/Oxfmt config files
363
+ - Generated config globs are resolved inside the config file's directory, so `--output-dir` must
364
+ point at the Biome project root or an ancestor of it
365
+ - `formatter.lineEnding: "auto"` has no Oxfmt equivalent (Oxfmt always writes LF)
366
+ - Lockfiles are not regenerated. When dependencies change, the report names the lockfile and the
367
+ install command needed before any frozen-lockfile install
349
368
  - Oxlint overrides cannot carry category presets; explicit override rules are migrated and category
350
369
  presets are reported for manual review
351
370
  - Oxfmt is in beta; review formatting changes before replacing the existing formatter
352
- - Type-aware linting requires TypeScript 7 and `oxlint-tsgolint`; 59 of the 61 targeted
353
- typescript-eslint rules are currently implemented
371
+ - Overlapping `overrides` are migrated as-is. Biome and Oxfmt both merge matching overrides field
372
+ by field with later entries winning, so the two agree; a conformance test pins this
373
+ - Type-aware linting is stable as of `oxlint-tsgolint` v7 and requires TypeScript 7; 59 of the 61
374
+ targeted typescript-eslint rules are currently implemented
354
375
  - CSS and JSON formatter overrides are mapped but may need manual review
355
376
  - Prettier plugin support is not available in Oxfmt
356
377
  - Some Biome rules still require JS plugin fallback until native rule parity is available
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { i as readJsonFile, n as DefaultReporter, r as findClosestPackageJson, t as migrate } from "../src-B1tF4LEv.mjs";
2
+ import { i as readJsonFile, n as DefaultReporter, r as findClosestPackageJson, t as migrate } from "../src-CYMpPpfK.mjs";
3
3
  import { realpath } from "node:fs/promises";
4
4
  import { dirname } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
@@ -19,7 +19,6 @@ const MigrationOptionsSchema = z.object({
19
19
  delete: z.boolean().default(false),
20
20
  noBackup: z.boolean().default(false),
21
21
  updateScripts: z.boolean().default(false),
22
- dom: z.boolean().default(false),
23
22
  verbose: z.boolean().default(false),
24
23
  typeAware: z.boolean().default(false),
25
24
  typeCheck: z.boolean().default(false),
@@ -63,14 +62,16 @@ async function runCli(argv, runtimeOptions = {}) {
63
62
  }
64
63
  return 2;
65
64
  }
66
- const report = await migrate({
65
+ const migrationOptions = {
67
66
  ...validationResult.data,
68
67
  signal: runtimeOptions.signal
69
- }, new DefaultReporter({
68
+ };
69
+ const reporter = new DefaultReporter({
70
70
  verbose: validationResult.data.verbose && !parsedCliOptions.json,
71
71
  stdout,
72
72
  stderr
73
- }));
73
+ });
74
+ const report = await migrate(migrationOptions, reporter);
74
75
  if (parsedCliOptions.json) stdout.write(`${JSON.stringify(report, null, 2)}\n`);
75
76
  else if (report.success) writeHumanSuccessSummary(report, validationResult.data.dryRun, stdout);
76
77
  return report.success ? 0 : 1;
@@ -105,7 +106,7 @@ function createCommand(packageMetadata, streams) {
105
106
  writeOut: (chunk) => {
106
107
  streams.stdout.write(chunk);
107
108
  }
108
- }).option("-c, --config <path>", "Path to biome.json or biome.jsonc").option("-o, --output-dir <path>", "Output directory for generated configs").option("--dry-run", "Show what would be done without making changes").option("--delete", "Delete legacy Biome files after migration (biome.json/biome.jsonc and .biomeignore)").option("--no-backup", "Skip backup of existing config files").option("--update-scripts", "Update package.json scripts to use oxlint/oxfmt").addOption(new Option("--dom").hideHelp()).option("--type-aware", "Include type-aware linting guidance and dependencies").option("--type-check", "Enable strict typed linting mode (implies --type-aware)").addOption(new Option("--type-aware-profile <profile>", "Type-aware profile: standard (--type-aware) or strict (--type-aware --type-check)").choices(["standard", "strict"]).default("standard")).addOption(new Option("--fix-strategy <strategy>", "Fix mode for rewritten scripts: safe | suggestions | dangerous").choices([
109
+ }).option("-c, --config <path>", "Path to biome.json or biome.jsonc").option("-o, --output-dir <path>", "Output directory for generated configs").option("--dry-run", "Show what would be done without making changes").option("--delete", "Delete legacy Biome files after migration (biome.json/biome.jsonc and .biomeignore)").option("--no-backup", "Skip backup of existing config files").option("--update-scripts", "Update package.json scripts to use oxlint/oxfmt").option("--type-aware", "Include type-aware linting guidance and dependencies").option("--type-check", "Enable strict typed linting mode (implies --type-aware)").addOption(new Option("--type-aware-profile <profile>", "Type-aware profile: standard (--type-aware) or strict (--type-aware --type-check)").choices(["standard", "strict"]).default("standard")).addOption(new Option("--fix-strategy <strategy>", "Fix mode for rewritten scripts: safe | suggestions | dangerous").choices([
109
110
  "safe",
110
111
  "suggestions",
111
112
  "dangerous"
@@ -113,7 +114,8 @@ function createCommand(packageMetadata, streams) {
113
114
  return command;
114
115
  }
115
116
  async function loadCliPackageMetadata() {
116
- const packageManifestPath = await findClosestPackageJson(dirname(fileURLToPath(import.meta.url)));
117
+ const packageDirectory = dirname(fileURLToPath(import.meta.url));
118
+ const packageManifestPath = await findClosestPackageJson(packageDirectory);
117
119
  if (!packageManifestPath) throw new Error("Unable to locate the CLI package manifest.");
118
120
  return readJsonFile(packageManifestPath, CliPackageMetadataSchema, `CLI package manifest at ${packageManifestPath}`);
119
121
  }
@@ -128,7 +130,6 @@ function normalizeMigrationOptions(options) {
128
130
  delete: options.delete,
129
131
  noBackup: options.backup === false,
130
132
  updateScripts: options.updateScripts,
131
- dom: options.dom,
132
133
  verbose: options.verbose,
133
134
  typeAware,
134
135
  typeCheck,
@@ -16,6 +16,8 @@ interface BiomeConfig {
16
16
  interface BiomeFilesConfig {
17
17
  include?: string[];
18
18
  includes?: string[];
19
+ /** Negated `includes` entries (`!pattern`), split out during normalization. */
20
+ exclude?: string[];
19
21
  ignore?: string[];
20
22
  ignoreUnknown?: boolean;
21
23
  maxSize?: number;
@@ -31,6 +33,8 @@ interface BiomeLinterConfig {
31
33
  enabled?: boolean;
32
34
  include?: string[];
33
35
  includes?: string[];
36
+ /** Negated `includes` entries (`!pattern`), split out during normalization. */
37
+ exclude?: string[];
34
38
  ignore?: string[];
35
39
  rules?: BiomeLinterRules;
36
40
  }
@@ -55,11 +59,13 @@ interface BiomeFormatterConfig {
55
59
  enabled?: boolean;
56
60
  include?: string[];
57
61
  includes?: string[];
62
+ /** Negated `includes` entries (`!pattern`), split out during normalization. */
63
+ exclude?: string[];
58
64
  ignore?: string[];
59
65
  formatWithErrors?: boolean;
60
66
  indentStyle?: 'tab' | 'space';
61
67
  indentWidth?: number;
62
- lineEnding?: 'lf' | 'crlf' | 'cr';
68
+ lineEnding?: 'lf' | 'crlf' | 'cr' | 'auto';
63
69
  lineWidth?: number;
64
70
  attributePosition?: 'auto' | 'multiline';
65
71
  bracketSpacing?: boolean;
@@ -82,7 +88,7 @@ interface BiomeJavaScriptConfig {
82
88
  bracketSpacing?: boolean;
83
89
  indentStyle?: 'tab' | 'space';
84
90
  indentWidth?: number;
85
- lineEnding?: 'lf' | 'crlf' | 'cr';
91
+ lineEnding?: 'lf' | 'crlf' | 'cr' | 'auto';
86
92
  lineWidth?: number;
87
93
  [key: string]: unknown;
88
94
  };
@@ -101,7 +107,7 @@ interface BiomeJsonConfig {
101
107
  enabled?: boolean;
102
108
  indentStyle?: 'tab' | 'space';
103
109
  indentWidth?: number;
104
- lineEnding?: 'lf' | 'crlf' | 'cr';
110
+ lineEnding?: 'lf' | 'crlf' | 'cr' | 'auto';
105
111
  lineWidth?: number;
106
112
  trailingCommas?: 'none' | 'all';
107
113
  };
@@ -117,7 +123,7 @@ interface BiomeCssConfig {
117
123
  enabled?: boolean;
118
124
  indentStyle?: 'tab' | 'space';
119
125
  indentWidth?: number;
120
- lineEnding?: 'lf' | 'crlf' | 'cr';
126
+ lineEnding?: 'lf' | 'crlf' | 'cr' | 'auto';
121
127
  lineWidth?: number;
122
128
  quoteStyle?: 'single' | 'double';
123
129
  };
@@ -128,6 +134,8 @@ interface BiomeCssConfig {
128
134
  interface BiomeOverride {
129
135
  include?: string[];
130
136
  includes?: string[];
137
+ /** Negated `includes` entries (`!pattern`), split out during normalization. */
138
+ exclude?: string[];
131
139
  ignore?: string[];
132
140
  linter?: BiomeLinterConfig;
133
141
  formatter?: BiomeFormatterConfig;
@@ -298,7 +306,6 @@ interface MigrationOptions {
298
306
  delete?: boolean;
299
307
  noBackup?: boolean;
300
308
  updateScripts?: boolean;
301
- dom?: boolean;
302
309
  verbose?: boolean;
303
310
  typeAware?: boolean;
304
311
  typeCheck?: boolean;
@@ -338,21 +345,45 @@ interface PackageUpdateSummary {
338
345
  dependenciesRemoved: PackageDependencyRemoval[];
339
346
  devDependencies: PackageDevDependencyChange[];
340
347
  changed: boolean;
348
+ /** Lockfile that dependency changes invalidated, when one was detected. */
349
+ lockfile?: LockfileStatus;
350
+ }
351
+ interface LockfileStatus {
352
+ path: string;
353
+ /** Command that regenerates the lockfile from the updated manifest. */
354
+ installCommand: string;
355
+ /** True when this run changed dependencies, so the lockfile no longer matches. */
356
+ stale: boolean;
341
357
  }
342
358
  interface MigrationReport {
359
+ /**
360
+ * True when the migration wrote every requested change without errors AND the
361
+ * generated configuration is a complete replacement for the Biome setup.
362
+ * A migration with semantic losses reports `success: false` because the project
363
+ * still depends on behaviour that Oxc does not reproduce.
364
+ */
343
365
  success: boolean;
344
366
  warnings: string[];
345
367
  errors: string[];
368
+ /** Source behaviour the generated target configs provably do not reproduce. */
369
+ losses: string[];
346
370
  suggestions: string[];
347
371
  packageJson?: PackageUpdateSummary;
348
372
  summary: {
349
373
  biomeConfigPath: string;
350
374
  oxlintConfigPath: string;
351
375
  oxfmtConfigPath: string;
376
+ /** Distinct Biome source rules that produced at least one Oxlint rule. */
352
377
  rulesConverted: number;
378
+ /** Distinct Biome source rules with no Oxlint equivalent. */
353
379
  rulesSkipped: number;
380
+ /** Oxlint rule entries emitted (one source rule can emit several). */
381
+ oxlintRulesEmitted: number;
354
382
  overridesConverted: number;
383
+ /** Oxfmt overrides derived from Biome `overrides` entries. */
355
384
  formatterOverridesConverted: number;
385
+ /** Oxfmt overrides synthesized to carry per-language Biome formatter settings. */
386
+ formatterLanguageOverrides: number;
356
387
  };
357
388
  detectedIntegrations?: {
358
389
  turborepo?: boolean;
@@ -360,16 +391,38 @@ interface MigrationReport {
360
391
  prettier?: boolean;
361
392
  typescript?: boolean;
362
393
  };
394
+ cleanup?: CleanupOutcome;
395
+ }
396
+ interface CleanupOutcome {
397
+ /** Whether `--delete` was requested. */
398
+ requested: boolean;
399
+ /** Whether legacy Biome files were (or in dry-run, would be) removed. */
400
+ performed: boolean;
401
+ /** Why cleanup was withheld, when it was requested but not performed. */
402
+ blockedReason?: string;
403
+ /** Legacy Biome files removed, or that would be removed in dry-run. */
404
+ files: string[];
363
405
  }
364
406
  interface Reporter {
365
407
  warn(message: string): void;
366
408
  error(message: string): void;
367
409
  info(message: string): void;
410
+ /**
411
+ * Records a semantic-loss diagnostic: source behaviour that the generated target
412
+ * configuration provably does not reproduce.
413
+ *
414
+ * Unlike {@link Reporter.warn}, a loss blocks destructive cleanup (`--delete`) and
415
+ * removal of the Biome dependency, because the migrated project is not yet a
416
+ * complete replacement for the Biome setup. Losses are also recorded as warnings so
417
+ * that existing warning consumers keep seeing them.
418
+ */
419
+ loss(message: string): void;
368
420
  getWarnings(): string[];
369
421
  getErrors(): string[];
422
+ getLosses(): string[];
370
423
  }
371
424
  //#endregion
372
425
  //#region src/index.d.ts
373
426
  declare function migrate(options?: MigrationOptions, reporter?: Reporter): Promise<MigrationReport>;
374
427
  //#endregion
375
- export { BiomeConfig, BiomeCssConfig, BiomeFilesConfig, BiomeFormatterConfig, BiomeJavaScriptConfig, BiomeJsonConfig, BiomeLinterConfig, BiomeLinterRules, BiomeOverride, BiomeRuleGroup, BiomeRuleSeverity, BiomeRuleSeverityValue, BiomeVcsConfig, FixStrategy, MigrationOptions, MigrationReport, OxfmtConfig, OxfmtOverride, OxlintBuiltinPlugin, OxlintConfig, OxlintJsPlugin, OxlintOverride, OxlintRuleSeverity, OxlintSettings, PackageDependencyRemoval, PackageDevDependencyChange, PackageScriptUpdate, PackageUpdateSummary, Reporter, TypeAwareProfile, migrate };
428
+ export { BiomeConfig, BiomeCssConfig, BiomeFilesConfig, BiomeFormatterConfig, BiomeJavaScriptConfig, BiomeJsonConfig, BiomeLinterConfig, BiomeLinterRules, BiomeOverride, BiomeRuleGroup, BiomeRuleSeverity, BiomeRuleSeverityValue, BiomeVcsConfig, CleanupOutcome, FixStrategy, LockfileStatus, MigrationOptions, MigrationReport, OxfmtConfig, OxfmtOverride, OxlintBuiltinPlugin, OxlintConfig, OxlintJsPlugin, OxlintOverride, OxlintRuleSeverity, OxlintSettings, PackageDependencyRemoval, PackageDevDependencyChange, PackageScriptUpdate, PackageUpdateSummary, Reporter, TypeAwareProfile, migrate };
@@ -1,2 +1,2 @@
1
- import { t as migrate } from "../src-B1tF4LEv.mjs";
1
+ import { t as migrate } from "../src-CYMpPpfK.mjs";
2
2
  export { migrate };