@finema/nuxt-oxclint

Shared oxlint + oxfmt configuration and Vue conventions checker for Finema Nuxt projects
414
Install
npm install @finema/nuxt-oxclint
Latest Version:1.0.1
License:MIT
Last Updated:Sep 22, 2026
Links: Repository  ·  npm
Maintainer: npm-finema

@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 under rules; they win over the shared ones.
  • .oxfmtrc.json – a copy of the shared formatter options. Tailwind class sorting is switched on when app/assets/css/main.css (or assets/css/main.css, */tailwind.css) exists.
  • .editorconfig
  • .vscode/settings.json and .vscode/extensions.json – merged into an existing file; your keys are kept.
  • package.json – adds the scripts below, a lint-staged block and the oxlint, oxfmt, lint-staged devDependencies 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:

  • eqeqeq is off. The old config only had vue/eqeqeq for template expressions.
  • dot-notation and vue/no-ref-object-reactivity-loss have no oxlint counterpart; they are conventions now.
  • vitest/valid-expect allows two arguments because specs use expect(value, message).
  • vitest/require-mock-type-parameters is off. It sits in oxlint's correctness category, so the vitest plugin would otherwise demand vi.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:

  • ignorePatterns is not inherited through extends; init writes 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 vitest plugin 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:

  1. <script lang="ts" setup> only, no export default (was vue/block-lang, vue/component-api-style).
  2. defineEmits before defineProps (was vue/define-macros-order).
  3. 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).
  4. An element with no content is self-closed; --fix rewrites these (was vue/html-self-closing). textarea, pre, slot, template, svg children 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:

  1. changelogen reads the conventional commits since the last v* tag, bumps version (feat: minor, fix:/chore:/anything else patch, ! or BREAKING CHANGE major), rewrites CHANGELOG.md, commits chore(release): vX.Y.Z and tags.
  2. The release commit and tag are pushed back to main with ci.skip, so they start no pipeline.
  3. 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

@vizejs/nuxt-lint-config

Shareable Nuxt-aware lint preset core for the Vize toolchain

47,114 909
@geoql/oxlint-plugin-nuxt-doctor

oxlint JS plugin for Nuxt 4 anti-patterns and AI-slop detection. Pairs with oxli...

21,363 7
@geoql/doctor-core

Audit engine for @geoql/vue-doctor and @geoql/nuxt-doctor. Hybrid two-pass: @vue...

15,374 7
@geoql/nuxt-doctor

CLI auditor for Nuxt 4 apps. Detects anti-patterns, AI-slop, and best-practice v...

14,295 7
oxlint-vue

Vue SFC linting and formatting on the oxc toolchain. Rules reach inside , no esl...

1,592 4

Version History

Version Released License
1.0.1 MIT
1.0.0 MIT