ultracite@7.12.2
Patch Changes
-
0f192a7:
ultracite initno longer wipes an existing Biome config it can’t read. Previously abiome.jsonorbiome.jsoncwith a syntax error, or a nested monorepo config with"extends": "//", was treated as empty and replaced with just the Ultraciteextends, losing every other setting. Now:- A config with a syntax error is left unchanged, with a warning asking you to fix it and re-run
init. - A nested config that extends the root config (
"extends": "//") is left unchanged, since the Ultracite presets belong in the root config. - A string
extendsis turned into a list that also includes the Ultracite presets. - Updates edit the file in place, so comments and formatting in
biome.jsoncare preserved.
ultracite doctorand thecheck/fixresolution check now follow a nested config that extends"//"to the root config, instead of warning that the nested config doesn’t extendultracite/biome/core. - A config with a syntax error is left unchanged, with a warning asking you to fix it and re-run
-
c68ba48:
ultracite checkandultracite fixhandle their arguments and missing tools more reliably:checknow treats explicit files the wayfixdoes. Oxlint only gets files it can lint, Prettier runs with--ignore-unknown, and oxfmt with--no-error-on-unmatched-pattern. Before,ultracite check README.mdorultracite check Dockerfile src/index.tsfailed even though nothing was wrong.- Linter flags that take a value keep it, even when the value looks like a file:
--tsconfig tsconfig.json,-c .oxlintrc.json,--config-path biome.json,--only lint/suspicious/noDebugger,--since origin/mainand similar. Before, the value was treated as a lint target, soultracite fix --tsconfig tsconfig.jsonskipped Oxlint entirely and only formattedtsconfig.json. Ultracite’s own--claude,--codex,--hookand--unsafenever take a value, so the next argument is always a target. ultracite fix --unsafewith the ESLint toolchain no longer fails with ESLint’s “Invalid option ‘–unsafe’”. ESLint has no unsafe fixes, so the flag is dropped with a warning.- A linter that isn’t installed is reported with a plain message instead of a stack trace. The other tools still run first. Stylelint is optional in the ESLint toolchain, as
ultracite doctoralready said, so a project without it now skips CSS linting with a warning instead of failing. “No linter configuration found” is also printed without a stack trace. - Linters installed in the project’s
node_modules/.binare found even when Ultracite isn’t run through a package manager script,npxorbunx, for example./node_modules/.bin/ultracite check.
-
f61f393:
ultracite initnow looks for existing ESLint, Prettier and Stylelint configs in the same order the tools do, so when a project has more than one, init updates the one the tool actually loads. For example, Prettier reads.prettierrc.jsonbeforeprettier.config.mjs, and ESLint readseslint.config.jsbeforeeslint.config.mjs. Before, init could update a config the tool ignored and leave the active one in place. Stylelint’s.stylelintrc.tsandstylelint.config.tsare now recognised too. -
242cd2a:
ultracite initnow updates an existing.vscode/settings.jsonor.zed/settings.jsonin place, so your comments and formatting are kept. Before, the file was re-serialised as plain JSON, which stripped every comment. A settings file with a syntax error is now left unchanged with a warning. Before, it was rewritten with whatever part the parser could recover, which dropped the rest.For the ESLint toolchain, init now also installs the Prettier VS Code extension (
esbenp.prettier-vscode), since the settings it writes make Prettier the default formatter. Before, only the ESLint extension was installed, so format-on-save did nothing until you added Prettier yourself. -
c273393:
ultracite initnow checks every flag value before it changes anything in the project. An unknown value for--linter,--pm,--frameworks,--editors,--agents,--hooks,--integrationsor--js-pluginsstops init with a message listing the valid values. Previously a misspelled--linter(for example--linter Biome) deleted every existing Biome, ESLint, Prettier, Stylelint, Oxlint and oxfmt config file and then crashed.When
--linteris not passed and init runs without prompts (because of--quiet,CI, or flags such as--agentsor--pm), it now keeps the linter the project is already set up with and only falls back to Oxlint when there is none. Runningultracite init --agents universalon a Biome project no longer migrates it to Oxlint. The interactive linter prompt also preselects the detected linter. -
3cb2e71: Clearer wording in the CLI:
ultracite init --helpnow lists the valid values for--pm,--linter,--frameworks,--hooksand--integrations, and explains what--type-awaredoes for Biome and for Oxlint.- The agent rules file says “Oxlint + Oxfmt will catch most mechanical issues automatically” instead of “Oxlint + Oxfmt’s linter will catch…”.
initandupgradesay “Using pnpm (detected from the project)” instead of “Detected lockfile”, since the package manager can also come frompackageManager.ultracite doctorspells “unrecognized” consistently, formats commands as code, and describes warnings as “Some checks have warnings” instead of “optional improvements”.
-
6ae8be8: The ESLint
nestjspreset imports@darraghor/eslint-plugin-nestjs-typed, butultracite init --linter eslint --frameworks nestjsnever installed it, so ESLint failed to load the config with “Cannot find package”.initnow installs the plugin with the preset, andultracite upgradeinstalls the plugins of every framework preset youreslint.config.*imports, so existing NestJS projects pick it up on their next upgrade. -
a8e83bb:
ultracite initnow migrates an existing.oxlintrc.json,.oxfmtrc.jsonor.oxfmtrc.jsoncwhen it sets up Oxlint. Oxlint and oxfmt refuse to load any config when a JSON config sits next tooxlint.config.tsoroxfmt.config.ts, and init used to write the TS configs beside the JSON ones, soultracite checkandultracite fixstopped working. Init now moves the JSON config’s settings into the TS config and deletes the JSON file:rules,overrides,env,pluginsand other options become properties of the generated config.ignorePatterns,settingsandjsPluginsare added to the ones Ultracite generates instead of replacing them.- Ultracite
extendsentries become presets. Otherextendspaths can’t be referenced from a TS config, so init names them in a warning.
A JSON config that doesn’t parse is left in place, and neither file is written.
Re-running
ultracite initalso keeps what you added tooxlint.config.tsandoxfmt.config.ts: customrules,overrides,ignorePatterns,settingsand other properties, extraextendsentries, your own imports and statements, and comments are carried over while the Ultracite parts are regenerated. Before, both files were regenerated from scratch. A config that doesn’t parse is now left unchanged with a warning instead of being overwritten.ultracite doctornow fails when a JSON config and a TS config for Oxlint or oxfmt sit side by side, and suggests runninginitto migrate a lone.oxfmtrc.json. -
981ef2f:
ultracite init --linter oxlintno longer adds"type": "module"topackage.json. That field changes how Node loads every.jsfile in the package, so CommonJS files such as anext.config.js,postcss.config.jsorjest.config.jsusingmodule.exportsstopped working after init.Instead, init writes the Oxlint and oxfmt configs as
oxlint.config.mtsandoxfmt.config.mtswhen the package isn’t an ES module package (no"type"or"type": "commonjs"). A.mtsfile always loads as an ES module, with noMODULE_TYPELESS_PACKAGE_JSONwarning on every run, and it works under"type": "commonjs". ES module packages ("type": "module") still getoxlint.config.tsandoxfmt.config.ts.Re-running init updates an existing config under the name it already has. The one exception is a
.tsconfig in a"type": "commonjs"package, which Node can’t load: init renames it to.mtsand says so.ultracite doctor, linter detection and the stale-config cleanup all recognise the.mtsnames.doctorfails a.tsconfig in a CommonJS package, and fails when a.tsand an.mtsconfig sit side by side.Requires oxfmt >= 0.59.0, the first release that finds
oxfmt.config.mtson its own. -
9a9beb0:
ultracite initis gentler withpackage.json:- It keeps the file’s key order, indentation and line endings. Before, every write moved
devDependenciesto the top and pushed keys likeprivate,mainandexportsto the end. - It no longer overwrites a
checkorfixscript the project already has (for example"check": "tsc --noEmit"). It prints a warning instead, and it only adds the scripts that are missing. Existing scripts that already run Ultracite with extra flags are left as they are. - When switching linters, it only removes the previous linter’s packages from
devDependencies. Packages independenciesandpeerDependenciesstay, such asprettierused at runtime oreslintas the peer of a published plugin. - With
--skip-install, the final message now says the dependencies were added topackage.jsoninstead of claiming they were installed.
- It keeps the file’s key order, indentation and line endings. Before, every write moved
-
c3027a5: Re-running
ultracite initno longer silently discards changes to an ESLint, Prettier or Stylelint config that builds on Ultracite’s:eslint.config.*: the framework presets it already spreads are kept alongside any newly selected ones, together with your own config objects, imports and adefineConfig(...)wrapper. Before, the file was regenerated from the selected frameworks only, dropping earlier presets and every custom rule.prettier.config.*: your options, imports and extra plugins are kept. Framework plugins are added, andprettier-plugin-tailwindcssstays last.- Stylelint: a config that already uses
ultracite/stylelint, including one inpackage.json, is left as it is.
A config that doesn’t use Ultracite’s presets is still replaced, but init now prints a warning naming the file (or the
package.jsonkey) it replaced. A config that doesn’t parse is left unchanged with a warning. With the ESLint toolchain, a"prettier"or"stylelint"key inpackage.jsonis now handled by these updates (and reported when replaced) instead of being deleted silently during migration. -
44d6997:
ultracite initno longer creates directories for agent rule files or agent hook configs (such as.claude/or.cursor/) before checking that the path stays inside the project. When one of those directories was a symlink to somewhere outside the project, init created the missing directories there before it refused to write the file. -
33a1ccd:
ultracite init --integrations husky --skip-installno longer replaces an existingpreparescript inpackage.json. It addshuskyto the script, as the installing path already did (for example"prepare": "svelte-kit sync && husky"). The Lefthook setup messages now name the config file init actually updates or creates, such as.lefthook.yaml, instead of always sayinglefthook.yml. -
923667b:
ultracite initno longer overrides a tsconfig that turnsstrictNullChecksoff. It leaves an explicit"strictNullChecks": false, whether set in the file or in a config it extends, as it is and prints a warning. It also followsextends, whether a relative path or a package such as@tsconfig/strictest, so a tsconfig that already inheritsstrictorstrictNullChecksis no longer given a redundant"strictNullChecks": true. -
199974c:
ultracite initandultracite upgradenow work in Yarn 2+ monorepos. The root install used to pass Yarn 1’s-Wflag, which Yarn 2+ rejects with “Unsupported option name”, whenever the Yarn version wasn’t named inpackage.json’spackageManagerfield. That happened with--pm yarn, with lockfile-only detection, and always in the second half ofupgrade, which re-ran the new CLI with a bare--pm yarn. Ultracite now tells Yarn 2+ apart from Yarn 1 by.yarnrc.ymlor the lockfile format.upgradeonly forwards--pmto the newly installed CLI when you passed it.ultracite doctor(and the check at the end ofupgrade) no longer fails in Yarn Plug’n’Play projects just because it can’t find Ultracite or the tools innode_modules. It warns that it can’t verify them there instead. -
c33158d: Agent hooks set up by
ultracite init --hookswork in more places and no longer double up.- GitHub Copilot:
.github/hooks/ultracite.jsonis now written in the format the Copilot CLI and Copilot cloud agent read:"version": 1and a camelCasepostToolUseevent. VS Code reads this format too. The previous file, with a PascalCasePostToolUseand noversion, was only picked up by VS Code. Re-running init moves an existing Ultracite hook to the new format instead of keeping both. fix --hookwith Copilot: the file path is now read from Copilot payloads:toolArgs.pathfrom the Copilot CLI (including whentoolArgsis a JSON string),tool_input.filePathfrom VS Code’s edit tools, every file of amulti_replace_string_in_fileedit, and the file headers of anapply_patch. So only the edited files are fixed, not the whole project. These hosts run the hook after every tool, so tools that edit nothing (such asread_file,vieworbash) now skip the fix instead of fixing the whole project.- Re-running init no longer adds a second hook next to one generated by Ultracite 7.8.2 or earlier:
pnpm fix …,bun fix …,yarn fix …, ornpm run fix --skip=…. That older hook is updated in place, so it stops running a whole-project fix after every edit. - A hook is now added even when the settings file mentions
ultracitesomewhere else, such as a Claude Code permission rule likeBash(npx ultracite check). Only existing hook commands are checked. fix --hooknow fixes a file at the project root whose name starts with.., instead of treating it as outside the project.- Zed: the
includePackageJsonAutoImportspreference is now set for vtsls, Zed’s default TypeScript and JavaScript language server (lsp.vtsls.settings), for both TypeScript and JavaScript. It was previously set undertypescript-language-server, which Zed doesn’t use by default.
- GitHub Copilot:
-
dc5e18e: Re-running
ultracite initnow updates the Ultracite rules inAGENTS.md,.claude/CLAUDE.md,GEMINI.mdand the other shared rule files in place, instead of appending another full copy.- Previously a copy was appended whenever the linter, the package manager or the rules text changed. For example, switching from Biome to Oxlint left instructions for both engines side by side. Duplicate copies written by earlier versions are now removed, and your own content before and after the block is kept.
- Files with CRLF line endings keep them, and are no longer duplicated on every run.
- The commands in the rules now run the project’s installed CLI (
npx ultracite fix,yarn ultracite fix,pnpm exec ultracite fix,bunx ultracite fix) instead of a dlx runner. For Deno they readdeno run -A npm:ultracite fix; previously the brokendeno run -A npm: ultracite fixwas written. - Firebender: rules are now written to
.firebender/rules/ultracite.mdcwithalwaysApply: true, where Firebender reads project rules. They were previously written as Markdown intofirebender.json, which made that file invalid JSON and overwrote an existing Firebender config. Afirebender.jsonthat still holds only those Markdown rules is reset to{}.
-
2ad7704: Fixes for
ultracite fix --claudeandultracite fix --codex:- On a terminal, the ✓/✗ result for every issue is now kept once a file is done. Previously a file with more issues than the terminal had rows kept only the last screenful of results.
- In CI logs and other piped output, lint messages are no longer cut off at 80 columns.
- The prompt is now sent to the agent CLI on stdin (
claude -p,codex exec -) instead of as a command-line argument. On Windows, an npm-installed CLI runs through a.cmdshim, which can cut an argument off at its first newline, so the agent received only the prompt’s first line and never saw the file or its issues. --codexnow passes--skip-git-repo-check, so it also works in a project that isn’t a git repository. Previouslycodex execrefused to run there and every file failed.
-
0b0775e: The Husky pre-commit hook written by
ultracite init --integrations huskyis more robust.- The hook now runs the project’s installed tools (
npx,yarn,pnpm exec,bunx) instead ofyarn dlx/pnpm dlx.yarn dlxdoes not exist in Yarn 1, so every commit failed with “Ultracite found issues that could not be auto-fixed”, andpnpm dlxdownloaded the latest release instead of the version the project pins. Husky itself is also initialised with the installed binary. - Installing Husky now chains
huskyonto an existingpreparescript (for example SvelteKit’ssvelte-kit sync || echo '') instead of replacing it. Choosing Husky and lefthook together no longer leaves only one of them inprepare. - When nothing is staged, the hook no longer exits early, so commands you keep after the Ultracite section still run.
- Re-running init replaces the Ultracite section of a hook saved with CRLF line endings instead of appending a second one. It also no longer drops the last line of a hook that only mentions “# ultracite” in a comment. Commands after a lint-staged section written by an older version are kept.
- The hook now runs the project’s installed tools (
-
3862221:
ultracite init --integrations lint-stagedno longer damages or shadows an existing lint-staged config.- An extension-less
.lintstagedrcwritten in YAML (which is how lint-staged reads it) is now edited as YAML. Previously it was overwritten with a JSON file holding only the Ultracite task, deleting the user’s own tasks, and single-quoted YAML was silently skipped. - YAML configs are edited through the
yamlDocument API, so comments and the rest of the file are kept. - TypeScript configs (
.lintstagedrc.ts,.mts,.ctsandlint-staged.config.ts,.mts,.cts) and thelint-stagedkey ofpackage.yamlare now recognised. Previously init created a.lintstagedrc.jsonnext to them, and because lint-staged uses only the first config in a directory, the user’s config silently stopped running. - When a config can’t be edited automatically (a function-based entry, a CommonJS module that can’t be loaded, a syntax error), init now leaves it untouched and prints the command to add, instead of writing a second config file that would replace it.
- When both exist, the dedicated config file is updated rather than the
package.jsonkey, matching the file lint-staged actually uses. - A single command already mapped to the same glob is kept alongside
ultracite fixinstead of being replaced. - The task now runs the project’s installed Ultracite (
npx,yarn,pnpm exec,bunx) instead ofyarn dlx/pnpm dlx, which don’t exist in Yarn 1 or download the latest release instead of the pinned one. Re-running init upgrades adlxcommand written by an earlier version.
- An extension-less
-
bffb8dd: Corrected the bundled Ultracite agent skill (
skills/ultracite/SKILL.md):- It said Oxlint has
githubandsonarjspresets that init includes by default. Neither exists. The GitHub, SonarJS and React Doctor rules live in theultracite/oxlint/js-pluginspreset (plusnext/js-pluginsandtanstack/js-plugins), next to theanti-slopandshadcnpresets, and all of them are opt-in. - The
--js-pluginsinit flag is now documented. - The skill now tells agents to run the project’s installed CLI (
npx,pnpm exec,yarn,bunx) rather thanpnpxoryarn dlx.
- It said Oxlint has
-
da2934a: Installing the Ultracite skill from
ultracite initnow works when init runs in a normal terminal.Init runs
skills add haydenbleasel/ultracitewithout a terminal attached. When theskillsCLI needed to ask which agents to install to, or whether to install into the project or globally, it could not show the prompt:- In some cases it cancelled the prompt and exited successfully with nothing installed, so init said “Ultracite skill installed.” when the skill wasn’t there.
- In others it failed, so the install never happened.
Init now passes
--yesand names the agents to install for, soskillsinstalls without asking. The skill always goes to the shared.agents/skillsdirectory, which Codex, Cursor, GitHub Copilot, Gemini CLI, Amp, Cline, OpenCode and other agents read. It also goes to the skills directory of any agent whose project folder exists (for example.claude,.windsurf,.codebuddyor.roo, which init creates when you choose those agents).Init names the agents because otherwise, in a project where
skillsdetects no agent,--yeswould install for every agent it knows. That also created a.claude/skillslink and a second copy in a top-levelagent/directory (for the Eve framework). The “install it later” hint still shows the interactiveskills add haydenbleasel/ultracitecommand. -
4941883:
ultracite initnow edits.pre-commit-config.yamland lefthook configs through theyamlDocument API instead of splicing text, so the result is always valid YAML and comments are kept.- pre-commit: a
repos:list written flush with its key (- repo:, the stylepre-commit sample-configgenerates) or with four-space indentation no longer turns into invalid YAML that breaks every commit. The file’s sequence style is kept. - lefthook: a four-space indented
pre-commit:block, a compactjobs:list, or a column-0 comment inside the block no longer produce invalid YAML or a duplicatejobs:key. A hook that usescommands:gets ajobs:list next to it. - lefthook: init now finds an existing
.lefthook.yml,lefthook.yaml,.config/lefthook.yml(and the other names lefthook reads) and edits it. Previously it createdlefthook.yml, which lefthook picks first, so the user’s own config silently stopped running. A JSON or TOML config is left alone with instructions. - lefthook: the job’s globs are now
*.js,*.ts, and so on. With lefthook’s default matcher, the previous**/*.jsform skipped root-level files such aspackage.jsonorindex.ts. Projects that setglob_matcher: doublestarget**/*.js. - lefthook: re-running init with a different package manager updates the existing job instead of adding a second one.
- Both now run the project’s installed Ultracite (
npx,yarn,pnpm exec,bunx) instead ofyarn dlx/pnpm dlx. Re-running init upgrades a hook or job written by an earlier version. lefthook installruns the project’s installed lefthook, and thepreparescript is chained onto an existing one (e.g.svelte-kit sync || echo '' && lefthook install) instead of replacing it.
- pre-commit: a
-
9c30c15: Raise the
oxlintpeer dependency to^1.82.0. The core Oxlint preset configuresno-unmodified-loop-conditionwithcheckConditionalExpressions, an option Oxlint 1.79 to 1.81 reject, so those releases failed with “Failed to parse oxlint configuration file” even though the old^1.79.0range allowed them.ultracite doctorand your package manager now flag the too-old version instead.Requires oxlint >= 1.82.0
-
47303f4: The Biome
jestpreset now declares Jest’s globals (describe,it,test,expect,jest,beforeEachand the rest ofglobals.jest) for test files. Jest injects them without imports, sonoUndeclaredVariablesfrom the core preset reported every one of them in idiomatic Jest tests. The ESLintjestpreset already declared them.The route-file exemptions in the Biome
remixandtanstackpresets (useFilenamingConventionoff inroutes/) now cover.jsand.jsxroute files as well as.tsand.tsx. -
8f236ee: The ESLint core preset now lints
.jsx,.tsx,.mtsand.ctsfiles. Its main block previously matched only.js,.mjs,.cjs,.ts,.jsonand.html, so ESLint skipped.mtsand.ctsfiles entirely (“File ignored because no matching configuration was supplied”), and.jsxand.tsxfiles received only the framework preset’s rules..tsxfiles were also parsed by espree, so any TypeScript syntax in them was a parse error. Every TypeScript extension now uses@typescript-eslint/parserwith type information, and the core rules apply to all of them.Type information now comes from the TypeScript project service (
parserOptions.projectService: true) instead of a fixedproject: "./tsconfig.json". Solution-style tsconfigs ("files": []plusreferences, as in the Vite templates) and monorepo packages now resolve their own tsconfig, where previously every file failed to parse. A file that no tsconfig includes still has to be added to one; typescript-eslint reports it as “not found by the project service” (see https://typescript-eslint.io/troubleshooting/typed-linting/#i-get-errors-telling-me-was-not-found-by-the-project-service-consider-either-including-it-in-the-tsconfigjson-or-including-it-in-allowdefaultproject).The JavaScript block no longer matches
**/*.json. espree cannot parse JSON, so everypackage.jsonandtsconfig.jsonfailed withParsing error: Unexpected token :. The ESLint preset does not lint JSON, matching the Oxlint preset. -
94722fa: The
eslint.config.mjsthatultracite initwrites no longer fails its own lint. Every ESLint preset exported a default namedconfig, soimport core from "ultracite/eslint/core"(and each framework import) trippedimport-x/no-rename-defaulttwice per preset. Each preset’s default export is now named after the identifier the generated config imports it as (core,react,tanstack, …). -
d9803a6: Fix ESLint presets that crashed the whole run or never reached the files they target.
- TanStack:
@tanstack/start/no-async-client-componentand@tanstack/start/no-client-code-in-server-componentneed type information and aborted ESLint (“You have used a rule which requires type information”) on the first.jsxor.tsxfile. They now run only on TypeScript files. The Query, Router and Start rules also apply to plain.ts/.jsmodules, where query and mutation options are usually declared, instead of only to.jsxand.tsx.sort-keysis off, andno-use-before-defineandunicorn/filename-caseare off inroutes/directories, matching the Oxlint and Biome TanStack presets (alphabetical option keys break TanStack’s type inference, and file routes are mutually recursive and encode the URL in the filename). - Qwik:
qwik/valid-lexical-scopeneeds type information and crashed ESLint on.jsxfiles. It now runs only on.tsxfiles. - Jest:
jest/no-deprecated-functionsthrew “Unable to detect Jest version” and crashed ESLint when thejestpackage was not resolvable from the plugin, for example in bun:test projects. The preset now reads the Jest version from the project and falls back to the latest major. - Cypress: the block used
globals.cypress, which does not exist in theglobalspackage, socy,Cypress,describeanditwere reported byno-undef. It now useseslint-plugin-cypress’s own globals config. It also matched only*.cy.js, and now covers*.cy.*for every JS and TS extension plus files undercypress/. - Storybook: the block matched only
*.stories.jsand*.stories.ts. It now covers*.stories.*and*.story.*for every JS and TS extension, including.tsxand.jsx.
- TanStack:
-
d6ee272: The ESLint
vue,svelteandastropresets now parse their files. They registered only the plugin and its rules, never a parser, so ESLint read every.vue,.svelteand.astrofile with espree and reported a parse error (Unexpected token <) instead of linting it.Each preset now starts from its plugin’s own base config (
flat/baseineslint-plugin-vue,eslint-plugin-svelteandeslint-plugin-astro), which registersvue-eslint-parser,svelte-eslint-parserorastro-eslint-parserfrom the plugin’s dependencies, and parses<script lang="ts">blocks with@typescript-eslint/parser. The Svelte preset also covers.svelte.jsand.svelte.tsrune modules. In Astro files, client-side<script>tags are linted without type information, because those in-memory virtual files cannot be type-checked. -
f7599ef: The ESLint core preset no longer re-enables formatting rules that
eslint-config-prettierturns off. The unicorn rule set was spread aftereslint-config-prettier, which switchedunicorn/number-literal-case,unicorn/template-indentandunicorn/empty-brace-spacesback on.unicorn/number-literal-casedemands0xABCDwhile Prettier prints0xabcd, soprettier/prettierand the unicorn rule reported every hex literal andeslint --fixcould not satisfy both.eslint-config-prettiernow applies after every plugin, sounicorn/number-literal-caseandunicorn/template-indentare off.unicorn/empty-brace-spacesstays on (Prettier prints empty braces as{}too), matching the Oxlint preset. -
efa80e8: The ESLint core preset no longer reports the same problem two or three times on TypeScript files, and no longer flags ambient type-only globals.
no-undefandsonarjs/no-reference-errorare off for.ts,.tsx,.mtsand.ctsfiles. TypeScript already reports unresolved identifiers, and both rules flagged valid ambient types such asNodeJS.Timeout(see the typescript-eslint FAQ).- The base
class-methods-use-this,consistent-return,no-unused-private-class-members,prefer-destructuringandprefer-promise-reject-errorsrules are off for TypeScript files, because their@typescript-eslintextension rules are enabled there. The base rules duplicated every report and ignored the extensions’ TypeScript-aware options. unused-imports/no-unused-varsis off. It is a copy ofno-unused-vars/@typescript-eslint/no-unused-vars, which stay on as in the Oxlint preset, so every unused variable was reported twice.unused-imports/no-unused-importsstays on for its autofix.@typescript-eslint/max-paramsis off, matchingmax-paramsbeing off in the core presets. It was on with its default limit of 3 parameters for TypeScript files only.
-
9ae95d1: Fix false positives in the ESLint
vue,svelteandastropresets that appeared once their files were actually parsed.vue/block-langandsvelte/block-langrejected every<script lang="ts">, because with no options they only allow blocks without alangattribute. They now allow TypeScript scripts, andlessandscssstyles, as well as blocks withoutlang.- Astro’s client-side
<script>tags are linted as virtual files named1_1.ts,2_1.jsand so on, whichgithub/filenames-match-regexreported on every page with a script tag. Filename rules are now off for those virtual files. - The Astro preset passes
@typescript-eslint/parserfor the frontmatter explicitly. ESLint’s config merge dropped the copy ineslint-plugin-astro’s base config, so frontmatter TypeScript parsed only when@typescript-eslint/parserhappened to resolve from the working directory, and failed withThe keyword 'interface' is reservedotherwise. ultracite initnow installseslint-plugin-jsx-a11yfor Astro projects using ESLint. The preset enableseslint-plugin-astro’sastro/jsx-a11y/*rules, which load that package and reported “you need to install eslint-plugin-jsx-a11y” on every.astrofile when it was missing.
-
e88e76d: The Prettier preset now sets
tailwindFunctionstoclsx,cva,tw,twMerge,cn,twJoinandtv, soprettier-plugin-tailwindcsssorts Tailwind classes passed to those helpers anywhere in a file, for exampleconst button = cva("p-4 flex"). Previously only class strings insideclass/classNameattributes were sorted with Prettier, while the Oxfmt (sortTailwindcss.functions) and Biome (useSortedClasses) presets already sorted these helpers. See https://github.com/tailwindlabs/prettier-plugin-tailwindcss#sorting-classes-in-function-calls. -
3a5885e:
github/filenames-match-regexnow accepts file-based routing filenames in the ESLint preset too, and Next.js’s special pages in both presets.- The page-route regex (Oxlint
js-pluginsand now ESLintcore) allows a leading underscore, so the Next.js pages-router files_app,_documentand_error, which cannot be renamed, are no longer reported. Other names inpages/(BadPage.ts) and camelCase route params ([postId], whichunicorn/filename-caserejects too) are still reported. - The ESLint
corepreset now carries the same route exemptions as the Oxlintjs-pluginspreset (#799, #804). Previously it applied the plain kebab-case regex everywhere and reported SvelteKit’s+page.tsand+server.ts, TanStack Router and React Router files such asposts.$postId.ts, and page routes such as[slug].json.tsand[...auth].ts. - The route-directory exemptions (
github/filenames-match-regexin Oxlintjs-pluginsand ESLintcore, plusunicorn/filename-caseandno-use-before-definein the Oxlint and ESLinttanstackpresets) now cover.jsand.jsxroute files as well as.tsand.tsx.
- The page-route regex (Oxlint
-
ad2adb0: The Stylelint preset now handles Tailwind CSS v4, SCSS and Less, and skips build output.
- Tailwind CSS v4:
@theme,@utility,@variant,@custom-variant,@slot,@pluginand@configare allowed byat-rule-no-unknown, alongside the Tailwind v3 at-rules and@sourceand@reference.at-rule-prelude-no-invalidno longer rejects every@apply(or the other Tailwind at-rules’ preludes).custom-property-patternaccepts theme namespace resets (--color-*: initial) and sub-properties (--text-xl--line-height).declaration-property-value-no-unknownandfunction-no-unknownaccept--spacing(),--alpha(),--value(),--modifier()andtheme().media-query-no-invalidacceptstheme()breakpoints, andnesting-selector-no-missing-scoping-rootallows&inside@custom-variant,@variantand@utility. See https://tailwindcss.com/docs/functions-and-directives. import-notationis now"string".stylelint-config-standard’s"url"autofix rewrote@import "tailwindcss"to@import url("tailwindcss").- SCSS and Less:
.scssfiles are parsed withpostcss-scssand.lessfiles withpostcss-less. Previously every SCSS and Less file failed withCssSyntaxError(for example on//comments or mixin calls). The CSS-only rules that cannot understand preprocessor syntax (unknown at-rules such as@use,@includeand@mixin,$variableand@variablevalues, Sass and Less built-in functions,//comments) are relaxed for those files.ultracite initnow installspostcss-scssandpostcss-lesswith the ESLint toolchain. .sass: the indented Sass syntax has no maintained PostCSS parser, soultracite checkandultracite fixno longer pass.sassfiles to Stylelint, where every one failed to parse.- Ignores:
ignoreFilesnow uses the shared ignore list (dist,build,.next,coverage,storybook-staticand so on). Stylelint does not read.gitignore, so it previously linted generated CSS. - Prettier:
prettier/prettieris now enabled. The preset loadedstylelint-prettierbut never turned its rule on. ultracite checkandultracite fixnow escape glob characters in directory arguments before passing them to Stylelint. A directory such asapp/(marketing)previously matched no files.
- Tailwind CSS v4:
-
89989df: Test-file overrides in every preset now match
.mts,.cts,.mjsand.cjstest files as well as.ts,.tsx,.jsand.jsx. The globs were**/*.{test,spec,test-d,spec-d}.{ts,tsx,js,jsx}and**/__tests__/**/*.{ts,tsx,js,jsx}, so asum.test.mtsfile got none of the test-file relaxations and none of the Jest or Vitest rules. For example,test.onlyin a.test.mtsfile was not reported byvitest/no-focused-testsorjest/no-focused-tests. This covers the Oxlintcore,js-plugins,jestandvitestpresets, the Biomecorepreset, and the ESLintcore,jestandvitestpresets. -
4d28cf9: The Oxlint
vuepreset now lists every non-nursery Vue rule Oxlint implements, like the other framework presets. It previously enabled 18 of them. Among the 27 newly enabled rules arevue/valid-define-props,vue/valid-define-emits,vue/no-export-in-script-setup,vue/no-lifecycle-after-await,vue/no-arrow-functions-in-watch,vue/no-side-effects-in-computed-properties,vue/require-typed-ref,vue/define-props-destructuringand thevue/no-deprecated-*rules.vue/max-propsis off in both the Oxlint and ESLint presets, because its default allows a single prop per component.The Oxlint
vitestpreset now enablesvitest/consistent-test-it, the one Vitest rule it was missing (the ESLint preset already had it).Vue files whose names Nuxt and file-based Vue Router require (
pages/**,layouts/**,error.vueandapp.vue) are exempt fromuseVueMultiWordComponentNamesin the Biome preset and fromvue/multi-word-component-namesin the ESLint preset. Previouslypages/index.vue,layouts/default.vueanderror.vuewere always reported.