feat(toolchain): adopt Biome, oxlint, attw; keep eslint-config-next + knip#98
Conversation
Add the 5-tool toolchain adoption plan (Biome formatter, oxlint, typescript-eslint+eslint, knip, attw) ported from libredb-database, and the .editorconfig that matches the Biome formatter settings.
|
@copilot review the PR |
Reviewed the current PR changes (phase 0). No blocking issues found. The |
There was a problem hiding this comment.
Pull request overview
Adds a Studio-specific 2026 toolchain adoption plan (Biome formatter, oxlint, attw; keep eslint-config-next + knip) and introduces an .editorconfig baseline so editors converge on formatting ahead of the formatter rollout.
Changes:
- Added
docs/TOOLCHAIN.mddescribing phased adoption, rationale, and proposed configs/scripts for the five tools. - Added
.editorconfigto standardize basic whitespace/newline/indent behavior (with a Markdown trailing-space exception).
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| docs/TOOLCHAIN.md | New adoption plan doc covering scope, phases, and proposed configs/scripts for Biome/oxlint/ESLint/attw/knip. |
| .editorconfig | Establishes editor defaults (2-space indent, LF, UTF-8, final newline; preserve trailing spaces in Markdown). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| > Status: PLANNED (no code changes yet). This is a per-tool adoption plan for five tools, ported from the | ||
| > researched-then-adversarially-verified decision record in `libredb-database/docs/TOOLCHAIN.md` and adapted to | ||
| > Studio's reality: a Next.js 16 + React 19 + TSX application that ALSO ships as the dual-format npm package | ||
| > `@libredb/studio` (consumed by `libredb-platform`). The database record is the rationale source of truth; | ||
| > this document records only what changes for Studio and why. |
| "format": "biome format src tests *.ts *.mjs", | ||
| "format:fix": "biome format --write src tests *.ts *.mjs" |
Add @biomejs/biome (formatter only; linter and assist disabled), biome.json (lineWidth 120, 2-space, double quotes, semicolons; CSS and JSON formatting disabled), and format/format:fix scripts. Apply a one-shot reformat across src, tests, e2e, scripts (461 files). CSS left untouched to avoid platform-integration style breakage. lint/typecheck/build/build:lib/test all green.
| const safeStack = errInfo.stack.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f]/g, ''); | ||
| console.error(full, '\n', safeStack); | ||
| const safeStack = errInfo.stack.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f]/g, ""); | ||
| console.error(full, "\n", safeStack); |
| `[DB:${sanitize(this.type)}] ${sanitize(operation)} failed: ${sanitize(errorMessage)}` | ||
| ); | ||
| const sanitize = (v: string) => v.replace(/[\r\n]/g, " ").replace(/[\x00-\x08\x0b\x0c\x0e-\x1f]/g, ""); | ||
| console.error(`[DB:${sanitize(this.type)}] ${sanitize(operation)} failed: ${sanitize(errorMessage)}`); |
| const sanitize = (v: string) => v.replace(/[\r\n]/g, ' ').replace(/[\x00-\x08\x0b\x0c\x0e-\x1f]/g, ''); | ||
| console.log(`[DB] Creating ${sanitize(connection.type)} provider for "${sanitize(connection.name || '')}"`); | ||
| const sanitize = (v: string) => v.replace(/[\r\n]/g, " ").replace(/[\x00-\x08\x0b\x0c\x0e-\x1f]/g, ""); | ||
| console.log(`[DB] Creating ${sanitize(connection.type)} provider for "${sanitize(connection.name || "")}"`); |
| const safeStack = errInfo.stack.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f]/g, ''); | ||
| console.error(full, '\n', safeStack); | ||
| const safeStack = errInfo.stack.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f]/g, ""); | ||
| console.error(full, "\n", safeStack); |
Add oxlint (correctness/suspicious=error, perf=warn) with React/Next/hooks/ jsx-a11y/import/typescript plugins, run before ESLint via the lint script. Disable false-positives and eslint-config-next-owned duplicates (react-in-jsx-scope under the automatic JSX runtime, no-unassigned-import, no-underscore-dangle, no-shadow, no-unused-vars, exhaustive-deps, no-control-regex for log sanitization, no-unstable-nested-components for the shadcn/TanStack idiom). Scope test-only idioms (no-new, no-extraneous-class, no-useless-constructor, no-constant-binary-expression) to tests. Keep jsx-a11y findings as warnings pending a dedicated accessibility pass. Fix three genuine issues oxlint surfaced: - profile/route.ts: typeof is always a truthy string, so the || "unknown" fallback was dead code; use a length check for the empty-sample case. - seed/config-loader.ts: preserve the caught error via the cause option. - merge-lcov.mjs: drop a useless escape inside a regex character class.
Strategy A: eslint-config-next keeps owning all React/Next/hooks linting and oxlint is the syntactic layer in front of it (phase 2). This adds a scoped type-aware safety net via typescript-eslint with projectService, limited to the async-heavy paths (src/app/api, src/lib/db) to keep lint fast: no-floating-promises, no-misused-promises, await-thenable. Fix the five genuine fire-and-forget bugs it surfaced (async functions invoked in setInterval/setTimeout/process signal handlers without handling the promise) using the void operator to make the intent explicit: factory.ts (idle sweep + SIGTERM/SIGINT shutdown), mysql.ts and postgres.ts (transaction auto-rollback timeout).
Add @arethetypeswrong/cli to validate the published type-resolution across module modes. Run against the packed tarball (bun pm pack) so it tests exactly what npm ships; rm runs first so a trailing rm cannot mask the exit code. Use --profile node16: the package requires Node >=24 and is consumed by modern bundlers, so node16 (CJS+ESM) + bundler are validated and the legacy node10 algorithm (which cannot resolve subpath exports without redirect stubs) is ignored. Wire prepublishOnly to build then run attw, and git-ignore the .attw/ + *.tgz packaging scratch. CI: add a Biome format check and build:lib + attw steps to the lint-and-build job, and rename the lint step (oxlint now runs before ESLint).
knip is green with NO knip.json change needed: every new tool is seen via its package.json script (biome, oxlint, attw) or a config import (typescript-eslint in eslint.config.mjs) - the database finding held. Update docs/TOOLCHAIN.md status to IMPLEMENTED and record the as-implemented deviations (oxlint rule tuning, the type-aware layer landing in phase 3, attw --profile node16). Sync CLAUDE.md dev commands and the pre-commit verification list (now five with format).
|
SummaryAdopts five tools in five phased commits, each green through CI before the next: Biome (formatter-only), Genuine bugs fixed along the way (not just config):
Local verification (beyond CI)
CI status and open itemsFunctional gates are all green: Lint/Typecheck/Build (now incl. Biome format + oxlint + Two analyzers report failures that are advisory (neither is a required status check on
After this PR squash-merges to Deliberate, philosophy-aligned decisions
Follow-ups tracked in #100. |




Toolchain adoption (single PR, phased)
Adopts five tools, ported from the libredb-database toolchain decision record and adapted to Studio
(Next.js + React + TSX, dual-format npm package). Full rationale and as-implemented deviations in
docs/TOOLCHAIN.md.@biomejs/biome(format-only)oxlinttypescript-eslint+eslintknip@arethetypeswrong/cli(attw)--profile node16)Phases (each committed separately, CI green before advancing)
.editorconfigCI status
Functional gates all green: Lint/Typecheck/Build (now incl. format + oxlint + build:lib + attw), Unit &
Integration Tests, E2E, Helm Chart Lint, CodeQL Analyze jobs, Platform Integration Rules.
Two advisory analyzers (CodeQL "new alerts", SonarCloud quality gate) report failures that are artifacts of
the 461-file one-shot reformat: it makes pre-existing lines count as "new code", so both re-attribute
PRE-EXISTING findings to this PR (their own caveat: "code changes were too large"). Verified the flagged
files' diffs are formatting-only; the findings are pre-existing and largely by-design (
Math.randomin atest-data generator;
js/sql-injectionin a tool whose purpose is running user SQL). No new vulnerability orbug is introduced by this PR. Neither check is a required status check on
main.Studio-specific deviations from database: attw uses
--profile node16(subpath exports fail only the legacynode10 algorithm; the package targets Node >=24 + modern bundlers); ESLint is not reduced to type-aware-only
(eslint-config-next remains the Next/React owner); CSS is kept out of the Biome formatter (platform-integration).