@finema/nuxt-oxclint
| Install | |
|---|---|
npm install @finema/nuxt-oxclint |
|
| Latest Version: | 1.0.1 |
| License: | MIT |
| Last Updated: | Sep 22, 2026 |
| Links: | Repository · npm |
@finema/nuxt-oxclint
Shared oxlint + oxfmt
configuration for Finema Nuxt projects, plus check-vue, a small checker for the Vue
conventions oxlint cannot see. Extracted from data-platform-frontend.
What you get:
| File / command | Role |
|---|---|
oxlint.jsonc |
The lint rules. Projects extends it from their .oxlintrc.json. |
oxfmt.json |
The formatter options. Copied into the project as .oxfmtrc.json (oxfmt has no extends). |
nuxt-oxclint init |
Writes the config files, .editorconfig, VS Code settings and package.json scripts. |
nuxt-oxclint check-vue |
Checks <script lang="ts" setup>, macro order, unused imports, self-closed empty elements. |
Setup in a project
bun add -d @finema/nuxt-oxclint oxlint oxfmt
bunx nuxt-oxclint init
bun install # picks up the devDependencies init added (lint-staged)
bun run check
init writes these files and never overwrites one that already exists (use --force):
.oxlintrc.json– extends the shared rules. Add project rules underrules; they win over the shared ones..oxfmtrc.json– a copy of the shared formatter options. Tailwind class sorting is switched on whenapp/assets/css/main.css(orassets/css/main.css,*/tailwind.css) exists..editorconfig.vscode/settings.jsonand.vscode/extensions.json– merged into an existing file; your keys are kept.package.json– adds the scripts below, alint-stagedblock and theoxlint,oxfmt,lint-stageddevDependencies when they are missing. Existing scripts are kept.
{
"lint": "oxlint",
"lint:fix": "oxlint --fix",
"format": "oxfmt",
"format:check": "oxfmt --check",
"check": "oxfmt --check && oxlint && nuxt-oxclint check-vue",
"check:vue": "nuxt-oxclint check-vue",
"check:vue:fix": "nuxt-oxclint check-vue --fix"
}
bun run check is the pre-commit check: formatting and Tailwind class order, the code rules, then
the Vue conventions. All three take about a second over a whole repo.
If the project still has ESLint or Prettier, init lists the leftover dependencies and config files
so you can remove them. Install the oxc.oxc-vscode extension; the written settings make it the
formatter and run source.fixAll.oxc on save.
Manual wiring
Without init, the minimum is:
// .oxlintrc.json
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"extends": ["./node_modules/@finema/nuxt-oxclint/oxlint.jsonc"],
// Not inherited through extends, so list it here.
"ignorePatterns": [".nuxt", ".output", ".nitro", ".cache", ".data", "dist", "coverage", "node_modules", "public"],
"rules": {}
}
and either copy node_modules/@finema/nuxt-oxclint/oxfmt.json to .oxfmtrc.json or run
oxfmt -c node_modules/@finema/nuxt-oxclint/oxfmt.json. From JavaScript, the package exports
oxlintConfigPath, oxfmtConfigPath and oxfmtConfig() (the options as an object, for an
.oxfmtrc.ts that wants to spread them).
Updating
Bump the package and run bun run check. Rule changes in oxlint.jsonc apply at once through
extends. Formatter changes do not: .oxfmtrc.json is a copy, so re-run
bunx nuxt-oxclint init --force (it rewrites the managed files and re-detects the Tailwind
stylesheet) or edit the file by hand.
The rules
Ported from the old @nuxt/eslint + custom ESLint config. Code rules: func-style,
prefer-arrow-callback, no-param-reassign, no-else-return, radix, no-var, prefer-const,
the unicorn subset Nuxt enables, typescript/consistent-type-imports, the import rules,
vue/prop-name-casing, vue/no-reserved-props, and no-unused-vars as a warning. The vitest
plugin runs on *.spec.* and *.test.* files.
Whitespace rules became formatter options: no semicolons, single quotes, 2-space indent, trailing
commas in objects and arrays but not call arguments (es5), 120 columns, operators at the start
of a wrapped line, one attribute per line in templates, objectWrap: preserve (an object written
multi-line stays multi-line).
Deliberate choices, kept from the source project:
eqeqeqis off. The old config only hadvue/eqeqeqfor template expressions.dot-notationandvue/no-ref-object-reactivity-losshave no oxlint counterpart; they are conventions now.vitest/valid-expectallows two arguments because specs useexpect(value, message).vitest/require-mock-type-parametersis off. It sits in oxlint's correctness category, so the vitest plugin would otherwise demandvi.fn<...>()everywhere.
Project overrides
Project rules go in the project's .oxlintrc.json, either under rules or in overrides. Two
oxlint behaviours to know, both checked against oxlint 1.85:
-
ignorePatternsis not inherited throughextends;initwrites the list into the project file for that reason. Add project directories (docs,k8s,playground/.output, ...) there. -
An override that touches a rule from a plugin the shared config enables only inside its own override (the
vitestplugin on spec files) must declare that plugin again, or the rule name is not resolved and the setting is silently ignored:{ "files": ["**/*.spec.ts"], "plugins": ["vitest"], "rules": { "vitest/no-disabled-tests": "off" } }
Overrides apply in order, so a later, narrower files glob wins over an earlier one. Negated
globs (!**/*.spec.ts) inside files do not exclude; use a second override instead.
Nuxt module repos
A module repo (src/module.ts, src/runtime/, playground/, test/) works with the same
setup. init finds the Tailwind stylesheet at src/runtime/styles/main.css, and check-vue
scans the whole repo when there is no app/ directory, skipping node_modules, .nuxt,
.output, dist and coverage. Point the scripts at the source directories to keep test
fixtures out:
{
"check": "oxfmt --check && oxlint && nuxt-oxclint check-vue src playground",
"check:vue": "nuxt-oxclint check-vue src playground"
}
Compared with @nuxt/eslint-config's tooling: true preset, the shared config does not enable
the jsdoc and regexp plugins or the wider unicorn set. Oxlint has jsdoc and the full
unicorn plugin if a repo wants them back; regexp has no counterpart.
check-vue
Oxlint lints only the <script> block of a .vue file and treats every top-level binding there as
used by the template, so four of the old rules moved to a text check:
<script lang="ts" setup>only, noexport default(wasvue/block-lang,vue/component-api-style).defineEmitsbeforedefineProps(wasvue/define-macros-order).- No unused imports; a name is used if it appears anywhere else in the file, template included,
in PascalCase or kebab-case (was
unused-imports/no-unused-imports). - An element with no content is self-closed;
--fixrewrites these (wasvue/html-self-closing).textarea,pre,slot,template,svgchildren and the like are left alone.
nuxt-oxclint check-vue # app/ when it exists, else the cwd
nuxt-oxclint check-vue --fix src pages # directories or files
Developing this package
bun install
bun run test # vitest: the rules on fixtures, the formatter on fixtures, check-vue, init
bun run check # the package's own code, formatted and linted with its own config
Fixtures live in test/fixtures/; the oxlint tests use test/oxlintrc.fixtures.json, which
extends oxlint.jsonc, through oxlint -c.
Releasing
Releases are automatic. .gitlab-ci.yml runs bun run check and bun run test on every
branch and merge request, and on every push to main it then runs scripts/release.sh:
- changelogen reads the conventional commits since the
last
v*tag, bumpsversion(feat:minor,fix:/chore:/anything else patch,!orBREAKING CHANGEmajor), rewritesCHANGELOG.md, commitschore(release): vX.Y.Zand tags. - The release commit and tag are pushed back to
mainwithci.skip, so they start no pipeline. npm publish --access public.
Write commit messages as conventional commits, then; every push to main becomes a version.
Squash-merge with a feat:/fix: title, or push a branch, and the version follows.
Required CI/CD variables (masked):
| Variable | What |
|---|---|
NPM_TOKEN |
npm automation token that can publish @finema/*. |
RELEASE_TOKEN |
Project access token created on this project, scope write_repository, role Maintainer if main is protected. |
RELEASE_USER |
Optional username to pair with the token (default oauth2). |
The script embeds the token in the remote URL and clears the runner's injected auth headers,
the same workaround ui-kit's CI-README.md documents, because the CI job token cannot push.
Recovery: if the publish fails after the push, main holds a tagged release commit that is not
on npm. Run the release job by hand on that commit (it is offered as a manual job); the script
sees HEAD tagged and unpublished and only publishes. The same path publishes a version exactly:
tag a commit vX.Y.Z matching package.json by hand, push branch and tag, and run the job.
When two pushes land close together, the job for the older commit sees that main has moved
on and exits without releasing; the newer commit's pipeline releases both. If a push slips in
between the checkout and the push, the push is rejected before anything reaches npm, and again
the next pipeline picks everything up.
Related Packages
oxlint JS plugin for Nuxt 4 anti-patterns and AI-slop detection. Pairs with oxli...
Audit engine for @geoql/vue-doctor and @geoql/nuxt-doctor. Hybrid two-pass: @vue...
CLI auditor for Nuxt 4 apps. Detects anti-patterns, AI-slop, and best-practice v...
Vue SFC linting and formatting on the oxc toolchain. Rules reach inside , no esl...
Version History
| Version | Released | License |
|---|---|---|
| 1.0.1 | MIT | |
| 1.0.0 | MIT |