Releases: haydenbleasel/ultracite
ultracite@7.8.3
Patch Changes
-
c863d09: Fix automatic editor extension installation during
ultracite init.The whole command line (e.g.
code --install-extension) was passed to
spawnSyncas the executable name, which always failed withENOENTand
silently fell back to the "install manually" message. The command is now split
into the binary and its arguments, so the linter extension actually installs
for VS Code-based editors. -
6888129: Enable the
eslint/no-await-in-looprule as an error in the core Oxlint
preset.Awaiting inside a loop forces each iteration to run sequentially, which can
lead to serious performance issues when the asynchronous operations could
otherwise run concurrently. Promoting this rule to an error encourages
collecting promises and resolving them together (e.g. withPromise.all)
instead of blocking on each one in turn. -
62a9b5c: Fix the generated Husky pre-commit hook's error handling and section
replacement.The standalone hook script set
set -eand then tried to capture the
formatter's exit code, re-stage files, and print a failure message — but a
non-zero formatter exit terminated the script immediately, so none of that
ever ran. The script now captures the exit code with|| FORMAT_EXIT_CODE=$?
so files are re-staged and failures are reported with the right exit code.Re-running
ultracite initalso deleted everything from the# ultracite
marker to the end of the hook, including commands the user added after the
ultracite section. The section is now terminated with an explicit
# ultracite endmarker and updates replace only the section between the
markers (legacy sections without an end marker are detected by their closing
echo line). -
6608ceb: Make the lint-staged integration idempotent and respect dedicated config
files.package.jsonwas always treated as the lint-staged config because the file
exists in every project, soultracite initwrote the lint-staged config into
package.jsoneven when a dedicated.lintstagedrc.*or
lint-staged.config.*file was present — leaving two conflicting configs.
package.jsonnow only counts when it actually has alint-stagedkey;
otherwise the dedicated config file is updated (or.lintstagedrc.jsonis
created).Re-running
ultracite initalso appended anothernpx ultracite fixentry on
every run because the merge concatenates arrays. Updates are now skipped when
the existing config already references ultracite. -
4e847f7: Insert
--before script arguments in npm hook commands.The post-edit hook command generated for npm projects was
npm run fix --skip=correctness/noUnusedImports, where npm consumes the
--skipflag itself instead of forwarding it to the script — so agent hooks
ran a plainultracite fix, including the unused-import removal the flag
exists to prevent mid-edit. The generated command is now
npm run fix -- --skip=correctness/noUnusedImports, matching the documented
form. -
ecb0d5b: Scope the Stylelint step of
ultracite checkandultracite fix(ESLint mode)
to style files.Stylelint was previously given the same targets as ESLint and Prettier (or
.
when no files were passed), so it tried to parse.ts/.jsonfiles as CSS and
failed withCssSyntaxError. Style files now pass through unchanged, directory
targets become**/*.{css,scss,sass,less}globs, other files are dropped, and
the step is skipped entirely when no style targets remain.
--allow-empty-inputis passed so projects without CSS still succeed. -
61ea0a1: Fix the project-path write guard's error message and ordering.
The "Refusing to write through directory outside project" error interpolated
thenode:pathmodule instead of the offending file path, printing
[object Object]. It now reports the actual path.writeProjectFilealso created directories (mkdir -p) before running the
path-escape check, so directories could be created outside the project before
the guard threw. Validation now happens first; the parent-directory check
resolves the nearest existing ancestor so writes into not-yet-created nested
directories still work.
ultracite@7.8.2
Patch Changes
-
30971a8: Enable newly available Oxlint and Stylelint rules in the shared configs.
For Oxlint, the core preset now enables
eslint/prefer-named-capture-group,
jsdoc/require-yields-description,node/callback-return,
typescript/method-signature-style, andunicorn/import-style.The Vue preset now enables
vue/component-definition-name-casing,
vue/no-computed-properties-in-data,vue/no-deprecated-props-default-this,
vue/no-expose-after-await,vue/no-reserved-component-names,
vue/no-shared-component-data,vue/no-watch-after-await,
vue/require-prop-type-constructor,vue/require-render-return,
vue/require-slots-as-functions,vue/return-in-emits-validator,
vue/valid-define-options, andvue/valid-next-tick.The Stylelint preset now enables
display-notationwith theshortoption.
ultracite@7.8.1
Patch Changes
- 8335be7: Build the CLI with
bun buildand atsgotype-check gate instead of tsup. - f747449: Fix the Oxlint TanStack preset so route files under
routes/andapp/routes/are exempt fromunicorn/filename-case, matching the documented 7.8.0 behavior. - 092597e: Fix generated
oxlint.config.tsto be pre-formatted according to oxfmt rules, soultracite checkpasses immediately afterultracite initwithout requiring a separate format step. - 81da6e8: Generate agent and editor hook commands through nypm's package-manager script helper.
- 81da6e8: Keep hyphen-prefixed file operands from being forwarded to linters as options.
ultracite@7.8.0
Minor Changes
-
4e2fea0: Add a dedicated
tanstackframework preset for Biome, ESLint, and Oxlint. The ESLint preset layers@tanstack/eslint-plugin-query,@tanstack/eslint-plugin-router, and@tanstack/eslint-plugin-start, while the Biome and Oxlint presets relax file-naming conventions forroutes/directories and the generatedrouteTree.gen.ts. Framework detection now maps@tanstack/react-query,@tanstack/react-router, and@tanstack/react-startto the newtanstackpreset.Two behavior changes for existing consumers: TanStack Query rules now live in the
tanstackpreset instead ofreact, so projects that relied on Query rules must opt intotanstack; and TanStack Router projects now resolve to thetanstackpreset rather thanremix.
Patch Changes
-
51a2af0: Recognize
.biome.jsonand.biome.jsoncas valid Biome config files across the CLI.detectLinter, thedoctorcommand, and the Biome config resolver now match the dot-prefixed names alongsidebiome.json/biome.jsonc, following Biome's documented configuration file resolution order. Closes #700. -
14b557c: Harden the generated standalone Husky hook by using
git add -- "$file"when restaging formatted files. This prevents option-shaped filenames from being interpreted as Git options during the hook. -
baa3dd0: Add
ignorePatternsto the generated oxlint config at the root level so they are actually applied. Oxlint does not mergeignorePatternsthroughextends(see oxc-project/oxc#10223), so patterns set in the core preset were silently ignored. The generated config now setsignorePatterns: core.ignorePatternsat the top level, reusing the patterns from the imported core preset. -
bd27fd4: Add newly supported Oxlint rules from the latest release to the core, React, and Vitest presets:
- Core:
id-match,no-implicit-globals,no-implied-eval,prefer-arrow-callback,prefer-regex-literals,import/newline-after-import,jsdoc/require-throws-description,jsdoc/require-throws-type, andjsdoc/require-yields-type - React:
jsx-a11y/control-has-associated-label,jsx-a11y/no-interactive-element-to-noninteractive-role,jsx-a11y/no-noninteractive-element-interactions,jsx-a11y/no-noninteractive-element-to-interactive-role,react/no-object-type-as-default-prop, andreact/no-unstable-nested-components - Vitest:
vitest/padding-around-after-all-blocks
- Core:
-
14b557c: Reject symlinked generated config targets before writing project files. CLI config writers now route through a shared project-file write guard that checks for symlinks and project-root escapes before mutating files.
-
14b557c: Validate package-manager names before generating agent and editor hook commands. Hook configuration now only uses supported package-manager prefixes, preventing unsafe values from being persisted into later-executed hook commands.
-
14b557c: Reject unsupported package-manager names during
ultracite init. Explicit--pmvalues and detectedpackageManagermetadata are now runtime-validated against the supported package managers before dependency installation, preventing malicious project metadata from selecting an arbitrary executable.
ultracite@7.7.0
Minor Changes
-
24b0d27: Wire up the
nestjsESLint preset to actually enforce rules. Previously the preset exported an emptyconst config = [], meaning users who importedultracite/eslint/nestjsgot nothing. It now layers@darraghor/eslint-plugin-nestjs-typed(22 rules covering NestJS conventions, dependency injection correctness, and class-validator/Swagger usage) using the same dynamic-enable pattern as the other framework presets.Consumers who already had the empty preset in their config may see new violations on first run.
Patch Changes
-
161418a: Add missing Biome stable rules to the core config:
suspicious/noDuplicateDependencies→"error"— flags a dependency listed multiple times in the same group, or acrossdependenciesanddevDependencies, inpackage.json.suspicious/useDeprecatedDate→"off"— GraphQL-only convention requiring adeletionDateargument on@deprecated; too opinionated for the default preset.
-
9a2b548: Pin
@angular-eslint/eslint-pluginto^21.3.1inpackages/cli/package.json. Previously declared as"latest", which defeats lockfile reproducibility and means eachbun installcould pull a newer version than what was tested at publish time. The current resolved version (21.3.1) is unchanged. -
44f6d7f: Align ESLint presets with the oxlint configs (the maintained source of truth). Mostly tightens ESLint where oxlint was stricter; a few documented behavioural exceptions oxlint carries (rule conflicts, bun:test compat) are mirrored back.
core —
eslint.mjsnow enforcescomplexity,no-unused-private-class-members,sort-keys,sort-vars, and fullprefer-destructuring(object + array).typescript.mjsnow enforcesno-confusing-void-expression,no-misused-promises,prefer-readonly,strict-boolean-expressions, and setsreturn-await: ["error", "always"].import.mjsnow setsconsistent-type-specifier-style: ["error", "prefer-top-level"].next — added
next-env.d.tsoverride that disablesimport-x/no-unassigned-importon the generated file.remix — added
routeTree.gen.tsoverride that disablesunicorn/filename-caseandunicorn/no-abusive-eslint-disableon the generated file.react — disabled
react/jsx-boolean-value,react/no-unknown-property, andreact/only-export-componentsto match oxlint.jest — broadened test globs to
**/*.{test,spec}.{ts,tsx,js,jsx}+**/__tests__/**/*.{ts,tsx,js,jsx}(previously missed*.spec.*and__tests__/). Disabledno-empty-functionandpromise/prefer-await-to-thenin test scope. Disabledjest/require-hook,jest/no-conditional-in-test,jest/no-hooks,jest/prefer-expect-assertionsto mirror oxlint's bun:test/mocking accommodations.vitest — same test-glob broadening; same
no-empty-function/promise/prefer-await-to-thentest-scope disables. Removed theprefer-importing-vitest-globalsandprefer-to-have-been-called-timesdisables (oxlint enforces these). Addedprefer-lowercase-title: offandvalid-title: offto resolve the documented conflict withprefer-describe-function-title(#665). -
63f6a18: Drop the redundant
react-hooks/exhaustive-deps: "error"override inconfig/eslint/react/rules/react-hooks.mjs. The dynamic-enable pattern already sets every non-deprecatedreact-hooks/*rule to"error", so the override was dead code. No behavior change. -
5a0ce67: Refresh the misleading header comment in
config/eslint/core/rules/eslint-typescript.mjs. The disables for the formatting rules (brace-style,comma-dangle,indent, etc.) used to defer to@typescript-eslint's typed equivalents, but those rules were removed in v8. They're now disabled because Prettier/Oxfmt owns formatting. Updated the comment to reflect the actual rationale. -
d681f70: Clean up
config/eslint/core/rules/typescript.mjs: remove 22 stale overrides that referenced rules no longer present in@typescript-eslint/eslint-pluginv8.Most were formatting rules moved out to
@stylistic(block-spacing,brace-style,comma-dangle,comma-spacing,func-call-spacing,indent,key-spacing,keyword-spacing,lines-around-comment,lines-between-class-members,member-delimiter-style,no-extra-parens,object-curly-spacing,padding-line-between-statements,quotes,semi,space-before-blocks,space-before-function-paren,space-infix-ops,type-annotation-spacing). The remaining two (no-type-alias,sort-type-union-intersection-members) were removed/deprecated upstream. All were dead no-ops — no behavior change.
ultracite@7.6.5
Patch Changes
- 3e08c25: Fix
ultracite initfailing withnpm error No workspaces found!in npm monorepos. WhenisMonorepo()was true, nypm was passedworkspace: true, which translates to--workspacesfor npm — that installs in every workspace package and errors when patterns match nothing. We now skip the workspace flag for npm (the default root install is what we want) while preserving the flag for pnpm (--workspace-root) and yarn classic (-W). Applies to ultracite, husky, lefthook, and lint-staged installs.
ultracite@7.6.4
Patch Changes
-
aba89bb: Add new oxlint 1.63.0 rules:
eslint/logical-assignment-operators→"error"— prefer||=,&&=,??=over their longhand equivalents; aligns with the modern-JS baseline.eslint/require-unicode-regexp→"error"— require theu(orv) flag on regex literals for correct Unicode handling.eslint/no-restricted-properties→"off"— purely a project-specific allowlist; no useful default to enforce.unicorn/no-negated-condition→"error"— newly split from the eslint version; the unicorn variant additionally covers ternary expressions and complements the existingeslint/no-negated-condition.jsx-a11y/interactive-supports-focus→"error"— interactive elements (click handlers,role="button", etc.) must be keyboard-focusable; matches the rest of the a11y baseline.vue/return-in-computed-property→"error"— computed properties must return a value; missingreturnsilently breaks reactivity.vue/no-deprecated-model-definition→"error"— flags Vue 2model: { ... }usage; Vue 3 is the supported target.vitest/prefer-mock-return-shorthand→"error",vitest/no-unneeded-async-expect-function→"error",vitest/prefer-to-have-been-called-times→"error",vitest/prefer-snapshot-hint→"error"— newly split out from the jest plugin; mirrors the existing jest config which has all four enabled.vitest/require-hook→"off"— newly split out from jest; disabled to mirror jest config (bun:testmock.module()must be called at top level).
-
522155e: Set
typescript/return-awaitto["error", "always"]to resolve a circular conflict betweeneslint/require-await,typescript/promise-function-async, andtypescript/return-awaiton Promise-returning functions outside try/catch. With the defaultin-try-catchmode, autofixers chase each other:promise-function-asyncaddsasync,require-awaitthen demands anawait, andreturn-awaitremoves anyreturn awaitoutside a try/catch — leaving no resolvable state. The"always"mode keepsreturn awaiteverywhere, breaking the cycle while preserving consistent stack traces.
ultracite@7.6.3
Patch Changes
-
f584d93: Disable
unicorn/number-literal-casedue to oxc-project/oxc#21949. -
ef5c3ae: Fix
ultracite checkandultracite fixshort-circuiting after the formatter step. Previously, when the formatter (oxfmt or Prettier) exited non-zero, the linter (oxlint, ESLint, Stylelint) was never invoked, hiding lint errors until formatting was clean. The commands now run every step, accumulate failures, and exit with the first failing tool's status. Fixes #690. -
3ecb159: Fix the generated
oxfmt.config.tstemplate, which usedextends: [ultracite]— a key oxfmt does not recognize, so the preset was silently dropped and built-in options likesortImportsnever took effect. The template now spreads the preset (...ultracite) so its options are actually applied. Fixes #689. -
5a18ec8: Add new oxlint 1.61.0 and 1.62.0 rules:
eslint/func-name-matching→"error"— function names should match the variable they're assigned to; matches the project's strict baseline.eslint/no-underscore-dangle→"off"— common patterns like_id(Mongo) and_internalmake this rule too noisy in practice.typescript/explicit-member-accessibility→"off"— forcingpublic/privateon every class member is verbose and not idiomatic in modern TS.jest/prefer-expect-assertions→"off"andvitest/prefer-expect-assertions→"off"— requiringexpect.assertions(n)in every test is too strict for general use; not all tests need explicit assertion counts.vitest/max-expects→"error"andvitest/max-nested-describe→"error"— newly split out from the jest plugin; mirrors the existing jest config which has both enabled.vitest/no-conditional-in-test→"off"— newly split out from jest; disabled to mirror jest config (mock factories use conditionals for path-based routing).vitest/no-hooks→"off"— newly split out from jest; disabled to mirror jest config (bun:test usesbeforeEachformock.restore()).react/forbid-component-props→"off"— parity with the ESLint config, which already disables this rule.
ultracite@7.6.2
Patch Changes
- 5be860c: Automatically detect frameworks during the
initprocess. - 10d9e95: Support
-vas a short alias for--versionon the CLI (previously only-Vworked). - 8ff1b96: Fix
updatecommand not migrating legacyultracite/<name>extends entries toultracite/biome/<name>(e.g.ultracite/core,ultracite/react,ultracite/type-aware, etc.). - 5e055ce: Ignore Cloudflare Workers' generated
worker-configuration.d.ts(produced bywrangler types), matching the existing handling ofnext-env.d.ts. - 9cc7416: Add a
universaleditor target that creates.vscode/settings.jsonfor every VS Code-based editor (VS Code, Cursor, Windsurf, CodeBuddy, Antigravity, IBM Bob, Kiro, Trae, Void) with a single selection. Theinitprompt now offers a "Universal" option, and--editors universalworks as an alias on the CLI.
ultracite@7.6.1
Patch Changes
- 2fbded9: Disable the
typescript/prefer-readonly-parameter-typesOxlint rule. While the rule is useful for user-authored types, it fires on virtually every parameter that touches a third-party type (ExpressRequest/Response, React events, NodeBuffer, ORM models, DOM APIs) because those types aren't deeply readonly internally — leaving users with unfixable violations. Matches the existing ESLint config, which already has this rule off. - 617affd: Fix
dist/,.next/,**/*.gen.*, and other strong-negation (!!) ignore globs being dropped when a consumer'sbiome.jsoncextendsultracite/biome/coreand also defines its ownfiles.includes. The globs moved intoconfig/shared/ignores.jsoncin 7.5.9 were transitively extended throughbiome/core, and Biome's extend merge doesn't carryfiles.includesthrough a two-level chain when the middle config lacks its own entry. The patterns are now inlined directly inbiome/core'sfiles.includes(still generated fromconfig/shared/ignores.mjs), matching the pre-7.5.9 behavior. - d681e08: Remove the nonexistent
import-x/enforce-node-protocol-usagerule from the ESLint core config, which caused ESLint 9 to throwCould not find "enforce-node-protocol-usage" in plugin "import-x". Node protocol enforcement is already covered byunicorn/prefer-node-protocol.