Skip to content

chore: migrate from prettier+eslint to biome#118

Open
Ovaculos wants to merge 7 commits into
NimbleBrainInc:mainfrom
Ovaculos:chore/biome-migration
Open

chore: migrate from prettier+eslint to biome#118
Ovaculos wants to merge 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Replace prettier + eslint with Biome in TypeScript packages

1 participant