Skip to content

bit ws-config write / bit install always write Oxlint configuration with generated defaults ignoring custom values #10487

Description

@rvnlord

Summary

Custom Oxlint configuration defined by an environment is ignored when workspace configuration is generated.

Running either:

bit ws-config write

or

bit install

always generates .oxlintrc.json files that extend a cached configuration containing the default values, instead of the custom configuration from the environment.

Environment

  • Windows 10 / Windows 11
  • Bit v2.0.11
  • Node.js v22.16
  • Fresh workspace
  • Clean pnpm store

Steps to reproduce

Create a custom environment:

bit init --default-scope projects
bit install

bit create react-env envs/my-react-env --aspect bitdev.react/react-env
bit create react-app apps/react-frontend --env projects/envs/my-react-env

Replace projects/envs/my-react-env/config/oxlintrc.json with:

{
  "$schema": "https://raw.githubusercontent.com/oxc-project/oxc/main/npm/oxlint/configuration_schema.json",
  "plugins": ["react", "unicorn", "typescript", "oxc"],
  "settings": {
    "react": {
      "version": "19"
    }
  },
  "options": {
    "typeAware": true
  },
  "rules": {
    "no-unused-vars": "off"
  }
}

Then run either:

bit ws-config write

or

bit install

Bit reports:

5 config files added to workspace
2 typescript configurations added
  projects\envs\tsconfig.json

  tsconfig.json

2 oxlint configurations added
  projects\envs\.oxlintrc.json
  .oxlintrc.json

1 prettier configurations added
  .prettierrc.cjs

Actual behavior

The generated .oxlintrc.json points to a cache:

{
  "extends": [
    "./node_modules/.cache/oxlintrc.bit.b3a5458e3e9b6949bee9213fc4dd6a329a94610a.json"
  ]
}

which contains the default configuration instead of the custom one:

{
  "plugins": [
    "react",
    "unicorn",
    "typescript",
    "oxc"
  ],
  "settings": {
    "react": {
      "version": "18"
    }
  },
  "options": {
    "typeAware": true
  }
}

Expected behavior

The generated configuration should reference a cache file containing the effective configuration from the environment rather than the built-in defaults.

Root cause

After looking into EslintConfigWriter, the primary issue appears to be how it handles configurations provided via configPath.

When the writer is created like this:

EslintConfigWriter.from({
  configPath: this.oxlintConfigPath,
  tsconfig: this.tsconfigPath,
})

the configuration is supplied as a file path rather than an object.

The execution flow is:

  1. EslintConfigWriter.create() calls computeEslintConfig().
  2. computeEslintConfig() returns:
{ overrideConfigFile: configPath }

It sets overrideConfigFile, but never loads the file into overrideConfig.

Later, calcConfigFiles() only checks overrideConfig:

const config = this.eslintConfig.overrideConfig;

if (!config) {
  return [];
}

Since overrideConfig is undefined, calcConfigFiles() returns an empty array without writing the environment's configuration to the cache.

As a result, Bit falls back to generating a cached .oxlintrc.bit.<hash>.json containing the default configuration, which is why custom values are lost and replaced with the defaults.

The fix would be to support both configuration modes consistently. Either:

  • calcConfigFiles() should read and serialize overrideConfigFile when overrideConfig is absent, or
  • computeEslintConfig() should populate overrideConfig from the supplied file before returning.

There is also a separate design limitation: EslintConfigWriter is still hardcoded around ESLint naming, so even with this bug fixed it would continue generating .eslintrc.json-style artifacts rather than Oxlint-specific ones.

Workaround

As a temporary workaround, I replaced the built-in EslintConfigWriter with a custom function that creates OxlintConfigWriter which reads the environment's config/oxlintrc.json directly, hashes its contents, writes the corresponding cached .oxlintrc.bit.<hash>.json and generates the extending .oxlintrc.json pointing to that cache file.

workspaceConfig(): ConfigWriterList {
    const cwl = ConfigWriterList.from([
        TypescriptConfigWriter.from({
            tsconfig: this.tsconfigPath,
            types: resolveTypes(__dirname, [this.tsTypesPath]),
        }),
        this.createOxlintConfigWriter(),
        PrettierConfigWriter.from({
            configPath: this.prettierConfigPath
        })
    ]);

    return cwl;
}

private createOxlintConfigWriter(): ConfigWriterHandler {
    const oxlintConfigPath = this.oxlintConfigPath;
    return {
        name: "OxlintConfigWriter",
        handler: () => {
            const configContent = fs.readFileSync(oxlintConfigPath, "utf-8");
            const configHash = sha1(configContent);
            const configFileName = `.oxlintrc.bit.${configHash}.json`;

            const entry: ConfigWriterEntry = {
                id: "oxlint",
                name: "OxlintConfigWriter",
                patterns: ["**/.oxlintrc.json"],
                calcConfigFiles: (): ConfigFile[] => {
                    console.log("Writing OXLint config from:", oxlintConfigPath);
                    return [
                        {
                            content: configContent,
                            name: configFileName,
                            hash: configHash,
                        },
                    ];
                },
                generateExtendingFile: (args: GenerateExtendingConfigFilesArgs) => {
                    const oxlintConfigFile = args.writtenConfigFiles.find(
                        (file) => file.name.includes(".oxlintrc.bit")
                    );
                    if (!oxlintConfigFile) return undefined;

                    const extendingConfig = {
                        extends: [`{${oxlintConfigFile.name}}`],
                    };
                    const content = `${BIT_GENERATED_OXLINT_CONFIG_COMMENT}\n${JSON.stringify(extendingConfig, null, 2)}`;
                    return {
                        content,
                        name: ".oxlintrc.json",
                        extendingTarget: oxlintConfigFile,
                        useAbsPaths: false,
                    };
                },
                isBitGenerated: (filePath: string) => {
                    const content = fs.readFileSync(filePath).toString();
                    return content.includes(BIT_GENERATED_OXLINT_CONFIG_COMMENT);
                },
            };
            return entry;
        },
    };
}

Additional observation: undocumented, dedicated Oxlint writer has similar issues

I just noticed there is an undocumented dedicated package, @teambit/oxc.linter.oxlint-linter, which looks to be the intended replacement for using EslintConfigWriter with Oxlint.

But, replacing EslintConfigWriter with it manually:

import { OxlintConfigWriter, OxlintLinter, OxlintTask } from "@teambit/oxc.linter.oxlint-linter";
OxlintConfigWriter.from({
    configPath: this.oxlintConfigPath
}),

currently results in generating two configuration files, one that's correct (at the root of the workspace) and another one with default values (for the environment variable):

bit ws-config write
2 oxlint configurations added
  projects\commonlib\envs\.oxlintrc.json
  .oxlintrc.json

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions