kvalita
v1.39.0
Published
Shared linter configurations for TypeScript projects.
Readme
Kvalita
Shared linter configurations for TypeScript projects.
Kvalita provides reusable, opinionated configurations for maintaining consistent code quality across TypeScript projects.
Installation
bun add -d kvalitaThis will automatically install all required dependencies.
[!NOTE] All tools are installed together for simplicity, even if you only use some of the configurations. This ensures all CLIs and configs work correctly and avoids resolution issues.
Usage
Biome Configuration
Create a biome.json file in your project root:
{
"extends": ["kvalita/biome"]
}On top of Biome's recommended rules, the config turns on more built-in rules and runs GritQL plugins, some only in *.test.ts files. All of it lives in configs/biome: shared.json lists the rules, each .grit file in plugins/ is one plugin with its message, and fixtures/ shows what each plugin flags. Rules waiting for the projects to be swept clean of them sit in upcoming.json, which no config extends.
The plugins load from node_modules/kvalita, so the config expects kvalita installed at the project root.
A project adds the configs that fit it after kvalita/biome:
kvalita/biome-troussefor a project that depends on trousse.kvalita/biome-libraryfor a published library, which declares its exports' return types and never logs or exits.kvalita/biome-reactfor a React app's components.kvalita/biome-drizzlefor a project that queries through Drizzle.kvalita/biome-localesfor a project that keeps its error messages undererrorsinsrc/locales.json. It reports at info level for now, so it never fails a run.
{
"extends": ["kvalita/biome", "kvalita/biome-trousse", "kvalita/biome-library"]
}Commitlint Configuration
Create a commitlint.json file in your project root:
{
"extends": ["./node_modules/kvalita/configs/commitlint.json"]
}Semantic Release Configuration
Use the --extends flag in your CI workflow:
- name: Release
run: bunx semantic-release --extends kvalita/semantic-releaseThis configuration includes:
- Branches:
main,rc(prerelease),beta(prerelease),alpha(prerelease), and maintenance branches namedN.xorN.N.x, which publish to therelease-N.xdist-tag conventionalcommitspreset withfeat!:syntax for breaking changes- npm provenance (requires
id-token: writepermission)
Lefthook Configuration
Create a lefthook.json file in your project root and extend the hooks you need:
{
"extends": [
"node_modules/kvalita/configs/lefthook-biome.json",
"node_modules/kvalita/configs/lefthook-typescript.json",
"node_modules/kvalita/configs/lefthook-commitlint.json"
]
}Available hooks:
lefthook-biome.json- Lints and formats staged files with Biome (pre-commit)lefthook-typescript.json- Type checks TypeScript files (pre-commit)lefthook-commitlint.json- Validates commit messages (commit-msg)
Shared Workflows
Call them instead of copying the job into every repo. secrets: inherit passes CODECOV_TOKEN and NPM_TOKEN through.
# .github/workflows/test.yml
name: Test
on:
push:
branches: [main, alpha, beta, rc]
pull_request:
jobs:
test:
uses: macieklamberski/kvalita/.github/workflows/shared-test.yml@main
secrets: inherit# .github/workflows/release.yml
name: Release
on:
workflow_dispatch:
inputs:
branch:
description: Branch to release
required: true
default: main
type: choice
options: [main, rc, beta, alpha]
jobs:
release:
uses: macieklamberski/kvalita/.github/workflows/shared-release.yml@main
with:
branch: ${{ inputs.branch }}
build: true
secrets: inheritshared-release takes branch, build, config and bun-version; shared-test takes bun-version and ref. Every one has a default, so a repo on the common setup passes nothing. Start the release from the branch it releases, and pass that branch as branch. npm's provenance names the branch the run started on, so a run started on main can't publish another branch. Pass the same branch as ref to any gate job, such as shared-test, so the gates check the code being released.
shared-package checks the package the way a consumer gets it. It builds the package, installs it into an empty project with npm install --install-links, so only the published files land there, and loads every entry point in exports: through import, and through require() where a require condition exists. It does that on every even Node.js major from the floor in engines.node, or 18 when it isn't set, up to the latest release. On the latest one, it also turns off syntax detection and require() of ESM files, which older versions don't have. Optional peers get installed too, since an entry point built on one needs it.
A second job checks the same entry points the way TypeScript and bundler users reach them. It type-checks them from an ESM and a CommonJS project under each module resolution (node10, node16, nodenext and bundler) with strict and skipLibCheck: false, and fails only on errors in the package's own types. It also bundles them with Vite as a server build. With browser: true, it bundles them for the browser as well. Every setup only imports what the package claims: CommonJS files only use entry points with a require condition, and node10 only checks the root entry, when the package has a top-level types.
It takes ref, bun-version and browser:
# .github/workflows/package.yml
name: Package
on:
push:
branches: [main, alpha, beta, rc]
pull_request:
workflow_call:
inputs:
ref:
type: string
default: ''
jobs:
package:
uses: macieklamberski/kvalita/.github/workflows/shared-package.yml@main
with:
ref: ${{ inputs.ref }}The workflow_call trigger lets the release workflow run it as a gate with ref: ${{ inputs.branch }}, next to shared-test.
Dependabot Configuration
Dependabot can't share a config between repos, so kvalita generates each repo's .github/dependabot.yml from the files it tracks:
bun node_modules/kvalita/configs/dependabot/dependabot.ts --writeEvery repo gets bun and github-actions. docker-compose and docker are added when the repo has a compose file or a Dockerfile, and they cover every directory through /**/*, so a new Docker directory needs no regeneration. All entries share one group that runs weekly and waits 7 days after a release, so each repo gets one PR a week with every update in it, majors included.
shared-lint runs the same script without --write and fails when the committed config differs.
Ignore File Templates
Copy the templates into your project root, then add whatever the project itself produces:
cp node_modules/kvalita/configs/gitignore .gitignore
cp node_modules/kvalita/configs/dockerignore .dockerignoreThe Docker entries carry a **/ prefix because Docker matches from the build context root, not at any depth like git. ** also matches zero directories, so the one spelling works in a flat project and a monorepo alike.
