Skip to content

chore: migrate from prettier+eslint to biome#118

Merged
mgoldsborough merged 7 commits into
NimbleBrainInc:mainfrom
Ovaculos:chore/biome-migration
May 27, 2026
Merged

chore: migrate from prettier+eslint to biome#118
mgoldsborough merged 7 commits into
NimbleBrainInc:mainfrom
Ovaculos:chore/biome-migration

Conversation

@Ovaculos
Copy link
Copy Markdown
Contributor

Summary

Replaces prettier (formatter) + eslint (linter) with biome across all TypeScript packages. One binary, one config, no plugin chain. Aligns mpak with the rest of NimbleBrain's TS tooling.

Closes #105.

Commits (logical, grep-able history)

Commit What
chore: migrate from prettier+eslint to biome Tooling swap: add @biomejs/biome, drop eslint/prettier deps, replace scripts, update CI workflows, delete .prettierrc/.prettierignore/eslint.config.js
chore: tsconfig adjustments for biome compatibility Relax noPropertyAccessFromIndexSignature in base; remove duplicate keys + redundant override in registry; pin types: [] in schemas
fix(web): a11y, React hook stability, and array key improvements aria-hidden on decorative SVGs, type="button", modal a11y, useCallback effect deps, content-based React keys
fix: explicit types and forEach braces for biome strict rules noImplicitAnyLet annotations, useIterableCallbackReturn braces, drop catch (err: any)
chore: biome-ignore intentional patterns and drop stale eslint comments File-level ignores for mpak manifest ${var} placeholders in tests; scoped ignores for deliberate as any and CSS specificity
style: biome reformat Mechanical: import organization, node: protocol, literal keys, formatting. No behavior change.
fix(docs): restore Starlight Header import dropped by biome migration Re-add import biome's unused-import autofix wrongly removed in the Astro override

Biome config

Vanilla recommended ruleset. Only mpak-required overrides:

  • indentStyle: space, lineWidth: 100, quoteStyle: single (matches prior prettier output — no churn)
  • css.parser.tailwindDirectives: true (else parse error on Tailwind @theme)
  • noNonNullAssertion: off (matches org config)

Notable decisions

  • Single root pnpm lint (biome check .) instead of per-package fan-out. Publish workflows lint the whole repo, but biome is ~100ms for 227 files and tags cut from already-green main, so no practical cost.
  • noPropertyAccessFromIndexSignature relaxed to resolve a deadlock between biome useLiteralKeys (wants dot) and tsc strict (wanted bracket). Repo style was already dot under the prior eslint config.
  • All ~770 biome findings resolved — zero suppressions left except documented intentional patterns. No follow-up cleanup issue needed.

Test plan

  • pnpm lint — 0 issues
  • pnpm lint:ci (biome ci .) — exit 0
  • pnpm typecheck — all 7 packages pass
  • pnpm test — pass (registry needs local Postgres; CI covers)
  • Registry boots, routes resolve (verified /health, route registration)
  • CLI builds + runs (--version, --help, config list)
  • Web smoke (home, package detail)
  • CI green on this PR

🤖 Generated with Claude Code

Ovaculos and others added 7 commits May 20, 2026 19:00
Closes NimbleBrainInc#105.

Replaces prettier (formatter) and eslint (linter) with biome across all
TypeScript packages. Single binary, single config, no plugin chain. Aligns
with NimbleBrain org tooling (nimblebrain repo, synapse-app servers).

Changes:
- Add @biomejs/biome ^2.0.0 as workspace devDependency
- Drop @typescript-eslint/eslint-plugin, @typescript-eslint/parser, eslint,
  prettier, typescript-eslint from root devDependencies
- Replace `pnpm format` / `pnpm format:check` with biome equivalents; add
  `pnpm lint`, `pnpm lint:fix`, `pnpm lint:ci` (biome ci)
- Remove `lint` task from turbo.json and per-package `lint` / `lint:fix`
  scripts in web, registry, cli, sdk-typescript, schemas (single root entry
  point via `biome check .` instead of turbo fan-out)
- Update CI workflows: drop separate "Format check" prettier step in
  sdk-typescript-ci/publish; route lint steps in cli-publish, schemas-publish,
  sdk-typescript-ci/publish through root `pnpm lint`; replace separate lint
  + typecheck steps in ci.yml with `pnpm lint:ci`
- Delete .prettierrc, .prettierignore, eslint.config.js

Biome configuration: vanilla recommended ruleset, only mpak-required
overrides (2-space indent, 100 line width, single quotes, tailwind
directives parser). `noNonNullAssertion` disabled to match org config.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three independent tsconfig fixes surfaced during the biome migration:

- tsconfig.base.json: set `noPropertyAccessFromIndexSignature` to false.
  Resolves a deadlock between biome's `lint/complexity/useLiteralKeys`
  (wants dot access on index signatures) and tsc strict (wanted bracket).
  Code style across the monorepo is already dot access since the prior
  ESLint config didn't enforce the strict variant; this aligns the type
  checker with the existing style and biome's recommendation.

- apps/registry/tsconfig.json: remove three duplicate compilerOptions keys
  (`exactOptionalPropertyTypes`, `noPropertyAccessFromIndexSignature`,
  `noUncheckedIndexedAccess`) that biome's `noDuplicateObjectKeys` rule
  caught — a real bug where the second declaration silently shadowed the
  first. Also drops the now-redundant `noPropertyAccessFromIndexSignature`
  override since the base now matches.

- packages/schemas/tsconfig.json: explicitly pin `types: []`. Without it,
  tsc auto-discovers every `@types/*` package found via node_modules walk,
  which broke after `pnpm install` reshuffled hoisting (schemas itself
  depends on none of those types — only zod).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
a11y additions surfaced by biome's recommended ruleset:
- aria-hidden="true" on 119 decorative inline SVG icons across components
  and pages. Audit confirmed each parent already carries an accessible
  label (visible text sibling or aria-label on the interactive ancestor).
- type="button" on 33 form-buttons that previously defaulted to "submit",
  preventing unintended form submission on click.
- ClaimPackageModal: explicit aria-label="Close" on the icon-only close
  button (the only true icon-only-no-label interactive in the codebase),
  htmlFor + matching ids on the two label/input pairs, Escape key handler
  on the backdrop dismissal, scoped biome-ignore for the necessary static-
  element interaction pattern.
- RootLayout: sr-only "GitHub" text inside the icon-only GitHub anchor
  pair so screen readers announce content (aria-label alone doesn't satisfy
  biome's useAnchorContent rule).

React hook stability (fixes useExhaustiveDependencies warnings on six
useEffect call sites): BrowsePackagesPage, CategoryPage, HomePage,
SkillsPage, UserPackagesPage, ClaimPackageModal. Each load* function
wrapped in useCallback with its real dependency set; the useEffect that
calls it now sits below the declaration so it doesn't reference a
not-yet-initialized binding.

Stable React keys: replace `key={index}` with content-based keys in
Breadcrumbs (href/label), ClaimPackageModal steps, SecurityReportSection
findings (purl), HomePage FAQ (question), SkillDetailPage examples
(prompt) + triggers, ConfigurationPanel CLI commands (command string).
Two syntax-highlight token-stream maps in ConfigurationPanel (JsonHighlight,
CliHighlight) keep numeric keys for positional rendering — scoped
biome-ignore explains intent.

Also drops two stale eslint-disable comments in SkillsPage and
SkillDetailPage (the noNonNullAssertion rule is now off project-wide
under biome).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Type annotations to satisfy biome's `noImplicitAnyLet` and to replace
`any` with concrete types:

- apps/registry/src/routes/packages.ts: replace `let result;` with an
  explicit shape literal type for the transaction return value.
- apps/registry/src/routes/v1/bundles.ts, v1/skills.ts: replace
  `let claims;` with `Awaited<ReturnType<typeof verifyGitHubOIDC>>`.
- apps/web/src/components/ScanTriggerButton.tsx: replace `catch (err: any)`
  with `catch (err)` + a narrow axios-shape cast on use. Also picks up
  the aria-hidden + type="button" additions from the a11y pass.

forEach callbacks wrapped in braces to satisfy `useIterableCallbackReturn`:
- packages/cli/src/commands/skills/show.ts (1 site)
- packages/cli/src/commands/skills/validate.ts (2 sites)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Suppressions for intentional patterns biome flagged:

- File-level `biome-ignore-all lint/suspicious/noTemplateCurlyInString` in
  nine test/seed files that legitimately use `${var}` placeholders inside
  string literals — mpak manifest substitution syntax, expanded at install
  time, not JS template literals:
    apps/registry/prisma/seed.ts
    apps/registry/tests/server-detail-composer.test.ts
    apps/web/src/lib/manifest.test.ts
    packages/cli/tests/bundles/{outdated,run}.test.ts
    packages/schemas/tests/manifest.test.ts
    packages/sdk-typescript/tests/{cache,mpak,validate}.test.ts

- packages/sdk-typescript/tests/mpak.test.ts: scoped biome-ignore on two
  `as any` casts that deliberately exercise the non-string env-var guard.
  Drops two stale `// eslint-disable-next-line` comments at the same sites.

- packages/sdk-typescript/tests/validate.test.ts: scoped biome-ignore on
  one `as any` cast against a deliberately malformed manifest.

- apps/web/src/index.css: scoped biome-ignore on a highlight.js token
  selector that descends in specificity — order is intentional for
  syntax-highlight token precedence.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mechanical reformat applied by `biome check --write [--unsafe]`. No
behavioral changes. Covers:

- Import organization (sorted, type-only imports promoted to `import type`)
- `useNodejsImportProtocol` autofix: `fs` -> `node:fs`, `path` -> `node:path`,
  etc., on all builtin module imports
- `useLiteralKeys` autofix: `record["KEY"]` -> `record.KEY` where the
  property name is a valid identifier (deadlock with tsc was resolved in
  the prior tsconfig commit)
- `noUnusedImports` autofix: drops two unused starlight imports in
  apps/docs/src/components/Header.astro
- Misc formatter passes: indentation, trailing commas, semicolons, line
  width 100, single quotes (matches prior prettier output).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The biome reformat stripped `import Default` from Header.astro because
its noUnusedImports pass parses only the .astro frontmatter, not the
template where <Default> is rendered. On a cold build the override
rendered empty (no nav, search, social, title, or content) — only a
warm dev cache hid it. Restore the import with a scoped biome-ignore
so biome keeps linting .astro without re-removing it.

Also document why packages/schemas/tsconfig.json pins `types: []`
(prevents tsc @types/* auto-discovery breaking on pnpm hoist changes).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@Ovaculos Ovaculos requested a review from mgoldsborough as a code owner May 24, 2026 16:34
@mgoldsborough mgoldsborough added the qa-reviewed QA review completed with no critical issues label May 25, 2026
@mgoldsborough mgoldsborough merged commit 66b44f5 into NimbleBrainInc:main May 27, 2026
3 checks passed
@Ovaculos Ovaculos deleted the chore/biome-migration branch May 28, 2026 03:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

qa-reviewed QA review completed with no critical issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Replace prettier + eslint with Biome in TypeScript packages

2 participants