Skip to content
Ultracite
Esc
↑↓navigate↵open⌘Jpreview
On this page

ultracite@8.2.1

Patch Changes

  • 7c4d3d3: The Biome cypress preset now declares the globals Cypress injects: cy, Cypress, and the Mocha and Chai ones (describe, it, beforeEach, expect, and the rest). The preset was empty, so noUndeclaredVariables reported every one of them in each *.cy.* spec and each file under cypress/. The ESLint preset already declared them.

  • e1ac218: The Biome nestjs and angular presets now parse parameter decorators. Biome rejects them unless javascript.parser.unsafeParameterDecoratorsEnabled is on, so every create(@Body() cat: Cat) in a NestJS controller, and every constructor(@Inject(TOKEN) value) in Angular, was reported as “Decorators are not valid here”, and ultracite fix applied no lint fixes to those files. Both presets now turn the option on.

  • e4702e2: ultracite init no longer leaves Biome unable to start when it adds the presets to a biome.json that already existed. The core preset reads ignore files from version control, and Biome only looks for them in the folder that holds its config, so a config in a package below the repository root, or in a folder with no repository at all, failed afterwards with “Biome couldn’t find an ignore file”. A new config already got vcs.root pointing at the repository, or a .gitignore outside one; an existing config now gets the same, unless it sets vcs itself.

  • e8c2207: ultracite fix no longer breaks dependency injection in NestJS and Angular projects. With the nestjs or angular preset, a class a constructor injects, as in constructor(private readonly cats: CatsService) {}, is only ever named as a type, so typescript/consistent-type-imports (Oxlint and ESLint) and useImportType (Biome) rewrote its import to import type { CatsService }. The code still compiled, but the class was gone at runtime: emitDecoratorMetadata recorded the parameter as Function, and the framework could no longer resolve the dependency. Both presets now turn the rule off on all three linters. If a previous fix already rewrote these imports, change them back to plain imports.

  • 0aecdd6: ultracite doctor now fails a biome.json or biome.jsonc that has a syntax error. It read the file with a parser that recovers from errors, so a truncated config that still listed ultracite/biome/core passed with “Everything looks good!” while ultracite check failed with “Biome exited because the configuration resulted in errors”.

  • 14c0377: The ESLint astro preset no longer reports a return in a page’s frontmatter, such as return Astro.redirect("/404");, as "return" should be used inside a function (unicorn/prefer-module). Astro compiles frontmatter into the page’s render function, so the statement is valid. The Oxlint preset has allowed it since #805; the ESLint preset now does the same, and still reports require, module.exports, __dirname and __filename in .astro files through import-x/no-commonjs and no-restricted-globals.

  • fdc424e: Re-running ultracite init on an ESLint project no longer discards or rewrites parts of an eslint.config.* that already uses Ultracite’s presets:

    • A config that passes the presets to defineConfig() without spreading them, as in defineConfig(core, react, { rules }), was replaced by a bare [...core], losing its other presets, ignores and rules. Those presets are now read like spread ones.
    • A config init can’t regenerate (one exported through a variable, loaded with require, or with the presets inside an entry’s extends) was replaced the same way. It is now left unchanged, with a warning to add new presets by hand.
    • A preset the config only applied to some files, such as { files: ["**/*.test.ts"], extends: [vitest] }, was also spread at the top level, so it applied to every file. It now stays scoped.
    • Presets were reordered to the order of their import statements. They now keep the order the config lists them in.
    • An eslint.config.ts in a package without "type": "module" was rewritten as eslint.config.mjs with its type annotations still in it, which ESLint could not parse. ESLint loads a TypeScript config whatever the package’s module type, so it now stays a .ts file.
  • 6a76431: The ESLint vitest preset no longer aborts the run on a describe() whose title is the name of something the file imports, such as describe("sum", …) next to import { sum } from "./sum". For that case vitest/prefer-describe-function-title asks the type checker what the import is, which threw “You have used a rule which requires type information” in a config without the type-aware presets, so nothing was linted. The rule now runs in ultracite/eslint/vitest/type-aware, with the plugin’s other type-checked rules.

  • 3f9fffd: The type declarations for ultracite/oxfmt and the ultracite/oxlint/* presets now describe what each preset sets, instead of the generic OxfmtConfig or OxlintConfig, where every field is optional. A config that spreads a preset’s fields now type-checks, such as sortTailwindcss: { ...ultracite.sortTailwindcss, stylesheet: "./app/globals.css" } in oxfmt.config.ts, or overrides: [...core.overrides, ...] and plugins: [...core.plugins, ...react.plugins] in oxlint.config.ts. selectJsPlugins() from ultracite/oxlint/js-plugins now declares the overrides, rules and settings it returns as always set, as it already did jsPlugins.

    Because the types are exact, two kinds of code that used to type-check now report errors: reading a field a preset doesn’t set, such as react.overrides, and looking up a rule the preset doesn’t list, such as core.rules["eslint/no-console"] (core lists it as no-console). The field is undefined at runtime, and the rule usually lives in another preset or under another name. The types are checked with exactOptionalPropertyTypes and noUncheckedIndexedAccess on, so configs that use those options type-check as well.

  • 800bc5d: ultracite check and ultracite fix now expand a glob target on Biome and Oxlint setups, such as ultracite check "src/**/*.ts" in a package.json script, where no shell expands the quoted pattern. Neither linter reads a glob as one. Biome looked for a file with that literal name, checked nothing and exited 0, so the script passed without linting anything; Oxlint exited 1 with “No files found to lint” after the formatter had run. A path that exists is still passed as it is, so app/[slug]/page.tsx is unaffected, and ESLint setups are unchanged because ESLint, Prettier and Stylelint expand globs themselves.

  • a5d7a16: ultracite init --hooks no longer rewrites an agent settings file that has a syntax error. The file was read with a parser that recovers from errors and then written back whole, so a .claude/settings.json with unresolved merge conflict markers, for example, came out with the markers gone and one side’s deny rules silently chosen. Such a file is now left unchanged with a warning to fix it and re-run init, as editor settings files already were.

  • 01c8ad3: ultracite init --integrations husky lint-staged no longer adds its own section to a .husky/pre-commit that already runs lint-staged. The section runs lint-staged too, so every commit ran it twice. The existing line is left to run the Ultracite task init adds to the lint-staged config.

  • 92db28c: With the eslint-plugin-jsdoc JS plugin enabled on Oxlint, ultracite fix no longer adds an empty /** */ block above every undocumented export. The block was the fix for jsdoc-js/require-jsdoc, and ultracite check then reported each one as jsdoc/no-blank-blocks, along with a missing @param and @returns. The rule still reports a missing comment; writing it is left to you.

  • ee6b830: ultracite init --integrations lint-staged now adds its entry to a CommonJS lint-staged config (lint-staged.config.js in a package without "type": "module", or a .cjs file) without rewriting the rest of it. It used to load the module and write back module.exports = followed by the evaluated value as JSON, which deleted comments and variables and froze every expression at its value on the machine that ran init: a task built from process.env.CI kept the local branch, and one built from __dirname kept that machine’s absolute path. A config that isn’t an object literal is left unchanged, with a warning naming the entry to add.

  • 467e264: The Oxlint workspace config ultracite init --workspace-framework writes now repeats only the properties the root config sets. It always read root.env, root.ignorePatterns and root.settings, but a root config has no settings unless a JS plugin needs them, so the generated file had a type error (Property 'settings' does not exist), and with --type-aware the project failed ultracite check straight after init with typescript(no-unsafe-assignment). If you add settings to the root config later, add settings: root.settings to the workspace config as well.

  • 8297ff4: Ultracite now recognises .oxlintrc.jsonc, which Oxlint loads like .oxlintrc.json. A project whose only Oxlint config had that name got “No linter configuration found” from check, fix, doctor and upgrade. ultracite init wrote an oxlint.config.ts next to it without migrating it, after which Oxlint refused to run with both, and doctor reported that setup as healthy. init now moves its settings into the TypeScript config and removes it, as it does for .oxlintrc.json, and doctor fails when it sits beside another Oxlint config.

  • 6c29ae8: ultracite check and ultracite fix no longer fail on an ESLint setup when given a file with no extension, such as a Dockerfile, Makefile, LICENSE, .gitignore or .env. Ultracite took any target without an extension for a directory and handed Stylelint <file>/**/*.{css,scss,less}, which crashed with ENOTDIR: not a directory, scandir. That blocked commits through the Husky, Lefthook and pre-commit hooks whenever one of those files was staged, and made fix --hook report a failure to the agent that had edited one. Stylelint now skips them, as ESLint and Prettier already did.

  • d100a28: ultracite upgrade --version now refuses a release older than 7.11.0, the first with an upgrade command, before it installs anything. It used to install that release and then hand off to its upgrade, which failed with error: unknown command 'upgrade' and left the old ultracite in package.json beside a linter nothing had synced to it. A major or dist-tag that still resolves to such a release is installed with a warning instead of the failed hand-off.

  • 1eea8d7: The vitest presets for Oxlint and ESLint no longer run vitest/prefer-importing-vitest-globals. The rule treats a test or expect imported from anywhere but vitest as a global, and its fix adds import { expect, test } from "vitest" above the existing import. The presets cover every *.test.* and *.spec.* file, so in a project that uses Vitest for unit tests and Playwright for end-to-end tests, ultracite fix rewrote each Playwright spec into a file that declares expect and test twice and no longer parses. Files importing from bun:test and node:test were reported the same way. If a previous fix added the import, delete it.

Last updated on

Was this page helpful?